diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 8c7fadaa..aa3ebc3f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -10,7 +10,7 @@ assignees: '' Please read the [guide for contributing](https://github.com/Electroblob77/Wizardry/blob/1.12.2/CONTRIBUTING.md) before posting. Minecraft version: 1.12.2 [change as necessary] -Wizardry version: 4.2.11 [change as necessary] +Wizardry version: 4.3 [change as necessary] Environment: Singleplayer/LAN game/Server [delete as necessary] Issue details: [describe what you were doing when the issue occurred, what went wrong, what you expected to happen, etc.] diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 06de0905..536b7327 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -10,7 +10,7 @@ assignees: '' Please read the [guide for contributing](https://github.com/Electroblob77/Wizardry/blob/1.12.2/CONTRIBUTING.md) before posting. Minecraft version: 1.12.2 [change as necessary] -Wizardry version: 4.2.11 [change as necessary] +Wizardry version: 4.3 [change as necessary] Question details: [explain your question here] diff --git a/CREDITS.md b/CREDITS.md index 6b8d96dc..734f0bde 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -1,7 +1,7 @@ # Credits Electroblob's Wizardry -Version 4.2.11 +Version 4.3 For Minecraft 1.12.2 Designed, coded and textured by Electroblob @@ -30,13 +30,21 @@ In addition, I'd like to thank the following individuals for their contributions - Spanish: MadWrist, Alsentar - Mexican Spanish: MadWrist -- Russian: VilagVil, kellixon, bigenergy +- Russian: VilagVil, kellixon, bigenergy, MugGod2 - French: Hahdrim - Brazilian Portuguese: lorrampi -- Chinese: ZHENGLOC, dragon-evol, Hokorizero, TUsama, Determancer +- Chinese (Simplified): ZHENGLOC, dragon-evol, Hokorizero, TUsama, Determancer - Korean: shejery, rewi_wire - Polish: Trozuu - German: BirdyDragon -- Taiwanese: chesterccj305 +- Chinese (Traditional): chesterccj305 +- Hungarian: Bombadil -Lightning ray sound effect from OhhWowProductions +#### Sound Effects +- OhhWowProductions +- fredzed +- deleted_user_3277771 +- DiscoveryME +- OGSoundFX +- leosalom +- juskiddink \ No newline at end of file diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index 1aac3a8b..adeb1759 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -1,7 +1,7 @@ Please read the [guide for contributing](https://github.com/Electroblob77/Wizardry/blob/1.12.2/guide_for_contributing.md) before posting. Minecraft version: 1.12.2 [change as necessary] -Wizardry version: 4.2.11 [change as necessary] +Wizardry version: 4.3 [change as necessary] Environment: Singleplayer/LAN game/Server [delete as necessary] Issue details: [describe what you were doing when the issue occurred, what went wrong, what you expected to happen, etc.] diff --git a/build.gradle b/build.gradle index ad9193cc..cee2fefd 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.2.11" // There, it matches semver, happy now? +version = "4.3" // There, it matches semver, happy now? group= "electroblob.wizardry"// http://maven.apache.org/guides/mini/guide-naming-conventions.html archivesBaseName = "ElectroblobsWizardry" @@ -38,7 +38,7 @@ repositories { } minecraft { - version = "1.12.2-14.23.5.2814" + version = "1.12.2-14.23.5.2847" runDir = "run" // the mappings can be changed at any time, and must be in the following format. diff --git a/gradle.properties b/gradle.properties index a4e7eb14..c98a6360 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,6 +3,6 @@ org.gradle.jvmargs=-Xmx3G mc_version=1.12.2 mc_version_short=1.12 -jei_version=4.13.1.225 +jei_version=4.15.0.291 baubles_version=1.5.2 antique_atlas_version=4.6.3 \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/CommonProxy.java b/src/main/java/electroblob/wizardry/CommonProxy.java index 583929a0..cf738200 100644 --- a/src/main/java/electroblob/wizardry/CommonProxy.java +++ b/src/main/java/electroblob/wizardry/CommonProxy.java @@ -1,5 +1,7 @@ package electroblob.wizardry; +import electroblob.wizardry.block.BlockBookshelf; +import electroblob.wizardry.inventory.ContainerArcaneWorkbench; import electroblob.wizardry.item.ItemSpectralBow; import electroblob.wizardry.packet.*; import electroblob.wizardry.registry.WizardryItems; @@ -19,6 +21,7 @@ import net.minecraft.util.text.translation.I18n; import net.minecraft.world.World; import net.minecraftforge.common.config.Property; +import javax.annotation.Nullable; import java.util.List; import java.util.Set; @@ -41,6 +44,8 @@ public class CommonProxy { public void initialiseLayers(){} + public void initialiseAnimations(){} + public void registerKeyBindings(){} public net.minecraft.client.model.ModelBiped getWizardArmourModel(){ @@ -74,7 +79,7 @@ public class CommonProxy { // SECTION Items // =============================================================================================================== - public boolean shouldDisplayDiscovered(Spell spell, ItemStack stack){ + public boolean shouldDisplayDiscovered(Spell spell, @Nullable ItemStack stack){ return false; } @@ -100,6 +105,29 @@ public class CommonProxy { return ((ItemSpectralBow)WizardryItems.spectral_bow).getDefaultDurabilityForDisplay(stack); } + /** + * Translates the given key with no specified style. Client-side only; on the server this simply returns + * the given translation key. Useful whenever translation from common classes is required, e.g. item tooltips. + * @param key The unlocalised name to be translated. + * @param args The format arguments to pass into the translation, if any. + * @return The resulting translated text. + */ + public String translate(String key, Object... args){ + return translate(key, new Style(), args); + } + + /** + * Translates the given key and formats it with the given style. Client-side only; on the server this simply returns + * the given translation key. Useful whenever translation from common classes is required, e.g. item tooltips. + * @param key The unlocalised name to be translated. + * @param style The {@link Style} to use for the displayed text. + * @param args The format arguments to pass into the translation, if any. + * @return The resulting translated text. + */ + public String translate(String key, Style style, Object... args){ + return key; + } + /** Like {@link CommonProxy#addMultiLineDescription(List, String, Style, Object...)}, but style defaults to light grey. */ public void addMultiLineDescription(List tooltip, String key, Object... args){ this.addMultiLineDescription(tooltip, key, new Style().setColor(TextFormatting.GRAY), args); @@ -169,6 +197,12 @@ public class CommonProxy { */ public void playMovingSound(Entity entity, SoundEvent sound, SoundCategory category, float volume, float pitch, boolean repeat){} + /** + * Plays the spell charge-up sound at the given entity. + * @param entity The source of the sound + */ + public void playChargeupSound(EntityLivingBase entity){} + /** * Plays a continuous spell sound which moves with the given entity. * @@ -256,15 +290,44 @@ public class CommonProxy { public void loadShader(EntityPlayer player, ResourceLocation shader){} /** - * Gets the client side world using Minecraft.getMinecraft().world. Only to be called client side! Returns - * null on the server side. + * Gets the client-side world using {@code Minecraft.getMinecraft().world}. Only to be called client side! + * Returns null on the server side. */ public World getTheWorld(){ return null; } + /** + * Gets the client-side player using Minecraft.getMinecraft().player. Only to be called client side! Returns + * null on the server side. + */ + public EntityPlayer getThePlayer(){ + return null; + } + + /** + * Returns true if the game is being viewed from the perspective of the given entity and is set to first-person + * view. Always returns false on the server side. + */ + public boolean isFirstPerson(Entity entity){ + return false; + } + /** Returns an unmodifiable set of the string keys for all of the loaded spell HUD skins. */ public Set getSpellHUDSkins(){ return null; } + + /** Notifies nearby players of a bookshelf change, causing any lectern or arcane workbench GUI (client-side) or + * container (both sides) to refresh its linked bookshelves (does not send packets). */ + public void notifyBookshelfChange(World world, BlockPos pos){ + for(EntityPlayer player : world.playerEntities){ + if(player.getDistanceSq(pos) < BlockBookshelf.PLAYER_NOTIFY_RANGE * BlockBookshelf.PLAYER_NOTIFY_RANGE){ + if(player.openContainer instanceof ContainerArcaneWorkbench){ + ((ContainerArcaneWorkbench)player.openContainer).refreshBookshelfSlots(); + } + } + } + } + } \ 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 bd36babc..e015c61e 100644 --- a/src/main/java/electroblob/wizardry/Settings.java +++ b/src/main/java/electroblob/wizardry/Settings.java @@ -1,5 +1,7 @@ package electroblob.wizardry; +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.item.ItemArtefact; import electroblob.wizardry.packet.PacketSyncSettings; import electroblob.wizardry.packet.WizardryPacketHandler; import electroblob.wizardry.spell.Spell; @@ -9,9 +11,9 @@ import electroblob.wizardry.util.MagicDamage.DamageType; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.EntityList; import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.util.ResourceLocation; -import net.minecraft.util.text.translation.I18n; import net.minecraftforge.common.config.ConfigCategory; import net.minecraftforge.common.config.Configuration; import net.minecraftforge.common.config.Property; @@ -24,6 +26,7 @@ import org.apache.commons.lang3.tuple.Pair; import java.io.File; import java.util.*; import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * Singleton class which deals with everything related to wizardry's config file. To access individual settings, use @@ -53,14 +56,19 @@ import java.util.regex.Pattern; */ // 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 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 difficulty config category. */ + public static final String DIFFICULTY_CATEGORY = "difficulty"; + /** The unlocalised name of the tweaks config category. */ + public static final String TWEAKS_CATEGORY = "tweaks"; /** The unlocalised name of the spells config category. */ public static final String SPELLS_CATEGORY = "spells"; + /** The unlocalised name of the artefacts config category. */ + public static final String ARTEFACTS_CATEGORY = "artefacts"; /** The unlocalised name of the resistances config category. */ public static final String RESISTANCES_CATEGORY = "resistances"; /** The unlocalised name of the client config category. */ @@ -97,37 +105,51 @@ public final class Settings { public int towerRarity = 700; /** [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")}; + 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")}; + 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 = 600; /** [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")}; + 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 = 1100; /** [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")}; + 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] List of dimension ids in which to generate library ruins. */ + public int[] libraryDimensions = {0}; + /** [Server-only] The rarity of library ruins, used by the world generator. Larger numbers are rarer. */ + public int libraryRarity = 900; + /** [Server-only] List of structure file locations for surface library ruins. */ + public ResourceLocation[] libraryFiles = {new ResourceLocation(Wizardry.MODID, "library_ruins_0"), + new ResourceLocation(Wizardry.MODID, "library_ruins_1"), + new ResourceLocation(Wizardry.MODID, "library_ruins_2"), + new ResourceLocation(Wizardry.MODID, "library_ruins_3")}; + /** [Server-only] List of structure file locations for underground library ruins. */ + public ResourceLocation[] undergroundLibraryFiles = {new ResourceLocation(Wizardry.MODID, "underground_library_ruins_0"), + new ResourceLocation(Wizardry.MODID, "underground_library_ruins_1"), + new ResourceLocation(Wizardry.MODID, "underground_library_ruins_2"), + new ResourceLocation(Wizardry.MODID, "underground_library_ruins_3")}; /** [Server-only] List of solid blocks (usually trees) which are ignored by the structure generators. */ public Pair[] treeBlocks = parseItemMetaStrings(DEFAULT_TREE_BLOCKS); /** [Server-only] The chance for wizard towers to generate with an evil wizard and chest inside. */ @@ -147,8 +169,8 @@ public final class Settings { 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"); + "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. @@ -166,11 +188,12 @@ public final class Settings { public boolean playersMoveEachOther = true; /** [Server-only] Whether spells cast by players can destroy blocks in the world. */ public boolean playerBlockDamage = true; + /** [Server-only] Whether spells cast by dispensers can destroy blocks in the world. */ + public boolean dispenserBlockDamage = 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 distance-based fall damage calculation with an equivalent, - * velocity-based one. */ - public boolean replaceVanillaFallDamage = true; + /** [Server-only] Whether to tweak the blindness effect to reduce follow distance when used on non-players. */ + public boolean blindnessTweak = true; /** [Server-only] Whether using bonemeal on grass blocks has a chance to grow crystal flowers. */ public boolean bonemealGrowsCrystalFlowers = true; /** @@ -237,18 +260,24 @@ public final class Settings { public String[] damageSourceBlacklist = {}; /** [Server-only] Whether to print compatibility warnings to the console. */ public boolean compatibilityWarnings = true; + // TODO: Should these really be server-only? /** [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 JEI integration features are enabled. */ + public boolean jeiIntegration = true; /** [Server-only] Whether Antique Atlas integration features are enabled. */ public boolean antiqueAtlasIntegration = true; + // These are server-only because that's where markers are stored /** [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; + /** [Server-only] Whether global markers for library ruins are added to antique atlases. */ + public boolean autoLibraryMarkers = true; + /** [Server-only] Whether global markers for underground library ruins are added to antique atlases. */ + public boolean autoUndergroundLibraryMarkers = false; // 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. @@ -270,10 +299,46 @@ public final class Settings { * effect. */ public boolean slowTimeAffectsPlayers = true; + /** [Synchronised] Whether passive mobs should count as allies, i.e. they should not be damaged indirectly by spells */ + public boolean passiveMobsAreAllies = false; /** [Synchronised] Whether to replace Minecraft's own fireballs with wizardry fireballs. */ public boolean replaceVanillaFireballs = true; + /** + * [Synchronised] Whether to replace Minecraft's distance-based fall damage calculation with an equivalent, + * velocity-based one. + */ + public boolean replaceVanillaFallDamage = true; /** [Synchronised] Chance of 'misreading' an undiscovered spell and triggering a forfeit instead. */ public double forfeitChance = 0.2; + /** [Synchronised] Progression requirements for upgrading a wand to each tier. */ + public int[] progressionRequirements = {1500, 3500, 6000}; + /** + * [Synchronised] The maximum number of blocks a bookshelf can be from an arcane workbench or lectern to be + * able to link to it. + */ + public int bookshelfSearchRadius = 4; + /** + * [Synchronised] List of registry names of blocks that count as bookshelves for the arcane workbench and + * lectern. + */ + public Pair[] bookshelfBlocks = parseItemMetaStrings( + Wizardry.MODID + ":oak_bookshelf", + Wizardry.MODID + ":spruce_bookshelf", + Wizardry.MODID + ":birch_bookshelf", + Wizardry.MODID + ":jungle_bookshelf", + Wizardry.MODID + ":acacia_bookshelf", + Wizardry.MODID + ":dark_oak_bookshelf" + ); + /** [Synchronised] List of registry names of items that can be placed in a bookshelf. */ + public Pair[] bookItems = parseItemMetaStrings(); + + /** + * [Synchronised*] The element to render for donation perks, or null if it should not be rendered. + *

+ * * Unlike the other options, this is synchronised client -> server, then the data for all players is sent + * back to every client when they join the game, or whenever it changes. + */ + public Element donationPerkElement = Element.MAGIC; // 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. @@ -304,6 +369,12 @@ public final class Settings { public boolean screenShake = true; /** [Client-only] Whether to use the screen blink effect for teleportation spells. */ public boolean blinkEffect = true; + /** [Client-only] Whether to use spellcasting animations for players. */ + public boolean spellcastingAnimations = true; + /** [Client-only] Whether to show the spell HUD when holding a wand. */ + public boolean showSpellHUD = true; + /** [Client-only] Whether to show the charge meter when charging up a spell. */ + public boolean showChargeMeter = true; /** [Client-only] The position of the spell HUD. */ public GuiPosition spellHUDPosition = GuiPosition.BOTTOM_LEFT; @@ -326,7 +397,7 @@ public final class Settings { /** 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; @@ -380,8 +451,10 @@ public final class Settings { Wizardry.logger.info("Setting up main config"); - setupGeneralConfig(); + setupGameplayConfig(); + setupDifficultyConfig(); setupWorldgenConfig(); + setupTweaksConfig(); if(event.getSide() == Side.CLIENT) setupClientConfig(); // Server has no spell HUD skins so this would crash it setupCommandsConfig(); setupCompatibilityConfig(); @@ -397,11 +470,12 @@ public final class Settings { void initConfigExtras(){ Wizardry.logger.info("Setting up spells config for " + Spell.getTotalSpellCount() + " spells"); - setupSpellsConfig(); - Wizardry.logger.info("Setting up resistances config"); + Wizardry.logger.info("Setting up artefacts config"); + setupArtefactsConfig(); + Wizardry.logger.info("Setting up resistances config"); setupResistancesConfig(); config.save(); @@ -412,12 +486,15 @@ public final class Settings { Wizardry.logger.info("Saving in-game config changes"); - setupGeneralConfig(); + setupGameplayConfig(); + setupDifficultyConfig(); setupWorldgenConfig(); + setupTweaksConfig(); setupClientConfig(); setupCommandsConfig(); setupCompatibilityConfig(); setupSpellsConfig(); + setupArtefactsConfig(); setupResistancesConfig(); config.save(); @@ -443,16 +520,36 @@ public final class Settings { for(Spell spell : Spell.getAllSpells()){ property = config.get(SPELLS_CATEGORY, spell.getRegistryName().toString(), true, - I18n.translateToLocal("spell." + spell.getUnlocalisedName() + ".desc")); + "Set to false to disable this spell"); // 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()); + spell.setEnabled(property.getBoolean(true)); } } - private void setupGeneralConfig(){ + private void setupArtefactsConfig(){ + + config.addCustomCategoryComment(ARTEFACTS_CATEGORY, + "Set an item to false to disable it. Disabled items will still appear in-game but will have no effect when worn. It is also advisable to remove disabled items from wizardry's (and addons') loot tables. Disable an item if it is causing problems, conflicts with another mod or creates an unintended exploit."); + + Property property; + + for(Item item : Item.REGISTRY){ + if(item instanceof ItemArtefact){ + property = config.get(ARTEFACTS_CATEGORY, item.getRegistryName().toString(), true, + "Set to false to disable this item"); + // Uses the same config key as the item name, because - well, that's what it's called! + property.setLanguageKey(item.getTranslationKey() + ".name"); + Wizardry.proxy.setToNamedBooleanEntry(property); + ((ItemArtefact)item).setEnabled(property.getBoolean()); + } + } + + } + + private void setupGameplayConfig(){ // This trick is borrowed from forge; it sorts the config options into the order you want them. List propOrder = new ArrayList<>(); @@ -461,32 +558,18 @@ public final class Settings { config.addCustomCategoryComment(GAMEPLAY_CATEGORY, "Global settings that affect game mechanics. In multiplayer, the server/LAN host settings will apply. Please note that changing some of these settings may make the mod very difficult to play."); - 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"); + property = config.get(GAMEPLAY_CATEGORY, "playerBlockDamage", true, + "Whether spells cast by players can destroy blocks in the world. Wizardry makes every attempt to respect protection mods and plugins, but cannot guarantee it will work in all cases for every mod. If you need absolutely watertight anti-grief, disable this setting. (N.B. This setting only affects players. To prevent mobs from destroying blocks with magic, use the mobGriefing gamerule.)"); + property.setLanguageKey("config." + Wizardry.MODID + ".player_block_damage"); Wizardry.proxy.setToNamedBooleanEntry(property); - property.setRequiresWorldRestart(true); - discoveryMode = property.getBoolean(); + playerBlockDamage = property.getBoolean(); propOrder.add(property.getName()); - 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"); + property = config.get(GAMEPLAY_CATEGORY, "dispenserBlockDamage", true, + "Whether spells cast by dispensers can destroy blocks in the world. Wizardry makes every attempt to respect protection mods and plugins, but cannot guarantee it will work in all cases for every mod. If you need absolutely watertight anti-grief, disable this setting."); + property.setLanguageKey("config." + Wizardry.MODID + ".dispenser_block_damage"); 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 = 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(); + dispenserBlockDamage = property.getBoolean(); propOrder.add(property.getName()); property = config.get(GAMEPLAY_CATEGORY, "playersMoveEachOther", true, @@ -496,13 +579,6 @@ public final class Settings { playersMoveEachOther = property.getBoolean(); propOrder.add(property.getName()); - 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"); @@ -524,34 +600,22 @@ public final class Settings { 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"); + 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); - 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(); + property.setRequiresWorldRestart(true); + slowTimeAffectsPlayers = 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); + property.setRequiresWorldRestart(true); 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, "bonemealGrowsCrystalFlowers", true, "Whether using bonemeal on grass blocks has a chance to grow crystal flowers."); property.setLanguageKey("config." + Wizardry.MODID + ".bonemeal_grows_crystal_flowers"); @@ -559,33 +623,87 @@ public final class Settings { bonemealGrowsCrystalFlowers = 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); + config.setCategoryPropertyOrder(GAMEPLAY_CATEGORY, propOrder); + + } + + private void setupDifficultyConfig(){ + + List propOrder = new ArrayList<>(); + + Property property; + + config.addCustomCategoryComment(DIFFICULTY_CATEGORY, "Settings that affect the mod's difficulty. In multiplayer, the server/LAN host settings will apply."); + + property = config.get(DIFFICULTY_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, "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); + property = config.get(DIFFICULTY_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, "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(); + property = config.get(DIFFICULTY_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 = FriendlyFire.fromName(property.getString()); 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); + property = config.get(DIFFICULTY_CATEGORY, "passiveMobsAreAllies", false, + "Whether passive mobs should count as allies, i.e. they should not be damaged indirectly by spells."); + property.setLanguageKey("config." + Wizardry.MODID + ".passive_mobs_are_allies"); + Wizardry.proxy.setToNamedBooleanEntry(property); + property.setRequiresWorldRestart(true); + passiveMobsAreAllies = property.getBoolean(); propOrder.add(property.getName()); - property = config.get(GAMEPLAY_CATEGORY, "evilWizardSpawnRate", 3, + property = config.get(DIFFICULTY_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(DIFFICULTY_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); + property.setRequiresWorldRestart(true); + forfeitChance = property.getDouble(); + propOrder.add(property.getName()); + + property = config.get(DIFFICULTY_CATEGORY, "progressionRequirements", new int[]{1500, 3500, 6000}, + "The amount of progression required to upgrade a wand to each tier (apprentice, advanced and master respectively)."); + property.setLanguageKey("config." + Wizardry.MODID + ".progression_requirements"); + property.setRequiresWorldRestart(true); + progressionRequirements = 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(DIFFICULTY_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(DIFFICULTY_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(DIFFICULTY_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"); @@ -594,7 +712,7 @@ public final class Settings { evilWizardSpawnRate = property.getInt(); propOrder.add(property.getName()); - property = config.get(GAMEPLAY_CATEGORY, "iceWraithSpawnRate", 3, + property = config.get(DIFFICULTY_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"); @@ -603,7 +721,7 @@ public final class Settings { iceWraithSpawnRate = property.getInt(); propOrder.add(property.getName()); - property = config.get(GAMEPLAY_CATEGORY, "lightningWraithSpawnRate", 1, + property = config.get(DIFFICULTY_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"); @@ -612,79 +730,132 @@ public final class Settings { 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(); + config.setCategoryPropertyOrder(DIFFICULTY_CATEGORY, propOrder); + + } + + private void setupTweaksConfig(){ + + List propOrder = new ArrayList<>(); + + Property property; + + config.addCustomCategoryComment(TWEAKS_CATEGORY, "Assorted settings for tweaking the mod's behaviour. In multiplayer, the server/LAN host settings will apply."); + + property = config.get(TWEAKS_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); + property.setRequiresWorldRestart(true); + replaceVanillaFireballs = property.getBoolean(); 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(); + property = config.get(TWEAKS_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); + property.setRequiresWorldRestart(true); + replaceVanillaFallDamage = property.getBoolean(); 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(); + property = config.get(TWEAKS_CATEGORY, "blindnessTweak", true, + "Whether to tweak the blindness effect to reduce follow distance when used on non-players. This automatically disables itself in favour of Potion Core's implementation if installed."); + property.setLanguageKey("config." + Wizardry.MODID + ".blindness_tweak"); + Wizardry.proxy.setToNamedBooleanEntry(property); + blindnessTweak = property.getBoolean(); propOrder.add(property.getName()); - property = config.get(GAMEPLAY_CATEGORY, "summonedCreatureTargetsWhitelist", new String[0], + property = config.get(TWEAKS_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(TWEAKS_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(TWEAKS_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(TWEAKS_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(TWEAKS_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"}, + property = config.get(TWEAKS_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[]{}, + property = config.get(TWEAKS_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. 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); mindControlTargetsBlacklist = getResourceLocationList(property); propOrder.add(property.getName()); - property = config.get(GAMEPLAY_CATEGORY, "pocketFurnaceItemBlacklist", new String[]{"cobblestone", "netherrack"}, + property = config.get(TWEAKS_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 = parseItemMetaStrings(property.getStringList()); 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 = config.get(TWEAKS_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 = parseItemMetaStrings(property.getStringList()); 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 = config.get(TWEAKS_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 = parseItemMetaStrings(property.getStringList()); 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 = config.get(TWEAKS_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 = parseItemMetaStrings(property.getStringList()); 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.", + property = config.get(TWEAKS_CATEGORY, "bookshelfBlocks", new String[]{Wizardry.MODID + ":oak_bookshelf", Wizardry.MODID + ":spruce_bookshelf", Wizardry.MODID + ":birch_bookshelf", Wizardry.MODID + ":jungle_bookshelf", Wizardry.MODID + ":acacia_bookshelf", Wizardry.MODID + ":dark_oak_bookshelf"}, "List of registry names of blocks that count as bookshelves for the arcane workbench and lectern. Block names are not case sensitive. For mod blocks, prefix with the mod ID (e.g. " + Wizardry.MODID + ":oak_bookshelf)."); + property.setLanguageKey("config." + Wizardry.MODID + ".bookshelf_blocks"); + property.setRequiresWorldRestart(true); + bookshelfBlocks = parseItemMetaStrings(property.getStringList()); + propOrder.add(property.getName()); + + property = config.get(TWEAKS_CATEGORY, "bookItems", new String[0], "List of registry names of items which can be placed in a bookshelf, in addition to the defaults. Item names are not case sensitive. For mod items, prefix with the mod ID (e.g. thaumcraft:thaumonomicon)."); + property.setLanguageKey("config." + Wizardry.MODID + ".book_items"); + property.setRequiresWorldRestart(true); + bookItems = parseItemMetaStrings(property.getStringList()); + propOrder.add(property.getName()); + + property = config.get(TWEAKS_CATEGORY, "bookshelfSearchRadius", 4, + "The maximum number of blocks a bookshelf can be from an arcane workbench or lectern to be able to link to it.", + 1, 10); + property.setLanguageKey("config." + Wizardry.MODID + ".bookshelf_search_radius"); + Wizardry.proxy.setToNumberSliderEntry(property); + property.setRequiresWorldRestart(true); + bookshelfSearchRadius = property.getInt(); + propOrder.add(property.getName()); + + property = config.get(TWEAKS_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-z0-9:_]+ [0-9]+")); property.setLanguageKey("config." + Wizardry.MODID + ".currency_items"); - property.setRequiresWorldRestart(true); propOrder.add(property.getName()); currencyItems = new HashMap<>(); for(String string : property.getStringList()){ @@ -694,14 +865,14 @@ public final class Settings { Wizardry.logger.warn("Invalid entry in currency items: {}", string); continue; // Ignore invalid entries, the pattern above should ensure this never happens } - try { + try{ currencyItems.put(parseItemMetaString(args[0]), Integer.parseInt(args[1])); }catch(NumberFormatException e){ Wizardry.logger.warn("Invalid integer in currency items: {}", args[1]); } } - config.setCategoryPropertyOrder(GAMEPLAY_CATEGORY, propOrder); + config.setCategoryPropertyOrder(TWEAKS_CATEGORY, propOrder); } @@ -797,7 +968,34 @@ public final class Settings { shrineFiles = getResourceLocationList(property); propOrder.add(property.getName()); - property = config.get(GAMEPLAY_CATEGORY, "treeBlocks", DEFAULT_TREE_BLOCKS, "List of registry names of blocks which can be overwritten by wizardry's structure generators, affecting both fast and fancy structure generation. Most tree blocks and other foliage should work automatically, but those that don't can be added manually here. Block names are not case sensitive. For mod blocks, prefix with the mod ID (e.g. dynamictrees:oakbranch)."); + property = config.get(WORLDGEN_CATEGORY, "libraryDimensions", new int[]{0}, "List of dimension ids in which library ruins will generate. Remove all dimensions to disable library ruins completely."); + property.setLanguageKey("config." + Wizardry.MODID + ".library_dimensions"); + property.setRequiresWorldRestart(true); + libraryDimensions = property.getIntList(); + propOrder.add(property.getName()); + + property = config.get(WORLDGEN_CATEGORY, "libraryRarity", 900, "Rarity of library ruins. 1 in this many chunks will contain a library ruin, meaning higher numbers are rarer.", 20, 5000); + property.setLanguageKey("config." + Wizardry.MODID + ".library_rarity"); + property.setRequiresWorldRestart(true); + Wizardry.proxy.setToNumberSliderEntry(property); + libraryRarity = property.getInt(); + propOrder.add(property.getName()); + + property = config.get(WORLDGEN_CATEGORY, "libraryFiles", new String[]{Wizardry.MODID + ":library_ruins_0", Wizardry.MODID + ":library_ruins_1", Wizardry.MODID + ":library_ruins_2", Wizardry.MODID + ":library_ruins_3"}, + "List of structure file locations for surface library ruins. One of these files will be randomly selected each time a surface library ruin 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. This list should not be empty; to disable library ruins, use the library dimensions setting."); + property.setLanguageKey("config." + Wizardry.MODID + ".library_files"); + property.setRequiresWorldRestart(true); + libraryFiles = getResourceLocationList(property); + propOrder.add(property.getName()); + + property = config.get(WORLDGEN_CATEGORY, "undergroundLibraryFiles", new String[]{Wizardry.MODID + ":underground_library_ruins_0", Wizardry.MODID + ":underground_library_ruins_1", Wizardry.MODID + ":underground_library_ruins_2", Wizardry.MODID + ":underground_library_ruins_3"}, + "List of structure file locations for underground library ruins. One of these files will be randomly selected each time an underground library ruin 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. This list should not be empty; to disable library ruins, use the library dimensions setting."); + property.setLanguageKey("config." + Wizardry.MODID + ".underground_library_files"); + property.setRequiresWorldRestart(true); + undergroundLibraryFiles = getResourceLocationList(property); + propOrder.add(property.getName()); + + property = config.get(WORLDGEN_CATEGORY, "treeBlocks", DEFAULT_TREE_BLOCKS, "List of registry names of blocks which can be overwritten by wizardry's structure generators, affecting both fast and fancy structure generation. Most tree blocks and other foliage should work automatically, but those that don't can be added manually here. Block names are not case sensitive. For mod blocks, prefix with the mod ID (e.g. dynamictrees:oakbranch)."); property.setLanguageKey("config." + Wizardry.MODID + ".tree_blocks"); property.setRequiresWorldRestart(true); treeBlocks = parseItemMetaStrings(property.getStringList()); @@ -847,6 +1045,20 @@ public final class Settings { reverseScrollDirection = property.getBoolean(); propOrder.add(property.getName()); + property = config.get(CLIENT_CATEGORY, "showSpellHUD", true, "Whether to show the spell HUD in the corner of the screen when holding a wand."); + property.setLanguageKey("config." + Wizardry.MODID + ".show_spell_hud"); + property.setRequiresWorldRestart(false); + Wizardry.proxy.setToNamedBooleanEntry(property); + showSpellHUD = property.getBoolean(); + propOrder.add(property.getName()); + + property = config.get(CLIENT_CATEGORY, "showChargeMeter", true, "Whether to show the spell charge-up meter around the crosshairs when charging up a spell."); + property.setLanguageKey("config." + Wizardry.MODID + ".show_charge_meter"); + property.setRequiresWorldRestart(false); + Wizardry.proxy.setToNamedBooleanEntry(property); + showChargeMeter = property.getBoolean(); + propOrder.add(property.getName()); + property = config.get(CLIENT_CATEGORY, "spellHUDPosition", GuiPosition.BOTTOM_LEFT.name, "The position of the spell HUD.", GuiPosition.names); property.setLanguageKey("config." + Wizardry.MODID + ".spell_hud_position"); spellHUDPosition = GuiPosition.fromName(property.getString()); @@ -894,6 +1106,20 @@ public final class Settings { blinkEffect = property.getBoolean(); propOrder.add(property.getName()); + property = config.get(CLIENT_CATEGORY, "spellcastingAnimations", true, "Whether to use custom animations for players casting spells. Disable this option if it conflicts with other mods that add player animations."); + property.setLanguageKey("config." + Wizardry.MODID + ".spellcasting_animations"); + property.setRequiresMcRestart(true); + Wizardry.proxy.setToNamedBooleanEntry(property); + spellcastingAnimations = property.getBoolean(); + propOrder.add(property.getName()); + + List elementNames = Arrays.stream(Element.values()).map(Element::getName).collect(Collectors.toList()); + elementNames.add(""); + property = config.get(CLIENT_CATEGORY, "donationPerkElement", Element.MAGIC.getName(), "The element of the flying companion orb rendered on donor and contributor players, leave empty to disable the effect. If you're one of them, this setting will change how all players see you (if not, it won't do anything).", elementNames.toArray(new String[0])); + property.setLanguageKey("config." + Wizardry.MODID + ".donation_perk_element"); + donationPerkElement = Element.fromName(property.getString(), null); // Fallback to null for no element + propOrder.add(property.getName()); + config.setCategoryPropertyOrder(CLIENT_CATEGORY, propOrder); } @@ -1050,13 +1276,13 @@ public final class Settings { 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, "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."); @@ -1090,6 +1316,22 @@ public final class Settings { autoShrineMarkers = property.getBoolean(); propOrder.add(property.getName()); + property = config.get(COMPATIBILITY_CATEGORY, "autoPlaceLibraryMarkers", true, + "Controls whether wizardry automatically places antique atlas markers at the locations of library ruins."); + property.setLanguageKey("config." + Wizardry.MODID + ".auto_place_library_markers"); + property.setRequiresMcRestart(true); + Wizardry.proxy.setToNamedBooleanEntry(property); + autoLibraryMarkers = property.getBoolean(); + propOrder.add(property.getName()); + + property = config.get(COMPATIBILITY_CATEGORY, "autoPlaceUndergroundLibraryMarkers", false, + "Controls whether wizardry automatically places antique atlas markers at the locations of underground library ruins."); + property.setLanguageKey("config." + Wizardry.MODID + ".auto_place_underground_library_markers"); + property.setRequiresMcRestart(true); + Wizardry.proxy.setToNamedBooleanEntry(property); + autoUndergroundLibraryMarkers = property.getBoolean(); + propOrder.add(property.getName()); + config.setCategoryPropertyOrder(COMPATIBILITY_CATEGORY, propOrder); } @@ -1104,7 +1346,7 @@ public final class Settings { return Arrays.stream(strings).map(s -> new ResourceLocation(s.toLowerCase(Locale.ROOT).trim())).toArray(ResourceLocation[]::new); } - /** Applies {@link Settings#parseItemMetaString(String)} to each input string and returns and array of the resulting + /** Applies {@link Settings#parseItemMetaString(String)} to each input string and returns an array of the resulting * {@link Pair}s. */ @SuppressWarnings("unchecked") // Shut up java public static Pair[] parseItemMetaStrings(String... strings){ @@ -1132,14 +1374,34 @@ public final class Settings { return Pair.of(new ResourceLocation(item), meta); } + /** + * Checks a metadata-sensitive list option (see {@link Settings#parseItemMetaStrings(String...)} for the given block. + * @param array The config option to check + * @param block The block state to search for + * @return True if the given array contains an entry that matches the given block, false if not. + */ public static boolean containsMetaBlock(Pair[] array, IBlockState block){ return containsMetaThing(array, block.getBlock().getRegistryName(), (short)block.getBlock().getMetaFromState(block)); } + /** + * Checks a metadata-sensitive list option (see {@link Settings#parseItemMetaStrings(String...)} for the given item. + * @param array The config option to check + * @param stack An item stack with the item and metadata to search for + * @return True if the given array contains an entry that matches the given stack, false if not. + */ public static boolean containsMetaItem(Pair[] array, ItemStack stack){ return containsMetaThing(array, stack.getItem().getRegistryName(), (short)stack.getMetadata()); } + /** + * Checks a metadata-sensitive list option (see {@link Settings#parseItemMetaStrings(String...)} for the given + * id/metadata pair. + * @param array The config option to check + * @param id The id to search for + * @param metadata The metadata value to search for + * @return True if the given array contains the given id/metadata pair, or the given id paired with the wildcard value. + */ public static boolean containsMetaThing(Pair[] array, ResourceLocation id, short metadata){ return Arrays.asList(array).contains(Pair.of(id, metadata)) || Arrays.asList(array).contains(Pair.of(id, (short)OreDictionary.WILDCARD_VALUE)); } diff --git a/src/main/java/electroblob/wizardry/Wizardry.java b/src/main/java/electroblob/wizardry/Wizardry.java index 946bf090..30e192b3 100644 --- a/src/main/java/electroblob/wizardry/Wizardry.java +++ b/src/main/java/electroblob/wizardry/Wizardry.java @@ -1,5 +1,6 @@ package electroblob.wizardry; +import electroblob.wizardry.block.BlockBookshelf; import electroblob.wizardry.command.CommandCastSpell; import electroblob.wizardry.command.CommandDiscoverSpell; import electroblob.wizardry.command.CommandSetAlly; @@ -8,11 +9,14 @@ 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.inventory.ContainerBookshelf; +import electroblob.wizardry.misc.DonationPerksHandler; import electroblob.wizardry.misc.Forfeit; import electroblob.wizardry.packet.WizardryPacketHandler; import electroblob.wizardry.registry.*; import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.SpellProperties; +import electroblob.wizardry.util.WandHelper; import electroblob.wizardry.worldgen.*; import net.minecraft.item.Item; import net.minecraft.util.SoundCategory; @@ -46,7 +50,8 @@ import java.util.Calendar; */ @Mod(modid = Wizardry.MODID, name = Wizardry.NAME, version = Wizardry.VERSION, acceptedMinecraftVersions = "[1.12.2]", - guiFactory = "electroblob.wizardry.WizardryGuiFactory", dependencies = "required-after:forge@[14.23.5.2814,);after:antiqueatlas@[4.6,)") + guiFactory = "electroblob.wizardry.WizardryGuiFactory", + dependencies = "required-after:forge@[14.23.5.2847,);after:jei@[4.15.0,);after:baubles@[1.5.2,);after:antiqueatlas@[4.6,)") public class Wizardry { @@ -64,7 +69,7 @@ public class Wizardry { * 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.2.11"; + public static final String VERSION = "4.3"; // IDEA: Triggering of inbuilt Forge events in relevant places? // IDEA: Abstract the vanilla particles behind the particle builder @@ -122,6 +127,7 @@ public class Wizardry { WizardryLoot.register(); WizardryAdvancementTriggers.register(); Forfeit.register(); + BlockBookshelf.registerStandardBookModelTextures(); // Client-side stuff (via proxies) proxy.registerRenderers(); @@ -150,6 +156,8 @@ public class Wizardry { GameRegistry.registerWorldGenerator(new WorldGenWizardTower(), 20); GameRegistry.registerWorldGenerator(new WorldGenObelisk(), 20); GameRegistry.registerWorldGenerator(new WorldGenShrine(), 20); + GameRegistry.registerWorldGenerator(new WorldGenLibraryRuins(), 20); + GameRegistry.registerWorldGenerator(new WorldGenUndergroundLibraryRuins(), 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 @@ -158,9 +166,13 @@ public class Wizardry { WizardryPacketHandler.initPackets(); // Post-registry extras + BlockBookshelf.compileBookModelTextures(); + ContainerBookshelf.initDefaultBookItems(); WizardryItems.populateWandMap(); WizardryItems.populateArmourMap(); WizardryItems.registerDispenseBehaviours(); + WizardryItems.registerBannerPatterns(); + WandHelper.populateUpgradeMap(); Spell.registry.forEach(Spell::init); SpellProperties.init(); @@ -173,6 +185,7 @@ public class Wizardry { @EventHandler public void postInit(FMLPostInitializationEvent event){ proxy.initialiseLayers(); + proxy.initialiseAnimations(); } @EventHandler @@ -187,7 +200,12 @@ public class Wizardry { public void onConfigChanged(net.minecraftforge.fml.client.event.ConfigChangedEvent.OnConfigChangedEvent event){ if(event.getModID().equals(Wizardry.MODID)){ settings.saveConfigChanges(); - // All of the synchronised settings require a world restart anyway so this doesn't need syncing. + // All of the synchronised settings require a world restart anyway so don't need syncing, except for the + // donor perks which have special behaviour (since we have to update everyone when a donor logs in anyway, + // we might as well reuse the packet and let them change the setting mid-game) + if(event.isWorldRunning() && DonationPerksHandler.isDonor(proxy.getThePlayer())){ + DonationPerksHandler.sendToServer(proxy.getThePlayer()); + } } } diff --git a/src/main/java/electroblob/wizardry/WizardryEventHandler.java b/src/main/java/electroblob/wizardry/WizardryEventHandler.java index bdac3b8b..5e0d21b6 100644 --- a/src/main/java/electroblob/wizardry/WizardryEventHandler.java +++ b/src/main/java/electroblob/wizardry/WizardryEventHandler.java @@ -26,6 +26,7 @@ import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.entity.projectile.EntityArrow; +import net.minecraft.init.MobEffects; import net.minecraft.item.ItemStack; import net.minecraft.potion.PotionEffect; import net.minecraft.util.EnumHand; @@ -83,6 +84,7 @@ public final class WizardryEventHandler { Wizardry.settings.sync((EntityPlayerMP)event.player); syncAdvancements((EntityPlayerMP)event.player, false); } + Spell.syncProperties(event.player); } @@ -173,7 +175,7 @@ public final class WizardryEventHandler { && 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, WizardrySounds.MISC_DISCOVER_SPELL, 1.25f, 1); + EntityUtils.playSoundAtPlayer(player, WizardrySounds.MISC_DISCOVER_SPELL, 1.25f, 1); player.sendMessage(new TextComponentTranslation("spell.discover", event.getSpell().getNameForTranslationFormatted())); } @@ -192,16 +194,33 @@ public final class WizardryEventHandler { @SubscribeEvent public static void onLivingSetAttackTargetEvent(LivingSetAttackTargetEvent event){ - if(event.getTarget() != null && event.getEntityLiving() instanceof EntityLiving - && event.getTarget().isPotionActive(WizardryPotions.muffle)){ + if(event.getEntityLiving() instanceof EntityLiving && event.getTarget() != null){ - 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){ + // Muffle + if(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); + } + } + + // Mirage + if(event.getTarget().isPotionActive(WizardryPotions.mirage)){ + // Can't find players under mirage at all! (Pretty sure this excludes revenge-targeting) + ((EntityLiving)event.getEntityLiving()).setAttackTarget(null); + } + + // Blindness tweak + // I'm not going as far as potion core's implementation, this is just so it does *something* to mobs + if(event.getEntityLiving().isPotionActive(MobEffects.BLINDNESS) && !Loader.isModLoaded("potioncore") + && Wizardry.settings.blindnessTweak && event.getTarget().getDistanceSq(event.getEntity()) > 3.5 * 3.5){ + // Can't detect anything more than 3.5 blocks away (roughly the player's view distance when blinded) ((EntityLiving)event.getEntityLiving()).setAttackTarget(null); } } @@ -269,7 +288,7 @@ public final class WizardryEventHandler { .target(attacker).spawn(world); ParticleBuilder.spawnShockParticles(world, attacker.posX, - attacker.getEntityBoundingBox().minY + attacker.height / 2, attacker.posZ); + attacker.posY + attacker.height / 2, attacker.posZ); } DamageSafetyChecker.attackEntitySafely(attacker, MagicDamage.causeDirectMagicDamage(event.getEntityLiving(), @@ -284,6 +303,13 @@ public final class WizardryEventHandler { @SubscribeEvent(priority = EventPriority.LOW) // Again, we don't want these effects if the event is cancelled public static void onLivingHurtEvent(LivingHurtEvent event){ + // Ward + PotionEffect effect = event.getEntityLiving().getActivePotionEffect(WizardryPotions.ward); + + if(effect != null && event.getSource().isMagicDamage()){ + event.setAmount(event.getAmount() * Math.max(0, 1 - 0.4f * effect.getAmplifier())); // Resistance is 20% + } + // Flaming and freezing swords if(event.getSource().getTrueSource() instanceof EntityLivingBase){ @@ -331,14 +357,6 @@ public final class WizardryEventHandler { @SubscribeEvent public static void onLivingUpdateEvent(LivingUpdateEvent event){ - // 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){ // Client-side continuous spell casting for NPCs @@ -358,10 +376,10 @@ public final class WizardryEventHandler { // 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); + + ((ISpellCaster)event.getEntity()).setSpellCounter(count + 1); } } - - ((ISpellCaster)event.getEntity()).setSpellCounter(count + 1); } } } @@ -389,7 +407,7 @@ public final class WizardryEventHandler { } } - for(ItemStack stack : WizardryUtilities.getPrioritisedHotbarAndOffhand(player)){ + for(ItemStack stack : InventoryUtils.getPrioritisedHotbarAndOffhand(player)){ if(stack.getItem() instanceof IManaStoringItem && !((IManaStoringItem)stack.getItem()).isManaFull(stack) && WandHelper.getUpgradeLevel(stack, WizardryItems.siphon_upgrade) > 0){ diff --git a/src/main/java/electroblob/wizardry/WizardryGuiHandler.java b/src/main/java/electroblob/wizardry/WizardryGuiHandler.java index 69ffe1ab..01d8d68a 100644 --- a/src/main/java/electroblob/wizardry/WizardryGuiHandler.java +++ b/src/main/java/electroblob/wizardry/WizardryGuiHandler.java @@ -1,10 +1,13 @@ package electroblob.wizardry; +import electroblob.wizardry.inventory.ContainerArcaneWorkbench; +import electroblob.wizardry.inventory.ContainerBookshelf; +import electroblob.wizardry.inventory.ContainerPortableWorkbench; import electroblob.wizardry.item.ItemSpellBook; import electroblob.wizardry.item.ItemWizardHandbook; -import electroblob.wizardry.tileentity.ContainerArcaneWorkbench; -import electroblob.wizardry.tileentity.ContainerPortableWorkbench; import electroblob.wizardry.tileentity.TileEntityArcaneWorkbench; +import electroblob.wizardry.tileentity.TileEntityBookshelf; +import electroblob.wizardry.tileentity.TileEntityLectern; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.math.BlockPos; @@ -20,40 +23,82 @@ public class WizardryGuiHandler implements IGuiHandler { public static final int ARCANE_WORKBENCH = nextGuiId++; public static final int WIZARD_HANDBOOK = nextGuiId++; public static final int PORTABLE_CRAFTING = nextGuiId++; + public static final int BOOKSHELF = nextGuiId++; + public static final int LECTERN = nextGuiId++; @Override public Object getServerGuiElement(int id, EntityPlayer player, World world, int x, int y, int z){ + if(id == ARCANE_WORKBENCH){ + TileEntity tileEntity = world.getTileEntity(new BlockPos(x, y, z)); + if(tileEntity instanceof TileEntityArcaneWorkbench){ return new ContainerArcaneWorkbench(player.inventory, (TileEntityArcaneWorkbench)tileEntity); } + }else if(id == PORTABLE_CRAFTING){ return new ContainerPortableWorkbench(player.inventory, world, new BlockPos(x, y, z)); + + }else if(id == BOOKSHELF){ + + TileEntity tileEntity = world.getTileEntity(new BlockPos(x, y, z)); + + if(tileEntity instanceof TileEntityBookshelf){ + return new ContainerBookshelf(player.inventory, (TileEntityBookshelf)tileEntity); + } } + return null; } @Override public Object getClientGuiElement(int id, EntityPlayer player, World world, int x, int y, int z){ + if(id == ARCANE_WORKBENCH){ + TileEntity tileEntity = world.getTileEntity(new BlockPos(x, y, z)); + if(tileEntity instanceof TileEntityArcaneWorkbench){ 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.gui.handbook.GuiWizardHandbook(); + }else if(id == SPELL_BOOK){ + if(player.getHeldItemMainhand().getItem() instanceof ItemSpellBook){ return new electroblob.wizardry.client.gui.GuiSpellBook(player.getHeldItemMainhand()); }else if(player.getHeldItemOffhand().getItem() instanceof ItemSpellBook){ return new electroblob.wizardry.client.gui.GuiSpellBook(player.getHeldItemOffhand()); } + }else if(id == PORTABLE_CRAFTING){ return new electroblob.wizardry.client.gui.GuiPortableCrafting(player.inventory, world, new BlockPos(x, y, z)); + + }else if(id == BOOKSHELF){ + + TileEntity tileEntity = world.getTileEntity(new BlockPos(x, y, z)); + + if(tileEntity instanceof TileEntityBookshelf){ + return new electroblob.wizardry.client.gui.GuiBookshelf(player.inventory, + (TileEntityBookshelf)tileEntity); + } + + }else if(id == LECTERN){ + + TileEntity tileEntity = world.getTileEntity(new BlockPos(x, y, z)); + + if(tileEntity instanceof TileEntityLectern){ + return new electroblob.wizardry.client.gui.GuiLectern((TileEntityLectern)tileEntity); + } + } + return null; } } \ 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 index aedcd6a4..de07e18b 100644 --- a/src/main/java/electroblob/wizardry/advancement/StructureTrigger.java +++ b/src/main/java/electroblob/wizardry/advancement/StructureTrigger.java @@ -5,7 +5,7 @@ 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 electroblob.wizardry.worldgen.WorldGenWizardryStructure; import net.minecraft.advancements.ICriterionTrigger; import net.minecraft.advancements.PlayerAdvancements; import net.minecraft.advancements.critereon.AbstractCriterionInstance; @@ -77,11 +77,11 @@ public class StructureTrigger implements ICriterionTrigger { +public class WizardryContainerTrigger implements ICriterionTrigger { private final ResourceLocation id; - private final Map listeners = Maps.newHashMap(); + private final Map listeners = Maps.newHashMap(); - public ArcaneWorkbenchTrigger(ResourceLocation id){ + public WizardryContainerTrigger(ResourceLocation id){ this.id = id; } @@ -32,21 +32,21 @@ public class ArcaneWorkbenchTrigger implements ICriterionTrigger listener){ + public void addListener(PlayerAdvancements advancements, Listener listener){ - ArcaneWorkbenchTrigger.Listeners listeners = this.listeners.get(advancements); + WizardryContainerTrigger.Listeners listeners = this.listeners.get(advancements); if(listeners == null){ - listeners = new ArcaneWorkbenchTrigger.Listeners(advancements); + listeners = new WizardryContainerTrigger.Listeners(advancements); this.listeners.put(advancements, listeners); } listeners.add(listener); } - public void removeListener(PlayerAdvancements advancements, Listener listener){ + public void removeListener(PlayerAdvancements advancements, Listener listener){ - ArcaneWorkbenchTrigger.Listeners listeners = this.listeners.get(advancements); + WizardryContainerTrigger.Listeners listeners = this.listeners.get(advancements); if(listeners != null){ listeners.remove(listener); @@ -61,13 +61,13 @@ public class ArcaneWorkbenchTrigger implements ICriterionTrigger> listeners = Sets.newHashSet(); + private final Set> listeners = Sets.newHashSet(); public Listeners(PlayerAdvancements advancements){ this.playerAdvancements = advancements; @@ -101,19 +101,19 @@ public class ArcaneWorkbenchTrigger implements ICriterionTrigger listener){ + public void add(Listener listener){ this.listeners.add(listener); } - public void remove(Listener listener){ + public void remove(Listener listener){ this.listeners.remove(listener); } public void trigger(ItemStack stack){ - List> list = null; + List> list = null; - for(Listener listener : this.listeners){ + for(Listener listener : this.listeners){ if(listener.getCriterionInstance().test(stack)){ @@ -126,7 +126,7 @@ public class ArcaneWorkbenchTrigger implements ICriterionTrigger listener : list){ + for(Listener listener : list){ listener.grantCriterion(this.playerAdvancements); } } diff --git a/src/main/java/electroblob/wizardry/block/BlockBookshelf.java b/src/main/java/electroblob/wizardry/block/BlockBookshelf.java new file mode 100644 index 00000000..3b37fa7e --- /dev/null +++ b/src/main/java/electroblob/wizardry/block/BlockBookshelf.java @@ -0,0 +1,368 @@ +package electroblob.wizardry.block; + +import com.google.common.collect.ImmutableList; +import electroblob.wizardry.Settings; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.WizardryGuiHandler; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.registry.WizardryTabs; +import electroblob.wizardry.tileentity.TileEntityBookshelf; +import net.minecraft.block.BlockHorizontal; +import net.minecraft.block.ITileEntityProvider; +import net.minecraft.block.SoundType; +import net.minecraft.block.material.Material; +import net.minecraft.block.properties.PropertyInteger; +import net.minecraft.block.state.BlockFaceShape; +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.inventory.IInventory; +import net.minecraft.inventory.InventoryHelper; +import net.minecraft.item.Item; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.*; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IBlockAccess; +import net.minecraft.world.IWorldEventListener; +import net.minecraft.world.World; +import net.minecraftforge.common.property.IExtendedBlockState; +import net.minecraftforge.common.property.Properties; +import net.minecraftforge.event.world.WorldEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import org.apache.commons.lang3.ArrayUtils; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +@Mod.EventBusSubscriber +public class BlockBookshelf extends BlockHorizontal implements ITileEntityProvider { + + /** When a bookshelf block (of any kind specified in the config) is added or removed, players within this range will + * be notified of the change. */ + public static final double PLAYER_NOTIFY_RANGE = 32; + public static final int SLOT_COUNT = 12; + + public static final UnlistedPropertyInt[] BOOKS = new UnlistedPropertyInt[SLOT_COUNT]; + + private static final Map, ResourceLocation> BOOK_TEXTURE_MAP = new HashMap<>(); + private static ImmutableList bookItems; + private static ImmutableList bookTextures; + + public BlockBookshelf(){ + super(Material.WOOD); + this.setDefaultState(this.blockState.getBaseState().withProperty(FACING, EnumFacing.NORTH)); + this.setCreativeTab(WizardryTabs.WIZARDRY); + this.setHardness(2.0F); + this.setResistance(5.0F); + this.setSoundType(SoundType.WOOD); + } + + @Override + protected BlockStateContainer createBlockState(){ +// IProperty[] properties = { FACING }; +// return new BlockStateContainer(this, ArrayUtils.addAll(properties, BOOKS)); + return new BlockStateContainer.Builder(this).add(FACING).add(BOOKS).build(); + } + + @Override + public BlockFaceShape getBlockFaceShape(IBlockAccess world, IBlockState state, BlockPos pos, EnumFacing face){ + return state.getValue(FACING).getAxis() == face.getAxis() ? BlockFaceShape.UNDEFINED : BlockFaceShape.SOLID; + } + + @Override + public IBlockState getStateForPlacement(World world, BlockPos pos, EnumFacing facing, float hitX, float hitY, float hitZ, int meta, EntityLivingBase placer){ + return this.getDefaultState().withProperty(FACING, placer.getHorizontalFacing().getOpposite()); + } + + // Why on earth are these two not in BlockHorizontal? + + @Override + public IBlockState withRotation(IBlockState state, Rotation rotation){ + return state.withProperty(FACING, rotation.rotate(state.getValue(FACING))); + } + + @Override + public IBlockState withMirror(IBlockState state, Mirror mirror){ + return state.withRotation(mirror.toRotation(state.getValue(FACING))); + } + + @Override + public void onBlockAdded(World worldIn, BlockPos pos, IBlockState state){ + super.onBlockAdded(worldIn, pos, state); + } + + @Override + public void breakBlock(World world, BlockPos pos, IBlockState block){ + + TileEntity tileentity = world.getTileEntity(pos); + + if(tileentity instanceof TileEntityBookshelf){ + InventoryHelper.dropInventoryItems(world, pos, (TileEntityBookshelf)tileentity); + } + + super.breakBlock(world, pos, block); // For blocks that don't extend BlockContainer, this removes the TE + } + + @Override + public IBlockState getStateFromMeta(int meta){ + EnumFacing enumfacing = EnumFacing.byIndex(meta); + if(enumfacing.getAxis() == EnumFacing.Axis.Y) enumfacing = EnumFacing.NORTH; + return this.getDefaultState().withProperty(FACING, enumfacing); + } + + @Override + public int getMetaFromState(IBlockState state){ + return state.getValue(FACING).getIndex(); + } + + @Override + public IBlockState getExtendedState(IBlockState state, IBlockAccess world, BlockPos pos){ + + IExtendedBlockState s = (IExtendedBlockState)super.getExtendedState(state, world, pos); + + if(world.getTileEntity(pos) instanceof TileEntityBookshelf){ + + TileEntityBookshelf tileentity = ((TileEntityBookshelf)world.getTileEntity(pos)); + + for(int i = 0; i < tileentity.getSizeInventory(); i++){ + + if(tileentity.getStackInSlot(i).isEmpty()){ + s = s.withProperty(BOOKS[i], bookItems.size()); + + }else{ + Item item = tileentity.getStackInSlot(i).getItem(); + // Default to the standard spell book texture, which will always be the first item in the list + s = s.withProperty(BOOKS[i], bookItems.contains(item) ? bookItems.indexOf(item) : 0); + } + } + } + + return s; + } + + @Nullable + @Override + public TileEntity createNewTileEntity(World world, int meta){ + return new TileEntityBookshelf(); + } + + @Override + public boolean onBlockActivated(World world, BlockPos pos, IBlockState block, EntityPlayer player, EnumHand hand, + EnumFacing side, float hitX, float hitY, float hitZ){ + + TileEntity tileEntity = world.getTileEntity(pos); + + if(tileEntity == null || player.isSneaking()){ + return false; + } + + player.openGui(Wizardry.instance, WizardryGuiHandler.BOOKSHELF, world, pos.getX(), pos.getY(), pos.getZ()); + return true; + } + + @Override + public boolean hasComparatorInputOverride(IBlockState state){ + return true; + } + + @Override + public int getComparatorInputOverride(IBlockState state, World world, BlockPos pos){ + + TileEntity tileEntity = world.getTileEntity(pos); + + if(tileEntity instanceof TileEntityBookshelf){ + + int slotsOccupied = 0; + + for(int i = 0; i < ((TileEntityBookshelf)tileEntity).getSizeInventory(); i++){ + if(!((TileEntityBookshelf)tileEntity).getStackInSlot(i).isEmpty()) slotsOccupied++; + } + + return slotsOccupied; + + } + + return super.getComparatorInputOverride(state, world, pos); + } + + @Override + public boolean eventReceived(IBlockState state, World world, BlockPos pos, int id, int param){ + super.eventReceived(state, world, pos, id, param); + TileEntity tileentity = world.getTileEntity(pos); + return tileentity != null && tileentity.receiveClientEvent(id, param); + } + + /** Returns the list of book textures, in ID order. */ + public static ImmutableList getBookTextures(){ + // ModelBookshelf calls this before init() so we need to return the map values as a fallback + return bookTextures == null ? ImmutableList.copyOf(BOOK_TEXTURE_MAP.values()) : bookTextures; + } + + /** Returns the list of registered book items, in ID order. */ + public static ImmutableList getBookItems(){ + return bookItems; + } + + /** Called during block registration to initialise the block properties for each book. */ + public static void initBookProperties(){ + for(int i=0; i { + + public UnlistedPropertyInt(String name){ + // Max is inclusive here, but we're using the largest value for no book so there's an extra one (can't use -1) + super(PropertyInteger.create(name, 0, BOOK_TEXTURE_MAP.size())); + } + } + + /** Called from {@link Wizardry#init(FMLInitializationEvent)} to retrieve the actual book items and compile them + * and their textures into immutable lists. */ + public static void compileBookModelTextures(){ + + ImmutableList.Builder itemListBuider = ImmutableList.builder(); + ImmutableList.Builder textureListBuilder = ImmutableList.builder(); + + for(Map.Entry, ResourceLocation> entry : BOOK_TEXTURE_MAP.entrySet()){ + itemListBuider.add(entry.getKey().get()); + textureListBuilder.add(entry.getValue()); + } + + bookItems = itemListBuider.build(); + bookTextures = textureListBuilder.build(); + } + + /** Called from {@link Wizardry#preInit(FMLPreInitializationEvent)} to register the default set of book textures. */ + public static void registerStandardBookModelTextures(){ + // Vanilla Minecraft books + // Regular brown books are the default so they're registered first + registerBookModelTexture(() -> Items.BOOK, new ResourceLocation(Wizardry.MODID, "blocks/books_brown")); + registerBookModelTexture(() -> Items.WRITABLE_BOOK, new ResourceLocation(Wizardry.MODID, "blocks/books_brown")); + registerBookModelTexture(() -> Items.WRITTEN_BOOK, new ResourceLocation(Wizardry.MODID, "blocks/books_brown")); + registerBookModelTexture(() -> Items.ENCHANTED_BOOK, new ResourceLocation(Wizardry.MODID, "blocks/books_enchanted")); + // Wizardry books + registerBookModelTexture(() -> WizardryItems.spell_book, new ResourceLocation(Wizardry.MODID, "blocks/books_red")); + registerBookModelTexture(() -> WizardryItems.wizard_handbook, new ResourceLocation(Wizardry.MODID, "blocks/books_blue")); + registerBookModelTexture(() -> WizardryItems.arcane_tome, new ResourceLocation(Wizardry.MODID, "blocks/books_purple")); + registerBookModelTexture(() -> WizardryItems.ruined_spell_book, new ResourceLocation(Wizardry.MODID, "blocks/books_brown")); + registerBookModelTexture(() -> WizardryItems.scroll, new ResourceLocation(Wizardry.MODID, "blocks/scrolls_blue")); + registerBookModelTexture(() -> WizardryItems.blank_scroll, new ResourceLocation(Wizardry.MODID, "blocks/scrolls_blue")); + registerBookModelTexture(() -> WizardryItems.identification_scroll, new ResourceLocation(Wizardry.MODID, "blocks/scrolls_purple")); + registerBookModelTexture(() -> WizardryItems.armour_upgrade, new ResourceLocation(Wizardry.MODID, "blocks/scrolls_red")); + // Can't use the map in WandHelper because this is called from preInit (addons will have to add theirs manually) + registerBookModelTexture(() -> WizardryItems.storage_upgrade, new ResourceLocation(Wizardry.MODID, "blocks/scrolls_wooden")); + registerBookModelTexture(() -> WizardryItems.siphon_upgrade, new ResourceLocation(Wizardry.MODID, "blocks/scrolls_wooden")); + registerBookModelTexture(() -> WizardryItems.condenser_upgrade, new ResourceLocation(Wizardry.MODID, "blocks/scrolls_wooden")); + registerBookModelTexture(() -> WizardryItems.range_upgrade, new ResourceLocation(Wizardry.MODID, "blocks/scrolls_wooden")); + registerBookModelTexture(() -> WizardryItems.duration_upgrade, new ResourceLocation(Wizardry.MODID, "blocks/scrolls_wooden")); + registerBookModelTexture(() -> WizardryItems.cooldown_upgrade, new ResourceLocation(Wizardry.MODID, "blocks/scrolls_wooden")); + registerBookModelTexture(() -> WizardryItems.blast_upgrade, new ResourceLocation(Wizardry.MODID, "blocks/scrolls_wooden")); + registerBookModelTexture(() -> WizardryItems.attunement_upgrade, new ResourceLocation(Wizardry.MODID, "blocks/scrolls_wooden")); + registerBookModelTexture(() -> WizardryItems.melee_upgrade, new ResourceLocation(Wizardry.MODID, "blocks/scrolls_wooden")); + } + + /** + * Registers a book texture to be used for the book model when the given item is placed in a bookshelf. This method + * must be called from {@code preInit} as the registered item count is required during block registration. + * This also necessitates the use of a supplier to fetch the item, since the items will not yet be registered. + * @param itemFactory A {@link Supplier} that returns the book item to link the texture to. This item need not + * be an instance of {@link electroblob.wizardry.item.ItemSpellBook ItemSpellBook}. Duplicate + * items will result in an {@link IllegalArgumentException} being thrown later. + * @param texture The texture to apply to the book model. + */ + public static void registerBookModelTexture(Supplier itemFactory, ResourceLocation texture){ + BOOK_TEXTURE_MAP.put(itemFactory, texture); + } + + /** + * Returns a list of nearby bookshelves' inventories, where 'bookshelves' are any tile entities with inventories + * whose blocks are specified in the config file under the {@code bookshelfBlocks} option. + * @param world The world to search in + * @param centre The position to search around + * @param exclude Any tile entities that should be excluded from the returned list + * @return A list of nearby {@link IInventory} objects that count as valid bookshelves + */ + public static List findNearbyBookshelves(World world, BlockPos centre, TileEntity... exclude){ + + List bookshelves = new ArrayList<>(); + + int searchRadius = Wizardry.settings.bookshelfSearchRadius; + + for(int x = -searchRadius; x <= searchRadius; x++){ + for(int y = -searchRadius; y <= searchRadius; y++){ + for(int z = -searchRadius; z <= searchRadius; z++){ + + BlockPos pos = centre.add(x, y, z); + + if(Settings.containsMetaBlock(Wizardry.settings.bookshelfBlocks, world.getBlockState(pos))){ + TileEntity te = world.getTileEntity(pos); + if(te instanceof IInventory && !ArrayUtils.contains(exclude, te)) bookshelves.add((IInventory)te); + } + + } + } + } + + return bookshelves; + + } + + @SubscribeEvent + public static void onWorldLoadEvent(WorldEvent.Load event){ + event.getWorld().addEventListener(Listener.instance); + } + + @SubscribeEvent + public static void onWorldUnloadEvent(WorldEvent.Unload event){ + event.getWorld().removeEventListener(Listener.instance); + } + + public static class Listener implements IWorldEventListener { + + public static final Listener instance = new Listener(); + + private Listener(){} + + @Override + public void notifyBlockUpdate(World world, BlockPos pos, IBlockState oldState, IBlockState newState, int flags){ + + if(oldState == newState) return; // Probably won't happen but just in case + + if(Settings.containsMetaBlock(Wizardry.settings.bookshelfBlocks, oldState) // Bookshelf removed + || Settings.containsMetaBlock(Wizardry.settings.bookshelfBlocks, newState)){ // Bookshelf placed + // It is also possible (with commands) for a bookshelf to be replaced with another bookshelf, in which + // case this should still just be called once + Wizardry.proxy.notifyBookshelfChange(world, pos); + } + + } + + // Dummy implementations + @Override public void notifyLightSet(BlockPos pos){} + @Override public void markBlockRangeForRenderUpdate(int x1, int y1, int z1, int x2, int y2, int z2){} + @Override public void playSoundToAllNearExcept(@Nullable EntityPlayer player, SoundEvent soundIn, SoundCategory category, double x, double y, double z, float volume, float pitch){} + @Override public void playRecord(SoundEvent soundIn, BlockPos pos){} + @Override public void spawnParticle(int particleID, boolean ignoreRange, double xCoord, double yCoord, double zCoord, double xSpeed, double ySpeed, double zSpeed, int... parameters){} + @Override public void spawnParticle(int id, boolean ignoreRange, boolean minimiseParticleLevel, double x, double y, double z, double xSpeed, double ySpeed, double zSpeed, int... parameters){} + @Override public void onEntityAdded(Entity entityIn){} + @Override public void onEntityRemoved(Entity entityIn){} + @Override public void broadcastSound(int soundID, BlockPos pos, int data){} + @Override public void playEvent(EntityPlayer player, int type, BlockPos blockPosIn, int data){} + @Override public void sendBlockBreakProgress(int breakerId, BlockPos pos, int progress){} + + } + +} diff --git a/src/main/java/electroblob/wizardry/block/BlockCrystalFlowerPot.java b/src/main/java/electroblob/wizardry/block/BlockCrystalFlowerPot.java new file mode 100644 index 00000000..361ad0d2 --- /dev/null +++ b/src/main/java/electroblob/wizardry/block/BlockCrystalFlowerPot.java @@ -0,0 +1,148 @@ +package electroblob.wizardry.block; + +import electroblob.wizardry.registry.WizardryBlocks; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import net.minecraft.block.Block; +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.init.Blocks; +import net.minecraft.init.Items; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntityFlowerPot; +import net.minecraft.util.BlockRenderLayer; +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.util.math.RayTraceResult; +import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; + +import javax.annotation.Nullable; +import java.util.Random; + +public class BlockCrystalFlowerPot extends Block { + + protected static final AxisAlignedBB BOUNDING_BOX = new AxisAlignedBB(0.3125D, 0.0D, 0.3125D, 0.6875D, 0.375D, 0.6875D); + + public BlockCrystalFlowerPot(){ + super(Material.CIRCUITS); + this.setLightLevel(0.4f); + this.setTickRandomly(true); + } + + @Override + public void randomDisplayTick(IBlockState state, World world, BlockPos pos, Random random){ + if(world.isRemote && random.nextBoolean()){ + ParticleBuilder.create(Type.SPARKLE) + .pos(pos.getX() + 0.3 + random.nextDouble() * 0.4, pos.getY() + 0.6 + random.nextDouble() * 0.3, pos.getZ() + 0.3 + random.nextDouble() * 0.4) + .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); + } + } + + @Nullable + @Override + public TileEntity createTileEntity(World world, IBlockState state){ + return new TileEntityFlowerPot(Item.getItemFromBlock(WizardryBlocks.crystal_flower), 0); + } + + @Override + public boolean hasTileEntity(IBlockState state){ + return true; + } + + @Override + public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ){ + + ItemStack stack = new ItemStack(WizardryBlocks.crystal_flower); + + if(player.getHeldItem(hand).isEmpty()){ + player.setHeldItem(hand, stack); + }else if(!player.addItemStackToInventory(stack)){ + player.dropItem(stack, false); + } + + world.setBlockState(pos, Blocks.FLOWER_POT.getDefaultState()); + + return true; + } + + @Override + public ItemStack getPickBlock(IBlockState state, RayTraceResult target, World world, BlockPos pos, EntityPlayer player){ + return new ItemStack(WizardryBlocks.crystal_flower); + } + + @Override + public void getDrops(net.minecraft.util.NonNullList drops, IBlockAccess world, BlockPos pos, IBlockState state, int fortune){ + super.getDrops(drops, world, pos, state, fortune); + drops.add(new ItemStack(WizardryBlocks.crystal_flower)); + } + + @Override + public Item getItemDropped(IBlockState state, Random rand, int fortune){ + return Items.FLOWER_POT; + } + + // The rest are identical behaviour to BlockFlowerPot + + @Override + public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos){ + return BOUNDING_BOX; + } + + @Override + public boolean isOpaqueCube(IBlockState state){ + return false; + } + + @Override + public boolean isFullCube(IBlockState state){ + return false; + } + + @Override + public boolean canPlaceBlockAt(World world, BlockPos pos){ + IBlockState downState = world.getBlockState(pos.down()); + return super.canPlaceBlockAt(world, pos) && (downState.isTopSolid() || downState.getBlockFaceShape(world, pos.down(), EnumFacing.UP) == BlockFaceShape.SOLID); + } + + @Override + public void neighborChanged(IBlockState state, World world, BlockPos pos, Block block, BlockPos neighbour){ + IBlockState downState = world.getBlockState(pos.down()); + if(!downState.isTopSolid() && downState.getBlockFaceShape(world, pos.down(), EnumFacing.UP) != BlockFaceShape.SOLID){ + this.dropBlockAsItem(world, pos, state, 0); + world.setBlockToAir(pos); + } + } + + @Override + public BlockRenderLayer getRenderLayer(){ + return BlockRenderLayer.CUTOUT; + } + + @Override + public BlockFaceShape getBlockFaceShape(IBlockAccess worldIn, IBlockState state, BlockPos pos, EnumFacing face){ + return BlockFaceShape.UNDEFINED; + } + + @Override + public boolean removedByPlayer(IBlockState state, World world, BlockPos pos, EntityPlayer player, boolean willHarvest){ + if(willHarvest) return true; // If it will harvest, delay deletion of the block until after getDrops + return super.removedByPlayer(state, world, pos, player, willHarvest); + } + + @Override + public void harvestBlock(World world, EntityPlayer player, BlockPos pos, IBlockState state, @Nullable TileEntity te, ItemStack tool){ + super.harvestBlock(world, player, pos, state, te, tool); + world.setBlockToAir(pos); + } + +} diff --git a/src/main/java/electroblob/wizardry/block/BlockDryFrostedIce.java b/src/main/java/electroblob/wizardry/block/BlockDryFrostedIce.java index 8e80b5ae..88782a8d 100644 --- a/src/main/java/electroblob/wizardry/block/BlockDryFrostedIce.java +++ b/src/main/java/electroblob/wizardry/block/BlockDryFrostedIce.java @@ -1,6 +1,8 @@ package electroblob.wizardry.block; import net.minecraft.block.BlockFrostedIce; +import net.minecraft.block.SoundType; +import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; @@ -12,6 +14,17 @@ import java.util.Random; * disappears instead of turning to water. */ public class BlockDryFrostedIce extends BlockFrostedIce { + public BlockDryFrostedIce(){ + super(); + setDefaultSlipperiness(0.98f); // For some strange reason BlockFrostedIce overrides BlockIce's icy-ness... + setSoundType(SoundType.GLASS); + } + + @Override + public Material getMaterial(IBlockState state){ + return Material.ICE; // For goodness sake + } + @Override protected void turnIntoWater(World world, BlockPos pos){ world.destroyBlock(pos, false); diff --git a/src/main/java/electroblob/wizardry/block/BlockGildedWood.java b/src/main/java/electroblob/wizardry/block/BlockGildedWood.java new file mode 100644 index 00000000..9e95cc6a --- /dev/null +++ b/src/main/java/electroblob/wizardry/block/BlockGildedWood.java @@ -0,0 +1,17 @@ +package electroblob.wizardry.block; + +import electroblob.wizardry.registry.WizardryTabs; +import net.minecraft.block.BlockPlanks; +import net.minecraft.block.SoundType; + +public class BlockGildedWood extends BlockPlanks { + + public BlockGildedWood(){ + super(); + this.setCreativeTab(WizardryTabs.WIZARDRY); + this.setHardness(2.0F); + this.setResistance(5.0F); + this.setSoundType(SoundType.WOOD); // Why is this protected?! + } + +} diff --git a/src/main/java/electroblob/wizardry/block/BlockImbuementAltar.java b/src/main/java/electroblob/wizardry/block/BlockImbuementAltar.java new file mode 100644 index 00000000..6d46f27b --- /dev/null +++ b/src/main/java/electroblob/wizardry/block/BlockImbuementAltar.java @@ -0,0 +1,168 @@ +package electroblob.wizardry.block; + +import electroblob.wizardry.registry.WizardryBlocks; +import electroblob.wizardry.registry.WizardryTabs; +import electroblob.wizardry.tileentity.TileEntityImbuementAltar; +import net.minecraft.block.Block; +import net.minecraft.block.ITileEntityProvider; +import net.minecraft.block.material.Material; +import net.minecraft.block.properties.PropertyBool; +import net.minecraft.block.state.BlockFaceShape; +import net.minecraft.block.state.BlockStateContainer; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.InventoryHelper; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.BlockRenderLayer; +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; + +import java.util.Arrays; + +public class BlockImbuementAltar extends Block implements ITileEntityProvider { + + public static final PropertyBool ACTIVE = PropertyBool.create("active"); + + private static final AxisAlignedBB AABB = new AxisAlignedBB(0.0, 0.0, 0.0, 1.0, 0.75, 1.0); + + public BlockImbuementAltar(){ + super(Material.ROCK); + this.setBlockUnbreakable(); + this.setResistance(6000000); + this.setLightLevel(0.4f); + this.setCreativeTab(WizardryTabs.WIZARDRY); + this.setDefaultState(this.blockState.getBaseState().withProperty(ACTIVE, false)); + } + + @Override + protected BlockStateContainer createBlockState(){ + return new BlockStateContainer(this, ACTIVE); + } + + @Override + public IBlockState getStateFromMeta(int meta){ + return this.getDefaultState().withProperty(ACTIVE, meta == 1); + } + + @Override + public int getMetaFromState(IBlockState state){ + return state.getValue(ACTIVE) ? 1 : 0; + } + + @Override + public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos){ + return AABB; + } + + @Override + public TileEntity createNewTileEntity(World world, int metadata){ + return new TileEntityImbuementAltar(); + } + + @Override + public boolean isNormalCube(IBlockState state, IBlockAccess world, BlockPos pos){ + return false; + } + + @Override + public EnumBlockRenderType getRenderType(IBlockState state){ + return EnumBlockRenderType.MODEL; + } + + @Override + public BlockRenderLayer getRenderLayer(){ + return BlockRenderLayer.CUTOUT; // Required to shade parts of the block faces differently to others + } + + @Override + public boolean isOpaqueCube(IBlockState state){ + 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 int getLightValue(IBlockState state, IBlockAccess world, BlockPos pos){ + return state.getValue(ACTIVE) ? super.getLightValue(state, world, pos) : 0; + } + + @Override + public void neighborChanged(IBlockState state, World world, BlockPos pos, Block block, BlockPos neighbour){ + + boolean shouldBeActive = Arrays.stream(EnumFacing.HORIZONTALS) + .allMatch(s -> world.getBlockState(pos.offset(s)).getBlock() == WizardryBlocks.receptacle + && world.getBlockState(pos.offset(s)).getValue(BlockReceptacle.FACING) == s); + + if(world.getBlockState(pos).getValue(ACTIVE) != shouldBeActive){ // Only set when it actually needs changing + world.setBlockState(pos, world.getBlockState(pos).withProperty(ACTIVE, shouldBeActive)); + world.checkLight(pos); + } + + TileEntity tileEntity = world.getTileEntity(pos); + if(tileEntity instanceof TileEntityImbuementAltar){ + ((TileEntityImbuementAltar)tileEntity).checkRecipe(); + } + } + + @Override + public boolean onBlockActivated(World world, BlockPos pos, IBlockState block, EntityPlayer player, EnumHand hand, + EnumFacing side, float hitX, float hitY, float hitZ){ + + TileEntity tileEntity = world.getTileEntity(pos); + + if(!(tileEntity instanceof TileEntityImbuementAltar) || player.isSneaking()){ + return false; + } + + ItemStack currentStack = ((TileEntityImbuementAltar)tileEntity).getStack(); + ItemStack toInsert = player.getHeldItem(hand); + + if(currentStack.isEmpty()){ + ItemStack stack = toInsert.copy(); + stack.setCount(1); + ((TileEntityImbuementAltar)tileEntity).setStack(stack); + ((TileEntityImbuementAltar)tileEntity).setLastUser(player); + if(!player.isCreative()) toInsert.shrink(1); + + }else{ + + if(toInsert.isEmpty()){ + player.setHeldItem(hand, currentStack); + }else if(!player.addItemStackToInventory(currentStack)){ + player.dropItem(currentStack, false); + } + + ((TileEntityImbuementAltar)tileEntity).setStack(ItemStack.EMPTY); + ((TileEntityImbuementAltar)tileEntity).setLastUser(null); + } + + return true; + } + + @Override + public void breakBlock(World world, BlockPos pos, IBlockState block){ + + TileEntity tileentity = world.getTileEntity(pos); + + if(tileentity instanceof TileEntityImbuementAltar){ + InventoryHelper.spawnItemStack(world, pos.getX(), pos.getY(), pos.getZ(), ((TileEntityImbuementAltar)tileentity).getStack()); + } + + super.breakBlock(world, pos, block); + } + +} diff --git a/src/main/java/electroblob/wizardry/block/BlockLectern.java b/src/main/java/electroblob/wizardry/block/BlockLectern.java new file mode 100644 index 00000000..7ea7d06a --- /dev/null +++ b/src/main/java/electroblob/wizardry/block/BlockLectern.java @@ -0,0 +1,138 @@ +package electroblob.wizardry.block; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.WizardryGuiHandler; +import electroblob.wizardry.registry.WizardryTabs; +import electroblob.wizardry.tileentity.TileEntityLectern; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import net.minecraft.block.BlockHorizontal; +import net.minecraft.block.ITileEntityProvider; +import net.minecraft.block.SoundType; +import net.minecraft.block.material.Material; +import net.minecraft.block.state.BlockFaceShape; +import net.minecraft.block.state.BlockStateContainer; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.Mirror; +import net.minecraft.util.Rotation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import javax.annotation.Nullable; +import java.util.Random; + +public class BlockLectern extends BlockHorizontal implements ITileEntityProvider { + + public BlockLectern(){ + super(Material.WOOD); + this.setDefaultState(this.blockState.getBaseState().withProperty(FACING, EnumFacing.NORTH)); + this.setCreativeTab(WizardryTabs.WIZARDRY); + this.setHardness(2.0F); + this.setResistance(5.0F); + this.setSoundType(SoundType.WOOD); + } + + @Override + protected BlockStateContainer createBlockState(){ + return new BlockStateContainer.Builder(this).add(FACING).build(); + } + + @Override + public IBlockState getStateFromMeta(int meta){ + EnumFacing enumfacing = EnumFacing.byIndex(meta); + if(enumfacing.getAxis() == EnumFacing.Axis.Y) enumfacing = EnumFacing.NORTH; + return this.getDefaultState().withProperty(FACING, enumfacing); + } + + @Override + public int getMetaFromState(IBlockState state){ + return state.getValue(FACING).getIndex(); + } + + @Override + @SideOnly(Side.CLIENT) + public void randomDisplayTick(IBlockState state, World world, BlockPos pos, Random rand){ + + EntityPlayer entityplayer = world.getClosestPlayer(pos.getX() + 0.5, pos.getY() + 0.5, + pos.getZ() + 0.5, TileEntityLectern.BOOK_OPEN_DISTANCE, false); + + if(entityplayer != null){ + ParticleBuilder.create(Type.DUST).pos(pos.getX() + rand.nextFloat(), pos.getY() + 1, pos.getZ() + rand.nextFloat()) + .vel(0, 0.03, 0).clr(1, 1, 0.65f).fade(0.7f, 0, 1).shaded(false).spawn(world); + } + } + + @Override + public boolean isOpaqueCube(IBlockState state){ + return false; + } + + @Override + public boolean isFullCube(IBlockState state){ + return false; + } + + @Override + public boolean isFullBlock(IBlockState state){ + return false; + } + + @Override + public boolean isNormalCube(IBlockState state){ + return false; + } + + @Override + public boolean canPlaceTorchOnTop(IBlockState state, IBlockAccess world, BlockPos pos){ + return false; + } + + @Override + public BlockFaceShape getBlockFaceShape(IBlockAccess world, IBlockState state, BlockPos pos, EnumFacing face){ + return face == EnumFacing.DOWN ? BlockFaceShape.SOLID : BlockFaceShape.UNDEFINED; + } + + @Override + public IBlockState getStateForPlacement(World world, BlockPos pos, EnumFacing facing, float hitX, float hitY, float hitZ, int meta, EntityLivingBase placer){ + return this.getDefaultState().withProperty(FACING, placer.getHorizontalFacing().getOpposite()); + } + + @Override + public IBlockState withRotation(IBlockState state, Rotation rotation){ + return state.withProperty(FACING, rotation.rotate(state.getValue(FACING))); + } + + @Override + public IBlockState withMirror(IBlockState state, Mirror mirror){ + return state.withRotation(mirror.toRotation(state.getValue(FACING))); + } + + @Nullable + @Override + public TileEntity createNewTileEntity(World world, int meta){ + return new TileEntityLectern(); + } + + @Override + public boolean onBlockActivated(World world, BlockPos pos, IBlockState block, EntityPlayer player, EnumHand hand, + EnumFacing side, float hitX, float hitY, float hitZ){ + + TileEntity tileEntity = world.getTileEntity(pos); + + if(tileEntity == null || player.isSneaking()){ + return false; + } + + player.openGui(Wizardry.instance, WizardryGuiHandler.LECTERN, world, pos.getX(), pos.getY(), pos.getZ()); + return true; + } + +} diff --git a/src/main/java/electroblob/wizardry/block/BlockPermafrost.java b/src/main/java/electroblob/wizardry/block/BlockPermafrost.java new file mode 100644 index 00000000..5043cdcb --- /dev/null +++ b/src/main/java/electroblob/wizardry/block/BlockPermafrost.java @@ -0,0 +1,91 @@ +package electroblob.wizardry.block; + +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.EntityUtils; +import net.minecraft.block.state.BlockFaceShape; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.potion.PotionEffect; +import net.minecraft.util.DamageSource; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public class BlockPermafrost extends BlockDryFrostedIce { + + protected static final AxisAlignedBB BOUNDING_BOX = new AxisAlignedBB(0, 0, 0, 1, 0, 1); + protected static final AxisAlignedBB SELECTION_BOUNDING_BOX = new AxisAlignedBB(0, 0, 0, 1, 0.125, 1); + + @Override + public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos){ + return BOUNDING_BOX; + } + + @Override + public AxisAlignedBB getSelectedBoundingBox(IBlockState state, World worldIn, BlockPos pos){ + return SELECTION_BOUNDING_BOX; + } + + @Nullable + @Override + public AxisAlignedBB getCollisionBoundingBox(IBlockState blockState, IBlockAccess worldIn, BlockPos pos){ + return super.getCollisionBoundingBox(blockState, worldIn, pos); + } + + @Override + public boolean isFullCube(IBlockState state){ + return false; + } + + @Override + public boolean isNormalCube(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 void onEntityCollision(World world, BlockPos pos, IBlockState state, Entity entity){ + + if(EntityUtils.isLiving(entity) && entity.ticksExisted % 30 == 0){ + // Can't make it player damage unless we make this block a tile entity, but there will be too many for that + entity.attackEntityFrom(DamageSource.MAGIC, Spells.permafrost.getProperty(Spell.DAMAGE).floatValue()); + int duration = Spells.permafrost.getProperty(Spell.EFFECT_DURATION).intValue(); + int amplifier = Spells.permafrost.getProperty(Spell.EFFECT_STRENGTH).intValue(); + ((EntityLivingBase)entity).addPotionEffect(new PotionEffect(WizardryPotions.frost, duration, amplifier)); + } + + // EntityLivingBase's slipperiness code doesn't get the block below it properly so slipperiness only works for + // full blocks... + if(entity.onGround){ + + // Not brilliant but it's about the best I can do + entity.motionX *= 1.12 - entity.motionX * entity.motionX; + entity.motionZ *= 1.12 - entity.motionZ * entity.motionZ; + +// if(entity instanceof EntityLivingBase){ +// double maxVel = 0.8; +// double x = entity.motionX; +// double y = entity.motionY; +// double z = entity.motionZ; +// double vel = MathHelper.sqrt(x*x + y*y + z*z); +// double m = vel / maxVel; +// if(m > 1){ +// entity.motionX /= m; +// entity.motionZ /= m; +// } +// } + } + + } +} diff --git a/src/main/java/electroblob/wizardry/block/BlockReceptacle.java b/src/main/java/electroblob/wizardry/block/BlockReceptacle.java new file mode 100644 index 00000000..6c7868a5 --- /dev/null +++ b/src/main/java/electroblob/wizardry/block/BlockReceptacle.java @@ -0,0 +1,303 @@ +package electroblob.wizardry.block; + +import com.google.common.collect.Maps; +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.item.ItemSpectralDust; +import electroblob.wizardry.registry.*; +import electroblob.wizardry.tileentity.TileEntityReceptacle; +import electroblob.wizardry.util.GeometryUtils; +import electroblob.wizardry.util.ParticleBuilder; +import net.minecraft.block.Block; +import net.minecraft.block.BlockTorch; +import net.minecraft.block.ITileEntityProvider; +import net.minecraft.block.SoundType; +import net.minecraft.block.state.BlockFaceShape; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.NonNullList; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.Map; +import java.util.Random; + +public class BlockReceptacle extends BlockTorch implements ITileEntityProvider { + + protected static final AxisAlignedBB STANDING_AABB = new AxisAlignedBB(4 / 16d, 0 / 16d, 4 / 16d, 12 / 16d, 8 / 16d, 12 / 16d); + protected static final AxisAlignedBB NORTH_WALL_AABB = new AxisAlignedBB(4 / 16d, 2 / 16d, 7 / 16d, 12 / 16d, 10 / 16d, 16 / 16d); + protected static final AxisAlignedBB SOUTH_WALL_AABB = new AxisAlignedBB(4 / 16d, 2 / 16d, 0 / 16d, 12 / 16d, 10 / 16d, 9 / 16d); + protected static final AxisAlignedBB WEST_WALL_AABB = new AxisAlignedBB(7 / 16d, 2 / 16d, 4 / 16d, 16 / 16d, 10 / 16d, 12 / 16d); + protected static final AxisAlignedBB EAST_WALL_AABB = new AxisAlignedBB(0 / 16d, 2 / 16d, 4 / 16d, 9 / 16d, 10 / 16d, 12 / 16d); + + private static final double WALL_PARTICLE_OFFSET = 3 / 16d; + + public static final Map PARTICLE_COLOURS; + + static{ + + Map map = Maps.newEnumMap(Element.class); + + map.put(Element.MAGIC, new int[]{0xe4c7cd, 0xfeffbe, 0x9d2cf3}); + map.put(Element.FIRE, new int[]{0xff9600, 0xfffe67, 0xd02700}); + map.put(Element.ICE, new int[]{0xa3e8f4, 0xe9f9fc, 0x138397}); + map.put(Element.LIGHTNING, new int[]{0x409ee1, 0xf5f0ff, 0x225474}); + map.put(Element.NECROMANCY, new int[]{0xa811ce, 0xf575f5, 0x382366}); + map.put(Element.EARTH, new int[]{0xa8f408, 0xc8ffb2, 0x795c28}); + map.put(Element.SORCERY, new int[]{0x56e8e3, 0xe8fcfc, 0x16a64d}); + map.put(Element.HEALING, new int[]{0xfff69e, 0xfffff6, 0xa18200}); + + PARTICLE_COLOURS = Maps.immutableEnumMap(map); + } + + public BlockReceptacle(){ + super(); + this.setCreativeTab(WizardryTabs.WIZARDRY); + this.setHardness(0); + this.setLightLevel(0.5f); + this.setSoundType(SoundType.STONE); + } + + @Override + public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess world, BlockPos pos){ + switch(state.getValue(FACING)){ + case EAST: + return EAST_WALL_AABB; + case WEST: + return WEST_WALL_AABB; + case SOUTH: + return SOUTH_WALL_AABB; + case NORTH: + return NORTH_WALL_AABB; + default: + return STANDING_AABB; + } + } + + @Override + public AxisAlignedBB getCollisionBoundingBox(IBlockState state, IBlockAccess world, BlockPos pos){ + return state.getBoundingBox(world, pos); + } + + @Override + public int getLightValue(IBlockState state, IBlockAccess world, BlockPos pos){ + + TileEntity tileEntity = world.getTileEntity(pos); + + if(tileEntity instanceof TileEntityReceptacle && ((TileEntityReceptacle)tileEntity).getElement() != null){ + return super.getLightValue(state, world, pos); // Return super to use float value from constructor + } + + return 0; + } + + @Override + public void getDrops(NonNullList drops, IBlockAccess world, BlockPos pos, IBlockState state, int fortune){ + + super.getDrops(drops, world, pos, state, fortune); + + TileEntity tileEntity = world.getTileEntity(pos); + + if(tileEntity instanceof TileEntityReceptacle){ + Element element = ((TileEntityReceptacle)tileEntity).getElement(); + if(element != null) drops.add(new ItemStack(WizardryItems.spectral_dust, 1, element.ordinal())); + } + } + + @Override + public boolean canPlaceBlockOnSide(World world, BlockPos pos, EnumFacing side){ + + if(side != EnumFacing.UP && side != EnumFacing.DOWN + && world.getBlockState(pos.offset(side.getOpposite())).getBlock() instanceof BlockImbuementAltar){ + return true; + } + return super.canPlaceBlockOnSide(world, pos, side); + } + + @Override + protected boolean checkForDrop(World world, BlockPos pos, IBlockState state){ + if(state.getBlock() == this && this.canPlaceAt(world, pos, state.getValue(FACING))){ + return true; + } + return super.checkForDrop(world, pos, state); + } + + @Override + public IBlockState getStateForPlacement(World world, BlockPos pos, EnumFacing facing, float hitX, float hitY, float hitZ, int meta, EntityLivingBase placer){ + + if(this.canPlaceAt(world, pos, facing)){ + return this.getDefaultState().withProperty(FACING, facing); + }else{ + + for(EnumFacing enumfacing : EnumFacing.Plane.HORIZONTAL){ + if(this.canPlaceAt(world, pos, enumfacing)){ + return this.getDefaultState().withProperty(FACING, enumfacing); + } + } + + return this.getDefaultState(); + } + } + + // Why is everything in BlockTorch private, for goodness sake... + private boolean canPlaceAt(World world, BlockPos pos, EnumFacing facing){ + + BlockPos blockpos = pos.offset(facing.getOpposite()); + IBlockState state = world.getBlockState(blockpos); + Block block = state.getBlock(); + BlockFaceShape blockfaceshape = state.getBlockFaceShape(world, blockpos, facing); + + if(facing.equals(EnumFacing.UP) && this.canPlaceOn(world, blockpos)){ + return true; + }else if(facing != EnumFacing.UP && facing != EnumFacing.DOWN){ + return !isExceptBlockForAttachWithPiston(block) && (blockfaceshape == BlockFaceShape.SOLID || block instanceof BlockImbuementAltar); + }else{ + return false; + } + } + + private boolean canPlaceOn(World world, BlockPos pos){ + IBlockState state = world.getBlockState(pos); + return state.getBlock().canPlaceTorchOnTop(state, world, pos); + } + + // See BlockFlowerPot for these two (this class is essentially based on a flower pot) + + @Override + public boolean removedByPlayer(IBlockState state, World world, BlockPos pos, EntityPlayer player, boolean willHarvest){ + if(willHarvest) return true; // If it will harvest, delay deletion of the block until after getDrops + return super.removedByPlayer(state, world, pos, player, willHarvest); + } + + @Override + public void harvestBlock(World world, EntityPlayer player, BlockPos pos, IBlockState state, @Nullable TileEntity te, ItemStack tool){ + super.harvestBlock(world, player, pos, state, te, tool); + world.setBlockToAir(pos); + } + + @Override + public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ){ + + TileEntity tileEntity = world.getTileEntity(pos); + + ItemStack stack = player.getHeldItem(hand); + + if(tileEntity instanceof TileEntityReceptacle){ + + Element currentElement = ((TileEntityReceptacle)tileEntity).getElement(); + + if(currentElement == null){ + + if(stack.getItem() instanceof ItemSpectralDust && stack.getMetadata() >= 0 + && stack.getMetadata() < Element.values().length){ + + ((TileEntityReceptacle)tileEntity).setElement(Element.values()[stack.getMetadata()]); + if(!player.capabilities.isCreativeMode) stack.shrink(1); + world.playSound(pos.getX(), pos.getY(), pos.getZ(), WizardrySounds.BLOCK_RECEPTACLE_IGNITE, + SoundCategory.BLOCKS, 0.7f, 0.7f, false); + return true; + } + + }else{ + + ((TileEntityReceptacle)tileEntity).setElement(null); + + ItemStack dust = new ItemStack(WizardryItems.spectral_dust, 1, currentElement.ordinal()); + + if(stack.isEmpty()){ + player.setHeldItem(hand, dust); + }else if(!player.addItemStackToInventory(dust)){ + player.dropItem(dust, false); + } + + return true; + } + } + + return super.onBlockActivated(world, pos, state, player, hand, facing, hitX, hitY, hitZ); + } + + @Override + public void randomDisplayTick(IBlockState state, World world, BlockPos pos, Random rand){ + + TileEntity tileEntity = world.getTileEntity(pos); + + if(tileEntity instanceof TileEntityReceptacle){ + + Element element = ((TileEntityReceptacle)tileEntity).getElement(); + + if(element != null){ + + EnumFacing facing = state.getValue(FACING).getOpposite(); + + Vec3d centre = GeometryUtils.getCentre(pos); + if(facing.getAxis().isHorizontal()){ + centre = centre.add(new Vec3d(facing.getDirectionVec()).scale(WALL_PARTICLE_OFFSET)).add(0, 0.125, 0); + } + + int[] colours = PARTICLE_COLOURS.get(element); + + ParticleBuilder.create(ParticleBuilder.Type.FLASH).pos(centre).scale(0.35f).time(48).clr(colours[0]).spawn(world); + + double r = 0.12; + + for(int i = 0; i < 3; i++){ + + double x = r * (rand.nextDouble() * 2 - 1); + double y = r * (rand.nextDouble() * 2 - 1); + double z = r * (rand.nextDouble() * 2 - 1); + + ParticleBuilder.create(ParticleBuilder.Type.DUST).pos(centre.x + x, centre.y + y, centre.z + z) + .vel(x * -0.03, 0.02, z * -0.03).time(24 + rand.nextInt(8)).clr(colours[1]).fade(colours[2]).spawn(world); + } + } + } + } + + @Override + public void onBlockPlacedBy(World world, BlockPos pos, IBlockState state, EntityLivingBase placer, ItemStack stack){ + if(placer instanceof EntityPlayer){ + BlockPos centre = pos.offset(world.getBlockState(pos).getValue(FACING).getOpposite()); + if(world.getBlockState(centre).getBlock() == WizardryBlocks.imbuement_altar + && Arrays.stream(EnumFacing.HORIZONTALS).allMatch(f -> world.getBlockState(centre.offset(f)).getBlock() == WizardryBlocks.receptacle)){ + WizardryAdvancementTriggers.restore_imbuement_altar.triggerFor((EntityPlayer)placer); + } + } + } + + @Nullable + @Override + public TileEntity createNewTileEntity(World world, int meta){ + return new TileEntityReceptacle(); + } + + @Override + public boolean hasComparatorInputOverride(IBlockState state){ + return true; + } + + @Override + public int getComparatorInputOverride(IBlockState state, World world, BlockPos pos){ + + TileEntity tileEntity = world.getTileEntity(pos); + + if(tileEntity instanceof TileEntityReceptacle){ + Element element = ((TileEntityReceptacle)tileEntity).getElement(); + return element == null ? 0 : element.ordinal() + 1; + } + + return super.getComparatorInputOverride(state, world, pos); + } + + +} diff --git a/src/main/java/electroblob/wizardry/block/BlockStatue.java b/src/main/java/electroblob/wizardry/block/BlockStatue.java index cf5b7dee..b8d2ee40 100644 --- a/src/main/java/electroblob/wizardry/block/BlockStatue.java +++ b/src/main/java/electroblob/wizardry/block/BlockStatue.java @@ -1,13 +1,14 @@ package electroblob.wizardry.block; import electroblob.wizardry.tileentity.TileEntityStatue; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.BlockUtils; import net.minecraft.block.Block; 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.entity.EntityLivingBase; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.BlockRenderLayer; import net.minecraft.util.EnumBlockRenderType; @@ -19,6 +20,7 @@ import net.minecraft.world.World; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; +import javax.annotation.Nullable; import java.util.Random; public class BlockStatue extends Block implements ITileEntityProvider { @@ -180,77 +182,77 @@ public class BlockStatue extends Block implements ITileEntityProvider { /** * 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 target The entity to turn into a statue. + * @param caster The entity that caused it, or null if it was not caused by an entity * @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){ + public boolean convertToStatue(EntityLiving target, @Nullable EntityLivingBase caster, int duration){ - if(entity.deathTime > 0) return false; + if(target.deathTime > 0) return false; - BlockPos pos = new BlockPos(entity); - World world = entity.world; + BlockPos pos = new BlockPos(target); + World world = target.world; - entity.hurtTime = 0; // Stops the entity looking red while frozen and the resulting z-fighting - entity.extinguish(); + target.hurtTime = 0; // Stops the entity looking red while frozen and the resulting z-fighting + target.extinguish(); // Short mobs such as spiders and pigs - if((entity.height < 1.2 || entity.isChild()) && WizardryUtilities.canBlockBeReplaced(world, pos)){ + if((target.height < 1.2 || target.isChild()) && BlockUtils.canBlockBeReplaced(world, pos) && BlockUtils.canPlaceBlock(caster, 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)).setCreatureAndPart(target, 1, 1); ((TileEntityStatue)world.getTileEntity(pos)).setLifetime(duration); } - entity.getEntityData().setBoolean(this.isIce ? FROZEN_NBT_KEY : PETRIFIED_NBT_KEY, true); - entity.setDead(); + target.getEntityData().setBoolean(this.isIce ? FROZEN_NBT_KEY : PETRIFIED_NBT_KEY, true); + target.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())){ + else if(target.height < 2.5 && BlockUtils.canBlockBeReplaced(world, pos) && BlockUtils.canBlockBeReplaced(world, pos.up()) + && BlockUtils.canPlaceBlock(caster, world, pos) && BlockUtils.canPlaceBlock(caster, 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)).setCreatureAndPart(target, 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); + ((TileEntityStatue)world.getTileEntity(pos.up())).setCreatureAndPart(target, 2, 2); } - entity.getEntityData().setBoolean(this.isIce ? FROZEN_NBT_KEY : PETRIFIED_NBT_KEY, true); - entity.setDead(); + target.getEntityData().setBoolean(this.isIce ? FROZEN_NBT_KEY : PETRIFIED_NBT_KEY, true); + target.setDead(); return true; } // Tall mobs like endermen - else if(WizardryUtilities.canBlockBeReplaced(world, pos) - && WizardryUtilities.canBlockBeReplaced(world, pos.up()) - && WizardryUtilities.canBlockBeReplaced(world, pos.up(2))){ + else if(BlockUtils.canBlockBeReplaced(world, pos) && BlockUtils.canBlockBeReplaced(world, pos.up()) && BlockUtils.canBlockBeReplaced(world, pos.up(2)) + && BlockUtils.canPlaceBlock(caster, world, pos) && BlockUtils.canPlaceBlock(caster, world, pos.up()) && BlockUtils.canPlaceBlock(caster, 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)).setCreatureAndPart(target, 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); + ((TileEntityStatue)world.getTileEntity(pos.up())).setCreatureAndPart(target, 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); + ((TileEntityStatue)world.getTileEntity(pos.up(2))).setCreatureAndPart(target, 3, 3); } - entity.getEntityData().setBoolean(this.isIce ? FROZEN_NBT_KEY : PETRIFIED_NBT_KEY, true); - entity.setDead(); + target.getEntityData().setBoolean(this.isIce ? FROZEN_NBT_KEY : PETRIFIED_NBT_KEY, true); + target.setDead(); return true; } diff --git a/src/main/java/electroblob/wizardry/block/BlockThorns.java b/src/main/java/electroblob/wizardry/block/BlockThorns.java index 8e22c841..2a671346 100644 --- a/src/main/java/electroblob/wizardry/block/BlockThorns.java +++ b/src/main/java/electroblob/wizardry/block/BlockThorns.java @@ -3,8 +3,9 @@ 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.tileentity.TileEntityThorns; import electroblob.wizardry.util.AllyDesignationSystem; +import electroblob.wizardry.util.EntityUtils; import electroblob.wizardry.util.MagicDamage; import net.minecraft.block.*; import net.minecraft.block.BlockDoublePlant.EnumBlockHalf; @@ -22,8 +23,10 @@ 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.ChunkCache; import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; import net.minecraftforge.event.entity.player.PlayerInteractEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; @@ -34,6 +37,7 @@ import java.util.Random; public class BlockThorns extends BlockBush implements ITileEntityProvider { public static final int GROWTH_STAGES = 8; + public static final int GROWTH_STAGE_DURATION = 2; public static final PropertyInteger AGE = PropertyInteger.create("age", 0, GROWTH_STAGES-1); public static final PropertyEnum HALF = PropertyEnum.create("half", EnumBlockHalf.class); @@ -52,22 +56,27 @@ public class BlockThorns extends BlockBush implements ITileEntityProvider { @Override public IBlockState getStateFromMeta(int meta){ - return this.getDefaultState().withProperty(HALF, EnumBlockHalf.values()[meta / GROWTH_STAGES]).withProperty(AGE, meta % GROWTH_STAGES); + return this.getDefaultState().withProperty(HALF, meta == 0 ? EnumBlockHalf.LOWER : EnumBlockHalf.UPPER); } @Override public int getMetaFromState(IBlockState state){ - return state.getValue(HALF).ordinal() * GROWTH_STAGES + state.getValue(AGE); + return state.getValue(HALF).ordinal(); } -// @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 + public IBlockState getActualState(IBlockState state, IBlockAccess world, BlockPos pos){ + + if(state.getValue(HALF) == EnumBlockHalf.UPPER) pos = pos.down(); + // Copied from BlockFlowerPot on authority of the Forge docs, which say it needs to be here + TileEntity tileentity = world instanceof ChunkCache ? ((ChunkCache)world).getTileEntity(pos, Chunk.EnumCreateEntityType.CHECK) : world.getTileEntity(pos); + + if(tileentity instanceof TileEntityThorns){ + return state.withProperty(AGE, ((TileEntityThorns)tileentity).getAge()); + }else{ + return state.withProperty(AGE, 7); + } + } @Override protected BlockStateContainer createBlockState(){ @@ -107,47 +116,36 @@ public class BlockThorns extends BlockBush implements ITileEntityProvider { } } -// @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(); - } + if(applyThornDamage(world, pos, state, entity)){ + entity.setInWeb(); // Needs to be called client-side for players (and besides, all of this is common code) } } - private static boolean applyThornDamage(World world, BlockPos pos, Entity target){ + private static boolean applyThornDamage(World world, BlockPos pos, IBlockState state, Entity target){ DamageSource source = DamageSource.CACTUS; + float damage = Spells.forest_of_thorns.getProperty(Spell.DAMAGE).floatValue(); - TileEntity tileentity = world.getTileEntity(pos); + TileEntity tileentity = world.getTileEntity(state.getValue(HALF) == EnumBlockHalf.UPPER ? pos.down() : pos); - if(tileentity instanceof TileEntityPlayerSaveTimed){ + if(tileentity instanceof TileEntityThorns){ - EntityLivingBase caster = ((TileEntityPlayerSaveTimed)tileentity).getCaster(); + damage *= ((TileEntityThorns)tileentity).damageMultiplier; - if(caster != null && AllyDesignationSystem.isValidTarget(caster, target)){ + EntityLivingBase caster = ((TileEntityThorns)tileentity).getCaster(); + + if(!AllyDesignationSystem.isValidTarget(caster, target)) return false; // Don't attack or slow allies of the caster + + if(caster != null){ source = MagicDamage.causeDirectMagicDamage(caster, 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()); + if(target.ticksExisted % 20 == 0){ + EntityUtils.attackEntityWithoutKnockback(target, source, damage); + } return true; } @@ -159,12 +157,12 @@ public class BlockThorns extends BlockBush implements ITileEntityProvider { @Override public TileEntity createNewTileEntity(World world, int metadata){ - return new TileEntityPlayerSaveTimed(600); + return new TileEntityThorns(); } @Override public boolean hasTileEntity(IBlockState state){ - return true; + return state.getValue(HALF) == EnumBlockHalf.LOWER; } @Override public boolean isReplaceable(IBlockAccess world, BlockPos pos){ return false; } @@ -174,9 +172,8 @@ public class BlockThorns extends BlockBush implements ITileEntityProvider { @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()); + if(!event.getWorld().isRemote && event.getWorld().getBlockState(event.getPos()).getBlock() == WizardryBlocks.thorns){ + applyThornDamage(event.getWorld(), event.getPos(), event.getWorld().getBlockState(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 163f043e..f72ea195 100644 --- a/src/main/java/electroblob/wizardry/block/BlockTransportationStone.java +++ b/src/main/java/electroblob/wizardry/block/BlockTransportationStone.java @@ -8,9 +8,9 @@ import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.spell.Transportation; +import electroblob.wizardry.util.GeometryUtils; 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; @@ -162,7 +162,7 @@ public class BlockTransportationStone extends Block { 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) + .pos(GeometryUtils.getCentre(centre).add(x, -0.3125, z)).clr(0x86ff65) .time(200).scale(2).spawn(world); } } @@ -174,10 +174,11 @@ public class BlockTransportationStone extends Block { return false; } - /** Returns whether the specified location is surrounded by a complete cicle of 8 transportation stones. */ + /** Returns whether the specified location is surrounded by a complete circle of 8 transportation stones. */ public static boolean testForCircle(World world, BlockPos pos){ - if(world.getBlockState(pos).getMaterial().blocksMovement()) return false; + if(world.getBlockState(pos).getMaterial().blocksMovement() || world.getBlockState(pos.up()).getMaterial() + .blocksMovement()) return false; for(int x = -1; x <= 1; x++){ for(int z = -1; z <= 1; z++){ diff --git a/src/main/java/electroblob/wizardry/client/ClientProxy.java b/src/main/java/electroblob/wizardry/client/ClientProxy.java index 07155cb6..5086b6a0 100644 --- a/src/main/java/electroblob/wizardry/client/ClientProxy.java +++ b/src/main/java/electroblob/wizardry/client/ClientProxy.java @@ -2,9 +2,14 @@ package electroblob.wizardry.client; import electroblob.wizardry.CommonProxy; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.block.BlockBookshelf; +import electroblob.wizardry.client.animation.ActionAnimation; +import electroblob.wizardry.client.animation.PlayerAnimator; import electroblob.wizardry.client.audio.MovingSoundEntity; +import electroblob.wizardry.client.audio.MovingSoundSpellCharge; import electroblob.wizardry.client.audio.SoundLoop; import electroblob.wizardry.client.audio.SoundLoopSpell; +import electroblob.wizardry.client.gui.GuiLectern; import electroblob.wizardry.client.gui.GuiSpellDisplay; import electroblob.wizardry.client.gui.config.NamedBooleanEntry; import electroblob.wizardry.client.gui.config.SpellHUDSkinChooserEntry; @@ -12,8 +17,12 @@ import electroblob.wizardry.client.gui.handbook.GuiWizardHandbook; import electroblob.wizardry.client.model.ModelWizardArmour; import electroblob.wizardry.client.particle.*; import electroblob.wizardry.client.particle.ParticleWizardry.IWizardryParticleFactory; -import electroblob.wizardry.client.renderer.*; +import electroblob.wizardry.client.renderer.entity.*; +import electroblob.wizardry.client.renderer.entity.layers.*; +import electroblob.wizardry.client.renderer.overlay.RenderBlinkEffect; +import electroblob.wizardry.client.renderer.tileentity.*; import electroblob.wizardry.command.SpellEmitter; +import electroblob.wizardry.constants.Constants; import electroblob.wizardry.data.DispenserCastingData; import electroblob.wizardry.data.SpellEmitterData; import electroblob.wizardry.data.SpellGlyphData; @@ -32,10 +41,7 @@ import electroblob.wizardry.packet.*; import electroblob.wizardry.registry.Spells; 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.tileentity.*; import electroblob.wizardry.util.ParticleBuilder; import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.WandHelper; @@ -72,6 +78,8 @@ 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.client.settings.KeyConflictContext; +import net.minecraftforge.client.settings.KeyModifier; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.config.Property; import net.minecraftforge.fml.client.config.GuiConfigEntries.NumberSliderEntry; @@ -79,6 +87,7 @@ import net.minecraftforge.fml.client.registry.ClientRegistry; import net.minecraftforge.fml.client.registry.RenderingRegistry; import org.lwjgl.input.Keyboard; +import javax.annotation.Nullable; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.List; @@ -100,8 +109,16 @@ public class ClientProxy extends CommonProxy { 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); + public static final KeyBinding NEXT_SPELL = new KeyBinding("key." + Wizardry.MODID + ".next_spell", KeyConflictContext.IN_GAME, Keyboard.KEY_N, "key.categories." + Wizardry.MODID); + public static final KeyBinding PREVIOUS_SPELL = new KeyBinding("key." + Wizardry.MODID + ".previous_spell", KeyConflictContext.IN_GAME, Keyboard.KEY_B, "key.categories." + Wizardry.MODID); + public static final KeyBinding[] SPELL_QUICK_ACCESS = new KeyBinding[ItemWand.BASE_SPELL_SLOTS + Constants.UPGRADE_STACK_LIMIT]; + + static { + for(int i = 0; i < SPELL_QUICK_ACCESS.length; i++){ + SPELL_QUICK_ACCESS[i] = new KeyBinding("key." + Wizardry.MODID + ".spell_" + (i+1), + KeyConflictContext.IN_GAME, KeyModifier.ALT, Keyboard.KEY_1 + i, "key.categories." + Wizardry.MODID); + } + } // Armour Model public static final ModelBiped WIZARD_ARMOUR_MODEL = new ModelWizardArmour(0.75f); @@ -109,7 +126,7 @@ public class ClientProxy extends CommonProxy { /** The wrap width for standard multi-line descriptions (see {@link ClientProxy#addMultiLineDescription(List, String, Style, Object...)}). */ private static final int TOOLTIP_WRAP_WIDTH = 140; - // SECTION Registry + // Registry // =============================================================================================================== @Override @@ -121,6 +138,7 @@ public class ClientProxy extends CommonProxy { public void registerKeyBindings(){ ClientRegistry.registerKeyBinding(NEXT_SPELL); ClientRegistry.registerKeyBinding(PREVIOUS_SPELL); + for(KeyBinding key : SPELL_QUICK_ACCESS) ClientRegistry.registerKeyBinding(key); } @Override @@ -128,7 +146,7 @@ public class ClientProxy extends CommonProxy { mixedFontRenderer = new MixedFontRenderer(Minecraft.getMinecraft().gameSettings, new ResourceLocation("textures/font/ascii.png"), Minecraft.getMinecraft().renderEngine, false); } - + @Override public void registerResourceReloadListeners(){ IResourceManager manager = Minecraft.getMinecraft().getResourceManager(); @@ -147,14 +165,14 @@ public class ClientProxy extends CommonProxy { WizardryAntiqueAtlasIntegration.registerMarkers(); } - // SECTION Misc + // Misc // =============================================================================================================== @Override public void setToNumberSliderEntry(Property property){ property.setConfigEntryClass(NumberSliderEntry.class); } - + @Override public void setToHUDChooserEntry(Property property){ property.setConfigEntryClass(SpellHUDSkinChooserEntry.class); @@ -171,32 +189,23 @@ public class ClientProxy extends CommonProxy { } @Override - 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)); + public EntityPlayer getThePlayer(){ + return Minecraft.getMinecraft().player; } @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)); - } + public boolean isFirstPerson(Entity entity){ + return entity == Minecraft.getMinecraft().getRenderViewEntity() && Minecraft.getMinecraft().gameSettings.thirdPersonView == 0; } @Override public void playBlinkEffect(EntityPlayer player){ - if(Minecraft.getMinecraft().player == player) WizardryClientEventHandler.playBlinkEffect(); + if(Minecraft.getMinecraft().player == player) RenderBlinkEffect.playBlinkEffect(); } @Override public void shakeScreen(EntityPlayer player, float intensity){ - if(Minecraft.getMinecraft().player == player) WizardryClientEventHandler.shakeScreen(intensity); + if(Minecraft.getMinecraft().player == player) ScreenShakeHandler.shakeScreen(intensity); } @Override @@ -211,17 +220,61 @@ public class ClientProxy extends CommonProxy { return GuiSpellDisplay.getSkinKeys(); } - // SECTION Items + @Override + public void notifyBookshelfChange(World world, BlockPos pos){ + + super.notifyBookshelfChange(world, pos); + + EntityPlayer player = Minecraft.getMinecraft().player; + + if(player.getDistanceSq(pos) < BlockBookshelf.PLAYER_NOTIFY_RANGE * BlockBookshelf.PLAYER_NOTIFY_RANGE){ + if(Minecraft.getMinecraft().currentScreen instanceof GuiLectern){ + ((GuiLectern)Minecraft.getMinecraft().currentScreen).refreshAvailableSpells(); + } + } + } + + // Sound // =============================================================================================================== @Override - public boolean shouldDisplayDiscovered(Spell spell, ItemStack stack){ + 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 playChargeupSound(EntityLivingBase entity){ + Minecraft.getMinecraft().getSoundHandler().playSound(new MovingSoundSpellCharge(entity, WizardrySounds.ITEM_WAND_CHARGEUP, WizardrySounds.SPELLS, 2.5f, 1.4f, false)); + } + + @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)); + } + } + + // Items + // =============================================================================================================== + + @Override + public boolean shouldDisplayDiscovered(Spell spell, @Nullable ItemStack stack){ EntityPlayerSP player = Minecraft.getMinecraft().player; if(player == null) return false; // Displayed recipe + // Weirdly, the gui is actually the only way of accessing the current IMerchant or their recipes, other than a + // brute-force search through all the entities in the world to find the merchant interacting with the player + // Since we only need this client-side anyway, we might as well go via the gui 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 @@ -298,12 +351,16 @@ public class ClientProxy extends CommonProxy { } @Override - public void addMultiLineDescription(List tooltip, String key, Style style, Object... args){ - String description = style.getFormattingCode() + I18n.format(key, args); - tooltip.addAll(Minecraft.getMinecraft().fontRenderer.listFormattedStringToWidth(description, TOOLTIP_WRAP_WIDTH)); + public String translate(String key, Style style, Object... args){ + return style.getFormattingCode() + I18n.format(key, args); } - // SECTION Particles + @Override + public void addMultiLineDescription(List tooltip, String key, Style style, Object... args){ + tooltip.addAll(Minecraft.getMinecraft().fontRenderer.listFormattedStringToWidth(translate(key, style, args), TOOLTIP_WRAP_WIDTH)); + } + + // Particles // =============================================================================================================== /** Use {@link ParticleWizardry#registerParticle(ResourceLocation, IWizardryParticleFactory)}, this is internal. */ @@ -316,10 +373,13 @@ public class ClientProxy extends CommonProxy { 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.BLOCK_HIGHLIGHT, ParticleBlockHighlight::new); ParticleWizardry.registerParticle(Type.BUFF, ParticleBuff::new); + ParticleWizardry.registerParticle(Type.CLOUD, ParticleCloud::new); ParticleWizardry.registerParticle(Type.DARK_MAGIC, ParticleDarkMagic::new); ParticleWizardry.registerParticle(Type.DUST, ParticleDust::new); ParticleWizardry.registerParticle(Type.FLASH, ParticleFlash::new); + ParticleWizardry.registerParticle(Type.GUARDIAN_BEAM, ParticleGuardianBeam::new); ParticleWizardry.registerParticle(Type.ICE, ParticleIce::new); ParticleWizardry.registerParticle(Type.LEAF, ParticleLeaf::new); ParticleWizardry.registerParticle(Type.LIGHTNING, ParticleLightning::new); @@ -332,10 +392,9 @@ public class ClientProxy extends CommonProxy { 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); @@ -352,7 +411,7 @@ public class ClientProxy extends CommonProxy { Minecraft.getMinecraft().effectRenderer.addEffect(new ParticleTornado(world, maxAge, x, z, radius, y, velX, velZ, block).setBlockPos(pos));// , world.rand.nextInt(6))); } - // SECTION Packet Handlers + // Packet Handlers // =============================================================================================================== @Override @@ -455,72 +514,74 @@ public class ClientProxy extends CommonProxy { 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){ World world = Minecraft.getMinecraft().world; - Entity caster = world.getEntityByID(message.casterID); + BlockPos pos = message.destination; - if(caster == null) return; // Shouldn't happen + Entity entity = world.getEntityByID(message.dismountEntityID); + if(message.dismountEntityID != -1 && entity != null) entity.dismountRidingEntity(); // Moved from when the packet is sent to when it is received; fixes the sound not playing in first person. - caster.playSound(WizardrySounds.SPELL_TRANSPORTATION_TRAVEL, 1, 1); + // Changed to a position to avoid syncing issues + world.playSound(pos.getX(), pos.getY(), pos.getZ(), WizardrySounds.SPELL_TRANSPORTATION_TRAVEL, WizardrySounds.SPELLS, 1, 1, false); for(int i = 0; i < 20; i++){ double radius = 1; 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 * MathHelper.sin(angle); + double x = pos.getX() + 0.5 + radius * MathHelper.cos(angle); + double y = pos.getY() + world.rand.nextDouble() * 2; + double z = pos.getZ() + 0.5 + 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; 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 * MathHelper.sin(angle); + double x = pos.getX() + 0.5 + radius * MathHelper.cos(angle); + double y = pos.getY() + world.rand.nextDouble() * 2; + double z = pos.getZ() + 0.5 + 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; 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 * MathHelper.sin(angle); + double x = pos.getX() + 0.5 + radius * MathHelper.cos(angle); + double y = pos.getY() + world.rand.nextDouble() * 2; + double z = pos.getZ() + 0.5 + radius * MathHelper.sin(angle); world.spawnParticle(EnumParticleTypes.ENCHANTMENT_TABLE, x, y, z, 0, 0.02, 0); } } @@ -639,7 +700,7 @@ public class ClientProxy extends CommonProxy { }else Wizardry.logger.warn("Received a PacketConquerShrine, but there was no shrine core at the position sent"); } - // SECTION Rendering + // Rendering // =============================================================================================================== private static final ResourceLocation ICE_WRAITH_TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/entity/ice_wraith.png"); @@ -650,8 +711,17 @@ public class ClientProxy extends CommonProxy { @Override public void initialiseLayers(){ - LayerStone.initialiseLayers(); - LayerFrost.initialiseLayers(); + LayerTiledOverlay.initialiseLayers(LayerStone::new); + LayerTiledOverlay.initialiseLayers(LayerFrost::new); + LayerTiledOverlay.initialiseLayers(LayerMindControl::new); + LayerTiledOverlay.initialiseLayers(LayerSummonAnimation::new); + LayerTiledOverlay.initialiseLayers(LayerDisintegrateAnimation::new); + } + + @Override + public void initialiseAnimations(){ + PlayerAnimator.init(); + ActionAnimation.register(); } @Override @@ -695,7 +765,9 @@ public class ClientProxy extends CommonProxy { RenderingRegistry.registerEntityRenderingHandler(EntityDart.class, manager -> new RenderMagicArrow(manager, new ResourceLocation(Wizardry.MODID, "textures/entity/dart.png"), false, 8.0, 2.0, 16, 5, true)); RenderingRegistry.registerEntityRenderingHandler(EntityIceLance.class, manager -> new RenderMagicArrow(manager, - new ResourceLocation(Wizardry.MODID, "textures/entity/ice_lance.png"), false, 16.0, 3.0, 22, 5, true)); + new ResourceLocation(Wizardry.MODID, "textures/entity/ice_lance.png"), false, 16.0, 3.0, 22, 5, false)); + RenderingRegistry.registerEntityRenderingHandler(EntityFlamecatcherArrow.class, manager -> new RenderMagicArrow(manager, + new ResourceLocation(Wizardry.MODID, "textures/entity/flamecatcher_arrow.png"), false, 8, 2.0, 16, 5, true)); RenderingRegistry.registerEntityRenderingHandler(EntityForceArrow.class, RenderForceArrow::new); @@ -705,6 +777,7 @@ public class ClientProxy extends CommonProxy { RenderingRegistry.registerEntityRenderingHandler(EntityWizard.class, RenderWizard::new); RenderingRegistry.registerEntityRenderingHandler(EntityEvilWizard.class, RenderEvilWizard::new); RenderingRegistry.registerEntityRenderingHandler(EntityDecoy.class, RenderDecoy::new); + RenderingRegistry.registerEntityRenderingHandler(EntityRemnant.class, RenderRemnant::new); // Throwables RenderingRegistry.registerEntityRenderingHandler(EntitySparkBomb.class, @@ -743,6 +816,11 @@ public class ClientProxy extends CommonProxy { RenderingRegistry.registerEntityRenderingHandler(EntityHammer.class, RenderHammer::new); RenderingRegistry.registerEntityRenderingHandler(EntityIceSpike.class, RenderIceSpike::new); RenderingRegistry.registerEntityRenderingHandler(EntityForcefield.class, RenderForcefield::new); + RenderingRegistry.registerEntityRenderingHandler(EntityZombieSpawner.class, RenderZombieSpawner::new); + RenderingRegistry.registerEntityRenderingHandler(EntityRadiantTotem.class, RenderRadiantTotem::new); + RenderingRegistry.registerEntityRenderingHandler(EntityBoulder.class, RenderBoulder::new); + RenderingRegistry.registerEntityRenderingHandler(EntityWitheringTotem.class, RenderWitheringTotem::new); + RenderingRegistry.registerEntityRenderingHandler(EntityIceBarrier.class, RenderIceBarrier::new); //RenderingRegistry.registerEntityRenderingHandler(EntityContainmentField.class, RenderContainmentField::new); // Stuff that doesn't render @@ -754,26 +832,29 @@ public class ClientProxy extends CommonProxy { RenderingRegistry.registerEntityRenderingHandler(EntityStormElemental.class, RenderBlank::new); RenderingRegistry.registerEntityRenderingHandler(EntityEarthquake.class, RenderBlank::new); RenderingRegistry.registerEntityRenderingHandler(EntityHailstorm.class, RenderBlank::new); + RenderingRegistry.registerEntityRenderingHandler(EntityStormcloud.class, RenderBlank::new); // Runes on ground RenderingRegistry.registerEntityRenderingHandler(EntityHealAura.class, - manager -> new RenderSigil(manager, new ResourceLocation(Wizardry.MODID, "textures/entity/healing_aura.png"), 5.0f, false)); + manager -> new RenderSigil(manager, new ResourceLocation(Wizardry.MODID, "textures/entity/healing_aura.png"), 0.3f, false)); RenderingRegistry.registerEntityRenderingHandler(EntityFireSigil.class, - manager -> new RenderSigil(manager, new ResourceLocation(Wizardry.MODID, "textures/entity/fire_sigil.png"), 2.0f, true)); + manager -> new RenderSigil(manager, new ResourceLocation(Wizardry.MODID, "textures/entity/fire_sigil.png"), 0, true)); RenderingRegistry.registerEntityRenderingHandler(EntityFrostSigil.class, - manager -> new RenderSigil(manager, new ResourceLocation(Wizardry.MODID, "textures/entity/frost_sigil.png"), 2.0f, true)); + manager -> new RenderSigil(manager, new ResourceLocation(Wizardry.MODID, "textures/entity/frost_sigil.png"), 0, true)); RenderingRegistry.registerEntityRenderingHandler(EntityLightningSigil.class, - manager -> new RenderSigil(manager, new ResourceLocation(Wizardry.MODID, "textures/entity/lightning_sigil.png"), 2.0f, true)); + manager -> new RenderSigil(manager, new ResourceLocation(Wizardry.MODID, "textures/entity/lightning_sigil.png"), 0, true)); RenderingRegistry.registerEntityRenderingHandler(EntityFireRing.class, - manager -> new RenderFireRing(manager, new ResourceLocation(Wizardry.MODID, "textures/entity/ring_of_fire.png"), 5.0f)); + manager -> new RenderFireRing(manager, new ResourceLocation(Wizardry.MODID, "textures/entity/ring_of_fire.png"))); RenderingRegistry.registerEntityRenderingHandler(EntityDecay.class, RenderDecay::new); RenderingRegistry.registerEntityRenderingHandler(EntityCombustionRune.class, - manager -> new RenderSigil(manager, new ResourceLocation(Wizardry.MODID, "textures/entity/combustion_rune.png"), 2.0f, true)); + manager -> new RenderSigil(manager, new ResourceLocation(Wizardry.MODID, "textures/entity/combustion_rune.png"), 0, true)); // TESRs ClientRegistry.bindTileEntitySpecialRenderer(TileEntityArcaneWorkbench.class, new RenderArcaneWorkbench()); ClientRegistry.bindTileEntitySpecialRenderer(TileEntityStatue.class, renderStatue = new RenderStatue()); ClientRegistry.bindTileEntitySpecialRenderer(TileEntityMagicLight.class, new RenderMagicLight()); + ClientRegistry.bindTileEntitySpecialRenderer(TileEntityLectern.class, new RenderLectern()); + ClientRegistry.bindTileEntitySpecialRenderer(TileEntityImbuementAltar.class, new RenderImbuementAltar()); } } \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/client/DrawingUtils.java b/src/main/java/electroblob/wizardry/client/DrawingUtils.java index 20b99a60..865718c4 100644 --- a/src/main/java/electroblob/wizardry/client/DrawingUtils.java +++ b/src/main/java/electroblob/wizardry/client/DrawingUtils.java @@ -9,9 +9,10 @@ import net.minecraft.item.ItemStack; import net.minecraft.util.math.MathHelper; import org.lwjgl.opengl.GL11; +import java.util.Random; + /** - * 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. + * Utility class containing some useful static methods for drawing GUIs, as well as general rendering. * * @author Electroblob * @since Wizardry 4.2 @@ -124,6 +125,29 @@ public final class DrawingUtils { tessellator.draw(); } + /** + * Draws a 'glitch' rectangle, with some rows of pixels shifted randomly to give a broken effect. + * + * @param random A random number generator to use + * @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 drawGlitchRect(Random random, int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight, boolean flipX, boolean flipY){ + for(int i=0; i= left - 1 && x < left + width + 1 && y >= top - 1 && y < top + height + 1; + } + } diff --git a/src/main/java/electroblob/wizardry/client/ScreenShakeHandler.java b/src/main/java/electroblob/wizardry/client/ScreenShakeHandler.java new file mode 100644 index 00000000..fb825170 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/ScreenShakeHandler.java @@ -0,0 +1,42 @@ +package electroblob.wizardry.client; + +import electroblob.wizardry.Wizardry; +import net.minecraft.client.Minecraft; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import net.minecraftforge.fml.relauncher.Side; + +@EventBusSubscriber(Side.CLIENT) +public class ScreenShakeHandler { + + /** The remaining time for which the screen shake effect will be active. */ + private static int screenShakeCounter = 0; + private static final float SHAKINESS = 0.5f; + + /** Starts the client-side screen shake effect. */ + public static void shakeScreen(float intensity){ + if(Wizardry.settings.screenShake){ + screenShakeCounter = (int)(intensity / SHAKINESS); + Minecraft.getMinecraft().player.rotationPitch -= intensity * 0.5f; // Start halfway down + } + } + + @SubscribeEvent + public static void onPlayerTickEvent(TickEvent.PlayerTickEvent event){ + + if(event.player == Minecraft.getMinecraft().player && event.phase == TickEvent.Phase.END){ + + if(Wizardry.settings.screenShake){ + if(screenShakeCounter > 0){ + float magnitude = screenShakeCounter * SHAKINESS; + Minecraft.getMinecraft().player.rotationPitch += screenShakeCounter % 2 == 0 ? magnitude : -magnitude; + screenShakeCounter--; + } + }else{ + screenShakeCounter = 0; + } + } + } + +} diff --git a/src/main/java/electroblob/wizardry/client/WizardryClientEventHandler.java b/src/main/java/electroblob/wizardry/client/WizardryClientEventHandler.java index 5084198a..9e3e8f0c 100644 --- a/src/main/java/electroblob/wizardry/client/WizardryClientEventHandler.java +++ b/src/main/java/electroblob/wizardry/client/WizardryClientEventHandler.java @@ -1,41 +1,35 @@ package electroblob.wizardry.client; -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.block.BlockMagicLight; -import electroblob.wizardry.constants.Constants; +import electroblob.wizardry.client.renderer.overlay.RenderBlinkEffect; 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.ItemFlamecatcher; import electroblob.wizardry.item.ItemSpectralBow; +import electroblob.wizardry.item.ItemWand; import electroblob.wizardry.potion.PotionSlowTime; -import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryPotions; -import electroblob.wizardry.spell.*; -import electroblob.wizardry.util.RayTracer; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.spell.Possession; +import electroblob.wizardry.spell.SixthSense; +import electroblob.wizardry.spell.SlowTime; +import electroblob.wizardry.spell.Transience; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiMerchant; +import net.minecraft.client.gui.ScaledResolution; 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.RenderManager; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.item.EntityArmorStand; -import net.minecraft.inventory.Slot; -import net.minecraft.item.ItemStack; +import net.minecraft.item.Item; import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntityDispenser; import net.minecraft.util.ResourceLocation; -import net.minecraft.util.math.RayTraceResult; -import net.minecraft.village.MerchantRecipe; import net.minecraft.world.World; -import net.minecraftforge.client.event.*; +import net.minecraftforge.client.event.FOVUpdateEvent; +import net.minecraftforge.client.event.InputUpdateEvent; +import net.minecraftforge.client.event.MouseEvent; +import net.minecraftforge.client.event.RenderHandEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.gameevent.TickEvent; @@ -46,8 +40,9 @@ import java.util.ArrayList; import java.util.List; /** - * Event handler responsible for client-side only events, mostly rendering. - * + * General-purpose client-side event handler for things that don't fit anywhere else or groups of related behaviours + * that are better kept together. + * * @author Electroblob * @since Wizardry 1.0 */ @@ -55,67 +50,19 @@ import java.util.List; @Mod.EventBusSubscriber(Side.CLIENT) public final class WizardryClientEventHandler { - 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"); + private WizardryClientEventHandler(){} // No instances! - /** 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; + // Tick Events + // =============================================================================================================== - /** The remaining time for which the screen shake effect will be active. */ - private static int screenShakeCounter = 0; - private static final float SHAKINESS = 0.5f; - - /** Starts the first-person blink overlay effect. */ - public static void playBlinkEffect(){ - if(Wizardry.settings.blinkEffect) blinkEffectTimer = BLINK_EFFECT_DURATION; - } - - /** Starts the client-side screen shake effect. */ - public static void shakeScreen(float intensity){ - if(Wizardry.settings.screenShake){ - screenShakeCounter = (int)(intensity / SHAKINESS); - Minecraft.getMinecraft().player.rotationPitch -= intensity * 0.5f; // Start halfway down - } - } - @SubscribeEvent public static void onPlayerTickEvent(TickEvent.PlayerTickEvent event){ if(event.player == Minecraft.getMinecraft().player && event.phase == TickEvent.Phase.END){ - if(Wizardry.settings.blinkEffect){ - if(blinkEffectTimer > 0) blinkEffectTimer--; - }else{ - blinkEffectTimer = 0; - } - - if(Wizardry.settings.screenShake){ - if(screenShakeCounter > 0){ - float magnitude = screenShakeCounter * SHAKINESS; - Minecraft.getMinecraft().player.rotationPitch += screenShakeCounter % 2 == 0 ? magnitude : -magnitude; - screenShakeCounter--; - } - }else{ - screenShakeCounter = 0; - } - - // 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){ + if(Minecraft.getMinecraft().entityRenderer.getShaderGroup() != null){ // IntelliJ is wrong, this can be null String activeShader = Minecraft.getMinecraft().entityRenderer.getShaderGroup().getShaderGroupName(); @@ -124,41 +71,16 @@ public final class WizardryClientEventHandler { || (activeShader.equals(Transience.SHADER.toString()) && !Minecraft.getMinecraft().player.isPotionActive(WizardryPotions.transience))){ if(activeShader.equals(SixthSense.SHADER.toString()) - || activeShader.equals(Transience.SHADER.toString())) playBlinkEffect(); + || activeShader.equals(Transience.SHADER.toString())) RenderBlinkEffect.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); - } - } - } - - // 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; - } - } + // The classes referenced here used to handle their own client tick events, but since they're not client-only + // classes, that crashed the dedicated server... @SubscribeEvent public static void onClientTickEvent(TickEvent.ClientTickEvent event){ @@ -183,10 +105,13 @@ public final class WizardryClientEventHandler { PotionSlowTime.cleanUpEntities(world); } } - + + // Input and Controls + // =============================================================================================================== + @SubscribeEvent public static void onMouseEvent(MouseEvent event){ - + // Prevents the player looking around when paralysed if(Minecraft.getMinecraft().player.isPotionActive(WizardryPotions.paralysis) && Minecraft.getMinecraft().inGameHasFocus){ @@ -198,267 +123,76 @@ public final class WizardryClientEventHandler { } } + // This event is called every tick, not just when a movement key is pressed @SubscribeEvent - public static void onFOVUpdateEvent(FOVUpdateEvent event){ - - // Bow zoom. Taken directly from AbstractClientPlayer so it works exactly like vanilla. - if(event.getEntity().isHandActive() && event.getEntity().getActiveItemStack().getItem() instanceof ItemSpectralBow){ - - int maxUseTicks = event.getEntity().getItemInUseMaxCount(); - - float maxUseSeconds = (float)maxUseTicks / 20.0F; - - if(maxUseSeconds > 1.0F){ - maxUseSeconds = 1.0F; - }else{ - maxUseSeconds = maxUseSeconds * maxUseSeconds; - } - - event.setNewfov(event.getFov() * 1.0F - maxUseSeconds * 0.15F); + 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; } - - if(blinkEffectTimer > 0){ - float f = ((float)Math.max(blinkEffectTimer - 2, 0))/BLINK_EFFECT_DURATION; - event.setNewfov(event.getFov() + f * f * 0.7f); + + if(ItemArtefact.isArtefactActive(event.getEntityPlayer(), WizardryItems.charm_move_speed) + && event.getEntityPlayer().isHandActive() + && event.getEntityPlayer().getActiveItemStack().getItem() instanceof ItemWand){ + // Normally speed is set to 20% when using items, this makes it 80% + event.getMovementInput().moveStrafe *= 4; + event.getMovementInput().moveForward *= 4; } } + // Rendering + // =============================================================================================================== + @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){ + public static void onRenderHandEvent(RenderHandEvent event){ - if((!(event.getPlayer().getHeldItemMainhand().getItem() instanceof ISpellCastingItem) - && !(event.getPlayer().getHeldItemOffhand().getItem() instanceof ISpellCastingItem)) - || !ItemArtefact.isArtefactActive(event.getPlayer(), WizardryItems.charm_light)){ + // Hide the player's empty hand in first-person when possessing + 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); } } } - // 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){ + public static void onFOVUpdateEvent(FOVUpdateEvent event){ - if(event.getGuiContainer() instanceof GuiMerchant){ - - GuiMerchant gui = (GuiMerchant)event.getGuiContainer(); - // Note that gui.getMerchant() returns an NpcMerchant, not an EntityWizard. - - // Using == the specific item rather than instanceof because that's how trades do it. - if(gui.inventorySlots.getSlot(0).getStack().getItem() == WizardryItems.spell_book - || gui.inventorySlots.getSlot(1).getStack().getItem() == WizardryItems.spell_book){ - - 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); - // 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. - DrawingUtils.drawItemAndTooltip(gui, trade.getItemToSell(), slot.xPos, slot.yPos, event.getMouseX(), event.getMouseY(), - gui.getSlotUnderMouse() == slot); - } + // Bow zoom. Taken directly from AbstractClientPlayer so it works exactly like vanilla. + if(event.getEntity().isHandActive()){ + + Item item = event.getEntity().getActiveItemStack().getItem(); + + if(item instanceof ItemSpectralBow || item instanceof ItemFlamecatcher){ + + int maxUseTicks = event.getEntity().getItemInUseMaxCount(); + + float maxUseSeconds = (float)maxUseTicks / (item instanceof ItemFlamecatcher ? ItemFlamecatcher.DRAW_TIME : 20); + + if(maxUseSeconds > 1.0F){ + maxUseSeconds = 1.0F; + }else{ + maxUseSeconds = maxUseSeconds * maxUseSeconds; } + + event.setNewfov(event.getFov() * 1.0F - maxUseSeconds * 0.15F); } } } - @SubscribeEvent - public static void onRenderLivingEvent(RenderLivingEvent.Post event){ - - Minecraft mc = Minecraft.getMinecraft(); - WizardData data = WizardData.get(mc.player); - RenderManager renderManager = event.getRenderer().getRenderManager(); - - ItemStack wand = mc.player.getHeldItemMainhand(); - - if(!(wand.getItem() instanceof ISpellCastingItem)){ - wand = mc.player.getHeldItemOffhand(); - } - - // Target selection pointer - 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(); - - 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.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); - - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - - 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(); - - tessellator.draw(); - - GlStateManager.enableCull(); - GlStateManager.enableLighting(); - GlStateManager.enableDepth(); - - GlStateManager.popMatrix(); - } - } - - // Summoned creature selection pointer - if(data != null && data.selectedMinion != null && data.selectedMinion.get() == event.getEntity()){ - - Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder buffer = tessellator.getBuffer(); - - 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.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); - - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - - mc.renderEngine.bindTexture(pointerTexture); - - 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(); - - GlStateManager.enableCull(); - GlStateManager.enableLighting(); - GlStateManager.enableDepth(); - - GlStateManager.popMatrix(); - } - - // 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) < 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(); - BufferBuilder buffer = tessellator.getBuffer(); - - GlStateManager.pushMatrix(); - - GlStateManager.disableCull(); - GlStateManager.enableBlend(); - GlStateManager.disableLighting(); - OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); - GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); - // Disabling depth test allows it to be seen through everything. - GlStateManager.disableDepth(); - - GlStateManager.translate(event.getX(), event.getY() + event.getEntity().height * 0.6, 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); - - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - - mc.renderEngine.bindTexture(sixthSenseTexture); - - buffer.pos(-0.6, 0.6, 0).tex(0, 0).endVertex(); - buffer.pos(0.6, 0.6, 0).tex(1, 0).endVertex(); - buffer.pos(0.6, -0.6, 0).tex(1, 1).endVertex(); - buffer.pos(-0.6, -0.6, 0).tex(0, 1).endVertex(); - - tessellator.draw(); - - GlStateManager.enableCull(); - GlStateManager.disableBlend(); - GlStateManager.enableLighting(); - GlStateManager.enableDepth(); - - GlStateManager.popMatrix(); - } - } - - @SubscribeEvent - public static void onRenderGameOverlayEvent(RenderGameOverlayEvent.Post event){ - - 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){ + /** + * Renders an overlay across the entire screen. + * @param resolution The screen resolution + * @param texture The overlay texture to display + */ + public static void renderScreenOverlay(ScaledResolution resolution, ResourceLocation texture){ GlStateManager.pushMatrix(); @@ -471,11 +205,12 @@ public final class WizardryClientEventHandler { 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(); + + buffer.pos(0, resolution.getScaledHeight(), -90).tex(0, 1).endVertex(); + buffer.pos(resolution.getScaledWidth(), resolution.getScaledHeight(), -90).tex(1, 1).endVertex(); + buffer.pos(resolution.getScaledWidth(), 0, -90).tex(1, 0).endVertex(); + buffer.pos(0, 0, -90).tex(0, 0).endVertex(); + tessellator.draw(); GlStateManager.depthMask(true); diff --git a/src/main/java/electroblob/wizardry/client/WizardryControlHandler.java b/src/main/java/electroblob/wizardry/client/WizardryControlHandler.java index 7b98aca0..377880a1 100644 --- a/src/main/java/electroblob/wizardry/client/WizardryControlHandler.java +++ b/src/main/java/electroblob/wizardry/client/WizardryControlHandler.java @@ -4,6 +4,7 @@ import electroblob.wizardry.Wizardry; import electroblob.wizardry.client.gui.GuiSpellDisplay; import electroblob.wizardry.item.ISpellCastingItem; import electroblob.wizardry.packet.PacketControlInput; +import electroblob.wizardry.packet.PacketSpellQuickAccess; import electroblob.wizardry.packet.WizardryPacketHandler; import electroblob.wizardry.registry.WizardrySounds; import net.minecraft.client.Minecraft; @@ -24,6 +25,7 @@ public class WizardryControlHandler { static boolean NkeyPressed = false; static boolean BkeyPressed = false; + static boolean[] quickAccessKeyPressed = new boolean[ClientProxy.SPELL_QUICK_ACCESS.length]; // 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! @@ -59,6 +61,19 @@ public class WizardryControlHandler { }else{ BkeyPressed = false; } + + for(int i = 0; i < ClientProxy.SPELL_QUICK_ACCESS.length; i++){ + if(ClientProxy.SPELL_QUICK_ACCESS[i].isKeyDown() && Minecraft.getMinecraft().inGameHasFocus){ + if(!quickAccessKeyPressed[i]){ + quickAccessKeyPressed[i] = true; + // Packet building + selectSpell(wand, i); + } + }else{ + quickAccessKeyPressed[i] = false; + } + } + } } } @@ -118,4 +133,17 @@ public class WizardryControlHandler { GuiSpellDisplay.playSpellSwitchAnimation(false); Minecraft.getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.ITEM_WAND_SWITCH_SPELL, 1)); } + + private static void selectSpell(ItemStack wand, int index){ + // GUI switch animation + if(((ISpellCastingItem)wand.getItem()).selectSpell(wand, index)){ // Makes sure the spell is set immediately for the client + // Packet building (no point sending it unless the client-side spell selection succeeded + IMessage msg = new PacketSpellQuickAccess.Message(index); + WizardryPacketHandler.net.sendToServer(msg); + + GuiSpellDisplay.playSpellSwitchAnimation(true); // This will do, it's only an animation + Minecraft.getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.ITEM_WAND_SWITCH_SPELL, 1)); + } + } + } diff --git a/src/main/java/electroblob/wizardry/client/animation/ActionAnimation.java b/src/main/java/electroblob/wizardry/client/animation/ActionAnimation.java new file mode 100644 index 00000000..70f3d092 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/animation/ActionAnimation.java @@ -0,0 +1,155 @@ +package electroblob.wizardry.client.animation; + +import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.item.SpellActions; +import electroblob.wizardry.spell.Grapple; +import electroblob.wizardry.util.GeometryUtils; +import electroblob.wizardry.util.InventoryUtils; +import net.minecraft.client.model.ModelBiped; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.EnumAction; +import net.minecraft.util.EnumHandSide; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; + +/** + * An animation that is associated with a particular {@link net.minecraft.item.EnumAction}. This animation will display + * when a player is using an item with that action. + * @author Electroblob + * @since Wizardry 4.3 + * @see electroblob.wizardry.item.SpellActions + */ +public abstract class ActionAnimation extends Animation { + + private final EnumAction action; + + public ActionAnimation(EnumAction action){ + super(action.name()); + this.action = action; + } + + @Override + public boolean shouldDisplay(EntityPlayer player, boolean firstPerson){ + return !firstPerson && player.getItemInUseMaxCount() > 0 && player.getActiveItemStack().getItemUseAction() == action; + } + + /** Called from the client proxy to register all of wizardry's action animations. */ + public static void register(){ + + PlayerAnimator.registerAnimation(new ActionAnimation(SpellActions.POINT){ + @Override + public void setRotationAngles(EntityPlayer player, ModelBiped model, float partialTicks, boolean firstPerson){ + // Something (probably some sort of race condition) causes getActiveItemStack to sometimes be wrong + if(player.getActiveItemStack() != player.getHeldItem(player.getActiveHand())) return; + + EnumHandSide side = InventoryUtils.getSideForHand(player, player.getActiveHand()); + + float pitch = (float)Math.toRadians(player.prevRotationPitch + (player.rotationPitch - player.prevRotationPitch) * partialTicks); + float yaw = (float)Math.toRadians(player.prevRotationYawHead + (player.rotationYawHead - player.prevRotationYawHead) * partialTicks) + - (float)Math.toRadians(player.prevRenderYawOffset + (player.renderYawOffset - player.prevRenderYawOffset) * partialTicks); + + float x = -((float)Math.PI / 2) + pitch + 0.2f; + float y = (side == EnumHandSide.RIGHT ? -0.25f : 0.25f) + yaw; + + ModelRendererExtended arm = (ModelRendererExtended)getArmForSide(model, side); + arm.setRotation(x, y, 0); + } + }); + + PlayerAnimator.registerAnimation(new ActionAnimation(SpellActions.POINT_UP){ + @Override + public void setRotationAngles(EntityPlayer player, ModelBiped model, float partialTicks, boolean firstPerson){ + if(player.getActiveItemStack() != player.getHeldItem(player.getActiveHand())) return; + EnumHandSide side = InventoryUtils.getSideForHand(player, player.getActiveHand()); + ModelRendererExtended arm = (ModelRendererExtended)getArmForSide(model, side); + arm.addRotation(-2.2f, side == EnumHandSide.RIGHT ? 0.2f : -0.2f, 0); + } + }); + + PlayerAnimator.registerAnimation(new ActionAnimation(SpellActions.POINT_DOWN){ + @Override + public void setRotationAngles(EntityPlayer player, ModelBiped model, float partialTicks, boolean firstPerson){ + float tick = player.getItemInUseMaxCount() + partialTicks; // It's not the "max" use count at all! + float y = Math.min(0.4f + tick * 0.05f, 0.7f); + EnumHandSide side = InventoryUtils.getSideForHand(player, player.getActiveHand()); + ModelRendererExtended arm = (ModelRendererExtended)getArmForSide(model, side); + arm.addRotation(-0.2f, side == EnumHandSide.RIGHT ? y : -y, 0); + } + }); + + PlayerAnimator.registerAnimation(new ActionAnimation(SpellActions.SUMMON){ + @Override + public void setRotationAngles(EntityPlayer player, ModelBiped model, float partialTicks, boolean firstPerson){ + float tick = player.getItemInUseMaxCount() + partialTicks; // It's not the "max" use count at all! + float x = -Math.min(0.4f + tick * 0.2f, 2f); + ((ModelRendererExtended)getArmForSide(model, EnumHandSide.RIGHT)).addRotation(x, 1.2f, 0); + ((ModelRendererExtended)getArmForSide(model, EnumHandSide.LEFT)).addRotation(x, -1.2f, 0); + } + }); + + PlayerAnimator.registerAnimation(new ActionAnimation(SpellActions.GRAPPLE){ + @Override + public void setRotationAngles(EntityPlayer player, ModelBiped model, float partialTicks, boolean firstPerson){ + + WizardData data = WizardData.get(player); + RayTraceResult hit = data.getVariable(Grapple.TARGET_KEY); + if(hit == null || hit.typeOfHit == RayTraceResult.Type.MISS) return; + 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 = GeometryUtils.getCentre(hit.entityHit); + } + + EnumHandSide side = InventoryUtils.getSideForHand(player, player.getActiveHand()); + + ModelRendererExtended arm = (ModelRendererExtended)getArmForSide(model, side); + + Vec3d direction = target.subtract(player.getPositionEyes(partialTicks)); + float yaw = player.prevRenderYawOffset + (player.renderYawOffset - player.prevRenderYawOffset) * partialTicks; + + float pitch = (float)MathHelper.atan2(MathHelper.sqrt(direction.x*direction.x + direction.z*direction.z), direction.y); + float x = pitch - (float)Math.PI * 0.9f; + float y = -(float)Math.toRadians(yaw) - (float)MathHelper.atan2(direction.x, direction.z); + y += (side == EnumHandSide.RIGHT ? -0.25f : 0.25f); + if(Math.abs(pitch) < 0.2f) y = arm.rotateAngleY; + + arm.setRotation(x, y, 0); + } + }); + + PlayerAnimator.registerAnimation(new ActionAnimation(SpellActions.IMBUE){ + @Override + public void setRotationAngles(EntityPlayer player, ModelBiped model, float partialTicks, boolean firstPerson){ + float tick = player.getItemInUseMaxCount() + partialTicks; // It's not the "max" use count at all! + float z = Math.max(1.5f - tick * 0.1f, 0.8f); + EnumHandSide side = InventoryUtils.getSideForHand(player, player.getActiveHand()); + ModelRendererExtended arm = (ModelRendererExtended)getArmForSide(model, side); + arm.addRotation(-1.2f, side == EnumHandSide.RIGHT ? -0.2f : 0.2f, side == EnumHandSide.RIGHT ? z : -z); + if(!player.getHeldItem(InventoryUtils.getHandForSide(player, side.opposite())).isEmpty()){ + ((ModelRendererExtended)getArmForSide(model, side.opposite())).addRotation(-0.8f, side == EnumHandSide.LEFT ? 0.3f : -0.3f, 0); + } + } + }); + + PlayerAnimator.registerAnimation(new ActionAnimation(SpellActions.THRUST){ + @Override + public void setRotationAngles(EntityPlayer player, ModelBiped model, float partialTicks, boolean firstPerson){ + if(player.getActiveItemStack() != player.getHeldItem(player.getActiveHand())) return; + EnumHandSide side = InventoryUtils.getSideForHand(player, player.getActiveHand()); + ModelRendererExtended arm = (ModelRendererExtended)getArmForSide(model, side); + float y = side == EnumHandSide.RIGHT ? -0.6f : 0.6f; + arm.addRotation(-1.2f, y, 0); + if(player.getHeldItem(InventoryUtils.getHandForSide(player, side.opposite())).isEmpty()){ + ModelRendererExtended otherArm = (ModelRendererExtended)getArmForSide(model, side.opposite()); + otherArm.setRotation(arm.rotateAngleX - 1.2f, -arm.rotateAngleY - y, otherArm.rotateAngleZ); + } + } + }); + + } + +} diff --git a/src/main/java/electroblob/wizardry/client/animation/Animation.java b/src/main/java/electroblob/wizardry/client/animation/Animation.java new file mode 100644 index 00000000..71be8a42 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/animation/Animation.java @@ -0,0 +1,77 @@ +package electroblob.wizardry.client.animation; + +import net.minecraft.client.model.ModelBiped; +import net.minecraft.client.model.ModelRenderer; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.EnumHandSide; + +/** + * Represents a player animation. Use one of the predefined subclasses below for common animation types, or extend this + * class directly to define a completely custom animation. Register instances of this class using + * {@link PlayerAnimator#registerAnimation(Animation)}. + * @author Electroblob + * @since Wizardry 4.3 + * @see ActionAnimation + */ +public abstract class Animation { + + private final String name; + + public Animation(String name){ + this.name = name; + } + + /** Returns the name of this animation, currently only used for warning messages / debugging. */ + public String getName(){ + return name; + } + + /** + * Returns whether this animation should be displayed for the given player. Note that if two registered + * animations have overlapping conditions, both will attempt to display simultaneously, so if they both animate the + * same model part, that part may not behave as expected. + * @param player The player being animated + * @param firstPerson True if the player being animated is the local client player, and they are in first-person + * view. In first-person, animations are only useful for animating the player's empty hand. + * @return True if the animation should be displayed, false if not. + */ + public abstract boolean shouldDisplay(EntityPlayer player, boolean firstPerson); + + /** + * Sets the rotation of the model parts for this animation. This method is called every time the player is rendered + * when {@link Animation#shouldDisplay(EntityPlayer, boolean)} returns true. + * @param player The player being animated + * @param model The model to animate. All of the standard {@link ModelBiped} parts will already be wrapped and may + * be safely cast to {@code ModelRendererExtended} in order to override the rotations set by the model + * itself - see {@link ModelRendererExtended ModelRendererExtended} for details. + * @param partialTicks The current partial tick time + * @param firstPerson True if the player being animated is the local client player, and they are in first-person + * view. In first-person, animations are only useful for animating the player's empty hand. + */ + public abstract void setRotationAngles(EntityPlayer player, ModelBiped model, float partialTicks, boolean firstPerson); + + /** + * Returns whether the boxes that form the second layer of the player's skin (for models that are instances of + * {@link net.minecraft.client.model.ModelPlayer ModelPlayer}) should automatically be set to the same angles as + * their corresponding first-layer parts. + * @param player The player being rendered, for reference + * @param firstPerson True if the player being animated is the local client player, and they are in first-person + * view. In first-person, animations are only useful for animating the player's empty hand. + * @return True to let {@link PlayerAnimator} auto-rotate the second skin layer, false to rotate them manually. + */ + public boolean autoRotateSecondLayer(EntityPlayer player, boolean firstPerson){ + return true; + } + + /** + * Returns the arm of the given model corresponding to the given side. Function is identical to the method of the + * same name in {@link ModelBiped}. + * @param model The model to get the arm of + * @param side The {@link EnumHandSide} to return the arm for + * @return The {@link ModelRenderer} corresponding to the given arm + */ + public static ModelRenderer getArmForSide(ModelBiped model, EnumHandSide side){ + return side == EnumHandSide.LEFT ? model.bipedLeftArm : model.bipedRightArm; + } + +} diff --git a/src/main/java/electroblob/wizardry/client/animation/ModelRendererExtended.java b/src/main/java/electroblob/wizardry/client/animation/ModelRendererExtended.java new file mode 100644 index 00000000..eb8b706d --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/animation/ModelRendererExtended.java @@ -0,0 +1,176 @@ +package electroblob.wizardry.client.animation; + +import net.minecraft.client.model.ModelBase; +import net.minecraft.client.model.ModelBiped; +import net.minecraft.client.model.ModelPlayer; +import net.minecraft.client.model.ModelRenderer; + +/** + * A wrapper around {@link ModelRenderer} which can be swapped into models to allow render pre/post events to + * circumvent {@code ModelBase.setRotationAngles(...)} and set model part rotations themselves. Instances of this class + * keep a reference to the original {@code ModelRenderer} and delegate all the actual rendering to it. + *

+ * {@link PlayerAnimator PlayerAnimator} does all the necessary setup with this + * class for player animations. It is also possible to set up custom animations for non-player entities with this class, + * see {@link ModelRendererExtended#wrap(ModelBiped)} and {@link ModelRendererExtended#wrap(ModelBase, ModelRenderer)}. + * @author Electroblob + * @since Wizardry 4.3 + */ +public class ModelRendererExtended extends ModelRenderer { + + // We probably could have copied all the fields over instead, but this way if anyone else modifies the boxes we + // won't overwrite their changes + // This way also avoids reflection because we don't need to copy private fields, which is better for performance + private ModelRenderer delegate; + + private float actualRotationX; + private float actualRotationY; + private float actualRotationZ; + + private float extraRotationX; + private float extraRotationY; + private float extraRotationZ; + + private ModelRendererExtended(ModelBase baseModel, ModelRenderer delegate){ + super(baseModel, delegate.boxName); + this.delegate = delegate; + // Copy over any public fields that might get changed + this.textureWidth = delegate.textureWidth; + this.textureHeight = delegate.textureHeight; + this.showModel = delegate.showModel; + this.isHidden = delegate.isHidden; + this.rotationPointX = delegate.rotationPointX; + this.rotationPointY = delegate.rotationPointY; + this.rotationPointZ = delegate.rotationPointZ; + this.resetRotation(); + } + + // Static initialisers + + /** + * Replaces the given part of the given {@link ModelBase} with a new {@code ModelRendererExtended} that wraps the + * original part. This method takes care of modifying the model's box list, but is unable to change any + * individual {@link ModelRenderer} fields - this method instead returns the resulting + * {@code ModelRendererExtended} to be assigned to the appropriate field. + * @param model The model whose part is to be wrapped + * @param box The part of the model to wrap, must belong to the above model + * @return The resulting wrapped model part, this should be assigned to the appropriate part field in the model. If + * the given model part was already wrapped, this method simply returns it unchanged. + * @throws IllegalArgumentException if the given box does not belong to the given model + */ + public static ModelRendererExtended wrap(ModelBase model, ModelRenderer box){ + if(box instanceof ModelRendererExtended) return (ModelRendererExtended)box; // Ignore already-wrapped parts + ModelRendererExtended wrapper = new ModelRendererExtended(model, box); + int index = model.boxList.indexOf(box); + if(index < 0) throw new IllegalArgumentException(String.format("The given ModelRenderer %s does not belong to the given model %s", box, model)); + model.boxList.set(index, wrapper); // I doubt the order matters but we may as well put it at the same index + return wrapper; + } + + /** + * Replaces the given {@link ModelBiped}'s parts with new {@code ModelRendererExtended} versions that wrap the + * original parts. If the given model is a {@link ModelPlayer}, the extra boxes for player skin overlays will also + * be wrapped. + */ + public static void wrap(ModelBiped model){ + // MMmmmmmm wraps + model.bipedHead = wrap(model, model.bipedHead); + model.bipedBody = wrap(model, model.bipedBody); + model.bipedRightArm = wrap(model, model.bipedRightArm); + model.bipedLeftArm = wrap(model, model.bipedLeftArm); + model.bipedRightLeg = wrap(model, model.bipedRightLeg); + model.bipedLeftLeg = wrap(model, model.bipedLeftLeg); + model.bipedHeadwear = wrap(model, model.bipedHeadwear); + + if(model instanceof ModelPlayer){ + ((ModelPlayer)model).bipedBodyWear = wrap(model, ((ModelPlayer)model).bipedBodyWear); + ((ModelPlayer)model).bipedRightArmwear = wrap(model, ((ModelPlayer)model).bipedRightArmwear); + ((ModelPlayer)model).bipedLeftArmwear = wrap(model, ((ModelPlayer)model).bipedLeftArmwear); + ((ModelPlayer)model).bipedRightLegwear = wrap(model, ((ModelPlayer)model).bipedRightLegwear); + ((ModelPlayer)model).bipedLeftLegwear = wrap(model, ((ModelPlayer)model).bipedLeftLegwear); + } + } + + /** Resets the rotation of this model part to the angle set by the parent model. */ + public void resetRotation(){ + this.actualRotationX = Float.NaN; + this.actualRotationY = Float.NaN; + this.actualRotationZ = Float.NaN; + this.extraRotationX = 0; + this.extraRotationY = 0; + this.extraRotationZ = 0; + } + + /** Sets the rotation of this model part, which will overwrite the angle set by the parent model. */ + public void setRotation(float x, float y, float z){ + this.actualRotationX = x; + this.actualRotationY = y; + this.actualRotationZ = z; + } + + /** Sets the extra rotation of this model part, which will be added onto the angle set by the parent model. */ + public void addRotation(float x, float y, float z){ + this.extraRotationX = x; + this.extraRotationY = y; + this.extraRotationZ = z; + } + + /** Sets the rotation of this model part to the same values as the given box. */ + public void setRotationTo(ModelRendererExtended box){ + this.actualRotationX = box.actualRotationX; + this.actualRotationY = box.actualRotationY; + this.actualRotationZ = box.actualRotationZ; + this.extraRotationX = box.extraRotationX; + this.extraRotationY = box.extraRotationY; + this.extraRotationZ = box.extraRotationZ; + } + + // Delegate rendering, but fiddle with the angles first + + @Override + public void render(float scale){ + + // Need to copy these over each time in case they were changed + delegate.showModel = this.showModel; + delegate.isHidden = this.isHidden; + delegate.rotationPointX = this.rotationPointX; + delegate.rotationPointY = this.rotationPointY; + delegate.rotationPointZ = this.rotationPointZ; + + if(!Float.isNaN(actualRotationX) && !Float.isNaN(actualRotationY) && !Float.isNaN(actualRotationZ)){ + delegate.rotateAngleX = actualRotationX; + delegate.rotateAngleY = actualRotationY; + delegate.rotateAngleZ = actualRotationZ; + }else{ + delegate.rotateAngleX = this.rotateAngleX + extraRotationX; + delegate.rotateAngleY = this.rotateAngleY + extraRotationY; + delegate.rotateAngleZ = this.rotateAngleZ + extraRotationZ; + } + + delegate.render(scale); + } + + // Delegate all other methods + + @Override + public void addChild(ModelRenderer renderer){ + delegate.addChild(renderer); + } + + @Override + public void renderWithRotation(float scale){ + delegate.renderWithRotation(scale); + } + + @Override + public void postRender(float scale){ + // Exactly the same setup as above, just add item rotation/translation fields and setters +// float angle = 1; +// float radius = 10; +// delegate.rotationPointY -= radius * MathHelper.cos(angle); +// delegate.rotationPointZ -= radius * MathHelper.sin(angle); +// delegate.rotateAngleX += angle; + delegate.postRender(scale); + } + +} diff --git a/src/main/java/electroblob/wizardry/client/animation/PlayerAnimator.java b/src/main/java/electroblob/wizardry/client/animation/PlayerAnimator.java new file mode 100644 index 00000000..e0acd4dc --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/animation/PlayerAnimator.java @@ -0,0 +1,220 @@ +package electroblob.wizardry.client.animation; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.item.SpellActions; +import electroblob.wizardry.util.InventoryUtils; +import electroblob.wizardry.util.JavaUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.AbstractClientPlayer; +import net.minecraft.client.model.ModelBiped; +import net.minecraft.client.model.ModelPlayer; +import net.minecraft.client.model.ModelRenderer; +import net.minecraft.client.renderer.GlStateManager; +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.LayerBipedArmor; +import net.minecraft.client.renderer.entity.layers.LayerRenderer; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.EntityEquipmentSlot; +import net.minecraft.item.EnumAction; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumHand; +import net.minecraft.util.EnumHandSide; +import net.minecraftforge.client.ForgeHooksClient; +import net.minecraftforge.client.event.RenderPlayerEvent; +import net.minecraftforge.client.event.RenderSpecificHandEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.ObfuscationReflectionHelper; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.relauncher.Side; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Handles the setup and rendering events for custom player animations, as well as registering of animations. Addons + * can also register their own animations here, see {@link PlayerAnimator#registerAnimation(Animation)}. + * @author Electroblob + * @since Wizardry 4.3 + * @see electroblob.wizardry.item.SpellActions SpellActions + */ +@Mod.EventBusSubscriber(Side.CLIENT) +public class PlayerAnimator { + + // TODO: Attempt to extend this to all bipeds + + private static final List animations = new ArrayList<>(); + + /** Stores all registered layer renderers for lazy-loading of armour models later. */ + private static final Map>> playerLayers = new HashMap<>(); + /** Stores the main model for each renderer plus all models for registered layer renderers. */ + private static final Map> playerLayerModels = new HashMap<>(); + /** Reflected into {@link RenderLivingBase}{@code #layerRenderers}. */ + private static final Field layerRenderers; + + static { + layerRenderers = ObfuscationReflectionHelper.findField(RenderLivingBase.class, "field_177097_h"); + } + + /** + * Registers a new player animation. This method should be called from the {@code init()} phase via a client proxy. + * @param animation The animation to register + */ + // N.B. This can (and will) be called before PlayerAnimator#init() is called + public static void registerAnimation(Animation animation){ + if(animations.contains(animation)){ + Wizardry.logger.warn("Animation {} is already registered!", animation.getName()); + }else{ + animations.add(animation); + } + } + + // TODO: Figure out if calling this from postInit ensures we catch all the layers, and lazy-load it if not + @SuppressWarnings("unchecked") + public static void init(){ + + if(!Wizardry.settings.spellcastingAnimations) return; + + for(RenderPlayer renderer : Minecraft.getMinecraft().getRenderManager().getSkinMap().values()){ + + List models = new ArrayList<>(); + ModelRendererExtended.wrap(renderer.getMainModel()); + models.add(renderer.getMainModel()); + + try { + + List> layers = (List>)layerRenderers.get(renderer); + + // Save armour layers for lazy-loading later (ignore other layers so iteration is faster) + playerLayers.put(renderer, layers.stream().filter(l -> l instanceof LayerBipedArmor).collect(Collectors.toList())); + + for(LayerRenderer layer : layers){ + + for(Field field : JavaUtils.getAllFields(layer.getClass())){ + + field.setAccessible(true); + + // If your layer model doesn't extend ModelBiped, you DESERVE to be incompatible! + // (Just kidding... there's nothing I can do about it anyway) + if(field.get(layer) instanceof ModelBiped){ + ModelBiped model = (ModelBiped)field.get(layer); + ModelRendererExtended.wrap(model); + models.add(model); + } + } + + } + + }catch(IllegalAccessException e){ + Wizardry.logger.error("Error during reflective access of render layers: ", e); + } + + playerLayerModels.put(renderer, models); + + } + } + + private static void updateModels(EntityPlayer player, Render renderer, float partialTicks, boolean firstPerson){ + + // Biped armour is special because of Forge's armour item render hook + // This needs to be lazy-loaded because we need access to an actual item + for(LayerRenderer layer : playerLayers.get(renderer)){ + if(layer instanceof LayerBipedArmor){ + for(EntityEquipmentSlot slot : InventoryUtils.ARMOUR_SLOTS){ + + ItemStack armour = player.getItemStackFromSlot(slot); + // This method could behave dynamically depending on stuff like NBT so there's not really any way to + // optimise it further, we *have* to retrieve the model every time (shouldn't be too bad though) + ModelBiped model = ForgeHooksClient.getArmorModel(player, armour, slot, ((LayerBipedArmor)layer).getModelFromSlot(slot)); + + List models = playerLayerModels.get(renderer); + + if(!models.contains(model)){ // Ignore already-wrapped models + models.add(model); + ModelRendererExtended.wrap(model); + } + } + } + } + + for(Animation animation : animations){ + + if(animation.shouldDisplay(player, firstPerson)){ + + boolean autoRotateSecondLayer = animation.autoRotateSecondLayer(player, firstPerson); + + for(ModelBiped model : playerLayerModels.get(renderer)){ + + animation.setRotationAngles(player, model, partialTicks, firstPerson); + + if(autoRotateSecondLayer && model instanceof ModelPlayer){ + alignSecondLayer((ModelPlayer)model); + } + } + } + } + + } + + /** Rotates the second layer boxes of the given (wrapped) player model to match the first layer. */ + public static void alignSecondLayer(ModelPlayer model){ + ((ModelRendererExtended)model.bipedBodyWear) .setRotationTo((ModelRendererExtended)model.bipedBody); + ((ModelRendererExtended)model.bipedRightArmwear).setRotationTo((ModelRendererExtended)model.bipedRightArm); + ((ModelRendererExtended)model.bipedLeftArmwear) .setRotationTo((ModelRendererExtended)model.bipedLeftArm); + ((ModelRendererExtended)model.bipedRightLegwear).setRotationTo((ModelRendererExtended)model.bipedBody); + ((ModelRendererExtended)model.bipedLeftLegwear) .setRotationTo((ModelRendererExtended)model.bipedBody); + } + + @SubscribeEvent + public static void onRenderHandEvent(RenderSpecificHandEvent event){ + + if(!Wizardry.settings.spellcastingAnimations) return; + + AbstractClientPlayer player = Minecraft.getMinecraft().player; + + EnumAction action = event.getItemStack().getItemUseAction(); + + if(player.isHandActive() && player.getActiveHand() == event.getHand() && SpellActions.getSpellActions().contains(action)){ + // Minecraft's item renderer helpfully doesn't have a default case for the usage action so it doesn't do any + // transformations at all (which is VERY ANNOYING!) - the following lines are from ItemRenderer#transformSideFirstPerson + int i = (player.getPrimaryHand() == EnumHandSide.RIGHT) == (event.getHand() == EnumHand.MAIN_HAND) ? 1 : -1; + GlStateManager.translate((float)i * 0.56F, -0.52F + event.getEquipProgress() * -0.6F, -0.72F); + } + + updateModels(player, Minecraft.getMinecraft().getRenderManager().getEntityRenderObject(player), event.getPartialTicks(), true); + } + + @SubscribeEvent + public static void onRenderPlayerPreEvent(RenderPlayerEvent.Pre event){ + + if(!Wizardry.settings.spellcastingAnimations) return; + +// boolean firstPerson = event.getEntityPlayer() == Minecraft.getMinecraft().player +// && Minecraft.getMinecraft().gameSettings.thirdPersonView == 0; + + updateModels(event.getEntityPlayer(), event.getRenderer(), event.getPartialRenderTick(), false); + } + + @SubscribeEvent + public static void onRenderPlayerPostEvent(RenderPlayerEvent.Post event){ + + if(!Wizardry.settings.spellcastingAnimations) return; + + for(ModelBiped model : playerLayerModels.get(event.getRenderer())){ + for(ModelRenderer box : model.boxList){ // For ModelPlayer, this will include the second layer + // Some models have extra boxes, they (probably) don't need wrapping but we need this check! + if(box instanceof ModelRendererExtended){ + ((ModelRendererExtended)box).resetRotation(); + } + } + } + + } + +} diff --git a/src/main/java/electroblob/wizardry/client/audio/MovingSoundSpellCharge.java b/src/main/java/electroblob/wizardry/client/audio/MovingSoundSpellCharge.java new file mode 100644 index 00000000..0384127d --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/audio/MovingSoundSpellCharge.java @@ -0,0 +1,36 @@ +package electroblob.wizardry.client.audio; + +import electroblob.wizardry.item.ISpellCastingItem; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.item.ItemStack; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.SoundEvent; + +/** A special type of moving sound that stops when the player stops charging up a spell, either by releasing the mouse + * button or when the charge-up time has elapsed. */ +public class MovingSoundSpellCharge extends MovingSoundEntity { + + public MovingSoundSpellCharge(EntityLivingBase entity, SoundEvent sound, SoundCategory category, float volume, float pitch, boolean repeat){ + super(entity, sound, category, volume, pitch, repeat); + } + + @Override + public void update(){ + + if(source.isHandActive()){ + + ItemStack stack = source.getActiveItemStack(); + + if(stack.getItem() instanceof ISpellCastingItem){ + + if(source.getItemInUseMaxCount() < ((ISpellCastingItem)stack.getItem()).getCurrentSpell(stack).getChargeup()){ + super.update(); + return; + } + } + } + + this.donePlaying = true; + } + +} diff --git a/src/main/java/electroblob/wizardry/client/audio/SoundLoop.java b/src/main/java/electroblob/wizardry/client/audio/SoundLoop.java index 651fb7fb..3bce8e3e 100644 --- a/src/main/java/electroblob/wizardry/client/audio/SoundLoop.java +++ b/src/main/java/electroblob/wizardry/client/audio/SoundLoop.java @@ -27,6 +27,16 @@ public abstract class SoundLoop implements ITickable { private static final Set activeLoops = new HashSet<>(); + // The issue we had here was that there is no way of hooking into the client tick loop between the sound manager + // update and the world tick (other than via the sounds themselves, but playing other sounds from there results in + // CMEs, which is why we made this class in the first place). This meant there was always a gap between consecutive + // sounds, which could be heard in-game as a weird audio artefact. The solution is simply to overlap the sounds by + // 1 tick, which is fairly straightforward for the end sound, but because the start sound has to finish for us to + // know when to play the others, this doubling-up solution is required (bit of a waste of resources but meh). + /** A dummy sound with effectively zero volume, played one tick in advance of the actual start sound so the loop + * sound can start one tick before the actual start sound ends. */ + private final ISound dummyStart; // It's the stupidest system ever but it works! + private final ISound start; private final ISound loop; private final ISound end; @@ -34,29 +44,28 @@ public abstract class SoundLoop implements ITickable { private boolean looping = false; private boolean needsRemoving = false; - public SoundLoop(SoundEvent start, SoundEvent loop, SoundEvent end, SoundCategory category, ISoundFactory factory){ + public SoundLoop(SoundEvent start, SoundEvent loop, SoundEvent end, SoundCategory category, float volume, 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); + this.dummyStart = factory.create(start, category, 0.00001f, false); // Inaudible but not actually 0 + this.start = factory.create(start, category, volume, false); + this.loop = factory.create(loop, category, volume, true); + this.end = factory.create(end, category, volume, 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)){ + if(!looping && !Minecraft.getMinecraft().getSoundHandler().isSoundPlaying(dummyStart)){ 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. */ + /** Starts playing the end part immediately and marks the loop part to stop next tick. 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 @@ -68,12 +77,20 @@ public abstract class SoundLoop implements ITickable { this.needsRemoving = true; } + /** Stops the start and loop sounds immediately. */ + protected void stopStartAndLoop(){ + Minecraft.getMinecraft().getSoundHandler().stopSound(dummyStart); + Minecraft.getMinecraft().getSoundHandler().stopSound(start); + Minecraft.getMinecraft().getSoundHandler().stopSound(loop); + } + // 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); + Minecraft.getMinecraft().getSoundHandler().playSound(loop.dummyStart); + Minecraft.getMinecraft().getSoundHandler().playDelayedSound(loop.start, 2); // 1 seems to be insufficient } @SubscribeEvent @@ -82,13 +99,14 @@ public abstract class SoundLoop implements ITickable { // 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.stream().filter(s -> s.needsRemoving).forEach(SoundLoop::stopStartAndLoop); activeLoops.removeIf(s -> s.needsRemoving); + activeLoops.forEach(SoundLoop::update); // Do this last to allow a 1-tick overlap } } @FunctionalInterface public interface ISoundFactory { - ISound create(SoundEvent sound, SoundCategory category, boolean repeat); + ISound create(SoundEvent sound, SoundCategory category, float volume, boolean repeat); } } diff --git a/src/main/java/electroblob/wizardry/client/audio/SoundLoopSpell.java b/src/main/java/electroblob/wizardry/client/audio/SoundLoopSpell.java index bb4d9b44..86a6c6aa 100644 --- a/src/main/java/electroblob/wizardry/client/audio/SoundLoopSpell.java +++ b/src/main/java/electroblob/wizardry/client/audio/SoundLoopSpell.java @@ -3,7 +3,7 @@ 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 electroblob.wizardry.util.EntityUtils; import net.minecraft.client.audio.PositionedSound; import net.minecraft.entity.EntityLivingBase; import net.minecraft.tileentity.TileEntity; @@ -17,8 +17,8 @@ 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); + public SoundLoopSpell(SoundEvent start, SoundEvent loop, SoundEvent end, float volume, ISoundFactory factory, Spell spell){ + super(start, loop, end, WizardrySounds.SPELLS, volume, factory); this.spell = spell; } @@ -43,13 +43,13 @@ public abstract class SoundLoopSpell extends SoundLoop { 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); + super(start, loop, end, volume, (sound, category, v, repeat) -> new MovingSoundEntity<>(source, sound, category, v, pitch, repeat), spell); this.source = source; } @Override protected boolean stillCasting(Spell spell){ - return WizardryUtilities.isCasting(source, spell); + return EntityUtils.isCasting(source, spell); } } @@ -58,13 +58,13 @@ public abstract class SoundLoopSpell extends SoundLoop { 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){{ + super(start, loop, end, sndVolume, (sound, category, v, 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.volume = v; this.pitch = sndPitch; }}, spell); } diff --git a/src/main/java/electroblob/wizardry/client/gui/CustomCreativeSearchHandler.java b/src/main/java/electroblob/wizardry/client/gui/CustomCreativeSearchHandler.java new file mode 100644 index 00000000..e6239eff --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/CustomCreativeSearchHandler.java @@ -0,0 +1,109 @@ +package electroblob.wizardry.client.gui; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.DrawingUtils; +import electroblob.wizardry.registry.WizardryTabs; +import electroblob.wizardry.spell.Spell; +import net.minecraft.client.gui.GuiTextField; +import net.minecraft.client.gui.inventory.GuiContainerCreative; +import net.minecraft.client.resources.I18n; +import net.minecraft.creativetab.CreativeTabs; +import net.minecraft.util.text.Style; +import net.minecraft.util.text.TextFormatting; +import net.minecraftforge.client.event.GuiScreenEvent; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber; +import net.minecraftforge.fml.common.ObfuscationReflectionHelper; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent.ClientTickEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent.Phase; +import net.minecraftforge.fml.relauncher.Side; + +import java.lang.reflect.Field; +import java.util.Locale; + +@EventBusSubscriber(Side.CLIENT) +public class CustomCreativeSearchHandler { + + private static final int SEARCH_TOOLTIP_HOVER_TIME = 20; + + private static final Style TOOLTIP_SYNTAX = new Style().setColor(TextFormatting.YELLOW); + private static final Style TOOLTIP_BODY = new Style().setColor(TextFormatting.WHITE); + + /** Reflected into {@code GuiContainerCreative#searchField} */ + private static final Field searchField; + + private static GuiTextField currentSearchField = null; + + private static int searchBarHoverTime; + + static { + searchField = ObfuscationReflectionHelper.findField(GuiContainerCreative.class, "field_147062_A"); + } + + @SubscribeEvent + public static void onInitGuiEvent(GuiScreenEvent.InitGuiEvent.Post event){ + // Reduces reflection as much as possible - searchField is created on GUI init and never reassigned so we need + // not (and should not!) use reflection to retrieve it every time a key is typed + if(event.getGui() instanceof GuiContainerCreative){ + try{ + currentSearchField = (GuiTextField)searchField.get(event.getGui()); + }catch(IllegalAccessException e){ + e.printStackTrace(); + } + } + } + + @SubscribeEvent + public static void onKeyboardInputEvent(GuiScreenEvent.KeyboardInputEvent.Post event){ + // Custom creative tab search behaviour + if(event.getGui() instanceof GuiContainerCreative){ + + GuiContainerCreative gui = (GuiContainerCreative)event.getGui(); + CreativeTabs tab = CreativeTabs.CREATIVE_TAB_ARRAY[gui.getSelectedTabIndex()]; + + if(tab == WizardryTabs.SPELLS){ + + GuiContainerCreative.ContainerCreative container = (GuiContainerCreative.ContainerCreative)gui.inventorySlots; + container.itemList.clear(); // Required or duplicates will appear! + String searchText = currentSearchField.getText().toLowerCase(Locale.ROOT); + tab.displayAllRelevantItems(container.itemList); + + if(!searchText.isEmpty()){ + container.itemList.removeIf(s -> !Spell.byMetadata(s.getMetadata()).matches(searchText)); + container.scrollTo(0); // Seems to refresh the GUI somehow so it displays correctly + } + } + } + } + + @SubscribeEvent + public static void onClientTickEvent(ClientTickEvent event){ + if(event.phase == Phase.END && searchBarHoverTime > 0 && searchBarHoverTime < SEARCH_TOOLTIP_HOVER_TIME){ + searchBarHoverTime++; + } + } + + @SubscribeEvent + public static void onDrawScreenPostEvent(GuiScreenEvent.DrawScreenEvent.Post event){ + + if(event.getGui() instanceof GuiContainerCreative && currentSearchField != null){ + + GuiContainerCreative gui = (GuiContainerCreative)event.getGui(); + CreativeTabs tab = CreativeTabs.CREATIVE_TAB_ARRAY[gui.getSelectedTabIndex()]; + + if(tab == WizardryTabs.SPELLS && DrawingUtils.isPointInRegion(currentSearchField.x, currentSearchField.y, currentSearchField.width, currentSearchField.height, event.getMouseX(), event.getMouseY())){ + + if(searchBarHoverTime == 0){ + searchBarHoverTime++; + }else if(searchBarHoverTime == SEARCH_TOOLTIP_HOVER_TIME){ + event.getGui().drawHoveringText(I18n.format("container." + Wizardry.MODID + ":arcane_workbench.search_tooltip", + TOOLTIP_SYNTAX.getFormattingCode(), TOOLTIP_BODY.getFormattingCode()), event.getMouseX(), event.getMouseY()); + } + } + + }else{ + searchBarHoverTime = 0; + } + } + +} diff --git a/src/main/java/electroblob/wizardry/client/gui/GuiArcaneWorkbench.java b/src/main/java/electroblob/wizardry/client/gui/GuiArcaneWorkbench.java index b4cf57a4..76445c10 100644 --- a/src/main/java/electroblob/wizardry/client/gui/GuiArcaneWorkbench.java +++ b/src/main/java/electroblob/wizardry/client/gui/GuiArcaneWorkbench.java @@ -5,32 +5,35 @@ import electroblob.wizardry.client.DrawingUtils; import electroblob.wizardry.constants.Element; import electroblob.wizardry.constants.Tier; 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.item.ItemWand; +import electroblob.wizardry.inventory.ContainerArcaneWorkbench; +import electroblob.wizardry.inventory.SlotBookList; +import electroblob.wizardry.item.*; 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.ISpellSortable; import electroblob.wizardry.util.WandHelper; import net.minecraft.client.Minecraft; import net.minecraft.client.audio.PositionedSoundRecord; +import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiTextField; 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.ClickType; 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.minecraft.util.math.MathHelper; +import net.minecraft.util.text.Style; import net.minecraft.util.text.TextFormatting; import net.minecraftforge.client.event.TextureStitchEvent; import net.minecraftforge.fml.common.Mod; @@ -38,19 +41,25 @@ 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; +import org.lwjgl.input.Mouse; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; @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"); + "textures/gui/container/arcane_workbench.png"); - private IInventory playerInventory; - private IInventory arcaneWorkbenchInventory; + private static final int TOOLTIP_WIDTH = 144; + private static final int TOOLTIP_BORDER = 6; + private static final int LINE_SPACING_WIDE = 5; + private static final int LINE_SPACING_NARROW = 1; - private static final int TOOLTIP_WIDTH = 164; - private static final int TOOLTIP_TEXT_INSET = 6; + private static final int BOOKSHELF_UI_WIDTH = ContainerArcaneWorkbench.BOOKSHELF_UI_WIDTH; // For conciseness /** 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. */ @@ -61,29 +70,98 @@ public class GuiArcaneWorkbench extends GuiContainer { private static final int RUNE_WIDTH = 100; private static final int RUNE_HEIGHT = 100; - private static final int PROGRESSION_BAR_WIDTH = 152; - private static final int PROGRESSION_BAR_HEIGHT = 3; + private static final int SCROLL_BAR_LEFT = 102; + private static final int SCROLL_BAR_TOP = 34; + private static final int SCROLL_BAR_WIDTH = 12; + private static final int SCROLL_BAR_HEIGHT = 178; + private static final int SCROLL_HANDLE_HEIGHT = 15; private static final int HALO_DIAMETER = 156; private static final int TEXTURE_WIDTH = 512; - private static final int TEXTURE_HEIGHT = 256; + private static final int TEXTURE_HEIGHT = 512; + + private static final int ANIMATION_DURATION = 20; + + private static final int SEARCH_TOOLTIP_HOVER_TIME = 20; + + private static final Style TOOLTIP_SYNTAX = new Style().setColor(TextFormatting.YELLOW); + private static final Style TOOLTIP_BODY = new Style().setColor(TextFormatting.WHITE); + + private InventoryPlayer playerInventory; + private IInventory arcaneWorkbenchInventory; + private ContainerArcaneWorkbench arcaneWorkbenchContainer; + + private GuiButton applyBtn; + private GuiButton[] sortButtons = new GuiButton[3]; + + private GuiTextField searchField; + private boolean searchNeedsClearing; + private int searchBarHoverTime; + + private final List tooltipElements = new ArrayList<>(); private int animationTimer = 0; - private static final int ANIMATION_DURATION = 20; + + private float scroll = 0; + private boolean scrolling = false; public GuiArcaneWorkbench(InventoryPlayer invPlayer, TileEntityArcaneWorkbench entity){ super(new ContainerArcaneWorkbench(invPlayer, entity)); + this.arcaneWorkbenchContainer = (ContainerArcaneWorkbench)inventorySlots; this.playerInventory = invPlayer; this.arcaneWorkbenchInventory = entity; xSize = MAIN_GUI_WIDTH; ySize = 220; } + @Override + public void initGui(){ + + this.mc.player.openContainer = this.inventorySlots; + this.guiLeft = (this.width - MAIN_GUI_WIDTH) / 2; // Use MAIN_GUI_WIDTH, not xSize, otherwise JEI messes with it + 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)); + this.buttonList.add(sortButtons[0] = new GuiButtonSpellSort(1, this.guiLeft - 44, this.guiTop + 8, ISpellSortable.SortType.TIER, arcaneWorkbenchContainer, this)); + this.buttonList.add(sortButtons[1] = new GuiButtonSpellSort(2, this.guiLeft - 31, this.guiTop + 8, ISpellSortable.SortType.ELEMENT, arcaneWorkbenchContainer, this)); + this.buttonList.add(sortButtons[2] = new GuiButtonSpellSort(3, this.guiLeft - 18, this.guiTop + 8, ISpellSortable.SortType.ALPHABETICAL, arcaneWorkbenchContainer, this)); + + this.searchField = new GuiTextField(0, this.fontRenderer, this.guiLeft - 113, this.guiTop + 22, 104, this.fontRenderer.FONT_HEIGHT); + this.searchField.setMaxStringLength(50); + this.searchField.setEnableBackgroundDrawing(false); + this.searchField.setVisible(true); + this.searchField.setTextColor(16777215); + this.searchField.setCanLoseFocus(false); + this.searchField.setFocused(true); + + this.tooltipElements.clear(); + this.tooltipElements.add(new TooltipElementItemName(new Style().setColor(TextFormatting.WHITE), LINE_SPACING_WIDE)); + this.tooltipElements.add(new TooltipElementManaReadout(LINE_SPACING_WIDE)); + this.tooltipElements.add(new TooltipElementProgressionBar(LINE_SPACING_WIDE)); + this.tooltipElements.add(new TooltipElementSpellList(LINE_SPACING_WIDE)); + this.tooltipElements.add(new TooltipElementUpgradeList(LINE_SPACING_WIDE)); + + } + + @Override + public void onGuiClosed(){ + super.onGuiClosed(); + Keyboard.enableRepeatEvents(false); + } + // Huh, didn't realise this method existed. Pretty neat. @Override public void updateScreen(){ if(animationTimer > 0) animationTimer--; + if(arcaneWorkbenchContainer.needsRefresh){ + arcaneWorkbenchContainer.refreshBookshelfSlots(); + arcaneWorkbenchContainer.needsRefresh = false; + } + if(searchBarHoverTime > 0 && searchBarHoverTime < SEARCH_TOOLTIP_HOVER_TIME) searchBarHoverTime++; } @Override @@ -93,21 +171,42 @@ public class GuiArcaneWorkbench extends GuiContainer { GlStateManager.color(1, 1, 1, 1); // Just in case - Slot slot = this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT); + boolean mouseHeld = Mouse.isButtonDown(0); - // 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; + if(!scrolling && mouseHeld && getMaxScrollRows() > 0 && isPointInRegion(SCROLL_BAR_LEFT, + SCROLL_BAR_TOP, SCROLL_BAR_WIDTH, SCROLL_BAR_HEIGHT, mouseX, mouseY)){ + scrolling = true; } - this.applyBtn.enabled = slot.getHasStack(); + if(!mouseHeld || getMaxScrollRows() == 0) scrolling = false; + + if(scrolling){ + scroll = MathHelper.clamp((float)(mouseY - SCROLL_BAR_TOP - SCROLL_HANDLE_HEIGHT/2 - guiTop) + /(SCROLL_BAR_HEIGHT - SCROLL_HANDLE_HEIGHT), 0, 1); + arcaneWorkbenchContainer.scrollTo((int)(getMaxScrollRows() * scroll + 0.5f)); + } + + Slot centreSlot = this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT); + + // Update the GUI dimensions based on whether there is a wand present and whether there are bookshelves + + xSize = MAIN_GUI_WIDTH; + guiLeft = (this.width - MAIN_GUI_WIDTH) / 2; + + if(centreSlot.getHasStack() && centreSlot.getStack().getItem() instanceof IWorkbenchItem + && ((IWorkbenchItem)centreSlot.getStack().getItem()).showTooltip(centreSlot.getStack())){ + xSize += TOOLTIP_WIDTH; + } + + if(arcaneWorkbenchContainer.hasBookshelves()){ + xSize += BOOKSHELF_UI_WIDTH; + guiLeft -= BOOKSHELF_UI_WIDTH; + } + + // Show/hide the relevant gui elements + this.applyBtn.enabled = centreSlot.getHasStack(); + for(GuiButton button : this.sortButtons) button.visible = arcaneWorkbenchContainer.hasBookshelves(); + this.searchField.setVisible(arcaneWorkbenchContainer.hasBookshelves()); super.drawScreen(mouseX, mouseY, partialTicks); @@ -121,10 +220,14 @@ public class GuiArcaneWorkbench extends GuiContainer { GlStateManager.color(1, 1, 1, 1); Minecraft.getMinecraft().renderEngine.bindTexture(texture); + // Coordinates of the top left corner of the main GUI + int left = arcaneWorkbenchContainer.hasBookshelves() ? guiLeft + BOOKSHELF_UI_WIDTH : guiLeft; + int top = guiTop; + // Animation // Grey background - DrawingUtils.drawTexturedRect(guiLeft + RUNE_LEFT, guiTop + RUNE_TOP, MAIN_GUI_WIDTH + TOOLTIP_WIDTH, 0, + DrawingUtils.drawTexturedRect(left + RUNE_LEFT, top + RUNE_TOP, MAIN_GUI_WIDTH + TOOLTIP_WIDTH, 0, RUNE_WIDTH, RUNE_HEIGHT, TEXTURE_WIDTH, TEXTURE_HEIGHT); // Yellow 'halo' @@ -134,8 +237,8 @@ public class GuiArcaneWorkbench extends GuiContainer { 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; + int x = left + RUNE_LEFT + RUNE_WIDTH/2; + int y = top + 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 @@ -150,11 +253,11 @@ public class GuiArcaneWorkbench extends GuiContainer { } // Main inventory - DrawingUtils.drawTexturedRect(guiLeft, guiTop, 0, 0, MAIN_GUI_WIDTH, ySize, TEXTURE_WIDTH, TEXTURE_HEIGHT); + DrawingUtils.drawTexturedRect(left, top, 0, 0, MAIN_GUI_WIDTH, ySize, TEXTURE_WIDTH, TEXTURE_HEIGHT); float opacity = (animationTimer + partialTicks)/ANIMATION_DURATION; - // Changing slots + // Spell book slots (always use guiLeft and guiTop here regardless of bookshelf UI visibility for(int i = 0; i < ContainerArcaneWorkbench.CRYSTAL_SLOT; i++){ Slot slot = this.inventorySlots.getSlot(i); @@ -218,6 +321,18 @@ public class GuiArcaneWorkbench extends GuiContainer { } } + // Bookshelf interface + if(arcaneWorkbenchContainer.hasBookshelves()){ + + DrawingUtils.drawTexturedRect(left - BOOKSHELF_UI_WIDTH, top, 0, 256, BOOKSHELF_UI_WIDTH, ySize, TEXTURE_WIDTH, TEXTURE_HEIGHT); + + // Scroll bar + DrawingUtils.drawTexturedRect(left - BOOKSHELF_UI_WIDTH + SCROLL_BAR_LEFT, + top + SCROLL_BAR_TOP + (int)(scroll * (SCROLL_BAR_HEIGHT - SCROLL_HANDLE_HEIGHT) + 0.5f), + getMaxScrollRows() > 0 ? 0 : SCROLL_BAR_WIDTH, 476, + SCROLL_BAR_WIDTH, SCROLL_HANDLE_HEIGHT, TEXTURE_WIDTH, TEXTURE_HEIGHT); + } + // Tooltip only drawn if there is a wand if(this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getHasStack()){ @@ -230,83 +345,28 @@ public class GuiArcaneWorkbench extends GuiContainer { if(((IWorkbenchItem)stack.getItem()).showTooltip(stack)){ + int tooltipHeight = tooltipElements.stream().mapToInt(e -> e.getTotalHeight(stack)).sum() + - tooltipElements.get(tooltipElements.size() - 1).spaceAfter; // Remove space after last element + // Tooltip box - DrawingUtils.drawTexturedRect(guiLeft + MAIN_GUI_WIDTH, guiTop, MAIN_GUI_WIDTH, 0, TOOLTIP_WIDTH, ySize, TEXTURE_WIDTH, TEXTURE_HEIGHT); + DrawingUtils.drawTexturedRect(left + MAIN_GUI_WIDTH, top, MAIN_GUI_WIDTH, 0, TOOLTIP_WIDTH, + TOOLTIP_BORDER + tooltipHeight, TEXTURE_WIDTH, TEXTURE_HEIGHT); + DrawingUtils.drawTexturedRect(left + MAIN_GUI_WIDTH, top + TOOLTIP_BORDER + tooltipHeight, + MAIN_GUI_WIDTH, ySize - TOOLTIP_BORDER, TOOLTIP_WIDTH, TOOLTIP_BORDER, TEXTURE_WIDTH, TEXTURE_HEIGHT); - int y = guiTop + 20; + int x = left + MAIN_GUI_WIDTH + TOOLTIP_BORDER; + int y = top + TOOLTIP_BORDER; - if(stack.getItem() instanceof IManaStoringItem && ((IManaStoringItem)stack.getItem()).showManaInWorkbench(this.mc.player, stack)){ - y += 14; - } - - // Progression bar - if(stack.getItem() instanceof ItemWand && !Wizardry.settings.legacyWandLevelling){ - - y += 10; - - Tier tier = ((ItemWand)stack.getItem()).tier; - - float progressFraction = 1; - - if(tier != Tier.MASTER){ - progressFraction = (float)WandHelper.getProgression(stack) / Tier.values()[tier.level + 1].progression; - } - - DrawingUtils.drawTexturedRect(guiLeft + MAIN_GUI_WIDTH + TOOLTIP_TEXT_INSET, y, MAIN_GUI_WIDTH, ySize + PROGRESSION_BAR_HEIGHT, PROGRESSION_BAR_WIDTH, PROGRESSION_BAR_HEIGHT, TEXTURE_WIDTH, TEXTURE_HEIGHT); - int width = (int)(PROGRESSION_BAR_WIDTH * progressFraction); - DrawingUtils.drawTexturedRect(guiLeft + MAIN_GUI_WIDTH + TOOLTIP_TEXT_INSET, y, MAIN_GUI_WIDTH, ySize, width, PROGRESSION_BAR_HEIGHT, TEXTURE_WIDTH, TEXTURE_HEIGHT); - - y += 8; - } - - 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 + TOOLTIP_TEXT_INSET - 1, 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 + TOOLTIP_TEXT_INSET + x, y); - this.itemRender.renderItemOverlayIntoGUI(this.fontRenderer, stack1, guiLeft + MAIN_GUI_WIDTH + TOOLTIP_TEXT_INSET + x, y, - null); - x += 18; - GlStateManager.disableDepth(); - } + for(TooltipElement element : this.tooltipElements){ + y = element.drawBackgroundLayer(x, y, stack, partialTicks, mouseX, mouseY); } } } + this.searchField.drawTextBox(); // Easier to do this last, then we don't need to re-bind the GUI texture twice + + GlStateManager.color(1, 1, 1, 1); + 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! @@ -319,11 +379,17 @@ public class GuiArcaneWorkbench extends GuiContainer { GlStateManager.color(1, 1, 1, 1); // Just in case + int left = arcaneWorkbenchContainer.hasBookshelves() ? BOOKSHELF_UI_WIDTH : 0; + this.fontRenderer .drawString(this.arcaneWorkbenchInventory.hasCustomName() ? this.arcaneWorkbenchInventory.getName() - : I18n.format(this.arcaneWorkbenchInventory.getName()), 8, 6, 4210752); + : I18n.format(this.arcaneWorkbenchInventory.getName()), left + 8, 6, 4210752); this.fontRenderer.drawString(this.playerInventory.hasCustomName() ? this.playerInventory.getName() - : I18n.format(this.playerInventory.getName()), 8, this.ySize - 96 + 2, 4210752); + : I18n.format(this.playerInventory.getName()), left + 8, this.ySize - 96 + 2, 4210752); + + if(arcaneWorkbenchContainer.hasBookshelves()){ + this.fontRenderer.drawString(I18n.format("container." + Wizardry.MODID + ":arcane_workbench.bookshelves"), 8, 6, 4210752); + } if(this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getHasStack()){ @@ -336,113 +402,38 @@ public class GuiArcaneWorkbench extends GuiContainer { if(((IWorkbenchItem)stack.getItem()).showTooltip(stack)){ - int y = 6; + int x = left + MAIN_GUI_WIDTH + TOOLTIP_BORDER; + int y = TOOLTIP_BORDER; - this.fontRenderer.drawStringWithShadow("\u00A7f" + stack.getDisplayName(), MAIN_GUI_WIDTH + TOOLTIP_TEXT_INSET, 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 + TOOLTIP_TEXT_INSET, y, 0); - } - - // Progression tier text - if(stack.getItem() instanceof ItemWand && !Wizardry.settings.legacyWandLevelling){ - - y += 14; - - Tier tier = ((ItemWand)stack.getItem()).tier; - - this.fontRenderer.drawStringWithShadow(tier.getDisplayNameWithFormatting(), MAIN_GUI_WIDTH + TOOLTIP_TEXT_INSET, y, 0); - - if(tier != Tier.MASTER){ - Tier nextTier = Tier.values()[tier.level + 1]; - String s = TextFormatting.DARK_GRAY.toString() + nextTier.getDisplayName(); - if(WandHelper.getProgression(stack) >= nextTier.progression) s = nextTier.getDisplayNameWithFormatting(); - this.fontRenderer.drawStringWithShadow(s, MAIN_GUI_WIDTH + TOOLTIP_WIDTH - - TOOLTIP_TEXT_INSET - fontRenderer.getStringWidth(s), y, 0); - } - - y += 4; - } - - 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 + TOOLTIP_TEXT_INSET + 10, y, 0); - }else{ - this.mc.standardGalacticFontRenderer.drawStringWithShadow( - "\u00A79" + SpellGlyphData.getGlyphName(spell, this.mc.world), MAIN_GUI_WIDTH + TOOLTIP_TEXT_INSET + 10, 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 + TOOLTIP_TEXT_INSET, 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 + TOOLTIP_TEXT_INSET + x, y, 16, 16, mouseX, mouseY)){ - ItemStack stack1 = new ItemStack(item, level); - this.renderToolTip(stack1, mouseX - guiLeft, mouseY - guiTop); - } - x += 18; - } - } + for(TooltipElement element : this.tooltipElements){ + y = element.drawForegroundLayer(x, y, stack, mouseX, mouseY); } } } + + this.buttonList.forEach(b -> b.drawButtonForegroundLayer(mouseX - guiLeft, mouseY - guiTop)); + + // Search tooltip + if(isPointInRegion(searchField.x, searchField.y, searchField.width, searchField.height, mouseX + guiLeft, mouseY + guiTop)){ + if(searchBarHoverTime == 0){ + searchBarHoverTime++; + }else if(searchBarHoverTime == SEARCH_TOOLTIP_HOVER_TIME){ + drawHoveringText(I18n.format("container." + Wizardry.MODID + ":arcane_workbench.search_tooltip", + TOOLTIP_SYNTAX.getFormattingCode(), TOOLTIP_BODY.getFormattingCode()), mouseX - guiLeft, mouseY - guiTop); + } + }else{ + searchBarHoverTime = 0; + } } - @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); - } + // Controls @Override protected void actionPerformed(GuiButton button){ + if(button.enabled){ - if(button.id == 0){ + + if(button == applyBtn){ // Packet building IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.APPLY_BUTTON); WizardryPacketHandler.net.sendToServer(msg); @@ -452,10 +443,538 @@ public class GuiArcaneWorkbench extends GuiContainer { // Animation animationTimer = 20; } + + if(button instanceof GuiButtonSpellSort) this.arcaneWorkbenchContainer.setSortType(((GuiButtonSpellSort)button).sortType); } } - private class GuiButtonApply extends GuiButton { + private int getMaxScrollRows(){ + return Math.max(0, MathHelper.ceil((float)arcaneWorkbenchContainer.getActiveBookshelfSlots().size() + / ContainerArcaneWorkbench.BOOKSHELF_SLOTS_X) - ContainerArcaneWorkbench.BOOKSHELF_SLOTS_Y); + } + + @Override + public void handleMouseInput() throws IOException { + + super.handleMouseInput(); + int scrollDist = -Mouse.getEventDWheel(); + + if(scrollDist != 0 && getMaxScrollRows() > 0){ + + if(scrollDist > 0) this.scroll += 1f / getMaxScrollRows(); + if(scrollDist < 0) this.scroll -= 1f / getMaxScrollRows(); + + scroll = MathHelper.clamp(scroll, 0, 1); + + arcaneWorkbenchContainer.scrollTo((int)(scroll * getMaxScrollRows() + 0.5f)); + } + } + + @Override + protected void handleMouseClick(Slot slot, int slotId, int mouseButton, ClickType type){ + + searchNeedsClearing = true; + + // Click type behaves weirdly, don't use it! Query the item stack held by the cursor instead + if(slot instanceof SlotBookList && ((SlotBookList)slot).hasDelegate() && playerInventory.getItemStack().isEmpty()){ + // If no item is currently being moved, divert book list slots to send the virtual slot through instead + // See explanation in ContainerArcaneWorkbench + Slot virtualSlot = ((SlotBookList)slot).getDelegate(); + super.handleMouseClick(virtualSlot, virtualSlot.slotNumber, mouseButton, type); + }else{ + super.handleMouseClick(slot, slotId, mouseButton, type); + } + + arcaneWorkbenchContainer.updateActiveBookshelfSlots(); + } + + @Override + protected void keyTyped(char typedChar, int keyCode) throws IOException { + + if(this.searchNeedsClearing){ + this.searchNeedsClearing = false; + this.searchField.setText(""); + } + + if(this.searchField.textboxKeyTyped(typedChar, keyCode)){ + arcaneWorkbenchContainer.setSearchText(searchField.getText().toLowerCase(Locale.ROOT)); + }else{ + super.keyTyped(typedChar, keyCode); + } + } + + @SubscribeEvent + public static void onTextureStitchEvent(TextureStitchEvent.Pre event){ + event.getMap().registerSprite(ContainerArcaneWorkbench.EMPTY_SLOT_CRYSTAL); + event.getMap().registerSprite(ContainerArcaneWorkbench.EMPTY_SLOT_UPGRADE); + } + + private abstract static class TooltipElement { + + private final TooltipElement[] children; + private final int spaceAfter; + + public TooltipElement(int spaceAfter, TooltipElement... children){ + this.children = children; + this.spaceAfter = spaceAfter; + } + + // Externally-called methods + + /** + * Returns the height of this tooltip element and its children, with spacing, or 0 if it is not visible. + * @param stack The item stack currently in the central slot of the workbench + * @return The total height of this tooltip element and its children, including spacing + */ + public int getTotalHeight(ItemStack stack){ + if(!this.isVisible(stack)) return 0; + int height = this.getHeight(stack); + for(TooltipElement child : children) height += child.getTotalHeight(stack); + return height + spaceAfter; + } + + /** + * Draws the background layer of this tooltip element and all of its children. + * @param x The x-coordinate of the top left corner of this element + * @param y The y-coordinate of the top left corner of this element + * @param stack The item stack currently in the central slot of the workbench + * @param partialTicks The current partial tick time + * @param mouseX The current x-coordinate of the cursor + * @param mouseY The current y-coordinate of the cursor + * @return The y-coordinate at which the next tooltip element should start + */ + public int drawBackgroundLayer(int x, int y, ItemStack stack, float partialTicks, int mouseX, int mouseY){ + if(!this.isVisible(stack)) return y; + this.drawBackground(x, y, stack, partialTicks, mouseX, mouseY); + y += this.getHeight(stack); + for(TooltipElement child : children) y = child.drawBackgroundLayer(x, y, stack, partialTicks, mouseX, mouseY); + return y + spaceAfter; + } + + /** + * Draws the foreground layer of this tooltip element and all of its children. + * @param x The x-coordinate of the top left corner of this element + * @param y The y-coordinate of the top left corner of this element + * @param stack The item stack currently in the central slot of the workbench + * @param mouseX The current x-coordinate of the cursor + * @param mouseY The current y-coordinate of the cursor + * @return The y-coordinate at which the next tooltip element should start + */ + public int drawForegroundLayer(int x, int y, ItemStack stack, int mouseX, int mouseY){ + if(!this.isVisible(stack)) return y; + this.drawForeground(x, y, stack, mouseX, mouseY); + y += this.getHeight(stack); + for(TooltipElement child : children) y = child.drawForegroundLayer(x, y, stack, mouseX, mouseY); + return y + spaceAfter; + } + + // Abstract internal methods + + /** + * Returns whether this tooltip element should be shown. + * @param stack The item stack currently in the central slot of the workbench + * @return True if this element should be shown, false if not. + */ + protected abstract boolean isVisible(ItemStack stack); + + /** + * Returns the height of this tooltip element. This is for internal implementation. + * @param stack The item stack currently in the central slot of the workbench + * @return The height of this tooltip element, excluding children and spacing. + */ + protected abstract int getHeight(ItemStack stack); + + /** + * Draws the background layer of this tooltip element (excluding children). This is for internal implementation. + * @param x The x-coordinate of the top left corner of this element + * @param y The y-coordinate of the top left corner of this element + * @param stack The item stack currently in the central slot of the workbench + * @param partialTicks The current partial tick time + * @param mouseX The current x-coordinate of the cursor + * @param mouseY The current y-coordinate of the cursor + */ + protected abstract void drawBackground(int x, int y, ItemStack stack, float partialTicks, int mouseX, int mouseY); + + /** + * Draws the foreground layer of this tooltip element (excluding children). This is for internal implementation. + * @param x The x-coordinate of the top left corner of this element + * @param y The y-coordinate of the top left corner of this element + * @param stack The item stack currently in the central slot of the workbench + * @param mouseX The current x-coordinate of the cursor + * @param mouseY The current y-coordinate of the cursor + */ + protected abstract void drawForeground(int x, int y, ItemStack stack, int mouseX, int mouseY); + + } + + private class TooltipElementText extends TooltipElement { + + private final String text; // Can't change the language whilst in a GUI so we can just store the translated text + private final Style style; + + public TooltipElementText(String text, Style style, int spaceAfter, TooltipElement... children){ + super(spaceAfter, children); + this.text = text; + this.style = style; + } + + /** Returns the text for this element. */ + protected String getText(ItemStack stack){ + return text; + } + + protected FontRenderer getFontRenderer(ItemStack stack){ + return fontRenderer; + } + + protected int getColour(ItemStack stack){ + return 0; + } + + @Override + protected boolean isVisible(ItemStack stack){ + return true; // Always visible by default + } + + @Override + protected int getHeight(ItemStack stack){ + return getFontRenderer(stack).listFormattedStringToWidth(getText(stack), TOOLTIP_WIDTH - 2 * TOOLTIP_BORDER) + .size() * getFontRenderer(stack).FONT_HEIGHT; + } + + @Override + protected void drawBackground(int x, int y, ItemStack stack, float partialTicks, int mouseX, int mouseY){ + // Nothing here because this element is only text! + } + + @Override + protected void drawForeground(int x, int y, ItemStack stack, int mouseX, int mouseY){ + for(String line : getFontRenderer(stack).listFormattedStringToWidth(getText(stack), TOOLTIP_WIDTH - 2 * TOOLTIP_BORDER)){ + getFontRenderer(stack).drawStringWithShadow(style.getFormattingCode() + line, x, y, getColour(stack)); + y += getFontRenderer(stack).FONT_HEIGHT; + } + } + } + + private class TooltipElementItemName extends TooltipElementText { + + public TooltipElementItemName(Style style, int spaceAfter){ + super(null, style, spaceAfter); + } + + @Override + protected String getText(ItemStack stack){ + return stack.getDisplayName(); + } + + } + + private class TooltipElementManaReadout extends TooltipElementText { + + public TooltipElementManaReadout(int spaceAfter){ + super(null, new Style().setColor(TextFormatting.BLUE), spaceAfter); + } + + @Override + protected String getText(ItemStack stack){ + return I18n.format("container." + Wizardry.MODID + ":arcane_workbench.mana", + ((IManaStoringItem)stack.getItem()).getMana(stack), + ((IManaStoringItem)stack.getItem()).getManaCapacity(stack)); + } + + @Override + protected boolean isVisible(ItemStack stack){ + return stack.getItem() instanceof IManaStoringItem && ((IManaStoringItem)stack.getItem()).showManaInWorkbench(mc.player, stack); + } + + } + + private class TooltipElementProgressionBar extends TooltipElement { + + private static final int PROGRESSION_BAR_WIDTH = 131; + private static final int PROGRESSION_BAR_HEIGHT = 3; + + public TooltipElementProgressionBar(int spaceAfter){ + super(spaceAfter); + } + + @Override + protected boolean isVisible(ItemStack stack){ + return stack.getItem() instanceof ItemWand && !Wizardry.settings.legacyWandLevelling; + } + + @Override + protected int getHeight(ItemStack stack){ + return fontRenderer.FONT_HEIGHT + LINE_SPACING_NARROW + PROGRESSION_BAR_HEIGHT; + } + + @Override + protected void drawBackground(int x, int y, ItemStack stack, float partialTicks, int mouseX, int mouseY){ + + y += fontRenderer.FONT_HEIGHT + LINE_SPACING_NARROW; + + Tier tier = ((ItemWand)stack.getItem()).tier; // Only called if isVisible() returns true so this is ok + + float progressFraction = 1; + + if(tier != Tier.MASTER){ + progressFraction = (float)WandHelper.getProgression(stack) / Tier.values()[tier.level + 1].getProgression(); + } + + DrawingUtils.drawTexturedRect(x, y, MAIN_GUI_WIDTH, ySize + PROGRESSION_BAR_HEIGHT, PROGRESSION_BAR_WIDTH, PROGRESSION_BAR_HEIGHT, TEXTURE_WIDTH, TEXTURE_HEIGHT); + int width = (int)(PROGRESSION_BAR_WIDTH * progressFraction); + DrawingUtils.drawTexturedRect(x, y, MAIN_GUI_WIDTH, ySize, width, PROGRESSION_BAR_HEIGHT, TEXTURE_WIDTH, TEXTURE_HEIGHT); + } + + @Override + protected void drawForeground(int x, int y, ItemStack stack, int mouseX, int mouseY){ + + Tier tier = ((ItemWand)stack.getItem()).tier; + + fontRenderer.drawStringWithShadow(tier.getDisplayNameWithFormatting(), x, y, 0); + + if(tier != Tier.MASTER){ + Tier nextTier = Tier.values()[tier.level + 1]; + String s = TextFormatting.DARK_GRAY.toString() + nextTier.getDisplayName(); + if(WandHelper.getProgression(stack) >= nextTier.getProgression()) s = nextTier.getDisplayNameWithFormatting(); + fontRenderer.drawStringWithShadow(s, x + TOOLTIP_WIDTH - TOOLTIP_BORDER * 2 - fontRenderer.getStringWidth(s), y, 0); + } + } + + } + + private class TooltipElementSpellList extends TooltipElement { + + public TooltipElementSpellList(int spaceAfter){ + super(spaceAfter, generateSpellEntries(8)); + } + + @Override + protected boolean isVisible(ItemStack stack){ + return stack.getItem() instanceof ISpellCastingItem && ((ISpellCastingItem)stack.getItem()).showSpellsInWorkbench(mc.player, stack); + } + + @Override + protected int getHeight(ItemStack stack){ + return 0; // Doesn't have any height of its own + } + + @Override + public int drawBackgroundLayer(int x, int y, ItemStack stack, float partialTicks, int mouseX, int mouseY){ + // It's more efficient to do GL state changes once in here + GlStateManager.enableBlend(); + y = super.drawBackgroundLayer(x, y, stack, partialTicks, mouseX, mouseY); + GlStateManager.disableBlend(); + return y; + } + + @Override + protected void drawBackground(int x, int y, ItemStack stack, float partialTicks, int mouseX, int mouseY){ + // Has no background of its own + } + + @Override + protected void drawForeground(int x, int y, ItemStack stack, int mouseX, int mouseY){ + //Has no text of its own + } + } + + private TooltipElement[] generateSpellEntries(int count){ + TooltipElement[] entries = new TooltipElement[count]; + for(int i=0; i 0; + } + + } + + private class TooltipElementUpgrades extends TooltipElement { + + private static final int ITEM_SIZE = 16; + private static final int ITEM_SPACING = 2; + + public TooltipElementUpgrades(int spaceAfter){ + super(spaceAfter); + } + + @Override + protected boolean isVisible(ItemStack stack){ + return true; // Handled by parent + } + + @Override + protected int getHeight(ItemStack stack){ + int rows = 1 + (WandHelper.getTotalUpgrades(stack) * (ITEM_SIZE + ITEM_SPACING) - ITEM_SPACING) + / (TOOLTIP_WIDTH - TOOLTIP_BORDER * 2); + return rows * (ITEM_SIZE + ITEM_SPACING) - ITEM_SPACING; + } + + @Override + protected void drawBackground(int x, int y, ItemStack stack, float partialTicks, int mouseX, int mouseY){ + + GlStateManager.enableDepth(); + + int x1 = 0; + + // Upgrades + for(Item item : WandHelper.getSpecialUpgrades()){ + + int level = WandHelper.getUpgradeLevel(stack, item); + + if(level > 0){ + + ItemStack upgrade = new ItemStack(item, level); + + itemRender.renderItemAndEffectIntoGUI(upgrade, x + x1, y); + itemRender.renderItemOverlayIntoGUI(fontRenderer, upgrade, x + x1, y, null); + + x1 += ITEM_SIZE + ITEM_SPACING; + + if(x1 + ITEM_SIZE > TOOLTIP_WIDTH - TOOLTIP_BORDER * 2){ + x1 = 0; + y += ITEM_SIZE + ITEM_SPACING; + } + } + } + + GlStateManager.disableDepth(); + GlStateManager.disableLighting(); // Whyyyyyy? + } + + @Override + protected void drawForeground(int x, int y, ItemStack stack, int mouseX, int mouseY){ + + int x1 = 0; + + // 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(x + x1, y, ITEM_SIZE, ITEM_SIZE, mouseX, mouseY)){ + ItemStack upgrade = new ItemStack(item, level); + renderToolTip(upgrade, mouseX - guiLeft, mouseY - guiTop); + } + + x1 += ITEM_SIZE + ITEM_SPACING; + + if(TOOLTIP_BORDER * 2 + x1 + ITEM_SIZE > TOOLTIP_WIDTH){ + x1 = 0; + y += ITEM_SIZE + ITEM_SPACING; + } + + } + } + + } + + } + + private static 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")); @@ -481,16 +1000,10 @@ public class GuiArcaneWorkbench extends GuiContainer { //colour = 10526880; } - DrawingUtils.drawTexturedRect(this.x, this.y, k, l, this.width, this.height, 512, 256); + DrawingUtils.drawTexturedRect(this.x, this.y, k, l, this.width, this.height, TEXTURE_WIDTH, TEXTURE_HEIGHT); //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/gui/GuiBookshelf.java b/src/main/java/electroblob/wizardry/client/gui/GuiBookshelf.java new file mode 100644 index 00000000..476674dd --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/GuiBookshelf.java @@ -0,0 +1,54 @@ +package electroblob.wizardry.client.gui; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.inventory.ContainerBookshelf; +import electroblob.wizardry.tileentity.TileEntityBookshelf; +import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public class GuiBookshelf extends GuiContainer { + + private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/gui/container/bookshelf.png"); + /** The player inventory bound to this GUI. */ + private final InventoryPlayer playerInventory; + /** The inventory contained within the corresponding bookshelf. */ + public IInventory bookshelfInventory; + + public GuiBookshelf(InventoryPlayer playerInv, TileEntityBookshelf bookshelfInv){ + super(new ContainerBookshelf(playerInv, bookshelfInv)); + this.playerInventory = playerInv; + this.bookshelfInventory = bookshelfInv; + this.allowUserInput = false; + this.ySize = 148; + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks){ + this.drawDefaultBackground(); + super.drawScreen(mouseX, mouseY, partialTicks); + this.renderHoveredToolTip(mouseX, mouseY); + } + + @Override + protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY){ + String s = this.bookshelfInventory.getDisplayName().getUnformattedText(); + this.fontRenderer.drawString(s, 8, 6, 4210752); + this.fontRenderer.drawString(this.playerInventory.getDisplayName().getUnformattedText(), 8, this.ySize - 96 + 2, 4210752); + } + + @Override + protected void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY){ + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + this.mc.getTextureManager().bindTexture(TEXTURE); + int i = (this.width - this.xSize) / 2; + int j = (this.height - this.ySize) / 2; + this.drawTexturedModalRect(i, j, 0, 0, this.xSize, this.ySize); + } + +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/client/gui/GuiButtonResurrect.java b/src/main/java/electroblob/wizardry/client/gui/GuiButtonResurrect.java index bf50192c..e9ad09d3 100644 --- a/src/main/java/electroblob/wizardry/client/gui/GuiButtonResurrect.java +++ b/src/main/java/electroblob/wizardry/client/gui/GuiButtonResurrect.java @@ -7,8 +7,8 @@ import electroblob.wizardry.packet.WizardryPacketHandler; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.spell.Resurrection; +import electroblob.wizardry.util.InventoryUtils; 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; @@ -52,7 +52,7 @@ public class GuiButtonResurrect extends GuiButton { 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))){ + && InventoryUtils.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")); @@ -65,7 +65,7 @@ public class GuiButtonResurrect extends GuiButton { if(event.getGui() instanceof GuiGameOver){ - ItemStack stack = WizardryUtilities.getHotbar(Minecraft.getMinecraft().player).stream() + ItemStack stack = InventoryUtils.getHotbar(Minecraft.getMinecraft().player).stream() .filter(s -> Resurrection.canStackResurrect(s, Minecraft.getMinecraft().player)).findFirst().orElse(null); if(stack != null){ diff --git a/src/main/java/electroblob/wizardry/client/gui/GuiButtonSpellSort.java b/src/main/java/electroblob/wizardry/client/gui/GuiButtonSpellSort.java new file mode 100644 index 00000000..cfde6624 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/GuiButtonSpellSort.java @@ -0,0 +1,57 @@ +package electroblob.wizardry.client.gui; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.DrawingUtils; +import electroblob.wizardry.util.ISpellSortable; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.resources.I18n; +import net.minecraft.util.ResourceLocation; + +public class GuiButtonSpellSort extends GuiButton { + + private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/gui/container/spell_sort_buttons.png"); + private static final int TEXTURE_WIDTH = 32; + private static final int TEXTURE_HEIGHT = 32; + + public final ISpellSortable.SortType sortType; + + private final ISpellSortable sortable; + private final GuiScreen parent; + + public GuiButtonSpellSort(int id, int x, int y, ISpellSortable.SortType sortType, ISpellSortable sortable, GuiScreen parent){ + super(id, x, y, 10, 10, I18n.format("container." + Wizardry.MODID + ":arcane_workbench.sort_" + sortType.name)); + this.sortType = sortType; + this.sortable = sortable; + this.parent = parent; + } + + @Override + public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partialTicks){ + + if(this.visible){ + + // 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 = 0; + int l = this.sortType.ordinal() * this.height; + + if(sortType == sortable.getSortType()){ + k += this.width; + if(sortable.isSortDescending()) k += this.width; + } + + parent.mc.getTextureManager().bindTexture(TEXTURE); + DrawingUtils.drawTexturedRect(this.x, this.y, k, l, this.width, this.height, TEXTURE_WIDTH, TEXTURE_HEIGHT); + + } + } + + @Override + public void drawButtonForegroundLayer(int mouseX, int mouseY){ + if(hovered) parent.drawHoveringText(this.displayString, mouseX, mouseY); + } + +} diff --git a/src/main/java/electroblob/wizardry/client/gui/handbook/GuiButtonTurnPage.java b/src/main/java/electroblob/wizardry/client/gui/GuiButtonTurnPage.java similarity index 69% rename from src/main/java/electroblob/wizardry/client/gui/handbook/GuiButtonTurnPage.java rename to src/main/java/electroblob/wizardry/client/gui/GuiButtonTurnPage.java index 662e71a1..ef45cae1 100644 --- a/src/main/java/electroblob/wizardry/client/gui/handbook/GuiButtonTurnPage.java +++ b/src/main/java/electroblob/wizardry/client/gui/GuiButtonTurnPage.java @@ -1,6 +1,5 @@ -package electroblob.wizardry.client.gui.handbook; +package electroblob.wizardry.client.gui; -import electroblob.wizardry.Wizardry; import electroblob.wizardry.client.DrawingUtils; import electroblob.wizardry.registry.WizardrySounds; import net.minecraft.client.Minecraft; @@ -11,12 +10,12 @@ import net.minecraft.client.renderer.GlStateManager; import net.minecraft.util.ResourceLocation; //@SideOnly(Side.CLIENT) -class GuiButtonTurnPage extends GuiButton { +public class GuiButtonTurnPage extends GuiButton { - static final int WIDTH = 20; - static final int HEIGHT = 12; + public static final int WIDTH = 20; + public static final int HEIGHT = 12; - enum Type { + public enum Type { NEXT_PAGE(0, 196), PREVIOUS_PAGE(0, 208), @@ -34,11 +33,15 @@ class GuiButtonTurnPage extends GuiButton { public final Type type; - private static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID, "textures/gui/handbook.png"); + private final ResourceLocation texture; + private final int textureWidth, textureHeight; - public GuiButtonTurnPage(int id, int x, int y, Type type){ + public GuiButtonTurnPage(int id, int x, int y, Type type, ResourceLocation texture, int textureWidth, int textureHeight){ super(id, x, y, WIDTH, HEIGHT, ""); this.type = type; + this.texture = texture; + this.textureWidth = textureWidth; + this.textureHeight = textureHeight; } @Override @@ -50,12 +53,13 @@ class GuiButtonTurnPage extends GuiButton { 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); + DrawingUtils.drawTexturedRect(this.x, this.y, flag ? type.u + width : type.u, type.v, width, height, textureWidth, textureHeight); } } + } diff --git a/src/main/java/electroblob/wizardry/client/gui/GuiLectern.java b/src/main/java/electroblob/wizardry/client/gui/GuiLectern.java new file mode 100644 index 00000000..71c5bde2 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/GuiLectern.java @@ -0,0 +1,499 @@ +package electroblob.wizardry.client.gui; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.block.BlockBookshelf; +import electroblob.wizardry.client.DrawingUtils; +import electroblob.wizardry.client.gui.GuiButtonTurnPage.Type; +import electroblob.wizardry.data.SpellGlyphData; +import electroblob.wizardry.item.ItemSpellBook; +import electroblob.wizardry.packet.PacketLectern; +import electroblob.wizardry.packet.WizardryPacketHandler; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.tileentity.TileEntityLectern; +import electroblob.wizardry.util.GeometryUtils; +import electroblob.wizardry.util.ISpellSortable; +import electroblob.wizardry.util.ParticleBuilder; +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.GuiTextField; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.resources.I18n; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.network.play.client.CPacketCloseWindow; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.text.Style; +import net.minecraft.util.text.TextFormatting; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; + +public class GuiLectern extends GuiSpellInfo implements ISpellSortable { + + private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/gui/container/lectern.png"); + /** The distance of the page buttons from the bottom outside corners of the GUI. */ + private static final int PAGE_BUTTON_INSET_X = 22, PAGE_BUTTON_INSET_Y = 13; + /** The distance between adjacent page turn buttons. */ + private static final int PAGE_BUTTON_SPACING = 20; + /** The distance of the sort buttons from the top left corner of the GUI. */ + private static final int SORT_BUTTON_INSET_X = 96, SORT_BUTTON_INSET_Y = 20; + /** The distance between adjacent sort buttons. */ + private static final int SORT_BUTTON_SPACING = 13; + /** The distance of the spell buttons from the top outside corners of the GUI. */ + private static final int SPELL_BUTTON_INSET_X = 23, SPELL_BUTTON_INSET_Y = 44; + /** The distance between adjacent spell buttons (in both x and y). */ + private static final int SPELL_BUTTON_SPACING = 38; + + private static final int SPELL_ROWS = 3, SPELL_COLUMNS = 3; + public static final int SPELL_BUTTON_COUNT = SPELL_ROWS * SPELL_COLUMNS * 2; // x2 because there are 2 pages + + private static final int SEARCH_TOOLTIP_HOVER_TIME = 20; + + private static final Style TOOLTIP_SYNTAX = new Style().setColor(TextFormatting.YELLOW); + private static final Style TOOLTIP_BODY = new Style().setColor(TextFormatting.WHITE); + + private final TileEntityLectern lectern; + + private GuiButton nextPageButton; + private GuiButton prevPageButton; + private GuiButton lastPageButton; + private GuiButton firstPageButton; + private GuiButton indexButton; + private GuiButton locateButton; + + private GuiButton[] sortButtons = new GuiButton[3]; + private GuiButtonSpell[] spellButtons = new GuiButtonSpell[SPELL_BUTTON_COUNT]; + + /** The spell currently being viewed, or null if the index is being viewed. */ + private Spell currentSpell; + /** The available spells in nearby bookshelves; should not contain duplicates. */ + private List availableSpells = new ArrayList<>(); + /** The spells matching the current search criteria, sorted according to the current sort settings. Will be a subset + * of {@link GuiLectern#availableSpells}. */ + private List matchingSpells; + + private ISpellSortable.SortType sortType = ISpellSortable.SortType.TIER; + private boolean sortDescending = false; + + private GuiTextField searchField; + private boolean searchNeedsClearing; + private int searchBarHoverTime; + + private int currentPage = 0; + + public GuiLectern(TileEntityLectern lectern){ + super(288, 180); + this.lectern = lectern; + this.currentSpell = lectern.currentSpell; + this.setTextureSize(512, 512); + } + + @Override + public Spell getSpell(){ + return currentSpell; + } + + @Override + public ResourceLocation getTexture(){ + return TEXTURE; + } + + @Override + public SortType getSortType(){ + return sortType; + } + + @Override + public boolean isSortDescending(){ + return sortDescending; + } + + private int getPageCount(){ + return MathHelper.ceil((float)matchingSpells.size() / SPELL_BUTTON_COUNT); + } + + private Spell getSpellForButton(GuiButtonSpell button){ + return matchingSpells.get(currentPage * SPELL_BUTTON_COUNT + button.index); + } + + @Override + public void initGui(){ + + super.initGui(); + + final int left = this.width / 2 - this.xSize / 2; + final int top = this.height / 2 - this.ySize / 2; + + int buttonID = 0; + + // Page buttons + this.buttonList.add(nextPageButton = new GuiButtonTurnPage(buttonID++, left + xSize - PAGE_BUTTON_INSET_X - GuiButtonTurnPage.WIDTH, + top + ySize - PAGE_BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.NEXT_PAGE, TEXTURE, textureWidth, textureHeight)); + + this.buttonList.add(prevPageButton = new GuiButtonTurnPage(buttonID++, left + PAGE_BUTTON_INSET_X, + top + ySize - PAGE_BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.PREVIOUS_PAGE, TEXTURE, textureWidth, textureHeight)); + + this.buttonList.add(lastPageButton = new GuiButtonTurnPage(buttonID++, left + xSize - PAGE_BUTTON_INSET_X - GuiButtonTurnPage.WIDTH - PAGE_BUTTON_SPACING, + top + ySize - PAGE_BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.NEXT_SECTION, TEXTURE, textureWidth, textureHeight)); + + this.buttonList.add(firstPageButton = new GuiButtonTurnPage(buttonID++, left + PAGE_BUTTON_INSET_X + PAGE_BUTTON_SPACING, + top + ySize - PAGE_BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.PREVIOUS_SECTION, TEXTURE, textureWidth, textureHeight)); + + this.buttonList.add(indexButton = new GuiButtonTurnPage(buttonID++, left + xSize/2 - 23, + top + ySize - PAGE_BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.CONTENTS, TEXTURE, textureWidth, textureHeight)); + + this.buttonList.add(locateButton = new GuiButtonLocateBook(buttonID++, left + xSize/2 - 34, + top + ySize - PAGE_BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT)); + + // Sort buttons + for(SortType sortType : SortType.values()){ + this.buttonList.add(sortButtons[sortType.ordinal()] = new GuiButtonSpellSort(buttonID++, + left + SORT_BUTTON_INSET_X + SORT_BUTTON_SPACING * sortType.ordinal(), + top + SORT_BUTTON_INSET_Y, sortType, this, this)); + } + + // Spell buttons + for(int i = 0; i < SPELL_BUTTON_COUNT; i++){ + + int row = i % SPELL_COLUMNS; + int column = (i / SPELL_COLUMNS) % SPELL_ROWS; + + int x = i < SPELL_BUTTON_COUNT/2 ? SPELL_BUTTON_INSET_X + row * SPELL_BUTTON_SPACING + : xSize - SPELL_BUTTON_INSET_X - GuiButtonSpell.WIDTH - (2-row) * SPELL_BUTTON_SPACING; + int y = SPELL_BUTTON_INSET_Y + column * SPELL_BUTTON_SPACING; + + this.buttonList.add(spellButtons[i] = new GuiButtonSpell(buttonID++, left + x, top + y, i)); + } + + this.searchField = new GuiTextField(0, this.fontRenderer, left + 157, top + 21, 106, this.fontRenderer.FONT_HEIGHT); + this.searchField.setMaxStringLength(50); + this.searchField.setEnableBackgroundDrawing(false); + this.searchField.setVisible(true); + this.searchField.setTextColor(16777215); + this.searchField.setCanLoseFocus(false); + this.searchField.setFocused(true); + + refreshAvailableSpells(); // Must be done last + + } + + @Override + public void updateScreen(){ + super.updateScreen(); + if(searchBarHoverTime > 0 && searchBarHoverTime < SEARCH_TOOLTIP_HOVER_TIME) searchBarHoverTime++; + } + + @Override + public void onGuiClosed(){ + WizardryPacketHandler.net.sendToServer(new PacketLectern.Message(lectern.getPos(), currentSpell)); + super.onGuiClosed(); + } + + @Override + protected void drawBackgroundLayer(int left, int top, int mouseX, int mouseY){ + if(currentSpell == Spells.none){ + drawIndexPage(left, top); + }else{ + super.drawBackgroundLayer(left, top, mouseX, mouseY); + } + } + + @Override + protected void drawForegroundLayer(int left, int top, int mouseX, int mouseY){ + + if(currentSpell == Spells.none){ + this.fontRenderer.drawString(I18n.format("gui." + Wizardry.MODID + ":lectern.title"), + left + 20, top + SORT_BUTTON_INSET_Y, 0); + }else{ + super.drawForegroundLayer(left, top, mouseX, mouseY); + } + + this.buttonList.forEach(b -> b.drawButtonForegroundLayer(mouseX, mouseY)); + + // Search tooltip + if(DrawingUtils.isPointInRegion(searchField.x, searchField.y, searchField.width, searchField.height, mouseX, mouseY)){ + if(searchBarHoverTime == 0){ + searchBarHoverTime++; + }else if(searchBarHoverTime == SEARCH_TOOLTIP_HOVER_TIME){ + drawHoveringText(I18n.format("container." + Wizardry.MODID + ":arcane_workbench.search_tooltip", + TOOLTIP_SYNTAX.getFormattingCode(), TOOLTIP_BODY.getFormattingCode()), mouseX, mouseY); + } + }else{ + searchBarHoverTime = 0; + } + } + + private void drawIndexPage(int left, int top){ + + for(int i = 0; i < SPELL_BUTTON_COUNT; i++){ + + int index = currentPage * SPELL_BUTTON_COUNT + i; + Spell spell = index < matchingSpells.size() ? matchingSpells.get(index) : Spells.none; + boolean discovered = Wizardry.proxy.shouldDisplayDiscovered(spell, null); + + Minecraft.getMinecraft().renderEngine.bindTexture(discovered ? spell.getIcon() : Spells.none.getIcon()); + + int row = i % SPELL_COLUMNS; + int column = (i / SPELL_COLUMNS) % SPELL_ROWS; + + int x = i < SPELL_BUTTON_COUNT/2 ? SPELL_BUTTON_INSET_X + row * SPELL_BUTTON_SPACING + : xSize - SPELL_BUTTON_INSET_X - GuiButtonSpell.WIDTH - (2-row) * SPELL_BUTTON_SPACING; + int y = SPELL_BUTTON_INSET_Y + column * SPELL_BUTTON_SPACING; + + DrawingUtils.drawTexturedRect(left + x + 1, top + y + 1, 0, 0, 32, 32, 32, 32); + } + + mc.renderEngine.bindTexture(getTexture()); + DrawingUtils.drawTexturedRect(left, top, 0, 256, xSize, ySize, textureWidth, textureHeight); + + this.searchField.drawTextBox(); + + GlStateManager.color(1, 1, 1, 1); + mc.renderEngine.bindTexture(getTexture()); + + } + + @Override + protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException { + super.mouseClicked(mouseX, mouseY, mouseButton); + searchNeedsClearing = true; + } + + @Override + protected void actionPerformed(GuiButton button){ + + int lastPage = getPageCount() - 1; + + if(button == indexButton){ + currentSpell = Spells.none; +// currentPage = 0; + + }else if(button == locateButton){ + // Close the GUI + this.mc.player.connection.sendPacket(new CPacketCloseWindow(this.mc.player.openContainer.windowId)); + this.mc.displayGuiScreen(null); + + // Find the location(s) of the current spell's book(s) and highlight them + for(IInventory bookshelf : BlockBookshelf.findNearbyBookshelves(lectern.getWorld(), lectern.getPos())){ + + for(int i = 0; i < bookshelf.getSizeInventory(); i++){ + + ItemStack stack = bookshelf.getStackInSlot(i); + + if(stack.getItem() instanceof ItemSpellBook){ + + Spell spell = Spell.byMetadata(stack.getMetadata()); + + if(spell == this.currentSpell){ + + BlockPos pos = (((TileEntity)bookshelf).getPos()); + + for(EnumFacing side : EnumFacing.VALUES){ + ParticleBuilder.create(ParticleBuilder.Type.BLOCK_HIGHLIGHT).pos( + GeometryUtils.getFaceCentre(pos, side) + .add(new Vec3d(side.getDirectionVec()) + .scale(GeometryUtils.ANTI_Z_FIGHTING_OFFSET))) + .face(side).clr(0.9f, 0.5f, 0.8f).fade(0.7f, 0, 1).spawn(mc.world); + } + + mc.world.playSound(pos, WizardrySounds.BLOCK_LECTERN_LOCATE_SPELL, SoundCategory.BLOCKS, 1, 0.7f, false); + + break; // This bookshelf has the spell, skip to the next bookshelf + } + } + } + } + + }else if(button == nextPageButton){ + if(currentPage < lastPage) currentPage++; + + }else if(button == prevPageButton){ + if(currentPage > 0) currentPage--; + + }else if(button == lastPageButton){ + currentPage = lastPage; + + }else if(button == firstPageButton){ + currentPage = 0; + + }else if(button instanceof GuiButtonSpell){ + currentSpell = getSpellForButton((GuiButtonSpell)button); + + }else if(button instanceof GuiButtonSpellSort){ + + SortType sortType = (((GuiButtonSpellSort)button).sortType); + + if(this.sortType == sortType){ + this.sortDescending = !this.sortDescending; + }else{ + this.sortType = sortType; + this.sortDescending = false; + } + + updateMatchingSpells(); + } + + updateButtonVisiblity(); + + } + + @Override + protected void keyTyped(char typedChar, int keyCode) throws IOException { + + if(this.searchNeedsClearing){ + this.searchNeedsClearing = false; + this.searchField.setText(""); + this.currentPage = 0; + } + + if(this.searchField.textboxKeyTyped(typedChar, keyCode)){ + this.currentPage = 0; + updateMatchingSpells(); + updateButtonVisiblity(); + }else{ + super.keyTyped(typedChar, keyCode); + } + } + + private void updateButtonVisiblity(){ + + if(currentSpell == Spells.none){ + + this.searchField.setVisible(true); + + int lastPage = getPageCount() - 1; + + prevPageButton.visible = currentPage > 0; + firstPageButton.visible = currentPage > 0; + nextPageButton.visible = currentPage < lastPage; + lastPageButton.visible = currentPage < lastPage; + + indexButton.visible = false; + locateButton.visible = false; + + for(GuiButton button : sortButtons) button.visible = true; + + for(GuiButtonSpell button : spellButtons){ + button.visible = currentPage * SPELL_BUTTON_COUNT + button.index < matchingSpells.size(); + } + + }else{ + this.searchField.setVisible(false); + this.buttonList.forEach(b -> b.visible = false); // Hide all buttons... + indexButton.visible = true; // ... except the index button and locate button + locateButton.visible = true; + } + } + + private void updateMatchingSpells(){ + matchingSpells = availableSpells.stream() + .filter(s -> s.matches(searchField.getText().toLowerCase(Locale.ROOT))) + .sorted(sortDescending ? sortType.comparator.reversed() : sortType.comparator) + .collect(Collectors.toList()); + } + + // TODO: Config option to always display all spells when in creative + /** Called on initialisation and whenever a bookshelf is added or removed, to update the list of spells. */ + public void refreshAvailableSpells(){ + + availableSpells.clear(); + + // No need to exclude the lectern TE because it's not an IInventory! + for(IInventory bookshelf : BlockBookshelf.findNearbyBookshelves(lectern.getWorld(), lectern.getPos())){ + + for(int i=0; i= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height; + GlStateManager.color(1, 1, 1, 1); + minecraft.getTextureManager().bindTexture(TEXTURE); + + DrawingUtils.drawTexturedRect(this.x, this.y, flag ? width : 0, 184, width, height, textureWidth, textureHeight); + } + } + + } + + private class GuiButtonSpell extends GuiButtonInvisible { + + private static final int WIDTH = 34, HEIGHT = 34; + + private final int index; + + public GuiButtonSpell(int id, int x, int y, int index){ + super(id, x, y, WIDTH, HEIGHT); + this.index = index; + } + + @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(visible){ + super.drawButton(minecraft, mouseX, mouseY, partialTicks); + if(hovered){ + mc.renderEngine.bindTexture(getTexture()); + DrawingUtils.drawTexturedRect(x, y, 40, 180, width, height, textureWidth, textureHeight); + } + } + } + + @Override + public void drawButtonForegroundLayer(int mouseX, int mouseY){ + if(visible && hovered){ + Spell spell = getSpellForButton(this); + if(Wizardry.proxy.shouldDisplayDiscovered(spell, null)){ + drawHoveringText(Collections.singletonList(spell.getDisplayName()), mouseX, mouseY, fontRenderer); + }else{ + drawHoveringText(Collections.singletonList(SpellGlyphData.getGlyphName(spell, mc.world)), + mouseX, mouseY, mc.standardGalacticFontRenderer); + } + } + } + + } + +} diff --git a/src/main/java/electroblob/wizardry/client/gui/GuiSpellBook.java b/src/main/java/electroblob/wizardry/client/gui/GuiSpellBook.java index 3e26c194..d46a9886 100644 --- a/src/main/java/electroblob/wizardry/client/gui/GuiSpellBook.java +++ b/src/main/java/electroblob/wizardry/client/gui/GuiSpellBook.java @@ -1,113 +1,35 @@ package electroblob.wizardry.client.gui; -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.item.ItemSpellBook; -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.client.resources.I18n; -import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; -import org.lwjgl.input.Keyboard; +import net.minecraft.util.ResourceLocation; -public class GuiSpellBook extends GuiScreen { +public class GuiSpellBook extends GuiSpellInfo { - private int xSize, ySize; - private ItemSpellBook book; - private Spell spell; + private final ItemSpellBook book; + private final Spell spell; public GuiSpellBook(ItemStack stack){ - super(); - xSize = 288; - ySize = 180; - if(!(stack.getItem() instanceof ItemSpellBook)) throw new ClassCastException("Cannot create spell book GUI for item that does not extend ItemSpellBook!"); + + super(288, 180); + + if(!(stack.getItem() instanceof ItemSpellBook)){ + throw new ClassCastException("Cannot create spell book GUI for item that does not extend ItemSpellBook!"); + } + this.book = (ItemSpellBook)stack.getItem(); - this.spell = Spell.byMetadata(stack.getItemDamage()); + this.spell = (Spell.byMetadata(stack.getItemDamage())); } @Override - 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(book.getGuiTexture(spell)); - 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); - } - - // Novice is usually white but this doesn't show up - String tier = I18n.format("gui.ebwizardry:spell_book.tier", spell.getTier() == Tier.NOVICE ? - "\u00A77" + spell.getTier().getDisplayName() : spell.getTier().getDisplayNameWithFormatting()); - this.fontRenderer.drawString(tier, xPos + 17, yPos + 45, 0); - - String element = I18n.format("gui.ebwizardry:spell_book.element", spell.getElement().getFormattingCode() + spell.getElement().getDisplayName()); - if(!discovered) element = I18n.format("gui.ebwizardry:spell_book.element_undiscovered"); - this.fontRenderer.drawString(element, xPos + 17, yPos + 57, 0); - - String manaCost = I18n.format("gui.ebwizardry:spell_book.mana_cost", spell.getCost()); - if(spell.isContinuous) manaCost = I18n.format("gui.ebwizardry:spell_book.mana_cost_continuous", spell.getCost()); - if(!discovered) manaCost = I18n.format("gui.ebwizardry:spell_book.mana_cost_undiscovered"); - 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 Spell getSpell(){ + return spell; } @Override - public void initGui(){ - super.initGui(); - Keyboard.enableRepeatEvents(true); - this.buttonList.clear(); - - this.mc.getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.MISC_BOOK_OPEN, 1)); - } - - @Override - public void onGuiClosed(){ - super.onGuiClosed(); - Keyboard.enableRepeatEvents(false); - } - - @Override - public boolean doesGuiPauseGame(){ - return Wizardry.settings.booksPauseGame; + public ResourceLocation getTexture(){ + return book.getGuiTexture(spell); } } diff --git a/src/main/java/electroblob/wizardry/client/gui/GuiSpellDisplay.java b/src/main/java/electroblob/wizardry/client/gui/GuiSpellDisplay.java index b370f0dc..52200a90 100644 --- a/src/main/java/electroblob/wizardry/client/gui/GuiSpellDisplay.java +++ b/src/main/java/electroblob/wizardry/client/gui/GuiSpellDisplay.java @@ -14,7 +14,7 @@ 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 electroblob.wizardry.util.SpellModifiers; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.renderer.GlStateManager; @@ -45,7 +45,8 @@ import java.nio.charset.StandardCharsets; public class GuiSpellDisplay { private static final ResourceLocation INDEX = new ResourceLocation(Wizardry.MODID, "textures/gui/spell_hud/_index.json"); - + private static final ResourceLocation CHARGE_METER = new ResourceLocation(Wizardry.MODID, "textures/gui/spell_charge_meter.png"); + /** 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 @@ -54,6 +55,12 @@ public class GuiSpellDisplay { 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(); + private static final Random random = new Random(); + + /** Width of the charge meter. */ + private static final int CHARGE_METER_WIDTH = 25; + /** Height of the charge meter. */ + private static final int CHARGE_METER_HEIGHT = 9; /** Width and height of the spell icon (very unlikely to change!) */ private static final int SPELL_ICON_SIZE = 32; @@ -99,12 +106,9 @@ public class GuiSpellDisplay { @SubscribeEvent public static void draw(RenderGameOverlayEvent.Post event){ - if(event.getType() != RenderGameOverlayEvent.ElementType.TEXT - && event.getType() != RenderGameOverlayEvent.ElementType.HOTBAR) return; - - Minecraft mc = Minecraft.getMinecraft(); + if(!Wizardry.settings.showSpellHUD && !Wizardry.settings.showChargeMeter) return; // Optimisation - EntityPlayer player = mc.player; + EntityPlayer player = Minecraft.getMinecraft().player; if(player.isSpectator()) return; // Spectators shouldn't have the spell HUD! @@ -117,12 +121,89 @@ public class GuiSpellDisplay { 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; + if(!(wand.getItem() instanceof ISpellCastingItem && ((ISpellCastingItem)wand.getItem()).showSpellHUD(player, wand))) + return; } int width = event.getResolution().getScaledWidth(); int height = event.getResolution().getScaledHeight(); + switch(event.getType()){ + case CROSSHAIRS: + renderChargeMeter(player, wand, width, height, event.getPartialTicks()); + break; + case HOTBAR: + renderSpellHUD(player, wand, mainHand, width, height, event.getPartialTicks(), false); + break; + case TEXT: + renderSpellHUD(player, wand, mainHand, width, height, event.getPartialTicks(), true); + break; + } + + } + + /** + * Renders the spell charge meter around the crosshairs. + * @param player A reference to the client player + * @param wand The wand the HUD is for + * @param width The width of the screen + * @param height The height of the screen + * @param partialTicks The current partial tick time + */ + private static void renderChargeMeter(EntityPlayer player, ItemStack wand, int width, int height, float partialTicks){ + + if(!Wizardry.settings.showChargeMeter) return; + if(Minecraft.getMinecraft().gameSettings.showDebugInfo) return; // Don't show charge meter in the debug screen + if(Minecraft.getMinecraft().gameSettings.thirdPersonView != 0) return; // Don't show in third person + if(wand != player.getActiveItemStack()) return; // Don't show when using the other held item + + if(!(wand.getItem() instanceof ISpellCastingItem)) throw new IllegalArgumentException("The given stack must contain an ISpellCastingItem!"); + + Spell spell = ((ISpellCastingItem)wand.getItem()).getCurrentSpell(wand); + + int chargeup = spell.getChargeup(); + + if(WizardData.get(player) != null){ + // Pretty sure this is accessible client-side since it's only assigned from common code + chargeup = (int)(chargeup * WizardData.get(player).itemCastingModifiers.get(SpellModifiers.CHARGEUP)); + } + + if(chargeup <= 0) return; + + // WHY WHY WHY are these methods named so misleadingly?! Sort yourselves out MCP! + // (getItemInUseCount returns the max count MINUS the use count, and getItemInUseMaxCount returns the use count) + if(player.getItemInUseMaxCount() == 0) return; // Not charging + float charge = (player.getItemInUseMaxCount() + partialTicks) / chargeup; + if(charge > 1) return; // Done charging + + Minecraft.getMinecraft().renderEngine.bindTexture(CHARGE_METER); + + int x1 = width/2 - CHARGE_METER_WIDTH/2; + int y = height/2 - CHARGE_METER_HEIGHT/2; + int w = (int)(CHARGE_METER_WIDTH/2 * charge); + int u = CHARGE_METER_WIDTH - w; + + DrawingUtils.drawTexturedRect(x1, y, 0, 0, w, CHARGE_METER_HEIGHT, 32, 32); + DrawingUtils.drawTexturedRect(x1 + u, y, u, 0, w, CHARGE_METER_HEIGHT, 32, 32); + + } + + /** + * Renders the main spell HUD in the corner of the screen. + * @param player A reference to the client player + * @param wand The wand the HUD is for + * @param mainHand True if the wand is in the player's main hand, false if it is in their offhand + * @param width The width of the screen + * @param height The height of the screen + * @param partialTicks The current partial tick time + * @param textLayer True to render the text layer, false to render the background (hotbar layer) + */ + private static void renderSpellHUD(EntityPlayer player, ItemStack wand, boolean mainHand, int width, int height, float partialTicks, boolean textLayer){ + + if(!Wizardry.settings.showSpellHUD) return; + + if(!(wand.getItem() instanceof ISpellCastingItem)) throw new IllegalArgumentException("The given stack must contain an ISpellCastingItem!"); + boolean flipX = Wizardry.settings.spellHUDPosition.flipX; boolean flipY = Wizardry.settings.spellHUDPosition.flipY; @@ -130,16 +211,16 @@ public class GuiSpellDisplay { // ............. | 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."); @@ -148,7 +229,7 @@ public class GuiSpellDisplay { } 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; @@ -169,45 +250,45 @@ public class GuiSpellDisplay { y = MathHelper.ceil(y/scale); } - Spell spell = WandHelper.getCurrentSpell(wand); - int cooldown = WandHelper.getCurrentCooldown(wand); - int maxCooldown = WandHelper.getCurrentMaxCooldown(wand); + Spell spell = ((ISpellCastingItem)wand.getItem()).getCurrentSpell(wand); + int cooldown = ((ISpellCastingItem)wand.getItem()).getCurrentCooldown(wand); + int maxCooldown = ((ISpellCastingItem)wand.getItem()).getCurrentMaxCooldown(wand); + + if(textLayer){ - 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)); + partialTicks) / SPELL_SWITCH_TIME); + + String prevSpellName = getFormattedSpellName(((ISpellCastingItem)wand.getItem()).getPreviousSpell(wand), player, 0); String spellName = getFormattedSpellName(spell, player, cooldown); - String nextSpellName = getFormattedSpellName(WandHelper.getNextSpell(wand), player, WandHelper.getNextCooldown(wand)); + String nextSpellName = getFormattedSpellName(((ISpellCastingItem)wand.getItem()).getNextSpell(wand), player, 0); skin.drawText(x, y, flipX, flipY, prevSpellName, spellName, nextSpellName, animationProgress); - }else if(event.getType() == RenderGameOverlayEvent.ElementType.HOTBAR){ + }else{ 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){ + if(!player.isCreative()){ // Subtracted partial tick time to make it smoother - progress = maxCooldown == 0 ? 1 : (maxCooldown - (float)cooldown + event.getPartialTicks()) / maxCooldown; + progress = maxCooldown == 0 ? 1 : (maxCooldown - (float)cooldown + partialTicks) / maxCooldown; } - - skin.drawBackground(x, y, flipX, flipY, icon, progress, player.isCreative()); - + + skin.drawBackground(x, y, flipX, flipY, icon, progress, player.isCreative(), player.isPotionActive(WizardryPotions.arcane_jammer)); + } GlStateManager.popMatrix(); } - + /** * Gets the name of the given spell, with formatting added according to its cooldown and whether the given player * has discovered it. @@ -224,9 +305,11 @@ public class GuiSpellDisplay { 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(); + // Makes spells greyed out if they are in cooldown + String format = cooldown > 0 ? "\u00A78" : spell.getElement().getFormattingCode(); if(!discovered) format = "\u00A79"; + // Obfuscates the spell name if the player has the arcane jammer effect + if(player.isPotionActive(WizardryPotions.arcane_jammer)) format = format + "\u00A7k"; String name = discovered ? spell.getDisplayName() : SpellGlyphData.getGlyphName(spell, player.world); name = format + name; @@ -458,8 +541,9 @@ public class GuiSpellDisplay { * @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. + * @param jammed True to show the 'glitch' effect (user for arcane jammer), false to draw normally. */ - public void drawBackground(int x, int y, boolean flipX, boolean flipY, ResourceLocation icon, float cooldownBarProgress, boolean creativeMode){ + public void drawBackground(int x, int y, boolean flipX, boolean flipY, ResourceLocation icon, float cooldownBarProgress, boolean creativeMode, boolean jammed){ // Moves the origin if the HUD does not mirror; neatens the rest of the code. if(flipX && !mirrorX) x -= width; @@ -476,8 +560,13 @@ public class GuiSpellDisplay { 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); + + if(jammed){ + random.setSeed(Minecraft.getMinecraft().world.getTotalWorldTime() / 2); + DrawingUtils.drawGlitchRect(random, x1, y1, 0, 0, SPELL_ICON_SIZE, SPELL_ICON_SIZE, SPELL_ICON_SIZE, SPELL_ICON_SIZE, false, false); + }else{ + 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); @@ -486,7 +575,11 @@ public class GuiSpellDisplay { 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); + if(jammed){ + DrawingUtils.drawGlitchRect(random, x1, y1, creativeMode ? 128 : 0, 0, width, height, 256, 256, flipX && mirrorX, flipY && mirrorY); + }else{ + 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)){ @@ -498,8 +591,12 @@ public class GuiSpellDisplay { 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); + + if(jammed){ + DrawingUtils.drawGlitchRect(random, x1, y1, u, v, l, cooldownBarHeight, 256, 256, flipX && cooldownBarMirrorX, flipY && cooldownBarMirrorY); + }else{ + DrawingUtils.drawTexturedFlippedRect(x1, y1, u, v, l, cooldownBarHeight, 256, 256, flipX && cooldownBarMirrorX, flipY && cooldownBarMirrorY); + } } GlStateManager.popMatrix(); diff --git a/src/main/java/electroblob/wizardry/client/gui/GuiSpellInfo.java b/src/main/java/electroblob/wizardry/client/gui/GuiSpellInfo.java new file mode 100644 index 00000000..4e49ed1e --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/GuiSpellInfo.java @@ -0,0 +1,138 @@ +package electroblob.wizardry.client.gui; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.DrawingUtils; +import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.data.SpellGlyphData; +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.client.resources.I18n; +import net.minecraft.util.ResourceLocation; +import org.lwjgl.input.Keyboard; + +/** Abstract base class for both {@link GuiSpellBook} and {@link GuiLectern}. Centralises code common to both + * those classes. */ +public abstract class GuiSpellInfo extends GuiScreen { + + protected static final String TRANSLATION_KEY_PREFIX = "gui." + Wizardry.MODID + ":spell_book"; + + protected final int xSize, ySize; + protected int textureWidth = 512; + protected int textureHeight = 256; + + public GuiSpellInfo(int xSize, int ySize){ + super(); + this.xSize = xSize; + this.ySize = ySize; + } + + protected void setTextureSize(int width, int height){ + this.textureWidth = width; + this.textureHeight = height; + } + + /** Returns the spell to be displayed by this GUI. */ + public abstract Spell getSpell(); + + /** Returns the main texture for the background of this GUI. */ + public abstract ResourceLocation getTexture(); + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks){ + int left = this.width/2 - xSize/2; + int top = this.height/2 - this.ySize/2; + this.drawDefaultBackground(); + this.drawBackgroundLayer(left, top, mouseX, mouseY); + super.drawScreen(mouseX, mouseY, partialTicks); // Just draws the buttons + this.drawForegroundLayer(left, top, mouseX, mouseY); + } + + /** + * Draws the background of the spell info GUI. This is called before buttons are drawn. + * @param left The x-coordinate of the left-hand edge of the GUI + * @param top The y-coordinate of the top edge of the GUI + * @param mouseX The current x position of the mouse pointer + * @param mouseY The current y position of the mouse pointer + */ + protected void drawBackgroundLayer(int left, int top, int mouseX, int mouseY){ + + boolean discovered = Wizardry.proxy.shouldDisplayDiscovered(getSpell(), null); + + 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 ? getSpell().getIcon() : Spells.none.getIcon()); + DrawingUtils.drawTexturedRect(left + 146, top + 20, 0, 0, 128, 128, 128, 128); + + Minecraft.getMinecraft().renderEngine.bindTexture(getTexture()); + DrawingUtils.drawTexturedRect(left, top, 0, 0, xSize, ySize, textureWidth, textureHeight); + } + + /** + * Draws the foreground of the spell info GUI. This is called after buttons are drawn. + * @param left The x-coordinate of the left-hand edge of the GUI + * @param top The y-coordinate of the top edge of the GUI + * @param mouseX The current x position of the mouse pointer + * @param mouseY The current y position of the mouse pointer + */ + protected void drawForegroundLayer(int left, int top, int mouseX, int mouseY){ + + boolean discovered = Wizardry.proxy.shouldDisplayDiscovered(getSpell(), null); + + if(discovered){ + this.fontRenderer.drawString(getSpell().getDisplayName(), left + 17, top + 15, 0); + this.fontRenderer.drawString(getSpell().getType().getDisplayName(), left + 17, top + 26, 0x777777); + }else{ + this.mc.standardGalacticFontRenderer.drawString(SpellGlyphData.getGlyphName(getSpell(), mc.world), left + 17, + top + 15, 0); + this.mc.standardGalacticFontRenderer.drawString(getSpell().getType().getDisplayName(), left + 17, top + 26, + 0x777777); + } + + // Novice is usually white but this doesn't show up + String tier = I18n.format(TRANSLATION_KEY_PREFIX + ".tier", getSpell().getTier() == Tier.NOVICE ? + "\u00A77" + getSpell().getTier().getDisplayName() : getSpell().getTier().getDisplayNameWithFormatting()); + this.fontRenderer.drawString(tier, left + 17, top + 45, 0); + + String element = I18n.format(TRANSLATION_KEY_PREFIX + ".element", getSpell().getElement().getFormattingCode() + getSpell().getElement().getDisplayName()); + if(!discovered) element = I18n.format(TRANSLATION_KEY_PREFIX + ".element_undiscovered"); + this.fontRenderer.drawString(element, left + 17, top + 57, 0); + + String manaCost = I18n.format(TRANSLATION_KEY_PREFIX + ".mana_cost", getSpell().getCost()); + if(getSpell().isContinuous) manaCost = I18n.format(TRANSLATION_KEY_PREFIX + ".mana_cost_continuous", getSpell().getCost()); + if(!discovered) manaCost = I18n.format(TRANSLATION_KEY_PREFIX + ".mana_cost_undiscovered"); + this.fontRenderer.drawString(manaCost, left + 17, top + 69, 0); + + if(discovered){ + this.fontRenderer.drawSplitString(getSpell().getDescription(), left + 17, top + 83, 118, 0); + }else{ + this.mc.standardGalacticFontRenderer.drawSplitString( + SpellGlyphData.getGlyphDescription(getSpell(), mc.world), left + 17, top + 83, 118, 0); + } + } + + @Override + public void initGui(){ + super.initGui(); + Keyboard.enableRepeatEvents(true); + this.buttonList.clear(); + this.mc.getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.MISC_BOOK_OPEN, 1)); + } + + @Override + 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/WizardTradeTweaksHandler.java b/src/main/java/electroblob/wizardry/client/gui/WizardTradeTweaksHandler.java new file mode 100644 index 00000000..d67bca14 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/gui/WizardTradeTweaksHandler.java @@ -0,0 +1,129 @@ +package electroblob.wizardry.client.gui; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.DrawingUtils; +import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.item.ItemSpellBook; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.spell.Spell; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiMerchant; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.ContainerMerchant; +import net.minecraft.inventory.Slot; +import net.minecraft.util.ResourceLocation; +import net.minecraft.village.MerchantRecipe; +import net.minecraft.village.MerchantRecipeList; +import net.minecraftforge.client.event.GuiContainerEvent; +import net.minecraftforge.client.event.GuiOpenEvent; +import net.minecraftforge.client.event.GuiScreenEvent.ActionPerformedEvent; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber; +import net.minecraftforge.fml.common.eventhandler.EventPriority; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.relauncher.Side; + +@EventBusSubscriber(Side.CLIENT) +public class WizardTradeTweaksHandler { + + private static final ResourceLocation NEW_SPELL_ICON = new ResourceLocation(Wizardry.MODID, "textures/gui/container/new_spell_indicator.png"); + + private static final int ICON_WIDTH = 8; + private static final int ICON_HEIGHT = 8; + + private static final int ANIMATION_FRAMES = 4; + private static final int ANIMATION_FRAME_TIME = 2; // In ticks + private static final int ANIMATION_PERIOD = 40; // In frame durations + + private static int tradeIndex; // Mirrors GuiMerchant#selectedMerchantRecipe (don't want to reflect into it every frame) + + @SubscribeEvent(priority = EventPriority.LOWEST) + public static void onGuiOpenEvent(GuiOpenEvent event){ + if(event.getGui() instanceof GuiMerchant) tradeIndex = 0; + } + + @SubscribeEvent + public static void onActionPerformedPostEvent(ActionPerformedEvent.Post event){ + + if(event.getGui() instanceof GuiMerchant){ + + MerchantRecipeList recipes = ((GuiMerchant)event.getGui()).getMerchant().getRecipes(Minecraft.getMinecraft().player); + + if(recipes == null) return; + + if(event.getButton().id == 1){ // Next + tradeIndex = Math.min(tradeIndex + 1, recipes.size()); + }else if(event.getButton().id == 2){ // Previous + tradeIndex = Math.max(tradeIndex - 1, 0); + } + } + } + + // 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(); + // Note that gui.getMerchant() returns an NpcMerchant, not an EntityWizard. + MerchantRecipeList trades = gui.getMerchant().getRecipes(Minecraft.getMinecraft().player); + + if(trades == null) return; + + // Using == the specific item rather than instanceof because that's how trades do it. + if(gui.inventorySlots.getSlot(0).getStack().getItem() == WizardryItems.spell_book + || gui.inventorySlots.getSlot(1).getStack().getItem() == WizardryItems.spell_book){ + + for(MerchantRecipe trade : trades){ + if(trade.getItemToBuy().getItem() == WizardryItems.spell_book && trade.getSecondItemToBuy().isEmpty()){ + Slot slot = gui.inventorySlots.getSlot(2); + // 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. + DrawingUtils.drawItemAndTooltip(gui, trade.getItemToSell(), slot.xPos, slot.yPos, event.getMouseX(), event.getMouseY(), + gui.getSlotUnderMouse() == slot); + } + } + } + + // New spell indicator + if(gui.inventorySlots instanceof ContainerMerchant){ + + // Can't use getCurrentRecipe because that only gets updated when the correct items are given + MerchantRecipe recipe = trades.get(tradeIndex); + + if(recipe != null && recipe.getItemToSell().getItem() instanceof ItemSpellBook){ + + EntityPlayer player = Minecraft.getMinecraft().player; + Spell spell = Spell.byMetadata(recipe.getItemToSell().getMetadata()); + + if(Wizardry.settings.discoveryMode && !player.isCreative() && Wizardry.proxy.shouldDisplayDiscovered(spell, recipe.getItemToSell()) + && WizardData.get(player) != null && !WizardData.get(player).hasSpellBeenDiscovered(spell)){ + + int x = gui.inventorySlots.getSlot(2).xPos + 14; + int y = gui.inventorySlots.getSlot(2).yPos - 17; + + RenderHelper.enableGUIStandardItemLighting(); + GlStateManager.color(1, 1, 1); + Minecraft.getMinecraft().renderEngine.bindTexture(NEW_SPELL_ICON); + + int frame = Math.max(player.ticksExisted/ANIMATION_FRAME_TIME % ANIMATION_PERIOD - (ANIMATION_PERIOD - ANIMATION_FRAMES), 0); + DrawingUtils.drawTexturedRect(x, y, 0, frame * ICON_HEIGHT, ICON_WIDTH, ICON_HEIGHT, ICON_WIDTH, ICON_HEIGHT * ANIMATION_FRAMES); + + RenderHelper.disableStandardItemLighting(); + +// int mouseX = event.getMouseX() - gui.getGuiLeft(); +// int mouseY = event.getMouseY() - gui.getGuiTop(); +// +// if(mouseX >= x + 1 && mouseX < x + w + 1 && mouseY >= y - 1 && mouseY < y + h + 1){ +// GuiUtils.drawHoveringText(Collections.singletonList("You haven't discovered this spell yet"), mouseX, +// mouseY, gui.width, gui.height, 150, Minecraft.getMinecraft().fontRenderer); +// } + } + } + } + } + } + +} diff --git a/src/main/java/electroblob/wizardry/client/gui/config/GuiConfigWizardry.java b/src/main/java/electroblob/wizardry/client/gui/config/GuiConfigWizardry.java index 7f76ad1d..1597d08d 100644 --- a/src/main/java/electroblob/wizardry/client/gui/config/GuiConfigWizardry.java +++ b/src/main/java/electroblob/wizardry/client/gui/config/GuiConfigWizardry.java @@ -25,15 +25,18 @@ public class GuiConfigWizardry extends GuiConfig { private static List getConfigEntries(){ - List configList = new ArrayList(1); + List configList = new ArrayList<>(1); - configList.add(new DummyCategoryElement("gameplayConfig", "config." + Wizardry.MODID + ".category." + Settings.GAMEPLAY_CATEGORY, GameplayCategory.class)); - configList.add(new DummyCategoryElement("worldgenConfig", "config." + Wizardry.MODID + ".category." + Settings.WORLDGEN_CATEGORY, WorldgenCategory.class)); - configList.add(new DummyCategoryElement("commandsConfig", "config." + Wizardry.MODID + ".category." + Settings.COMMANDS_CATEGORY, CommandsCategory.class)); - 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.add(new DummyCategoryElement("gameplayConfig", "config." + Wizardry.MODID + ".category." + Settings.GAMEPLAY_CATEGORY, GameplayCategory.class)); + configList.add(new DummyCategoryElement("difficultyConfig", "config." + Wizardry.MODID + ".category." + Settings.DIFFICULTY_CATEGORY, DifficultyCategory.class)); + configList.add(new DummyCategoryElement("worldgenConfig", "config." + Wizardry.MODID + ".category." + Settings.WORLDGEN_CATEGORY, WorldgenCategory.class)); + configList.add(new DummyCategoryElement("tweaksConfig", "config." + Wizardry.MODID + ".category." + Settings.TWEAKS_CATEGORY, TweaksCategory.class)); + configList.add(new DummyCategoryElement("commandsConfig", "config." + Wizardry.MODID + ".category." + Settings.COMMANDS_CATEGORY, CommandsCategory.class)); + 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("artefactsConfig", "config." + Wizardry.MODID + ".category." + Settings.ARTEFACTS_CATEGORY, ArtefactsCategory.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()); @@ -79,6 +82,16 @@ public class GuiConfigWizardry extends GuiConfig { @Override protected String getCategory() { return Settings.GAMEPLAY_CATEGORY; } } + /** Difficulty category of the config gui. */ + public static class DifficultyCategory extends CategoryBase { + + public DifficultyCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){ + super(owningScreen, owningEntryList, prop); + } + + @Override protected String getCategory() { return Settings.DIFFICULTY_CATEGORY; } + } + /** Worldgen category of the config gui. */ public static class WorldgenCategory extends CategoryBase { @@ -89,6 +102,16 @@ public class GuiConfigWizardry extends GuiConfig { @Override protected String getCategory() { return Settings.WORLDGEN_CATEGORY; } } + /** Tweaks category of the config gui. */ + public static class TweaksCategory extends CategoryBase { + + public TweaksCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){ + super(owningScreen, owningEntryList, prop); + } + + @Override protected String getCategory() { return Settings.TWEAKS_CATEGORY; } + } + /** Commands category of the config gui. */ public static class CommandsCategory extends CategoryBase { @@ -119,6 +142,16 @@ public class GuiConfigWizardry extends GuiConfig { @Override protected String getCategory() { return Settings.SPELLS_CATEGORY; } } + /** Artefacts category of the config gui. */ + public static class ArtefactsCategory extends CategoryBase { + + public ArtefactsCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){ + super(owningScreen, owningEntryList, prop); + } + + @Override protected String getCategory() { return Settings.ARTEFACTS_CATEGORY; } + } + /** Resistances category of the config gui. */ public static class ResistancesCategory extends CategoryBase { diff --git a/src/main/java/electroblob/wizardry/client/gui/config/GuiSelectHUDSkin.java b/src/main/java/electroblob/wizardry/client/gui/config/GuiSelectHUDSkin.java index 985d1594..f992f85e 100644 --- a/src/main/java/electroblob/wizardry/client/gui/config/GuiSelectHUDSkin.java +++ b/src/main/java/electroblob/wizardry/client/gui/config/GuiSelectHUDSkin.java @@ -65,15 +65,15 @@ public class GuiSelectHUDSkin extends GuiSelectString { 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; + float x = (previewLeft + previewRight)/2f - (skin.getWidth()*scale)/2; + float y = (previewBottom + previewTop)/2f + (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); + Spells.magic_missile.getIcon(), 0.6f, false, false); skin.drawText((int)(x/scale), (int)(y/scale), false, false, Spells.none.getDisplayNameWithFormatting(), diff --git a/src/main/java/electroblob/wizardry/client/gui/handbook/Contents.java b/src/main/java/electroblob/wizardry/client/gui/handbook/Contents.java index 1853e5a1..a6a762a5 100644 --- a/src/main/java/electroblob/wizardry/client/gui/handbook/Contents.java +++ b/src/main/java/electroblob/wizardry/client/gui/handbook/Contents.java @@ -3,7 +3,7 @@ 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 electroblob.wizardry.util.JavaUtils; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.GuiButton; import net.minecraft.util.JsonUtils; @@ -50,7 +50,7 @@ class Contents { /** Returns an unmodifiable, flattened collection of all the buttons in this contents. */ Collection getButtons(){ - return WizardryUtilities.flatten(buttons); + return JavaUtils.flatten(buttons); } void addEntry(Section section){ diff --git a/src/main/java/electroblob/wizardry/client/gui/handbook/GuiWizardHandbook.java b/src/main/java/electroblob/wizardry/client/gui/handbook/GuiWizardHandbook.java index 14b9e7bf..b68c4756 100644 --- a/src/main/java/electroblob/wizardry/client/gui/handbook/GuiWizardHandbook.java +++ b/src/main/java/electroblob/wizardry/client/gui/handbook/GuiWizardHandbook.java @@ -8,7 +8,8 @@ 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.client.gui.GuiButtonTurnPage; +import electroblob.wizardry.client.gui.GuiButtonTurnPage.Type; import electroblob.wizardry.constants.Constants; import electroblob.wizardry.constants.Element; import electroblob.wizardry.constants.Tier; @@ -54,7 +55,7 @@ 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"); + static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID, "textures/gui/handbook/handbook.png"); /** Global Gson instance for the handbook. */ private static final Gson gson = new Gson(); @@ -244,6 +245,8 @@ public class GuiWizardHandbook extends GuiScreen { @Override public void drawScreen(int mouseX, int mouseY, float partialTicks){ + this.drawDefaultBackground(); + int left = this.width / 2 - GUI_WIDTH / 2; int top = this.height / 2 - GUI_HEIGHT / 2; @@ -349,19 +352,19 @@ public class GuiWizardHandbook extends GuiScreen { 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)); + top + GUI_HEIGHT - BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.NEXT_PAGE, texture, TEXTURE_WIDTH, TEXTURE_HEIGHT)); this.buttonList.add(previous = new GuiButtonTurnPage(nextButtonId++, left + BUTTON_INSET_X, - top + GUI_HEIGHT - BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.PREVIOUS_PAGE)); + top + GUI_HEIGHT - BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.PREVIOUS_PAGE, texture, TEXTURE_WIDTH, TEXTURE_HEIGHT)); 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)); + top + GUI_HEIGHT - BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.NEXT_SECTION, texture, TEXTURE_WIDTH, TEXTURE_HEIGHT)); this.buttonList.add(previousSection = new GuiButtonTurnPage(nextButtonId++, left + BUTTON_INSET_X + BUTTON_SPACING, - top + GUI_HEIGHT - BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.PREVIOUS_SECTION)); + top + GUI_HEIGHT - BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.PREVIOUS_SECTION, texture, TEXTURE_WIDTH, TEXTURE_HEIGHT)); this.buttonList.add(menu = new GuiButtonTurnPage(nextButtonId++, left + GUI_WIDTH/2 - 28, - top + GUI_HEIGHT - BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.CONTENTS)); + top + GUI_HEIGHT - BUTTON_INSET_Y - GuiButtonTurnPage.HEIGHT, Type.CONTENTS, texture, TEXTURE_WIDTH, TEXTURE_HEIGHT)); this.buttonList.add(bookmark = new GuiButtonInvisible(nextButtonId++, left + 130, top + 172, 11, 19) { @Override @@ -398,6 +401,11 @@ public class GuiWizardHandbook extends GuiScreen { */ public static void loadHandbookFile(IResourceManager manager){ + if(manager == null){ + Wizardry.logger.error("Tried to reload the handbook file, but received a null resource manager. Aborting!"); + return; + } + IResource handbookFile = getHandbookResource(manager); if(handbookFile != null){ diff --git a/src/main/java/electroblob/wizardry/client/gui/handbook/Section.java b/src/main/java/electroblob/wizardry/client/gui/handbook/Section.java index c057d5e5..5a1a8bd6 100644 --- a/src/main/java/electroblob/wizardry/client/gui/handbook/Section.java +++ b/src/main/java/electroblob/wizardry/client/gui/handbook/Section.java @@ -6,7 +6,7 @@ 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 electroblob.wizardry.util.JavaUtils; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.GuiButton; @@ -63,7 +63,7 @@ class Section { } Collection getButtons(){ - return WizardryUtilities.flatten(buttons); + return JavaUtils.flatten(buttons); } /** diff --git a/src/main/java/electroblob/wizardry/client/model/BakedModelBookshelf.java b/src/main/java/electroblob/wizardry/client/model/BakedModelBookshelf.java new file mode 100644 index 00000000..ca8d2476 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/model/BakedModelBookshelf.java @@ -0,0 +1,74 @@ +package electroblob.wizardry.client.model; + +import electroblob.wizardry.block.BlockBookshelf; +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.ItemOverrideList; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.util.EnumFacing; +import net.minecraftforge.common.property.IExtendedBlockState; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + +public class BakedModelBookshelf implements IBakedModel { + + private final IBakedModel bookshelf; + private final IBakedModel[][] books; + + public BakedModelBookshelf(IBakedModel bookshelf, IBakedModel[][] books){ + this.bookshelf = bookshelf; + this.books = books; + } + + @Override + public List getQuads(@Nullable IBlockState state, @Nullable EnumFacing side, long rand){ + + IBakedModel fallback = Minecraft.getMinecraft().getBlockRendererDispatcher().getBlockModelShapes().getModelManager().getMissingModel(); + if(state == null) return fallback.getQuads(null, side, rand); + + // It took me waaay too long to realise I needed a new list here + List quads = new ArrayList<>(bookshelf.getQuads(state, side, rand)); // Lists ain't immutable, chief + + IExtendedBlockState extendedState = (IExtendedBlockState)state; + + for(int i = 0; i

+ * To use this system in a model file:

+ * 1. Make a base model file and define the item overrides in it. This model must be placed in the folder + * {@code models/item/override_generators} (unfortunately the folder has to be predefined, see below).
+ * 2. Make the model files your overrides point to. These should define everything about the model except textures.
+ * 3. In each model that should generate overrides dynamically, define a single override that points to the location of + * the model from step 1 (the predicate doesn't matter).
+ *

+ * It is advisable to make the parent of the override model (from step 2) the same as the parent of the model to + * be overridden, so that they share any necessary properties and texture variables. For example, both + * {@code ebwizardry:item/magic_wand.json} and {@code ebwizardry:item/wand_point.json} have {@code item/handheld} as + * their parent. The override model will reference the textures in the original model, so if the override defines a + * face with the texture {@code "#face"}, the texture used will be the file associated with {@code "face"} in the + * original model. For simple models, this will just be the standard {@code "layer0"}. + * @since Wizardry 4.3 + * @author Electroblob + */ +// Okay, it's not *that* bad if you only have one override, but imagine having to duplicate every single wand model 5 +// times if we wanted 5 animations... +// The nice thing is resource packs don't lose any flexibility this way, they can still redefine the models themselves +// and specify custom models for the overrides if they want, or they can keep using this system - in fact, they GAIN +// flexibility because they can also overwrite the base wand.json model and specify global overrides too +@Mod.EventBusSubscriber(Side.CLIENT) +public class BakedModelGenerateOverrides implements IBakedModel { + + // I tried having this as a prefix that gets removed at runtime, which worked fine but it spammed the log with + // errors because the prefixed file doesn't actually exist, so even though it's never used the game still complains + private static final String OVERRIDE_GENERATORS = "override_generators"; + + private final IBakedModel delegate; + private final ItemOverrideList overrides; + + public BakedModelGenerateOverrides(IBakedModel delegate, ItemOverrideList overrides){ + this.delegate = delegate; + this.overrides = overrides; + } + + @Override + public ItemOverrideList getOverrides(){ + return overrides; // The only thing that's not delegated + } + + // Delegate everything else + + @Override + public List getQuads(@Nullable IBlockState state, @Nullable EnumFacing side, long rand){ + return delegate.getQuads(state, side, rand); + } + + @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 boolean isAmbientOcclusion(IBlockState state){ + return delegate.isAmbientOcclusion(state); + } + + @Override + public Pair handlePerspective(ItemCameraTransforms.TransformType cameraTransformType){ + return delegate.handlePerspective(cameraTransformType); + } + + // Baking + + @SubscribeEvent + public static void bake(ModelBakeEvent event){ + + for(ModelResourceLocation location : event.getModelRegistry().getKeys()){ + + IBakedModel original = event.getModelRegistry().getObject(location); + + if(original != null){ + original.getOverrides().getOverrides().stream().map(ItemOverride::getLocation) + .filter(l -> l.getPath().contains(OVERRIDE_GENERATORS)).findFirst() + .ifPresent(l -> event.getModelRegistry().putObject(location, + substituteWandModel(event.getModelManager(), location, original, l))); + } + } + } + + // We have to use ModelBakeEvent for this because we need the original model to be baked with the vanilla system + private static IBakedModel substituteWandModel(ModelManager modelManager, ModelResourceLocation location, IBakedModel original, ResourceLocation overrideLocation){ + + try { + + IModel unbakedOriginal = ModelLoaderRegistry.getModel(location); + IModel referenceModel = ModelLoaderRegistry.getModel(overrideLocation); + ModelBlock vanillaOriginal = unbakedOriginal.asVanillaModel().orElse(null); + ModelBlock vanillaRefModel = referenceModel.asVanillaModel().orElse(null); + + if(vanillaOriginal != null && vanillaRefModel != null){ + + // So... many... data structures... + List overrides = vanillaRefModel.getOverrides(); + Map replacementMap = new HashMap<>(); + + for(ItemOverride override : overrides){ + IModel replacement = ModelLoaderRegistry.getModel(override.getLocation()).retexture(ImmutableMap.copyOf(vanillaOriginal.textures)); + replacementMap.put(override.getLocation(), replacement.bake(unbakedOriginal.getDefaultState(), DefaultVertexFormats.ITEM, ModelLoader.defaultTextureGetter())); + } + + ItemOverrideListMapped overrideList = new ItemOverrideListMapped(overrides, replacementMap); + return new BakedModelGenerateOverrides(original, overrideList); + } + + }catch(Exception exception){ + Wizardry.logger.error("Error baking item display override models: ", exception); + } + + return modelManager.getMissingModel(); + } + +} diff --git a/src/main/java/electroblob/wizardry/client/model/BakedModelGlowingOverlay.java b/src/main/java/electroblob/wizardry/client/model/BakedModelGlowingOverlay.java index c3324830..960e95a5 100644 --- a/src/main/java/electroblob/wizardry/client/model/BakedModelGlowingOverlay.java +++ b/src/main/java/electroblob/wizardry/client/model/BakedModelGlowingOverlay.java @@ -28,7 +28,7 @@ import java.util.List; *
* 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?) + * models into the item models folder and add {@code "shade":false} where appropriate. (If it works, it works, right?) * * @author Electroblob * @author raoulvdberge diff --git a/src/main/java/electroblob/wizardry/client/model/ItemOverrideListMapped.java b/src/main/java/electroblob/wizardry/client/model/ItemOverrideListMapped.java new file mode 100644 index 00000000..2e8837bc --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/model/ItemOverrideListMapped.java @@ -0,0 +1,42 @@ +package electroblob.wizardry.client.model; + +import com.google.common.collect.ImmutableMap; +import net.minecraft.client.renderer.block.model.IBakedModel; +import net.minecraft.client.renderer.block.model.ItemOverride; +import net.minecraft.client.renderer.block.model.ItemOverrideList; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.World; + +import javax.annotation.Nullable; +import java.util.List; +import java.util.Map; + +/** A version of {@link ItemOverrideList} that uses a predefined map to substitute a specific baked model depending on + * the location the currently-applicable override points to. */ +public class ItemOverrideListMapped extends ItemOverrideList { + + private final Map replacementMap; + + public ItemOverrideListMapped(List overrides, Map replacementMap){ + super(overrides); + this.replacementMap = ImmutableMap.copyOf(replacementMap); + } + + @Override + public IBakedModel handleItemState(IBakedModel originalModel, ItemStack stack, @Nullable World world, @Nullable EntityLivingBase entity){ + + if(!stack.isEmpty() && stack.getItem().hasCustomProperties()){ + + // Get the location the original override points to... + ResourceLocation location = applyOverride(stack, world, entity); // I wonder why this is deprecated? + + if(location != null){ + return replacementMap.get(location); // ... then substitute in the corresponding generated model + } + } + + return originalModel; + } +} diff --git a/src/main/java/electroblob/wizardry/client/model/ModelBookshelf.java b/src/main/java/electroblob/wizardry/client/model/ModelBookshelf.java new file mode 100644 index 00000000..8a7d7e1c --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/model/ModelBookshelf.java @@ -0,0 +1,99 @@ +package electroblob.wizardry.client.model; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.block.BlockBookshelf; +import net.minecraft.client.renderer.block.model.IBakedModel; +import net.minecraft.client.renderer.block.model.ModelResourceLocation; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.vertex.VertexFormat; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.model.IModel; +import net.minecraftforge.client.model.ModelLoaderRegistry; +import net.minecraftforge.common.model.IModelState; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Function; + +public class ModelBookshelf implements IModel { + + private static final ResourceLocation DEFAULT_BOOK_TEXTURE = new ResourceLocation(Wizardry.MODID, "blocks/books"); + + private static final List bookModelLocations = new ArrayList<>(); + + private final ModelResourceLocation bookshelfModelLocation; + private final String variant; + + static { + for(int i = 0; i < BlockBookshelf.SLOT_COUNT; i++){ + bookModelLocations.add(new ResourceLocation(Wizardry.MODID, "bookshelf_parts/books" + i)); + } + } + + public ModelBookshelf(String bookshelfModelName){ + String[] args = bookshelfModelName.split("#", 2); + String model = args[0]; + variant = args.length == 2 ? args[1] : null; + bookshelfModelLocation = new ModelResourceLocation(new ResourceLocation(Wizardry.MODID, "bookshelf_parts/" + model), variant); + } + + @Override + public IBakedModel bake(IModelState state, VertexFormat format, Function bakedTextureGetter){ + + try { + + IModel bookshelfModel = ModelLoaderRegistry.getModel(bookshelfModelLocation); + // I don't know why default state works here (surely it ought to be the state param?), but it works so who cares + IBakedModel bookshelf = bookshelfModel.bake(bookshelfModel.getDefaultState(), format, bakedTextureGetter); + + ImmutableList textures = BlockBookshelf.getBookTextures(); + + IBakedModel[][] books = new IBakedModel[textures.size()][BlockBookshelf.SLOT_COUNT]; + + for(int i = 0; i < textures.size(); i++){ + + ImmutableMap retexturer = ImmutableMap.of("books", textures.get(i).toString()); + + for(int j = 0; j < BlockBookshelf.SLOT_COUNT; j++){ + IModel bookModel = ModelLoaderRegistry.getModel(new ModelResourceLocation(bookModelLocations.get(j), variant)).retexture(retexturer); + books[i][j] = bookModel.bake(bookModel.getDefaultState(), format, bakedTextureGetter); // Same here! + } + } + + // To clarify: the whole point of doing this was to avoid having to bake 4 * 2^12 models per bookshelf type + // Therefore we have to have a baked bookshelf model that's kind of dynamic + return new BakedModelBookshelf(bookshelf, books); + + }catch(Exception exception){ + Wizardry.logger.error("Error baking bookshelf models: ", exception); + return ModelLoaderRegistry.getMissingModel().bake(state, format, bakedTextureGetter); + } + + } + + @Override + public Collection getDependencies(){ + return bookModelLocations; + } + + @Override + public Collection getTextures(){ + + // I simply have no idea how I'm *supposed* to do it, so I'm using the best solution I can come up with + // This is at least better than manually registering it with TextureStitchEvent + // However I'm also pretty sure that if a texture pack decides to change the texture that the book models point + // to, it won't work... but since nobody ever will, I don't really care! + + // Why am I confused? + // 1. How come it magically finds the bookshelf texture, but not the book texture? + // 2. Why don't the textures get retrieved automatically based on getDependencies? Surely that's the point? + // 3. Is it correct to use getModel() to get the dependency models upon construction of this class so I can + // access their textures properly? Or is that too early? + // 4. If so, what's the point of getDependencies, and why should I override it? + return BlockBookshelf.getBookTextures();// ImmutableList.of(DEFAULT_BOOK_TEXTURE); + } + +} diff --git a/src/main/java/electroblob/wizardry/client/model/ModelBoulder.java b/src/main/java/electroblob/wizardry/client/model/ModelBoulder.java new file mode 100644 index 00000000..7ee3fc64 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/model/ModelBoulder.java @@ -0,0 +1,74 @@ +package electroblob.wizardry.client.model; + +import net.minecraft.client.model.ModelBase; +import net.minecraft.client.model.ModelBox; +import net.minecraft.client.model.ModelRenderer; +import net.minecraft.entity.Entity; + +// Made with Blockbench 3.5.2 +public class ModelBoulder extends ModelBase { + + private final ModelRenderer mainGroup; + private final ModelRenderer box1; + private final ModelRenderer box2; + private final ModelRenderer box3; + private final ModelRenderer box4; + private final ModelRenderer box5; + private final ModelRenderer box6; + + public ModelBoulder(){ + + textureWidth = 64; + textureHeight = 32; + + mainGroup = new ModelRenderer(this); + mainGroup.setRotationPoint(0.0F, 24.0F, 0.0F); + + box1 = new ModelRenderer(this); + box1.setRotationPoint(0.0F, -19.0F, 0.0F); + mainGroup.addChild(box1); + box1.cubeList.add(new ModelBox(box1, 0, 0, -11.0F, -11.0F, 11.0F, 22, 22, 8, 0.0F, false)); + + box2 = new ModelRenderer(this); + box2.setRotationPoint(0.0F, -19.0F, 0.0F); + mainGroup.addChild(box2); + setRotationAngle(box2, -1.5708F, 0.0F, 0.0F); + box2.cubeList.add(new ModelBox(box2, 0, 0, -11.0F, -11.0F, 11.0F, 22, 22, 8, 0.0F, false)); + + box3 = new ModelRenderer(this); + box3.setRotationPoint(0.0F, -19.0F, 0.0F); + mainGroup.addChild(box3); + setRotationAngle(box3, 0.0F, -1.5708F, 0.0F); + box3.cubeList.add(new ModelBox(box3, 0, 0, -11.0F, -11.0F, 11.0F, 22, 22, 8, 0.0F, false)); + + box4 = new ModelRenderer(this); + box4.setRotationPoint(0.0F, -19.0F, 0.0F); + mainGroup.addChild(box4); + setRotationAngle(box4, 0.0F, 1.5708F, 0.0F); + box4.cubeList.add(new ModelBox(box4, 0, 0, -11.0F, -11.0F, 11.0F, 22, 22, 8, 0.0F, false)); + + box5 = new ModelRenderer(this); + box5.setRotationPoint(0.0F, -19.0F, 0.0F); + mainGroup.addChild(box5); + setRotationAngle(box5, 1.5708F, 0.0F, 0.0F); + box5.cubeList.add(new ModelBox(box5, 0, 0, -11.0F, -11.0F, 11.0F, 22, 22, 8, 0.0F, false)); + + box6 = new ModelRenderer(this); + box6.setRotationPoint(0.0F, -19.0F, 0.0F); + mainGroup.addChild(box6); + setRotationAngle(box6, 3.1416F, 0.0F, 0.0F); + box6.cubeList.add(new ModelBox(box6, 0, 0, -11.0F, -11.0F, 11.0F, 22, 22, 8, 0.0F, false)); + } + + @Override + public void render(Entity entity, float f, float f1, float f2, float f3, float f4, float f5){ + mainGroup.render(f5); + } + + public void setRotationAngle(ModelRenderer modelRenderer, float x, float y, float z){ + modelRenderer.rotateAngleX = x; + modelRenderer.rotateAngleY = y; + modelRenderer.rotateAngleZ = z; + } + +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/client/model/ModelIceBarrier.java b/src/main/java/electroblob/wizardry/client/model/ModelIceBarrier.java new file mode 100644 index 00000000..0a2137e5 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/model/ModelIceBarrier.java @@ -0,0 +1,35 @@ +package electroblob.wizardry.client.model; + +import net.minecraft.client.model.ModelBase; +import net.minecraft.client.model.ModelBox; +import net.minecraft.client.model.ModelRenderer; +import net.minecraft.entity.Entity; + +// Made with Blockbench 3.5.4 +public class ModelIceBarrier extends ModelBase { + + private final ModelRenderer mainGroup; + + public ModelIceBarrier(){ + + textureWidth = 64; + textureHeight = 64; + + mainGroup = new ModelRenderer(this); + mainGroup.setRotationPoint(0.0F, 24.0F, 0.0F); + setRotationAngle(mainGroup, 0.0F, 0.0F, 0.7854F); + mainGroup.cubeList.add(new ModelBox(mainGroup, 0, 0, -14.5F, -14.5F, -1.5F, 29, 29, 3, 0.0F, false)); + mainGroup.cubeList.add(new ModelBox(mainGroup, 0, 32, -9.0F, -9.0F, -4.0F, 18, 18, 8, 0.0F, false)); + } + + @Override + public void render(Entity entity, float f, float f1, float f2, float f3, float f4, float f5) { + mainGroup.render(f5); + } + + public void setRotationAngle(ModelRenderer modelRenderer, float x, float y, float z) { + modelRenderer.rotateAngleX = x; + modelRenderer.rotateAngleY = y; + modelRenderer.rotateAngleZ = z; + } +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/client/model/ModelLoaderBookshelf.java b/src/main/java/electroblob/wizardry/client/model/ModelLoaderBookshelf.java new file mode 100644 index 00000000..0c7dcb79 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/model/ModelLoaderBookshelf.java @@ -0,0 +1,28 @@ +package electroblob.wizardry.client.model; + +import electroblob.wizardry.Wizardry; +import net.minecraft.client.resources.IResourceManager; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.model.ICustomModelLoader; +import net.minecraftforge.client.model.IModel; + +public class ModelLoaderBookshelf implements ICustomModelLoader { + + private static final String SMART_MODEL_PREFIX = "models/block/smart_model/"; + + @Override + public void onResourceManagerReload(IResourceManager resourceManager){ + // Do nothing + } + + @Override + public boolean accepts(ResourceLocation modelLocation){ + return modelLocation.getNamespace().equals(Wizardry.MODID) && modelLocation.getPath().startsWith(SMART_MODEL_PREFIX); + } + + @Override + public IModel loadModel(ResourceLocation modelLocation){ + return new ModelBookshelf(modelLocation.getPath().substring(SMART_MODEL_PREFIX.length())); + } + +} diff --git a/src/main/java/electroblob/wizardry/client/model/ModelRemnant.java b/src/main/java/electroblob/wizardry/client/model/ModelRemnant.java new file mode 100644 index 00000000..ec9d74d2 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/model/ModelRemnant.java @@ -0,0 +1,48 @@ +package electroblob.wizardry.client.model; + +import net.minecraft.client.model.ModelBase; +import net.minecraft.client.model.ModelRenderer; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.entity.Entity; +import net.minecraft.util.math.MathHelper; + +/** Based on ModelEnderCrystal */ +public class ModelRemnant extends ModelBase { + + private final ModelRenderer cube = new ModelRenderer(this, "cube"); + + public ModelRemnant(){ + this.cube.setTextureOffset(0, 0).addBox(-4.0F, -4.0F, -4.0F, 8, 8, 8); + } + + @Override + public void render(Entity entity, float rotationSpeed, float expandFraction, float age, float netHeadYaw, float headPitch, float scale){ + + GlStateManager.pushMatrix(); + +// GlStateManager.scale(2.0F, 2.0F, 2.0F); +// GlStateManager.translate(0.0F, -0.5F, 0.0F); + + GlStateManager.disableCull(); + + GlStateManager.translate(0.0F, entity.height/2, 0.0F); + + float s = expandFraction + MathHelper.sin(age * 0.1f) * 0.06f; + + GlStateManager.scale(s, s, s); + + GlStateManager.rotate(age * rotationSpeed/2, 0.0F, 1.0F, 0.0F); + GlStateManager.rotate(60.0F, 0.7071F, 0.0F, 0.7071F); + GlStateManager.rotate(age * rotationSpeed, 0.0F, 1.0F, 0.0F); + this.cube.render(scale); + + GlStateManager.scale(0.875F, 0.875F, 0.875F); + GlStateManager.rotate(60.0F, 0.7071F, 0.0F, 0.7071F); + GlStateManager.rotate(age * rotationSpeed, 0.0F, 1.0F, 0.0F); + this.cube.render(scale); + + GlStateManager.enableCull(); + + GlStateManager.popMatrix(); + } +} diff --git a/src/main/java/electroblob/wizardry/client/model/WizardryModels.java b/src/main/java/electroblob/wizardry/client/model/WizardryModels.java index 05a6ccc0..49c47de5 100644 --- a/src/main/java/electroblob/wizardry/client/model/WizardryModels.java +++ b/src/main/java/electroblob/wizardry/client/model/WizardryModels.java @@ -5,10 +5,12 @@ 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.ItemBlockMultiTextured; import electroblob.wizardry.item.ItemCrystal; +import electroblob.wizardry.item.ItemSpectralDust; import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.registry.WizardryItems; +import net.minecraft.block.BlockPlanks; import net.minecraft.client.renderer.block.model.IBakedModel; import net.minecraft.client.renderer.block.model.ModelBakery; import net.minecraft.client.renderer.block.model.ModelResourceLocation; @@ -20,11 +22,15 @@ 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.client.model.ModelLoaderRegistry; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.oredict.OreDictionary; +import java.util.ArrayList; +import java.util.List; + /** * Class responsible for registering all of wizardry's item and block models. * @@ -37,32 +43,39 @@ public final class WizardryModels { private WizardryModels(){} // No instances! + /** Keeps track of all items whose models have been registered manually to exclude them from automatic registry of + * standard item models. Internal only, this gets cleared once model registry is complete. */ + private static final List registeredItems = new ArrayList<>(); + @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); + ItemBlockMultiTextured crystalBlockItem = (ItemBlockMultiTextured)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); + ItemBlockMultiTextured runestoneItem = (ItemBlockMultiTextured)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); + ItemBlockMultiTextured pedestalItem = (ItemBlockMultiTextured)Item.getItemFromBlock(WizardryBlocks.runestone_pedestal); registerMultiTexturedModel(pedestalItem); + ModelLoader.setCustomStateMapper(WizardryBlocks.gilded_wood, new StateMap.Builder() + .withName(BlockPlanks.VARIANT).withSuffix("_gilded_wood").build()); + ItemBlockMultiTextured gildedWoodItem = (ItemBlockMultiTextured)Item.getItemFromBlock(WizardryBlocks.gilded_wood); + registerMultiTexturedModel(gildedWoodItem); + + // Explanation for all this here -> https://github.com/TheGreyGhost/MinecraftByExample/tree/master/src/main/java/minecraftbyexample/mbe05_block_dynamic_block_model2 + ModelLoaderRegistry.registerLoader(new ModelLoaderBookshelf()); + // Items registerMultiTexturedModel((ItemCrystal)WizardryItems.magic_crystal); @@ -72,11 +85,9 @@ public final class WizardryModels { registerWandModel(WizardryItems.advanced_wand); registerWandModel(WizardryItems.master_wand); - registerItemModel(WizardryItems.spell_book); + registerItemModel(WizardryItems.spell_book); // Also need this or auto-registry will ignore it // Wildcard registered for wizard trades. registerItemModel(WizardryItems.spell_book, OreDictionary.WILDCARD_VALUE, "normal"); - registerItemModel(WizardryItems.arcane_tome); - registerItemModel(WizardryItems.wizard_handbook); registerWandModel(WizardryItems.novice_fire_wand); registerWandModel(WizardryItems.novice_ice_wand); @@ -110,160 +121,16 @@ public final class WizardryModels { registerWandModel(WizardryItems.master_sorcery_wand); registerWandModel(WizardryItems.master_healing_wand); - registerItemModel(WizardryItems.spectral_sword); - registerItemModel(WizardryItems.spectral_pickaxe); - registerItemModel(WizardryItems.spectral_bow); + registerMultiTexturedModel((ItemSpectralDust)WizardryItems.spectral_dust); - 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); + // Automatic item model registry + for(Item item : Item.REGISTRY){ + if(!registeredItems.contains(item) && item.getRegistryName().getNamespace().equals(Wizardry.MODID)){ + registerItemModel(item); // Standard item model + } + } + registeredItems.clear(); // Might as well clean this up } @SubscribeEvent @@ -280,11 +147,12 @@ public final class WizardryModels { if(location.getNamespace().equals(Wizardry.MODID)){ - if(location.getPath().contains("runestone") || location.getPath().contains("runestone_pedestal")){ - IBakedModel original = event.getModelRegistry().getObject(location); + IBakedModel original = event.getModelRegistry().getObject(location); + + if(location.getPath().contains("runestone") || location.getPath().contains("runestone_pedestal") + || location.getPath().endsWith("imbuement_altar")){ // Ends with to exclude inactive version event.getModelRegistry().putObject(location, new BakedModelGlowingOverlay(original, "overlay")); }else if(location.getPath().contains("spectral_block")){ - IBakedModel original = event.getModelRegistry().getObject(location); event.getModelRegistry().putObject(location, new BakedModelGlowingOverlay(original, "spectral_block")); } } @@ -302,6 +170,7 @@ public final class WizardryModels { ModelBakery.registerItemVariants(item, new ModelResourceLocation(item.getRegistryName(), "inventory")); // Assigns the model for all metadata values ModelLoader.setCustomMeshDefinition(item, s -> new ModelResourceLocation(item.getRegistryName(), "inventory")); + registeredItems.add(item); } /** @@ -315,6 +184,7 @@ public final class WizardryModels { ModelBakery.registerItemVariants(item, new ModelResourceLocation("ebwizardry:festive_wand", "inventory")); // Assigns the model for all metadata values ModelLoader.setCustomMeshDefinition(item, s -> new ModelResourceLocation("ebwizardry:festive_wand", "inventory")); + registeredItems.add(item); }else{ registerItemModel(item); } @@ -338,6 +208,8 @@ public final class WizardryModels { new ModelResourceLocation(item.getModelName(stack), "inventory")); } } + + registeredItems.add(item); } /** @@ -348,6 +220,7 @@ public final class WizardryModels { private static void registerItemModel(Item item, int metadata, String variant){ ModelLoader.setCustomModelResourceLocation(item, metadata, new ModelResourceLocation(item.getRegistryName(), variant)); + registeredItems.add(item); // Still ought to do this in case I ever use this method alone for an item } } diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleBlockHighlight.java b/src/main/java/electroblob/wizardry/client/particle/ParticleBlockHighlight.java new file mode 100644 index 00000000..c083590b --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleBlockHighlight.java @@ -0,0 +1,60 @@ +package electroblob.wizardry.client.particle; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.util.GeometryUtils; +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 ParticleBlockHighlight extends ParticleWizardry { + + private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "particle/block_highlight"); + + public ParticleBlockHighlight(World world, double x, double y, double z){ + + super(world, x, y, z, TEXTURE); + + this.particleGravity = 0; + this.setMaxAge(160); + // 5 makes it the exact size of a block face, plus a little bit to account for the anti-z-fighting offset + this.particleScale = 5 * (1 + 2 * (float)GeometryUtils.ANTI_Z_FIGHTING_OFFSET); + this.shaded = false; + } + + @Override + public boolean shouldDisableDepth(){ + return true; + } + + @Override + public void onUpdate(){ + + super.onUpdate(); + + // 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){ + event.getMap().registerSprite(TEXTURE); + } +} diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleBuff.java b/src/main/java/electroblob/wizardry/client/particle/ParticleBuff.java index a7a8a752..e62e0ed4 100644 --- a/src/main/java/electroblob/wizardry/client/particle/ParticleBuff.java +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleBuff.java @@ -59,7 +59,7 @@ public class ParticleBuff extends ParticleWizardry { float rotationYZ, float rotationXY, float rotationXZ){ // Copied from ParticleWizardry, needs to be here since we're not calling super - updateEntityLinking(partialTicks); + updateEntityLinking(viewer, partialTicks); GlStateManager.pushMatrix(); GlStateManager.pushAttrib(); diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleCloud.java b/src/main/java/electroblob/wizardry/client/particle/ParticleCloud.java new file mode 100644 index 00000000..f59ebbf7 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleCloud.java @@ -0,0 +1,52 @@ +package electroblob.wizardry.client.particle; + +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; + +@Mod.EventBusSubscriber(Side.CLIENT) +public class ParticleCloud extends ParticleWizardry { + + private static final ResourceLocation[] TEXTURES = generateTextures("cloud", 4); + + public ParticleCloud(World world, double x, double y, double z){ + + super(world, x, y, z, TEXTURES[world.rand.nextInt(TEXTURES.length)]); + + this.setRBGColorF(1, 1, 1); + this.particleMaxAge = 48 + this.rand.nextInt(12); + this.particleScale *= 6; + this.setGravity(false); + this.setAlphaF(0); + this.canCollide = false; + this.shaded = true; + } + + @Override + public boolean shouldDisableDepth(){ + return true; + } + + @Override + public void onUpdate(){ + + super.onUpdate(); + + // Fading + float fadeTime = this.particleMaxAge * 0.3f; + this.setAlphaF(MathHelper.clamp(Math.min(this.particleAge / fadeTime, (this.particleMaxAge - this.particleAge) / fadeTime), 0, 1)); + + } + + @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/ParticleDust.java b/src/main/java/electroblob/wizardry/client/particle/ParticleDust.java index cd925b81..069f6a40 100644 --- a/src/main/java/electroblob/wizardry/client/particle/ParticleDust.java +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleDust.java @@ -17,15 +17,4 @@ public class ParticleDust extends ParticleWizardry { this.setRBGColorF(1, 1, 1); } - @Override - public void onUpdate(){ - this.prevPosX = this.posX; - this.prevPosY = this.posY; - this.prevPosZ = this.posZ; - this.move(this.motionX, this.motionY, this.motionZ); - - if(this.particleMaxAge-- <= 0){ - this.setExpired(); - } - } } diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleFlash.java b/src/main/java/electroblob/wizardry/client/particle/ParticleFlash.java index f85bc90c..57943960 100644 --- a/src/main/java/electroblob/wizardry/client/particle/ParticleFlash.java +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleFlash.java @@ -35,15 +35,10 @@ public class ParticleFlash extends ParticleWizardry { 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; + buffer.pos(f5 - rotationX * f4 - rotationXY * f4, f6 - rotationZ * f4, 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(f5 - rotationX * f4 + rotationXY * f4, f6 + rotationZ * f4, 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(f5 + rotationX * f4 + rotationXY * f4, f6 + rotationZ * f4, 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(f5 + rotationX * f4 - rotationXY * f4, f6 - rotationZ * f4, f7 + rotationYZ * f4 - rotationXZ * f4).tex(0.25D, 0.375D).color(this.particleRed, this.particleGreen, this.particleBlue, this.particleAlpha).lightmap(j, k).endVertex(); } } diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleGuardianBeam.java b/src/main/java/electroblob/wizardry/client/particle/ParticleGuardianBeam.java new file mode 100644 index 00000000..ee92031b --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleGuardianBeam.java @@ -0,0 +1,78 @@ +package electroblob.wizardry.client.particle; + +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.util.ResourceLocation; +import net.minecraft.world.World; +import org.lwjgl.opengl.GL11; + +public class ParticleGuardianBeam extends ParticleTargeted { + + /** Half the width of the beam. */ + private static final float THICKNESS = 0.15f; + + private static final ResourceLocation TEXTURE = new ResourceLocation("minecraft:textures/entity/guardian_beam.png"); + + public ParticleGuardianBeam(World world, double x, double y, double z){ + super(world, x, y, z); // Does't use TextureAtlasSprite + this.setRBGColorF(1, 1, 1); + this.setMaxAge(3); + this.particleScale = 1; + } + + @Override + public int getFXLayer(){ + return 3; + } + + @Override + protected void draw(Tessellator tessellator, double length, float partialTicks){ + + float scale = this.particleScale; + + BufferBuilder buffer = tessellator.getBuffer(); + + GlStateManager.disableLighting(); + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); + + GlStateManager.pushMatrix(); + // Enables texture tiling (Also used for guardian beam, beacon beam and ender crystal beam, amongst others) + 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.rotate(Minecraft.getMinecraft().player.ticksExisted + partialTicks, 0, 0, 1); + + Minecraft.getMinecraft().renderEngine.bindTexture(TEXTURE); + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR); + + float t = THICKNESS * scale; + float v1 = 2 * (particleAge + partialTicks) / particleMaxAge; + float v2 = v1 + (float)length * 2; // Multiply by 2 to make it squished like it is in vanilla + + buffer.pos(-t, 0, 0) .tex(0, v1) .color(particleRed, particleGreen, particleBlue, 1).endVertex(); + buffer.pos( t, 0, 0) .tex(0.5, v1) .color(particleRed, particleGreen, particleBlue, 1).endVertex(); + buffer.pos( t, 0, length).tex(0.5, v2).color(particleRed, particleGreen, particleBlue, 1).endVertex(); + buffer.pos(-t, 0, length).tex(0, v2).color(particleRed, particleGreen, particleBlue, 1).endVertex(); + + buffer.pos(0, -t, 0) .tex(0, v1) .color(particleRed, particleGreen, particleBlue, 1).endVertex(); + buffer.pos(0, t, 0) .tex(0.5, v1) .color(particleRed, particleGreen, particleBlue, 1).endVertex(); + buffer.pos(0, t, length).tex(0.5, v2).color(particleRed, particleGreen, particleBlue, 1).endVertex(); + buffer.pos(0, -t, length).tex(0, v2).color(particleRed, particleGreen, particleBlue, 1).endVertex(); + + tessellator.draw(); + +// 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.popMatrix(); + + // Makes the rain go weird + //GlStateManager.enableLighting(); + } + +} diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleSphere.java b/src/main/java/electroblob/wizardry/client/particle/ParticleSphere.java index 89eaf5a7..a863911d 100644 --- a/src/main/java/electroblob/wizardry/client/particle/ParticleSphere.java +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleSphere.java @@ -41,7 +41,7 @@ public class ParticleSphere extends ParticleWizardry { float rotationXY, float rotationXZ){ // Copied from ParticleWizardry, needs to be here since we're not calling super - updateEntityLinking(partialTicks); + updateEntityLinking(viewer, partialTicks); float x = (float)(this.prevPosX + (this.posX - this.prevPosX) * (double)partialTicks); float y = (float)(this.prevPosY + (this.posY - this.prevPosY) * (double)partialTicks); diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleSummon.java b/src/main/java/electroblob/wizardry/client/particle/ParticleSummon.java deleted file mode 100644 index 55daba3e..00000000 --- a/src/main/java/electroblob/wizardry/client/particle/ParticleSummon.java +++ /dev/null @@ -1,132 +0,0 @@ -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 index cd89d0af..100016ef 100644 --- a/src/main/java/electroblob/wizardry/client/particle/ParticleTargeted.java +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleTargeted.java @@ -1,6 +1,7 @@ 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.Tessellator; @@ -15,6 +16,8 @@ import javax.annotation.Nullable; /** Superclass for particles with a second target entity or target position. */ public abstract class ParticleTargeted extends ParticleWizardry { + private static final double THIRD_PERSON_AXIAL_OFFSET = 1.2; + protected double targetX; protected double targetY; protected double targetZ; @@ -73,16 +76,26 @@ public abstract class ParticleTargeted extends ParticleWizardry { float rotationXY, float rotationXZ){ // Copied from ParticleWizardry, needs to be here since we're not calling super - updateEntityLinking(partialTicks); + updateEntityLinking(viewer, 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); + // Translates the particle a short distance in front of the entity + if(this.entity != null && this.shouldApplyOriginOffset()){ + if(this.entity != viewer || Minecraft.getMinecraft().gameSettings.thirdPersonView != 0){ + Vec3d look = entity.getLook(partialTicks).scale(THIRD_PERSON_AXIAL_OFFSET); + x += look.x; + y += look.y; + z += look.z; + } + } + 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; + double correction = this.target.posY - 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; @@ -133,6 +146,12 @@ public abstract class ParticleTargeted extends ParticleWizardry { GlStateManager.popMatrix(); } + /** Returns whether the origin of this particle should be moved a short distance in front of the entity it is + * linked to, if any. */ + protected boolean shouldApplyOriginOffset(){ + return true; + } + /** 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 diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleVine.java b/src/main/java/electroblob/wizardry/client/particle/ParticleVine.java index 79c9d872..21f336f2 100644 --- a/src/main/java/electroblob/wizardry/client/particle/ParticleVine.java +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleVine.java @@ -46,6 +46,11 @@ public class ParticleVine extends ParticleTargeted { buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.PARTICLE_POSITION_TEX_COLOR_LMAP); } + @Override + protected boolean shouldApplyOriginOffset(){ + return false; + } + @Override protected void draw(Tessellator tessellator, double length, float partialTicks){ diff --git a/src/main/java/electroblob/wizardry/client/particle/ParticleWizardry.java b/src/main/java/electroblob/wizardry/client/particle/ParticleWizardry.java index 1b91c6aa..69f73843 100644 --- a/src/main/java/electroblob/wizardry/client/particle/ParticleWizardry.java +++ b/src/main/java/electroblob/wizardry/client/particle/ParticleWizardry.java @@ -3,7 +3,7 @@ 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 electroblob.wizardry.util.EntityUtils; import net.minecraft.client.Minecraft; import net.minecraft.client.particle.Particle; import net.minecraft.client.renderer.BufferBuilder; @@ -27,7 +27,7 @@ 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 + * {@code ParticleBuilder} by exposing all the necessary variables through setters, allowing them to be set on the fly * rather than needing to be passed into the constructor. *

* The new system is as follows: @@ -206,7 +206,7 @@ public abstract class ParticleWizardry extends Particle { this.entity = entity; // Set these to the correct values if(entity != null){ - this.setPosition(this.entity.posX + relativeX, this.entity.getEntityBoundingBox().minY + this.setPosition(this.entity.posX + relativeX, this.entity.posY + relativeY, this.entity.posZ + relativeZ); this.prevPosX = this.posX; this.prevPosY = this.posY; @@ -336,7 +336,7 @@ public abstract class ParticleWizardry extends Particle { public void renderParticle(BufferBuilder buffer, Entity viewer, float partialTicks, float lookZ, float lookY, float lookX, float lookXY, float lookYZ){ - updateEntityLinking(partialTicks); + updateEntityLinking(viewer, partialTicks); if(Float.isNaN(this.yaw) || Float.isNaN(this.pitch)){ // Normal behaviour (rotates to face the viewer) @@ -369,12 +369,18 @@ public abstract class ParticleWizardry extends Particle { super.renderParticle(buffer, viewer, partialTicks, rotationX, rotationY, rotationZ, rotationYZ, rotationXY); } - protected void updateEntityLinking(float partialTicks){ + protected void updateEntityLinking(Entity viewer, float partialTicks){ + // TODO: Still not working, it seems this bug was a thing back in 4.2.x anyway 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); + }else if(this.getFXLayer() == 3){ + // Not sure why, but when fx layer is 3, the interp pos is wrong when not linked to an entity + interpPosX = viewer.lastTickPosX + (viewer.posX - viewer.lastTickPosX) * (double)partialTicks; + interpPosY = viewer.lastTickPosY + (viewer.posY - viewer.lastTickPosY) * (double)partialTicks; + interpPosZ = viewer.lastTickPosZ + (viewer.posZ - viewer.lastTickPosZ) * (double)partialTicks; } } @@ -467,12 +473,12 @@ public abstract class ParticleWizardry extends Particle { double searchRadius = 20; - List nearbyEntities = WizardryUtilities.getEntitiesWithinRadius(searchRadius, this.posX, + List nearbyEntities = EntityUtils.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(); + if(nearbyEntities.stream().anyMatch(e -> e instanceof ICustomHitbox + && ((ICustomHitbox)e).calculateIntercept(new Vec3d(posX, posY, posZ), + new Vec3d(prevPosX, prevPosY, prevPosZ), 0) != null)) this.setExpired(); } @@ -547,7 +553,7 @@ public abstract class ParticleWizardry extends Particle { /** Internal overload for {@link ParticleWizardry#generateTextures(String, String, int, int)} which uses wizardry's * mod ID automatically. */ - public static ResourceLocation[][] generateTextures(String stem, int m, int n){ + static ResourceLocation[][] generateTextures(String stem, int m, int n){ return generateTextures(Wizardry.MODID, stem, m, n); } diff --git a/src/main/java/electroblob/wizardry/client/renderer/LayerFrost.java b/src/main/java/electroblob/wizardry/client/renderer/LayerFrost.java deleted file mode 100644 index a51606e3..00000000 --- a/src/main/java/electroblob/wizardry/client/renderer/LayerFrost.java +++ /dev/null @@ -1,176 +0,0 @@ -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.size() >= 1 && 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); - - boolean headWearHidden = false; - boolean bodyWearHidden = false; - boolean leftArmWearHidden = false; - boolean rightArmWearHidden = false; - boolean leftLegWearHidden = false; - boolean rightLegWearHidden = false; - - // Hides the hat layer for bipeds - if(this.model instanceof ModelBiped){ - headWearHidden = ((ModelBiped)this.model).bipedHeadwear.isHidden; - ((ModelBiped)this.model).bipedHeadwear.isHidden = true; - } - - if(this.model instanceof ModelPlayer){ - bodyWearHidden = ((ModelPlayer)this.model).bipedBodyWear.isHidden; - leftArmWearHidden = ((ModelPlayer)this.model).bipedLeftArmwear.isHidden; - rightArmWearHidden = ((ModelPlayer)this.model).bipedRightArmwear.isHidden; - leftLegWearHidden = ((ModelPlayer)this.model).bipedLeftLegwear.isHidden; - rightLegWearHidden = ((ModelPlayer)this.model).bipedRightLegwear.isHidden; - ((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 = headWearHidden; - - if(this.model instanceof ModelPlayer){ - ((ModelPlayer)this.model).bipedBodyWear.isHidden = bodyWearHidden; - ((ModelPlayer)this.model).bipedLeftArmwear.isHidden = leftArmWearHidden; - ((ModelPlayer)this.model).bipedRightArmwear.isHidden = rightArmWearHidden; - ((ModelPlayer)this.model).bipedLeftLegwear.isHidden = leftLegWearHidden; - ((ModelPlayer)this.model).bipedRightLegwear.isHidden = rightLegWearHidden; - } - - // 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 deleted file mode 100644 index ed1d5669..00000000 --- a/src/main/java/electroblob/wizardry/client/renderer/LayerStone.java +++ /dev/null @@ -1,151 +0,0 @@ -package electroblob.wizardry.client.renderer; - -import electroblob.wizardry.block.BlockStatue; -import electroblob.wizardry.client.ClientProxy; -import net.minecraft.client.Minecraft; -import net.minecraft.client.model.ModelBase; -import net.minecraft.client.model.ModelBiped; -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.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 stone texture on a petrified creature. Handles dynamic tiling of the stone texture. - * - * @author Electroblob - * @since Wizardry 1.2 - */ -public class LayerStone implements LayerRenderer { - - protected ModelBase model; - private final RenderLivingBase renderer; - - private static final ResourceLocation texture = new ResourceLocation("textures/blocks/stone.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 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)renderer).addLayer(new LayerStone((RenderLivingBase)renderer)); - } - } - } - - public LayerStone(RenderLivingBase renderer){ - 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(BlockStatue.PETRIFIED_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); - - ResourceLocation breakingTexture = ClientProxy.renderStatue.getBlockBreakingTexture(); - - if(breakingTexture != null){ - // Block breaking animation - // TODO: Spider eyes and enderman eyes (any others?) show through the stone when the block is being - // broken... - GlStateManager.enableBlend(); - GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA); - this.renderer.bindTexture(breakingTexture); - this.renderEntityModel(entity, limbSwing, limbSwingAmount, partialTicks, ageInTicks, netHeadYaw, - headPitch, scale); - GlStateManager.disableBlend(); - }else{ - // Stone texture - this.renderer.bindTexture(texture); - this.renderEntityModel(entity, limbSwing, limbSwingAmount, partialTicks, ageInTicks, netHeadYaw, - headPitch, scale); - } - } - } - - 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; - - 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/RayHelper.java b/src/main/java/electroblob/wizardry/client/renderer/RayHelper.java index 1f5a979b..7046bfda 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RayHelper.java +++ b/src/main/java/electroblob/wizardry/client/renderer/RayHelper.java @@ -1,14 +1,14 @@ package electroblob.wizardry.client.renderer; /** Instances of this class can calculate their distance from the camera viewpoint and sort themselves accordingly. */ -class RayHelper implements Comparable { +public class RayHelper implements Comparable { int ordinal; - double x1, y1, z1; - double x2, y2, z2; + public double x1, y1, z1; + public double x2, y2, z2; double offsetX, offsetY, offsetZ; - RayHelper(int ordinal, double x1, double y1, double z1, double x2, double y2, double z2, double offsetX, + public RayHelper(int ordinal, double x1, double y1, double z1, double x2, double y2, double z2, double offsetX, double offsetY, double offsetZ){ this.ordinal = ordinal; this.x1 = x1; @@ -37,13 +37,7 @@ class RayHelper implements Comparable { @Override public int compareTo(RayHelper ray){ - if(this.getDistanceFromViewpoint() > ray.getDistanceFromViewpoint()){ - return -1; - }else if(this.getDistanceFromViewpoint() < ray.getDistanceFromViewpoint()){ - return 1; - }else{ - return 0; - } + return Double.compare(ray.getDistanceFromViewpoint(), this.getDistanceFromViewpoint()); } } diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderFireRing.java b/src/main/java/electroblob/wizardry/client/renderer/RenderFireRing.java deleted file mode 100644 index 9e01b89b..00000000 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderFireRing.java +++ /dev/null @@ -1,186 +0,0 @@ -package electroblob.wizardry.client.renderer; - -import electroblob.wizardry.entity.construct.EntityFireRing; -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.entity.Render; -import net.minecraft.client.renderer.entity.RenderManager; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; -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 { - - private final ResourceLocation texture; - private float scale = 1.0f; - - public RenderFireRing(RenderManager renderManager, ResourceLocation texture, float scale){ - super(renderManager); - this.texture = texture; - this.scale = scale; - } - - @Override - public void doRender(EntityFireRing 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(texture); - 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(); - - // Fire - - GlStateManager.disableLighting(); - TextureAtlasSprite icon = Minecraft.getMinecraft().getBlockRendererDispatcher() - .getModelForState(Blocks.FIRE.getDefaultState()).getParticleTexture(); - int sides = 16; - float height = 1.0f; - - for(int k = 0; k < sides; k++){ - - GlStateManager.pushMatrix(); - GlStateManager.translate((float)par2, (float)par4 + 0.05f, (float)par6); - float f1 = 1.0f; - GlStateManager.scale(f1, f1, f1); - float f2 = 0.5F; - float f3 = 0.0F; - float f4 = 0.2f; - float f5 = (float)(entity.posY - entity.getEntityBoundingBox().minY); - GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); - float f61 = 0.0F; - int i = 0; - - GlStateManager.rotate((360f / (float)sides) * k, 0, 1, 0); - GlStateManager.translate(0, 0, -2.3f); - - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - - while(f4 > 0.0F){ - - this.bindTexture(TextureMap.LOCATION_BLOCKS_TEXTURE); - float f71 = icon.getMinU(); - float f81 = icon.getMinV(); - float f9 = icon.getMaxU(); - float f10 = icon.getMaxV(); - - if(i / 2 % 2 == 0){ - float f11 = f9; - f9 = f71; - f71 = f11; - } - - buffer.pos((double)(f2 - f3), (double)(0.0F - f5), (double)f61).tex((double)f9, (double)f10) - .endVertex(); - buffer.pos((double)(-f2 - f3), (double)(0.0F - f5), (double)f61).tex((double)f71, (double)f10) - .endVertex(); - buffer.pos((double)(-f2 - f3), (double)(height - f5), (double)f61).tex((double)f71, (double)f81) - .endVertex(); - buffer.pos((double)(f2 - f3), (double)(height - f5), (double)f61).tex((double)f9, (double)f81) - .endVertex(); - f4 -= 0.45F; - f5 -= 0.45F; - f2 *= 0.9F; - f61 += 0.03F; - ++i; - } - - tessellator.draw(); - GlStateManager.popMatrix(); - } - - for(int k = 0; k < sides; k++){ - - GlStateManager.pushMatrix(); - GlStateManager.translate((float)par2, (float)par4 + 0.05f, (float)par6); - float f1 = 1.0f; - GlStateManager.scale(f1, f1, f1); - float f2 = 0.5F; - float f3 = 0.0F; - float f4 = 0.2f; - float f5 = (float)(entity.posY - entity.getEntityBoundingBox().minY); - GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); - float f61 = 0.0F; - int i = 0; - - GlStateManager.rotate((360f / (float)sides) * k, 0, 1, 0); - GlStateManager.translate(0, 0, 2.3f); - - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - - while(f4 > 0.0F){ - - this.bindTexture(TextureMap.LOCATION_BLOCKS_TEXTURE); - float f71 = icon.getMinU(); - float f81 = icon.getMinV(); - float f9 = icon.getMaxU(); - float f10 = icon.getMaxV(); - - if(i / 2 % 2 == 0){ - float f11 = f9; - f9 = f71; - f71 = f11; - } - - buffer.pos((double)(f2 - f3), (double)(0.0F - f5), (double)f61).tex((double)f9, (double)f10) - .endVertex(); - buffer.pos((double)(-f2 - f3), (double)(0.0F - f5), (double)f61).tex((double)f71, (double)f10) - .endVertex(); - buffer.pos((double)(-f2 - f3), (double)(height - f5), (double)f61).tex((double)f71, (double)f81) - .endVertex(); - buffer.pos((double)(f2 - f3), (double)(height - f5), (double)f61).tex((double)f9, (double)f81) - .endVertex(); - f4 -= 0.45F; - f5 -= 0.45F; - f2 *= 0.9F; - f61 += 0.03F; - ++i; - } - - tessellator.draw(); - GlStateManager.popMatrix(); - } - - GlStateManager.enableLighting(); - } - - @Override - protected ResourceLocation getEntityTexture(EntityFireRing entity){ - return null; - } - -} diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderContainmentField.java b/src/main/java/electroblob/wizardry/client/renderer/effect/RenderContainmentField.java similarity index 83% rename from src/main/java/electroblob/wizardry/client/renderer/RenderContainmentField.java rename to src/main/java/electroblob/wizardry/client/renderer/effect/RenderContainmentField.java index b52b83b0..d9ebb1bd 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderContainmentField.java +++ b/src/main/java/electroblob/wizardry/client/renderer/effect/RenderContainmentField.java @@ -1,9 +1,9 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.effect; import electroblob.wizardry.Wizardry; import electroblob.wizardry.potion.PotionContainment; import electroblob.wizardry.registry.WizardryPotions; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.GeometryUtils; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.GlStateManager; @@ -24,14 +24,14 @@ import org.lwjgl.opengl.GL11; @Mod.EventBusSubscriber(Side.CLIENT) public class RenderContainmentField { - private static final ResourceLocation[] textures = new ResourceLocation[8]; + 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> 16; + int g1 = (c1 & 0xFF00) >> 8; + int b1 = (c1 & 0xFF); + + int c2 = BlockReceptacle.PARTICLE_COLOURS.get(element)[2]; + int r2 = (c2 & 0xFF0000) >> 16; + int g2 = (c2 & 0xFF00) >> 8; + int b2 = (c2 & 0xFF); + + Tessellator tessellator = Tessellator.getInstance(); + + GlStateManager.pushMatrix(); + + GlStateManager.enableBlend(); + GlStateManager.disableLighting(); + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240, 240); + GlStateManager.depthMask(false); + GlStateManager.disableCull(); + + double x1 = player.chasingPosX - player.posX; + double y1 = player.chasingPosY - player.posY; + double z1 = player.chasingPosZ - player.posZ; + + double x2 = player.prevChasingPosX - player.prevPosX; + double y2 = player.prevChasingPosY - player.prevPosY; + double z2 = player.prevChasingPosZ - player.prevPosZ; + + double dx = x2 + (x1 - x2) * partialTicks; + double dy = y2 + (y1 - y2) * partialTicks; + double dz = z2 + (z1 - z2) * partialTicks; + + float t = player.ticksExisted + partialTicks; + float hMoveFraction = MathHelper.clamp((float)(dx * dx + dz * dz) / 0.3f, 0, 1); + float vMoveFraction = MathHelper.clamp((float)(dy * dy) / 0.3f, 0, 1); + + double x = event.getX() + ROTATION_RADIUS * MathHelper.sin(t / ROTATION_PERIOD) * (1 - hMoveFraction) + FOLLOW_DISTANCE * dx; + double y = event.getY() + HEIGHT_FRACTION * player.height + BOBBING_DISTANCE * MathHelper.sin(t / BOBBING_PERIOD) * (1 - vMoveFraction) + FOLLOW_DISTANCE * dy; + double z = event.getZ() + ROTATION_RADIUS * MathHelper.cos(t / ROTATION_PERIOD) * (1 - hMoveFraction) + FOLLOW_DISTANCE * dz; + GlStateManager.translate(x, y, z); + + GlStateManager.scale(0.4, 0.4, 0.4); + + // Makes the 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); + GlStateManager.color(r1/255f, g1/255f, b1/255f); + drawFlare(tessellator); + + // Reverses the colour addition change + GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE); + GlStateManager.color(r2/255f, g2/255f, b2/255f); + drawCube(player, tessellator, partialTicks); + + GlStateManager.color(1, 1, 1,1); + GlStateManager.disableBlend(); + GlStateManager.enableLighting(); + GlStateManager.depthMask(true); + GlStateManager.enableCull(); + + GlStateManager.popMatrix(); + } + + } + + private static void drawFlare(Tessellator tessellator){ + + BufferBuilder buffer = tessellator.getBuffer(); + + GlStateManager.pushMatrix(); + + GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + + Minecraft.getMinecraft().renderEngine.bindTexture(FLARE_TEXTURE); + + // 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 = Minecraft.getMinecraft().gameSettings.thirdPersonView == 2 + ? Minecraft.getMinecraft().getRenderManager().playerViewX + : -Minecraft.getMinecraft().getRenderManager().playerViewX; + GlStateManager.rotate(180.0F - Minecraft.getMinecraft().getRenderManager().playerViewY, 0.0F, 1.0F, 0.0F); + GlStateManager.rotate(yaw, 1.0F, 0.0F, 0.0F); + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + + float r = 0.5f; + + buffer.pos(-r, r, 0).tex(0, 0).endVertex(); + buffer.pos( r, r, 0).tex(1, 0).endVertex(); + buffer.pos( r, -r, 0).tex(1, 1).endVertex(); + buffer.pos(-r, -r, 0).tex(0, 1).endVertex(); + + tessellator.draw(); + + GlStateManager.popMatrix(); + } + + private static void drawCube(EntityPlayer player, Tessellator tessellator, float partialTicks){ + + BufferBuilder buffer = tessellator.getBuffer(); + + GlStateManager.pushMatrix(); + + float age = player.ticksExisted + partialTicks; + float rotationSpeed = 2; + + GlStateManager.rotate(age * rotationSpeed/2, 0.0F, 1.0F, 0.0F); + GlStateManager.rotate(60.0F, 0.7071F, 0.0F, 0.7071F); + GlStateManager.rotate(age * rotationSpeed, 0.0F, 1.0F, 0.0F); + + GlStateManager.scale(0.5, 0.5, 0.5); + + GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE); + + Minecraft.getMinecraft().renderEngine.bindTexture(CUBE_TEXTURES[player.ticksExisted % CUBE_TEXTURES.length]); + + Vec3d[] vertices = GeometryUtils.getVertices(new AxisAlignedBB(-0.5, -0.5, -0.5, 0.5, 0.5, 0.5)); + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + + // Outside + drawFace(buffer, vertices[0], vertices[1], vertices[3], vertices[2], 0.5f, 0, 0.75f, 0.5f); // Bottom + drawFace(buffer, vertices[6], vertices[7], vertices[2], vertices[3], 0.75f, 0.5f, 1, 1); // South + drawFace(buffer, vertices[5], vertices[6], vertices[1], vertices[2], 0, 0.5f, 0.25f, 1); // East + drawFace(buffer, vertices[4], vertices[5], vertices[0], vertices[1], 0.25f, 0.5f, 0.5f, 1); // North + drawFace(buffer, vertices[7], vertices[4], vertices[3], vertices[0], 0.5f, 0.5f, 0.75f, 1); // West + drawFace(buffer, vertices[5], vertices[4], vertices[6], vertices[7], 0.25f, 0, 0.5f, 0.5f); // Top + + tessellator.draw(); + + GlStateManager.popMatrix(); + } + + private static void drawFace(BufferBuilder buffer, Vec3d topLeft, Vec3d topRight, Vec3d bottomLeft, Vec3d bottomRight, float u1, float v1, float u2, float v2){ + buffer.pos(topLeft.x, topLeft.y, topLeft.z).tex(u1, v1).endVertex(); + buffer.pos(topRight.x, topRight.y, topRight.z).tex(u2, v1).endVertex(); + buffer.pos(bottomRight.x, bottomRight.y, bottomRight.z).tex(u2, v2).endVertex(); + buffer.pos(bottomLeft.x, bottomLeft.y, bottomLeft.z).tex(u1, v2).endVertex(); + } + +} diff --git a/src/main/java/electroblob/wizardry/client/renderer/effect/RenderMirage.java b/src/main/java/electroblob/wizardry/client/renderer/effect/RenderMirage.java new file mode 100644 index 00000000..3e8337d4 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/effect/RenderMirage.java @@ -0,0 +1,45 @@ +package electroblob.wizardry.client.renderer.effect; + +import electroblob.wizardry.registry.WizardryPotions; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.GlStateManager.DestFactor; +import net.minecraft.client.renderer.GlStateManager.SourceFactor; +import net.minecraftforge.client.event.RenderLivingEvent; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.relauncher.Side; + +import java.util.Random; + +@EventBusSubscriber(Side.CLIENT) +public class RenderMirage { + + private static final int BLINK_PERIOD_1 = 47; // Use two prime numbers to make it look as chaotic as possible + private static final int BLINK_PERIOD_2 = 71; + + private static final double MAX_OFFSET = 3; + + private static final Random random = new Random(); // Seed will be set later + + @SubscribeEvent + public static void onRenderLivingEvent(RenderLivingEvent.Pre event){ + if(event.getEntity().isPotionActive(WizardryPotions.mirage)){ + random.setSeed(event.getEntity().ticksExisted / BLINK_PERIOD_1 + + event.getEntity().ticksExisted / BLINK_PERIOD_2); + GlStateManager.pushMatrix(); + GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE); // GhoOOoOostly OooOOOo00oOOo + GlStateManager.color(1, 1, 1, 0.5f); + GlStateManager.translate((random.nextDouble() * 2 - 1) * MAX_OFFSET, + (random.nextDouble() * 2 - 1) * MAX_OFFSET, (random.nextDouble() * 2 - 1) * MAX_OFFSET); + } + } + + @SubscribeEvent + public static void onRenderLivingEvent(RenderLivingEvent.Post event){ + if(event.getEntity().isPotionActive(WizardryPotions.mirage)){ + GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA); + GlStateManager.popMatrix(); + } + } + +} diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderShadowWard.java b/src/main/java/electroblob/wizardry/client/renderer/effect/RenderShadowWard.java similarity index 87% rename from src/main/java/electroblob/wizardry/client/renderer/RenderShadowWard.java rename to src/main/java/electroblob/wizardry/client/renderer/effect/RenderShadowWard.java index a0cf181f..6c42d726 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderShadowWard.java +++ b/src/main/java/electroblob/wizardry/client/renderer/effect/RenderShadowWard.java @@ -1,8 +1,8 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.effect; import electroblob.wizardry.Wizardry; import electroblob.wizardry.registry.Spells; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.EntityUtils; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.GlStateManager; @@ -11,7 +11,6 @@ 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; @@ -32,7 +31,7 @@ public class RenderShadowWard { EntityPlayer player = Minecraft.getMinecraft().player; - if(WizardryUtilities.isCasting(player, Spells.shadow_ward)){ + if(EntityUtils.isCasting(player, Spells.shadow_ward)){ GlStateManager.pushMatrix(); @@ -52,7 +51,7 @@ public class RenderShadowWard { GlStateManager.pushMatrix(); GlStateManager.translate(0, 0, 1.2); - GlStateManager.rotate(player.world.getTotalWorldTime() * -2, 0, 0, 1); + GlStateManager.rotate(player.ticksExisted * -2, 0, 0, 1); GlStateManager.scale(1.1, 1.1, 1.1); Tessellator tessellator = Tessellator.getInstance(); @@ -94,7 +93,7 @@ public class RenderShadowWard { EntityPlayer player = event.getEntityPlayer(); - if(WizardryUtilities.isCasting(player, Spells.shadow_ward)){ + if(EntityUtils.isCasting(player, Spells.shadow_ward)){ GlStateManager.pushMatrix(); @@ -103,9 +102,7 @@ public class RenderShadowWard { 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(event.getX(), event.getY(), event.getZ()); GlStateManager.rotate(180, 0, 1, 0); GlStateManager.rotate(-player.renderYawOffset, 0, 1, 0); @@ -116,7 +113,7 @@ public class RenderShadowWard { BufferBuilder buffer = tessellator.getBuffer(); GlStateManager.translate(0, 1.2, 0); - GlStateManager.rotate(player.world.getTotalWorldTime() * -2, 0, 0, 1); + GlStateManager.rotate(player.ticksExisted * -2, 0, 0, 1); GlStateManager.scale(1.1, 1.1, 1.1); buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderShield.java b/src/main/java/electroblob/wizardry/client/renderer/effect/RenderShield.java similarity index 73% rename from src/main/java/electroblob/wizardry/client/renderer/RenderShield.java rename to src/main/java/electroblob/wizardry/client/renderer/effect/RenderShield.java index 6211d4f4..c0feb28c 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderShield.java +++ b/src/main/java/electroblob/wizardry/client/renderer/effect/RenderShield.java @@ -1,10 +1,10 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.effect; 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 electroblob.wizardry.util.EntityUtils; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.GlStateManager; @@ -13,7 +13,6 @@ 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; @@ -34,7 +33,7 @@ public class RenderShield { EntityPlayer player = Minecraft.getMinecraft().player; - if(WizardData.get(player).getVariable(Shield.SHIELD_KEY) != null && WizardryUtilities.isCasting(player, Spells.shield)){ + if(WizardData.get(player).getVariable(Shield.SHIELD_KEY) != null && EntityUtils.isCasting(player, Spells.shield)){ GlStateManager.pushMatrix(); @@ -50,7 +49,25 @@ public class RenderShield { GlStateManager.rotate(-player.rotationYaw, 0, 1, 0); GlStateManager.rotate(player.rotationPitch, 1, 0, 0); - GlStateManager.translate(0, 0, 0.8); + GlStateManager.translate(0, 0, 1); + + GlStateManager.pushMatrix(); + + // Changes the scale at which the texture is applied to the model. See LayerCreeper for a similar example, + // but with translation instead of scaling. + // You can do all sorts of fun stuff with this, just by applying transformations in the 2D texture space. + GlStateManager.matrixMode(GL11.GL_TEXTURE); // Switch to the 2D texture space + GlStateManager.loadIdentity(); + + GlStateManager.translate(0.5, 0.5, 0); + + float s = 1 - ((player.ticksExisted + event.getPartialTicks()) % 5) / 5 * 0.52f; + s = s*s; + GlStateManager.scale(s, s, 1); + + GlStateManager.translate(-0.5, -0.5, 0); + + GlStateManager.matrixMode(GL11.GL_MODELVIEW); // Switch back to the 3D model space Tessellator tessellator = Tessellator.getInstance(); @@ -58,6 +75,13 @@ public class RenderShield { render(tessellator); + // Undo the texture scaling + GlStateManager.matrixMode(GL11.GL_TEXTURE); + GlStateManager.loadIdentity(); + GlStateManager.matrixMode(GL11.GL_MODELVIEW); + + GlStateManager.popMatrix(); + GlStateManager.enableLighting(); GlStateManager.shadeModel(GL11.GL_FLAT); @@ -76,7 +100,7 @@ public class RenderShield { EntityPlayer player = event.getEntityPlayer(); - if(WizardData.get(player).getVariable(Shield.SHIELD_KEY) != null && WizardryUtilities.isCasting(player, Spells.shield)){ + if(WizardData.get(player).getVariable(Shield.SHIELD_KEY) != null && EntityUtils.isCasting(player, Spells.shield)){ GlStateManager.pushMatrix(); @@ -89,9 +113,7 @@ public class RenderShield { 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(event.getX(), event.getY(), event.getZ()); GlStateManager.translate(0, 1.3, 0); @@ -99,7 +121,25 @@ public class RenderShield { GlStateManager.rotate(-player.renderYawOffset, 0, 1, 0); // GlStateManager.rotate(-player.rotationPitch, 1, 0, 0); - GlStateManager.translate(0, 0, 0.8); + GlStateManager.translate(0, 0, 1); + + GlStateManager.pushMatrix(); + + // Changes the scale at which the texture is applied to the model. See LayerCreeper for a similar example, + // but with translation instead of scaling. + // You can do all sorts of fun stuff with this, just by applying transformations in the 2D texture space. + GlStateManager.matrixMode(GL11.GL_TEXTURE); // Switch to the 2D texture space + GlStateManager.loadIdentity(); + + GlStateManager.translate(0.5, 0.5, 0); + + float s = 1 - ((player.ticksExisted + event.getPartialRenderTick()) % 5) / 5 * 0.52f; + s = s*s; + GlStateManager.scale(s, s, 1); + + GlStateManager.translate(-0.5, -0.5, 0); + + GlStateManager.matrixMode(GL11.GL_MODELVIEW); // Switch back to the 3D model space Tessellator tessellator = Tessellator.getInstance(); @@ -107,6 +147,13 @@ public class RenderShield { render(tessellator); + // Undo the texture scaling + GlStateManager.matrixMode(GL11.GL_TEXTURE); + GlStateManager.loadIdentity(); + GlStateManager.matrixMode(GL11.GL_MODELVIEW); + + GlStateManager.popMatrix(); + GlStateManager.enableLighting(); GlStateManager.shadeModel(GL11.GL_FLAT); diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderWings.java b/src/main/java/electroblob/wizardry/client/renderer/effect/RenderWings.java similarity index 89% rename from src/main/java/electroblob/wizardry/client/renderer/RenderWings.java rename to src/main/java/electroblob/wizardry/client/renderer/effect/RenderWings.java index 5701d736..735d6e43 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderWings.java +++ b/src/main/java/electroblob/wizardry/client/renderer/effect/RenderWings.java @@ -1,8 +1,8 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.effect; import electroblob.wizardry.Wizardry; import electroblob.wizardry.registry.Spells; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.EntityUtils; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.GlStateManager; @@ -12,7 +12,6 @@ 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; @@ -32,7 +31,7 @@ public class RenderWings { EntityPlayer player = event.getEntityPlayer(); - if(WizardryUtilities.isCasting(player, Spells.flight)){ + if(EntityUtils.isCasting(player, Spells.flight)){ GlStateManager.pushMatrix(); @@ -41,9 +40,7 @@ public class RenderWings { 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(event.getX(), event.getY(), event.getZ()); // GlStateManager.rotate(-entityplayer.rotationYawHead, 0, 1, 0); GlStateManager.rotate(-player.renderYawOffset, 0, 1, 0); diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderBlackHole.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderBlackHole.java similarity index 77% rename from src/main/java/electroblob/wizardry/client/renderer/RenderBlackHole.java rename to src/main/java/electroblob/wizardry/client/renderer/entity/RenderBlackHole.java index cbde5ed2..247c3606 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderBlackHole.java +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderBlackHole.java @@ -1,6 +1,8 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.entity; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.DrawingUtils; +import electroblob.wizardry.client.renderer.RayHelper; import electroblob.wizardry.entity.construct.EntityBlackHole; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.*; @@ -16,17 +18,18 @@ import java.util.Collections; public class RenderBlackHole extends Render { - private static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID, - "textures/entity/dark_ray.png"); - private static final ResourceLocation texture2 = new ResourceLocation(Wizardry.MODID, - "textures/entity/black_hole.png"); + // TODO: Remove RayHelper and the ray texture entirely and use colours with depth mask off (like imbuement altar) + private static final ResourceLocation RAY_TEXTURE = new ResourceLocation(Wizardry.MODID, + "textures/entity/black_hole/ray.png"); + private static final ResourceLocation CENTRE_TEXTURE = new ResourceLocation(Wizardry.MODID, + "textures/entity/black_hole/centre.png"); public RenderBlackHole(RenderManager renderManager){ super(renderManager); } @Override - public void doRender(EntityBlackHole blackhole, double x, double y, double z, float fa, float fb){ + public void doRender(EntityBlackHole entity, double x, double y, double z, float entityYaw, float partialTicks){ GlStateManager.pushMatrix(); @@ -48,16 +51,10 @@ public class RenderBlackHole extends Render { GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); - if(blackhole.ticksExisted < 10){ - GlStateManager.scale((float)blackhole.ticksExisted / 10, (float)blackhole.ticksExisted / 10, - (float)blackhole.ticksExisted / 10); - } - if(blackhole.ticksExisted > blackhole.lifetime - 10){ - float scale = Math.max(0, (float)(blackhole.lifetime - blackhole.ticksExisted) / 10); - GlStateManager.scale(scale, scale, scale); - } + float s = DrawingUtils.smoothScaleFactor(entity.lifetime, entity.ticksExisted, partialTicks, 10, 10); + GlStateManager.scale(s, s, s); - this.bindTexture(texture); + this.bindTexture(RAY_TEXTURE); // In theory this stuff should sort the rays into the correct render order based on the distance from // the 'camera' (i.e. the player's viewpoint) @@ -65,24 +62,24 @@ public class RenderBlackHole extends Render { // coordinates which were inconsistent with my calculated ones. Since I know those will give the desired // effect anyway, I just used them directly instead. - ArrayList rays = new ArrayList(1); + ArrayList rays = new ArrayList<>(1); for(int j = 0; j < 30; j++){ - float scale = 3.0f; + float radius = 3.0f * entity.getSizeMultiplier(); - int a = blackhole.randomiser[j]; - int b = blackhole.randomiser2[j]; + int a = entity.randomiser[j]; + int b = entity.randomiser2[j]; int sliceAngle = 20 + a; - double x1 = scale * MathHelper.sin((blackhole.ticksExisted + 40 * j) * ((float)Math.PI / 180f)); + double x1 = radius * MathHelper.sin((entity.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 z1 = radius * MathHelper.cos((entity.ticksExisted + 40 * j) * ((float)Math.PI / 180)); - double x2 = scale * MathHelper.sin((blackhole.ticksExisted + 40 * j - sliceAngle) * ((float)Math.PI / 180)); + double x2 = radius * MathHelper.sin((entity.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 z2 = radius * MathHelper.cos((entity.ticksExisted + 40 * j - sliceAngle) * ((float)Math.PI / 180)); double absoluteX = x1 * MathHelper.cos(31 * b); double absoluteY = z1 * MathHelper.sin(31 * a) + x1 * MathHelper.cos(31 * a) * MathHelper.sin(31 * b); @@ -142,7 +139,7 @@ public class RenderBlackHole extends Render { buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - this.bindTexture(texture2); + this.bindTexture(CENTRE_TEXTURE); buffer.pos(-0.4, 0.4, 0).tex(0, 0).endVertex(); buffer.pos(0.4, 0.4, 0).tex(1, 0).endVertex(); @@ -164,7 +161,7 @@ public class RenderBlackHole extends Render { @Override protected ResourceLocation getEntityTexture(EntityBlackHole entity){ - return texture; + return RAY_TEXTURE; } } diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderBlank.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderBlank.java similarity index 90% rename from src/main/java/electroblob/wizardry/client/renderer/RenderBlank.java rename to src/main/java/electroblob/wizardry/client/renderer/entity/RenderBlank.java index 477b6092..dafc793f 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderBlank.java +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderBlank.java @@ -1,4 +1,4 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.entity; import net.minecraft.client.renderer.entity.Render; import net.minecraft.client.renderer.entity.RenderManager; diff --git a/src/main/java/electroblob/wizardry/client/renderer/entity/RenderBoulder.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderBoulder.java new file mode 100644 index 00000000..286d5eb2 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderBoulder.java @@ -0,0 +1,51 @@ +package electroblob.wizardry.client.renderer.entity; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.DrawingUtils; +import electroblob.wizardry.client.model.ModelBoulder; +import electroblob.wizardry.entity.construct.EntityBoulder; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.entity.Render; +import net.minecraft.client.renderer.entity.RenderManager; +import net.minecraft.util.ResourceLocation; + +public class RenderBoulder extends Render { + + private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/entity/boulder.png"); + + private ModelBoulder model = new ModelBoulder(); + + public RenderBoulder(RenderManager manager){ + super(manager); + } + + @Override + public void doRender(EntityBoulder entity, double x, double y, double z, float yaw, float partialTicks){ + + GlStateManager.pushMatrix(); + GlStateManager.translate(x, y + entity.height/2, z); + GlStateManager.rotate(180, 0F, 0F, 1F); + GlStateManager.rotate(yaw - 90, 0, 1, 0); + GlStateManager.rotate(entity.prevRotationPitch + (entity.rotationPitch - entity.prevRotationPitch) * partialTicks, 0, 0, 1); + GlStateManager.translate(0, -entity.height/2, 0); + + // Pass in -1 for the lifetime as the boulder crumbles when the time expires + float s = DrawingUtils.smoothScaleFactor(-1, entity.ticksExisted, partialTicks, 10, 10); + s *= entity.getSizeMultiplier(); + GlStateManager.scale(s, s, s); + + GlStateManager.translate(0, 0.875, 0); // No idea why it starts 7/8 of a block too low, but it does + + this.bindTexture(TEXTURE); + + model.render(entity, 0, 0, 0, 0, 0, 0.0625f); + + GlStateManager.popMatrix(); + } + + @Override + protected ResourceLocation getEntityTexture(EntityBoulder entity){ + return TEXTURE; + } + +} diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderBubble.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderBubble.java similarity index 55% rename from src/main/java/electroblob/wizardry/client/renderer/RenderBubble.java rename to src/main/java/electroblob/wizardry/client/renderer/entity/RenderBubble.java index 2694f1e8..5147a994 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderBubble.java +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderBubble.java @@ -1,8 +1,9 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.entity; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.DrawingUtils; import electroblob.wizardry.entity.construct.EntityBubble; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.EntityUtils; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.GlStateManager; @@ -11,21 +12,21 @@ 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.entity.Entity; import net.minecraft.util.ResourceLocation; import org.lwjgl.opengl.GL11; public class RenderBubble extends Render { - private static final ResourceLocation particleTextures = new ResourceLocation("textures/particle/particles.png"); - private static final ResourceLocation darkOrbTexture = new ResourceLocation(Wizardry.MODID, - "textures/entity/dark_orb.png"); + private static final ResourceLocation PARTICLE_TEXTURES = new ResourceLocation("textures/particle/particles.png"); + private static final ResourceLocation ENTRAPMENT_TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/entity/entrapment.png"); public RenderBubble(RenderManager renderManager){ super(renderManager); } @Override - public void doRender(EntityBubble entity, double par2, double par4, double par6, float par8, float par9){ + public void doRender(EntityBubble entity, double x, double y, double z, float entityYaw, float partialTicks){ GlStateManager.pushMatrix(); GlStateManager.enableBlend(); @@ -33,18 +34,20 @@ public class RenderBubble extends Render { float yOffset = 0; - if(WizardryUtilities.getRider(entity) != null){ - yOffset = WizardryUtilities.getRider(entity).height / 2; + Entity rider = EntityUtils.getRider(entity); + + if(rider != null){ + yOffset = rider.height / 2; } - GlStateManager.translate((float)par2, (float)par4 + yOffset, (float)par6); + GlStateManager.translate((float)x, (float)y + yOffset, (float)z); - this.bindTexture(((EntityBubble)entity).isDarkOrb ? darkOrbTexture : particleTextures); + this.bindTexture(entity.isDarkOrb ? ENTRAPMENT_TEXTURE : PARTICLE_TEXTURES); float f6 = 1.0F; float f7 = 0.5F; float f8 = 0.5F; - if(((EntityBubble)entity).isDarkOrb){ + if(entity.isDarkOrb){ GlStateManager.disableLighting(); OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240, 240); }else{ @@ -63,35 +66,32 @@ public class RenderBubble extends Render { GlStateManager.rotate(180.0F - this.renderManager.playerViewY, 0.0F, 1.0F, 0.0F); GlStateManager.rotate(yaw, 1.0F, 0.0F, 0.0F); - float f11 = 3.0F; - GlStateManager.scale(f11, f11, f11); + // Bubble 'bursts' so doesn't shrink when is disappears + float s = 3 * DrawingUtils.smoothScaleFactor(entity.isDarkOrb ? entity.lifetime : -1, entity.ticksExisted, partialTicks, 10, 10); + GlStateManager.scale(s, s, s); double pixelwidth = (1.0d / 128); Tessellator tessellator = Tessellator.getInstance(); BufferBuilder buffer = tessellator.getBuffer(); buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - // tessellator.setColorRGBA_I(k1, 128); - // buffer.normal(0.0F, 1.0F, 0.0F); - if(((EntityBubble)entity).isDarkOrb){ - buffer.pos((double)(0.0F - f7), (double)(0.0F - f8), 0.0D).tex(0, 1).endVertex(); - buffer.pos((double)(f6 - f7), (double)(0.0F - f8), 0.0D).tex(1, 1).endVertex(); - buffer.pos((double)(f6 - f7), (double)(1.0F - f8), 0.0D).tex(1, 0).endVertex(); - buffer.pos((double)(0.0F - f7), (double)(1.0F - f8), 0.0D).tex(0, 0).endVertex(); + if(entity.isDarkOrb){ + buffer.pos(0.0F - f7, 0.0F - f8, 0.0D).tex(0, 1).endVertex(); + buffer.pos(f6 - f7, 0.0F - f8, 0.0D).tex(1, 1).endVertex(); + buffer.pos(f6 - f7, 1.0F - f8, 0.0D).tex(1, 0).endVertex(); + buffer.pos(0.0F - f7, 1.0F - f8, 0.0D).tex(0, 0).endVertex(); }else{ - buffer.pos((double)(0.0F - f7), (double)(0.0F - f8), 0.0D).tex(pixelwidth, pixelwidth * 24).endVertex(); - buffer.pos((double)(f6 - f7), (double)(0.0F - f8), 0.0D).tex(pixelwidth * 8, pixelwidth * 24).endVertex(); - buffer.pos((double)(f6 - f7), (double)(1.0F - f8), 0.0D).tex(pixelwidth * 8, pixelwidth * 17).endVertex(); - buffer.pos((double)(0.0F - f7), (double)(1.0F - f8), 0.0D).tex(pixelwidth, pixelwidth * 17).endVertex(); + buffer.pos(0.0F - f7, 0.0F - f8, 0.0D).tex(pixelwidth, pixelwidth * 24).endVertex(); + buffer.pos(f6 - f7, 0.0F - f8, 0.0D).tex(pixelwidth * 8, pixelwidth * 24).endVertex(); + buffer.pos(f6 - f7, 1.0F - f8, 0.0D).tex(pixelwidth * 8, pixelwidth * 17).endVertex(); + buffer.pos(0.0F - f7, 1.0F - f8, 0.0D).tex(pixelwidth, pixelwidth * 17).endVertex(); } tessellator.draw(); GlStateManager.disableBlend(); - if(((EntityBubble)entity).isDarkOrb){ - GlStateManager.enableLighting(); - } + if(entity.isDarkOrb) GlStateManager.enableLighting(); GlStateManager.disableRescaleNormal(); GlStateManager.popMatrix(); diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderDecay.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderDecay.java similarity index 61% rename from src/main/java/electroblob/wizardry/client/renderer/RenderDecay.java rename to src/main/java/electroblob/wizardry/client/renderer/entity/RenderDecay.java index cd27522a..47a9ef21 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderDecay.java +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderDecay.java @@ -1,6 +1,7 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.entity; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.DrawingUtils; import electroblob.wizardry.entity.construct.EntityDecay; import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.GlStateManager; @@ -14,17 +15,17 @@ import org.lwjgl.opengl.GL11; public class RenderDecay extends Render { - private static final ResourceLocation[] textures = new ResourceLocation[10]; + private static final ResourceLocation[] TEXTURES = new ResourceLocation[10]; public RenderDecay(RenderManager renderManager){ super(renderManager); for(int i = 0; i < 10; i++){ - textures[i] = new ResourceLocation(Wizardry.MODID, "textures/entity/decay_" + i + ".png"); + TEXTURES[i] = new ResourceLocation(Wizardry.MODID, "textures/entity/decay/decay_" + i + ".png"); } } @Override - public void doRender(EntityDecay entity, double par2, double par4, double par6, float par8, float par9){ + public void doRender(EntityDecay entity, double x, double y, double z, float entityYaw, float partialTicks){ GlStateManager.pushMatrix(); GlStateManager.enableBlend(); @@ -34,28 +35,26 @@ public class RenderDecay extends Render { float yOffset = 0; - GlStateManager.translate((float)par2, (float)par4 + yOffset, (float)par6); + GlStateManager.translate((float)x, (float)y + yOffset, (float)z); - this.bindTexture(textures[((EntityDecay)entity).textureIndex]); + this.bindTexture(TEXTURES[entity.textureIndex]); float f6 = 1.0F; float f7 = 0.5F; float f8 = 0.5F; GlStateManager.rotate(-90, 1, 0, 0); - float scale = 2 * Math.min(1, (float)(entity.lifetime - entity.ticksExisted) / 50f); - - GlStateManager.scale(scale, scale, scale); + float s = 2 * DrawingUtils.smoothScaleFactor(entity.lifetime, entity.ticksExisted, partialTicks, 10, 50); + GlStateManager.scale(s, s, s); Tessellator tessellator = Tessellator.getInstance(); BufferBuilder buffer = tessellator.getBuffer(); buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - // tessellator.setColorRGBA_I(k1, 128); - // buffer.normal(0.0F, 1.0F, 0.0F); - 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(); + + buffer.pos(0.0F - f7, 0.0F - f8, 0.01).tex(0, 1).endVertex(); + buffer.pos(f6 - f7, 0.0F - f8, 0.01).tex(1, 1).endVertex(); + buffer.pos(f6 - f7, 1.0F - f8, 0.01).tex(1, 0).endVertex(); + buffer.pos(0.0F - f7, 1.0F - f8, 0.01).tex(0, 0).endVertex(); tessellator.draw(); diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderDecoy.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderDecoy.java similarity index 92% rename from src/main/java/electroblob/wizardry/client/renderer/RenderDecoy.java rename to src/main/java/electroblob/wizardry/client/renderer/entity/RenderDecoy.java index 10acfa56..0b5dd961 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderDecoy.java +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderDecoy.java @@ -1,4 +1,4 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.entity; import electroblob.wizardry.Wizardry; import electroblob.wizardry.entity.living.EntityDecoy; @@ -8,7 +8,7 @@ import net.minecraft.client.renderer.entity.RenderBiped; 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.common.ObfuscationReflectionHelper; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -18,8 +18,8 @@ public class RenderDecoy extends RenderBiped { private static final ResourceLocation steveTextures = new ResourceLocation("textures/entity/steve.png"); - private static final Method getEntityTexture = ReflectionHelper.findMethod(Render.class, "getEntityTexture", - "func_110775_a", Entity.class); // Generic parameter T is erased to Entity at runtime. + private static final Method getEntityTexture = ObfuscationReflectionHelper.findMethod(Render.class, "func_110775_a", + ResourceLocation.class, Entity.class); // Generic parameter T is erased to Entity at runtime. public RenderDecoy(RenderManager manager){ super(manager, new ModelBiped(0.0f), 0.5f); diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderEvilWizard.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderEvilWizard.java similarity index 76% rename from src/main/java/electroblob/wizardry/client/renderer/RenderEvilWizard.java rename to src/main/java/electroblob/wizardry/client/renderer/entity/RenderEvilWizard.java index 34299e1c..02819d83 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderEvilWizard.java +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderEvilWizard.java @@ -1,4 +1,4 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.entity; import electroblob.wizardry.Wizardry; import electroblob.wizardry.client.model.ModelWizard; @@ -10,14 +10,15 @@ import net.minecraft.util.ResourceLocation; //@SideOnly(Side.CLIENT) public class RenderEvilWizard extends RenderBiped { - static final ResourceLocation[] textures = new ResourceLocation[6]; + + static final ResourceLocation[] TEXTURES = new ResourceLocation[6]; public RenderEvilWizard(RenderManager renderManager){ super(renderManager, new ModelWizard(), 0.5F); for(int i = 0; i < 6; i++){ - textures[i] = new ResourceLocation(Wizardry.MODID, "textures/entity/evil_wizard_" + i + ".png"); + TEXTURES[i] = new ResourceLocation(Wizardry.MODID, "textures/entity/evil_wizard/evil_wizard_" + i + ".png"); } // Just using the default without overriding models, since the armour sets its own model anyway. this.addLayer(new LayerBipedArmor(this)); @@ -25,7 +26,7 @@ public class RenderEvilWizard extends RenderBiped { @Override protected ResourceLocation getEntityTexture(EntityEvilWizard wizard){ - return textures[wizard.textureIndex]; + return TEXTURES[wizard.textureIndex]; } } diff --git a/src/main/java/electroblob/wizardry/client/renderer/entity/RenderFireRing.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderFireRing.java new file mode 100644 index 00000000..b935f29f --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderFireRing.java @@ -0,0 +1,180 @@ +package electroblob.wizardry.client.renderer.entity; + +import electroblob.wizardry.client.DrawingUtils; +import electroblob.wizardry.entity.construct.EntityFireRing; +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.entity.Render; +import net.minecraft.client.renderer.entity.RenderManager; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +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 { + + private final ResourceLocation texture; + + public RenderFireRing(RenderManager renderManager, ResourceLocation texture){ + super(renderManager); + this.texture = texture; + } + + @Override + public void doRender(EntityFireRing entity, double x, double y, double z, float entityYaw, float partialTicks){ + + 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(x, y + yOffset, z); + + this.bindTexture(texture); + float f6 = 1.0F; + float f7 = 0.5F; + float f8 = 0.5F; + + GlStateManager.rotate(-90, 1, 0, 0); + + float s = DrawingUtils.smoothScaleFactor(entity.lifetime, entity.ticksExisted, partialTicks, 10, 10); + GlStateManager.scale(entity.width * s, entity.width * s, entity.width * s); + + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder buffer = tessellator.getBuffer(); + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + buffer.pos(0.0F - f7, 0.0F - f8, 0.01).tex(0, 1).endVertex(); + buffer.pos(f6 - f7, 0.0F - f8, 0.01).tex(1, 1).endVertex(); + buffer.pos(f6 - f7, 1.0F - f8, 0.01).tex(1, 0).endVertex(); + buffer.pos(0.0F - f7, 1.0F - f8, 0.01).tex(0, 0).endVertex(); + + tessellator.draw(); + + GlStateManager.disableBlend(); + GlStateManager.enableLighting(); + GlStateManager.disableRescaleNormal(); + GlStateManager.popMatrix(); + + // Fire + // TODO: Bleeeuuurugh this is disgusting must fix :0 + + if(s >= 1){ + GlStateManager.disableLighting(); + TextureAtlasSprite icon = Minecraft.getMinecraft().getBlockRendererDispatcher() + .getModelForState(Blocks.FIRE.getDefaultState()).getParticleTexture(); + float s1 = entity.width/5; + int sides = (int)(16 * s1); + float height = 1.0f; + + for(int k = 0; k < sides; k++){ + + GlStateManager.pushMatrix(); + GlStateManager.translate((float)x, (float)y + 0.05f, (float)z); +// GlStateManager.scale(s1, s1, s1); + float f2 = 0.5F; + float f3 = 0.0F; + float f4 = 0.2f; + float f5 = (float)(entity.posY - entity.posY); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + float f61 = 0.0F; + int i = 0; + + GlStateManager.rotate((360f / (float)sides) * k, 0, 1, 0); + GlStateManager.translate(0, 0, -2.3f * s1); + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + + while(f4 > 0.0F){ + + this.bindTexture(TextureMap.LOCATION_BLOCKS_TEXTURE); + float f71 = icon.getMinU(); + float f81 = icon.getMinV(); + float f9 = icon.getMaxU(); + float f10 = icon.getMaxV(); + + if(i / 2 % 2 == 0){ + float f11 = f9; + f9 = f71; + f71 = f11; + } + + buffer.pos((f2 - f3), (0.0F - f5), f61).tex(f9, f10).endVertex(); + buffer.pos((-f2 - f3), (0.0F - f5), f61).tex(f71, f10).endVertex(); + buffer.pos((-f2 - f3), (height - f5), f61).tex(f71, f81).endVertex(); + buffer.pos((f2 - f3), (height - f5), f61).tex(f9, f81).endVertex(); + f4 -= 0.45F; + f5 -= 0.45F; + f2 *= 0.9F; + f61 += 0.03F; + ++i; + } + + tessellator.draw(); + GlStateManager.popMatrix(); + } + + for(int k = 0; k < sides; k++){ + + GlStateManager.pushMatrix(); + GlStateManager.translate((float)x, (float)y + 0.05f, (float)z); +// GlStateManager.scale(s1, s1, s1); + float f2 = 0.5F; + float f3 = 0.0F; + float f4 = 0.2f; + float f5 = (float)(entity.posY - entity.posY); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + float f61 = 0.0F; + int i = 0; + + GlStateManager.rotate((360f / (float)sides) * k, 0, 1, 0); + GlStateManager.translate(0, 0, 2.3f * s1); + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + + while(f4 > 0.0F){ + + this.bindTexture(TextureMap.LOCATION_BLOCKS_TEXTURE); + float f71 = icon.getMinU(); + float f81 = icon.getMinV(); + float f9 = icon.getMaxU(); + float f10 = icon.getMaxV(); + + if(i / 2 % 2 == 0){ + float f11 = f9; + f9 = f71; + f71 = f11; + } + + buffer.pos((f2 - f3), (0.0F - f5), f61).tex(f9, f10).endVertex(); + buffer.pos((-f2 - f3), (0.0F - f5), f61).tex(f71, f10).endVertex(); + buffer.pos((-f2 - f3), (height - f5), f61).tex(f71, f81).endVertex(); + buffer.pos((f2 - f3), (height - f5), f61).tex(f9, f81).endVertex(); + f4 -= 0.45F; + f5 -= 0.45F; + f2 *= 0.9F; + f61 += 0.03F; + ++i; + } + + tessellator.draw(); + GlStateManager.popMatrix(); + } + } + + GlStateManager.enableLighting(); + } + + @Override + protected ResourceLocation getEntityTexture(EntityFireRing entity){ + return null; + } + +} diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderForceArrow.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderForceArrow.java similarity index 98% rename from src/main/java/electroblob/wizardry/client/renderer/RenderForceArrow.java rename to src/main/java/electroblob/wizardry/client/renderer/entity/RenderForceArrow.java index 7f908e71..9b9d6974 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderForceArrow.java +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderForceArrow.java @@ -1,4 +1,4 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.entity; import electroblob.wizardry.Wizardry; import electroblob.wizardry.entity.projectile.EntityForceArrow; diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderForcefield.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderForcefield.java similarity index 98% rename from src/main/java/electroblob/wizardry/client/renderer/RenderForcefield.java rename to src/main/java/electroblob/wizardry/client/renderer/entity/RenderForcefield.java index 660c1e1a..c43c5647 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderForcefield.java +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderForcefield.java @@ -1,4 +1,4 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.entity; import electroblob.wizardry.entity.construct.EntityForcefield; import net.minecraft.client.renderer.BufferBuilder; diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderHammer.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderHammer.java similarity index 96% rename from src/main/java/electroblob/wizardry/client/renderer/RenderHammer.java rename to src/main/java/electroblob/wizardry/client/renderer/entity/RenderHammer.java index 3962e04b..fda1ae08 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderHammer.java +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderHammer.java @@ -1,4 +1,4 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.entity; import electroblob.wizardry.Wizardry; import electroblob.wizardry.client.model.ModelHammer; diff --git a/src/main/java/electroblob/wizardry/client/renderer/entity/RenderIceBarrier.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderIceBarrier.java new file mode 100644 index 00000000..2d1edcb1 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderIceBarrier.java @@ -0,0 +1,45 @@ +package electroblob.wizardry.client.renderer.entity; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.model.ModelIceBarrier; +import electroblob.wizardry.entity.construct.EntityIceBarrier; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.entity.Render; +import net.minecraft.client.renderer.entity.RenderManager; +import net.minecraft.util.ResourceLocation; + +public class RenderIceBarrier extends Render { + + private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/entity/ice_barrier.png"); + + private ModelIceBarrier model = new ModelIceBarrier(); + + public RenderIceBarrier(RenderManager manager){ + super(manager); + } + + @Override + public void doRender(EntityIceBarrier entity, double x, double y, double z, float yaw, float partialTicks){ + + GlStateManager.pushMatrix(); + GlStateManager.translate(x, y + entity.height/2, z); + GlStateManager.rotate(180, 0F, 0F, 1F); + GlStateManager.rotate(yaw, 0, 1, 0); + GlStateManager.translate(0, -entity.height/2 - 0.3, 0); + + float s = entity.getSizeMultiplier(); + GlStateManager.scale(s, s, s); + + this.bindTexture(TEXTURE); + + model.render(entity, 0, 0, 0, 0, 0, 0.0625f); + + GlStateManager.popMatrix(); + } + + @Override + protected ResourceLocation getEntityTexture(EntityIceBarrier entity){ + return TEXTURE; + } + +} diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderIceGiant.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderIceGiant.java similarity index 96% rename from src/main/java/electroblob/wizardry/client/renderer/RenderIceGiant.java rename to src/main/java/electroblob/wizardry/client/renderer/entity/RenderIceGiant.java index e20f22bf..e4b45987 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderIceGiant.java +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderIceGiant.java @@ -1,4 +1,4 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.entity; import electroblob.wizardry.Wizardry; import electroblob.wizardry.client.model.ModelIceGiant; diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderIceSpike.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderIceSpike.java similarity index 98% rename from src/main/java/electroblob/wizardry/client/renderer/RenderIceSpike.java rename to src/main/java/electroblob/wizardry/client/renderer/entity/RenderIceSpike.java index 3a88d93b..07159c09 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderIceSpike.java +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderIceSpike.java @@ -1,4 +1,4 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.entity; import electroblob.wizardry.Wizardry; import electroblob.wizardry.entity.construct.EntityIceSpike; diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderLightningDisc.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderLightningDisc.java similarity index 98% rename from src/main/java/electroblob/wizardry/client/renderer/RenderLightningDisc.java rename to src/main/java/electroblob/wizardry/client/renderer/entity/RenderLightningDisc.java index 96fe6110..d58b8f63 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderLightningDisc.java +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderLightningDisc.java @@ -1,4 +1,4 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.entity; import electroblob.wizardry.entity.projectile.EntityLightningDisc; import net.minecraft.client.renderer.BufferBuilder; diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderMagicArrow.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderMagicArrow.java similarity index 98% rename from src/main/java/electroblob/wizardry/client/renderer/RenderMagicArrow.java rename to src/main/java/electroblob/wizardry/client/renderer/entity/RenderMagicArrow.java index b0db9b36..96b41925 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderMagicArrow.java +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderMagicArrow.java @@ -1,4 +1,4 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.entity; import electroblob.wizardry.entity.projectile.EntityMagicArrow; import net.minecraft.client.renderer.BufferBuilder; diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderPhoenix.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderPhoenix.java similarity index 96% rename from src/main/java/electroblob/wizardry/client/renderer/RenderPhoenix.java rename to src/main/java/electroblob/wizardry/client/renderer/entity/RenderPhoenix.java index 5735655f..9a19a911 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderPhoenix.java +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderPhoenix.java @@ -1,4 +1,4 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.entity; import electroblob.wizardry.Wizardry; import electroblob.wizardry.client.model.ModelPhoenix; diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderPossessingPlayer.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderPossessingPlayer.java similarity index 97% rename from src/main/java/electroblob/wizardry/client/renderer/RenderPossessingPlayer.java rename to src/main/java/electroblob/wizardry/client/renderer/entity/RenderPossessingPlayer.java index 0820a178..4da59b1e 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderPossessingPlayer.java +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderPossessingPlayer.java @@ -1,4 +1,4 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.entity; import electroblob.wizardry.spell.Possession; import net.minecraft.client.renderer.entity.Render; diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderProjectile.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderProjectile.java similarity index 98% rename from src/main/java/electroblob/wizardry/client/renderer/RenderProjectile.java rename to src/main/java/electroblob/wizardry/client/renderer/entity/RenderProjectile.java index f5ece03f..10427e2a 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderProjectile.java +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderProjectile.java @@ -1,4 +1,4 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.entity; import electroblob.wizardry.entity.projectile.EntityMagicProjectile; import net.minecraft.client.Minecraft; diff --git a/src/main/java/electroblob/wizardry/client/renderer/entity/RenderRadiantTotem.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderRadiantTotem.java new file mode 100644 index 00000000..fee417cb --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderRadiantTotem.java @@ -0,0 +1,151 @@ +package electroblob.wizardry.client.renderer.entity; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.DrawingUtils; +import electroblob.wizardry.entity.construct.EntityRadiantTotem; +import electroblob.wizardry.util.GeometryUtils; +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.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.Vec3d; +import org.lwjgl.opengl.GL11; + +import javax.annotation.Nullable; + +public class RenderRadiantTotem extends Render { + + private static final ResourceLocation FLARE_TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/entity/totem/flare.png"); + private static final ResourceLocation[] CUBE_TEXTURES = new ResourceLocation[14]; + + static { + for(int i = 0; i< CUBE_TEXTURES.length; i++) CUBE_TEXTURES[i] = new ResourceLocation(Wizardry.MODID, "textures/entity/totem/cube_" + i + ".png"); + } + + public RenderRadiantTotem(RenderManager manager){ + super(manager); + } + + @Nullable + @Override + protected ResourceLocation getEntityTexture(EntityRadiantTotem entity){ + return CUBE_TEXTURES[entity.ticksExisted % CUBE_TEXTURES.length]; + } + + @Override + public void doRender(EntityRadiantTotem entity, double x, double y, double z, float entityYaw, float partialTicks){ + + Tessellator tessellator = Tessellator.getInstance(); + + GlStateManager.pushMatrix(); + + GlStateManager.enableBlend(); + GlStateManager.disableLighting(); + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240, 240); + GlStateManager.depthMask(false); + GlStateManager.disableCull(); + + GlStateManager.translate(x, y + entity.height/2, z); + + float s = DrawingUtils.smoothScaleFactor(entity.lifetime, entity.ticksExisted, partialTicks, 10, 10); + GlStateManager.scale(s, s, s); + + drawFlare(tessellator); + drawCube(entity, tessellator, partialTicks); + + GlStateManager.disableBlend(); + GlStateManager.enableLighting(); + GlStateManager.depthMask(true); + GlStateManager.enableCull(); + + GlStateManager.popMatrix(); + } + + private void drawFlare(Tessellator tessellator){ + + BufferBuilder buffer = tessellator.getBuffer(); + + GlStateManager.pushMatrix(); + + // Makes the 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); + GlStateManager.color(1, 0.957f, 0.608f); + GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + + bindTexture(FLARE_TEXTURE); + + // 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 = Minecraft.getMinecraft().gameSettings.thirdPersonView == 2 + ? Minecraft.getMinecraft().getRenderManager().playerViewX + : -Minecraft.getMinecraft().getRenderManager().playerViewX; + GlStateManager.rotate(180.0F - Minecraft.getMinecraft().getRenderManager().playerViewY, 0.0F, 1.0F, 0.0F); + GlStateManager.rotate(yaw, 1.0F, 0.0F, 0.0F); + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + + float r = 0.5f; + + buffer.pos(-r, r, 0).tex(0, 0).endVertex(); + buffer.pos( r, r, 0).tex(1, 0).endVertex(); + buffer.pos( r, -r, 0).tex(1, 1).endVertex(); + buffer.pos(-r, -r, 0).tex(0, 1).endVertex(); + + tessellator.draw(); + + // Reverses the colour addition change + GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE); + GlStateManager.color(1, 1, 1, 1); + GlStateManager.popMatrix(); + } + + private void drawCube(EntityRadiantTotem entity, Tessellator tessellator, float partialTicks){ + + BufferBuilder buffer = tessellator.getBuffer(); + + GlStateManager.pushMatrix(); + + float age = entity.ticksExisted + partialTicks; + float rotationSpeed = 2; + + GlStateManager.rotate(age * rotationSpeed/2, 0.0F, 1.0F, 0.0F); + GlStateManager.rotate(60.0F, 0.7071F, 0.0F, 0.7071F); + GlStateManager.rotate(age * rotationSpeed, 0.0F, 1.0F, 0.0F); + + GlStateManager.scale(0.5, 0.5, 0.5); + + GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE); + + bindEntityTexture(entity); + + Vec3d[] vertices = GeometryUtils.getVertices(entity.getEntityBoundingBox().offset(entity.getPositionVector() + .add(0, entity.height/2, 0).scale(-1))); + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + + // Outside + drawFace(buffer, vertices[0], vertices[1], vertices[3], vertices[2], 0.5f, 0, 0.75f, 0.5f); // Bottom + drawFace(buffer, vertices[6], vertices[7], vertices[2], vertices[3], 0.75f, 0.5f, 1, 1); // South + drawFace(buffer, vertices[5], vertices[6], vertices[1], vertices[2], 0, 0.5f, 0.25f, 1); // East + drawFace(buffer, vertices[4], vertices[5], vertices[0], vertices[1], 0.25f, 0.5f, 0.5f, 1); // North + drawFace(buffer, vertices[7], vertices[4], vertices[3], vertices[0], 0.5f, 0.5f, 0.75f, 1); // West + drawFace(buffer, vertices[5], vertices[4], vertices[6], vertices[7], 0.25f, 0, 0.5f, 0.5f); // Top + + tessellator.draw(); + + GlStateManager.popMatrix(); + } + + private static void drawFace(BufferBuilder buffer, Vec3d topLeft, Vec3d topRight, Vec3d bottomLeft, Vec3d bottomRight, float u1, float v1, float u2, float v2){ + buffer.pos(topLeft.x, topLeft.y, topLeft.z).tex(u1, v1).endVertex(); + buffer.pos(topRight.x, topRight.y, topRight.z).tex(u2, v1).endVertex(); + buffer.pos(bottomRight.x, bottomRight.y, bottomRight.z).tex(u2, v2).endVertex(); + buffer.pos(bottomLeft.x, bottomLeft.y, bottomLeft.z).tex(u1, v2).endVertex(); + } + +} diff --git a/src/main/java/electroblob/wizardry/client/renderer/entity/RenderRemnant.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderRemnant.java new file mode 100644 index 00000000..ed4932e8 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderRemnant.java @@ -0,0 +1,75 @@ +package electroblob.wizardry.client.renderer.entity; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.model.ModelRemnant; +import electroblob.wizardry.entity.living.EntityRemnant; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.entity.Render; +import net.minecraft.client.renderer.entity.RenderManager; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; + +import javax.annotation.Nullable; + +public class RenderRemnant extends Render { + + private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/entity/remnant.png"); + + private final ModelRemnant model; + + public RenderRemnant(RenderManager manager){ + super(manager); + model = new ModelRemnant(); + } + + @Override + public void doRender(EntityRemnant entity, double x, double y, double z, float entityYaw, float partialTicks){ + + float age = (float)entity.ticksExisted + partialTicks; + GlStateManager.pushMatrix(); + GlStateManager.translate((float)x, (float)y, (float)z); + this.bindTexture(TEXTURE); +// float whatIsThis = MathHelper.sin(age * 0.2F) / 2.0F + 0.5F; +// whatIsThis = whatIsThis * whatIsThis + whatIsThis; + + if(this.renderOutlines){ + GlStateManager.enableColorMaterial(); + GlStateManager.enableOutlineMode(this.getTeamColor(entity)); + } + + float expansion = 0.9f; + + if(entity.deathTime > 0){ + float f = (entity.deathTime + partialTicks) * 0.25f; + f = 1 - (1 / (f + 1)); + expansion -= f * 0.75f; + + }else if(entity.hurtTime > 0){ + float f = (entity.hurtTime - partialTicks) / entity.maxHurtTime; + // Neat bit of maths borrowed from the camera tilt effect when hurt + f = MathHelper.sin(f * f * f * f * (float)Math.PI); + expansion += f * 0.2f; + } + +// if(entity.isAttacking()) s -= 0.4f; + float rotationSpeed = entity.isAttacking() ? 10 : 3; + + this.model.render(entity, rotationSpeed, expansion, age, 0, 0, 0.0625f); + + if(this.renderOutlines){ + GlStateManager.disableOutlineMode(); + GlStateManager.disableColorMaterial(); + } + + GlStateManager.popMatrix(); + + super.doRender(entity, x, y, z, entityYaw, partialTicks); + } + + @Nullable + @Override + protected ResourceLocation getEntityTexture(EntityRemnant entity){ + return TEXTURE; + } + +} diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderSigil.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderSigil.java similarity index 64% rename from src/main/java/electroblob/wizardry/client/renderer/RenderSigil.java rename to src/main/java/electroblob/wizardry/client/renderer/entity/RenderSigil.java index 5d43d31c..8310d968 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderSigil.java +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderSigil.java @@ -1,6 +1,6 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.entity; -import electroblob.wizardry.entity.construct.EntityHealAura; +import electroblob.wizardry.client.DrawingUtils; import electroblob.wizardry.entity.construct.EntityMagicConstruct; import electroblob.wizardry.util.AllyDesignationSystem; import net.minecraft.client.Minecraft; @@ -18,24 +18,24 @@ import org.lwjgl.opengl.GL11; public class RenderSigil extends Render { private final ResourceLocation texture; - private float scale = 1.0f; + private float rotationSpeed; private boolean invisibleToEnemies; - public RenderSigil(RenderManager renderManager, ResourceLocation texture, float scale, boolean invisibleToEnemies){ + public RenderSigil(RenderManager renderManager, ResourceLocation texture, float rotationSpeed, boolean invisibleToEnemies){ super(renderManager); this.texture = texture; - this.scale = scale; + this.rotationSpeed = rotationSpeed; this.invisibleToEnemies = invisibleToEnemies; } @Override - public void doRender(EntityMagicConstruct entity, double par2, double par4, double par6, float par8, float par9){ + public void doRender(EntityMagicConstruct entity, double x, double y, double z, float entityYaw, float partialTicks){ // Makes the sigil invisible to enemies of the player that created it if(this.invisibleToEnemies){ // 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)){ + if(entity.getCaster() != Minecraft.getMinecraft().player && entity.getCaster() instanceof EntityPlayer + && !AllyDesignationSystem.isPlayerAlly((EntityPlayer)entity.getCaster(), Minecraft.getMinecraft().player)){ return; } } @@ -48,7 +48,7 @@ public class RenderSigil extends Render { float yOffset = 0; - GlStateManager.translate((float)par2, (float)par4 + yOffset, (float)par6); + GlStateManager.translate((float)x, (float)y + yOffset, (float)z); this.bindTexture(texture); float f6 = 1.0F; @@ -58,19 +58,19 @@ public class RenderSigil extends Render { GlStateManager.rotate(-90, 1, 0, 0); // Healing aura rotates slowly - if(entity instanceof EntityHealAura) GlStateManager.rotate(entity.ticksExisted / 3.0f, 0, 0, 1); + if(rotationSpeed != 0) GlStateManager.rotate(entity.ticksExisted * rotationSpeed, 0, 0, 1); - GlStateManager.scale(scale, scale, scale); + float s = entity.width * DrawingUtils.smoothScaleFactor(entity.lifetime, entity.ticksExisted, partialTicks, 10, 10); + GlStateManager.scale(s, s, s); Tessellator tessellator = Tessellator.getInstance(); BufferBuilder buffer = tessellator.getBuffer(); buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - // tessellator.setColorRGBA_I(k1, 128); - // buffer.normal(0.0F, 1.0F, 0.0F); - 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(); + + buffer.pos(0.0F - f7, 0.0F - f8, 0.01).tex(0, 1).endVertex(); + buffer.pos(f6 - f7, 0.0F - f8, 0.01).tex(1, 1).endVertex(); + buffer.pos(f6 - f7, 1.0F - f8, 0.01).tex(1, 0).endVertex(); + buffer.pos(0.0F - f7, 1.0F - f8, 0.01).tex(0, 0).endVertex(); tessellator.draw(); diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderSpiritHorse.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderSpiritHorse.java similarity index 97% rename from src/main/java/electroblob/wizardry/client/renderer/RenderSpiritHorse.java rename to src/main/java/electroblob/wizardry/client/renderer/entity/RenderSpiritHorse.java index 79cd0ab7..24632ada 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderSpiritHorse.java +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderSpiritHorse.java @@ -1,4 +1,4 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.entity; import electroblob.wizardry.Wizardry; import electroblob.wizardry.entity.living.EntitySpiritHorse; diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderSpiritWolf.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderSpiritWolf.java similarity index 97% rename from src/main/java/electroblob/wizardry/client/renderer/RenderSpiritWolf.java rename to src/main/java/electroblob/wizardry/client/renderer/entity/RenderSpiritWolf.java index 0564f69b..0d732f81 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderSpiritWolf.java +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderSpiritWolf.java @@ -1,4 +1,4 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.entity; import electroblob.wizardry.Wizardry; import electroblob.wizardry.entity.living.EntitySpiritWolf; diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderStrayMinion.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderStrayMinion.java similarity index 86% rename from src/main/java/electroblob/wizardry/client/renderer/RenderStrayMinion.java rename to src/main/java/electroblob/wizardry/client/renderer/entity/RenderStrayMinion.java index 815d4979..eea464f0 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderStrayMinion.java +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderStrayMinion.java @@ -1,5 +1,6 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.entity; +import electroblob.wizardry.client.renderer.entity.layers.LayerStrayMinionClothing; import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.client.renderer.entity.RenderSkeleton; import net.minecraft.entity.monster.AbstractSkeleton; diff --git a/src/main/java/electroblob/wizardry/client/renderer/entity/RenderWitheringTotem.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderWitheringTotem.java new file mode 100644 index 00000000..b5398be0 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderWitheringTotem.java @@ -0,0 +1,159 @@ +package electroblob.wizardry.client.renderer.entity; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.DrawingUtils; +import electroblob.wizardry.entity.construct.EntityWitheringTotem; +import electroblob.wizardry.util.GeometryUtils; +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.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.Vec3d; +import org.lwjgl.opengl.GL11; + +import javax.annotation.Nullable; + +public class RenderWitheringTotem extends Render { + + private static final ResourceLocation FLARE_TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/entity/totem/flare.png"); + private static final ResourceLocation[] CUBE_TEXTURES = new ResourceLocation[14]; + + static { + for(int i = 0; i< CUBE_TEXTURES.length; i++) CUBE_TEXTURES[i] = new ResourceLocation(Wizardry.MODID, "textures/entity/totem/cube_" + i + ".png"); + } + + public RenderWitheringTotem(RenderManager manager){ + super(manager); + } + + @Nullable + @Override + protected ResourceLocation getEntityTexture(EntityWitheringTotem entity){ + return CUBE_TEXTURES[entity.ticksExisted % CUBE_TEXTURES.length]; + } + + @Override + public void doRender(EntityWitheringTotem entity, double x, double y, double z, float entityYaw, float partialTicks){ + + Tessellator tessellator = Tessellator.getInstance(); + + GlStateManager.pushMatrix(); + + GlStateManager.enableBlend(); + GlStateManager.disableLighting(); + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240, 240); + GlStateManager.depthMask(false); + GlStateManager.disableCull(); + + GlStateManager.translate(x, y + entity.height/2, z); + + float charge = entity.getHealthDrained() / 50f; + + float s = DrawingUtils.smoothScaleFactor(entity.lifetime, entity.ticksExisted, partialTicks, 10, 10); + s *= 1 + charge * 0.3f; // Gets bigger the more health it drains + GlStateManager.scale(s, s, s); + + drawFlare(charge, tessellator); + drawCube(entity, tessellator, partialTicks); + + GlStateManager.disableBlend(); + GlStateManager.enableLighting(); + GlStateManager.depthMask(true); + GlStateManager.enableCull(); + + GlStateManager.popMatrix(); + } + + private void drawFlare(float redness, Tessellator tessellator){ + + BufferBuilder buffer = tessellator.getBuffer(); + + GlStateManager.pushMatrix(); + + int c = DrawingUtils.mix(0xb333e6, 0xff0044, redness); + int r = c >> 16 & 255; + int g = c >> 8 & 255; + int b = c & 255; + + // Makes the 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); + GlStateManager.color(r/255f, g/255f, b/255f); // Gets redder the more health it drains + GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + + bindTexture(FLARE_TEXTURE); + + // 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 = Minecraft.getMinecraft().gameSettings.thirdPersonView == 2 + ? Minecraft.getMinecraft().getRenderManager().playerViewX + : -Minecraft.getMinecraft().getRenderManager().playerViewX; + GlStateManager.rotate(180.0F - Minecraft.getMinecraft().getRenderManager().playerViewY, 0.0F, 1.0F, 0.0F); + GlStateManager.rotate(yaw, 1.0F, 0.0F, 0.0F); + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + + float radius = 0.5f; + + buffer.pos(-radius, radius, 0).tex(0, 0).endVertex(); + buffer.pos( radius, radius, 0).tex(1, 0).endVertex(); + buffer.pos( radius, -radius, 0).tex(1, 1).endVertex(); + buffer.pos(-radius, -radius, 0).tex(0, 1).endVertex(); + + tessellator.draw(); + + // Reverses the colour addition change + GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE); + GlStateManager.color(1, 1, 1, 1); + GlStateManager.popMatrix(); + } + + private void drawCube(EntityWitheringTotem entity, Tessellator tessellator, float partialTicks){ + + BufferBuilder buffer = tessellator.getBuffer(); + + GlStateManager.pushMatrix(); + + float age = entity.ticksExisted + partialTicks; + float rotationSpeed = 2; + + GlStateManager.rotate(age * rotationSpeed/2, 0.0F, 1.0F, 0.0F); + GlStateManager.rotate(60.0F, 0.7071F, 0.0F, 0.7071F); + GlStateManager.rotate(age * rotationSpeed, 0.0F, 1.0F, 0.0F); + + GlStateManager.scale(0.5, 0.5, 0.5); + + GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE); + + bindEntityTexture(entity); + + Vec3d[] vertices = GeometryUtils.getVertices(entity.getEntityBoundingBox().offset(entity.getPositionVector() + .add(0, entity.height/2, 0).scale(-1))); + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + + // Outside + drawFace(buffer, vertices[0], vertices[1], vertices[3], vertices[2], 0.5f, 0, 0.75f, 0.5f); // Bottom + drawFace(buffer, vertices[6], vertices[7], vertices[2], vertices[3], 0.75f, 0.5f, 1, 1); // South + drawFace(buffer, vertices[5], vertices[6], vertices[1], vertices[2], 0, 0.5f, 0.25f, 1); // East + drawFace(buffer, vertices[4], vertices[5], vertices[0], vertices[1], 0.25f, 0.5f, 0.5f, 1); // North + drawFace(buffer, vertices[7], vertices[4], vertices[3], vertices[0], 0.5f, 0.5f, 0.75f, 1); // West + drawFace(buffer, vertices[5], vertices[4], vertices[6], vertices[7], 0.25f, 0, 0.5f, 0.5f); // Top + + tessellator.draw(); + + GlStateManager.popMatrix(); + } + + private static void drawFace(BufferBuilder buffer, Vec3d topLeft, Vec3d topRight, Vec3d bottomLeft, Vec3d bottomRight, float u1, float v1, float u2, float v2){ + buffer.pos(topLeft.x, topLeft.y, topLeft.z).tex(u1, v1).endVertex(); + buffer.pos(topRight.x, topRight.y, topRight.z).tex(u2, v1).endVertex(); + buffer.pos(bottomRight.x, bottomRight.y, bottomRight.z).tex(u2, v2).endVertex(); + buffer.pos(bottomLeft.x, bottomLeft.y, bottomLeft.z).tex(u1, v2).endVertex(); + } + +} diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderWizard.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderWizard.java similarity index 76% rename from src/main/java/electroblob/wizardry/client/renderer/RenderWizard.java rename to src/main/java/electroblob/wizardry/client/renderer/entity/RenderWizard.java index 566bc5a7..3c6e0d3b 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderWizard.java +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderWizard.java @@ -1,4 +1,4 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.entity; import electroblob.wizardry.Wizardry; import electroblob.wizardry.client.model.ModelWizard; @@ -11,14 +11,14 @@ import net.minecraft.util.ResourceLocation; //@SideOnly(Side.CLIENT) public class RenderWizard extends RenderBiped { - static final ResourceLocation[] textures = new ResourceLocation[6]; + static final ResourceLocation[] TEXTURES = new ResourceLocation[6]; public RenderWizard(RenderManager renderManager){ super(renderManager, new ModelWizard(), 0.5F); for(int i = 0; i < 6; i++){ - textures[i] = new ResourceLocation(Wizardry.MODID, "textures/entity/wizard_" + i + ".png"); + TEXTURES[i] = new ResourceLocation(Wizardry.MODID, "textures/entity/wizard/wizard_" + i + ".png"); } // Just using the default without overriding models, since the armour sets its own model anyway. this.addLayer(new LayerBipedArmor(this)); @@ -26,7 +26,7 @@ public class RenderWizard extends RenderBiped { @Override protected ResourceLocation getEntityTexture(EntityWizard wizard){ - return textures[wizard.textureIndex]; + return TEXTURES[wizard.textureIndex]; } } diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderWraithMinion.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderWraithMinion.java similarity index 92% rename from src/main/java/electroblob/wizardry/client/renderer/RenderWraithMinion.java rename to src/main/java/electroblob/wizardry/client/renderer/entity/RenderWraithMinion.java index d8cfe382..a7629475 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderWraithMinion.java +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderWraithMinion.java @@ -1,4 +1,4 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.entity; import electroblob.wizardry.entity.living.EntityBlazeMinion; import net.minecraft.client.model.ModelBlaze; diff --git a/src/main/java/electroblob/wizardry/client/renderer/entity/RenderZombieSpawner.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderZombieSpawner.java new file mode 100644 index 00000000..28d543ae --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderZombieSpawner.java @@ -0,0 +1,115 @@ +package electroblob.wizardry.client.renderer.entity; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.DrawingUtils; +import electroblob.wizardry.entity.construct.EntityZombieSpawner; +import electroblob.wizardry.util.GeometryUtils; +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.AxisAlignedBB; +import net.minecraft.util.math.Vec3d; +import org.lwjgl.opengl.GL11; + +public class RenderZombieSpawner extends Render { + + private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/entity/zombie_spawner.png"); + + private static final Vec3d[] HIDDEN_BOX = GeometryUtils.getVertices(new AxisAlignedBB(-1, 0, -1, 1, 2.5, 1)); + + public RenderZombieSpawner(RenderManager renderManager){ + super(renderManager); + } + + @Override + public void doRender(EntityZombieSpawner entity, double x, double y, double z, float yaw, float partialTicks){ + + GlStateManager.pushMatrix(); + GlStateManager.enableBlend(); + GlStateManager.disableLighting(); + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240, 240); + GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + + GlStateManager.translate(x, y, z); + + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder buffer = tessellator.getBuffer(); + + // The visible bit + GlStateManager.pushMatrix(); + + float s = DrawingUtils.smoothScaleFactor(entity.lifetime, entity.ticksExisted, partialTicks, 10, 10); + GlStateManager.scale(s, s, s); + + GlStateManager.rotate((entity.ticksExisted + partialTicks) * 2, 0, 1, 0); + + this.bindTexture(TEXTURE); + + Vec3d[] vertices = GeometryUtils.getVertices(entity.getEntityBoundingBox().offset(entity.getPositionVector().scale(-1))); + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + drawFace(buffer, vertices[0], vertices[1], vertices[3], vertices[2], 0, 0, 1, 1); // Bottom + drawFace(buffer, vertices[1], vertices[0], vertices[2], vertices[3], 0, 0, 1, 1); // Top + tessellator.draw(); + + GlStateManager.popMatrix(); + + // Hidden box + GlStateManager.disableTexture2D(); + GlStateManager.colorMask(false, false, false, false); // Magically hide the zombies + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR); + + // Outside (hides everything) + drawFaceColour(buffer, HIDDEN_BOX[6], HIDDEN_BOX[7], HIDDEN_BOX[2], HIDDEN_BOX[3], 0, 0, 0, 1); // South + drawFaceColour(buffer, HIDDEN_BOX[5], HIDDEN_BOX[6], HIDDEN_BOX[1], HIDDEN_BOX[2], 0, 0, 0, 1); // East + drawFaceColour(buffer, HIDDEN_BOX[4], HIDDEN_BOX[5], HIDDEN_BOX[0], HIDDEN_BOX[1], 0, 0, 0, 1); // North + drawFaceColour(buffer, HIDDEN_BOX[7], HIDDEN_BOX[4], HIDDEN_BOX[3], HIDDEN_BOX[0], 0, 0, 0, 1); // West + drawFaceColour(buffer, HIDDEN_BOX[5], HIDDEN_BOX[4], HIDDEN_BOX[6], HIDDEN_BOX[7], 0, 0, 0, 1); // Top + + tessellator.draw(); + + GlStateManager.disableBlend(); + GlStateManager.enableLighting(); + GlStateManager.disableRescaleNormal(); + GlStateManager.enableTexture2D(); + GlStateManager.colorMask(true, true, true, true); + GlStateManager.popMatrix(); + } + + @Override + public void renderMultipass(EntityZombieSpawner entity, double x, double y, double z, float yaw, float partialTicks){ + + + } + + @Override + public boolean isMultipass(){ + return false; + } + + @Override + protected ResourceLocation getEntityTexture(EntityZombieSpawner entity){ + return TEXTURE; + } + + private static void drawFace(BufferBuilder buffer, Vec3d topLeft, Vec3d topRight, Vec3d bottomLeft, Vec3d bottomRight, float u1, float v1, float u2, float v2){ + buffer.pos(topLeft.x, topLeft.y, topLeft.z).tex(u1, v1).endVertex(); + buffer.pos(topRight.x, topRight.y, topRight.z).tex(u2, v1).endVertex(); + buffer.pos(bottomRight.x, bottomRight.y, bottomRight.z).tex(u2, v2).endVertex(); + buffer.pos(bottomLeft.x, bottomLeft.y, bottomLeft.z).tex(u1, v2).endVertex(); + } + + private static void drawFaceColour(BufferBuilder buffer, Vec3d topLeft, Vec3d topRight, Vec3d bottomLeft, Vec3d bottomRight, float r, float g, float b, float a){ + buffer.pos(topLeft.x, topLeft.y, topLeft.z).color(r, g, b, a).endVertex(); + buffer.pos(topRight.x, topRight.y, topRight.z).color(r, g, b, a).endVertex(); + buffer.pos(bottomRight.x, bottomRight.y, bottomRight.z).color(r, g, b, a).endVertex(); + buffer.pos(bottomLeft.x, bottomLeft.y, bottomLeft.z).color(r, g, b, a).endVertex(); + } + +} diff --git a/src/main/java/electroblob/wizardry/client/renderer/entity/layers/LayerDisintegrateAnimation.java b/src/main/java/electroblob/wizardry/client/renderer/entity/layers/LayerDisintegrateAnimation.java new file mode 100644 index 00000000..ba99bc16 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/layers/LayerDisintegrateAnimation.java @@ -0,0 +1,71 @@ +package electroblob.wizardry.client.renderer.entity.layers; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.spell.Disintegration; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.renderer.entity.RenderLivingBase; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; +import net.minecraftforge.client.event.RenderLivingEvent; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.relauncher.Side; + +/** + * Layer used to render the appear/disappear animation for summoned creatures. + * + * @author Electroblob + * @since Wizardry 4.3 + */ +@EventBusSubscriber(Side.CLIENT) +public class LayerDisintegrateAnimation extends LayerTiledOverlay { + + private static final int ANIMATION_TICKS = 19; // One less than the max death time + + private static final ResourceLocation[] TEXTURES = new ResourceLocation[ANIMATION_TICKS]; + + static { + for(int i=0; i renderer){ + super(renderer, 32, 32); + } + + @Override + public boolean shouldRender(T entity, float partialTicks){ + return entity.getEntityData().hasKey(Disintegration.NBT_KEY); + } + + @Override + public ResourceLocation getTexture(T entity, float partialTicks){ + return TEXTURES[MathHelper.clamp(entity.ticksExisted - entity.getEntityData().getInteger(Disintegration.NBT_KEY), 0, ANIMATION_TICKS - 1)]; + } + + @Override + public void doRenderLayer(T entity, float limbSwing, float limbSwingAmount, float partialTicks, + float ageInTicks, float netHeadYaw, float headPitch, float scale){ + GlStateManager.disableLighting(); + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); + super.doRenderLayer(entity, limbSwing, limbSwingAmount, partialTicks, ageInTicks, netHeadYaw, headPitch, scale); + GlStateManager.enableLighting(); + } + + @SubscribeEvent + public static void onRenderLivingEvent(RenderLivingEvent.Pre event){ + if(event.getEntity().getEntityData().hasKey(Disintegration.NBT_KEY)){ + event.getEntity().deathTime = 0; + event.getEntity().setInvisible(true); + } + } + + @SubscribeEvent + public static void onRenderLivingEvent(RenderLivingEvent.Post event){ + if(event.getEntity().getEntityData().hasKey(Disintegration.NBT_KEY)) event.getEntity().setInvisible(false); + } + +} diff --git a/src/main/java/electroblob/wizardry/client/renderer/entity/layers/LayerFrost.java b/src/main/java/electroblob/wizardry/client/renderer/entity/layers/LayerFrost.java new file mode 100644 index 00000000..9e93a8af --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/layers/LayerFrost.java @@ -0,0 +1,49 @@ +package electroblob.wizardry.client.renderer.entity.layers; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.block.BlockStatue; +import electroblob.wizardry.registry.WizardryPotions; +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.entity.RenderLivingBase; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.util.ResourceLocation; + +/** + * Layer used to render the frost texture on creatures with the frostbite effect. + * + * @author Electroblob + * @since Wizardry 1.2 + */ +public class LayerFrost extends LayerTiledOverlay { + + private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/entity/frost_overlay.png"); + + public LayerFrost(RenderLivingBase renderer){ + super(renderer); + } + + @Override + public boolean shouldRender(EntityLivingBase entity, float partialTicks){ + return !entity.isInvisible() && entity.isPotionActive(WizardryPotions.frost) || entity.getEntityData().getBoolean(BlockStatue.FROZEN_NBT_KEY); + } + + @Override + public ResourceLocation getTexture(EntityLivingBase entity, float partialTicks){ + return TEXTURE; + } + + @Override + public void doRenderLayer(EntityLivingBase entity, float limbSwing, float limbSwingAmount, float partialTicks, + float ageInTicks, float netHeadYaw, float headPitch, float scale){ + + GlStateManager.enableBlend(); + GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA); + + super.doRenderLayer(entity, limbSwing, limbSwingAmount, partialTicks, ageInTicks, netHeadYaw, headPitch, scale); + + GlStateManager.disableBlend(); + } + +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/client/renderer/entity/layers/LayerMindControl.java b/src/main/java/electroblob/wizardry/client/renderer/entity/layers/LayerMindControl.java new file mode 100644 index 00000000..3fbdbe3f --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/layers/LayerMindControl.java @@ -0,0 +1,60 @@ +package electroblob.wizardry.client.renderer.entity.layers; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.spell.Possession; +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.entity.RenderLivingBase; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.potion.PotionEffect; +import net.minecraft.util.ResourceLocation; + +/** + * Layer used to render the mind control overlay on creatures with the mind control effect. + * + * @author Electroblob + * @since Wizardry 4.3 + */ +public class LayerMindControl extends LayerTiledOverlay { + + private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/entity/mind_control_overlay.png"); + + public LayerMindControl(RenderLivingBase renderer){ + super(renderer); + } + + @Override + public boolean shouldRender(EntityLivingBase entity, float partialTicks){ + return !entity.isInvisible() && (entity.isPotionActive(WizardryPotions.mind_control) || entity.getEntityData().getBoolean(Possession.NBT_KEY)); + } + + @Override + public ResourceLocation getTexture(EntityLivingBase entity, float partialTicks){ + return TEXTURE; + } + + @Override + public void doRenderLayer(EntityLivingBase entity, float limbSwing, float limbSwingAmount, float partialTicks, + float ageInTicks, float netHeadYaw, float headPitch, float scale){ + + GlStateManager.enableBlend(); + GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE); + + PotionEffect effect = entity.getActivePotionEffect(WizardryPotions.mind_control); + if(effect != null){ + GlStateManager.color(1, 1, 1, Math.min(1, (effect.getDuration() - partialTicks) / 20)); + } + + super.doRenderLayer(entity, limbSwing, limbSwingAmount, partialTicks, ageInTicks, netHeadYaw, headPitch, scale); + + GlStateManager.disableBlend(); + } + + @Override + protected void applyTextureSpaceTransformations(EntityLivingBase entity, float partialTicks){ + float f = entity.ticksExisted + partialTicks; + GlStateManager.translate(f * 0.003f, f * 0.003f, 0); + } +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/client/renderer/entity/layers/LayerStone.java b/src/main/java/electroblob/wizardry/client/renderer/entity/layers/LayerStone.java new file mode 100644 index 00000000..ba188ef9 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/layers/LayerStone.java @@ -0,0 +1,58 @@ +package electroblob.wizardry.client.renderer.entity.layers; + +import electroblob.wizardry.block.BlockStatue; +import electroblob.wizardry.client.ClientProxy; +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.entity.RenderLivingBase; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.util.ResourceLocation; + +/** + * Layer used to render the stone texture on a petrified creature. + * + * @author Electroblob + * @since Wizardry 1.2 + */ +// N.B. The rule here is don't restrict the type any more than necessary, so even though we *know* petrified creatures +// are always instances of EntityLiving, we don't need any methods/fields specific to EntityLiving so use ELB instead. +public class LayerStone extends LayerTiledOverlay { + + private static final ResourceLocation TEXTURE = new ResourceLocation("textures/blocks/stone.png"); + + public LayerStone(RenderLivingBase renderer){ + super(renderer); + } + + @Override + public boolean shouldRender(EntityLivingBase entity, float partialTicks){ + return entity.getEntityData().getBoolean(BlockStatue.PETRIFIED_NBT_KEY); + } + + @Override + public ResourceLocation getTexture(EntityLivingBase entity, float partialTicks){ + ResourceLocation breakingTexture = ClientProxy.renderStatue.getBlockBreakingTexture(); + return breakingTexture == null ? TEXTURE : breakingTexture; + } + + @Override + public void doRenderLayer(EntityLivingBase entity, float limbSwing, float limbSwingAmount, float partialTicks, + float ageInTicks, float netHeadYaw, float headPitch, float scale){ + + // N.B. It's one or the other because this effectively gets called twice via RenderStatue + if(ClientProxy.renderStatue.getBlockBreakingTexture() != null){ + + GlStateManager.enableBlend(); + GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA); + + super.doRenderLayer(entity, limbSwing, limbSwingAmount, partialTicks, ageInTicks, netHeadYaw, headPitch, scale); + + GlStateManager.disableBlend(); + + }else{ + super.doRenderLayer(entity, limbSwing, limbSwingAmount, partialTicks, ageInTicks, netHeadYaw, headPitch, scale); + } + } + +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/client/renderer/LayerStrayMinionClothing.java b/src/main/java/electroblob/wizardry/client/renderer/entity/layers/LayerStrayMinionClothing.java similarity index 96% rename from src/main/java/electroblob/wizardry/client/renderer/LayerStrayMinionClothing.java rename to src/main/java/electroblob/wizardry/client/renderer/entity/layers/LayerStrayMinionClothing.java index 00496ecb..aa38886a 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/LayerStrayMinionClothing.java +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/layers/LayerStrayMinionClothing.java @@ -1,4 +1,4 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.entity.layers; import electroblob.wizardry.entity.living.EntityStrayMinion; import net.minecraft.client.model.ModelSkeleton; diff --git a/src/main/java/electroblob/wizardry/client/renderer/entity/layers/LayerSummonAnimation.java b/src/main/java/electroblob/wizardry/client/renderer/entity/layers/LayerSummonAnimation.java new file mode 100644 index 00000000..49e5fc2e --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/layers/LayerSummonAnimation.java @@ -0,0 +1,89 @@ +package electroblob.wizardry.client.renderer.entity.layers; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.entity.living.ISummonedCreature; +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.entity.RenderLivingBase; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.event.RenderLivingEvent; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.relauncher.Side; + +/** + * Layer used to render the appear/disappear animation for summoned creatures. + * + * @author Electroblob + * @since Wizardry 4.3 + */ +@EventBusSubscriber(Side.CLIENT) +public class LayerSummonAnimation extends LayerTiledOverlay { + + private static final int ANIMATION_TICKS = 19; + private static final int HIDE_MODEL_TICKS = 9; + + private static final ResourceLocation[] TEXTURES = new ResourceLocation[ANIMATION_TICKS]; + + static { + for(int i=0; i renderer){ + super(renderer, 32, 32); + } + + @Override + public boolean shouldRender(T entity, float partialTicks){ + return entity instanceof ISummonedCreature && ((ISummonedCreature)entity).hasAnimation() + && ((ISummonedCreature)entity).getLifetime() > 0 && getFrameNumber(entity) < ANIMATION_TICKS; + } + + @Override + public ResourceLocation getTexture(T entity, float partialTicks){ + return TEXTURES[getFrameNumber(entity)]; + } + + private int getFrameNumber(T entity){ + if(!(entity instanceof ISummonedCreature)) throw new IllegalArgumentException("Entity must be an ISummonedCreature"); + return Math.min(entity.ticksExisted, Math.max(((ISummonedCreature)entity).getLifetime() - entity.ticksExisted - 1, 0)); + } + + @Override + public void doRenderLayer(T entity, float limbSwing, float limbSwingAmount, float partialTicks, + float ageInTicks, float netHeadYaw, float headPitch, float scale){ + + if(!(entity instanceof ISummonedCreature)) return; + + GlStateManager.enableBlend(); + GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA); + + int colour = ((ISummonedCreature)entity).getAnimationColour((float)getFrameNumber(entity) / ANIMATION_TICKS); + GlStateManager.color((colour >> 16 & 255) / 255f, (colour >> 8 & 255) / 255f, (colour & 255) / 255f); + + super.doRenderLayer(entity, limbSwing, limbSwingAmount, partialTicks, ageInTicks, netHeadYaw, headPitch, scale); + + GlStateManager.disableBlend(); + } + + @SubscribeEvent + public static void onRenderLivingEvent(RenderLivingEvent.Pre event){ + if(shouldHideMainModel(event.getEntity())) event.getEntity().setInvisible(true); + } + + @SubscribeEvent + public static void onRenderLivingEvent(RenderLivingEvent.Post event){ + if(shouldHideMainModel(event.getEntity())) event.getEntity().setInvisible(false); + } + + private static boolean shouldHideMainModel(EntityLivingBase entity){ + return entity instanceof ISummonedCreature && ((ISummonedCreature)entity).hasAnimation() + && ((ISummonedCreature)entity).getLifetime() > 0 && (entity.ticksExisted < HIDE_MODEL_TICKS + || ((ISummonedCreature)entity).getLifetime() - entity.ticksExisted < HIDE_MODEL_TICKS); + } + +} diff --git a/src/main/java/electroblob/wizardry/client/renderer/entity/layers/LayerTiledOverlay.java b/src/main/java/electroblob/wizardry/client/renderer/entity/layers/LayerTiledOverlay.java new file mode 100644 index 00000000..04f848a7 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/layers/LayerTiledOverlay.java @@ -0,0 +1,253 @@ +package electroblob.wizardry.client.renderer.entity.layers; + +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.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.entity.player.EntityPlayer; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import org.lwjgl.opengl.GL11; + +import java.util.function.Function; + +/** + * Base class for layers that overlay a tiled texture onto the entity model. Handles dynamic tiling of the texture, + * scaling to preserve in-world pixel size (1/16 of a block), and the necessary GL state changes. + * + * @author Electroblob + * @since Wizardry 4.3 + */ +public abstract class LayerTiledOverlay implements LayerRenderer { + + private final RenderLivingBase renderer; + + private final int textureWidth; + private final int textureHeight; + + /** Creates a new {@code LayerTiledOverlay} with the given renderer parameters. */ + public LayerTiledOverlay(RenderLivingBase renderer, int textureWidth, int textureHeight){ + this.textureWidth = textureWidth; + this.textureHeight = textureHeight; + this.renderer = renderer; + } + + /** Creates a new {@code LayerTiledOverlay} with the given renderer. Texture width and height default to 16. */ + public LayerTiledOverlay(RenderLivingBase renderer){ + this(renderer, 16, 16); + } + + /** + * Returns true if this layer should be rendered for the given entity, false if not. + * @param entity The entity being rendered + * @param partialTicks The current partial tick time + * @return True if this layer should be rendered, false if not. + */ + public abstract boolean shouldRender(T entity, float partialTicks); + + /** + * Returns the texture to use for the overlay. + * @param entity The entity being rendered + * @param partialTicks The current partial tick time + * @return A {@link ResourceLocation} specifying the texture to use. + */ + public abstract ResourceLocation getTexture(T entity, float partialTicks); + + /** + * Returns the model to use for the overlay. Defaults to the renderer's main model. + * @param entity The entity being rendered + * @param partialTicks The current partial tick time + * @return The model to use. Swapping different models in dynamically is fine, but don't create new ones each time. + */ + public ModelBase getModel(T entity, float partialTicks){ + return renderer.getMainModel(); + } + + /** + * Returns whether to render the second layer of mob/player skins (which is not an actual render layer, but + * part of the model) as part of this layer. Defaults to false. + * @param entity The entity being rendered + * @param partialTicks The current partial tick time + * @return True to render any non-hidden second layer boxes, false to always hide them from this layer. + */ + public boolean renderSecondLayer(T entity, float partialTicks){ + return false; // Normally it looks kind of weird because second layers typically have holes in them + } + + /** + * Allows subclasses to perform additional transformations in the texture space before the model is rendered. This + * can be used for a creeper-charge-like effect. To do this, simply override this method and call the appropriate + * transformation method(s) (translate, rotate, scale) in {@link GlStateManager}. This method does nothing by + * default. + * @param entity The entity being rendered + * @param partialTicks The current partial tick time + */ + protected void applyTextureSpaceTransformations(T entity, float partialTicks){} + + @Override + public void doRenderLayer(T entity, float limbSwing, float limbSwingAmount, float partialTicks, + float ageInTicks, float netHeadYaw, float headPitch, float scale){ + + if(shouldRender(entity, partialTicks)){ + this.renderer.bindTexture(getTexture(entity, partialTicks)); + this.renderEntityModel(entity, limbSwing, limbSwingAmount, partialTicks, ageInTicks, netHeadYaw, headPitch, scale); + } + } + + @Override + public boolean shouldCombineTextures(){ + return false; + } + + protected static int getBlockBrightnessForEntity(Entity entity){ + + 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(T entity, float limbSwing, float limbSwingAmount, float partialTicks, + float ageInTicks, float netHeadYaw, float headPitch, float scale){ + + GlStateManager.pushMatrix(); + // Enables texture tiling (Also used for guardian beam, beacon beam and ender crystal beam, amongst others) + 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. + // You can do all sorts of fun stuff with this, just by applying transformations in the 2D texture space. + GlStateManager.matrixMode(GL11.GL_TEXTURE); // Switch to the 2D texture space + GlStateManager.loadIdentity(); + + ModelBase model = getModel(entity, partialTicks); + + // Calculate the scaling required in each direction + 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 / textureWidth; + scaleY = (double)model.boxList.get(0).textureHeight / textureHeight; + }else{ // Fallback to model fields; should never be needed + scaleX = (double)model.textureWidth / textureWidth; + scaleY = (double)model.textureHeight / textureHeight; + } + + GlStateManager.scale(scaleX, scaleY, 1); + + applyTextureSpaceTransformations(entity, partialTicks); + + GlStateManager.matrixMode(GL11.GL_MODELVIEW); // Switch back to the 3D model space + + boolean headWearHidden = false; + boolean bodyWearHidden = false; + boolean leftArmWearHidden = false; + boolean rightArmWearHidden = false; + boolean leftLegWearHidden = false; + boolean rightLegWearHidden = false; + + if(!renderSecondLayer(entity, partialTicks)){ + // Hide the hat layer for bipeds, and all the other body part overlays for players + if(model instanceof ModelBiped){ + headWearHidden = ((ModelBiped)model).bipedHeadwear.isHidden; + ((ModelBiped)model).bipedHeadwear.isHidden = true; + } + + if(model instanceof ModelPlayer){ + bodyWearHidden = ((ModelPlayer)model).bipedBodyWear.isHidden; + leftArmWearHidden = ((ModelPlayer)model).bipedLeftArmwear.isHidden; + rightArmWearHidden = ((ModelPlayer)model).bipedRightArmwear.isHidden; + leftLegWearHidden = ((ModelPlayer)model).bipedLeftLegwear.isHidden; + rightLegWearHidden = ((ModelPlayer)model).bipedRightLegwear.isHidden; + ((ModelPlayer)model).bipedBodyWear.isHidden = true; + ((ModelPlayer)model).bipedLeftArmwear.isHidden = true; + ((ModelPlayer)model).bipedRightArmwear.isHidden = true; + ((ModelPlayer)model).bipedLeftLegwear.isHidden = true; + ((ModelPlayer)model).bipedRightLegwear.isHidden = true; + } + } + + // Actually render the model + model.setLivingAnimations(entity, limbSwing, limbSwingAmount, partialTicks); + model.render(entity, limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch, scale); + + if(!renderSecondLayer(entity, partialTicks)){ + // Reset the hidden status of everything to how it was before + if(model instanceof ModelBiped){ + ((ModelBiped)model).bipedHeadwear.isHidden = headWearHidden; + } + + if(model instanceof ModelPlayer){ + ((ModelPlayer)model).bipedBodyWear.isHidden = bodyWearHidden; + ((ModelPlayer)model).bipedLeftArmwear.isHidden = leftArmWearHidden; + ((ModelPlayer)model).bipedRightArmwear.isHidden = rightArmWearHidden; + ((ModelPlayer)model).bipedLeftLegwear.isHidden = leftLegWearHidden; + ((ModelPlayer)model).bipedRightLegwear.isHidden = rightLegWearHidden; + } + } + + // Undo the texture scaling + GlStateManager.matrixMode(GL11.GL_TEXTURE); + GlStateManager.loadIdentity(); + GlStateManager.matrixMode(GL11.GL_MODELVIEW); + + GlStateManager.popMatrix(); + } + + /** + * Helper method to initialise a layer renderer for all applicable renderers. This method will work for any layer + * renderer, not just instances of {@code LayerTiledOverlay} (it is stored here purely for convenience). + * @param entityType The class of entity to initialise this layer renderer for (including subclasses) + * @param layerFactory A function that creates a layer for a given renderer, usually a constructor reference + * @param The type of entity this layer renderer is applicable for + */ + @SuppressWarnings("unchecked") + public static void initialiseLayers(Class entityType, Function, LayerRenderer> layerFactory){ + + for(Class c : Minecraft.getMinecraft().getRenderManager().entityRenderMap.keySet()){ + if(entityType.isAssignableFrom(c)){ + Render renderer = Minecraft.getMinecraft().getRenderManager().getEntityClassRenderObject(c); + if(renderer instanceof RenderLivingBase){ // Should always be true + // For some reason IntelliJ is quite happy with this cast, even without suppress warnings + // Instinct suggests Java shouldn't be happy casting to RenderLivingBase but somehow it works + ((RenderLivingBase)renderer).addLayer(layerFactory.apply((RenderLivingBase)renderer)); + } + } + } + + // Players have a separate renderer map + if(entityType.isAssignableFrom(EntityPlayer.class)){ + for(RenderPlayer renderer : Minecraft.getMinecraft().getRenderManager().getSkinMap().values()){ + renderer.addLayer(layerFactory.apply((RenderLivingBase)renderer)); // This does need suppress warnings + } + } + } + + /** + * Helper method to initialise a layer renderer for all living entity renderers. This is a shorthand version of + * {@link LayerTiledOverlay#initialiseLayers(Class, Function)}. + * @param layerFactory A function that creates a layer for a given renderer, usually a constructor reference + */ + public static void initialiseLayers(Function, LayerRenderer> layerFactory){ + initialiseLayers(EntityLivingBase.class, layerFactory); + } + +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/client/renderer/overlay/RenderBlinkEffect.java b/src/main/java/electroblob/wizardry/client/renderer/overlay/RenderBlinkEffect.java new file mode 100644 index 00000000..e0f6c8a7 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/overlay/RenderBlinkEffect.java @@ -0,0 +1,77 @@ +package electroblob.wizardry.client.renderer.overlay; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.WizardryClientEventHandler; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.event.FOVUpdateEvent; +import net.minecraftforge.client.event.RenderGameOverlayEvent; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import net.minecraftforge.fml.relauncher.Side; +import org.lwjgl.opengl.GL11; + +@EventBusSubscriber(Side.CLIENT) +public class RenderBlinkEffect { + + /** 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 ResourceLocation SCREEN_OVERLAY_TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/gui/blink_overlay.png"); + + /** Starts the first-person blink overlay effect. */ + public static void playBlinkEffect(){ + if(Wizardry.settings.blinkEffect) blinkEffectTimer = BLINK_EFFECT_DURATION; + } + + @SubscribeEvent + public static void onPlayerTickEvent(TickEvent.PlayerTickEvent event){ + + if(event.player == Minecraft.getMinecraft().player && event.phase == TickEvent.Phase.END){ + + if(Wizardry.settings.blinkEffect){ + if(blinkEffectTimer > 0) blinkEffectTimer--; + }else{ + blinkEffectTimer = 0; + } + } + } + + @SubscribeEvent + public static void onFOVUpdateEvent(FOVUpdateEvent event){ + + 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 onRenderGameOverlayEvent(RenderGameOverlayEvent.Post event){ + + if(event.getType() == RenderGameOverlayEvent.ElementType.HELMET){ + + 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(); + + WizardryClientEventHandler.renderScreenOverlay(event.getResolution(), SCREEN_OVERLAY_TEXTURE); + + GlStateManager.enableAlpha(); + GlStateManager.color(1, 1, 1, 1); + } + } + } + +} diff --git a/src/main/java/electroblob/wizardry/client/renderer/overlay/RenderFrostOverlay.java b/src/main/java/electroblob/wizardry/client/renderer/overlay/RenderFrostOverlay.java new file mode 100644 index 00000000..605e21e5 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/overlay/RenderFrostOverlay.java @@ -0,0 +1,40 @@ +package electroblob.wizardry.client.renderer.overlay; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.WizardryClientEventHandler; +import electroblob.wizardry.registry.WizardryPotions; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.event.RenderGameOverlayEvent; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.relauncher.Side; +import org.lwjgl.opengl.GL11; + +@EventBusSubscriber(Side.CLIENT) +public class RenderFrostOverlay { + + private static final ResourceLocation SCREEN_OVERLAY_TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/gui/frost_overlay.png"); + + @SubscribeEvent + public static void onRenderGameOverlayEvent(RenderGameOverlayEvent.Post event){ + + if(event.getType() == RenderGameOverlayEvent.ElementType.HELMET){ + + 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(); + + WizardryClientEventHandler.renderScreenOverlay(event.getResolution(), SCREEN_OVERLAY_TEXTURE); + + GlStateManager.enableAlpha(); + GlStateManager.color(1, 1, 1, 1); + } + } + } + +} diff --git a/src/main/java/electroblob/wizardry/client/renderer/overlay/RenderSixthSense.java b/src/main/java/electroblob/wizardry/client/renderer/overlay/RenderSixthSense.java new file mode 100644 index 00000000..f73dfb0f --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/overlay/RenderSixthSense.java @@ -0,0 +1,130 @@ +package electroblob.wizardry.client.renderer.overlay; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.WizardryClientEventHandler; +import electroblob.wizardry.constants.Constants; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.spell.Spell; +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.entity.RenderManager; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.item.EntityArmorStand; +import net.minecraft.entity.monster.IMob; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.event.RenderGameOverlayEvent; +import net.minecraftforge.client.event.RenderLivingEvent; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.relauncher.Side; +import org.lwjgl.opengl.GL11; + +@EventBusSubscriber(Side.CLIENT) +public class RenderSixthSense { + + private static final ResourceLocation MARKER_TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/gui/sixth_sense_marker.png"); + private static final ResourceLocation SCREEN_OVERLAY_TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/gui/sixth_sense_overlay.png"); + + private static final int PASSIVE_MOB_MARKER_COLOUR = 0xc6ff00; + private static final int HOSTILE_MOB_MARKER_COLOUR = 0x004a97; + private static final int PLAYER_MARKER_COLOUR = 0xffffff; + + @SubscribeEvent + public static void onRenderGameOverlayEvent(RenderGameOverlayEvent.Post event){ + + 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(); + + WizardryClientEventHandler.renderScreenOverlay(event.getResolution(), SCREEN_OVERLAY_TEXTURE); + + GlStateManager.enableAlpha(); + GlStateManager.color(1, 1, 1, 1); + } + } + } + + @SubscribeEvent + public static void onRenderLivingEvent(RenderLivingEvent.Post event){ + + Minecraft mc = Minecraft.getMinecraft(); + RenderManager renderManager = event.getRenderer().getRenderManager(); + + 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(); + BufferBuilder buffer = tessellator.getBuffer(); + + GlStateManager.pushMatrix(); + + GlStateManager.disableCull(); + GlStateManager.enableBlend(); + GlStateManager.disableLighting(); + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); + GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + // Disabling depth test allows it to be seen through everything. + GlStateManager.disableDepth(); + + GlStateManager.translate(event.getX(), event.getY() + event.getEntity().height * 0.6, 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); + + // Makes the 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); + + int colour = PASSIVE_MOB_MARKER_COLOUR; + + if(ItemArtefact.isArtefactActive(mc.player, WizardryItems.charm_sixth_sense)){ + if(event.getEntity() instanceof IMob) colour = HOSTILE_MOB_MARKER_COLOUR; + else if(event.getEntity() instanceof EntityPlayer) colour = PLAYER_MARKER_COLOUR; + } + + int r = colour >> 16 & 255; + int g = colour >> 8 & 255; + int b = colour & 255; + + GlStateManager.color(r/255f, g/255f, b/255f, 1); + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + + mc.renderEngine.bindTexture(MARKER_TEXTURE); + + buffer.pos(-0.6, 0.6, 0).tex(0, 0).endVertex(); + buffer.pos(0.6, 0.6, 0).tex(1, 0).endVertex(); + buffer.pos(0.6, -0.6, 0).tex(1, 1).endVertex(); + buffer.pos(-0.6, -0.6, 0).tex(0, 1).endVertex(); + + tessellator.draw(); + + GlStateManager.enableCull(); + GlStateManager.disableBlend(); + GlStateManager.enableLighting(); + GlStateManager.enableDepth(); + // Reverses the colour addition change from before + GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE); + + GlStateManager.popMatrix(); + } + + } + +} diff --git a/src/main/java/electroblob/wizardry/client/renderer/overlay/RenderTargetPointers.java b/src/main/java/electroblob/wizardry/client/renderer/overlay/RenderTargetPointers.java new file mode 100644 index 00000000..191f236a --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/overlay/RenderTargetPointers.java @@ -0,0 +1,134 @@ +package electroblob.wizardry.client.renderer.overlay; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.item.ISpellCastingItem; +import electroblob.wizardry.util.EntityUtils; +import electroblob.wizardry.util.RayTracer; +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.entity.RenderManager; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.RayTraceResult; +import net.minecraftforge.client.event.RenderLivingEvent; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.relauncher.Side; +import org.lwjgl.opengl.GL11; + +@EventBusSubscriber(Side.CLIENT) +public class RenderTargetPointers { + + private static final ResourceLocation POINTER_TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/gui/pointer.png"); + private static final ResourceLocation TARGET_POINTER_TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/gui/target_pointer.png"); + + @SubscribeEvent + public static void onRenderLivingEvent(RenderLivingEvent.Post event){ + + Minecraft mc = Minecraft.getMinecraft(); + WizardData data = WizardData.get(mc.player); + RenderManager renderManager = event.getRenderer().getRenderManager(); + + ItemStack wand = mc.player.getHeldItemMainhand(); + + if(!(wand.getItem() instanceof ISpellCastingItem)){ + wand = mc.player.getHeldItemOffhand(); + } + + // Target selection pointer + if(mc.player.isSneaking() && wand.getItem() instanceof ISpellCastingItem && EntityUtils.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(); + + 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.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); + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + + mc.renderEngine.bindTexture(TARGET_POINTER_TEXTURE); + + 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(); + + GlStateManager.enableCull(); + GlStateManager.enableLighting(); + GlStateManager.enableDepth(); + + GlStateManager.popMatrix(); + } + } + + // Summoned creature selection pointer + if(data != null && data.selectedMinion != null && data.selectedMinion.get() == event.getEntity()){ + + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder buffer = tessellator.getBuffer(); + + 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.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); + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + + mc.renderEngine.bindTexture(POINTER_TEXTURE); + + 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(); + + GlStateManager.enableCull(); + GlStateManager.enableLighting(); + GlStateManager.enableDepth(); + + GlStateManager.popMatrix(); + } + } + +} diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderTransportationUI.java b/src/main/java/electroblob/wizardry/client/renderer/overlay/RenderTransportationUI.java similarity index 88% rename from src/main/java/electroblob/wizardry/client/renderer/RenderTransportationUI.java rename to src/main/java/electroblob/wizardry/client/renderer/overlay/RenderTransportationUI.java index 599d168e..2be2861f 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderTransportationUI.java +++ b/src/main/java/electroblob/wizardry/client/renderer/overlay/RenderTransportationUI.java @@ -1,4 +1,4 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.overlay; import electroblob.wizardry.Wizardry; import electroblob.wizardry.data.WizardData; @@ -7,8 +7,8 @@ import electroblob.wizardry.item.ItemArtefact; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.spell.Transportation; +import electroblob.wizardry.util.GeometryUtils; 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; @@ -84,13 +84,13 @@ public class RenderTransportationUI { buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR); - Vec3d position = WizardryUtilities.getCentre(location.pos).subtract(origin); + Vec3d position = GeometryUtils.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 displayDist = Math.min(distance, distanceCap); double factor = displayDist/distance; GlStateManager.translate(position.x * factor, position.y * factor, position.z * factor); @@ -109,9 +109,9 @@ public class RenderTransportationUI { 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, 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(); @@ -157,10 +157,10 @@ public class RenderTransportationUI { 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(); + bufferbuilder.pos((-i - 1), (-1 + verticalShift), 0.0D).color(0.0F, 0.0F, 0.0F, 0.25F).endVertex(); + bufferbuilder.pos((-i - 1), ( 8 + verticalShift), 0.0D).color(0.0F, 0.0F, 0.0F, 0.25F).endVertex(); + bufferbuilder.pos(( i + 1), ( 8 + verticalShift), 0.0D).color(0.0F, 0.0F, 0.0F, 0.25F).endVertex(); + bufferbuilder.pos(( i + 1), (-1 + verticalShift), 0.0D).color(0.0F, 0.0F, 0.0F, 0.25F).endVertex(); tessellator.draw(); GlStateManager.enableTexture2D(); diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderArcaneLock.java b/src/main/java/electroblob/wizardry/client/renderer/tileentity/RenderArcaneLock.java similarity index 86% rename from src/main/java/electroblob/wizardry/client/renderer/RenderArcaneLock.java rename to src/main/java/electroblob/wizardry/client/renderer/tileentity/RenderArcaneLock.java index e35bdbcc..e62577cf 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderArcaneLock.java +++ b/src/main/java/electroblob/wizardry/client/renderer/tileentity/RenderArcaneLock.java @@ -1,8 +1,8 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.tileentity; import electroblob.wizardry.Wizardry; import electroblob.wizardry.spell.ArcaneLock; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.GeometryUtils; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.GlStateManager; @@ -23,11 +23,11 @@ import org.lwjgl.opengl.GL11; @Mod.EventBusSubscriber(Side.CLIENT) public class RenderArcaneLock { - private static final ResourceLocation[] textures = new ResourceLocation[8]; + private static final ResourceLocation[] TEXTURES = new ResourceLocation[8]; static { - for(int i=0; i { - private static final ResourceLocation runeTexture = new ResourceLocation(Wizardry.MODID, "textures/entity/rune.png"); + private static final ResourceLocation RUNE_TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/entity/arcane_workbench_rune.png"); public RenderArcaneWorkbench(){} @@ -56,7 +56,7 @@ public class RenderArcaneWorkbench extends TileEntitySpecialRenderer { + + public RenderImbuementAltar(){} + + @Override + public void render(TileEntityImbuementAltar tileentity, double x, double y, double z, float partialTicks, int destroyStage, float alpha){ + + GlStateManager.pushMatrix(); + + GlStateManager.translate((float)x + 0.5F, (float)y + 1.4F, (float)z + 0.5F); + GlStateManager.rotate(180, 0F, 0F, 1F); + + float t = Minecraft.getMinecraft().player.ticksExisted + partialTicks; + GlStateManager.translate(0, 0.05f * MathHelper.sin(t/15), 0); + + this.renderItem(tileentity, t); + this.renderRays(tileentity, partialTicks); + + GlStateManager.popMatrix(); + } + + private void renderItem(TileEntityImbuementAltar tileentity, float t){ + + ItemStack stack = tileentity.getStack(); + + if(!stack.isEmpty()){ + + GlStateManager.pushMatrix(); + + GlStateManager.rotate(180, 1, 0, 0); + GlStateManager.rotate(t, 0, 1, 0); + GlStateManager.scale(0.85F, 0.85F, 0.85F); + + Minecraft.getMinecraft().getRenderItem().renderItem(stack, TransformType.FIXED); + + GlStateManager.popMatrix(); + } + } + + private void renderRays(TileEntityImbuementAltar tileentity, float partialTicks){ + + float t = Minecraft.getMinecraft().player.ticksExisted + partialTicks; + + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder buffer = tessellator.getBuffer(); + Random random = new Random(tileentity.getPos().toLong()); // Use position to get a constant seed + + int[] colours = BlockReceptacle.PARTICLE_COLOURS.get(tileentity.getDisplayElement()); + + if(colours == null) return; // Shouldn't happen + + GlStateManager.disableCull(); + GlStateManager.enableBlend(); + GlStateManager.enableAlpha(); + GlStateManager.disableTexture2D(); + GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE); + GlStateManager.shadeModel(GL11.GL_SMOOTH); + GlStateManager.disableLighting(); + GlStateManager.depthMask(false); + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); + RenderHelper.disableStandardItemLighting(); + + int r1 = colours[1] >> 16 & 255; + int g1 = colours[1] >> 8 & 255; + int b1 = colours[1] & 255; + + int r2 = colours[2] >> 16 & 255; + int g2 = colours[2] >> 8 & 255; + int b2 = colours[2] & 255; + + for(int j = 0; j < 30; j++){ + + int m = random.nextInt(10); + int n = random.nextInt(10); + + int sliceAngle = 20 + m; + float scale = 0.5f; + + GlStateManager.pushMatrix(); + + float progress = Math.min(tileentity.getImbuementProgress() + partialTicks/141, 1); + float s = 1 - progress; + s = 1 - s*s; + GlStateManager.scale(s, s, s); + + // TODO: This needs optimising! We should easily be able to do this with a single draw() call + // Same for magic light and black hole, which don't need ray textures either! + GlStateManager.rotate(31 * m, 1, 0, 0); + GlStateManager.rotate(31 * n, 0, 0, 1); + + buffer.begin(5, DefaultVertexFormats.POSITION_COLOR); + + float fade = (Math.min(1, 1.9f - progress) - 0.9f) * 10; + + buffer.pos(0, 0, 0).color(r1, g1, b1, (int)(255 * fade)).endVertex(); + buffer.pos(0, 0, 0).color(r1, g1, b1, (int)(255 * fade)).endVertex(); + + double x1 = scale * MathHelper.sin((t + 40 * j) * ((float)Math.PI / 180)); + double z1 = scale * MathHelper.cos((t + 40 * j) * ((float)Math.PI / 180)); + + double x2 = scale * MathHelper.sin((t + 40 * j - sliceAngle) * ((float)Math.PI / 180)); + double z2 = scale * MathHelper.cos((t + 40 * j - sliceAngle) * ((float)Math.PI / 180)); + + buffer.pos(x1, 0, z1).color(r2, g2, b2, 0).endVertex(); + buffer.pos(x2, 0, z2).color(r2, g2, b2, 0).endVertex(); + + tessellator.draw(); + + GlStateManager.popMatrix(); + } + + GlStateManager.shadeModel(GL11.GL_FLAT); + GlStateManager.enableCull(); + GlStateManager.disableBlend(); + GlStateManager.disableAlpha(); + GlStateManager.depthMask(true); + GlStateManager.enableTexture2D(); + GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + GlStateManager.enableLighting(); + RenderHelper.enableStandardItemLighting(); + } + +} diff --git a/src/main/java/electroblob/wizardry/client/renderer/tileentity/RenderLectern.java b/src/main/java/electroblob/wizardry/client/renderer/tileentity/RenderLectern.java new file mode 100644 index 00000000..1f0db40d --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/tileentity/RenderLectern.java @@ -0,0 +1,53 @@ +package electroblob.wizardry.client.renderer.tileentity; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.tileentity.TileEntityLectern; +import net.minecraft.block.BlockHorizontal; +import net.minecraft.client.model.ModelBook; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; + +public class RenderLectern extends TileEntitySpecialRenderer { + + private static final ResourceLocation BOOK_TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/entity/lectern_book.png"); + private final ModelBook modelBook = new ModelBook(); + + @Override + public void render(TileEntityLectern te, double x, double y, double z, float partialTicks, int destroyStage, float alpha){ + + GlStateManager.pushMatrix(); + GlStateManager.translate((float)x + 0.5F, (float)y + 1, (float)z + 0.5F); + GlStateManager.rotate(90 - te.getWorld().getBlockState(te.getPos()).getValue(BlockHorizontal.FACING).getHorizontalAngle(), 0, 1, 0); + + float time = (float)te.ticksExisted + partialTicks; + + float spread = te.bookSpreadPrev + (te.bookSpread - te.bookSpreadPrev) * partialTicks; + + GlStateManager.translate(0, 0.12, 0); + if(spread > 0.3) GlStateManager.translate(0, MathHelper.sin(time * 0.1F) * 0.01F, 0); + + GlStateManager.rotate(112.5F, 0, 0, 1); + + GlStateManager.translate(0, 0.04 + (1 - spread) * 0.09, (1 - spread) * -0.1875); + + GlStateManager.rotate((1 - spread) * -90, 0, 1, 0); + + this.bindTexture(BOOK_TEXTURE); + + float f3 = te.pageFlipPrev + (te.pageFlip - te.pageFlipPrev) * partialTicks + 0.25F; + float f4 = te.pageFlipPrev + (te.pageFlip - te.pageFlipPrev) * partialTicks + 0.75F; + f3 = (f3 - (float)MathHelper.fastFloor(f3)) * 1.6F - 0.3F; + f4 = (f4 - (float)MathHelper.fastFloor(f4)) * 1.6F - 0.3F; + + f3 = MathHelper.clamp(f3, 0, 1); + f4 = MathHelper.clamp(f4, 0, 1); + + GlStateManager.enableCull(); + this.modelBook.render(null, time, f3, f4, spread, 0.0F, 0.0625F); + GlStateManager.popMatrix(); + + } + +} diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderMagicLight.java b/src/main/java/electroblob/wizardry/client/renderer/tileentity/RenderMagicLight.java similarity index 69% rename from src/main/java/electroblob/wizardry/client/renderer/RenderMagicLight.java rename to src/main/java/electroblob/wizardry/client/renderer/tileentity/RenderMagicLight.java index a0e2a2ce..c38d8124 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/RenderMagicLight.java +++ b/src/main/java/electroblob/wizardry/client/renderer/tileentity/RenderMagicLight.java @@ -1,6 +1,11 @@ -package electroblob.wizardry.client.renderer; +package electroblob.wizardry.client.renderer.tileentity; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.block.BlockMagicLight; +import electroblob.wizardry.client.DrawingUtils; +import electroblob.wizardry.item.ISpellCastingItem; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.tileentity.TileEntityMagicLight; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.*; @@ -8,17 +13,23 @@ 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 net.minecraft.util.math.RayTraceResult; +import net.minecraftforge.client.event.DrawBlockHighlightEvent; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.relauncher.Side; import org.lwjgl.opengl.GL11; +@EventBusSubscriber(Side.CLIENT) public class RenderMagicLight extends TileEntitySpecialRenderer { - private static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID, - "textures/entity/light_ray.png"); - private static final ResourceLocation texture2 = new ResourceLocation(Wizardry.MODID, - "textures/entity/light_aura.png"); + private static final ResourceLocation RAY_TEXTURE = new ResourceLocation(Wizardry.MODID, + "textures/entity/light/ray.png"); + private static final ResourceLocation FLARE_TEXTURE = new ResourceLocation(Wizardry.MODID, + "textures/entity/light/flare.png"); @Override - public void render(TileEntityMagicLight tileentity, double x, double y, double z, float f, + public void render(TileEntityMagicLight tileentity, double x, double y, double z, float partialTicks, int destroyStage, float alpha){ GlStateManager.pushMatrix(); @@ -27,19 +38,14 @@ public class RenderMagicLight extends TileEntitySpecialRenderer 0 && tileentity.timer > tileentity.maxTimer - 10){ - float scale = Math.max(0, (float)(tileentity.maxTimer - tileentity.timer) / 10); - GlStateManager.scale(scale, scale, scale); - } + float s = DrawingUtils.smoothScaleFactor(tileentity.getLifetime(), tileentity.timer, partialTicks, 10, 10); + GlStateManager.scale(s, s, s); // Renders the aura effect @@ -60,7 +66,7 @@ public class RenderMagicLight extends TileEntitySpecialRenderer= 30){ for(int j = 0; j < 30; j++){ @@ -121,9 +127,25 @@ public class RenderMagicLight extends TileEntitySpecialRenderer 0 ? values()[ordinal() - 1] : this; + } + + /** Returns the translated display name of this tier, without formatting. */ public String getDisplayName(){ - return net.minecraft.client.resources.I18n.format("tier." + unlocalisedName); + return Wizardry.proxy.translate("tier." + unlocalisedName); } /** @@ -66,9 +72,9 @@ public enum Tier { return new TextComponentTranslation("tier." + unlocalisedName); } - @SideOnly(Side.CLIENT) + /** Returns the translated display name of this tier, with formatting. */ public String getDisplayNameWithFormatting(){ - return this.getFormattingCode() + net.minecraft.client.resources.I18n.format("tier." + unlocalisedName); + return Wizardry.proxy.translate("tier." + unlocalisedName, this.colour); } /** @@ -87,6 +93,11 @@ public enum Tier { return colour.getFormattingCode(); } + /** The progression required for a wand to be upgraded to this tier. */ + public int getProgression(){ + return Wizardry.settings.progressionRequirements[this.ordinal() - 1]; + } + /** * Returns a random tier based on the standard weighting. Currently, the standard weighting is: Basic (Novice) 60%, * Apprentice 25%, Advanced 10%, Master 5%. If an array of tiers is given, it picks a tier from the array, with the @@ -112,4 +123,5 @@ public enum Tier { // This will never happen, but it might as well be a sensible result. return tiers[tiers.length - 1]; } + } diff --git a/src/main/java/electroblob/wizardry/data/IVariable.java b/src/main/java/electroblob/wizardry/data/IVariable.java index 3388be93..3f123159 100644 --- a/src/main/java/electroblob/wizardry/data/IVariable.java +++ b/src/main/java/electroblob/wizardry/data/IVariable.java @@ -6,9 +6,9 @@ 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 + * Instances of this interface act as keys which allow data of any type to be stored in {@link WizardData} at + * runtime. This means spells (or anything else, for that matter) may define their own variables to be stored + * with the player and handle those variables 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}. *

diff --git a/src/main/java/electroblob/wizardry/data/WizardData.java b/src/main/java/electroblob/wizardry/data/WizardData.java index 7e70b21d..2b723acf 100644 --- a/src/main/java/electroblob/wizardry/data/WizardData.java +++ b/src/main/java/electroblob/wizardry/data/WizardData.java @@ -2,6 +2,7 @@ package electroblob.wizardry.data; import com.google.common.collect.EvictingQueue; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.constants.Tier; import electroblob.wizardry.enchantment.Imbuement; import electroblob.wizardry.entity.living.ISummonedCreature; import electroblob.wizardry.event.SpellCastEvent; @@ -74,6 +75,8 @@ public class WizardData implements INBTSerializable { /** The maximum number of recent spells to track. */ public static final int MAX_RECENT_SPELLS = ItemWand.BASE_SPELL_SLOTS; + private static final int IMBUEMENT_UPDATE_INTERVAL = 20; + /** The player this WizardData instance belongs to. */ private final EntityPlayer player; @@ -109,6 +112,9 @@ public class WizardData implements INBTSerializable { * {@link WizardData#discoverSpell(Spell)} instead. */ public Set spellsDiscovered; + /** The highest {@link Tier} this player has upgraded a wand to, used to apply progression modifiers. */ + private Tier maxTierReached = Tier.NOVICE; + 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 @@ -188,7 +194,7 @@ public class WizardData implements INBTSerializable { /** 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){ + public static void registerStoredVariables(IStoredVariable... variables){ storedVariables.addAll(Arrays.asList(variables)); } @@ -207,7 +213,7 @@ public class WizardData implements INBTSerializable { * 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) + // This use of type parameters guarantees that data 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); @@ -257,6 +263,24 @@ public class WizardData implements INBTSerializable { return spellsDiscovered.add(spell); } + /** + * Sets the tier this player has reached to the given tier, if they have not yet reached it. + * @param tier The tier the player has reached + */ + public void setTierReached(Tier tier){ + if(!hasReachedTier(tier)) this.maxTierReached = tier; + } + + /** + * Returns true if this player has previously upgraded a wand to the given tier. + * @param tier The tier to check for + * @return True if this player has already upgraded a wand to the given tier, false if not. This does not include + * wands that were purchased at the given tier, unless they have since been upgraded. + */ + public boolean hasReachedTier(Tier tier){ + return tier.level >= maxTierReached.level; + } + // Recent spell tracking /** @@ -285,14 +309,13 @@ public class WizardData implements INBTSerializable { * @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). + // 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.)"); + "Attempted to set an imbuement duration for something that isn't an Imbuement!"); } } @@ -315,6 +338,10 @@ public class WizardData implements INBTSerializable { */ private void updateImbuedItems(){ + // FIXME: Iterating through the entire inventory each tick is an AWFUL way of doing this, and is super-slow! + // Change the imbuement durations map to be ItemStack -> Integer and just decrement them, adding entries + // whenever an item with an imbuement enters the inventory and removing them when the time expires. + Set activeImbuements = new HashSet(); // For each item in the player's inventory @@ -335,12 +362,12 @@ public class WizardData implements INBTSerializable { // If the imbuement is still active: if(duration > 0){ // Decrements the timer - this.imbuementDurations.put((Imbuement)enchantment, duration - 1); + this.imbuementDurations.put((Imbuement)enchantment, duration - IMBUEMENT_UPDATE_INTERVAL); // 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(); + iterator.remove(); // FIXME: Apparently this can cause a CME } } } @@ -463,9 +490,9 @@ public class WizardData implements INBTSerializable { prevMotionY = player.motionY; - // This new system removes a lot of repetitive event handler code and inflexible spellData which had duplicate + // This new system removes a lot of repetitive event handler code and inflexible data which had duplicate // functions, just for different enchantments. - updateImbuedItems(); + if(player.ticksExisted % IMBUEMENT_UPDATE_INTERVAL == 0) updateImbuedItems(); updateContinuousSpellCasting(); this.spellData.forEach((k, v) -> this.spellData.put(k, k.update(player, v))); @@ -474,10 +501,10 @@ public class WizardData implements INBTSerializable { /** * 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. + * travelling to a different dimension. Used to copy over any data that should persist over player death. This + * is the inverse of the old onPlayerDeath method, which reset the data that shouldn't persist. * - * @param data The old WizardData whose spellData are to be copied over. + * @param data The old WizardData whose data is 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){ @@ -486,6 +513,7 @@ public class WizardData implements INBTSerializable { this.allyNames = data.allyNames; this.selectedMinion = data.selectedMinion; this.spellsDiscovered = data.spellsDiscovered; + this.maxTierReached = data.maxTierReached; this.recentSpells = data.recentSpells; for(IVariable variable : data.spellData.keySet()){ @@ -531,6 +559,8 @@ public class WizardData implements INBTSerializable { } properties.setIntArray("discoveredSpells", spells); + properties.setInteger("maxTierReached", maxTierReached.ordinal()); + NBTExtras.storeTagSafely(properties, "recentSpells", NBTExtras.listToNBT(recentSpells, s -> new NBTTagInt(s.metadata()))); storedVariables.forEach(k -> k.write(properties, this.spellData.get(k))); @@ -554,6 +584,8 @@ public class WizardData implements INBTSerializable { spellsDiscovered.add(Spell.byMetadata(id)); } + this.maxTierReached = Tier.values()[nbt.getInteger("maxTierReached")]; + // 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), diff --git a/src/main/java/electroblob/wizardry/entity/EntityLevitatingBlock.java b/src/main/java/electroblob/wizardry/entity/EntityLevitatingBlock.java index 72b67202..795ea113 100644 --- a/src/main/java/electroblob/wizardry/entity/EntityLevitatingBlock.java +++ b/src/main/java/electroblob/wizardry/entity/EntityLevitatingBlock.java @@ -4,9 +4,9 @@ import electroblob.wizardry.Wizardry; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.AllyDesignationSystem; +import electroblob.wizardry.util.EntityUtils; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.NBTExtras; -import electroblob.wizardry.util.WizardryUtilities; import io.netty.buffer.ByteBuf; import net.minecraft.block.Block; import net.minecraft.block.BlockFalling; @@ -81,7 +81,7 @@ public class EntityLevitatingBlock extends EntityFallingBlock implements IEntity } if(this.getCaster() == null && this.casterUUID != null){ - Entity entity = WizardryUtilities.getEntityByUUID(world, casterUUID); + Entity entity = EntityUtils.getEntityByUUID(world, casterUUID); if(entity instanceof EntityLivingBase){ this.caster = new WeakReference<>((EntityLivingBase)entity); } diff --git a/src/main/java/electroblob/wizardry/entity/EntityMeteor.java b/src/main/java/electroblob/wizardry/entity/EntityMeteor.java index 938e5c16..aa85983f 100644 --- a/src/main/java/electroblob/wizardry/entity/EntityMeteor.java +++ b/src/main/java/electroblob/wizardry/entity/EntityMeteor.java @@ -5,7 +5,7 @@ import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.spell.Meteor; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.EntityUtils; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.MoverType; import net.minecraft.entity.item.EntityFallingBlock; @@ -76,7 +76,7 @@ public class EntityMeteor extends EntityFallingBlock { this.setDead(); }else{ - WizardryUtilities.getEntitiesWithinRadius(15, posX, posY, posZ, world, EntityPlayer.class) + EntityUtils.getEntitiesWithinRadius(15, posX, posY, posZ, world, EntityPlayer.class) .forEach(p -> Wizardry.proxy.shakeScreen(p, 10)); } } diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityArrowRain.java b/src/main/java/electroblob/wizardry/entity/construct/EntityArrowRain.java index 9f26e85b..98033d36 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityArrowRain.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityArrowRain.java @@ -1,17 +1,23 @@ package electroblob.wizardry.entity.construct; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.spell.Spell; 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 class EntityArrowRain extends EntityScaledConstruct { public EntityArrowRain(World world){ super(world); - this.height = 3.0f; - this.width = 5.0f; + setSize(Spells.arrow_rain.getProperty(Spell.EFFECT_RADIUS).floatValue() * 2, 5); + } + + @Override + protected boolean shouldScaleHeight(){ + return false; } public void onUpdate(){ @@ -19,14 +25,21 @@ public class EntityArrowRain extends EntityMagicConstruct { super.onUpdate(); 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); + + double x = posX + (world.rand.nextDouble() - 0.5D) * (double)width; + double y = posY + world.rand.nextDouble() * (double)height; + double z = posZ + (world.rand.nextDouble() - 0.5D) * (double)width; + + EntityTippedArrow arrow = new EntityTippedArrow(world, x, y, z); + arrow.motionX = MathHelper.cos((float)Math.toRadians(this.rotationYaw + 90)); arrow.motionY = -0.6; 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)); + this.world.spawnEntity(arrow); } } diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityBlackHole.java b/src/main/java/electroblob/wizardry/entity/construct/EntityBlackHole.java index 378c9066..5cd84e76 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityBlackHole.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityBlackHole.java @@ -1,36 +1,47 @@ package electroblob.wizardry.entity.construct; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.entity.EntityLevitatingBlock; 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.BlockUtils; +import electroblob.wizardry.util.EntityUtils; 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.state.IBlockState; +import net.minecraft.entity.Entity; 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.nbt.NBTTagCompound; import net.minecraft.network.play.server.SPacketEntityVelocity; import net.minecraft.util.DamageSource; import net.minecraft.util.EnumParticleTypes; +import net.minecraft.util.math.BlockPos; 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 { - +public class EntityBlackHole extends EntityScaledConstruct { + private static final double SUCTION_STRENGTH = 0.075; + /** The maximum number of blocks that can be unhooked each tick, reduces lag from excessive numbers of entities. */ + private static final int BLOCK_UNHOOK_LIMIT = 3; public int[] randomiser; public int[] randomiser2; public EntityBlackHole(World world){ super(world); - this.width = 6.0f; - this.height = 3.0f; + float r = Spells.black_hole.getProperty(Spell.EFFECT_RADIUS).floatValue(); + setSize(r * 2, r); randomiser = new int[30]; for(int i = 0; i < randomiser.length; i++){ randomiser[i] = this.rand.nextInt(10); @@ -82,10 +93,46 @@ public class EntityBlackHole extends EntityMagicConstruct { if(!this.world.isRemote){ - List targets = WizardryUtilities.getEntitiesWithinRadius(6.0d, this.posX, this.posY, - this.posZ, this.world); + double radius = 2 * height * sizeMultiplier; - for(EntityLivingBase target : targets){ + boolean suckInBlocks = getCaster() instanceof EntityPlayer && EntityUtils.canDamageBlocks(getCaster(), world) + && ItemArtefact.isArtefactActive((EntityPlayer)getCaster(), WizardryItems.charm_black_hole); + + if(suckInBlocks){ + + List sphere = BlockUtils.getBlockSphere(new BlockPos(this), radius); + + int blocksUnhooked = 0; + + for(BlockPos pos : sphere){ + + if(rand.nextInt(Math.max(1, (int)this.getDistanceSq(pos) * 3)) == 0){ + + if(!BlockUtils.isBlockUnbreakable(world, pos) && !world.isAirBlock(pos) + && world.isBlockNormalCube(pos, false) && BlockUtils.canBreakBlock(getCaster(), world, pos)){ + // Checks that the block above is not solid, since this causes the falling block to vanish. +// && !world.isBlockNormalCube(pos.up(), false)){ + + EntityFallingBlock fallingBlock = new EntityLevitatingBlock(world, pos.getX() + 0.5, + pos.getY() + 0.5, pos.getZ() + 0.5, world.getBlockState(pos)); +// fallingBlock.noClip = true; + fallingBlock.fallTime = 1; // Prevent it from trying to delete the block itself + world.spawnEntity(fallingBlock); + world.setBlockToAir(pos); + + if(++blocksUnhooked >= BLOCK_UNHOOK_LIMIT) break; // Lag prevention + } + } + } + + } + + List targets = EntityUtils.getEntitiesWithinRadius(radius, this.posX, this.posY, + this.posZ, this.world, Entity.class); + + targets.removeIf(t -> !(t instanceof EntityLivingBase || (suckInBlocks && t instanceof EntityFallingBlock))); + + for(Entity target : targets){ if(this.isValidTarget(target)){ @@ -93,7 +140,8 @@ public class EntityBlackHole extends EntityMagicConstruct { if(!(target instanceof EntityPlayer && ((getCaster() instanceof EntityPlayer && !Wizardry.settings.playersMoveEachOther) || ItemArtefact.isArtefactActive((EntityPlayer)target, WizardryItems.amulet_anchoring)))){ - WizardryUtilities.undoGravity(target); + EntityUtils.undoGravity(target); + if(target instanceof EntityLevitatingBlock) ((EntityLevitatingBlock)target).suspend(); // Sucks the target in if(this.posX > target.posX && target.motionX < 1){ @@ -121,13 +169,22 @@ public class EntityBlackHole extends EntityMagicConstruct { } if(this.getDistance(target) <= 2){ - // Damages the target if it is close enough - if(this.getCaster() != null){ - target.attackEntityFrom( - MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.MAGIC), - 2 * damageMultiplier); + // Damages the target if it is close enough, or destroys it if it's a block + if(target instanceof EntityFallingBlock){ + target.playSound(WizardrySounds.ENTITY_BLACK_HOLE_BREAK_BLOCK, 0.5f, + (rand.nextFloat() - rand.nextFloat()) * 0.2f + 1); + IBlockState state = ((EntityFallingBlock)target).getBlock(); + if(state != null) world.playEvent(2001, new BlockPos(target), Block.getStateId(state)); + target.setDead(); + }else{ - target.attackEntityFrom(DamageSource.MAGIC, 2 * damageMultiplier); + if(this.getCaster() != null){ + target.attackEntityFrom( + MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.MAGIC), + 2 * damageMultiplier); + }else{ + target.attackEntityFrom(DamageSource.MAGIC, 2 * damageMultiplier); + } } } } diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityBlizzard.java b/src/main/java/electroblob/wizardry/entity/construct/EntityBlizzard.java index 9cc90a43..d03760b2 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityBlizzard.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityBlizzard.java @@ -4,11 +4,11 @@ import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.EntityUtils; 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.EntityLivingBase; import net.minecraft.potion.PotionEffect; import net.minecraft.util.DamageSource; @@ -16,12 +16,18 @@ import net.minecraft.world.World; import java.util.List; -public class EntityBlizzard extends EntityMagicConstruct { +public class EntityBlizzard extends EntityScaledConstruct { public EntityBlizzard(World world){ super(world); - this.height = 1.0f; - this.width = 1.0f; + // TODO: Set the size properly and do whatever forcefield does to allow block and entity interaction inside it + // (Probably need to do this for several others too) + setSize(Spells.blizzard.getProperty(Spell.EFFECT_RADIUS).floatValue() * 2, 3); + } + + @Override + protected boolean shouldScaleHeight(){ + return false; } public void onUpdate(){ @@ -34,11 +40,11 @@ public class EntityBlizzard extends EntityMagicConstruct { // 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(); + double radius = Spells.blizzard.getProperty(Spell.EFFECT_RADIUS).doubleValue() * sizeMultiplier; if(!this.world.isRemote){ - List targets = WizardryUtilities.getEntitiesWithinRadius(radius, this.posX, this.posY, + List targets = EntityUtils.getLivingWithinRadius(radius, this.posX, this.posY, this.posZ, this.world); for(EntityLivingBase target : targets){ @@ -46,11 +52,11 @@ public class EntityBlizzard extends EntityMagicConstruct { if(this.isValidTarget(target)){ if(this.getCaster() != null){ - WizardryUtilities.attackEntityWithoutKnockback(target, + EntityUtils.attackEntityWithoutKnockback(target, MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.FROST), 1 * damageMultiplier); }else{ - WizardryUtilities.attackEntityWithoutKnockback(target, DamageSource.MAGIC, + EntityUtils.attackEntityWithoutKnockback(target, DamageSource.MAGIC, 1 * damageMultiplier); } } @@ -62,10 +68,16 @@ public class EntityBlizzard extends EntityMagicConstruct { }else{ - 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); + for(int i=0; i<6; i++){ + double speed = (rand.nextBoolean() ? 1 : -1) * (0.1 + 0.05 * rand.nextDouble()); + ParticleBuilder.create(Type.SNOW).pos(this.posX, this.posY + rand.nextDouble() * height, this.posZ).vel(0, 0, 0) + .time(100).scale(2).spin(rand.nextDouble() * (radius - 0.5) + 0.5, speed).shaded(true).spawn(world); + } + + for(int i=0; i<3; i++){ + double speed = (rand.nextBoolean() ? 1 : -1) * (0.05 + 0.02 * rand.nextDouble()); + ParticleBuilder.create(Type.CLOUD).pos(this.posX, this.posY + rand.nextDouble() * (height - 0.5), this.posZ) + .clr(0xffffff).shaded(true).spin(rand.nextDouble() * (radius - 1) + 0.5, speed).spawn(world); } } } diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityBoulder.java b/src/main/java/electroblob/wizardry/entity/construct/EntityBoulder.java new file mode 100644 index 00000000..4a1d65cd --- /dev/null +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityBoulder.java @@ -0,0 +1,256 @@ +package electroblob.wizardry.entity.construct; + +import com.google.common.collect.Lists; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Boulder; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.BlockUtils; +import electroblob.wizardry.util.EntityUtils; +import electroblob.wizardry.util.MagicDamage; +import electroblob.wizardry.util.MagicDamage.DamageType; +import io.netty.buffer.ByteBuf; +import net.minecraft.block.Block; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.MoverType; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.init.Blocks; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.EnumParticleTypes; +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.minecraft.world.World; + +import java.util.List; + +public class EntityBoulder extends EntityScaledConstruct { + + private double velX, velZ; + private int hitsRemaining; + private boolean soundStarted = false; + + public EntityBoulder(World world){ + super(world); + setSize(2.375f, 2.375f); + this.noClip = false; + this.setNoGravity(false); + this.stepHeight = 1; + hitsRemaining = 5; + } + + public void setHorizontalVelocity(double velX, double velZ){ + this.velX = velX; + this.velZ = velZ; + } + + @Override + public void onUpdate(){ + + if(world.isRemote && !soundStarted && onGround){ + soundStarted = true; + Wizardry.proxy.playMovingSound(this, WizardrySounds.ENTITY_BOULDER_ROLL, WizardrySounds.SPELLS, 1, 1, true); + } + + super.onUpdate(); + + this.motionY -= 0.03999999910593033D; // Gravity + + this.move(MoverType.SELF, velX, motionY, velZ); + + // Entity damage + List collided = world.getEntitiesWithinAABB(EntityLivingBase.class, this.getEntityBoundingBox()); + + float damage = Spells.boulder.getProperty(Spell.DAMAGE).floatValue() * damageMultiplier; + float knockback = Spells.boulder.getProperty(Boulder.KNOCKBACK_STRENGTH).floatValue(); + + for(EntityLivingBase entity : collided){ + + if(!isValidTarget(entity)) break; + + boolean crushBonus = entity.posY < this.posY + && entity.getEntityBoundingBox().minX > this.getEntityBoundingBox().minX + && entity.getEntityBoundingBox().maxX < this.getEntityBoundingBox().maxX + && entity.getEntityBoundingBox().minZ > this.getEntityBoundingBox().minZ + && entity.getEntityBoundingBox().maxZ < this.getEntityBoundingBox().maxZ; + + if(EntityUtils.attackEntityWithoutKnockback(entity, MagicDamage.causeIndirectMagicDamage(this, + getCaster(), DamageType.MAGIC), crushBonus ? damage * 1.5f : damage) && !crushBonus){ + // Only knock back if not crushing + EntityUtils.applyStandardKnockback(this, entity, knockback); + entity.motionX += this.motionX; + entity.motionZ += this.motionZ; + } + entity.playSound(WizardrySounds.ENTITY_BOULDER_HIT, 1, 1); + } + + // Wall smashing + if(EntityUtils.canDamageBlocks(getCaster(), world) && collidedHorizontally){ + AxisAlignedBB box = getEntityBoundingBox().offset(velX, 0, velZ); + List cuboid = Lists.newArrayList(BlockPos.getAllInBox(MathHelper.floor(box.minX), MathHelper.floor(box.minY), + MathHelper.floor(box.minZ), MathHelper.floor(box.maxX), MathHelper.floor(box.maxY), MathHelper.floor(box.maxZ))); + smashBlocks(cuboid, true); + } + + // Trailing particles + for(int i = 0; i < 10; i++){ + + double particleX = this.posX + width * 0.7 * (rand.nextDouble() - 0.5); + double particleZ = this.posZ + width * 0.7 * (rand.nextDouble() - 0.5); + + IBlockState block = world.getBlockState(new BlockPos(this).down()); + + if(block.getBlock() != Blocks.AIR){ + world.spawnParticle(EnumParticleTypes.BLOCK_DUST, particleX, this.posY, particleZ, + 0, 0.2, 0, Block.getStateId(block)); + } + } + } + + /** + * Attempts to smash the given list of blocks. If the boulder has exhausted its hits, or if any of the blocks are + * too hard to be smashed and breakIfTooHard is true, the boulder is destroyed. + * @param blocks A list of block positions at which any solid blocks that are weak enough will be destroyed + * @param breakIfTooHard True if the boulder should break if any of the blocks are too hard, false if it should stop + * on those blocks + * @return True if something was destroyed (either the boulder, one or more blocks, or both), false otherwise + */ + private boolean smashBlocks(List blocks, boolean breakIfTooHard){ + + if(blocks.removeIf(p -> world.getBlockState(p).getBlock().getExplosionResistance(world, p, this, null) > 3 + || (!world.isRemote && !BlockUtils.canBreakBlock(getCaster(), world, p)))){ + // If any of the blocks were not breakable, the boulder is smashed + if(breakIfTooHard){ + this.despawn(); + }else{ + return false; + } + + }else{ + + if(!world.isRemote){ + blocks.forEach(p -> world.destroyBlock(p, false)); + if(--hitsRemaining <= 0) this.despawn(); + }else{ + world.playSound(posX, posY, posZ, WizardrySounds.ENTITY_BOULDER_BREAK_BLOCK, SoundCategory.BLOCKS, 1, 1, false); + } + } + + shakeNearbyPlayers(); + return true; + } + + private void shakeNearbyPlayers(){ + EntityUtils.getEntitiesWithinRadius(10, posX, posY, posZ, world, EntityPlayer.class) + .forEach(p -> Wizardry.proxy.shakeScreen(p, 8)); + } + + @Override + public void despawn(){ + + if(world.isRemote){ + + for(int i = 0; i < 200; i++){ + double x = posX + (rand.nextDouble() - 0.5) * width; + double y = posY + rand.nextDouble() * height; + double z = posZ + (rand.nextDouble() - 0.5) * width; + world.spawnParticle(EnumParticleTypes.BLOCK_DUST, x, y, z, (x - posX) * 0.1, + (y - posY + height / 2) * 0.1, (z - posZ) * 0.1, Block.getStateId(Blocks.DIRT.getDefaultState())); + } + + world.playSound(posX, posY, posZ, WizardrySounds.ENTITY_BOULDER_BREAK_BLOCK, SoundCategory.BLOCKS, 1, 1, false); + } + + super.despawn(); + } + + @Override + public void move(MoverType type, double x, double y, double z){ + super.move(type, x, y, z); + this.rotationPitch += Math.toDegrees(MathHelper.sqrt(x*x + y*y + z*z) / (width/2)); // That's how we roll + } + + @Override + public void fall(float distance, float damageMultiplier){ + + super.fall(distance, damageMultiplier); + + // Floor smashing + if(EntityUtils.canDamageBlocks(getCaster(), world) && distance > 3){ + AxisAlignedBB box = getEntityBoundingBox().offset(velX, motionY, velZ); + List cuboid = Lists.newArrayList(BlockPos.getAllInBox(MathHelper.floor(box.minX), MathHelper.floor(box.minY), + MathHelper.floor(box.minZ), MathHelper.floor(box.maxX), MathHelper.floor(box.maxY), MathHelper.floor(box.maxZ))); + if(smashBlocks(cuboid, distance > 8)) return; + hitsRemaining--; + } + + if(world.isRemote){ + + // Landing particles + for(int i = 0; i < 40; i++){ + + double particleX = this.posX - 1.5 + 3 * rand.nextDouble(); + double particleZ = this.posZ - 1.5 + 3 * rand.nextDouble(); + // Roundabout way of getting a block instance for the block the boulder is standing on (if any). + IBlockState block = world.getBlockState(new BlockPos(this.posX, this.posY - 2, this.posZ)); + + if(block.getBlock() != Blocks.AIR){ + world.spawnParticle(EnumParticleTypes.BLOCK_DUST, particleX, this.posY, particleZ, + particleX - this.posX, 0, particleZ - this.posZ, Block.getStateId(block)); + } + } + + // Other landing effects + if(distance > 1.2){ + world.playSound(posX, posY, posZ, WizardrySounds.ENTITY_BOULDER_LAND, SoundCategory.BLOCKS, Math.min(2, distance / 4), 1, false); + shakeNearbyPlayers(); +// EntityUtils.getEntitiesWithinRadius(Math.min(12, distance * 2), posX, posY, posZ, world, EntityPlayer.class) +// .forEach(p -> Wizardry.proxy.shakeScreen(p, Math.min(12, distance * 2))); + } + } + } + + @Override + public boolean canBeCollidedWith(){ + return true; + } + + @Override + public AxisAlignedBB getCollisionBoundingBox(){ + return this.getEntityBoundingBox(); + } + + @Override + protected void readEntityFromNBT(NBTTagCompound nbt){ + super.readEntityFromNBT(nbt); + velX = nbt.getDouble("velX"); + velZ = nbt.getDouble("velZ"); + hitsRemaining = nbt.getInteger("hitsRemaining"); + } + + @Override + protected void writeEntityToNBT(NBTTagCompound nbt){ + super.writeEntityToNBT(nbt); + nbt.setDouble("velX", velX); + nbt.setDouble("velZ", velZ); + nbt.setInteger("hitsRemaining", hitsRemaining); + } + + @Override + public void writeSpawnData(ByteBuf data){ + super.writeSpawnData(data); + data.writeDouble(velX); + data.writeDouble(velZ); + } + + @Override + public void readSpawnData(ByteBuf data){ + super.readSpawnData(data); + this.velX = data.readDouble(); + this.velZ = data.readDouble(); + } + +} diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityBubble.java b/src/main/java/electroblob/wizardry/entity/construct/EntityBubble.java index 08a59b14..72884fd5 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityBubble.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityBubble.java @@ -3,9 +3,9 @@ package electroblob.wizardry.entity.construct; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.spell.Entrapment; +import electroblob.wizardry.util.EntityUtils; 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; @@ -46,32 +46,32 @@ public class EntityBubble extends EntityMagicConstruct { // Synchronises the rider field 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)); + && EntityUtils.getRider(this) instanceof EntityLivingBase + && !EntityUtils.getRider(this).isDead){ + this.rider = new WeakReference<>((EntityLivingBase)EntityUtils.getRider(this)); } // Prevents dismounting - if(WizardryUtilities.getRider(this) == null && this.rider != null && this.rider.get() != null + if(EntityUtils.getRider(this) == null && this.rider != null && this.rider.get() != null && !this.rider.get().isDead){ this.rider.get().startRiding(this); } // Stops the bubble bursting instantly. - if(this.ticksExisted < 1 && !isDarkOrb) ((EntityLivingBase)WizardryUtilities.getRider(this)).hurtTime = 0; + if(this.ticksExisted < 1 && !isDarkOrb) ((EntityLivingBase)EntityUtils.getRider(this)).hurtTime = 0; this.move(MoverType.SELF, 0, 0.03, 0); if(isDarkOrb){ - if(WizardryUtilities.getRider(this) != null - && WizardryUtilities.getRider(this).ticksExisted % Spells.entrapment.getProperty(Entrapment.DAMAGE_INTERVAL).intValue() == 0){ + if(EntityUtils.getRider(this) != null + && EntityUtils.getRider(this).ticksExisted % Spells.entrapment.getProperty(Entrapment.DAMAGE_INTERVAL).intValue() == 0){ if(this.getCaster() != null){ - WizardryUtilities.getRider(this).attackEntityFrom( + EntityUtils.getRider(this).attackEntityFrom( MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.MAGIC), 1 * damageMultiplier); }else{ - WizardryUtilities.getRider(this).attackEntityFrom(DamageSource.MAGIC, 1 * damageMultiplier); + EntityUtils.getRider(this).attackEntityFrom(DamageSource.MAGIC, 1 * damageMultiplier); } } @@ -92,7 +92,7 @@ public class EntityBubble extends EntityMagicConstruct { // 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(EntityUtils.getRider(this) == null && this.ticksExisted > 1){ if(!this.isDarkOrb) this.playSound(WizardrySounds.ENTITY_BUBBLE_POP, 1.5f, 1.0f); this.setDead(); } @@ -100,8 +100,8 @@ public class EntityBubble extends EntityMagicConstruct { @Override public void despawn(){ - if(WizardryUtilities.getRider(this) != null){ - ((EntityLivingBase)WizardryUtilities.getRider(this)).dismountEntity(this); + if(EntityUtils.getRider(this) != null){ + ((EntityLivingBase)EntityUtils.getRider(this)).dismountEntity(this); } if(!this.isDarkOrb) this.playSound(WizardrySounds.ENTITY_BUBBLE_POP, 1.5f, 1.0f); super.despawn(); diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityCombustionRune.java b/src/main/java/electroblob/wizardry/entity/construct/EntityCombustionRune.java index 1077cdd3..155dd248 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityCombustionRune.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityCombustionRune.java @@ -2,7 +2,7 @@ package electroblob.wizardry.entity.construct; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.spell.Spell; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.EntityUtils; import net.minecraft.entity.EntityLivingBase; import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.math.MathHelper; @@ -10,12 +10,21 @@ import net.minecraft.world.World; import java.util.List; -public class EntityCombustionRune extends EntityMagicConstruct { +public class EntityCombustionRune extends EntityScaledConstruct { public EntityCombustionRune(World world){ super(world); - this.height = 0.2f; - this.width = 2.0f; + setSize(2, 0.2f); + } + + @Override + protected boolean shouldScaleWidth(){ + return false; // We're using the blast modifier for an actual explosion here, rather than the entity size + } + + @Override + protected boolean shouldScaleHeight(){ + return false; } @Override @@ -25,16 +34,16 @@ public class EntityCombustionRune extends EntityMagicConstruct { if(!this.world.isRemote){ - List targets = WizardryUtilities.getEntitiesWithinRadius(width/2, posX, posY, posZ, world); + List targets = EntityUtils.getLivingWithinRadius(width/2, posX, posY, posZ, world); for(EntityLivingBase target : targets){ if(this.isValidTarget(target)){ - float strength = Spells.combustion_rune.getProperty(Spell.BLAST_RADIUS).floatValue(); + float strength = Spells.combustion_rune.getProperty(Spell.BLAST_RADIUS).floatValue() * sizeMultiplier; world.newExplosion(this.getCaster(), this.posX, this.posY, this.posZ, strength, true, - getCaster() != null && WizardryUtilities.canDamageBlocks(getCaster(), world)); + EntityUtils.canDamageBlocks(getCaster(), world)); // The trap is destroyed once triggered. this.setDead(); @@ -48,9 +57,6 @@ public class EntityCombustionRune extends EntityMagicConstruct { } } - @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 e781a2d9..506276e9 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityDecay.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityDecay.java @@ -4,11 +4,10 @@ import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.EntityUtils; import electroblob.wizardry.util.ParticleBuilder; import electroblob.wizardry.util.ParticleBuilder.Type; -import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.nbt.NBTTagCompound; import net.minecraft.potion.PotionEffect; import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; @@ -17,7 +16,7 @@ import java.util.List; public class EntityDecay extends EntityMagicConstruct { - public int textureIndex = 0; + public int textureIndex; public EntityDecay(World world){ super(world); @@ -36,7 +35,7 @@ public class EntityDecay extends EntityMagicConstruct { 0.6F + rand.nextFloat() * 0.15F); if(!this.world.isRemote){ - List targets = WizardryUtilities.getEntitiesWithinRadius(1.0d, this.posX, this.posY, + List targets = EntityUtils.getLivingWithinRadius(1.0d, this.posX, this.posY, this.posZ, this.world); for(EntityLivingBase target : targets){ if(target != this.getCaster()){ @@ -62,12 +61,6 @@ public class EntityDecay extends EntityMagicConstruct { } } - @Override protected void entityInit(){} - - @Override protected void readEntityFromNBT(NBTTagCompound nbttagcompound){} - - @Override protected void writeEntityToNBT(NBTTagCompound nbttagcompound){} - @Override 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 19dd083c..8fc6c454 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityEarthquake.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityEarthquake.java @@ -2,9 +2,10 @@ package electroblob.wizardry.entity.construct; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.spell.Earthquake; +import electroblob.wizardry.util.BlockUtils; +import electroblob.wizardry.util.EntityUtils; 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.EntityPlayerMP; @@ -17,12 +18,11 @@ import net.minecraft.world.World; import java.util.List; -public class EntityEarthquake extends EntityMagicConstruct { +public class EntityEarthquake extends EntityMagicConstruct { // NOT a scaled construct, the size is controlled by time public EntityEarthquake(World world){ super(world); - this.height = 1.0f; - this.width = 1.0f; + setSize(1, 1); // This one probably should be small } public void onUpdate(){ @@ -31,7 +31,7 @@ public class EntityEarthquake extends EntityMagicConstruct { double speed = Spells.earthquake.getProperty(Earthquake.SPREAD_SPEED).doubleValue(); - if(!world.isRemote){ + if(!world.isRemote && EntityUtils.canDamageBlocks(getCaster(), world)){ // The further the earthquake is going to spread, the finer the angle increments. for(float angle = 0; angle < 2 * Math.PI; angle += Math.PI / (lifetime * 1.5)){ @@ -46,10 +46,9 @@ public class EntityEarthquake extends EntityMagicConstruct { BlockPos pos = new BlockPos(x, y, z); - if(!WizardryUtilities.isBlockUnbreakable(world, pos) && !world.isAirBlock(pos) - && world.isBlockNormalCube(pos, false) + if(!BlockUtils.isBlockUnbreakable(world, pos) && !world.isAirBlock(pos) && world.isBlockNormalCube(pos, false) // Checks that the block above is not solid, since this causes the falling sand to vanish. - && !world.isBlockNormalCube(pos.up(), false)){ + && !world.isBlockNormalCube(pos.up(), false) && BlockUtils.canBreakBlock(getCaster(), world, pos)){ // Falling blocks do the setting block to air themselves. EntityFallingBlock fallingblock = new EntityFallingBlock(world, x + 0.5, y + 0.5, z + 0.5, @@ -61,8 +60,8 @@ public class EntityEarthquake extends EntityMagicConstruct { } - List targets = WizardryUtilities - .getEntitiesWithinRadius((this.ticksExisted * speed) + 1.5, this.posX, this.posY, this.posZ, world); + List targets = EntityUtils + .getLivingWithinRadius((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. diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityFireRing.java b/src/main/java/electroblob/wizardry/entity/construct/EntityFireRing.java index e6e20e96..b23a744b 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityFireRing.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityFireRing.java @@ -3,23 +3,25 @@ package electroblob.wizardry.entity.construct; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.EntityUtils; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.EntityLivingBase; import net.minecraft.util.DamageSource; import net.minecraft.world.World; import java.util.List; -public class EntityFireRing extends EntityMagicConstruct { - - // TODO: Implement blast modifiers +public class EntityFireRing extends EntityScaledConstruct { public EntityFireRing(World world){ super(world); - this.height = 1.0f; - this.width = 5.0f; + setSize(Spells.ring_of_fire.getProperty(Spell.EFFECT_RADIUS).floatValue() * 2, 1); + } + + @Override + protected boolean shouldScaleHeight(){ + return false; } public void onUpdate(){ @@ -32,8 +34,7 @@ public class EntityFireRing extends EntityMagicConstruct { if(!this.world.isRemote){ - List targets = WizardryUtilities.getEntitiesWithinRadius(2.5d, this.posX, this.posY, - this.posZ, this.world); + List targets = EntityUtils.getLivingWithinRadius(width/2, this.posX, this.posY, this.posZ, this.world); for(EntityLivingBase target : targets){ diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityFireSigil.java b/src/main/java/electroblob/wizardry/entity/construct/EntityFireSigil.java index 509511ff..de3de2b6 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityFireSigil.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityFireSigil.java @@ -3,9 +3,9 @@ package electroblob.wizardry.entity.construct; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.EntityUtils; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; -import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.EntityLivingBase; import net.minecraft.util.DamageSource; import net.minecraft.util.EnumParticleTypes; @@ -14,12 +14,17 @@ import net.minecraft.world.World; import java.util.List; -public class EntityFireSigil extends EntityMagicConstruct { +// TODO: Try to collect sigils into one superclass +public class EntityFireSigil extends EntityScaledConstruct { public EntityFireSigil(World world){ super(world); - this.height = 0.2f; - this.width = 2.0f; + setSize(Spells.fire_sigil.getProperty(Spell.EFFECT_RADIUS).floatValue() * 2, 0.2f); + } + + @Override + protected boolean shouldScaleHeight(){ + return false; } @Override @@ -29,7 +34,7 @@ public class EntityFireSigil extends EntityMagicConstruct { if(!this.world.isRemote){ - List targets = WizardryUtilities.getEntitiesWithinRadius(width/2, posX, posY, posZ, world); + List targets = EntityUtils.getLivingWithinRadius(width/2, posX, posY, posZ, world); for(EntityLivingBase target : targets){ @@ -59,16 +64,13 @@ public class EntityFireSigil extends EntityMagicConstruct { } } }else if(this.rand.nextInt(15) == 0){ - double radius = 0.5 + rand.nextDouble() * 0.3; - float angle = rand.nextFloat() * (float)Math.PI * 2;; + double radius = (0.5 + rand.nextDouble() * 0.3) * width/2; + 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/EntityForcefield.java b/src/main/java/electroblob/wizardry/entity/construct/EntityForcefield.java index 759b7cb9..d118d016 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityForcefield.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityForcefield.java @@ -6,18 +6,19 @@ 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.EntityUtils; +import electroblob.wizardry.util.GeometryUtils; 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.nbt.NBTTagCompound; import net.minecraft.network.play.server.SPacketEntityVelocity; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.MathHelper; @@ -34,6 +35,7 @@ import java.util.Arrays; import java.util.Comparator; import java.util.List; +// TODO: Possibly convert this to EntityScaledConstruct @Mod.EventBusSubscriber public class EntityForcefield extends EntityMagicConstruct implements ICustomHitbox { @@ -65,16 +67,6 @@ public class EntityForcefield extends EntityMagicConstruct implements ICustomHit return radius; } - @Override - public boolean canBeCollidedWith(){ - return false;//!this.isDead; - } - - @Override - public AxisAlignedBB getCollisionBox(Entity entity){ - return null;//entity.getEntityBoundingBox(); - } - @Nullable @Override public AxisAlignedBB getCollisionBoundingBox(){ @@ -91,12 +83,16 @@ public class EntityForcefield extends EntityMagicConstruct implements ICustomHit super.onUpdate(); + if(ticksExisted == 1 && world.isRemote){ + Wizardry.proxy.playMovingSound(this, WizardrySounds.ENTITY_FORCEFIELD_AMBIENT, WizardrySounds.SPELLS, 0.5f, 1, true); + } + // 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); + List targets = EntityUtils.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 @@ -113,7 +109,7 @@ public class EntityForcefield extends EntityMagicConstruct implements ICustomHit if(this.isValidTarget(target)){ - Vec3d currentPos = Arrays.stream(WizardryUtilities.getVertices(target.getEntityBoundingBox())) + Vec3d currentPos = Arrays.stream(GeometryUtils.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 @@ -126,7 +122,7 @@ public class EntityForcefield extends EntityMagicConstruct implements ICustomHit boolean flag; - if(WizardryUtilities.isLiving(target)){ + if(EntityUtils.isLiving(target)){ // Non-allied living entities shouldn't be inside at all flag = nextTickDistance <= radius; }else{ @@ -139,7 +135,7 @@ public class EntityForcefield extends EntityMagicConstruct implements ICustomHit // Ring of interdiction if(getCaster() instanceof EntityPlayer && ItemArtefact.isArtefactActive((EntityPlayer)getCaster(), - WizardryItems.ring_interdiction) && WizardryUtilities.isLiving(target)){ + WizardryItems.ring_interdiction) && EntityUtils.isLiving(target)){ target.attackEntityFrom(MagicDamage.causeIndirectMagicDamage(this, getCaster(), MagicDamage.DamageType.MAGIC), 1); } @@ -147,7 +143,7 @@ public class EntityForcefield extends EntityMagicConstruct implements ICustomHit Vec3d targetRelativePos = currentPos.subtract(this.getPositionVector()); double nudgeVelocity = this.contains(target) ? -0.1 : 0.1; - if(WizardryUtilities.isLiving(target)) nudgeVelocity = 0.25; + if(EntityUtils.isLiving(target)) nudgeVelocity = 0.25; Vec3d extraVelocity = targetRelativePos.normalize().scale(nudgeVelocity); // ...make it bounce off! @@ -212,7 +208,7 @@ public class EntityForcefield extends EntityMagicConstruct implements ICustomHit /** 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); + return Arrays.stream(GeometryUtils.getVertices(box)).allMatch(this::contains); } /** Returns true if the given entity is completely inside this forcefield (the surface counts as outside). */ @@ -242,31 +238,28 @@ public class EntityForcefield extends EntityMagicConstruct implements ICustomHit 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; + @Override + protected void writeEntityToNBT(NBTTagCompound nbt){ + super.writeEntityToNBT(nbt); + nbt.setFloat("radius", radius); + } - Entity entity = world.getEntityByID(data.readInt()); - - if(entity instanceof EntityLivingBase){ - setCaster((EntityLivingBase)entity); - }else{ - Wizardry.logger.warn("Forcefield caster with ID in spawn data not found"); - } + @Override + protected void readEntityFromNBT(NBTTagCompound nbt){ + super.readEntityFromNBT(nbt); + radius = nbt.getFloat("radius"); } @Override @@ -299,7 +292,7 @@ public class EntityForcefield extends EntityMagicConstruct implements ICustomHit double searchRadius = 20; - List forcefields = WizardryUtilities.getEntitiesWithinRadius(searchRadius, vec.x, + List forcefields = EntityUtils.getEntitiesWithinRadius(searchRadius, vec.x, vec.y, vec.z, world, EntityForcefield.class); forcefields.removeIf(f -> !f.contains(vec)); @@ -313,7 +306,7 @@ public class EntityForcefield extends EntityMagicConstruct implements ICustomHit double searchRadius = 20; - List forcefields = WizardryUtilities.getEntitiesWithinRadius(searchRadius, vec.x, + List forcefields = EntityUtils.getEntitiesWithinRadius(searchRadius, vec.x, vec.y, vec.z, world, EntityForcefield.class); forcefields.removeIf(f -> !f.contains(box)); @@ -344,7 +337,7 @@ public class EntityForcefield extends EntityMagicConstruct implements ICustomHit // 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)) + if(getSurroundingForcefield(event.getWorld(), GeometryUtils.getCentre(box)) != getSurroundingForcefield(event.getWorld(), event.getEntityPlayer().getPositionVector())){ event.setCanceled(true); } diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityFrostSigil.java b/src/main/java/electroblob/wizardry/entity/construct/EntityFrostSigil.java index 690e7ec8..fc08e7d9 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityFrostSigil.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityFrostSigil.java @@ -4,11 +4,11 @@ import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.EntityUtils; 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.EntityLivingBase; import net.minecraft.potion.PotionEffect; import net.minecraft.util.DamageSource; @@ -17,12 +17,16 @@ import net.minecraft.world.World; import java.util.List; -public class EntityFrostSigil extends EntityMagicConstruct { +public class EntityFrostSigil extends EntityScaledConstruct { public EntityFrostSigil(World world){ super(world); - this.height = 0.2f; - this.width = 2.0f; + setSize(Spells.frost_sigil.getProperty(Spell.EFFECT_RADIUS).floatValue() * 2, 0.2f); + } + + @Override + protected boolean shouldScaleHeight(){ + return false; } @Override @@ -32,14 +36,14 @@ public class EntityFrostSigil extends EntityMagicConstruct { if(!this.world.isRemote){ - List targets = WizardryUtilities.getEntitiesWithinRadius(1.0d, this.posX, this.posY, + List targets = EntityUtils.getLivingWithinRadius(width/2, this.posX, this.posY, this.posZ, this.world); for(EntityLivingBase target : targets){ if(this.isValidTarget(target)){ - WizardryUtilities.attackEntityWithoutKnockback(target, this.getCaster() != null + EntityUtils.attackEntityWithoutKnockback(target, this.getCaster() != null ? MagicDamage.causeIndirectMagicDamage(this, this.getCaster(), DamageType.FROST) : DamageSource.MAGIC, Spells.frost_sigil.getProperty(Spell.DAMAGE).floatValue() * damageMultiplier); @@ -56,8 +60,8 @@ public class EntityFrostSigil extends EntityMagicConstruct { } } }else if(this.rand.nextInt(15) == 0){ - double radius = 0.5 + rand.nextDouble() * 0.3; - float angle = rand.nextFloat() * (float)Math.PI * 2;; + double radius = (0.5 + rand.nextDouble() * 0.3) * width/2; + 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 @@ -65,9 +69,6 @@ public class EntityFrostSigil extends EntityMagicConstruct { } } - @Override - protected void entityInit(){} - @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 f792d4ef..599ae80f 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityHailstorm.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityHailstorm.java @@ -1,15 +1,21 @@ package electroblob.wizardry.entity.construct; import electroblob.wizardry.entity.projectile.EntityIceShard; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.spell.Spell; import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; -public class EntityHailstorm extends EntityMagicConstruct { +public class EntityHailstorm extends EntityScaledConstruct { public EntityHailstorm(World world){ super(world); - this.height = 3.0f; - this.width = 5.0f; + setSize(Spells.hailstorm.getProperty(Spell.EFFECT_RADIUS).floatValue() * 2, 5); + } + + @Override + protected boolean shouldScaleHeight(){ + return false; } public void onUpdate(){ @@ -17,15 +23,21 @@ public class EntityHailstorm extends EntityMagicConstruct { super.onUpdate(); if(!this.world.isRemote){ - // System.out.println(this.rotationYaw); + + double x = posX + (world.rand.nextDouble() - 0.5D) * (double)width; + double y = posY + world.rand.nextDouble() * (double)height; + double z = posZ + (world.rand.nextDouble() - 0.5D) * (double)width; + 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.setPosition(x, y, z); + iceshard.motionX = MathHelper.cos((float)Math.toRadians(this.rotationYaw + 90)); iceshard.motionY = -0.6; 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 328a4077..40ba1997 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityHammer.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityHammer.java @@ -8,11 +8,11 @@ import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.spell.LightningHammer; import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.EntityUtils; 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 io.netty.buffer.ByteBuf; import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; @@ -105,7 +105,7 @@ public class EntityHammer extends EntityMagicConstruct { double seekerRange = Spells.lightning_hammer.getProperty(Spell.EFFECT_RADIUS).doubleValue(); - List targets = WizardryUtilities.getEntitiesWithinRadius(seekerRange, this.posX, + List targets = EntityUtils.getLivingWithinRadius(seekerRange, this.posX, this.posY + 1, this.posZ, world); int maxTargets = Spells.lightning_hammer.getProperty(LightningHammer.SECONDARY_MAX_TARGETS).intValue(); @@ -113,7 +113,7 @@ public class EntityHammer extends EntityMagicConstruct { for(EntityLivingBase target : targets){ - if(WizardryUtilities.isLiving(target) && this.isValidTarget(target) + if(EntityUtils.isLiving(target) && this.isValidTarget(target) && target.ticksExisted % Spells.lightning_hammer.getProperty(LightningHammer.ATTACK_INTERVAL).floatValue() == 0){ if(world.isRemote){ @@ -121,7 +121,7 @@ public class EntityHammer extends EntityMagicConstruct { 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.posY + target.height, target.posZ); } target.playSound(WizardrySounds.ENTITY_HAMMER_ATTACK, 1.0F, rand.nextFloat() * 0.4F + 1.5F); @@ -129,9 +129,9 @@ public class EntityHammer extends EntityMagicConstruct { float damage = Spells.lightning_hammer.getProperty(Spell.SPLASH_DAMAGE).floatValue() * damageMultiplier; if(this.getCaster() != null){ - WizardryUtilities.attackEntityWithoutKnockback(target, MagicDamage.causeIndirectMagicDamage( + EntityUtils.attackEntityWithoutKnockback(target, MagicDamage.causeIndirectMagicDamage( this, getCaster(), DamageType.SHOCK), damage); - WizardryUtilities.applyStandardKnockback(this, target); + EntityUtils.applyStandardKnockback(this, target); }else{ target.attackEntityFrom(DamageSource.MAGIC, damage); } @@ -183,7 +183,7 @@ public class EntityHammer extends EntityMagicConstruct { } if(this.fallDistance > 10){ - WizardryUtilities.getEntitiesWithinRadius(10, posX, posY, posZ, world, EntityPlayer.class) + EntityUtils.getEntitiesWithinRadius(10, posX, posY, posZ, world, EntityPlayer.class) .forEach(p -> Wizardry.proxy.shakeScreen(p, 6)); } @@ -241,23 +241,12 @@ public class EntityHammer extends EntityMagicConstruct { 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 dc32d17b..d887eabe 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityHealAura.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityHealAura.java @@ -3,11 +3,11 @@ package electroblob.wizardry.entity.construct; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.EntityUtils; 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.EntityLivingBase; import net.minecraft.util.DamageSource; import net.minecraft.util.math.MathHelper; @@ -15,14 +15,11 @@ import net.minecraft.world.World; import java.util.List; -public class EntityHealAura extends EntityMagicConstruct { - - // TODO: Implement blast modifiers +public class EntityHealAura extends EntityScaledConstruct { public EntityHealAura(World world){ super(world); - this.height = 1.0f; - this.width = 5.0f; + setSize(Spells.healing_aura.getProperty(Spell.EFFECT_RADIUS).floatValue() * 2, 1); } @Override @@ -36,7 +33,7 @@ public class EntityHealAura extends EntityMagicConstruct { if(!this.world.isRemote){ - List targets = WizardryUtilities.getEntitiesWithinRadius(2.5, posX, posY, posZ, world); + List targets = EntityUtils.getLivingWithinRadius(width/2, posX, posY, posZ, world); for(EntityLivingBase target : targets){ @@ -69,8 +66,8 @@ public class EntityHealAura extends EntityMagicConstruct { }else{ for(int i=1; i<3; i++){ float brightness = 0.5f + (rand.nextFloat() * 0.5f); - double radius = rand.nextDouble() * 2.0; - float angle = rand.nextFloat() * (float)Math.PI * 2;; + double radius = rand.nextDouble() * (width/2); + 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) diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityIceBarrier.java b/src/main/java/electroblob/wizardry/entity/construct/EntityIceBarrier.java new file mode 100644 index 00000000..39183c54 --- /dev/null +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityIceBarrier.java @@ -0,0 +1,167 @@ +package electroblob.wizardry.entity.construct; + +import electroblob.wizardry.entity.ICustomHitbox; +import electroblob.wizardry.registry.WizardrySounds; +import net.minecraft.entity.Entity; +import net.minecraft.entity.MoverType; +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.math.AxisAlignedBB; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +public class EntityIceBarrier extends EntityScaledConstruct implements ICustomHitbox { + + private static final double THICKNESS = 0.4; + + private int delay = 0; + + public EntityIceBarrier(World world){ + super(world); + this.setSize(1.8f, 1.05f); + } + + public void setDelay(int delay){ + this.delay = delay; + this.lifetime += delay; + } + + @Override + public void setRotation(float yaw, float pitch){ + super.setRotation(yaw, pitch); + float a = MathHelper.cos((float)Math.toRadians(rotationYaw)); + float b = MathHelper.sin((float)Math.toRadians(rotationYaw)); + double x = width/2 * a + THICKNESS/2 * b; + double z = width/2 * b + THICKNESS/2 * a; + setEntityBoundingBox(new AxisAlignedBB(this.posX - x, this.posY, this.posZ - z, this.posX + x, this.posY + height, this.posZ + z)); + } + + @Override + public boolean canBeCollidedWith(){ + return true; + } + + @Override + public void onUpdate(){ + + // Bit of a cheat but it's easier than trying to sync FrostBarrier#addConstructExtras + if(world.isRemote && firstUpdate){ + setSizeMultiplier(sizeMultiplier); // Do this first or it'll overwrite the bounding box + setRotation(rotationYaw, rotationPitch); + } + + this.prevPosX = posX; + this.prevPosY = posY; + this.prevPosZ = posZ; + + if(!world.isRemote){ + + double extensionSpeed = 0; + + if(lifetime - this.ticksExisted < 20){ + extensionSpeed = -0.01 * (this.ticksExisted - (lifetime - 20)) * sizeMultiplier; + }else if(ticksExisted > 3 + delay){ + extensionSpeed = 0; + }else if(ticksExisted > delay){ + extensionSpeed = 0.5 * sizeMultiplier; + } + + this.move(MoverType.SELF, 0, extensionSpeed, 0); + } + + if(ticksExisted == delay + 1) this.playSound(WizardrySounds.ENTITY_ICE_BARRIER_EXTEND, 1, 1.5f); + + super.onUpdate(); + + Vec3d look = this.getLookVec(); + + if(!world.isRemote){ + + for(Entity entity : world.getEntitiesWithinAABBExcludingEntity(this, getEntityBoundingBox().grow(2))){ + + if(entity instanceof EntityMagicConstruct) continue; + + if(!entity.getEntityBoundingBox().intersects(this.getEntityBoundingBox())) continue; + + // For some reason the player position seems to be off by 1 block in x and z, no idea how so for now + // I've just fudged it by adding 1 to x and z + double perpendicularDist = getSignedPerpendicularDistance(entity.getPositionVector().add(1, 0, 1)); + + if(Math.abs(perpendicularDist) < entity.width/2 + THICKNESS/2){ + + double velocity = 0.25 * Math.signum(perpendicularDist); + entity.addVelocity(velocity * look.x, 0, velocity * look.z); + // Player motion is handled on that player's client so needs packets + if(entity instanceof EntityPlayerMP){ + ((EntityPlayerMP)entity).connection.sendPacket(new SPacketEntityVelocity(entity)); + } + } + } + } + + } + + @Override + public boolean isBurning(){ + return false; + } + + @Override + public boolean attackEntityFrom(DamageSource source, float amount){ + this.playSound(WizardrySounds.ENTITY_ICE_BARRIER_DEFLECT, 0.7f, 2.5f); + return super.attackEntityFrom(source, amount); + } + +// @Override +// public int getBrightnessForRender(){ +// return 15728880; +// } + + @Override + protected void readEntityFromNBT(NBTTagCompound nbt){ + super.readEntityFromNBT(nbt); + delay = nbt.getInteger("delay"); + } + + @Override + protected void writeEntityToNBT(NBTTagCompound nbt){ + super.writeEntityToNBT(nbt); + nbt.setInteger("delay", delay); + } + + @Override + public Vec3d calculateIntercept(Vec3d origin, Vec3d endpoint, float fuzziness){ + // Calculate the point at which the line intersects the barrier plane + Vec3d vec = endpoint.subtract(origin); + + double perpendicularDist = getPerpendicularDistance(origin); + double perpendicularDist2 = getPerpendicularDistance(endpoint); + + Vec3d intercept = origin.add(vec.scale(perpendicularDist / (perpendicularDist + perpendicularDist2))); + + // This seems to be all over the palce, but the calculation MUST be right because it works for entity collisions! +// world.spawnParticle(EnumParticleTypes.END_ROD, intercept.x, intercept.y, intercept.z, 0, 0, 0); + + // If the point is within the hitbox (expanded by the fuzziness), it was a hit + return getEntityBoundingBox().grow(fuzziness).contains(intercept) ? intercept : null; + } + + @Override + public boolean contains(Vec3d point){ + return this.getEntityBoundingBox().contains(point) && getPerpendicularDistance(point) < THICKNESS/2; + } + + private double getPerpendicularDistance(Vec3d point){ + return Math.abs(getSignedPerpendicularDistance(point)); + } + + private double getSignedPerpendicularDistance(Vec3d point){ + Vec3d look = this.getLookVec(); + Vec3d delta = new Vec3d(point.x - this.posX, 0, point.z - this.posZ); + return delta.dotProduct(look); + } + +} diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityIceSpike.java b/src/main/java/electroblob/wizardry/entity/construct/EntityIceSpike.java index d79ac809..3d4b85c8 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityIceSpike.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityIceSpike.java @@ -4,9 +4,9 @@ import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.GeometryUtils; 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; @@ -27,9 +27,9 @@ public class EntityIceSpike extends EntityMagicConstruct { public void setFacing(EnumFacing facing){ this.facing = facing; - this.setRotation(-facing.getHorizontalAngle(), WizardryUtilities.getPitch(facing)); + this.setRotation(-facing.getHorizontalAngle(), GeometryUtils.getPitch(facing)); float yaw = (-facing.getHorizontalAngle()) * (float)Math.PI/180; - float pitch = (WizardryUtilities.getPitch(facing) - 90) * (float)Math.PI/180; + float pitch = (GeometryUtils.getPitch(facing) - 90) * (float)Math.PI/180; Vec3d min = this.getPositionVector().add(new Vec3d(-width/2, 0, -width/2).rotatePitch(pitch).rotateYaw(yaw)); Vec3d max = this.getPositionVector().add(new Vec3d(width/2, height, width/2).rotatePitch(pitch).rotateYaw(yaw)); this.setEntityBoundingBox(new AxisAlignedBB(min.x, min.y, min.z, max.x, max.y, max.z)); @@ -44,26 +44,29 @@ public class EntityIceSpike extends EntityMagicConstruct { double extensionSpeed = 0; - if(lifetime - this.ticksExisted < 15){ - extensionSpeed = -0.01 * (this.ticksExisted - (lifetime - 15)); - }else if(lifetime - this.ticksExisted < 25){ - extensionSpeed = 0; - }else if(lifetime - this.ticksExisted < 28){ - extensionSpeed = 0.25; + if(!world.isRemote){ + if(lifetime - this.ticksExisted < 15){ + extensionSpeed = -0.01 * (this.ticksExisted - (lifetime - 15)); + }else if(lifetime - this.ticksExisted < 25){ + extensionSpeed = 0; + }else if(lifetime - this.ticksExisted < 28){ + extensionSpeed = 0.25; + } + + 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(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.ENTITY_ICE_SPIKE_EXTEND, 1, 2); + if(lifetime - this.ticksExisted == 30) this.playSound(WizardrySounds.ENTITY_ICE_SPIKE_EXTEND, 1, 2.5f); if(!this.world.isRemote){ for(Object entity : this.world.getEntitiesWithinAABBExcludingEntity(this, this.getEntityBoundingBox())){ if(entity instanceof EntityLivingBase && this.isValidTarget((EntityLivingBase)entity)){ DamageSource source = this.getCaster() == null ? DamageSource.MAGIC : MagicDamage.causeDirectMagicDamage(this.getCaster(), DamageType.FROST); - // Potion effect only gets added if the damage succeeded. + // Potion effect only gets added if the damage succeeded + // We DO want knockback here or the entity gets stuck on the spike, which is a bit of a cheat if(((EntityLivingBase)entity).attackEntityFrom(source, Spells.ice_spikes.getProperty(Spell.DAMAGE).floatValue() * this.damageMultiplier)) ((EntityLivingBase)entity).addPotionEffect(new PotionEffect(WizardryPotions.frost, Spells.ice_spikes.getProperty(Spell.EFFECT_DURATION).intValue(), diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityLightningSigil.java b/src/main/java/electroblob/wizardry/entity/construct/EntityLightningSigil.java index 412df121..63cc1325 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityLightningSigil.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityLightningSigil.java @@ -3,11 +3,11 @@ package electroblob.wizardry.entity.construct; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.EntityUtils; 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.EntityLivingBase; import net.minecraft.util.DamageSource; import net.minecraft.util.math.MathHelper; @@ -15,14 +15,19 @@ import net.minecraft.world.World; import java.util.List; -public class EntityLightningSigil extends EntityMagicConstruct { +public class EntityLightningSigil extends EntityScaledConstruct { + public static final String SECONDARY_RANGE = "secondary_range"; public static final String SECONDARY_MAX_TARGETS = "secondary_max_targets"; public EntityLightningSigil(World world){ super(world); - this.height = 0.2f; - this.width = 2.0f; + setSize(Spells.frost_sigil.getProperty(Spell.EFFECT_RADIUS).floatValue() * 2, 0.2f); + } + + @Override + protected boolean shouldScaleHeight(){ + return false; } @Override @@ -34,7 +39,7 @@ public class EntityLightningSigil extends EntityMagicConstruct { this.setDead(); } - List targets = WizardryUtilities.getEntitiesWithinRadius(1.0d, this.posX, this.posY, + List targets = EntityUtils.getLivingWithinRadius(width/2, this.posX, this.posY, this.posZ, this.world); for(EntityLivingBase target : targets){ @@ -58,9 +63,9 @@ public class EntityLightningSigil extends EntityMagicConstruct { this.playSound(WizardrySounds.ENTITY_LIGHTNING_SIGIL_TRIGGER, 1.0f, 1.0f); // Secondary chaining effect - double seekerRange = Spells.lightning_sigil.getProperty(Spell.EFFECT_RADIUS).doubleValue(); + double seekerRange = Spells.lightning_sigil.getProperty(SECONDARY_RANGE).doubleValue(); - List secondaryTargets = WizardryUtilities.getEntitiesWithinRadius(seekerRange, + List secondaryTargets = EntityUtils.getLivingWithinRadius(seekerRange, target.posX, target.posY + target.height / 2, target.posZ, world); for(int j = 0; j < Math.min(secondaryTargets.size(), @@ -76,7 +81,7 @@ public class EntityLightningSigil extends EntityMagicConstruct { .pos(0, target.height/2, 0).target(secondaryTarget).spawn(world); ParticleBuilder.spawnShockParticles(world, secondaryTarget.posX, - secondaryTarget.getEntityBoundingBox().minY + secondaryTarget.height / 2, + secondaryTarget.posY + secondaryTarget.height / 2, secondaryTarget.posZ); } @@ -96,8 +101,8 @@ public class EntityLightningSigil extends EntityMagicConstruct { } if(this.world.isRemote && this.rand.nextInt(15) == 0){ - double radius = 0.5 + rand.nextDouble() * 0.3; - float angle = rand.nextFloat() * (float)Math.PI * 2;; + double radius = (0.5 + rand.nextDouble() * 0.3) * width/2; + 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); diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityMagicConstruct.java b/src/main/java/electroblob/wizardry/entity/construct/EntityMagicConstruct.java index 14b514a9..08a8caa8 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityMagicConstruct.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityMagicConstruct.java @@ -1,15 +1,20 @@ package electroblob.wizardry.entity.construct; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.item.ISpellCastingItem; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.AllyDesignationSystem; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.EntityUtils; import io.netty.buffer.ByteBuf; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.IEntityOwnable; +import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.EnumHand; import net.minecraft.util.SoundCategory; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData; import net.minecraftforge.fml.relauncher.Side; @@ -19,26 +24,21 @@ 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 - * 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. - * + * This class is for all inanimate magical constructs which are not projectiles. Generally speaking, subclasses of this + * class are areas of effect which deal damage or apply effects over time, including black hole, blizzard, tornado and + * a few others. The caster UUID, lifetime and damage multiplier are stored here, and lifetime is also synced here. + * * @since Wizardry 1.0 */ public abstract class EntityMagicConstruct extends Entity implements IEntityOwnable, IEntityAdditionalSpawnData { - /** The UUID of the caster. As of Wizardry 4.2, this is synced, and rather than storing the caster + /** The UUID of the caster. As of Wizardry 4.3, 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; - /** - * The time in ticks this magical construct lasts for; defaults to 600 (30 seconds). If this is -1 the construct - * doesn't despawn. - */ + /** The time in ticks this magical construct lasts for; defaults to 600 (30 seconds). If this is -1 the construct + * doesn't despawn. */ public int lifetime = 600; /** The damage multiplier for this construct, determined by the wand with which it was cast. */ @@ -70,6 +70,18 @@ public abstract class EntityMagicConstruct extends Entity implements IEntityOwna } + @Override + public EnumActionResult applyPlayerInteraction(EntityPlayer player, Vec3d vec, EnumHand hand){ + + // Permanent constructs can now be dispelled by sneak-right-clicking + if(lifetime == -1 && getCaster() == player && player.isSneaking() && player.getHeldItem(hand).getItem() instanceof ISpellCastingItem){ + this.despawn(); + return EnumActionResult.SUCCESS; + } + + return super.applyPlayerInteraction(player, vec, hand); + } + /** * Defaults to just setDead() in EntityMagicConstruct, but is provided to allow subclasses to override this e.g. * bubble uses it to dismount the entity inside it and play the 'pop' sound before calling super(). You should @@ -82,20 +94,20 @@ public abstract class EntityMagicConstruct extends Entity implements IEntityOwna @Override protected void entityInit(){ - + // We could leave this unimplemented, but since the majority of subclasses don't use it, let's make it optional } @Override protected void readEntityFromNBT(NBTTagCompound nbttagcompound){ - casterUUID = nbttagcompound.getUniqueId("casterUUID"); + if(nbttagcompound.hasUniqueId("casterUUID")) casterUUID = nbttagcompound.getUniqueId("casterUUID"); lifetime = nbttagcompound.getInteger("lifetime"); damageMultiplier = nbttagcompound.getFloat("damageMultiplier"); } @Override protected void writeEntityToNBT(NBTTagCompound nbttagcompound){ - if(this.getCaster() != null){ - nbttagcompound.setUniqueId("casterUUID", this.getCaster().getUniqueID()); + if(casterUUID != null){ + nbttagcompound.setUniqueId("casterUUID", casterUUID); } nbttagcompound.setInteger("lifetime", lifetime); nbttagcompound.setFloat("damageMultiplier", damageMultiplier); @@ -104,11 +116,26 @@ public abstract class EntityMagicConstruct extends Entity implements IEntityOwna @Override public void writeSpawnData(ByteBuf data){ data.writeInt(lifetime); + data.writeInt(getCaster() == null ? -1 : getCaster().getEntityId()); } @Override public void readSpawnData(ByteBuf data){ + lifetime = data.readInt(); + + int id = data.readInt(); + + if(id == -1){ + setCaster(null); + }else{ + Entity entity = world.getEntityByID(id); + if(entity instanceof EntityLivingBase){ + setCaster((EntityLivingBase)entity); + }else{ + Wizardry.logger.warn("Construct caster with ID in spawn data not found"); + } + } } @Nullable @@ -131,7 +158,7 @@ public abstract class EntityMagicConstruct extends Entity implements IEntityOwna @Nullable public EntityLivingBase getCaster(){ // Kept despite the above method because it returns an EntityLivingBase - Entity entity = WizardryUtilities.getEntityByUUID(world, getOwnerId()); + Entity entity = EntityUtils.getEntityByUUID(world, getOwnerId()); if(entity != null && !(entity instanceof EntityLivingBase)){ // Should never happen Wizardry.logger.warn("{} has a non-living owner!", this); diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityRadiantTotem.java b/src/main/java/electroblob/wizardry/entity/construct/EntityRadiantTotem.java new file mode 100644 index 00000000..13a1b880 --- /dev/null +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityRadiantTotem.java @@ -0,0 +1,143 @@ +package electroblob.wizardry.entity.construct; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.constants.Constants; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.RadiantTotem; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.*; +import electroblob.wizardry.util.BlockUtils.SurfaceCriteria; +import electroblob.wizardry.util.MagicDamage.DamageType; +import electroblob.wizardry.util.ParticleBuilder.Type; +import io.netty.buffer.ByteBuf; +import net.minecraft.entity.EntityLivingBase; +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.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +public class EntityRadiantTotem extends EntityScaledConstruct { + + private static final int PERIMETER_PARTICLE_DENSITY = 6; + + public EntityRadiantTotem(World world){ + super(world); + this.setSize(1, 1); // This entity is different in that its area of effect is kind of 'outside' it + } + + @Override + protected boolean shouldScaleWidth(){ + return false; + } + + @Override + protected boolean shouldScaleHeight(){ + return false; + } + + @Override + public void onUpdate(){ + + if(world.isRemote && this.ticksExisted == 1){ + Wizardry.proxy.playMovingSound(this, WizardrySounds.ENTITY_RADIANT_TOTEM_AMBIENT, WizardrySounds.SPELLS, 1, 1, true); + } + + super.onUpdate(); + + double radius = Spells.radiant_totem.getProperty(Spell.EFFECT_RADIUS).floatValue() * sizeMultiplier; + + if(world.isRemote){ + + ParticleBuilder.create(Type.DUST, rand, posX, posY + 0.2, posZ, 0.3, false) + .vel(0, -0.02 - world.rand.nextFloat() * 0.01, 0).clr(0xffffff).fade(0xffec90).spawn(world); + + for(int i=0; i nearby = EntityUtils.getLivingWithinRadius(radius, posX, posY, posZ, world); + nearby.sort(Comparator.comparingDouble(e -> e.getDistanceSq(this))); + + List nearbyAllies = nearby.stream().filter(e -> e == getCaster() + || AllyDesignationSystem.isAllied(getCaster(), e)).collect(Collectors.toList()); + nearby.removeAll(nearbyAllies); + + int targetsRemaining = Spells.radiant_totem.getProperty(RadiantTotem.MAX_TARGETS).intValue() + + (int)((damageMultiplier - 1) / Constants.POTENCY_INCREASE_PER_TIER); + + while(!nearbyAllies.isEmpty() && targetsRemaining > 0){ + + EntityLivingBase ally = nearbyAllies.remove(0); + + if(ally.getHealth() < ally.getMaxHealth()){ + // Slightly slower than healing aura, and it only does 1 at a time (without potency modifiers) + if(ally.ticksExisted % 8 == 0) ally.heal(Spells.radiant_totem.getProperty(Spell.HEALTH).floatValue()); + targetsRemaining--; + + if(world.isRemote){ + ParticleBuilder.create(Type.BEAM).pos(this.getPositionVector().add(0, height/2, 0)) + .target(ally).clr(1, 0.6f + 0.3f * world.rand.nextFloat(), 0.2f).spawn(world); + } + } + } + + while(!nearby.isEmpty() && targetsRemaining > 0){ + + EntityLivingBase target = nearby.remove(0); + + if(EntityUtils.isLiving(target) && isValidTarget(target)){ + + if(target.ticksExisted % target.maxHurtResistantTime == 1){ + + float damage = Spells.radiant_totem.getProperty(Spell.DAMAGE).floatValue(); + + EntityUtils.attackEntityWithoutKnockback(target, MagicDamage.causeIndirectMagicDamage(this, + getCaster(), DamageType.RADIANT), damage); + } + + targetsRemaining--; + + if(world.isRemote){ + ParticleBuilder.create(Type.BEAM).pos(this.getPositionVector().add(0, height/2, 0)) + .target(target).clr(1, 0.6f + 0.3f * world.rand.nextFloat(), 0.2f).spawn(world); + } + } + } + } + + @Override + public void despawn(){ + this.playSound(WizardrySounds.ENTITY_RADIANT_TOTEM_VANISH, 1, 1); + super.despawn(); + } + + // Usually damage multipliers don't need syncing, but here we're using it for the non-standard purpose of targeting + + @Override + public void writeSpawnData(ByteBuf data){ + super.writeSpawnData(data); + data.writeFloat(damageMultiplier); + } + + @Override + public void readSpawnData(ByteBuf data){ + super.readSpawnData(data); + damageMultiplier = data.readFloat(); + } +} diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityScaledConstruct.java b/src/main/java/electroblob/wizardry/entity/construct/EntityScaledConstruct.java new file mode 100644 index 00000000..9e37ce7e --- /dev/null +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityScaledConstruct.java @@ -0,0 +1,68 @@ +package electroblob.wizardry.entity.construct; + +import io.netty.buffer.ByteBuf; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.World; + +/** + * Extension of {@link EntityMagicConstruct} that implements saving and loading of size (blast) multipliers. What + * the entity actually does with the multiplier value is up to subclasses to define; however, by default this class + * scales the entity bounding box according to the size multiplier (this can be controlled by overriding + * {@link EntityScaledConstruct#shouldScaleWidth()} and {@link EntityScaledConstruct#shouldScaleHeight()}). + * + * @author Electroblob + * @since Wizardry 4.3 + */ +public abstract class EntityScaledConstruct extends EntityMagicConstruct { + + /** The size multiplier for this construct, usually determined by the blast modifier the spell was cast with. */ + protected float sizeMultiplier = 1; + + public EntityScaledConstruct(World world){ + super(world); + } + + public float getSizeMultiplier(){ + return sizeMultiplier; + } + + public void setSizeMultiplier(float sizeMultiplier){ + this.sizeMultiplier = sizeMultiplier; + setSize(shouldScaleWidth() ? width * sizeMultiplier : width, shouldScaleHeight() ? height * sizeMultiplier : height); + } + + /** Returns true if the width of this entity's bounding box should be scaled by the size multiplier on creation. */ + protected boolean shouldScaleWidth(){ + return true; + } + + /** Returns true if the height of this entity's bounding box should be scaled by the size multiplier on creation. */ + protected boolean shouldScaleHeight(){ + return true; + } + + @Override + protected void readEntityFromNBT(NBTTagCompound nbt){ + super.readEntityFromNBT(nbt); + setSizeMultiplier(nbt.getFloat("sizeMultiplier")); + } + + @Override + protected void writeEntityToNBT(NBTTagCompound nbt){ + super.writeEntityToNBT(nbt); + nbt.setFloat("sizeMultiplier", sizeMultiplier); + + } + + @Override + public void readSpawnData(ByteBuf data){ + super.readSpawnData(data); + setSizeMultiplier(data.readFloat()); // Set the width correctly on the client side + } + + @Override + public void writeSpawnData(ByteBuf data){ + super.writeSpawnData(data); + data.writeFloat(sizeMultiplier); + } +} diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityStormcloud.java b/src/main/java/electroblob/wizardry/entity/construct/EntityStormcloud.java new file mode 100644 index 00000000..4e5f7b6f --- /dev/null +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityStormcloud.java @@ -0,0 +1,87 @@ +package electroblob.wizardry.entity.construct; + +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.EntityUtils; +import electroblob.wizardry.util.MagicDamage; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.MoverType; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import java.util.Comparator; +import java.util.List; + +public class EntityStormcloud extends EntityScaledConstruct { + + public EntityStormcloud(World world){ + super(world); + setSize(Spells.stormcloud.getProperty(Spell.EFFECT_RADIUS).floatValue() * 2, 2); + } + + @Override + protected boolean shouldScaleHeight(){ + return false; + } + + public void onUpdate(){ + + super.onUpdate(); + + this.move(MoverType.SELF, motionX, 0, motionZ); + + if(this.world.isRemote){ + + float areaFactor = (width * width) / 36; // Ensures cloud/raindrop density stays the same for different sizes + + for(int i = 0; i < 2 * areaFactor; i++) ParticleBuilder.create(Type.CLOUD, this) + .clr(0.3f, 0.3f, 0.3f).shaded(true).spawn(world); + } + + boolean stormcloudRingActive = getCaster() instanceof EntityPlayer && ItemArtefact.isArtefactActive((EntityPlayer)getCaster(), WizardryItems.ring_stormcloud); + + List targets = world.getEntitiesWithinAABB(EntityLivingBase.class, + this.getEntityBoundingBox().expand(0, -10, 0)); + + targets.removeIf(t -> !this.isValidTarget(t)); + + float damage = Spells.stormcloud.getProperty(Spell.DAMAGE).floatValue() * this.damageMultiplier; + + for(EntityLivingBase target : targets){ + + if(target.ticksExisted % 150 == 0){ // Use target's lifetime so they don't all get hit at once, looks better + + if(!this.world.isRemote){ + EntityUtils.attackEntityWithoutKnockback(target, MagicDamage.causeIndirectMagicDamage( + this, this.getCaster(), MagicDamage.DamageType.SHOCK), damage); + }else{ + ParticleBuilder.create(Type.LIGHTNING).pos(target.posX, posY + height/2, target.posZ) + .target(target).scale(2).spawn(world); + ParticleBuilder.spawnShockParticles(world, target.posX, target.posY + target.height, target.posZ); + } + + target.playSound(WizardrySounds.ENTITY_STORMCLOUD_THUNDER, 1, 1.6f); + target.playSound(WizardrySounds.ENTITY_STORMCLOUD_ATTACK, 1, 1); + + if(stormcloudRingActive) this.lifetime -= 40; // Each strike prolongs the lifetime by 2 seconds with the ring + } + } + + if(stormcloudRingActive){ + EntityUtils.getLivingWithinRadius(width * 3, posX, posY, posZ, world).stream() + .filter(this::isValidTarget).min(Comparator.comparingDouble(this::getDistanceSq)).ifPresent(e -> { + Vec3d vel = e.getPositionVector().subtract(this.getPositionVector()).normalize().scale(0.2); + this.motionX = vel.x; + this.motionZ = vel.z; + }); + } + + } + +} diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityTornado.java b/src/main/java/electroblob/wizardry/entity/construct/EntityTornado.java index 18c8d51d..581b9663 100644 --- a/src/main/java/electroblob/wizardry/entity/construct/EntityTornado.java +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityTornado.java @@ -7,11 +7,12 @@ import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.spell.Spell; import electroblob.wizardry.spell.Tornado; +import electroblob.wizardry.util.BlockUtils; +import electroblob.wizardry.util.EntityUtils; 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 io.netty.buffer.ByteBuf; import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; @@ -29,17 +30,21 @@ import net.minecraft.world.World; import java.util.List; -public class EntityTornado extends EntityMagicConstruct { +public class EntityTornado extends EntityScaledConstruct { private double velX, velZ; public EntityTornado(World world){ super(world); - this.height = 8.0f; - this.width = 5.0f; + setSize(Spells.tornado.getProperty(Spell.EFFECT_RADIUS).floatValue(), 8); this.isImmuneToFire = false; } - + + @Override + protected boolean shouldScaleHeight(){ + return false; + } + public void setHorizontalVelocity(double velX, double velZ){ this.velX = velX; this.velZ = velZ; @@ -50,7 +55,7 @@ public class EntityTornado extends EntityMagicConstruct { super.onUpdate(); - double radius = Spells.tornado.getProperty(Spell.EFFECT_RADIUS).doubleValue(); + double radius = width/2; if(this.ticksExisted % 120 == 1 && world.isRemote){ // Repeat is false so that the sound fades out when the tornado does rather than stopping suddenly @@ -60,7 +65,7 @@ public class EntityTornado extends EntityMagicConstruct { this.move(MoverType.SELF, velX, motionY, velZ); BlockPos pos = new BlockPos(this); - Integer y = WizardryUtilities.getNearestSurface(world, pos.up(3), EnumFacing.UP, 5, true, WizardryUtilities.SurfaceCriteria.NOT_AIR_TO_AIR); + Integer y = BlockUtils.getNearestSurface(world, pos.up(3), EnumFacing.UP, 5, true, BlockUtils.SurfaceCriteria.NOT_AIR_TO_AIR); if(y != null){ @@ -74,7 +79,7 @@ public class EntityTornado extends EntityMagicConstruct { if(!this.world.isRemote){ - List targets = WizardryUtilities.getEntitiesWithinRadius(radius, this.posX, this.posY, + List targets = EntityUtils.getLivingWithinRadius(radius, this.posX, this.posY, this.posZ, this.world); for(EntityLivingBase target : targets){ @@ -125,7 +130,7 @@ public class EntityTornado extends EntityMagicConstruct { BlockPos pos1 = new BlockPos(blockX, this.posY + 3, blockZ); - Integer blockY = WizardryUtilities.getNearestSurface(world, pos1, EnumFacing.UP, 5, true, WizardryUtilities.SurfaceCriteria.NOT_AIR_TO_AIR); + Integer blockY = BlockUtils.getNearestSurface(world, pos1, EnumFacing.UP, 5, true, BlockUtils.SurfaceCriteria.NOT_AIR_TO_AIR); if(blockY != null){ diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityWitheringTotem.java b/src/main/java/electroblob/wizardry/entity/construct/EntityWitheringTotem.java new file mode 100644 index 00000000..fca25006 --- /dev/null +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityWitheringTotem.java @@ -0,0 +1,175 @@ +package electroblob.wizardry.entity.construct; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.constants.Constants; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.spell.WitheringTotem; +import electroblob.wizardry.util.*; +import electroblob.wizardry.util.BlockUtils.SurfaceCriteria; +import electroblob.wizardry.util.MagicDamage.DamageType; +import electroblob.wizardry.util.ParticleBuilder.Type; +import io.netty.buffer.ByteBuf; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.init.MobEffects; +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.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import java.util.Comparator; +import java.util.List; + +public class EntityWitheringTotem extends EntityScaledConstruct { + + private static final int PERIMETER_PARTICLE_DENSITY = 6; + + private static final DataParameter HEALTH_DRAINED = EntityDataManager.createKey(EntityWitheringTotem.class, DataSerializers.FLOAT); + + public EntityWitheringTotem(World world){ + super(world); + this.setSize(1, 1); // This entity is different in that its area of effect is kind of 'outside' it + } + + @Override + protected void entityInit(){ + dataManager.register(HEALTH_DRAINED, 0f); + } + + public float getHealthDrained(){ + return dataManager.get(HEALTH_DRAINED); + } + + public void addHealthDrained(float health){ + dataManager.set(HEALTH_DRAINED, getHealthDrained() + health); + } + + @Override + protected boolean shouldScaleWidth(){ + return false; + } + + @Override + protected boolean shouldScaleHeight(){ + return false; + } + + @Override + public void onUpdate(){ + + if(world.isRemote && this.ticksExisted == 1){ + Wizardry.proxy.playMovingSound(this, WizardrySounds.ENTITY_WITHERING_TOTEM_AMBIENT, WizardrySounds.SPELLS, 1, 1, true); + } + + super.onUpdate(); + + double radius = Spells.withering_totem.getProperty(Spell.EFFECT_RADIUS).floatValue() * sizeMultiplier; + + if(world.isRemote){ + + ParticleBuilder.create(Type.DUST, rand, posX, posY + 0.2, posZ, 0.3, false) + .vel(0, -0.02 - world.rand.nextFloat() * 0.01, 0).clr(0xf575f5).fade(0x382366).spawn(world); + + for(int i=0; i nearby = EntityUtils.getLivingWithinRadius(radius, posX, posY, posZ, world); + nearby.removeIf(e -> !isValidTarget(e)); + nearby.sort(Comparator.comparingDouble(e -> e.getDistanceSq(this))); + + int targetsRemaining = Spells.withering_totem.getProperty(WitheringTotem.MAX_TARGETS).intValue() + + (int)((damageMultiplier - 1) / Constants.POTENCY_INCREASE_PER_TIER); + + while(!nearby.isEmpty() && targetsRemaining > 0){ + + EntityLivingBase target = nearby.remove(0); + + if(EntityUtils.isLiving(target)){ + + if(target.ticksExisted % target.maxHurtResistantTime == 1){ + + float damage = Spells.withering_totem.getProperty(Spell.DAMAGE).floatValue(); + + if(EntityUtils.attackEntityWithoutKnockback(target, MagicDamage.causeIndirectMagicDamage(this, + getCaster(), DamageType.WITHER), damage)){ + addHealthDrained(damage); + } + } + + targetsRemaining--; + + if(world.isRemote){ + + Vec3d centre = GeometryUtils.getCentre(this); + Vec3d pos = GeometryUtils.getCentre(target); + + ParticleBuilder.create(Type.BEAM).pos(centre).target(target) + .clr(0.1f + 0.2f * world.rand.nextFloat(), 0, 0.3f).spawn(world); + + for(int i = 0; i < 3; i++){ + ParticleBuilder.create(Type.DUST, rand, pos.x, pos.y, pos.z, 0.3, false) + .vel(pos.subtract(centre).normalize().scale(-0.1)).clr(0x0c0024).fade(0x610017).spawn(world); + } + } + } + } + } + + @Override + public void despawn(){ + + double radius = Spells.withering_totem.getProperty(Spell.EFFECT_RADIUS).floatValue() * sizeMultiplier; + + List nearby = EntityUtils.getLivingWithinRadius(radius, posX, posY, posZ, world); + nearby.removeIf(e -> !isValidTarget(e)); + + float damage = Math.min(getHealthDrained() * 0.2f, Spells.withering_totem.getProperty(WitheringTotem.MAX_EXPLOSION_DAMAGE).floatValue()); + + for(EntityLivingBase target : nearby){ + + if(EntityUtils.attackEntityWithoutKnockback(target, MagicDamage.causeIndirectMagicDamage(this, + getCaster(), DamageType.MAGIC), damage)){ + target.addPotionEffect(new PotionEffect(MobEffects.WITHER, Spells.withering_totem.getProperty(Spell.EFFECT_DURATION).intValue(), + Spells.withering_totem.getProperty(Spell.EFFECT_STRENGTH).intValue())); + } + } + + if(world.isRemote) ParticleBuilder.create(Type.SPHERE).pos(GeometryUtils.getCentre(this)).scale((float)radius).clr(0xbe1a53) + .fade(0x210f4a).spawn(world); + + this.playSound(WizardrySounds.ENTITY_WITHERING_TOTEM_EXPLODE, 1, 1); + super.despawn(); + } + + // Usually damage multipliers don't need syncing, but here we're using it for the non-standard purpose of targeting + + @Override + public void writeSpawnData(ByteBuf data){ + super.writeSpawnData(data); + data.writeFloat(damageMultiplier); + } + + @Override + public void readSpawnData(ByteBuf data){ + super.readSpawnData(data); + damageMultiplier = data.readFloat(); + } +} diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityZombieSpawner.java b/src/main/java/electroblob/wizardry/entity/construct/EntityZombieSpawner.java new file mode 100644 index 00000000..95b3cdff --- /dev/null +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityZombieSpawner.java @@ -0,0 +1,95 @@ +package electroblob.wizardry.entity.construct; + +import electroblob.wizardry.entity.living.EntityHuskMinion; +import electroblob.wizardry.entity.living.EntityZombieMinion; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.SpellMinion; +import electroblob.wizardry.spell.ZombieApocalypse; +import electroblob.wizardry.util.EntityUtils; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import net.minecraft.entity.SharedMonsterAttributes; +import net.minecraft.entity.ai.attributes.AttributeModifier; +import net.minecraft.entity.ai.attributes.IAttributeInstance; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.World; + +public class EntityZombieSpawner extends EntityMagicConstruct { + + private static final double MAX_NUDGE_DISTANCE = 0.1; // Prevents zombies all bunching up directly below the spawner + + public boolean spawnHusks; + + private int spawnTimer = 10; + + public EntityZombieSpawner(World world){ + super(world); + this.setSize(4, 2); + } + + @Override + public void onUpdate(){ + + super.onUpdate(); + + if(lifetime - ticksExisted > 10 && spawnTimer-- == 0){ + + this.playSound(WizardrySounds.ENTITY_ZOMBIE_SPAWNER_SPAWN, 1, 1); + + if(!world.isRemote){ + + EntityZombieMinion zombie = spawnHusks ? new EntityHuskMinion(world) : new EntityZombieMinion(world); + + zombie.setPosition(this.posX + (rand.nextDouble() * 2 - 1) * MAX_NUDGE_DISTANCE, this.posY, + this.posZ + (rand.nextDouble() * 2 - 1) * MAX_NUDGE_DISTANCE); + zombie.setCaster(this.getCaster()); + // Modifier implementation + // Attribute modifiers are pretty opaque, see https://minecraft.gamepedia.com/Attribute#Modifiers + zombie.setLifetime(Spells.zombie_apocalypse.getProperty(SpellMinion.MINION_LIFETIME).intValue()); + IAttributeInstance attribute = zombie.getEntityAttribute(SharedMonsterAttributes.ATTACK_DAMAGE); + attribute.applyModifier(new AttributeModifier(SpellMinion.POTENCY_ATTRIBUTE_MODIFIER, + damageMultiplier - 1, EntityUtils.Operations.MULTIPLY_CUMULATIVE)); + zombie.setHealth(zombie.getMaxHealth()); // Need to set this because we may have just modified the value + zombie.hurtResistantTime = 30; // Prevent fall damage + zombie.hideParticles(); // Hide spawn particles or they pop out the top of the hidden box + + world.spawnEntity(zombie); + } + + spawnTimer += Spells.zombie_apocalypse.getProperty(ZombieApocalypse.MINION_SPAWN_INTERVAL).intValue() + rand.nextInt(20); + } + + if(world.isRemote){ + + float b = 0.15f; + + for(double r = 1.5; r < 4; r += 0.2){ + ParticleBuilder.create(Type.CLOUD).clr(b-=0.02, 0, 0).pos(posX, posY - 0.3, posZ).scale(0.5f / (float)r) + .spin(r, 0.02/r * (1 + world.rand.nextDouble())).spawn(world); + } + + } + + } + + @Override + public boolean shouldRenderInPass(int pass){ + return super.shouldRenderInPass(pass); + } + + @Override + protected void writeEntityToNBT(NBTTagCompound nbt){ + super.writeEntityToNBT(nbt); + nbt.setInteger("spawnTimer", spawnTimer); + nbt.setBoolean("spawnHusks", spawnHusks); + } + + @Override + protected void readEntityFromNBT(NBTTagCompound nbt){ + super.readEntityFromNBT(nbt); + this.spawnTimer = nbt.getInteger("spawnTimer"); + this.spawnHusks = nbt.getBoolean("spawnHusks"); + } + +} diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityAIAttackSpell.java b/src/main/java/electroblob/wizardry/entity/living/EntityAIAttackSpell.java index 21cf3471..f8a75ac1 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityAIAttackSpell.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityAIAttackSpell.java @@ -117,7 +117,7 @@ public class EntityAIAttackSpell extends // Only executed server side. - double distanceSq = this.attacker.getDistanceSq(this.target.posX, this.target.getEntityBoundingBox().minY, + double distanceSq = this.attacker.getDistanceSq(this.target.posX, this.target.posY, this.target.posZ); boolean targetIsVisible = this.attacker.getEntitySenses().canSee(this.target); @@ -153,8 +153,8 @@ public class EntityAIAttackSpell extends // ...reset the continuous spell timer and start the cooldown. this.continuousSpellTimer = 0; + this.cooldown = attacker.getContinuousSpell().getCooldown() + this.baseCooldown; setContinuousSpellAndNotify(Spells.none, new SpellModifiers()); - this.cooldown = this.baseCooldown; return; }else if(this.continuousSpellDuration - this.continuousSpellTimer == 1){ diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityBlazeMinion.java b/src/main/java/electroblob/wizardry/entity/living/EntityBlazeMinion.java index fc7873b2..912365be 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityBlazeMinion.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityBlazeMinion.java @@ -1,6 +1,7 @@ package electroblob.wizardry.entity.living; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.DrawingUtils; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.ai.EntityAIHurtByTarget; import net.minecraft.entity.ai.EntityAINearestAttackableTarget; @@ -94,8 +95,8 @@ public class EntityBlazeMinion extends EntityBlaze implements ISummonedCreature protected void spawnParticleEffect(){ if(this.world.isRemote){ for(int i = 0; i < 15; i++){ - this.world.spawnParticle(EnumParticleTypes.FLAME, this.posX + this.rand.nextFloat(), - this.posY + 1 + this.rand.nextFloat(), this.posZ + this.rand.nextFloat(), 0, 0, 0); + this.world.spawnParticle(EnumParticleTypes.FLAME, this.posX + this.rand.nextFloat() - 0.5f, + this.posY + this.rand.nextFloat() * height, this.posZ + this.rand.nextFloat() - 0.5f, 0, 0, 0); } } } @@ -105,6 +106,11 @@ public class EntityBlazeMinion extends EntityBlaze implements ISummonedCreature return false; } + @Override + public int getAnimationColour(float animationProgress){ + return DrawingUtils.mix(0xffdd4d, 0xff6600, animationProgress); + } + @Override protected boolean processInteract(EntityPlayer player, EnumHand hand){ // In this case, the delegate method determines whether super is called. diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityDecoy.java b/src/main/java/electroblob/wizardry/entity/living/EntityDecoy.java index a797807d..9c4df7ac 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityDecoy.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityDecoy.java @@ -44,7 +44,7 @@ public class EntityDecoy extends EntitySummonedCreature { 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 + .pos(this.posX + (this.rand.nextDouble() - 0.5) * this.width, this.posY + this.rand.nextDouble() * this.height, this.posZ + (this.rand.nextDouble() - 0.5) * this.width) .time(40) .clr(0.2f, 1.0f, 0.8f) @@ -54,6 +54,11 @@ public class EntityDecoy extends EntitySummonedCreature { } } + @Override + public int getAnimationColour(float animationProgress){ + return 0xffc600; + } + @Override public boolean isEntityInvulnerable(DamageSource source){ return true; diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityEvilWizard.java b/src/main/java/electroblob/wizardry/entity/living/EntityEvilWizard.java index ca20dd61..769f2baa 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityEvilWizard.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityEvilWizard.java @@ -369,7 +369,7 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity Element element = this.getElement(); // Adds armour. - for(EntityEquipmentSlot slot : WizardryUtilities.ARMOUR_SLOTS){ + for(EntityEquipmentSlot slot : InventoryUtils.ARMOUR_SLOTS){ this.setItemStackToSlot(slot, new ItemStack(WizardryItems.getArmour(element, slot))); } diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityIceGiant.java b/src/main/java/electroblob/wizardry/entity/living/EntityIceGiant.java index b964ac3e..10e94e52 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityIceGiant.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityIceGiant.java @@ -1,6 +1,7 @@ package electroblob.wizardry.entity.living; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.DrawingUtils; import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.ParticleBuilder; @@ -96,6 +97,11 @@ public class EntityIceGiant extends EntityIronGolem implements ISummonedCreature } } + @Override + public int getAnimationColour(float animationProgress){ + return DrawingUtils.mix(0xffffff, 0x73e1ff, animationProgress); + } + @Override public void onLivingUpdate(){ diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityIceWraith.java b/src/main/java/electroblob/wizardry/entity/living/EntityIceWraith.java index 8e4e55b4..37abc2af 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityIceWraith.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityIceWraith.java @@ -1,6 +1,7 @@ package electroblob.wizardry.entity.living; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.DrawingUtils; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.ParticleBuilder; @@ -50,9 +51,7 @@ public class EntityIceWraith extends EntityBlazeMinion { if(this.world.isRemote){ for(int i = 0; i < 15; i++){ float brightness = 0.5f + (rand.nextFloat() / 2); - 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()) + ParticleBuilder.create(Type.SPARKLE, this) .vel(0, 0.05f, 0) .time(20 + rand.nextInt(10)) .clr(brightness, brightness + 0.1f, 1.0f) @@ -61,6 +60,11 @@ public class EntityIceWraith extends EntityBlazeMinion { } } + @Override + public int getAnimationColour(float animationProgress){ + return DrawingUtils.mix(0xffffff, 0x73e1ff, animationProgress); + } + @Override public void onLivingUpdate(){ diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityLightningWraith.java b/src/main/java/electroblob/wizardry/entity/living/EntityLightningWraith.java index e0dd58be..60a766ec 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityLightningWraith.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityLightningWraith.java @@ -1,6 +1,7 @@ package electroblob.wizardry.entity.living; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.DrawingUtils; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.util.ParticleBuilder; import electroblob.wizardry.util.ParticleBuilder.Type; @@ -49,6 +50,11 @@ public class EntityLightningWraith extends EntityBlazeMinion { } } + @Override + public int getAnimationColour(float animationProgress){ + return DrawingUtils.mix(0xffffff, 0x0092ff, animationProgress); + } + @Override public void onLivingUpdate(){ // Fortunately, lightning wraiths don't replace any of blazes' particle effects or the fire sound, they only diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityMagicSlime.java b/src/main/java/electroblob/wizardry/entity/living/EntityMagicSlime.java index 725186fc..12dac4f8 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityMagicSlime.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityMagicSlime.java @@ -145,6 +145,11 @@ public class EntityMagicSlime extends EntitySlime implements ISummonedCreature { return false; } + @Override + public boolean hasAnimation(){ + return false; + } + @Override protected boolean processInteract(EntityPlayer player, EnumHand hand){ // In this case, the delegate method determines whether super is called. diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityPhoenix.java b/src/main/java/electroblob/wizardry/entity/living/EntityPhoenix.java index 23e11730..dbfed9f9 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityPhoenix.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityPhoenix.java @@ -1,10 +1,11 @@ package electroblob.wizardry.entity.living; +import electroblob.wizardry.client.DrawingUtils; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.BlockUtils; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.SharedMonsterAttributes; import net.minecraft.entity.ai.EntityAIHurtByTarget; @@ -145,17 +146,22 @@ public class EntityPhoenix extends EntitySummonedCreature implements ISpellCaste private void spawnParticleEffect(){ if(this.world.isRemote){ for(int i = 0; i < 15; i++){ - this.world.spawnParticle(EnumParticleTypes.FLAME, this.posX + this.rand.nextFloat(), - this.posY + 1 + this.rand.nextFloat(), this.posZ + this.rand.nextFloat(), 0, 0, 0); + this.world.spawnParticle(EnumParticleTypes.FLAME, this.posX + this.rand.nextFloat() - 0.5f, + this.posY + this.rand.nextFloat() * height, this.posZ + this.rand.nextFloat() - 0.5f, 0, 0, 0); } } } + @Override + public int getAnimationColour(float animationProgress){ + return DrawingUtils.mix(0xffdd4d, 0xff6600, animationProgress); + } + @Override public void onLivingUpdate(){ // Makes the phoenix hover. - Integer floorLevel = WizardryUtilities.getNearestFloor(world, new BlockPos(this), 4); + Integer floorLevel = BlockUtils.getNearestFloor(world, new BlockPos(this), 4); if(floorLevel == null || this.posY - floorLevel > 3){ this.motionY = -0.1; diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityRemnant.java b/src/main/java/electroblob/wizardry/entity/living/EntityRemnant.java new file mode 100644 index 00000000..2a63089d --- /dev/null +++ b/src/main/java/electroblob/wizardry/entity/living/EntityRemnant.java @@ -0,0 +1,344 @@ +package electroblob.wizardry.entity.living; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.block.BlockReceptacle; +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.util.ParticleBuilder; +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.*; +import net.minecraft.entity.monster.EntityMob; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTUtil; +import net.minecraft.network.datasync.DataParameter; +import net.minecraft.network.datasync.DataSerializers; +import net.minecraft.network.datasync.EntityDataManager; +import net.minecraft.util.DamageSource; +import net.minecraft.util.ResourceLocation; +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.world.DifficultyInstance; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public class EntityRemnant extends EntityMob { + + /** Data parameter for the remnant's element. */ + private static final DataParameter ELEMENT = EntityDataManager.createKey(EntityRemnant.class, DataSerializers.VARINT); + /** Data parameter that tracks whether the remnant is currently attacking (charging). */ + private static final DataParameter ATTACKING = EntityDataManager.createKey(EntityRemnant.class, DataSerializers.BOOLEAN); + + private ResourceLocation lootTable; + + @Nullable + private BlockPos boundOrigin; + + public EntityRemnant(World world){ + super(world); + this.setSize(0.8f, 0.8f); + this.moveHelper = new EntityRemnant.AIMoveControl(this); + this.experienceValue = 8; + } + + @Override + protected void entityInit(){ + super.entityInit(); + this.dataManager.register(ELEMENT, 1); // Default to fire + this.dataManager.register(ATTACKING, false); // Default to fire + } + + @Override + protected void initEntityAI(){ + super.initEntityAI(); + this.tasks.addTask(0, new EntityAISwimming(this)); + this.tasks.addTask(4, new EntityRemnant.AIChargeAttack()); + this.tasks.addTask(8, new EntityRemnant.AIMoveRandom()); + this.tasks.addTask(9, new EntityAIWatchClosest(this, EntityPlayer.class, 3.0F, 1.0F)); + this.tasks.addTask(10, new EntityAIWatchClosest(this, EntityLiving.class, 8.0F)); + this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, true, EntityRemnant.class)); + this.targetTasks.addTask(3, new EntityAINearestAttackableTarget<>(this, EntityPlayer.class, true)); + } + + @Override + protected void applyEntityAttributes(){ + super.applyEntityAttributes(); + this.getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(16); + this.getEntityAttribute(SharedMonsterAttributes.ATTACK_DAMAGE).setBaseValue(4); + } + + @Nullable + @Override + public IEntityLivingData onInitialSpawn(DifficultyInstance difficulty, @Nullable IEntityLivingData livingdata){ + this.setElement(Element.values()[1 + rand.nextInt(Element.values().length - 1)]); // Exclude MAGIC + this.setBoundOrigin(new BlockPos(this)); + return super.onInitialSpawn(difficulty, livingdata); + } + + public Element getElement(){ + return Element.values()[this.dataManager.get(ELEMENT)]; + } + + public void setElement(Element element){ + this.dataManager.set(ELEMENT, element.ordinal()); + this.lootTable = new ResourceLocation(Wizardry.MODID, "entities/remnant/" + element.getName()); + } + + public boolean isAttacking(){ + return this.dataManager.get(ATTACKING); + } + + public void setAttacking(boolean attacking){ + this.dataManager.set(ATTACKING, attacking); + } + + @Nullable + public BlockPos getBoundOrigin(){ + return this.boundOrigin; + } + + public void setBoundOrigin(@Nullable BlockPos boundOriginIn){ + this.boundOrigin = boundOriginIn; + } + + @Override + protected boolean isValidLightLevel(){ + return true; + } + + @Override + public float getBlockPathWeight(BlockPos pos){ + // Goddamnit Minecraft, stop imposing obscure spawning restrictions + return 1; // This won't affect pathfinding since remnants are flying mobs anyway + } + + @Override + protected float applyPotionDamageCalculations(DamageSource source, float damage){ + damage = super.applyPotionDamageCalculations(source, damage); + if(source.isMagicDamage()) damage *= 0.25f; // Remnants are 75% resistant to magic damage + return damage; + } + + @Override + public void onUpdate(){ + + // Use the same trick as EntityVex to fly through stuff + this.noClip = true; + super.onUpdate(); + this.noClip = false; + + this.setNoGravity(true); + + if(world.isRemote){ + + Vec3d centre = this.getPositionVector().add(0, height/2, 0); + + int[] colours = BlockReceptacle.PARTICLE_COLOURS.get(this.getElement()); + + if(rand.nextInt(10) == 0){ + ParticleBuilder.create(ParticleBuilder.Type.FLASH).entity(this).pos(0, height/2, 0).scale(width).time(48).clr(colours[0]).spawn(world); + } + + double r = width/3; + + double x = r * (rand.nextDouble() * 2 - 1); + double y = r * (rand.nextDouble() * 2 - 1); + double z = r * (rand.nextDouble() * 2 - 1); + + if(this.deathTime > 0){ + // Spew out particles on death + for(int i = 0; i < 8; i++){ + ParticleBuilder.create(ParticleBuilder.Type.DUST, rand, centre.x + x, centre.y + y, centre.z + z, 0.1, true) + .time(12).clr(colours[1]).fade(colours[2]).spawn(world); + } + }else{ + ParticleBuilder.create(ParticleBuilder.Type.DUST).pos(centre.x + x, centre.y + y, centre.z + z) + .vel(x * -0.03, 0.02, z * -0.03).time(24 + rand.nextInt(8)).clr(colours[1]).fade(colours[2]).spawn(world); + } + } + + } + + @Override + protected SoundEvent getAmbientSound(){ + return WizardrySounds.ENTITY_REMNANT_AMBIENT; + } + + @Override + protected SoundEvent getDeathSound(){ + return WizardrySounds.ENTITY_REMNANT_DEATH; + } + + @Override + protected SoundEvent getHurtSound(DamageSource source){ + return WizardrySounds.ENTITY_REMNANT_HURT; + } + + @Nullable + @Override + protected ResourceLocation getLootTable(){ + return lootTable; + } + + @Override + public void readEntityFromNBT(NBTTagCompound nbt){ + super.readEntityFromNBT(nbt); + this.setElement(Element.values()[nbt.getInteger("Element")]); + if(nbt.hasKey("BoundOrigin")) boundOrigin = NBTUtil.getPosFromTag(nbt.getCompoundTag("BoundOrigin")); + } + + @Override + public void writeEntityToNBT(NBTTagCompound nbt){ + super.writeEntityToNBT(nbt); + nbt.setInteger("Element", this.getElement().ordinal()); + if(boundOrigin != null) nbt.setTag("BoundOrigin", NBTUtil.createPosTag(boundOrigin)); + } + + // AI classes (copied from EntityVex) + + class AIChargeAttack extends EntityAIBase { + + public AIChargeAttack(){ + this.setMutexBits(1); + } + + @Override + public boolean shouldExecute(){ + if(EntityRemnant.this.getAttackTarget() != null && !EntityRemnant.this.getMoveHelper().isUpdating() && EntityRemnant.this.rand.nextInt(7) == 0){ + return EntityRemnant.this.getDistanceSq(EntityRemnant.this.getAttackTarget()) > 4.0D; + }else{ + return false; + } + } + + @Override + public boolean shouldContinueExecuting(){ + return EntityRemnant.this.getMoveHelper().isUpdating() && EntityRemnant.this.isAttacking() && EntityRemnant.this.getAttackTarget() != null && EntityRemnant.this.getAttackTarget().isEntityAlive(); + } + + @Override + public void startExecuting(){ + EntityLivingBase entitylivingbase = EntityRemnant.this.getAttackTarget(); + Vec3d vec3d = entitylivingbase.getPositionEyes(1.0F); + EntityRemnant.this.moveHelper.setMoveTo(vec3d.x, vec3d.y, vec3d.z, 1.0D); + EntityRemnant.this.setAttacking(true); +// EntityRemnant.this.playSound(SoundEvents.ENTITY_VEX_CHARGE, 1.0F, 1.0F); + } + + @Override + public void resetTask(){ + EntityRemnant.this.setAttacking(false); + } + + @Override + public void updateTask(){ + + EntityLivingBase entitylivingbase = EntityRemnant.this.getAttackTarget(); + + if(EntityRemnant.this.getEntityBoundingBox().intersects(entitylivingbase.getEntityBoundingBox())){ + EntityRemnant.this.attackEntityAsMob(entitylivingbase); + EntityRemnant.this.setAttacking(false); + }else{ + double d0 = EntityRemnant.this.getDistanceSq(entitylivingbase); + + if(d0 < 9.0D){ + Vec3d vec3d = entitylivingbase.getPositionEyes(1.0F); + EntityRemnant.this.moveHelper.setMoveTo(vec3d.x, vec3d.y, vec3d.z, 1.0D); + } + } + } + } + + class AIMoveControl extends EntityMoveHelper { + + public AIMoveControl(EntityRemnant host){ + super(host); + } + + @Override + public void onUpdateMoveHelper(){ + + if(this.action == EntityMoveHelper.Action.MOVE_TO){ + + double d0 = this.posX - EntityRemnant.this.posX; + double d1 = this.posY - EntityRemnant.this.posY; + double d2 = this.posZ - EntityRemnant.this.posZ; + double d3 = d0 * d0 + d1 * d1 + d2 * d2; + + d3 = MathHelper.sqrt(d3); + + if(d3 < EntityRemnant.this.getEntityBoundingBox().getAverageEdgeLength()){ + + this.action = EntityMoveHelper.Action.WAIT; + EntityRemnant.this.motionX *= 0.5D; + EntityRemnant.this.motionY *= 0.5D; + EntityRemnant.this.motionZ *= 0.5D; + + }else{ + + EntityRemnant.this.motionX += d0 / d3 * 0.05D * this.speed; + EntityRemnant.this.motionY += d1 / d3 * 0.05D * this.speed; + EntityRemnant.this.motionZ += d2 / d3 * 0.05D * this.speed; + + if(EntityRemnant.this.getAttackTarget() == null){ + EntityRemnant.this.rotationYaw = -((float)MathHelper.atan2(EntityRemnant.this.motionX, EntityRemnant.this.motionZ)) * (180F / (float)Math.PI); + }else{ + double d4 = EntityRemnant.this.getAttackTarget().posX - EntityRemnant.this.posX; + double d5 = EntityRemnant.this.getAttackTarget().posZ - EntityRemnant.this.posZ; + EntityRemnant.this.rotationYaw = -((float)MathHelper.atan2(d4, d5)) * (180F / (float)Math.PI); + } + + EntityRemnant.this.renderYawOffset = EntityRemnant.this.rotationYaw; + } + } + } + } + + class AIMoveRandom extends EntityAIBase { + + public AIMoveRandom(){ + this.setMutexBits(1); + } + + @Override + public boolean shouldExecute(){ + return !EntityRemnant.this.getMoveHelper().isUpdating() && EntityRemnant.this.rand.nextInt(7) == 0; + } + + @Override + public boolean shouldContinueExecuting(){ + return false; + } + + @Override + public void updateTask(){ + + BlockPos blockpos = EntityRemnant.this.getBoundOrigin(); + + if(blockpos == null){ + blockpos = new BlockPos(EntityRemnant.this); + } + + for(int i = 0; i < 3; ++i){ + BlockPos blockpos1 = blockpos.add(EntityRemnant.this.rand.nextInt(15) - 7, EntityRemnant.this.rand.nextInt(11) - 5, EntityRemnant.this.rand.nextInt(15) - 7); + + if(EntityRemnant.this.world.isAirBlock(blockpos1)){ + EntityRemnant.this.moveHelper.setMoveTo((double)blockpos1.getX() + 0.5D, (double)blockpos1.getY() + 0.5D, (double)blockpos1.getZ() + 0.5D, 0.25D); + + if(EntityRemnant.this.getAttackTarget() == null){ + EntityRemnant.this.getLookHelper().setLookPosition((double)blockpos1.getX() + 0.5D, (double)blockpos1.getY() + 0.5D, (double)blockpos1.getZ() + 0.5D, 180.0F, 20.0F); + } + + break; + } + } + } + } + +} diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityShadowWraith.java b/src/main/java/electroblob/wizardry/entity/living/EntityShadowWraith.java index 1ebd82ba..730d6000 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityShadowWraith.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityShadowWraith.java @@ -5,7 +5,6 @@ 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 net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.SharedMonsterAttributes; import net.minecraft.entity.ai.*; diff --git a/src/main/java/electroblob/wizardry/entity/living/EntitySkeletonMinion.java b/src/main/java/electroblob/wizardry/entity/living/EntitySkeletonMinion.java index 2d10fd34..50f26bbd 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntitySkeletonMinion.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntitySkeletonMinion.java @@ -1,7 +1,9 @@ package electroblob.wizardry.entity.living; import electroblob.wizardry.Wizardry; -import electroblob.wizardry.util.WizardryUtilities.Operations; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.EntityUtils; import net.minecraft.entity.EntityFlying; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.IEntityLivingData; @@ -73,7 +75,7 @@ public class EntitySkeletonMinion extends AbstractSkeleton implements ISummonedC 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, Operations.MULTIPLY_FLAT)); + .applyModifier(new AttributeModifier("Random spawn bonus", this.rand.nextGaussian() * 0.05D, EntityUtils.Operations.MULTIPLY_FLAT)); if(this.rand.nextFloat() < 0.05F){ this.setLeftHanded(true); @@ -117,6 +119,9 @@ public class EntitySkeletonMinion extends AbstractSkeleton implements ISummonedC @Override public void onSpawn(){ this.spawnParticleEffect(); + if(getCaster() instanceof EntityPlayer && ItemArtefact.isArtefactActive((EntityPlayer)getCaster(), WizardryItems.charm_undead_helmets)){ + setItemStackToSlot(EntityEquipmentSlot.HEAD, new ItemStack(Items.LEATHER_HELMET)); + } } @Override @@ -127,8 +132,8 @@ public class EntitySkeletonMinion extends AbstractSkeleton implements ISummonedC private void spawnParticleEffect(){ if(this.world.isRemote){ for(int i = 0; i < 15; i++){ - this.world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, this.posX + this.rand.nextFloat(), - this.posY + 1 + this.rand.nextFloat(), this.posZ + this.rand.nextFloat(), 0, 0, 0); + this.world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, this.posX + this.rand.nextFloat() - 0.5f, + this.posY + this.rand.nextFloat() * height, this.posZ + this.rand.nextFloat() - 0.5f, 0, 0, 0); } } } diff --git a/src/main/java/electroblob/wizardry/entity/living/EntitySpiderMinion.java b/src/main/java/electroblob/wizardry/entity/living/EntitySpiderMinion.java index b4efebc2..d3510beb 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntitySpiderMinion.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntitySpiderMinion.java @@ -1,9 +1,9 @@ package electroblob.wizardry.entity.living; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.util.EntityUtils; 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; @@ -67,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, Operations.MULTIPLY_FLAT)); + .applyModifier(new AttributeModifier("Random spawn bonus", this.rand.nextGaussian() * 0.05D, EntityUtils.Operations.MULTIPLY_FLAT)); if(this.rand.nextFloat() < 0.05F){ this.setLeftHanded(true); diff --git a/src/main/java/electroblob/wizardry/entity/living/EntitySpiritHorse.java b/src/main/java/electroblob/wizardry/entity/living/EntitySpiritHorse.java index 98deb764..a03639ab 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntitySpiritHorse.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntitySpiritHorse.java @@ -3,13 +3,15 @@ package electroblob.wizardry.entity.living; import electroblob.wizardry.Wizardry; import electroblob.wizardry.item.ISpellCastingItem; import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.util.EntityUtils; 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.IEntityLivingData; import net.minecraft.entity.SharedMonsterAttributes; +import net.minecraft.entity.ai.attributes.IAttribute; +import net.minecraft.entity.passive.AbstractHorse; import net.minecraft.entity.passive.EntityAnimal; import net.minecraft.entity.passive.EntityHorse; import net.minecraft.entity.player.EntityPlayer; @@ -30,6 +32,8 @@ import net.minecraft.world.World; @SuppressWarnings("deprecation") // It's what Entity does, so... public class EntitySpiritHorse extends EntityHorse { + public static final IAttribute JUMP_STRENGTH = AbstractHorse.JUMP_STRENGTH; + private int idleTimer = 0; private int dispelTimer = 0; @@ -97,7 +101,7 @@ public class EntitySpiritHorse extends EntityHorse { // clicking mounts the horse in this case). if(itemstack.getItem() instanceof ISpellCastingItem && this.getOwner() == player && player.isSneaking()){ // Prevents accidental double clicking. - if(this.ticksExisted > 20){ + if(this.ticksExisted > 20 && dispelTimer == 0){ this.dispelTimer++; @@ -124,7 +128,7 @@ public class EntitySpiritHorse extends EntityHorse { private EntityLivingBase getOwner(){ // I think the DataManager stores any objects, so it now stores the UUID instead of its string representation. - Entity owner = WizardryUtilities.getEntityByUUID(world, this.getOwnerUniqueId()); + Entity owner = EntityUtils.getEntityByUUID(world, this.getOwnerUniqueId()); if(owner instanceof EntityLivingBase){ return (EntityLivingBase)owner; @@ -163,7 +167,7 @@ public class EntitySpiritHorse extends EntityHorse { this.idleTimer = 0; } - if(this.idleTimer > 200){ + if(this.idleTimer > 200 && dispelTimer == 0){ this.playSound(WizardrySounds.ENTITY_SPIRIT_HORSE_VANISH, 0.7F, rand.nextFloat() * 0.4F + 1.0F); diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityStormElemental.java b/src/main/java/electroblob/wizardry/entity/living/EntityStormElemental.java index ef6c3aee..9e188d68 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityStormElemental.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityStormElemental.java @@ -5,7 +5,6 @@ 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 net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.SharedMonsterAttributes; import net.minecraft.entity.ai.*; @@ -32,7 +31,6 @@ public class EntityStormElemental extends EntitySummonedCreature implements ISpe public EntityStormElemental(World world){ super(world); // 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); } diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityVexMinion.java b/src/main/java/electroblob/wizardry/entity/living/EntityVexMinion.java index 73ed60ba..42c8d8f6 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityVexMinion.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityVexMinion.java @@ -91,6 +91,11 @@ public class EntityVexMinion extends EntityVex implements ISummonedCreature { } } + @Override + public int getAnimationColour(float animationProgress){ + return 0xef829c; + } + @Override public boolean hasParticleEffect(){ return true; diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityWitherSkeletonMinion.java b/src/main/java/electroblob/wizardry/entity/living/EntityWitherSkeletonMinion.java index 4ec2238a..a34f63f6 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityWitherSkeletonMinion.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityWitherSkeletonMinion.java @@ -1,7 +1,7 @@ package electroblob.wizardry.entity.living; import electroblob.wizardry.Wizardry; -import electroblob.wizardry.util.WizardryUtilities.Operations; +import electroblob.wizardry.util.EntityUtils; import net.minecraft.entity.EntityFlying; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.IEntityLivingData; @@ -75,7 +75,7 @@ public class EntityWitherSkeletonMinion extends EntityWitherSkeleton implements 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, Operations.MULTIPLY_FLAT)); + .applyModifier(new AttributeModifier("Random spawn bonus", this.rand.nextGaussian() * 0.05D, EntityUtils.Operations.MULTIPLY_FLAT)); if(this.rand.nextFloat() < 0.05F){ this.setLeftHanded(true); @@ -126,8 +126,8 @@ public class EntityWitherSkeletonMinion extends EntityWitherSkeleton implements private void spawnParticleEffect(){ if(this.world.isRemote){ for(int i = 0; i < 15; i++){ - this.world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, this.posX + this.rand.nextFloat(), - this.posY + 1 + this.rand.nextFloat(), this.posZ + this.rand.nextFloat(), 0, 0, 0); + this.world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, this.posX + this.rand.nextFloat() - 0.5f, + this.posY + this.rand.nextFloat() * height, this.posZ + this.rand.nextFloat() - 0.5f, 0, 0, 0); } } } diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityWizard.java b/src/main/java/electroblob/wizardry/entity/living/EntityWizard.java index c5737951..fce4bd33 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityWizard.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityWizard.java @@ -33,6 +33,7 @@ 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.math.MathHelper; import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.village.MerchantRecipe; @@ -251,7 +252,6 @@ 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(){ @@ -468,7 +468,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp 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); + EntityUtils.playSoundAtPlayer(this.getCustomer(), WizardrySounds.MISC_DISCOVER_SPELL, 1.25f, 1); this.getCustomer().sendMessage(new TextComponentTranslation("spell.discover", spell.getNameForTranslationFormatted())); } @@ -608,7 +608,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp // ((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, meta); + return new ItemStack(item, MathHelper.clamp((8 + tier.ordinal() * 16 + rand.nextInt(9)) / value, 1, 64), meta); } private ItemStack getRandomItemOfTier(Tier tier){ @@ -672,7 +672,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp }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 = InventoryUtils.ARMOUR_SLOTS[rand.nextInt(InventoryUtils.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. @@ -754,7 +754,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp Element element = this.getElement(); // Adds armour. - for(EntityEquipmentSlot slot : WizardryUtilities.ARMOUR_SLOTS){ + for(EntityEquipmentSlot slot : InventoryUtils.ARMOUR_SLOTS){ this.setItemStackToSlot(slot, new ItemStack(WizardryItems.getArmour(element, slot))); } @@ -887,7 +887,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp // Makes wizards angry if a player breaks a block in their tower if(!(event.getPlayer() instanceof FakePlayer)){ - List wizards = WizardryUtilities.getEntitiesWithinRadius(64, event.getPos().getX(), + List wizards = EntityUtils.getEntitiesWithinRadius(64, event.getPos().getX(), event.getPos().getY(), event.getPos().getZ(), event.getWorld(), EntityWizard.class); if(!wizards.isEmpty()){ diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityZombieMinion.java b/src/main/java/electroblob/wizardry/entity/living/EntityZombieMinion.java index f61e33a3..f08661c2 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityZombieMinion.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityZombieMinion.java @@ -1,6 +1,8 @@ package electroblob.wizardry.entity.living; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.registry.WizardryItems; import net.minecraft.entity.EntityFlying; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.ai.EntityAIHurtByTarget; @@ -8,9 +10,14 @@ import net.minecraft.entity.ai.EntityAIMoveThroughVillage; import net.minecraft.entity.ai.EntityAINearestAttackableTarget; import net.minecraft.entity.monster.EntityZombie; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.init.Items; +import net.minecraft.inventory.EntityEquipmentSlot; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.datasync.DataParameter; +import net.minecraft.network.datasync.DataSerializers; +import net.minecraft.network.datasync.EntityDataManager; import net.minecraft.util.EnumHand; import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.ResourceLocation; @@ -24,6 +31,8 @@ import java.util.UUID; public class EntityZombieMinion extends EntityZombie implements ISummonedCreature { + private static final DataParameter SPAWN_PARTICLES = EntityDataManager.createKey(EntityZombieMinion.class, DataSerializers.BOOLEAN); + // Field implementations private int lifetime = -1; private UUID casterUUID; @@ -40,6 +49,12 @@ public class EntityZombieMinion extends EntityZombie implements ISummonedCreatur this.experienceValue = 0; } + @Override + protected void entityInit(){ + super.entityInit(); + this.dataManager.register(SPAWN_PARTICLES, true); + } + // EntityZombie overrides (EntityZombie is a complex class so there are lots of these) @Override @@ -72,7 +87,11 @@ public class EntityZombieMinion extends EntityZombie implements ISummonedCreatur @Override public void onSpawn(){ - this.spawnParticleEffect(); + if(this.dataManager.get(SPAWN_PARTICLES)) this.spawnParticleEffect(); + if(shouldBurnInDay() && getCaster() instanceof EntityPlayer + && ItemArtefact.isArtefactActive((EntityPlayer)getCaster(), WizardryItems.charm_undead_helmets)){ + setItemStackToSlot(EntityEquipmentSlot.HEAD, new ItemStack(Items.LEATHER_HELMET)); + } } @Override @@ -83,8 +102,8 @@ public class EntityZombieMinion extends EntityZombie implements ISummonedCreatur private void spawnParticleEffect(){ if(this.world.isRemote){ for(int i = 0; i < 15; i++){ - this.world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, this.posX + this.rand.nextFloat(), - this.posY + 1 + this.rand.nextFloat(), this.posZ + this.rand.nextFloat(), 0, 0, 0); + this.world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, this.posX + this.rand.nextFloat() - 0.5f, + this.posY + this.rand.nextFloat() * 2, this.posZ + this.rand.nextFloat() - 0.5f, 0, 0, 0); } } } @@ -94,6 +113,15 @@ public class EntityZombieMinion extends EntityZombie implements ISummonedCreatur return true; } + @Override + public boolean hasAnimation(){ + return this.dataManager.get(SPAWN_PARTICLES) || this.ticksExisted > 20; + } + + public void hideParticles(){ + this.dataManager.set(SPAWN_PARTICLES, false); + } + @Override protected boolean processInteract(EntityPlayer player, EnumHand hand){ // In this case, the delegate method determines whether super is called. diff --git a/src/main/java/electroblob/wizardry/entity/living/ISpellCaster.java b/src/main/java/electroblob/wizardry/entity/living/ISpellCaster.java index af70b1d1..37144516 100644 --- a/src/main/java/electroblob/wizardry/entity/living/ISpellCaster.java +++ b/src/main/java/electroblob/wizardry/entity/living/ISpellCaster.java @@ -2,8 +2,8 @@ package electroblob.wizardry.entity.living; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.EntityUtils; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.world.EnumDifficulty; import javax.annotation.Nonnull; @@ -91,6 +91,6 @@ public interface ISpellCaster { * skeletons, which are: Easy - 10, Normal - 6, Hard - 2, Peaceful - 10 (rarely used). */ default int getAimingError(EnumDifficulty difficulty) { - return WizardryUtilities.getDefaultAimingError(difficulty); + return EntityUtils.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 c4639338..4f75d3e0 100644 --- a/src/main/java/electroblob/wizardry/entity/living/ISummonedCreature.java +++ b/src/main/java/electroblob/wizardry/entity/living/ISummonedCreature.java @@ -20,6 +20,7 @@ import net.minecraft.util.EntityDamageSourceIndirect; import net.minecraft.util.EnumHand; 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 net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData; @@ -118,7 +119,7 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData, IEntityOw 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()); + Entity entity = EntityUtils.getEntityByUUID(((Entity)this).world, getOwnerId()); if(entity != null && !(entity instanceof EntityLivingBase)){ // Should never happen Wizardry.logger.warn("{} has a non-living owner!", this); @@ -239,6 +240,16 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData, IEntityOw /** Whether this creature should spawn a subtle black swirl particle effect while alive. */ boolean hasParticleEffect(); + /** Returns whether this creature has an animation when appearing and disappearing. */ + default boolean hasAnimation(){ + return true; + } + + /** Returns the colour of this creature's appear/disappear animation. */ + default int getAnimationColour(float animationProgress){ + return 0x000000; + } + /** * 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 @@ -342,7 +353,7 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData, IEntityOw // Damage system - @SubscribeEvent + @SubscribeEvent(priority = EventPriority.HIGHEST) // Needs to be first because we're replacing damage entirely static void onLivingAttackEvent(LivingAttackEvent event){ // Rather than bother overriding entire attack methods in ISummonedCreature implementations, it's easier (and @@ -385,7 +396,7 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData, IEntityOw // 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()); + EntityUtils.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 // (if it didn't revenge-target, do nothing) diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityEmber.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityEmber.java index 13325283..61f4ddec 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityEmber.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityEmber.java @@ -39,9 +39,9 @@ public class EntityEmber extends EntityMagicProjectile { @Override protected void onImpact(RayTraceResult result){ - if(result.entityHit != null){ - result.entityHit.setFire(Spells.disintegration.getProperty(Spell.BURN_DURATION).intValue()); - } +// if(result.entityHit != null){ +// result.entityHit.setFire(Spells.disintegration.getProperty(Spell.BURN_DURATION).intValue()); +// } if(result.typeOfHit == RayTraceResult.Type.BLOCK){ this.inGround = true; @@ -60,7 +60,7 @@ public class EntityEmber extends EntityMagicProjectile { super.applyEntityCollision(entity); - if(entity instanceof EntityLivingBase){ + if(entity instanceof EntityLivingBase && ((EntityLivingBase)entity).getHealth() > 0){ entity.setFire(Spells.disintegration.getProperty(Spell.BURN_DURATION).intValue()); } } @@ -77,6 +77,7 @@ public class EntityEmber extends EntityMagicProjectile { } world.getEntitiesInAABBexcluding(thrower, this.getEntityBoundingBox(), e -> e instanceof EntityLivingBase) + .stream().filter(e -> !(e instanceof EntityLivingBase) || ((EntityLivingBase)e).getHealth() > 0) .forEach(e -> e.setFire(Spells.disintegration.getProperty(Spell.BURN_DURATION).intValue())); // Copied from ParticleLava diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityFirebomb.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityFirebomb.java index 0c4693ce..d380ba84 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityFirebomb.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityFirebomb.java @@ -3,11 +3,11 @@ package electroblob.wizardry.entity.projectile; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.EntityUtils; 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.util.EnumParticleTypes; @@ -69,7 +69,7 @@ public class EntityFirebomb extends EntityBomb { double range = Spells.firebomb.getProperty(Spell.BLAST_RADIUS).floatValue() * blastMultiplier; - List targets = WizardryUtilities.getEntitiesWithinRadius(range, this.posX, this.posY, + List targets = EntityUtils.getLivingWithinRadius(range, this.posX, this.posY, this.posZ, this.world); for(EntityLivingBase target : targets){ diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityFlamecatcherArrow.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityFlamecatcherArrow.java new file mode 100644 index 00000000..d45ea22b --- /dev/null +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityFlamecatcherArrow.java @@ -0,0 +1,71 @@ +package electroblob.wizardry.entity.projectile; + +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.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +public class EntityFlamecatcherArrow extends EntityMagicArrow { + + public static final float SPEED = 3; + + /** Creates a new magic missile in the given world. */ + public EntityFlamecatcherArrow(World world){ + super(world); + } + + @Override public double getDamage(){ return Spells.flamecatcher.getProperty(Spell.DAMAGE).floatValue(); } + + @Override public int getLifetime(){ return (int)(Spells.flamecatcher.getProperty(Spell.RANGE).floatValue() / SPEED); } + + @Override public boolean doGravity(){ return false; } // Zero gravity arrows! + + @Override public boolean doDeceleration(){ return false; } + + @Override + public void onEntityHit(EntityLivingBase entityHit){ + entityHit.setFire(Spells.flamecatcher.getProperty(Spell.BURN_DURATION).intValue()); + this.playSound(WizardrySounds.ENTITY_FLAMECATCHER_ARROW_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(0xff6d00).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(0xff6d00).fade(0.85f, 0.5f, 0.8f).spawn(world); + } + } + + @Override + public void tickInAir(){ + + if(this.world.isRemote){ + + ParticleBuilder.create(Type.MAGIC_FIRE, rand, posX, posY, posZ, 0.03, false) + .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.MAGIC_FIRE, rand, x, y, z, 0.03, false) + .time(20 + rand.nextInt(10)).spawn(world); + } + } + } + + @Override + protected void entityInit(){ + if(world != null && world.isRemote){ + ParticleBuilder.create(Type.FLASH).entity(this).time(this.getLifetime()).scale(1.5f).clr(0xffb800).spawn(world); + } + } + +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityForceArrow.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityForceArrow.java index 0b5bb9cb..27bb0da8 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityForceArrow.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityForceArrow.java @@ -7,10 +7,10 @@ import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.InventoryUtils; 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.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; @@ -65,7 +65,7 @@ public class EntityForceArrow extends EntityMagicArrow { if(!player.capabilities.isCreativeMode && ItemArtefact.isArtefactActive(player, WizardryItems.ring_mana_return)){ - for(ItemStack stack : WizardryUtilities.getPrioritisedHotbarAndOffhand(player)){ + for(ItemStack stack : InventoryUtils.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); diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityForceOrb.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityForceOrb.java index 3967b0ba..8db9243a 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityForceOrb.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityForceOrb.java @@ -3,11 +3,11 @@ package electroblob.wizardry.entity.projectile; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.EntityUtils; 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.EntityLivingBase; import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.math.RayTraceResult; @@ -53,7 +53,7 @@ public class EntityForceOrb extends EntityBomb { double blastRadius = Spells.force_orb.getProperty(Spell.BLAST_RADIUS).floatValue() * blastMultiplier; - List targets = WizardryUtilities.getEntitiesWithinRadius(blastRadius, this.posX, + List targets = EntityUtils.getLivingWithinRadius(blastRadius, this.posX, this.posY, this.posZ, this.world); for(EntityLivingBase target : targets){ diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityIceCharge.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityIceCharge.java index 048d47c9..79463235 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityIceCharge.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityIceCharge.java @@ -4,11 +4,12 @@ import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.BlockUtils; +import electroblob.wizardry.util.EntityUtils; 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.init.Blocks; @@ -74,7 +75,7 @@ public class EntityIceCharge extends EntityBomb { double radius = Spells.ice_charge.getProperty(Spell.EFFECT_RADIUS).floatValue() * blastMultiplier; - List targets = WizardryUtilities.getEntitiesWithinRadius(radius, this.posX, this.posY, + List targets = EntityUtils.getLivingWithinRadius(radius, this.posX, this.posY, this.posZ, this.world); // Slows targets @@ -93,8 +94,8 @@ public class EntityIceCharge extends EntityBomb { BlockPos pos = new BlockPos(this.posX + i, this.posY, this.posZ + j); - Integer y = WizardryUtilities.getNearestSurface(world, pos, EnumFacing.UP, 7, true, - WizardryUtilities.SurfaceCriteria.SOLID_LIQUID_TO_AIR); + Integer y = BlockUtils.getNearestSurface(world, pos, EnumFacing.UP, 7, true, + BlockUtils.SurfaceCriteria.SOLID_LIQUID_TO_AIR); if(y != null){ diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityIceball.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityIceball.java index f3989ffa..dae6c5ff 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityIceball.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityIceball.java @@ -4,10 +4,10 @@ import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.BlockUtils; 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.EntityLivingBase; import net.minecraft.init.Blocks; @@ -47,14 +47,11 @@ public class EntityIceball extends EntityMagicProjectile { }else{ - if(this.getThrower() == null || WizardryUtilities.canDamageBlocks(this.getThrower(), world)){ + BlockPos pos = rayTrace.getBlockPos(); - 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()); - } + if(rayTrace.sideHit == EnumFacing.UP && !world.isRemote && world.isSideSolid(pos, EnumFacing.UP) + && BlockUtils.canBlockBeReplaced(world, pos.up()) && BlockUtils.canPlaceBlock(thrower, world, pos)){ + world.setBlockState(pos.up(), Blocks.SNOW_LAYER.getDefaultState()); } } diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityLargeMagicFireball.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityLargeMagicFireball.java index 4a4d9b43..f34b6d57 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityLargeMagicFireball.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityLargeMagicFireball.java @@ -3,7 +3,7 @@ package electroblob.wizardry.entity.projectile; import electroblob.wizardry.Wizardry; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.spell.Spell; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.EntityUtils; import io.netty.buffer.ByteBuf; import net.minecraft.entity.projectile.EntityLargeFireball; import net.minecraft.nbt.NBTTagCompound; @@ -51,8 +51,8 @@ public class EntityLargeMagicFireball extends EntityMagicFireball { protected void onImpact(RayTraceResult rayTrace){ if(!world.isRemote){ - boolean flag = this.getThrower() == null || WizardryUtilities.canDamageBlocks(this.getThrower(), world); - this.world.newExplosion(null, this.posX, this.posY, this.posZ, getExplosionPower() * blastMultiplier, flag, flag); + boolean terrainDamage = EntityUtils.canDamageBlocks(this.getThrower(), world); + this.world.newExplosion(null, this.posX, this.posY, this.posZ, getExplosionPower() * blastMultiplier, terrainDamage, terrainDamage); } super.onImpact(rayTrace); diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityMagicArrow.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityMagicArrow.java index 2dd166e6..cccb2e61 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityMagicArrow.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityMagicArrow.java @@ -4,10 +4,10 @@ import electroblob.wizardry.item.ItemArtefact; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.AllyDesignationSystem; +import electroblob.wizardry.util.EntityUtils; 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; import net.minecraft.block.material.Material; @@ -93,7 +93,7 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE this.setCaster(caster); - this.setLocationAndAngles(caster.posX, caster.getEntityBoundingBox().minY + (double)caster.getEyeHeight() - LAUNCH_Y_OFFSET, + this.setLocationAndAngles(caster.posX, caster.posY + (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); @@ -120,10 +120,10 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE this.setCaster(caster); - this.posY = caster.getEntityBoundingBox().minY + (double)caster.getEyeHeight() - LAUNCH_Y_OFFSET; + this.posY = caster.posY + (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 dy = this.doGravity() ? target.posY + (double)(target.height / 3.0f) - this.posY + : target.posY + (double)(target.height / 2.0f) - this.posY; double dz = target.posZ - caster.posZ; double horizontalDistance = (double)MathHelper.sqrt(dx * dx + dz * dz); @@ -237,7 +237,7 @@ public abstract class EntityMagicArrow extends Entity implements IProjectile, IE } if(this.getCaster() == null && this.casterUUID != null){ - Entity entity = WizardryUtilities.getEntityByUUID(world, casterUUID); + Entity entity = EntityUtils.getEntityByUUID(world, casterUUID); if(entity instanceof EntityLivingBase){ this.caster = new WeakReference<>((EntityLivingBase)entity); } diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityMagicFireball.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityMagicFireball.java index 96d98607..28356618 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityMagicFireball.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityMagicFireball.java @@ -3,10 +3,10 @@ package electroblob.wizardry.entity.projectile; import electroblob.wizardry.Wizardry; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.BlockUtils; import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage.DamageType; import electroblob.wizardry.util.ParticleBuilder; -import electroblob.wizardry.util.WizardryUtilities; import io.netty.buffer.ByteBuf; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; @@ -84,13 +84,12 @@ public class EntityMagicFireball extends EntityMagicProjectile { }else{ - if(this.getThrower() == null || WizardryUtilities.canDamageBlocks(this.getThrower(), world)){ + BlockPos pos = rayTrace.getBlockPos().offset(rayTrace.sideHit); - BlockPos blockpos = rayTrace.getBlockPos().offset(rayTrace.sideHit); - - if(this.world.isAirBlock(blockpos)){ - this.world.setBlockState(blockpos, Blocks.FIRE.getDefaultState()); - } + // Remember that canPlaceBlock should ALWAYS be the last thing that gets checked, or it risks other mods + // thinking the block was placed even when a later condition prevents it, which may have side-effects + if(this.world.isAirBlock(pos) && BlockUtils.canPlaceBlock(thrower, world, pos)){ + this.world.setBlockState(pos, Blocks.FIRE.getDefaultState()); } } diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityMagicProjectile.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityMagicProjectile.java index fa94a473..edf5386e 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityMagicProjectile.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityMagicProjectile.java @@ -48,7 +48,7 @@ public abstract class EntityMagicProjectile extends EntityThrowable implements I /** 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.setPosition(caster.posX, caster.posY + (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; @@ -66,10 +66,10 @@ public abstract class EntityMagicProjectile extends EntityThrowable implements I // 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; + this.posY = caster.posY + (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 dy = !this.hasNoGravity() ? target.posY + (double)(target.height / 3.0f) - this.posY + : target.posY + (double)(target.height / 2.0f) - this.posY; double dz = target.posZ - caster.posZ; double horizontalDistance = MathHelper.sqrt(dx * dx + dz * dz); diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntityPoisonBomb.java b/src/main/java/electroblob/wizardry/entity/projectile/EntityPoisonBomb.java index 286e7259..83ffbb9e 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntityPoisonBomb.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntityPoisonBomb.java @@ -3,11 +3,11 @@ package electroblob.wizardry.entity.projectile; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.EntityUtils; 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.init.MobEffects; @@ -74,7 +74,7 @@ public class EntityPoisonBomb extends EntityBomb { double range = Spells.poison_bomb.getProperty(Spell.EFFECT_RADIUS).floatValue() * blastMultiplier; - List targets = WizardryUtilities.getEntitiesWithinRadius(range, this.posX, this.posY, + List targets = EntityUtils.getLivingWithinRadius(range, this.posX, this.posY, this.posZ, this.world); for(EntityLivingBase target : targets){ diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntitySmokeBomb.java b/src/main/java/electroblob/wizardry/entity/projectile/EntitySmokeBomb.java index 1339c929..d3bbeed8 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntitySmokeBomb.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntitySmokeBomb.java @@ -1,13 +1,11 @@ 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.EntityUtils; 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.init.MobEffects; import net.minecraft.potion.PotionEffect; @@ -39,13 +37,12 @@ public class EntitySmokeBomb extends EntityBomb { 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; + + float brightness = rand.nextFloat() * 0.1f + 0.1f; + ParticleBuilder.create(Type.CLOUD, rand, posX, posY, posZ, 2*blastMultiplier, false) + .clr(brightness, brightness, brightness).time(80 + this.rand.nextInt(12)).shaded(true).spawn(world); + + brightness = rand.nextFloat() * 0.3f; ParticleBuilder.create(Type.DARK_MAGIC, rand, posX, posY, posZ, 2*blastMultiplier, false) .clr(brightness, brightness, brightness).spawn(world); } @@ -58,21 +55,14 @@ public class EntitySmokeBomb extends EntityBomb { double range = Spells.smoke_bomb.getProperty(Spell.BLAST_RADIUS).floatValue() * blastMultiplier; - List targets = WizardryUtilities.getEntitiesWithinRadius(range, this.posX, this.posY, + List targets = EntityUtils.getLivingWithinRadius(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, and mind trick if it's not a player (since this has the desired - // effect of preventing targeting) target.addPotionEffect(new PotionEffect(MobEffects.BLINDNESS, duration, 0)); - - if(target instanceof EntityLiving){ - ((EntityLiving)target).setAttackTarget(null); - target.addPotionEffect(new PotionEffect(WizardryPotions.mind_trick, duration, 0)); - } } } diff --git a/src/main/java/electroblob/wizardry/entity/projectile/EntitySparkBomb.java b/src/main/java/electroblob/wizardry/entity/projectile/EntitySparkBomb.java index 721a06a2..facb0dd0 100644 --- a/src/main/java/electroblob/wizardry/entity/projectile/EntitySparkBomb.java +++ b/src/main/java/electroblob/wizardry/entity/projectile/EntitySparkBomb.java @@ -3,11 +3,11 @@ package electroblob.wizardry.entity.projectile; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.EntityUtils; 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.player.EntityPlayer; @@ -55,7 +55,7 @@ public class EntitySparkBomb extends EntityBomb { double seekerRange = Spells.spark_bomb.getProperty(Spell.EFFECT_RADIUS).doubleValue() * blastMultiplier; - List targets = WizardryUtilities.getEntitiesWithinRadius(seekerRange, this.posX, this.posY, + List targets = EntityUtils.getLivingWithinRadius(seekerRange, this.posX, this.posY, this.posZ, this.world); for(int i = 0; i < Math.min(targets.size(), Spells.spark_bomb.getProperty(SECONDARY_MAX_TARGETS).intValue()); i++){ @@ -81,7 +81,7 @@ public class EntitySparkBomb extends EntityBomb { }else{ ParticleBuilder.create(Type.LIGHTNING).pos(this.getPositionVector()).target(target).spawn(world); - ParticleBuilder.spawnShockParticles(world, target.posX, target.getEntityBoundingBox().minY + target.height/2, target.posZ); + ParticleBuilder.spawnShockParticles(world, target.posX, target.posY + target.height/2, target.posZ); } } } diff --git a/src/main/java/electroblob/wizardry/event/SpellBindEvent.java b/src/main/java/electroblob/wizardry/event/SpellBindEvent.java index 2e2de430..08b1d2b5 100644 --- a/src/main/java/electroblob/wizardry/event/SpellBindEvent.java +++ b/src/main/java/electroblob/wizardry/event/SpellBindEvent.java @@ -1,6 +1,6 @@ package electroblob.wizardry.event; -import electroblob.wizardry.tileentity.ContainerArcaneWorkbench; +import electroblob.wizardry.inventory.ContainerArcaneWorkbench; import net.minecraft.entity.player.EntityPlayer; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.entity.player.PlayerContainerEvent; diff --git a/src/main/java/electroblob/wizardry/event/SpellCastEvent.java b/src/main/java/electroblob/wizardry/event/SpellCastEvent.java index ec471598..756cc77b 100644 --- a/src/main/java/electroblob/wizardry/event/SpellCastEvent.java +++ b/src/main/java/electroblob/wizardry/event/SpellCastEvent.java @@ -134,9 +134,9 @@ public abstract class SpellCastEvent extends Event { /** * SpellCastEvent.Pre is fired just before a spell is cast. Use this event to change the spell modifiers and * generally alter the behaviour of the spell, or stop it from being cast entirely. For example, wizardry uses this - * event to cancel spells cast by entities that have the arcane jammer effect. Note that for wands, this is called - * before mana, tier and cooldowns are checked. Also note that this event is only fired once for continuous - * spells, when they start casting.
+ * event to cancel spells cast by entities that have the arcane jammer effect. For wands, this is called + * before mana, tier and cooldowns are checked, and before charge-up. Also note that this event is only fired + * once for continuous spells, when they start casting.
*
* This event is {@link Cancelable}. If this event is canceled, the spell is not cast, mana is not consumed, and the * right-click action that caused it (if any) returns a result of FAIL, meaning that the right-click is passed to diff --git a/src/main/java/electroblob/wizardry/integration/DamageSafetyChecker.java b/src/main/java/electroblob/wizardry/integration/DamageSafetyChecker.java index 2bbfb421..af2e7afc 100644 --- a/src/main/java/electroblob/wizardry/integration/DamageSafetyChecker.java +++ b/src/main/java/electroblob/wizardry/integration/DamageSafetyChecker.java @@ -2,7 +2,7 @@ package electroblob.wizardry.integration; import com.google.common.collect.ImmutableSet; import electroblob.wizardry.Wizardry; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.EntityUtils; import net.minecraft.entity.Entity; import net.minecraft.util.DamageSource; import net.minecraftforge.fml.common.Mod; @@ -58,7 +58,7 @@ public final class DamageSafetyChecker { * @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)}). + * (see {@link EntityUtils#attackEntityWithoutKnockback(Entity, DamageSource, float)}). */ public static boolean attackEntitySafely(Entity target, DamageSource source, float damage, String originalSourceName, DamageSource fallback, boolean knockback){ @@ -70,7 +70,7 @@ public final class DamageSafetyChecker { // No harm in still incrementing the counter attacksThisTick++; return knockback ? target.attackEntityFrom(fallback, damage) - : WizardryUtilities.attackEntityWithoutKnockback(target, fallback, damage); + : EntityUtils.attackEntityWithoutKnockback(target, fallback, damage); } } @@ -88,13 +88,13 @@ public final class DamageSafetyChecker { // Fallback behaviour attacksThisTick++; return knockback ? target.attackEntityFrom(fallback, damage) - : WizardryUtilities.attackEntityWithoutKnockback(target, fallback, damage); + : EntityUtils.attackEntityWithoutKnockback(target, fallback, damage); }else{ // Normal behaviour attacksThisTick++; return knockback ? target.attackEntityFrom(source, damage) - : WizardryUtilities.attackEntityWithoutKnockback(target, source, damage); + : EntityUtils.attackEntityWithoutKnockback(target, source, damage); } } @@ -124,9 +124,9 @@ public final class DamageSafetyChecker { boolean vanillaName = VANILLA_DAMAGE_NAMES.contains(originalSourceName); if(aborted){ - Wizardry.logger.warn("SoundLoopSpellEntity attack excessive call limit reached, aborting entity damage entirely!"); + Wizardry.logger.warn("Entity attack excessive call limit reached, aborting entity damage entirely!"); }else{ - Wizardry.logger.warn("SoundLoopSpellEntity attack excessive call threshold reached, substituting for non-entity-based " + + Wizardry.logger.warn("Entity attack excessive call threshold reached, substituting for non-entity-based " + "damage to avert a crash."); } diff --git a/src/main/java/electroblob/wizardry/integration/antiqueatlas/WizardryAntiqueAtlasIntegration.java b/src/main/java/electroblob/wizardry/integration/antiqueatlas/WizardryAntiqueAtlasIntegration.java index 5eabb205..a85d71ab 100644 --- a/src/main/java/electroblob/wizardry/integration/antiqueatlas/WizardryAntiqueAtlasIntegration.java +++ b/src/main/java/electroblob/wizardry/integration/antiqueatlas/WizardryAntiqueAtlasIntegration.java @@ -29,6 +29,7 @@ public class WizardryAntiqueAtlasIntegration { 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 final ResourceLocation LIBRARY_MARKER = new ResourceLocation(Wizardry.MODID, "library_ruins"); private static boolean antiqueAtlasLoaded; @@ -65,6 +66,14 @@ public class WizardryAntiqueAtlasIntegration { } } + /** Places a global library ruins marker in all antique atlases at the given coordinates in the given world if + * {@link electroblob.wizardry.Settings#autoLibraryMarkers} is enabled. Server side only! */ + public static void markLibrary(World world, int x, int z, boolean underground){ + if(enabled() && (underground ? Wizardry.settings.autoUndergroundLibraryMarkers : Wizardry.settings.autoLibraryMarkers)){ + AtlasAPI.getMarkerAPI().putGlobalMarker(world, false, LIBRARY_MARKER.toString(), "integration.antiqueatlas.marker." + LIBRARY_MARKER.toString().replace(':', '.'), x, z); + } + } + /** Registers the marker icons with Antique Atlas. Client side only! */ public static void registerMarkers(){ @@ -73,6 +82,7 @@ public class WizardryAntiqueAtlasIntegration { 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"))); + AtlasAPI.getMarkerAPI().registerMarker(new MarkerType(LIBRARY_MARKER, new ResourceLocation(Wizardry.MODID, "textures/integration/antiqueatlas/library_ruins.png"))); } @SubscribeEvent diff --git a/src/main/java/electroblob/wizardry/integration/baubles/WizardryBaublesIntegration.java b/src/main/java/electroblob/wizardry/integration/baubles/WizardryBaublesIntegration.java index 11f56ce2..a4f37104 100644 --- a/src/main/java/electroblob/wizardry/integration/baubles/WizardryBaublesIntegration.java +++ b/src/main/java/electroblob/wizardry/integration/baubles/WizardryBaublesIntegration.java @@ -65,7 +65,9 @@ public final class WizardryBaublesIntegration { } /** - * Returns a list of artefact stacks equipped of the given types. + * Returns a list of artefact stacks equipped of the given types. This method does not check whether artefacts + * have been disabled in the config! {@link ItemArtefact#getActiveArtefacts(EntityPlayer, ItemArtefact.Type...)} + * should be used instead of this method in nearly all cases. * @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}. diff --git a/src/main/java/electroblob/wizardry/integration/jei/ArcaneWorkbenchRecipe.java b/src/main/java/electroblob/wizardry/integration/jei/ArcaneWorkbenchRecipe.java new file mode 100644 index 00000000..9adea78d --- /dev/null +++ b/src/main/java/electroblob/wizardry/integration/jei/ArcaneWorkbenchRecipe.java @@ -0,0 +1,119 @@ +package electroblob.wizardry.integration.jei; + +import electroblob.wizardry.client.DrawingUtils; +import electroblob.wizardry.constants.Constants; +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.inventory.ContainerArcaneWorkbench; +import electroblob.wizardry.item.IWorkbenchItem; +import electroblob.wizardry.registry.WizardryItems; +import mezz.jei.api.ingredients.IIngredients; +import mezz.jei.api.ingredients.VanillaTypes; +import mezz.jei.api.recipe.IRecipeWrapper; +import net.minecraft.client.Minecraft; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.MathHelper; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Represents a 'recipe' for the arcane workbench. + * + * @author Electroblob + * @since Wizardry 4.3 + */ +public class ArcaneWorkbenchRecipe implements IRecipeWrapper { + + private final ItemStack centreStack; + private final List books; + private final List crystals; + private final List upgrades; + private final ItemStack result; + + private final int bookSlots; + + private final List> inputs; + + public ArcaneWorkbenchRecipe(ItemStack centreStack, List books, List crystals, List upgrades, ItemStack result){ + + this.centreStack = centreStack; + this.books = books; // CAUTION! This list is an OUTER LIST! + this.crystals = crystals; + this.upgrades = upgrades; + this.result = result; + + this.inputs = new ArrayList<>(); + for(ItemStack book : books) this.inputs.add(Collections.singletonList(book)); + this.inputs.add(crystals); + this.inputs.add(Collections.singletonList(centreStack)); + this.inputs.add(upgrades); + + if(centreStack.getItem() instanceof IWorkbenchItem){ + bookSlots = ((IWorkbenchItem)centreStack.getItem()).getSpellSlotCount(centreStack); + }else{ + bookSlots = 0; + } + + } + + public ArcaneWorkbenchRecipe(ItemStack centreStack, List books, int mana, List upgrades, ItemStack result){ + this(centreStack, books, generateCrystalStacks(mana), upgrades, result); + } + + @Override + public void getIngredients(IIngredients ingredients){ + ingredients.setInputLists(VanillaTypes.ITEM, inputs); + ingredients.setOutput(VanillaTypes.ITEM, result); + } + + @Override + public void drawInfo(Minecraft minecraft, int recipeWidth, int recipeHeight, int mouseX, int mouseY){ + // Can't do this in IRecipeCategory#drawExtras because we have no access to the recipe there! + // ArcaneWorkbenchRecipeCategory.TEXTURE is already bound at this point + for(int i = 0; i < bookSlots; i++){ + int x = ArcaneWorkbenchRecipeCategory.CENTRE_SLOT_X + ContainerArcaneWorkbench.getBookSlotXOffset(i, bookSlots) - 9; + int y = ArcaneWorkbenchRecipeCategory.CENTRE_SLOT_Y + ContainerArcaneWorkbench.getBookSlotYOffset(i, bookSlots) - 9; + DrawingUtils.drawTexturedRect(x, y, 0, ArcaneWorkbenchRecipeCategory.HEIGHT, 36, 36, 256, 256); + } + } + + /** + * Returns a list of item stacks, one for each type of crystal (regular, elemental, grand and shards), each with + * the minimum quantity needed to supply the given amount of mana. Types of crystal for which more than the max. + * stack size would be needed are ignored. + */ + public static List generateCrystalStacks(int mana){ + + if(mana < 0) throw new IllegalArgumentException("Cannot create an arcane workbench recipe with negative mana!"); + + if(mana == 0) return Collections.emptyList(); + + List crystalStacks = new ArrayList<>(); + + int count = MathHelper.ceil((float)mana / Constants.MANA_PER_CRYSTAL); + // A stack of crystals will almost certainly be enough mana, but you never know! + // Using ItemStack.EMPTY to avoid deprecated method; crystals' stack size is not stack-sensitive so it doesn't matter + if(count <= WizardryItems.magic_crystal.getItemStackLimit(ItemStack.EMPTY)){ + for(int meta = 0; meta < Element.values().length; meta++){ + crystalStacks.add(new ItemStack(WizardryItems.magic_crystal, count, meta)); + } + } + + count = MathHelper.ceil((float)mana / Constants.MANA_PER_SHARD); + + if(count <= WizardryItems.crystal_shard.getItemStackLimit(ItemStack.EMPTY)){ + crystalStacks.add(new ItemStack(WizardryItems.crystal_shard, count)); + } + + count = MathHelper.ceil((float)mana / Constants.GRAND_CRYSTAL_MANA); + + if(count <= WizardryItems.grand_crystal.getItemStackLimit(ItemStack.EMPTY)){ + crystalStacks.add(new ItemStack(WizardryItems.grand_crystal, count)); + } + + return crystalStacks; + + } + +} diff --git a/src/main/java/electroblob/wizardry/integration/jei/ArcaneWorkbenchRecipeCategory.java b/src/main/java/electroblob/wizardry/integration/jei/ArcaneWorkbenchRecipeCategory.java new file mode 100644 index 00000000..10a0545e --- /dev/null +++ b/src/main/java/electroblob/wizardry/integration/jei/ArcaneWorkbenchRecipeCategory.java @@ -0,0 +1,272 @@ +package electroblob.wizardry.integration.jei; + +import com.google.common.collect.Streams; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.constants.Constants; +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.inventory.ContainerArcaneWorkbench; +import electroblob.wizardry.item.*; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.registry.WizardryRecipes; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.WandHelper; +import mezz.jei.api.IGuiHelper; +import mezz.jei.api.gui.IDrawable; +import mezz.jei.api.gui.IGuiItemStackGroup; +import mezz.jei.api.gui.IRecipeLayout; +import mezz.jei.api.ingredients.IIngredients; +import mezz.jei.api.ingredients.VanillaTypes; +import mezz.jei.api.recipe.IRecipeCategory; +import mezz.jei.api.recipe.IRecipeCategoryRegistration; +import net.minecraft.client.resources.I18n; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.NonNullList; +import net.minecraft.util.ResourceLocation; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * JEI recipe category implementation for all 'recipes' in the arcane workbench (of course, the arcane workbench + * doesn't have 'recipes' in the normal sense, but that's how they're displayed in JEI). + * + * @author Electroblob + * @since Wizardry 4.3 + */ +public class ArcaneWorkbenchRecipeCategory implements IRecipeCategory { + + static final String UID = "ebwizardry:arcane_workbench"; + static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/integration/jei/arcane_workbench_background.png"); + + static final int WIDTH = 166; + static final int HEIGHT = 126; + // Annoyingly, these seem to be for 18x18 slots rather than the actual highlighted area, unlike container slots + static final int CENTRE_SLOT_X = 74; + static final int CENTRE_SLOT_Y = 54; + static final int CRYSTAL_SLOT_X = 7; + static final int CRYSTAL_SLOT_Y = 91; + static final int UPGRADE_SLOT_X = 141; + static final int UPGRADE_SLOT_Y = 7; + static final int OUTPUT_SLOT_X = 141; + static final int OUTPUT_SLOT_Y = 101; + + private final IDrawable background; + + public ArcaneWorkbenchRecipeCategory(IRecipeCategoryRegistration registry){ + IGuiHelper helper = registry.getJeiHelpers().getGuiHelper(); + background = helper.createDrawable(TEXTURE, 0, 0, WIDTH, HEIGHT); + } + + @Override + public String getUid(){ + return UID; + } + + @Override + public String getTitle(){ + // JEI is client-side, so client classes can safely be used here + return I18n.format("integration.jei.category." + UID); + } + + @Override + public String getModName(){ + return Wizardry.NAME; + } + + @Override + public IDrawable getBackground(){ + return background; + } + + @Override + public void setRecipe(IRecipeLayout recipeLayout, ArcaneWorkbenchRecipe recipeWrapper, IIngredients ingredients){ + + // Okay, they're not technically *slots* but to all intents and purposes, that's how they behave + IGuiItemStackGroup slots = recipeLayout.getItemStacks(); + + List> inputs = ingredients.getInputs(VanillaTypes.ITEM); + List> outputs = ingredients.getOutputs(VanillaTypes.ITEM); + + ItemStack centreStack = inputs.get(inputs.size() - 2).get(0); + + int bookSlots = 0; + + if(centreStack.getItem() instanceof IWorkbenchItem){ + bookSlots = ((IWorkbenchItem)centreStack.getItem()).getSpellSlotCount(centreStack); + } + + // Slot initialisation + int i = 0; + + while(i < bookSlots){ + int x = CENTRE_SLOT_X + ContainerArcaneWorkbench.getBookSlotXOffset(i, bookSlots); + int y = CENTRE_SLOT_Y + ContainerArcaneWorkbench.getBookSlotYOffset(i, bookSlots); + slots.init(i++, true, x, y); + } + + // Add dummy slots for the hidden book slots so the transfer handler works correctly + // Sure, we COULD use an IRecipeTransferHandler, but this is far less effort! + while(i < ContainerArcaneWorkbench.CRYSTAL_SLOT){ + slots.init(i++, true, 0, 0); + } + + slots.init(i++, true, CRYSTAL_SLOT_X, CRYSTAL_SLOT_Y); + slots.init(i++, true, CENTRE_SLOT_X, CENTRE_SLOT_Y); + slots.init(i++, true, UPGRADE_SLOT_X, UPGRADE_SLOT_Y); + + slots.init(i++, false, OUTPUT_SLOT_X, OUTPUT_SLOT_Y); + + // Assign ingredients to slots + // The number of books we actually have is inputs.size() - 3, probably less than the number of book slots + for(int j = 0; j < Math.min(bookSlots, inputs.size() - 3); j++){ + slots.set(j, inputs.get(j)); + } + + slots.set(ContainerArcaneWorkbench.CRYSTAL_SLOT, inputs.get(inputs.size() - 3)); + slots.set(ContainerArcaneWorkbench.CENTRE_SLOT, inputs.get(inputs.size() - 2)); + slots.set(ContainerArcaneWorkbench.UPGRADE_SLOT, inputs.get(inputs.size() - 1)); + + for(int k = 0; k < outputs.size(); k++) slots.set(11 + k, outputs.get(k)); + + } + + /** Called to generate all of wizardry's arcane workbench 'recipes' for JEI. */ + public static Collection generateRecipes(){ + + List recipes = new ArrayList<>(); + + recipes.addAll(generateUpgradeRecipes()); // Probably nicest to have these first, they're the most useful + recipes.addAll(generateChargingRecipes()); + recipes.addAll(generateScrollRecipes()); + + return recipes; + + } + + private static Collection generateUpgradeRecipes(){ + + List recipes = new ArrayList<>(); + + List upgrades = new ArrayList<>(); + + for(Item item : Item.REGISTRY){ + if(item instanceof ItemArcaneTome || item instanceof ItemArmourUpgrade){ + NonNullList variants = NonNullList.create(); + item.getSubItems(item.getCreativeTab(), variants); + upgrades.addAll(variants); + } + } + + // Condense all special upgrades into one ingredient in an effort to reduce the number of separate recipes + List specialUpgrades = new ArrayList<>(); + + for(Item item : WandHelper.getSpecialUpgrades()){ + NonNullList variants = NonNullList.create(); + item.getSubItems(item.getCreativeTab(), variants); + specialUpgrades.addAll(variants); + } + + for(Item item : Item.REGISTRY){ + + if(item instanceof IWorkbenchItem){ + + ItemStack original = new ItemStack(item); + + for(ItemStack upgrade : upgrades){ + // Copy both input stacks to ignore any modifications to them during the upgrading process + ItemStack result = ((IWorkbenchItem)item).applyUpgrade(null, original.copy(), upgrade.copy()); + // It's only a valid 'recipe' if something actually changed + if(!ItemStack.areItemStacksEqual(original, result)){ + recipes.add(new ArcaneWorkbenchRecipe(original, Collections.emptyList(), Collections.emptyList(), + Collections.singletonList(upgrade), result)); + } + } + + List applicableSpecialUpgrades = new ArrayList<>(); + + for(ItemStack upgrade : specialUpgrades){ + // Copy both input stacks to ignore any modifications to them during the upgrading process + ItemStack result = ((IWorkbenchItem)item).applyUpgrade(null, original.copy(), upgrade.copy()); + // It's only a valid 'recipe' if something actually changed + if(!ItemStack.areItemStacksEqual(original, result)){ + applicableSpecialUpgrades.add(upgrade); + } + } + + if(!applicableSpecialUpgrades.isEmpty()){ + recipes.add(new ArcaneWorkbenchRecipe(original, Collections.emptyList(), Collections.emptyList(), + applicableSpecialUpgrades, original)); // Wands with special upgrades look no different anyway + } + + } + } + + return recipes; + + } + + private static Collection generateChargingRecipes(){ + + List recipes = new ArrayList<>(); + + List crystals = new ArrayList<>(); + for(int meta = 0; meta < Element.values().length; meta++) crystals.add(new ItemStack(WizardryItems.magic_crystal, 1, meta)); + List shard = Collections.singletonList(new ItemStack(WizardryItems.crystal_shard)); + List grandCrystal = Collections.singletonList(new ItemStack(WizardryItems.grand_crystal)); + + for(Item chargeable : WizardryRecipes.getChargeableItems()){ + + if(!(chargeable instanceof IManaStoringItem)) throw new IllegalArgumentException("Item to be charged must be an instance of IManaStoringItem"); + + ItemStack input = new ItemStack(chargeable); + ((IManaStoringItem)chargeable).setMana(input, 0); + + ItemStack result = new ItemStack(chargeable); + ((IManaStoringItem)chargeable).setMana(result, Constants.MANA_PER_CRYSTAL); + recipes.add(new ArcaneWorkbenchRecipe(input, Collections.emptyList(), crystals, Collections.emptyList(), result)); + + result = new ItemStack(chargeable); + ((IManaStoringItem)chargeable).setMana(result, Constants.MANA_PER_SHARD); + recipes.add(new ArcaneWorkbenchRecipe(input, Collections.emptyList(), shard, Collections.emptyList(), result)); + + result = new ItemStack(chargeable); + ((IManaStoringItem)chargeable).setMana(result, Constants.GRAND_CRYSTAL_MANA); + recipes.add(new ArcaneWorkbenchRecipe(input, Collections.emptyList(), grandCrystal, Collections.emptyList(), result)); + } + + return recipes; + + } + + private static Collection generateScrollRecipes(){ + + List recipes = new ArrayList<>(); + + ItemStack blankScroll = new ItemStack(WizardryItems.blank_scroll); + + // We need not make people register these manually since spells already have control over what they can be put on + List spellBooks = Streams.stream(Item.REGISTRY).filter(i -> i instanceof ItemSpellBook).collect(Collectors.toList()); + List scrolls = Streams.stream(Item.REGISTRY).filter(i -> i instanceof ItemScroll).collect(Collectors.toList()); + + for(Spell spell : Spell.getAllSpells()){ + + for(Item spellBook : spellBooks){ + for(Item scroll : scrolls){ + if(spell.applicableForItem(spellBook) && spell.applicableForItem(scroll)){ + List books = Collections.singletonList(new ItemStack(WizardryItems.spell_book, 1, spell.metadata())); + ItemStack result = new ItemStack(WizardryItems.scroll, 1, spell.metadata()); + recipes.add(new ArcaneWorkbenchRecipe(blankScroll, books, spell.getCost(), Collections.emptyList(), result)); + } + } + } + } + + return recipes; + + } + +} diff --git a/src/main/java/electroblob/wizardry/integration/jei/ArcaneWorkbenchTransferHandler.java b/src/main/java/electroblob/wizardry/integration/jei/ArcaneWorkbenchTransferHandler.java new file mode 100644 index 00000000..a911bd1a --- /dev/null +++ b/src/main/java/electroblob/wizardry/integration/jei/ArcaneWorkbenchTransferHandler.java @@ -0,0 +1,52 @@ +package electroblob.wizardry.integration.jei; + +import electroblob.wizardry.inventory.ContainerArcaneWorkbench; +import mezz.jei.api.recipe.transfer.IRecipeTransferInfo; +import net.minecraft.inventory.Slot; + +import java.util.ArrayList; +import java.util.List; + +/** + * JEI recipe transfer handler for the arcane workbench. This differs from a standard recipe handler in that it returns + * the active bookshelf (virtual) slots from the workbench as part of the inventory slots, and does not require complete + * sets of ingredients. + * + * @author Electroblob + * @since Wizardry 4.3 + */ +public class ArcaneWorkbenchTransferHandler implements IRecipeTransferInfo { + + @Override + public Class getContainerClass(){ + return ContainerArcaneWorkbench.class; + } + + @Override + public String getRecipeCategoryUid(){ + return ArcaneWorkbenchRecipeCategory.UID; + } + + @Override + public boolean canHandle(ContainerArcaneWorkbench container){ + return true; + } + + @Override + public boolean requireCompleteSets(){ + return false; // Arcane workbench maths doesn't work like crafting maths! + } + + @Override + public List getRecipeSlots(ContainerArcaneWorkbench container){ + return container.inventorySlots.subList(0, ContainerArcaneWorkbench.UPGRADE_SLOT + 1); + } + + @Override + public List getInventorySlots(ContainerArcaneWorkbench container){ + List slots = new ArrayList<>(container.inventorySlots.subList(ContainerArcaneWorkbench.UPGRADE_SLOT + 1, ContainerArcaneWorkbench.UPGRADE_SLOT + 37)); + slots.addAll(container.getBookshelfSlots()); + return slots; + } + +} diff --git a/src/main/java/electroblob/wizardry/integration/jei/ImbuementAltarRecipe.java b/src/main/java/electroblob/wizardry/integration/jei/ImbuementAltarRecipe.java new file mode 100644 index 00000000..eb7cc84e --- /dev/null +++ b/src/main/java/electroblob/wizardry/integration/jei/ImbuementAltarRecipe.java @@ -0,0 +1,49 @@ +package electroblob.wizardry.integration.jei; + +import electroblob.wizardry.Wizardry; +import mezz.jei.api.ingredients.IIngredients; +import mezz.jei.api.ingredients.VanillaTypes; +import mezz.jei.api.recipe.IRecipeWrapper; +import net.minecraft.item.ItemStack; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Represents a 'recipe' for the imbuement altar. + * + * @author Electroblob + * @since Wizardry 4.3 + */ +public class ImbuementAltarRecipe implements IRecipeWrapper { + + private final ItemStack result; + + private final List> inputs; + + public ImbuementAltarRecipe(ItemStack centreStack, List> dusts, ItemStack result){ + + this.result = result; + + this.inputs = new ArrayList<>(); + this.inputs.add(Collections.singletonList(centreStack)); + + if(dusts.size() > 4){ + Wizardry.logger.warn("Tried to create an imbuement altar JEI recipe with more than 4 dust stacks, ignoring the rest"); + dusts = dusts.subList(0, 4); + }else if(dusts.size() < 4){ + throw new IllegalArgumentException("Imbuement altar recipes require 4 stacks of dust"); + } + + this.inputs.addAll(dusts); + + } + + @Override + public void getIngredients(IIngredients ingredients){ + ingredients.setInputLists(VanillaTypes.ITEM, inputs); + ingredients.setOutput(VanillaTypes.ITEM, result); + } + +} diff --git a/src/main/java/electroblob/wizardry/integration/jei/ImbuementAltarRecipeCategory.java b/src/main/java/electroblob/wizardry/integration/jei/ImbuementAltarRecipeCategory.java new file mode 100644 index 00000000..d9953e24 --- /dev/null +++ b/src/main/java/electroblob/wizardry/integration/jei/ImbuementAltarRecipeCategory.java @@ -0,0 +1,194 @@ +package electroblob.wizardry.integration.jei; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.item.ItemWizardArmour; +import electroblob.wizardry.registry.WizardryBlocks; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.tileentity.TileEntityImbuementAltar; +import mezz.jei.api.IGuiHelper; +import mezz.jei.api.gui.IDrawable; +import mezz.jei.api.gui.IGuiItemStackGroup; +import mezz.jei.api.gui.IRecipeLayout; +import mezz.jei.api.ingredients.IIngredients; +import mezz.jei.api.ingredients.VanillaTypes; +import mezz.jei.api.recipe.IRecipeCategory; +import mezz.jei.api.recipe.IRecipeCategoryRegistration; +import net.minecraft.client.resources.I18n; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.NonNullList; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.oredict.OreDictionary; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * JEI recipe category implementation for all 'recipes' in the imbuement altar. + * + * @author Electroblob + * @since Wizardry 4.3 + */ +public class ImbuementAltarRecipeCategory implements IRecipeCategory { + + static final String UID = "ebwizardry:imbuement_altar"; + static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/integration/jei/imbuement_altar_background.png"); + + static final int WIDTH = 134; + static final int HEIGHT = 74; + // Annoyingly, these seem to be for 18x18 slots rather than the actual highlighted area, unlike container slots + static final int CENTRE_SLOT_X = 28; + static final int CENTRE_SLOT_Y = 28; + static final int SLOT_SPACING_X = 28; + static final int SLOT_SPACING_Y = 28; + static final int OUTPUT_SLOT_X = 112; + static final int OUTPUT_SLOT_Y = 28; + + private final IDrawable background; + + public ImbuementAltarRecipeCategory(IRecipeCategoryRegistration registry){ + IGuiHelper helper = registry.getJeiHelpers().getGuiHelper(); + background = helper.createDrawable(TEXTURE, 0, 0, WIDTH, HEIGHT); + } + + @Override + public String getUid(){ + return UID; + } + + @Override + public String getTitle(){ + // JEI is client-side, so client classes can safely be used here + return I18n.format("integration.jei.category." + UID); + } + + @Override + public String getModName(){ + return Wizardry.NAME; + } + + @Override + public IDrawable getBackground(){ + return background; + } + + @Override + public void setRecipe(IRecipeLayout recipeLayout, ImbuementAltarRecipe recipeWrapper, IIngredients ingredients){ + + // Okay, they're not technically *slots* but to all intents and purposes, that's how they behave + IGuiItemStackGroup slots = recipeLayout.getItemStacks(); + + List> inputs = ingredients.getInputs(VanillaTypes.ITEM); + List> outputs = ingredients.getOutputs(VanillaTypes.ITEM); + + // Slot initialisation + int i = 0; + slots.init(i++, true, CENTRE_SLOT_X, CENTRE_SLOT_Y); // Centre + slots.init(i++, true, CENTRE_SLOT_X, CENTRE_SLOT_Y - SLOT_SPACING_Y); // Top + slots.init(i++, true, CENTRE_SLOT_X - SLOT_SPACING_X, CENTRE_SLOT_Y); // Left + slots.init(i++, true, CENTRE_SLOT_X, CENTRE_SLOT_Y + SLOT_SPACING_Y); // Bottom + slots.init(i++, true, CENTRE_SLOT_X + SLOT_SPACING_X, CENTRE_SLOT_Y); // Right + slots.init(i++, false, OUTPUT_SLOT_X, OUTPUT_SLOT_Y); // Output + + // Assign ingredients to slots + for(int j = 0; j < inputs.size(); j++) slots.set(j, inputs.get(j)); + for(int k = 0; k < outputs.size(); k++) slots.set(inputs.size() + k, outputs.get(k)); + + } + + /** Called to generate all of wizardry's imbuement altar 'recipes' for JEI. */ + public static Collection generateRecipes(){ + + List recipes = new ArrayList<>(); + + recipes.addAll(generateBookRecipes()); + recipes.addAll(generateCrystalRecipes()); + recipes.addAll(generateCrystalBlockRecipes()); + recipes.addAll(generateArmourRecipes()); + + return recipes; + + } + + private static Collection generateBookRecipes(){ + + List recipes = new ArrayList<>(); + + NonNullList variants = NonNullList.create(); + WizardryItems.spectral_dust.getSubItems(WizardryItems.spectral_dust.getCreativeTab(), variants); + + List> dusts = new ArrayList<>(); + // Generate 4 separate lists, each in a different order to make it obvious they can be any element + for(int i = 0; i < 4; i++){ + Collections.shuffle(variants); + dusts.add(new ArrayList<>(variants)); + } + + recipes.add(new ImbuementAltarRecipe(new ItemStack(WizardryItems.ruined_spell_book), dusts, new ItemStack(WizardryItems.spell_book, 1, OreDictionary.WILDCARD_VALUE))); + + return recipes; + + } + + private static Collection generateCrystalRecipes(){ + + List recipes = new ArrayList<>(); + + ItemStack input = new ItemStack(WizardryItems.magic_crystal); + + for(int meta = 1; meta < Element.values().length; meta++){ + List> dusts = Collections.nCopies(4, Collections.singletonList(new ItemStack(WizardryItems.spectral_dust, 1, meta))); + ItemStack output = new ItemStack(WizardryItems.magic_crystal, 1, meta); + recipes.add(new ImbuementAltarRecipe(input, dusts, output)); + } + + return recipes; + + } + + private static Collection generateCrystalBlockRecipes(){ + + List recipes = new ArrayList<>(); + + ItemStack input = new ItemStack(WizardryBlocks.crystal_block); + + for(int meta = 1; meta < Element.values().length; meta++){ + List> dusts = Collections.nCopies(4, Collections.singletonList(new ItemStack(WizardryItems.spectral_dust, 1, meta))); + ItemStack output = new ItemStack(WizardryBlocks.crystal_block, 1, meta); + recipes.add(new ImbuementAltarRecipe(input, dusts, output)); + } + + return recipes; + + } + + private static Collection generateArmourRecipes(){ + + List recipes = new ArrayList<>(); + + for(Item item : Item.REGISTRY){ + + if(item instanceof ItemWizardArmour){ + + ItemStack input = new ItemStack(item); + + for(Element e : Element.values()){ + + if(e == Element.MAGIC) continue; + + List> dusts = Collections.nCopies(4, Collections.singletonList(new ItemStack(WizardryItems.spectral_dust, 1, e.ordinal()))); + ItemStack output = TileEntityImbuementAltar.getImbuementResult(input, new Element[]{e, e, e, e}, false, null, null); + + if(!output.isEmpty()) recipes.add(new ImbuementAltarRecipe(input, dusts, output)); + } + } + } + + return recipes; + + } + +} diff --git a/src/main/java/electroblob/wizardry/integration/jei/WizardryJEIPlugin.java b/src/main/java/electroblob/wizardry/integration/jei/WizardryJEIPlugin.java new file mode 100644 index 00000000..104022ad --- /dev/null +++ b/src/main/java/electroblob/wizardry/integration/jei/WizardryJEIPlugin.java @@ -0,0 +1,149 @@ +package electroblob.wizardry.integration.jei; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.enchantment.Imbuement; +import electroblob.wizardry.item.IConjuredItem; +import electroblob.wizardry.item.ItemArcaneTome; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.item.ItemWandUpgrade; +import electroblob.wizardry.registry.WizardryBlocks; +import electroblob.wizardry.registry.WizardryEnchantments; +import electroblob.wizardry.registry.WizardryItems; +import mezz.jei.api.IModPlugin; +import mezz.jei.api.IModRegistry; +import mezz.jei.api.ISubtypeRegistry; +import mezz.jei.api.JEIPlugin; +import mezz.jei.api.ingredients.IIngredientBlacklist; +import mezz.jei.api.ingredients.VanillaTypes; +import mezz.jei.api.recipe.IRecipeCategoryRegistration; +import net.minecraft.enchantment.Enchantment; +import net.minecraft.enchantment.EnchantmentData; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.NonNullList; + +import java.util.Arrays; +import java.util.Locale; + +@JEIPlugin +public class WizardryJEIPlugin implements IModPlugin { + + @Override + public void registerCategories(IRecipeCategoryRegistration registry){ + + if(!Wizardry.settings.jeiIntegration) return; + + registry.addRecipeCategories(new ArcaneWorkbenchRecipeCategory(registry)); + registry.addRecipeCategories(new ImbuementAltarRecipeCategory(registry)); + } + + @Override + public void register(IModRegistry registry){ + + if(!Wizardry.settings.jeiIntegration) return; + + // Add arcane workbench as the item required to use arcane workbench recipes + registry.addRecipeCatalyst(new ItemStack(WizardryBlocks.arcane_workbench), ArcaneWorkbenchRecipeCategory.UID); + registry.addRecipes(ArcaneWorkbenchRecipeCategory.generateRecipes(), ArcaneWorkbenchRecipeCategory.UID); + registry.getRecipeTransferRegistry().addRecipeTransferHandler(new ArcaneWorkbenchTransferHandler()); + + // Add imbuement altar as the item required to use imbuement altar recipes + registry.addRecipeCatalyst(new ItemStack(WizardryBlocks.imbuement_altar), ImbuementAltarRecipeCategory.UID); + registry.addRecipes(ImbuementAltarRecipeCategory.generateRecipes(), ImbuementAltarRecipeCategory.UID); + + // Hide conjured items from JEI + IIngredientBlacklist blacklist = registry.getJeiHelpers().getIngredientBlacklist(); + for(Item item : Item.REGISTRY){ + if(item instanceof IConjuredItem) blacklist.addIngredientToBlacklist(new ItemStack(item)); + } + + // Hide imbuements from JEI + for(Enchantment enchantment : Enchantment.REGISTRY){ + if(enchantment instanceof Imbuement){ + for(int level = enchantment.getMinLevel(); level <= enchantment.getMaxLevel(); level++){ + blacklist.addIngredientToBlacklist(new EnchantmentData(enchantment, level)); + } + } + } + + addItemInfo(registry, WizardryItems.magic_crystal); + addItemInfo(registry, WizardryItems.wizard_handbook); + addItemInfo(registry, WizardryItems.crystal_shard); + addItemInfo(registry, WizardryItems.grand_crystal); + addItemInfo(registry, WizardryItems.identification_scroll, ".desc_extended"); + addItemInfo(registry, WizardryItems.spell_book); + addItemInfo(registry, WizardryItems.scroll); + addItemInfo(registry, WizardryItems.armour_upgrade, ".desc_extended"); + addItemInfo(registry, WizardryItems.purifying_elixir, ".desc_extended"); + addItemInfo(registry, WizardryItems.astral_diamond); + addItemInfo(registry, WizardryItems.ruined_spell_book); + + for(Item item : Item.REGISTRY){ + + if(item instanceof ItemArtefact){ + + ItemStack stack = new ItemStack(item); + registry.addIngredientInfo(stack, VanillaTypes.ITEM, "item." + Wizardry.MODID + ":" + + ((ItemArtefact)item).getType().toString().toLowerCase(Locale.ROOT) + ".generic.desc"); + + }else if(item instanceof ItemWandUpgrade || item instanceof ItemArcaneTome){ + + NonNullList subItems = NonNullList.create(); + item.getSubItems(item.getCreativeTab(), subItems); + + for(ItemStack stack : subItems){ + registry.addIngredientInfo(stack, VanillaTypes.ITEM, "item." + Wizardry.MODID + + ":wand_upgrade.generic.desc"); + } + } + } + + addEnchantmentInfo(registry, WizardryEnchantments.magic_protection); + addEnchantmentInfo(registry, WizardryEnchantments.frost_protection); + addEnchantmentInfo(registry, WizardryEnchantments.shock_protection); + + } + + @Override + public void registerItemSubtypes(ISubtypeRegistry subtypeRegistry){ + + if(!Wizardry.settings.jeiIntegration) return; + + // TODO: Probably need to distinguish between normal/legendary armour, at the very least + // ... that being said, we may well be changing that mechanic anyway + + } + + private static void addItemInfo(IModRegistry registry, Item item){ + addItemInfo(registry, item, ".desc"); + } + + private static void addItemInfo(IModRegistry registry, Item item, String... suffixes){ + NonNullList subItems = NonNullList.create(); + item.getSubItems(item.getCreativeTab(), subItems); + for(ItemStack stack : subItems) addItemInfo(registry, stack, suffixes); + } + + private static void addItemInfo(IModRegistry registry, ItemStack stack, String... suffixes){ + String prefix = stack.getItem().getTranslationKey(stack); + String[] keys = Arrays.stream(suffixes).map(s -> prefix + s).toArray(String[]::new); + registry.addIngredientInfo(stack, VanillaTypes.ITEM, keys); + } + + private static void addEnchantmentInfo(IModRegistry registry, Enchantment enchantment){ + addEnchantmentInfo(registry, enchantment, ".desc"); + } + + private static void addEnchantmentInfo(IModRegistry registry, Enchantment enchantment, String... suffixes){ + for(int level = enchantment.getMinLevel(); level <= enchantment.getMaxLevel(); level++){ + addEnchantmentInfo(registry, new EnchantmentData(enchantment, level), suffixes); + } + } + + private static void addEnchantmentInfo(IModRegistry registry, EnchantmentData data, String... suffixes){ + String prefix = data.enchantment.getName().replace(":", "."); // Compat with WAWLA descriptions + String[] keys = Arrays.stream(suffixes).map(s -> prefix + s).toArray(String[]::new); + registry.addIngredientInfo(data, VanillaTypes.ENCHANT, keys); + } + +} diff --git a/src/main/java/electroblob/wizardry/inventory/ContainerArcaneWorkbench.java b/src/main/java/electroblob/wizardry/inventory/ContainerArcaneWorkbench.java new file mode 100644 index 00000000..cc770a03 --- /dev/null +++ b/src/main/java/electroblob/wizardry/inventory/ContainerArcaneWorkbench.java @@ -0,0 +1,573 @@ +package electroblob.wizardry.inventory; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.block.BlockBookshelf; +import electroblob.wizardry.event.SpellBindEvent; +import electroblob.wizardry.item.IWorkbenchItem; +import electroblob.wizardry.item.ItemSpellBook; +import electroblob.wizardry.registry.WizardryAdvancementTriggers; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.tileentity.TileEntityArcaneWorkbench; +import electroblob.wizardry.util.ISpellSortable; +import electroblob.wizardry.util.InventoryUtils; +import electroblob.wizardry.util.WandHelper; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.inventory.ClickType; +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.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; +import net.minecraftforge.common.MinecraftForge; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.*; +import java.util.stream.Collectors; + +/** + * The container for the arcane workbench GUI. + *

+ * The virtual slot system works as follows:

+ * - The container has two types of slots: {@link SlotBookList} and {@link VirtualSlot}.
+ * - The {@code SlotBookList}s are the ones that actually get displayed. They essentially delegate all their + * functions to the relevant {@code VirtualSlot}.
+ * - Each {@code VirtualSlot} refers to a specific slot in another {@link IInventory} nearby, but is not displayed + * directly on the GUI. They are sorted and filtered according to the GUI input via + * {@link ContainerArcaneWorkbench#getActiveBookshelfSlots()}.
+ * - When a stack is taken from a {@code SlotBookList} (or its current stack is queried), the {@code VirtualSlot} + * it delegates to depends on the current search term, sort order and state of the linked bookshelves.
+ * - When a stack is inserted into a {@code SlotBookList}, the {@code VirtualSlot} it delegates to depends + * instead on where the stack came from originally and which virtual slots are free.
+ * - Finally, the {@code SlotBookList}s are only really used on the client side (though they are included on the server + * for consistency, just in case). The bookshelf slot delegates to a virtual slot on the client side (which is where + * the search and sorting is done), and then the click is sent to the server. + */ +public class ContainerArcaneWorkbench extends Container implements ISpellSortable { + + /** The arcane workbench tile entity associated with this container. */ + public TileEntityArcaneWorkbench tileentity; + + public static final ResourceLocation EMPTY_SLOT_CRYSTAL = new ResourceLocation(Wizardry.MODID, "gui/container/empty_slot_crystal"); + public static final ResourceLocation EMPTY_SLOT_UPGRADE = new ResourceLocation(Wizardry.MODID, "gui/container/empty_slot_upgrade"); + + public static final int CRYSTAL_SLOT = 8; + public static final int CENTRE_SLOT = 9; + public static final int UPGRADE_SLOT = 10; + + public static final int SLOT_RADIUS = 42; + + public static final int BOOKSHELF_SLOTS_X = 5; + public static final int BOOKSHELF_SLOTS_Y = 10; + + public static final int PLAYER_INVENTORY_SIZE = 36; + + public static final int BOOKSHELF_UI_WIDTH = 122; + + private List bookshelfSlots = new ArrayList<>(); + private List activeBookshelfSlots = new ArrayList<>(); + + private boolean hasBookshelves; + + private int scroll = 0; + private ISpellSortable.SortType sortType = ISpellSortable.SortType.TIER; + private boolean sortDescending = false; + private String searchText = ""; + + public boolean needsRefresh; + + public ContainerArcaneWorkbench(IInventory inventory, TileEntityArcaneWorkbench tileentity){ + + this.tileentity = tileentity; + + ItemStack wand = tileentity.getStackInSlot(CENTRE_SLOT); + + for(int i = 0; i < 8; i++){ + Slot slot = new SlotItemClassList(tileentity, i, -999, -999, 1, ItemSpellBook.class); + this.addSlotToContainer(slot); + } + + 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 SlotWorkbenchItem(tileentity, CENTRE_SLOT, 80, 64, this)); + + 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, 147, 17, 1, upgrades.toArray(new Item[0]))) + .setBackgroundName(EMPTY_SLOT_UPGRADE.toString()); + + for(int x = 0; x < 9; x++){ + this.addSlotToContainer(new Slot(inventory, x, 8 + x * 18, 196)); + } + + for(int y = 0; y < 3; y++){ + for(int x = 0; x < 9; x++){ + this.addSlotToContainer(new Slot(inventory, 9 + x + y * 9, 8 + x * 18, 138 + y * 18)); + } + } + + for(int y = 0; y < BOOKSHELF_SLOTS_Y; y++){ + for(int x = 0; x < BOOKSHELF_SLOTS_X; x++){ + int index = x + y * BOOKSHELF_SLOTS_X; + this.addSlotToContainer(new SlotBookList(tileentity, UPGRADE_SLOT + 1 + index, 8 + x * 18, 34 + y * 18, this, index)); + } + } + + refreshBookshelfSlots(); // Must be done last + + 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. + */ + // We're not using slot.isEnabled() because it would mean making another slot class, and we'd still need to handle + // what happens to the contents when the slot is hidden + 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 == CENTRE_SLOT){ + + if(stack.isEmpty()){ + // If the stack has been removed, hide all the spell book slots + for(int i = 0; i < CRYSTAL_SLOT; i++){ + this.hideSlot(i, player); + } + + }else{ + + 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++){ + int x = centreX + getBookSlotXOffset(i, spellSlots); + int y = centreY + getBookSlotYOffset(i, spellSlots); + showSlot(i, x, y); + } + + // Hide the rest + for(int i = spellSlots; i < CRYSTAL_SLOT; i++){ + hideSlot(i, player); + } + + } + } + } + } + + /** Returns the x offset (relative to the central slot) of the ith book slot when the total number of book slots is + * equal to {@code bookSlotCount}. */ + public static int getBookSlotXOffset(int i, int bookSlotCount){ + float angle = i * (2 * (float)Math.PI) / bookSlotCount; + return Math.round(SLOT_RADIUS * MathHelper.sin(angle)); + } + + /** Returns the y offset (relative to the central slot) of the ith book slot when the total number of book slots is + * equal to {@code bookSlotCount}. */ + public static int getBookSlotYOffset(int i, int bookSlotCount){ + float angle = i * (2 * (float)Math.PI) / bookSlotCount; + return Math.round(SLOT_RADIUS * -MathHelper.cos(angle)); // -cos because +y is downwards + } + + // 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 = 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 + + // Workbench -> inventory/bookshelves + if(clickedSlotId <= UPGRADE_SLOT){ + // Try to move the stack into the bookshelves. If this fails... + if(!mergeStackIntoBookshelves(stack)){ + // ...try to move the stack into the player's inventory. If this fails... + if(!this.mergeItemStack(stack, UPGRADE_SLOT + 1, UPGRADE_SLOT + 1 + PLAYER_INVENTORY_SIZE, true)){ + return ItemStack.EMPTY; // ...nothing else happens. + } + } + } + // Bookshelves -> workbench/inventory + else if(getSlot(clickedSlotId) instanceof VirtualSlot){ + + int[] slotRange = findSlotRangeForItem(stack); + + // Try to move the stack into the workbench. If this fails... + if(slotRange == null || !this.mergeItemStack(stack, slotRange[0], slotRange[1] + 1, false)){ + // ...try to move the stack into the player's inventory. If this fails... + if(!this.mergeItemStack(stack, UPGRADE_SLOT + 1, UPGRADE_SLOT + 1 + PLAYER_INVENTORY_SIZE, true)){ + return ItemStack.EMPTY; // ...nothing else happens. + } + } + } + // Inventory -> workbench/bookshelves + else{ + + int[] slotRange = findSlotRangeForItem(stack); + + // Try to move the stack into the workbench. If this fails... + if(slotRange == null || !this.mergeItemStack(stack, slotRange[0], slotRange[1] + 1, false)){ + // ...try to move the stack into the bookshelves. If this fails... + if(!mergeStackIntoBookshelves(stack)){ + return ItemStack.EMPTY; // ...nothing else happens. + } + } + } + + if(stack.getCount() == 0){ + slot.putStack(ItemStack.EMPTY); + }else{ + slot.onSlotChanged(); + } + + if(stack.getCount() == remainder.getCount()){ + return ItemStack.EMPTY; + } + + slot.onTake(player, stack); + } + + return remainder; + } + + /** + * Returns the minimum and maximum IDs (inclusive) of the workbench slots that are appropriate for the given stack, + * or null if no slots are appropriate. Note that this does not mean the stack will fit, only that it is valid + * for all of the slots in the given range, and will fit if there is space for it. + * @param stack The stack to find a slot for + * @return A 2-element int array of the minimum and maximum slot IDs respectively + */ + @Nullable + private int[] findSlotRangeForItem(ItemStack stack){ + + if(this.getSlot(0).isItemValid(stack)){ // Spell books + + ItemStack centreStack = getSlot(CENTRE_SLOT).getStack(); + + if(centreStack.getItem() instanceof IWorkbenchItem){ + // Restrict the range to visible slots + // (How did I not think of this before? Why did I go to the trouble of overriding mergeItemStack? And + // how did that fix ever work in the first place?!) + int spellSlots = ((IWorkbenchItem)centreStack.getItem()).getSpellSlotCount(centreStack); + if(spellSlots > 0){ + return new int[]{0, spellSlots - 1}; + } + } + + }else if(getSlot(CRYSTAL_SLOT).isItemValid(stack)){ + return new int[]{CRYSTAL_SLOT, CRYSTAL_SLOT}; + + }else if(getSlot(CENTRE_SLOT).isItemValid(stack)){ + return new int[]{CENTRE_SLOT, CENTRE_SLOT}; + + }else if(getSlot(UPGRADE_SLOT).isItemValid(stack)){ + return new int[]{UPGRADE_SLOT, UPGRADE_SLOT}; + } + + return null; // It won't fit! + } + + @Override + public ItemStack slotClick(int slotId, int dragType, ClickType clickTypeIn, EntityPlayer player){ + + // -999 is used for slots in the player inventory + if(slotId > 0 && getSlot(slotId) instanceof SlotBookList){ + + ItemStack stack = player.inventory.getItemStack(); + + if(!stack.isEmpty() && !getBookshelfSlots().isEmpty()){ + mergeStackIntoBookshelves(stack); + return stack; + } + } + + return super.slotClick(slotId, dragType, clickTypeIn, player); + } + + /** + * Tries to merge the given stack into the bookshelf slots, accounting for their previous contents. The stack will + * be merged into slots in the following order of priority: + *

+ * 1. Slots that currently contain a matching item
+ * 2. Empty slots that previously contained a matching item
+ * 3. Empty slots that did not previously contain any items
+ * 4. Empty slots that previously contained a non-matching item + * @param stack The {@link ItemStack} to be merged; will be reduced by the number of items that fitted into the + * bookshelf slots + * @return True if the entire stack was merged into the bookshelf slots, false if not (equivalent to calling + * {@link ItemStack#isEmpty()} on the input stack after this method returns) + */ + private boolean mergeStackIntoBookshelves(ItemStack stack){ + + // As far as the client is concerned, which stacks were *previously* in which slots never affects whether a + // given book can fit or not, so as long as it refreshes it doesn't matter where the book actually gets put + if(tileentity.getWorld().isRemote) this.needsRefresh = true; // It's a bit clunky but it works! + + // LinkedHashSet preserves iteration order whilst ignoring duplicates - neat! + Set slots = new LinkedHashSet<>(bookshelfSlots.size()); + + // Add all slots that currently contain a matching stack - collect to list to retain order + slots.addAll(bookshelfSlots.stream().filter(s -> InventoryUtils.canMerge(stack, s.getStack())).collect(Collectors.toList())); + // Then add all empty slots that previously contained a matching stack, if they weren't already added + // No need to actually check if they're empty since inserting a new stack overwrites prevStack anyway + slots.addAll(bookshelfSlots.stream().filter(s -> InventoryUtils.canMerge(stack, s.getPrevStack())).collect(Collectors.toList())); + // Then add all slots that did not previously contain anything + slots.addAll(bookshelfSlots.stream().filter(s -> s.getPrevStack().isEmpty()).collect(Collectors.toList())); + // Finally add all other empty slots (these will be the ones that used to contain something else) + slots.addAll(bookshelfSlots.stream().filter(s -> !s.getHasStack()).collect(Collectors.toList())); + + slots.removeIf(s -> !s.isItemValid(stack)); // Should never be true, but just in case... + + // Now we have a set of slots in order of priority to try merging into + // We already know the stack will fit, so it's simply a question of distributing it + // This is waaay neater and more flexible than vanilla's (disgusting) mergeItemStack implementation - and that + // only has two priorities of slot to deal with! + + for(Slot slot : slots){ + + ItemStack contents = slot.getStack(); + + if(contents.isEmpty()){ + + // Not sure why mergeItemStack differentiates between full/partial merging, as far as I can tell the + // following line will work for both cases + slot.putStack(stack.splitStack(contents.getMaxStackSize())); + //slot.onSlotChanged(); + + if(stack.isEmpty()) return true; // The whole stack has been merged, so we're done! + + }else{ + + int totalItemCount = contents.getCount() + stack.getCount(); + int maxSize = Math.min(slot.getSlotStackLimit(), stack.getMaxStackSize()); + + if(totalItemCount <= maxSize){ + + stack.setCount(0); + contents.setCount(totalItemCount); + slot.onSlotChanged(); + return true; // The whole stack has been merged, so we're done! + + }else if(contents.getCount() < maxSize){ + + stack.shrink(maxSize - contents.getCount()); + contents.setCount(maxSize); + slot.onSlotChanged(); + } + } + } + + return false; // If we get this far, it didn't all fit so return false + } + + /** + * Called (via {@link electroblob.wizardry.packet.PacketControlInput PacketControlInput}) when the apply button in + * the arcane workbench GUI is pressed. + */ + // 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){ + + 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)){ + + if(player instanceof EntityPlayerMP){ + WizardryAdvancementTriggers.arcane_workbench.trigger((EntityPlayerMP)player, centre.getStack()); + } + } + } + } + + /** Scrolls to the given row number. */ + public void scrollTo(int row){ + this.scroll = row; + } + + /** Sets the sorting type to the given type, or toggles the sort direction if it is already that type. */ + public void setSortType(ISpellSortable.SortType sortType){ + + if(this.sortType == sortType){ + this.sortDescending = !this.sortDescending; + }else{ + this.sortType = sortType; + this.sortDescending = false; + } + + updateActiveBookshelfSlots(); + } + + @Override + public ISpellSortable.SortType getSortType(){ + return sortType; + } + + @Override + public boolean isSortDescending(){ + return sortDescending; + } + + /** Sets the search text for the bookshelf slots. */ + public void setSearchText(@Nonnull String searchText){ + this.searchText = searchText; + this.scrollTo(0); + updateActiveBookshelfSlots(); + } + + /** Returns all bookshelf slots currently linked to this container, including empty ones. The returned list + * is a copy of the internal virtual slot list, with any invalid slots removed. */ + public List getBookshelfSlots(){ + List validSlots = new ArrayList<>(bookshelfSlots); + validSlots.removeIf(s -> !s.isValid()); + return validSlots; + } + + /** Updates the active bookshelf slots with the current search term and sorting. Client-side only! */ + public void updateActiveBookshelfSlots(){ + activeBookshelfSlots = bookshelfSlots.stream().filter(s -> s.isValid() && !s.getStack().isEmpty() + // Slot 0 is a convenient way of testing if the item is a valid spell book + && this.getSlot(0).isItemValid(s.getStack()) + && Spell.byMetadata(s.getStack().getMetadata()).matches(searchText)) + // TODO: This doesn't account for non-spell book items at the moment + .sorted(Comparator.comparing(s -> Spell.byMetadata(s.getStack().getMetadata()), + sortDescending ? sortType.comparator.reversed() : sortType.comparator)) + .collect(Collectors.toList()); + } + + /** Returns all the {@link VirtualSlot}s that are currently active, sorted according to the current sort order. A + * virtual slot is active if it is not empty and its contents match the current search term (if any). */ + public List getActiveBookshelfSlots(){ + return activeBookshelfSlots; + } + + /** Returns all the {@link VirtualSlot}s that are currently visible on screen (and hence have an associated 'real' + * slot), accounting for search and scrolling, and sorted according to the current sort order. */ + public List getVisibleBookshelfSlots(){ + List activeSlots = getActiveBookshelfSlots(); + return activeSlots.subList(BOOKSHELF_SLOTS_X * scroll, activeSlots.size()); + } + + /** Returns true if there are any nearby bookshelves that are accessible from this workbench, false if not. */ + public boolean hasBookshelves(){ + return hasBookshelves; + } + + // This is relatively expensive so should not be done every tick! + // N.B. If we drop the requirement of it working with any container it could potentially be a lot easier since + // we then always have control over the bookshelf classes + + /** Called on initialisation and whenever a bookshelf is added or removed, to update the virtual slot list. */ + public void refreshBookshelfSlots(){ + + // We can't simply re-initialise everything because this method gets called whenever a bookshelf is updated, + // and that also happens whenever a stack is added or removed to update the book state on the client - and + // unfortunately there is no way for a world event listener to detect whether the tile entity changed or not + + // Remove slots that are no longer valid + this.inventorySlots.removeIf(s -> s instanceof VirtualSlot && !((VirtualSlot)s).isValid()); + bookshelfSlots.removeIf(s -> !s.isValid()); + + List bookshelves = BlockBookshelf.findNearbyBookshelves(tileentity.getWorld(), tileentity.getPos(), tileentity); + + if(bookshelves.isEmpty() == hasBookshelves){ // If the bookshelf status changed + + // Move all the slots appropriately + for(Slot slot : this.inventorySlots){ + if(!(slot instanceof SlotBookList || slot instanceof VirtualSlot)){ + slot.xPos += bookshelves.isEmpty() ? -BOOKSHELF_UI_WIDTH : BOOKSHELF_UI_WIDTH; + } + } + + hasBookshelves = !bookshelves.isEmpty(); + } + + // Ignore bookshelves we already have slots for + bookshelves.removeIf(b -> bookshelfSlots.stream().anyMatch(s -> s.inventory == b)); + + if(!bookshelves.isEmpty()){ + + for(IInventory bookshelf : bookshelves){ + for(int i = 0; i < bookshelf.getSizeInventory(); i++){ + VirtualSlot slot = new VirtualSlot(bookshelf, i); // This sets the slot INDEX (for the INVENTORY) + bookshelfSlots.add(slot); + this.addSlotToContainer(slot); // This sets the slot NUMBER (for the CONTAINER) + } + } + } + + if(tileentity.getWorld().isRemote) updateActiveBookshelfSlots(); + + } + +} diff --git a/src/main/java/electroblob/wizardry/inventory/ContainerBookshelf.java b/src/main/java/electroblob/wizardry/inventory/ContainerBookshelf.java new file mode 100644 index 00000000..8240aa3e --- /dev/null +++ b/src/main/java/electroblob/wizardry/inventory/ContainerBookshelf.java @@ -0,0 +1,177 @@ +package electroblob.wizardry.inventory; + +import electroblob.wizardry.Settings; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.block.BlockBookshelf; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.tileentity.TileEntityBookshelf; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.init.Items; +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.util.ResourceLocation; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; + +import java.util.HashSet; +import java.util.Set; +import java.util.function.Supplier; + +public class ContainerBookshelf extends Container { + + private static final Set validItems = new HashSet<>(); + + /** The bookshelf tile entity associated with this container. */ + public TileEntityBookshelf tileentity; + + public ContainerBookshelf(IInventory inventory, TileEntityBookshelf tileentity){ + + this.tileentity = tileentity; + + for(int y = 0; y < 2; y++){ + for(int x = 0; x < BlockBookshelf.SLOT_COUNT / 2; x++){ + this.addSlotToContainer(new SlotBookshelf(tileentity, x + BlockBookshelf.SLOT_COUNT / 2 * y, 35 + x * 18, 17 + y * 18)); + } + } + + for(int x = 0; x < 9; x++){ + this.addSlotToContainer(new Slot(inventory, x, 8 + x * 18, 124)); + } + + for(int y = 0; y < 3; y++){ + for(int x = 0; x < 9; x++){ + this.addSlotToContainer(new Slot(inventory, 9 + x + y * 9, 8 + x * 18, 66 + y * 18)); + } + } + + } + + /** Called from individual slots when their item is changed or removed. */ + public void onSlotChanged(){ + this.tileentity.sync(); + } + + @Override + public boolean canInteractWith(EntityPlayer player){ + return this.tileentity.isUsableByPlayer(player); + } + + @Override + public ItemStack transferStackInSlot(EntityPlayer player, int clickedSlotId){ + + ItemStack remainder = ItemStack.EMPTY; + 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 + + // Bookshelf -> inventory + if(clickedSlotId < BlockBookshelf.SLOT_COUNT){ + // Tries to move the stack into the player's inventory. If this fails... + if(!this.mergeItemStack(stack, BlockBookshelf.SLOT_COUNT, this.inventorySlots.size(), true)){ + return ItemStack.EMPTY; // ...nothing else happens. + } + } + // Inventory -> bookshelf + else{ + + int minSlotId = 0; + int maxSlotId = BlockBookshelf.SLOT_COUNT - 1; + + if(!isBook(stack)) return ItemStack.EMPTY; + + if(!this.mergeItemStack(stack, minSlotId, maxSlotId + 1, false)){ + return ItemStack.EMPTY; + } + } + + if(stack.getCount() == 0){ + slot.putStack(ItemStack.EMPTY); + }else{ + slot.onSlotChanged(); + } + + if(stack.getCount() == remainder.getCount()){ + return ItemStack.EMPTY; + } + + slot.onTake(player, stack); + } + + return remainder; + } + + /** Returns true if the given stack counts as a book and can be placed in a bookshelf, false if not. */ + public static boolean isBook(ItemStack stack){ + return validItems.contains(stack.getItem()) || Settings.containsMetaItem(Wizardry.settings.bookItems, stack); + } + + /** + * Adds the given item to the set of items that can be put in a bookshelf. This method should be called from the + * {@code init()} phase. + * @param item The item to register + * @see BlockBookshelf#registerBookModelTexture(Supplier, ResourceLocation) + */ + public static void registerBookItem(Item item){ + validItems.add(item); + } + + /** Called from {@link Wizardry#init(FMLInitializationEvent)} to register the default book items. */ + public static void initDefaultBookItems(){ + registerBookItem(Items.BOOK); + registerBookItem(Items.WRITTEN_BOOK); + registerBookItem(Items.WRITABLE_BOOK); + registerBookItem(Items.ENCHANTED_BOOK); + registerBookItem(WizardryItems.spell_book); + registerBookItem(WizardryItems.arcane_tome); + registerBookItem(WizardryItems.wizard_handbook); + registerBookItem(WizardryItems.ruined_spell_book); + registerBookItem(WizardryItems.scroll); + registerBookItem(WizardryItems.blank_scroll); + registerBookItem(WizardryItems.identification_scroll); + registerBookItem(WizardryItems.armour_upgrade); + // Not using the map in WandHelper for consistency with BlockBookshelf (again, addons must add theirs manually) + registerBookItem(WizardryItems.storage_upgrade); + registerBookItem(WizardryItems.siphon_upgrade); + registerBookItem(WizardryItems.condenser_upgrade); + registerBookItem(WizardryItems.range_upgrade); + registerBookItem(WizardryItems.duration_upgrade); + registerBookItem(WizardryItems.cooldown_upgrade); + registerBookItem(WizardryItems.blast_upgrade); + registerBookItem(WizardryItems.attunement_upgrade); + registerBookItem(WizardryItems.melee_upgrade); + } + + public class SlotBookshelf extends Slot { + + public SlotBookshelf(IInventory inventory, int index, int x, int y){ + super(inventory, index, x, y); + } + + @Override + public void putStack(ItemStack stack){ + boolean statusChanged = this.getStack().isEmpty() != stack.isEmpty() + || BlockBookshelf.getBookItems().indexOf(this.getStack().getItem()) + != BlockBookshelf.getBookItems().indexOf(stack.getItem()); + super.putStack(stack); + if(statusChanged) ContainerBookshelf.this.onSlotChanged(); + } + + @Override + public ItemStack onTake(EntityPlayer player, ItemStack stack){ + ItemStack result = super.onTake(player, stack); + ContainerBookshelf.this.onSlotChanged(); + return result; + } + + @Override + public boolean isItemValid(ItemStack stack){ + return isBook(stack); + } + } + +} diff --git a/src/main/java/electroblob/wizardry/tileentity/ContainerPortableWorkbench.java b/src/main/java/electroblob/wizardry/inventory/ContainerPortableWorkbench.java similarity index 93% rename from src/main/java/electroblob/wizardry/tileentity/ContainerPortableWorkbench.java rename to src/main/java/electroblob/wizardry/inventory/ContainerPortableWorkbench.java index 92b6f3c6..2111f1f7 100644 --- a/src/main/java/electroblob/wizardry/tileentity/ContainerPortableWorkbench.java +++ b/src/main/java/electroblob/wizardry/inventory/ContainerPortableWorkbench.java @@ -1,4 +1,4 @@ -package electroblob.wizardry.tileentity; +package electroblob.wizardry.inventory; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.InventoryPlayer; diff --git a/src/main/java/electroblob/wizardry/inventory/SlotBookList.java b/src/main/java/electroblob/wizardry/inventory/SlotBookList.java new file mode 100644 index 00000000..b39e712a --- /dev/null +++ b/src/main/java/electroblob/wizardry/inventory/SlotBookList.java @@ -0,0 +1,73 @@ +package electroblob.wizardry.inventory; + +import electroblob.wizardry.item.ItemSpellBook; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; + +public class SlotBookList extends SlotItemClassList { + + private final ContainerArcaneWorkbench container; + /** The position in the book list this slot occupies (in other words: the slot index, but offset so the first book + * list slot starts at 0). */ + private final int listIndex; + + public SlotBookList(IInventory inventory, int index, int x, int y, ContainerArcaneWorkbench container, int listIndex){ + super(inventory, index, x, y, 64, ItemSpellBook.class); + this.container = container; + this.listIndex = listIndex; + } + + /** Returns the {@link VirtualSlot} this slot currently accesses, or null if it does not currently have one. */ + public VirtualSlot getDelegate(){ + return hasDelegate() ? container.getVisibleBookshelfSlots().get(listIndex) : null; + } + + /** Returns true if this slot currently accesses a {@link VirtualSlot}, false otherwise. */ + public boolean hasDelegate(){ + return listIndex < container.getVisibleBookshelfSlots().size(); + } + + @Override + public ItemStack getStack(){ + return hasDelegate() ? getDelegate().getStack() : ItemStack.EMPTY; // Delegate item lookup to virtual slot + } + + // This doesn't depend on the client-side search and sorting stuff so it can be done in here + @Override + public boolean isItemValid(ItemStack stack){ + // It's valid if there is a virtual slot that will accept it + return container.getBookshelfSlots().stream().anyMatch(s -> s.isItemValid(stack)); + } + + @Override + public boolean isEnabled(){ + return container.hasBookshelves(); + } + + // Actual item interaction is delegated client-side before it ever gets here (in the GUI class) + // Therefore we only need to handle empty slot clicks + + @Override + public ItemStack decrStackSize(int amount){ + return ItemStack.EMPTY; + } + + @Override + public boolean canTakeStack(EntityPlayer player){ + return false; + } + + @Override + public ItemStack onTake(EntityPlayer player, ItemStack stack){ + return stack; + } + + @Override + public void putStack(ItemStack stack){ + // Can't handle this here either because it gets tangled up with the 348-line behemoth that is + // Container#slotClick, which has all sorts of side-effects + // Instead, we need to delegate client-side, as before + } + +} diff --git a/src/main/java/electroblob/wizardry/tileentity/SlotItemClassList.java b/src/main/java/electroblob/wizardry/inventory/SlotItemClassList.java similarity index 94% rename from src/main/java/electroblob/wizardry/tileentity/SlotItemClassList.java rename to src/main/java/electroblob/wizardry/inventory/SlotItemClassList.java index 783249d9..d29c000e 100644 --- a/src/main/java/electroblob/wizardry/tileentity/SlotItemClassList.java +++ b/src/main/java/electroblob/wizardry/inventory/SlotItemClassList.java @@ -1,4 +1,4 @@ -package electroblob.wizardry.tileentity; +package electroblob.wizardry.inventory; import net.minecraft.inventory.IInventory; import net.minecraft.inventory.Slot; @@ -23,10 +23,12 @@ public class SlotItemClassList extends Slot { this.stackLimit = stackLimit; } + @Override public int getSlotStackLimit(){ return stackLimit; } + @Override public boolean isItemValid(ItemStack stack){ for(Class itemClass : itemClasses){ diff --git a/src/main/java/electroblob/wizardry/tileentity/SlotItemList.java b/src/main/java/electroblob/wizardry/inventory/SlotItemList.java similarity index 95% rename from src/main/java/electroblob/wizardry/tileentity/SlotItemList.java rename to src/main/java/electroblob/wizardry/inventory/SlotItemList.java index db95b5e0..bf1a82a9 100644 --- a/src/main/java/electroblob/wizardry/tileentity/SlotItemList.java +++ b/src/main/java/electroblob/wizardry/inventory/SlotItemList.java @@ -1,4 +1,4 @@ -package electroblob.wizardry.tileentity; +package electroblob.wizardry.inventory; import net.minecraft.inventory.IInventory; import net.minecraft.inventory.Slot; diff --git a/src/main/java/electroblob/wizardry/tileentity/SlotWorkbenchItem.java b/src/main/java/electroblob/wizardry/inventory/SlotWorkbenchItem.java similarity index 91% rename from src/main/java/electroblob/wizardry/tileentity/SlotWorkbenchItem.java rename to src/main/java/electroblob/wizardry/inventory/SlotWorkbenchItem.java index 0e0a2f7d..83eb3659 100644 --- a/src/main/java/electroblob/wizardry/tileentity/SlotWorkbenchItem.java +++ b/src/main/java/electroblob/wizardry/inventory/SlotWorkbenchItem.java @@ -1,4 +1,4 @@ -package electroblob.wizardry.tileentity; +package electroblob.wizardry.inventory; import electroblob.wizardry.item.IWorkbenchItem; import net.minecraft.entity.player.EntityPlayer; @@ -29,8 +29,9 @@ public class SlotWorkbenchItem extends Slot { @Override public ItemStack onTake(EntityPlayer player, ItemStack stack){ + ItemStack result = super.onTake(player, stack); this.container.onSlotChanged(slotNumber, ItemStack.EMPTY, player); - return super.onTake(player, stack); + return result; } @Override diff --git a/src/main/java/electroblob/wizardry/inventory/VirtualSlot.java b/src/main/java/electroblob/wizardry/inventory/VirtualSlot.java new file mode 100644 index 00000000..619a77b4 --- /dev/null +++ b/src/main/java/electroblob/wizardry/inventory/VirtualSlot.java @@ -0,0 +1,95 @@ +package electroblob.wizardry.inventory; + +import electroblob.wizardry.tileentity.TileEntityBookshelf; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.inventory.Slot; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; + +/** + * A {@code VirtualSlot} represents a slot in an inventory other than the one that is currently open. Like regular slots, + * they hold a single {@link net.minecraft.item.ItemStack}, but unlike regular slots, they are not drawn to the screen + * and cannot be interacted with directly. A virtual slot is effectively a reference to a specific slot in some other + * inventory that allows the details of that inventory to be abstracted away from the current container. + *

+ * N.B. For normal slots, {@code slotIndex == slotNumber} (index is for the inventory, number is for the container). + * Importantly, for virtual slots this is not the case, since the {@code Container} they belong to is not the + * one associated with the virtual slot's {@code IInventory}. + */ +public class VirtualSlot extends Slot { + + private final TileEntity tileEntity; + /** Allows the virtual slot to remember what was last stored in it, so items can be put back in the same place. */ + private ItemStack prevStack; // For now this doesn't persist over GUI close + + public VirtualSlot(IInventory inventory, int index){ + super(inventory, index, -999, -999); + if(!(inventory instanceof TileEntity)) throw new IllegalArgumentException("Inventory must be a tile entity!"); + this.tileEntity = (TileEntity)inventory; + this.prevStack = getStack().copy(); // We MUST copy the stack or it will get changed from elsewhere later! + } + + @Override + public boolean isEnabled(){ + return false; // Virtual slots are never displayed + } + + // We don't really want to be updating the bookshelves every single tick (let alone every frame!), so we're going + // to have to do some 'assuming things stay the same until told otherwise'. This means it's possible that virtual + // slots will remain even when their containers are gone, so we need a failsafe for when that happens. + + /** Returns true if this slot is valid, i.e. the tile entity still exists. */ + public boolean isValid(){ + return !tileEntity.isInvalid(); + } + + // Normally the container decides if a stack is valid, but that's not going to work here + @Override + public boolean isItemValid(ItemStack stack){ + // getSlotIndex() is required here, NOT slotNumber (which is only populated when the slot is added to a + // container - the javadoc is wrong, it has nothing to do with inventories) + return isValid() && inventory.isItemValidForSlot(getSlotIndex(), stack); + } + + @Override + public boolean canTakeStack(EntityPlayer playerIn){ + return isValid() && super.canTakeStack(playerIn); + } + + @Override + public ItemStack onTake(EntityPlayer player, ItemStack stack){ + return isValid() ? super.onTake(player, stack) : ItemStack.EMPTY; + } + + @Override + public void onSlotChanged(){ + super.onSlotChanged(); + if(this.getHasStack()) this.prevStack = this.getStack().copy(); // Ignore stack removal (insertion of empty stacks) + } + + @Override + public ItemStack getStack(){ + return isValid() ? super.getStack() : ItemStack.EMPTY; + } + + /** Returns the stack that was last in this slot. */ + public ItemStack getPrevStack(){ + return prevStack; + } + + @Override + public void putStack(ItemStack stack){ + if(isValid()){ + if(inventory instanceof TileEntityBookshelf) ((TileEntityBookshelf)inventory).sync(); + super.putStack(stack); + } + } + + @Override + public ItemStack decrStackSize(int amount){ + if(isValid() && inventory instanceof TileEntityBookshelf) ((TileEntityBookshelf)inventory).sync(); + return isValid() ? super.decrStackSize(amount) : ItemStack.EMPTY; + } + +} diff --git a/src/main/java/electroblob/wizardry/item/IConjuredItem.java b/src/main/java/electroblob/wizardry/item/IConjuredItem.java index ea2dfb71..7fabc926 100644 --- a/src/main/java/electroblob/wizardry/item/IConjuredItem.java +++ b/src/main/java/electroblob/wizardry/item/IConjuredItem.java @@ -1,5 +1,6 @@ package electroblob.wizardry.item; +import electroblob.wizardry.client.DrawingUtils; import electroblob.wizardry.spell.Spell; import electroblob.wizardry.spell.SpellConjuration; import net.minecraft.entity.EntityLivingBase; @@ -102,6 +103,12 @@ public interface IConjuredItem { return 8; } + /** Returns a blue colour to use for the durability bar for the given stack, so all items that use it as a timer + * have the same colours. */ + static int getTimerBarColour(ItemStack stack){ + return DrawingUtils.mix(0x8bffe0, 0x2ed1e4, (float)stack.getItem().getDurabilityForDisplay(stack)); + } + @SubscribeEvent static void onLivingDropsEvent(LivingDropsEvent event){ // Destroys conjured items if their caster dies. diff --git a/src/main/java/electroblob/wizardry/item/IManaStoringItem.java b/src/main/java/electroblob/wizardry/item/IManaStoringItem.java index de93846d..26317e9e 100644 --- a/src/main/java/electroblob/wizardry/item/IManaStoringItem.java +++ b/src/main/java/electroblob/wizardry/item/IManaStoringItem.java @@ -4,6 +4,8 @@ import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; +import javax.annotation.Nullable; + /** * 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. @@ -48,7 +50,7 @@ public interface IManaStoringItem { /** 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){ + default void consumeMana(ItemStack stack, int mana, @Nullable EntityLivingBase wielder){ if(wielder instanceof EntityPlayer && ((EntityPlayer)wielder).isCreative()) return; // Mana isn't consumed in creative setMana(stack, Math.max(getMana(stack) - mana, 0)); } @@ -69,4 +71,10 @@ public interface IManaStoringItem { default boolean isManaEmpty(ItemStack stack){ return getMana(stack) == 0; } + + /** Returns how full the given stack's mana is, as a fraction between 0 (empty) and 1 (full) */ + default float getFullness(ItemStack stack){ + return (float)getMana(stack) / getManaCapacity(stack); + } + } diff --git a/src/main/java/electroblob/wizardry/item/IMultiTexturedItem.java b/src/main/java/electroblob/wizardry/item/IMultiTexturedItem.java index 849f9ae8..45abaa42 100644 --- a/src/main/java/electroblob/wizardry/item/IMultiTexturedItem.java +++ b/src/main/java/electroblob/wizardry/item/IMultiTexturedItem.java @@ -9,7 +9,7 @@ import net.minecraft.util.ResourceLocation; * 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 + * @see ItemBlockMultiTextured */ public interface IMultiTexturedItem { diff --git a/src/main/java/electroblob/wizardry/item/ISpellCastingItem.java b/src/main/java/electroblob/wizardry/item/ISpellCastingItem.java index 0d532200..7a595919 100644 --- a/src/main/java/electroblob/wizardry/item/ISpellCastingItem.java +++ b/src/main/java/electroblob/wizardry/item/ISpellCastingItem.java @@ -2,6 +2,7 @@ package electroblob.wizardry.item; import electroblob.wizardry.data.WizardData; import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.EntityUtils; import electroblob.wizardry.util.SpellModifiers; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; @@ -18,9 +19,9 @@ import javax.annotation.Nonnull; * 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)})
+ * - General-purpose detection of continuous spell casting (see {@link EntityUtils#isCasting(EntityLivingBase, Spell)})
* - Display of the arcane workbench tooltip (in conjunction with {@link IManaStoringItem})
- * - Spell HUD visibility
+ * - Supplying information to the spell HUD
* - 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 @@ -39,6 +40,28 @@ public interface ISpellCastingItem { @Nonnull Spell getCurrentSpell(ItemStack stack); + /** + * Returns the spell equipped in the next slot on the given itemstack. The given itemstack will be of this item. + * @param stack The itemstack to query. + * @return The next spell, or {@link electroblob.wizardry.registry.Spells#none Spells.none} if no spell is equipped + * in the next slot. Returns the current spell by default (useful for items with only one spell). + */ + @Nonnull + default Spell getNextSpell(ItemStack stack){ + return getCurrentSpell(stack); + } + + /** + * Returns the spell equipped in the previous slot on the given itemstack. The given itemstack will be of this item. + * @param stack The itemstack to query. + * @return The previous spell, or {@link electroblob.wizardry.registry.Spells#none Spells.none} if no spell is + * equipped in the previous slot. Returns the current spell by default (useful for items with only one spell). + */ + @Nonnull + default Spell getPreviousSpell(ItemStack stack){ + return getCurrentSpell(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. @@ -65,6 +88,16 @@ public interface ISpellCastingItem { // Nothing here either } + /** + * Selects the spell at the given index bound to the given itemstack. The given itemstack will be of this item. + * @param stack The itemstack to query. + * @param index The index to set. + * @return True if the operation succeeded, false if not. + */ + default boolean selectSpell(ItemStack stack, int index){ + return false; + } + /** * 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. @@ -73,6 +106,24 @@ public interface ISpellCastingItem { */ boolean showSpellHUD(EntityPlayer player, ItemStack stack); + /** + * Returns the current cooldown to display on the spell HUD for the given itemstack. + * @param stack The itemstack to query. + * @return The current cooldown for the equipped spell. + */ + default int getCurrentCooldown(ItemStack stack){ + return 0; + } + + /** + * Returns the max cooldown of the current spell to display on the spell HUD for the given itemstack. + * @param stack The itemstack to query. + * @return The max cooldown for the equipped spell. + */ + default int getCurrentMaxCooldown(ItemStack stack){ + return 0; + } + /** * 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}. @@ -107,7 +158,8 @@ public interface ISpellCastingItem { /** * 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. + * also performs any post-casting logic, such as mana costs and cooldowns. This method does not handle charge-up + * times. *

* N.B. Continuous spell casting from outside of the items requires a bit of extra legwork, see * {@link WizardData} for an example. diff --git a/src/main/java/electroblob/wizardry/item/IWorkbenchItem.java b/src/main/java/electroblob/wizardry/item/IWorkbenchItem.java index e85fd314..146246e3 100644 --- a/src/main/java/electroblob/wizardry/item/IWorkbenchItem.java +++ b/src/main/java/electroblob/wizardry/item/IWorkbenchItem.java @@ -5,6 +5,8 @@ import net.minecraft.entity.player.EntityPlayer; import net.minecraft.inventory.Slot; import net.minecraft.item.ItemStack; +import javax.annotation.Nullable; + /** * 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 @@ -37,7 +39,7 @@ public interface IWorkbenchItem { * 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} @@ -60,5 +62,20 @@ public interface IWorkbenchItem { * @return True if the workbench tooltip should be shown, false if not. */ boolean showTooltip(ItemStack stack); + + /** + * Applies the given upgrade to this wand. This method is responsible for all checks including tier, progression, + * upgrade stack limits, etc. Subclasses are responsible for calling this (usually from + * {@link IWorkbenchItem#onApplyButtonPressed(EntityPlayer, Slot, Slot, Slot, Slot[])}), but it has been extracted + * as an interface method here for use by JEI 'recipes'. + * @param player The player doing the upgrading, or null during JEI recipe lookup (mainly used for advancements) + * @param stack The stack being upgraded (it is guaranteed that {@code this == stack.getItem()}) + * @param upgrade The upgrade item stack being applied. This method is responsible for consuming it! + * @return The resulting upgraded wand stack. In many cases, this is simply the input {@code stack}, which has + * had its NBT modified. If the given upgrade cannot be applied, simply return the input {@code stack}. + */ + default ItemStack applyUpgrade(@Nullable EntityPlayer player, ItemStack stack, ItemStack upgrade){ + return stack; + } } diff --git a/src/main/java/electroblob/wizardry/item/ItemArmourUpgrade.java b/src/main/java/electroblob/wizardry/item/ItemArmourUpgrade.java index b7307b15..921947bd 100644 --- a/src/main/java/electroblob/wizardry/item/ItemArmourUpgrade.java +++ b/src/main/java/electroblob/wizardry/item/ItemArmourUpgrade.java @@ -34,9 +34,7 @@ public class ItemArmourUpgrade extends Item { @Override @SideOnly(Side.CLIENT) 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")); + Wizardry.proxy.addMultiLineDescription(tooltip, "item." + this.getRegistryName() + ".desc", "\u00A7d"); } } diff --git a/src/main/java/electroblob/wizardry/item/ItemArtefact.java b/src/main/java/electroblob/wizardry/item/ItemArtefact.java index 68b51e6e..a286dd91 100644 --- a/src/main/java/electroblob/wizardry/item/ItemArtefact.java +++ b/src/main/java/electroblob/wizardry/item/ItemArtefact.java @@ -5,11 +5,13 @@ import electroblob.wizardry.Wizardry; import electroblob.wizardry.constants.Element; import electroblob.wizardry.data.WizardData; import electroblob.wizardry.entity.construct.EntityFireRing; +import electroblob.wizardry.entity.construct.EntityIceBarrier; 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.event.SpellCastEvent.Source; import electroblob.wizardry.integration.DamageSafetyChecker; import electroblob.wizardry.integration.baubles.WizardryBaublesIntegration; import electroblob.wizardry.registry.*; @@ -30,6 +32,8 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.potion.PotionEffect; import net.minecraft.util.EnumHand; import net.minecraft.util.math.Vec3d; +import net.minecraft.util.text.Style; +import net.minecraft.util.text.TextFormatting; import net.minecraft.world.World; import net.minecraft.world.biome.Biome; import net.minecraftforge.common.BiomeDictionary; @@ -104,6 +108,9 @@ public class ItemArtefact extends Item { private final EnumRarity rarity; private final Type type; + /** False if this artefact has been disabled in the config, true otherwise. */ + private boolean enabled = true; + public ItemArtefact(EnumRarity rarity, Type type){ setMaxStackSize(1); setCreativeTab(WizardryTabs.GEAR); @@ -111,6 +118,11 @@ public class ItemArtefact extends Item { this.type = type; } + /** Sets whether this artefact is enabled or not. */ + public void setEnabled(boolean enabled){ + this.enabled = enabled; + } + @Override public EnumRarity getRarity(ItemStack stack){ return rarity; @@ -127,8 +139,9 @@ public class ItemArtefact extends Item { @Override @SideOnly(Side.CLIENT) - public void addInformation(ItemStack stack, @Nullable World worldIn, List tooltip, net.minecraft.client.util.ITooltipFlag flagIn){ + public void addInformation(ItemStack stack, @Nullable World world, List tooltip, net.minecraft.client.util.ITooltipFlag advanced){ Wizardry.proxy.addMultiLineDescription(tooltip, "item." + this.getRegistryName() + ".desc"); + if(!enabled) tooltip.add(Wizardry.proxy.translate("item." + Wizardry.MODID + ":generic.disabled", new Style().setColor(TextFormatting.RED))); } @Nullable @@ -161,12 +174,14 @@ public class ItemArtefact extends Item { if(!(artefact instanceof ItemArtefact)) throw new IllegalArgumentException("Not an artefact!"); + if(!((ItemArtefact)artefact).enabled) return false; // Disabled in the config + 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 + return InventoryUtils.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 @@ -192,16 +207,18 @@ public class ItemArtefact extends Item { if(types.length == 0) types = Type.values(); if(WizardryBaublesIntegration.enabled()){ - return WizardryBaublesIntegration.getEquippedArtefacts(player, types); + List artefacts = WizardryBaublesIntegration.getEquippedArtefacts(player, types); + artefacts.removeIf(i -> !i.enabled); // Remove artefacts that are disabled in the config + return artefacts; }else{ List artefacts = new ArrayList<>(); for(Type type : types){ - artefacts.addAll(WizardryUtilities.getPrioritisedHotbarAndOffhand(player).stream() + artefacts.addAll(InventoryUtils.getPrioritisedHotbarAndOffhand(player).stream() .filter(s -> s.getItem() instanceof ItemArtefact) .map(s -> (ItemArtefact)s.getItem()) - .filter(i -> type == i.type) + .filter(i -> type == i.type && i.enabled) .limit(type.maxAtOnce) .collect(Collectors.toList())); } @@ -222,7 +239,7 @@ public class ItemArtefact extends Item { */ public static boolean findMatchingWandAndExecute(EntityPlayer player, Spell spell, Consumer action){ - List hotbar = WizardryUtilities.getPrioritisedHotbarAndOffhand(player); + List hotbar = InventoryUtils.getPrioritisedHotbarAndOffhand(player); Optional stack = hotbar.stream().filter(s -> s.getItem() instanceof ISpellCastingItem && Arrays.asList(((ISpellCastingItem)s.getItem()).getSpells(s)).contains(spell)).findFirst(); @@ -266,7 +283,7 @@ public class ItemArtefact extends Item { if(artefact == WizardryItems.ring_condensing){ if(player.ticksExisted % 150 == 0){ - for(ItemStack stack : WizardryUtilities.getHotbar(player)){ + for(ItemStack stack : InventoryUtils.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) @@ -327,7 +344,7 @@ public class ItemArtefact extends Item { findMatchingWandAndExecute(player, Spells.shield, wand -> { - List projectiles = WizardryUtilities.getEntitiesWithinRadius(5, player.posX, player.posY, player.posZ, world, Entity.class); + List projectiles = EntityUtils.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); @@ -348,10 +365,28 @@ public class ItemArtefact extends Item { } }); + }else if(artefact == WizardryItems.amulet_frost_warding){ + + if(!world.isRemote && player.ticksExisted % 40 == 0){ + + List barriers = world.getEntitiesWithinAABB(EntityIceBarrier.class, player.getEntityBoundingBox().grow(1.5)); + + // Check whether any barriers near the player are facing away from them, meaning the player is behind them + if(!barriers.isEmpty() && barriers.stream().anyMatch(b -> b.getLookVec().dotProduct(b.getPositionVector().subtract(player.getPositionVector())) > 0)){ + player.addPotionEffect(new PotionEffect(WizardryPotions.ward, 50, 1)); + } + + } + }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); + // Every 5 seconds, feed the player if they are hungry enough + if(player.ticksExisted % 100 == 0){ + if(player.getFoodStats().getFoodLevel() < 20 - Spells.satiety.getProperty(Satiety.HUNGER_POINTS).intValue()){ + if(findMatchingWandAndCast(player, Spells.satiety)) continue; + } + if(player.getFoodStats().getFoodLevel() < 20 - Spells.replenish_hunger.getProperty(ReplenishHunger.HUNGER_POINTS).intValue()){ + findMatchingWandAndCast(player, Spells.replenish_hunger); + } } } } @@ -436,20 +471,43 @@ public class ItemArtefact extends Item { }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); + + }else if(artefact == WizardryItems.charm_hunger_casting){ + + if(!player.capabilities.isCreativeMode && event.getSource() == Source.WAND && !event.getSpell().isContinuous){ // TODO: Continuous spells? + + ItemStack wand = player.getHeldItemMainhand(); + + if(!(wand.getItem() instanceof ISpellCastingItem && wand.getItem() instanceof IManaStoringItem)){ + wand = player.getHeldItemOffhand(); + if(!(wand.getItem() instanceof ISpellCastingItem && wand.getItem() instanceof IManaStoringItem)) return; + } + + if(((IManaStoringItem)wand.getItem()).getMana(wand) < event.getSpell().getCost() * modifiers.get(SpellModifiers.COST)){ + + int hunger = event.getSpell().getCost() / 5; + + if(player.getFoodStats().getFoodLevel() >= hunger){ + player.getFoodStats().addStats(-hunger, 0); + modifiers.set(SpellModifiers.COST, 0, false); + } + } + + } + } } } } @SubscribeEvent - public static void onSpellCastPostEvent(SpellCastEvent.Pre event){ + public static void onSpellCastPostEvent(SpellCastEvent.Post event){ if(event.getCaster() instanceof EntityPlayer){ @@ -461,7 +519,7 @@ public class ItemArtefact extends Item { // 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()); + List nearby = EntityUtils.getLivingWithinRadius(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()){ @@ -486,13 +544,13 @@ public class ItemArtefact extends Item { if(entityNBT.hasUniqueId(MindControl.NBT_KEY)){ - Entity caster = WizardryUtilities.getEntityByUUID(entity.world, entityNBT.getUniqueId(MindControl.NBT_KEY)); + Entity caster = EntityUtils.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() + EntityUtils.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)) @@ -554,7 +612,7 @@ public class ItemArtefact extends Item { }else if(artefact == WizardryItems.amulet_potential){ - if(player.world.rand.nextFloat() < 0.2f && WizardryUtilities.isMeleeDamage(event.getSource()) + if(player.world.rand.nextFloat() < 0.2f && EntityUtils.isMeleeDamage(event.getSource()) && event.getSource().getTrueSource() instanceof EntityLivingBase){ EntityLivingBase target = (EntityLivingBase)event.getSource().getTrueSource(); @@ -565,7 +623,7 @@ public class ItemArtefact extends Item { .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); + target.posY + target.height/2, target.posZ); } DamageSafetyChecker.attackEntitySafely(target, MagicDamage.causeDirectMagicDamage(player, @@ -578,7 +636,7 @@ public class ItemArtefact extends Item { 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); + List nearbyMobs = EntityUtils.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()){ @@ -592,7 +650,7 @@ public class ItemArtefact extends Item { }else if(artefact == WizardryItems.amulet_banishing){ - if(player.world.rand.nextFloat() < 0.2f && WizardryUtilities.isMeleeDamage(event.getSource()) + if(player.world.rand.nextFloat() < 0.2f && EntityUtils.isMeleeDamage(event.getSource()) && event.getSource().getTrueSource() instanceof EntityLivingBase){ EntityLivingBase target = (EntityLivingBase)event.getSource().getTrueSource(); @@ -620,25 +678,25 @@ public class ItemArtefact extends Item { 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 + if(EntityUtils.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 + if(EntityUtils.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 + if(EntityUtils.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) + EntityUtils.getLivingWithinRadius(3, player.posX, player.posY, player.posZ, world).stream() + .filter(EntityUtils::isLiving) .filter(e -> e != player) .min(Comparator.comparingDouble(player::getDistanceSq)) .ifPresent(target -> { @@ -649,7 +707,7 @@ public class ItemArtefact extends Item { .pos(0, event.getEntity().height/2, 0).target(target).spawn(world); ParticleBuilder.spawnShockParticles(world, target.posX, - target.getEntityBoundingBox().minY + target.height/2, target.posZ); + target.posY + target.height/2, target.posZ); } DamageSafetyChecker.attackEntitySafely(target, MagicDamage.causeDirectMagicDamage(player, @@ -660,14 +718,14 @@ public class ItemArtefact extends Item { }else if(artefact == WizardryItems.ring_necromancy_melee){ - if(WizardryUtilities.isMeleeDamage(event.getSource()) && mainhandItem.getItem() instanceof ItemWand + if(EntityUtils.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 + if(EntityUtils.isMeleeDamage(event.getSource()) && mainhandItem.getItem() instanceof ItemWand && ((ItemWand)mainhandItem.getItem()).element == Element.EARTH){ event.getEntityLiving().addPotionEffect(new PotionEffect(MobEffects.POISON, 200, 0)); } @@ -677,7 +735,7 @@ public class ItemArtefact extends Item { 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())){ + && EntityUtils.isMeleeDamage(event.getSource())){ event.setAmount(12f); @@ -703,7 +761,7 @@ public class ItemArtefact extends Item { 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()) + || (event.getSource().getImmediateSource() == player && !EntityUtils.isMeleeDamage(event.getSource()) && Streams.stream(player.getHeldEquipment()).anyMatch(s -> s.getItem() instanceof ISpellCastingItem && ((ISpellCastingItem)s.getItem()).getCurrentSpell(s).getElement() == Element.NECROMANCY))){ @@ -716,7 +774,7 @@ public class ItemArtefact extends Item { 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()) + || (event.getSource().getImmediateSource() == player && !EntityUtils.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)))){ @@ -734,7 +792,7 @@ public class ItemArtefact extends Item { // ...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()) + || (event.getSource().getImmediateSource() == player && !EntityUtils.isMeleeDamage(event.getSource()) && Streams.stream(player.getHeldEquipment()).anyMatch(s -> s.getItem() instanceof ISpellCastingItem && ((ISpellCastingItem)s.getItem()).getCurrentSpell(s).getElement() == Element.EARTH))){ @@ -749,11 +807,11 @@ public class ItemArtefact extends Item { // ...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()) + || (event.getSource().getImmediateSource() == player && !EntityUtils.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() + InventoryUtils.getPrioritisedHotbarAndOffhand(player).stream() .filter(s -> s.getItem() instanceof ISpellCastingItem && s.getItem() instanceof IManaStoringItem && !((IManaStoringItem)s.getItem()).isManaFull(s)) .findFirst() @@ -826,7 +884,7 @@ public class ItemArtefact extends Item { .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 + if(!InventoryUtils.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 diff --git a/src/main/java/electroblob/wizardry/item/ItemBlankScroll.java b/src/main/java/electroblob/wizardry/item/ItemBlankScroll.java index 068e78c8..3c88cb62 100644 --- a/src/main/java/electroblob/wizardry/item/ItemBlankScroll.java +++ b/src/main/java/electroblob/wizardry/item/ItemBlankScroll.java @@ -13,6 +13,7 @@ import net.minecraft.inventory.Slot; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; public class ItemBlankScroll extends Item implements IWorkbenchItem { @@ -55,7 +56,7 @@ public class ItemBlankScroll extends Item implements IWorkbenchItem { if(crystals.getStack().getCount() * manaPerItem > cost){ // Rounds up to the nearest whole crystal - crystals.decrStackSize(cost / manaPerItem + 1); + crystals.decrStackSize(MathHelper.ceil((float)cost / manaPerItem)); centre.putStack(new ItemStack(WizardryItems.scroll, centre.getStack().getCount(), spell.metadata())); return true; } diff --git a/src/main/java/electroblob/wizardry/item/ItemBlockMultiTexturedElemental.java b/src/main/java/electroblob/wizardry/item/ItemBlockMultiTextured.java similarity index 71% rename from src/main/java/electroblob/wizardry/item/ItemBlockMultiTexturedElemental.java rename to src/main/java/electroblob/wizardry/item/ItemBlockMultiTextured.java index 0ed575cf..8084f853 100644 --- a/src/main/java/electroblob/wizardry/item/ItemBlockMultiTexturedElemental.java +++ b/src/main/java/electroblob/wizardry/item/ItemBlockMultiTextured.java @@ -1,32 +1,33 @@ 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 { +public class ItemBlockMultiTextured extends ItemBlock implements IMultiTexturedItem { private final boolean separateNames; + private final String[] prefixes; - public ItemBlockMultiTexturedElemental(Block block, boolean separateNames){ + public ItemBlockMultiTextured(Block block, boolean separateNames, String... prefixes){ super(block); this.setHasSubtypes(true); this.setMaxDamage(0); this.separateNames = separateNames; + this.prefixes = prefixes; } @Override public ResourceLocation getModelName(ItemStack stack){ int metadata = stack.getMetadata(); - if(metadata >= Element.values().length) metadata = 0; + if(metadata >= prefixes.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()); + prefixes[metadata] + "_" + this.block.getRegistryName().getPath()); } @Override diff --git a/src/main/java/electroblob/wizardry/item/ItemCrystalFlower.java b/src/main/java/electroblob/wizardry/item/ItemCrystalFlower.java new file mode 100644 index 00000000..040a74cd --- /dev/null +++ b/src/main/java/electroblob/wizardry/item/ItemCrystalFlower.java @@ -0,0 +1,39 @@ +package electroblob.wizardry.item; + +import electroblob.wizardry.registry.WizardryBlocks; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.init.Items; +import net.minecraft.item.ItemBlock; +import net.minecraft.stats.StatList; +import net.minecraft.tileentity.TileEntityFlowerPot; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +public class ItemCrystalFlower extends ItemBlock { + + public ItemCrystalFlower(){ + super(WizardryBlocks.crystal_flower); + } + + @Override + public EnumActionResult onItemUseFirst(EntityPlayer player, World world, BlockPos pos, EnumFacing side, float hitX, float hitY, float hitZ, EnumHand hand){ + + if(world.getTileEntity(pos) instanceof TileEntityFlowerPot){ + + TileEntityFlowerPot tileEntity = (TileEntityFlowerPot)world.getTileEntity(pos); + + if(tileEntity.getFlowerPotItem() == null || tileEntity.getFlowerPotItem() == Items.AIR){ + player.addStat(StatList.FLOWER_POTTED); + if(!player.capabilities.isCreativeMode) player.getHeldItem(hand).shrink(1); + world.setBlockState(pos, WizardryBlocks.crystal_flower_pot.getDefaultState()); + return EnumActionResult.SUCCESS; + } + } + + return super.onItemUseFirst(player, world, pos, side, hitX, hitY, hitZ, hand); + } + +} diff --git a/src/main/java/electroblob/wizardry/item/ItemFirebomb.java b/src/main/java/electroblob/wizardry/item/ItemFirebomb.java index c3404136..d08ac5dc 100644 --- a/src/main/java/electroblob/wizardry/item/ItemFirebomb.java +++ b/src/main/java/electroblob/wizardry/item/ItemFirebomb.java @@ -29,6 +29,8 @@ public class ItemFirebomb extends Item { player.playSound(WizardrySounds.ENTITY_FIREBOMB_THROW, 0.5F, 0.4F / (itemRand.nextFloat() * 0.4F + 0.8F)); + player.getCooldownTracker().setCooldown(this, 20); + if(!world.isRemote){ EntityFirebomb firebomb = new EntityFirebomb(world); firebomb.aim(player, 1); diff --git a/src/main/java/electroblob/wizardry/item/ItemFlamecatcher.java b/src/main/java/electroblob/wizardry/item/ItemFlamecatcher.java new file mode 100644 index 00000000..0e69a622 --- /dev/null +++ b/src/main/java/electroblob/wizardry/item/ItemFlamecatcher.java @@ -0,0 +1,226 @@ +package electroblob.wizardry.item; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.DrawingUtils; +import electroblob.wizardry.entity.projectile.EntityFlamecatcherArrow; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.spell.Flamecatcher; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.EnumRarity; +import net.minecraft.item.IItemPropertyGetter; +import net.minecraft.item.ItemBow; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ActionResult; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.EnumHand; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import javax.annotation.Nullable; + +public class ItemFlamecatcher extends ItemBow implements IConjuredItem { + + public static final float DRAW_TIME = 10; + + public ItemFlamecatcher(){ + super(); + setMaxDamage(1200); + setNoRepair(); + setCreativeTab(null); + this.addPropertyOverride(new ResourceLocation("pull"), new IItemPropertyGetter(){ + @SideOnly(Side.CLIENT) + public float apply(ItemStack stack, @Nullable World worldIn, @Nullable EntityLivingBase entityIn){ + if(entityIn == null){ + return 0.0F; + }else{ + ItemStack itemstack = entityIn.getActiveItemStack(); + return itemstack.getItem() == ItemFlamecatcher.this + ? (float)(stack.getMaxItemUseDuration() - entityIn.getItemInUseCount()) / DRAW_TIME : 0; + } + } + }); + this.addPropertyOverride(new ResourceLocation("pulling"), new IItemPropertyGetter(){ + @SideOnly(Side.CLIENT) + public float apply(ItemStack stack, @Nullable World worldIn, @Nullable EntityLivingBase entityIn){ + return entityIn != null && entityIn.isHandActive() && entityIn.getActiveItemStack() == stack ? 1 : 0; + } + }); + addAnimationPropertyOverrides(); + } + + @Override + public EnumRarity getRarity(ItemStack stack){ + return EnumRarity.EPIC; + } + + @Override + @SideOnly(Side.CLIENT) + // Why does this still exist? Item models deal with this now, right? + public boolean isFull3D(){ + return true; + } + + @Override + public int getMaxDamage(ItemStack stack){ + return this.getMaxDamageFromNBT(stack, Spells.flamecatcher); + } + + @Override + public int getRGBDurabilityForDisplay(ItemStack stack){ + return IConjuredItem.getTimerBarColour(stack); + } + + @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) + // This code should only run on the client side, so using Minecraft is ok. + // Why the heck was this here? + //&& !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); + stack.setItemDamage(damage + 1); + } + + // The following two methods re-route the displayed durability through the proxies in order to override the pausing + // of the item timer when the bow is being pulled. + + @Override + public double getDurabilityForDisplay(ItemStack stack){ + return Wizardry.proxy.getConjuredBowDurability(stack); + } + + public double getDefaultDurabilityForDisplay(ItemStack stack){ + return super.getDurabilityForDisplay(stack); + } + + @Override + public ActionResult onItemRightClick(World world, EntityPlayer player, EnumHand hand){ + + ItemStack stack = player.getHeldItem(hand); + + int shotsLeft = stack.getTagCompound().getInteger(Flamecatcher.SHOTS_REMAINING_NBT_KEY); + if(shotsLeft == 0) return ActionResult.newResult(EnumActionResult.PASS, stack); + + ActionResult ret = net.minecraftforge.event.ForgeEventFactory.onArrowNock(stack, world, player, hand, + true); + + if(ret != null) return ret; + + player.setActiveHand(hand); + + return ActionResult.newResult(EnumActionResult.SUCCESS, stack); + + } + + @Override + public boolean getIsRepairable(ItemStack stack, ItemStack stack2){ + 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; + } + +// @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){ + // Decreases the timer by the amount it should have been decreased while the bow was in use. + if(!world.isRemote) stack.setItemDamage(stack.getItemDamage() + (this.getMaxItemUseDuration(stack) - timeLeft)); + + if(entity instanceof EntityPlayer){ + + EntityPlayer player = (EntityPlayer)entity; + + int charge = this.getMaxItemUseDuration(stack) - timeLeft; + charge = net.minecraftforge.event.ForgeEventFactory.onArrowLoose(stack, world, (EntityPlayer)entity, charge, true); + if(charge < 0) return; + + if(stack.getTagCompound() != null){ + int shotsLeft = stack.getTagCompound().getInteger(Flamecatcher.SHOTS_REMAINING_NBT_KEY) - 1; + stack.getTagCompound().setInteger(Flamecatcher.SHOTS_REMAINING_NBT_KEY, shotsLeft); + if(shotsLeft == 0 && !world.isRemote){ + stack.setItemDamage(getMaxDamage(stack) - getAnimationFrames()); + } + } + + float velocity = (float)charge / DRAW_TIME; + velocity = (velocity * velocity + velocity * 2) / 3; + + if(velocity > 1) velocity = 1; + + if((double)velocity >= 0.1D){ + + if(!world.isRemote){ + EntityFlamecatcherArrow arrow = new EntityFlamecatcherArrow(world); + arrow.aim(player, EntityFlamecatcherArrow.SPEED * velocity); + world.spawnEntity(arrow); + } + + world.playSound(null, player.posX, player.posY, player.posZ, + WizardrySounds.ITEM_FLAMECATCHER_SHOOT, WizardrySounds.SPELLS, 1, 1); + + world.playSound(null, player.posX, player.posY, player.posZ, + WizardrySounds.ITEM_FLAMECATCHER_FLAME, WizardrySounds.SPELLS, 1, 1); + } + } + } + +} diff --git a/src/main/java/electroblob/wizardry/item/ItemFlamingAxe.java b/src/main/java/electroblob/wizardry/item/ItemFlamingAxe.java index dcfe3110..a015fd30 100644 --- a/src/main/java/electroblob/wizardry/item/ItemFlamingAxe.java +++ b/src/main/java/electroblob/wizardry/item/ItemFlamingAxe.java @@ -3,9 +3,9 @@ package electroblob.wizardry.item; import com.google.common.collect.Multimap; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.EntityUtils; 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; @@ -39,7 +39,7 @@ public class ItemFlamingAxe extends ItemAxe implements IConjuredItem { if(slot == EntityEquipmentSlot.MAINHAND){ multimap.put(SharedMonsterAttributes.ATTACK_DAMAGE.getName(), new AttributeModifier(POTENCY_MODIFIER, - "Potency modifier", IConjuredItem.getDamageMultiplier(stack) - 1, WizardryUtilities.Operations.MULTIPLY_CUMULATIVE)); + "Potency modifier", IConjuredItem.getDamageMultiplier(stack) - 1, EntityUtils.Operations.MULTIPLY_CUMULATIVE)); } return multimap; @@ -60,6 +60,11 @@ public class ItemFlamingAxe extends ItemAxe implements IConjuredItem { return this.getMaxDamageFromNBT(stack, Spells.flaming_axe); } + @Override + public int getRGBDurabilityForDisplay(ItemStack stack){ + return IConjuredItem.getTimerBarColour(stack); + } + @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. diff --git a/src/main/java/electroblob/wizardry/item/ItemFrostAxe.java b/src/main/java/electroblob/wizardry/item/ItemFrostAxe.java index a7f3fffc..35ed354b 100644 --- a/src/main/java/electroblob/wizardry/item/ItemFrostAxe.java +++ b/src/main/java/electroblob/wizardry/item/ItemFrostAxe.java @@ -4,9 +4,9 @@ import com.google.common.collect.Multimap; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.EntityUtils; 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; @@ -41,7 +41,7 @@ public class ItemFrostAxe extends ItemAxe implements IConjuredItem { if(slot == EntityEquipmentSlot.MAINHAND){ multimap.put(SharedMonsterAttributes.ATTACK_DAMAGE.getName(), new AttributeModifier(POTENCY_MODIFIER, - "Potency modifier", IConjuredItem.getDamageMultiplier(stack) - 1, WizardryUtilities.Operations.MULTIPLY_CUMULATIVE)); + "Potency modifier", IConjuredItem.getDamageMultiplier(stack) - 1, EntityUtils.Operations.MULTIPLY_CUMULATIVE)); } return multimap; @@ -62,6 +62,11 @@ public class ItemFrostAxe extends ItemAxe implements IConjuredItem { return this.getMaxDamageFromNBT(stack, Spells.frost_axe); } + @Override + public int getRGBDurabilityForDisplay(ItemStack stack){ + return IConjuredItem.getTimerBarColour(stack); + } + @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. diff --git a/src/main/java/electroblob/wizardry/item/ItemIdentificationScroll.java b/src/main/java/electroblob/wizardry/item/ItemIdentificationScroll.java index 61375b1e..9801d6e8 100644 --- a/src/main/java/electroblob/wizardry/item/ItemIdentificationScroll.java +++ b/src/main/java/electroblob/wizardry/item/ItemIdentificationScroll.java @@ -6,7 +6,7 @@ import electroblob.wizardry.event.DiscoverSpellEvent; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.registry.WizardryTabs; import electroblob.wizardry.spell.Spell; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.InventoryUtils; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.EnumRarity; import net.minecraft.item.Item; @@ -56,7 +56,7 @@ public class ItemIdentificationScroll extends Item { WizardData data = WizardData.get(player); - for(ItemStack stack1 : WizardryUtilities.getPrioritisedHotbarAndOffhand(player)){ + for(ItemStack stack1 : InventoryUtils.getPrioritisedHotbarAndOffhand(player)){ if(!stack1.isEmpty()){ Spell spell = Spell.byMetadata(stack1.getItemDamage()); diff --git a/src/main/java/electroblob/wizardry/item/ItemLightningHammer.java b/src/main/java/electroblob/wizardry/item/ItemLightningHammer.java index 33ecab67..f8bb19e9 100644 --- a/src/main/java/electroblob/wizardry/item/ItemLightningHammer.java +++ b/src/main/java/electroblob/wizardry/item/ItemLightningHammer.java @@ -6,11 +6,11 @@ import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.spell.LightningHammer; import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.EntityUtils; 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; @@ -68,6 +68,11 @@ public class ItemLightningHammer extends Item implements IConjuredItem { return super.getMaxDamage(stack); } + @Override + public int getRGBDurabilityForDisplay(ItemStack stack){ + return IConjuredItem.getTimerBarColour(stack); + } + private float getDamageMultiplier(ItemStack stack){ if(stack.hasTagCompound() && stack.getTagCompound().hasKey(DAMAGE_MULTIPLIER_NBT_KEY)){ return stack.getTagCompound().getFloat(DAMAGE_MULTIPLIER_NBT_KEY); @@ -83,9 +88,9 @@ public class ItemLightningHammer extends Item implements IConjuredItem { if(slot == EntityEquipmentSlot.MAINHAND){ float attackDamage = Spells.lightning_hammer.arePropertiesInitialised() ? Spells.lightning_hammer.getProperty(Spell.DIRECT_DAMAGE).floatValue() : 10; // Fallback for search tree init, value doesn't really matter - multimap.put(SharedMonsterAttributes.ATTACK_DAMAGE.getName(), new AttributeModifier(ATTACK_DAMAGE_MODIFIER, "Weapon modifier", attackDamage, 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)); + multimap.put(SharedMonsterAttributes.ATTACK_DAMAGE.getName(), new AttributeModifier(ATTACK_DAMAGE_MODIFIER, "Weapon modifier", attackDamage, EntityUtils.Operations.ADD)); + multimap.put(SharedMonsterAttributes.ATTACK_SPEED.getName(), new AttributeModifier(ATTACK_SPEED_MODIFIER, "Weapon modifier", ATTACK_SPEED, EntityUtils.Operations.ADD)); + multimap.put(SharedMonsterAttributes.MOVEMENT_SPEED.getName(), new AttributeModifier(MOVEMENT_SPEED_MODIFIER, "Weapon modifier", MOVEMENT_SPEED_REDUCTION, EntityUtils.Operations.MULTIPLY_FLAT)); } return multimap; @@ -135,7 +140,7 @@ public class ItemLightningHammer extends Item implements IConjuredItem { world.spawnEntity(hammer); } - WizardryUtilities.playSoundAtPlayer(player, WizardrySounds.ENTITY_HAMMER_THROW, 1.0F, 0.8f); + EntityUtils.playSoundAtPlayer(player, WizardrySounds.ENTITY_HAMMER_THROW, 1.0F, 0.8f); //player.swingArm(hand); @@ -195,7 +200,7 @@ public class ItemLightningHammer extends Item implements IConjuredItem { 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); + List nearby = EntityUtils.getLivingWithinRadius(CHAINING_RANGE, hit.posX, hit.posY, hit.posZ, hit.world); nearby.remove(hit); nearby.remove(wielder); @@ -210,7 +215,7 @@ public class ItemLightningHammer extends Item implements IConjuredItem { 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); + ParticleBuilder.spawnShockParticles(hit.world, target.posX, target.posY + 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 index 8823b632..69ef67b4 100644 --- a/src/main/java/electroblob/wizardry/item/ItemManaFlask.java +++ b/src/main/java/electroblob/wizardry/item/ItemManaFlask.java @@ -1,9 +1,25 @@ package electroblob.wizardry.item; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.registry.WizardryTabs; +import electroblob.wizardry.util.EntityUtils; +import electroblob.wizardry.util.InventoryUtils; +import net.minecraft.client.util.ITooltipFlag; +import net.minecraft.entity.player.EntityPlayer; 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.world.World; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import javax.annotation.Nullable; +import java.util.Comparator; +import java.util.List; public class ItemManaFlask extends Item { @@ -28,10 +44,47 @@ public class ItemManaFlask extends Item { super(); this.size = size; this.setCreativeTab(WizardryTabs.WIZARDRY); + this.setMaxStackSize(16); } @Override public EnumRarity getRarity(ItemStack stack){ return size.rarity; } + + @Override + @SideOnly(Side.CLIENT) + public void addInformation(ItemStack stack, @Nullable World worldIn, List tooltip, ITooltipFlag flagIn){ + Wizardry.proxy.addMultiLineDescription(tooltip, "item." + Wizardry.MODID + ":mana_flask.desc", size.capacity); + } + + @Override + public ActionResult onItemRightClick(World world, EntityPlayer player, EnumHand hand){ + + ItemStack flask = player.getHeldItem(hand); + + List stacks = InventoryUtils.getPrioritisedHotbarAndOffhand(player); + stacks.addAll(player.inventory.armorInventory); // player#getArmorInventoryList() only returns an Iterable + + // Find the chargeable item with the least mana + ItemStack toCharge = stacks.stream() + .filter(s -> s.getItem() instanceof IManaStoringItem && !((IManaStoringItem)s.getItem()).isManaFull(s)) + .min(Comparator.comparingDouble(s -> ((IManaStoringItem)s.getItem()).getFullness(s))).orElse(null); + + if(toCharge != null){ + + ((IManaStoringItem)toCharge.getItem()).rechargeMana(toCharge, size.capacity); + + EntityUtils.playSoundAtPlayer(player, WizardrySounds.ITEM_MANA_FLASK_USE, 1, 1); + EntityUtils.playSoundAtPlayer(player, WizardrySounds.ITEM_MANA_FLASK_RECHARGE, 0.7f, 1.1f); + + if(!player.isCreative()) flask.shrink(1); + player.getCooldownTracker().setCooldown(this, 20); + + return new ActionResult<>(EnumActionResult.SUCCESS, flask); + + }else{ + return new ActionResult<>(EnumActionResult.FAIL, flask); + } + } } diff --git a/src/main/java/electroblob/wizardry/item/ItemPoisonBomb.java b/src/main/java/electroblob/wizardry/item/ItemPoisonBomb.java index a68c8aa4..742cdfb1 100644 --- a/src/main/java/electroblob/wizardry/item/ItemPoisonBomb.java +++ b/src/main/java/electroblob/wizardry/item/ItemPoisonBomb.java @@ -29,6 +29,8 @@ public class ItemPoisonBomb extends Item { player.playSound(WizardrySounds.ENTITY_POISON_BOMB_THROW, 0.5F, 0.4F / (itemRand.nextFloat() * 0.4F + 0.8F)); + player.getCooldownTracker().setCooldown(this, 20); + if(!world.isRemote){ EntityPoisonBomb poisonbomb = new EntityPoisonBomb(world); poisonbomb.aim(player, 1); diff --git a/src/main/java/electroblob/wizardry/item/ItemPurifyingElixir.java b/src/main/java/electroblob/wizardry/item/ItemPurifyingElixir.java index 785ed409..62607cdf 100644 --- a/src/main/java/electroblob/wizardry/item/ItemPurifyingElixir.java +++ b/src/main/java/electroblob/wizardry/item/ItemPurifyingElixir.java @@ -59,7 +59,7 @@ public class ItemPurifyingElixir extends Item { 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 y = entity.posY + 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); diff --git a/src/main/java/electroblob/wizardry/item/ItemScroll.java b/src/main/java/electroblob/wizardry/item/ItemScroll.java index c1947d35..0bdc3b85 100644 --- a/src/main/java/electroblob/wizardry/item/ItemScroll.java +++ b/src/main/java/electroblob/wizardry/item/ItemScroll.java @@ -1,6 +1,7 @@ package electroblob.wizardry.item; 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; @@ -11,6 +12,7 @@ import electroblob.wizardry.util.SpellModifiers; import net.minecraft.creativetab.CreativeTabs; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.Slot; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.util.*; @@ -22,7 +24,7 @@ import net.minecraftforge.fml.relauncher.SideOnly; import java.util.List; -public class ItemScroll extends Item implements ISpellCastingItem { +public class ItemScroll extends Item implements ISpellCastingItem, IWorkbenchItem { /** 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; @@ -120,6 +122,8 @@ public class ItemScroll extends Item implements ISpellCastingItem { if(spell.isContinuous){ if(!player.isHandActive()){ player.setActiveHand(hand); + // Store the modifiers for use each tick (there aren't any by default but there could be, as above) + if(WizardData.get(player) != null) WizardData.get(player).itemCastingModifiers = modifiers; return new ActionResult<>(EnumActionResult.SUCCESS, stack); } }else{ @@ -189,6 +193,9 @@ public class ItemScroll extends Item implements ISpellCastingItem { // Scrolls are consumed upon successful use in survival mode if(!spell.isContinuous && !caster.isCreative()) stack.shrink(1); + + // Now uses the vanilla cooldown mechanic to prevent spamming of spells + if(!spell.isContinuous) caster.getCooldownTracker().setCooldown(this, spell.getCooldown()); } return true; @@ -199,27 +206,59 @@ public class ItemScroll extends Item implements ISpellCastingItem { @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); - } + // Casting has stopped before the full time has elapsed + finishCasting(stack, user, timeLeft); } - + @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); - } - + // Full casting time has elapsed + finishCasting(stack, user, 0); return stack; } + private void finishCasting(ItemStack stack, EntityLivingBase user, int timeLeft){ + + if(Spell.byMetadata(stack.getItemDamage()).isContinuous){ + // Consume scrolls in survival mode + if(!(user instanceof EntityPlayer) || !((EntityPlayer)user).isCreative()) stack.shrink(1); + + Spell spell = Spell.byMetadata(stack.getItemDamage()); + SpellModifiers modifiers = new SpellModifiers(); + int castingTick = stack.getMaxItemUseDuration() - timeLeft; + + MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Finish(Source.SCROLL, spell, user, modifiers, castingTick)); + spell.finishCasting(user.world, user, Double.NaN, Double.NaN, Double.NaN, null, castingTick, modifiers); + + if(user instanceof EntityPlayer){ + ((EntityPlayer)user).getCooldownTracker().setCooldown(this, spell.getCooldown()); + } + } + } + @Override @SideOnly(Side.CLIENT) public net.minecraft.client.gui.FontRenderer getFontRenderer(ItemStack stack){ return Wizardry.proxy.getFontRenderer(stack); } + + @Override + public int getSpellSlotCount(ItemStack stack){ + return 1; // Stop spell books immediately leaving the workbench when a scroll is enchanted + } + + @Override + public boolean canPlace(ItemStack stack){ + return false; // Prevent players putting scrolls back in the workbench + } + + @Override + public boolean onApplyButtonPressed(EntityPlayer player, Slot centre, Slot crystals, Slot upgrade, Slot[] spellBooks){ + return false; + } + + @Override + public boolean showTooltip(ItemStack stack){ + return false; + } } diff --git a/src/main/java/electroblob/wizardry/item/ItemSmokeBomb.java b/src/main/java/electroblob/wizardry/item/ItemSmokeBomb.java index a62a1179..22a47cf2 100644 --- a/src/main/java/electroblob/wizardry/item/ItemSmokeBomb.java +++ b/src/main/java/electroblob/wizardry/item/ItemSmokeBomb.java @@ -29,6 +29,8 @@ public class ItemSmokeBomb extends Item { player.playSound(WizardrySounds.ENTITY_SMOKE_BOMB_THROW, 0.5F, 0.4F / (itemRand.nextFloat() * 0.4F + 0.8F)); + player.getCooldownTracker().setCooldown(this, 20); + if(!world.isRemote){ EntitySmokeBomb smokebomb = new EntitySmokeBomb(world); smokebomb.aim(player, 1); diff --git a/src/main/java/electroblob/wizardry/item/ItemSparkBomb.java b/src/main/java/electroblob/wizardry/item/ItemSparkBomb.java index 319b3dae..4378f417 100644 --- a/src/main/java/electroblob/wizardry/item/ItemSparkBomb.java +++ b/src/main/java/electroblob/wizardry/item/ItemSparkBomb.java @@ -29,6 +29,8 @@ public class ItemSparkBomb extends Item { player.playSound(WizardrySounds.ENTITY_SPARK_BOMB_THROW, 0.5F, 0.4F / (itemRand.nextFloat() * 0.4F + 0.8F)); + player.getCooldownTracker().setCooldown(this, 20); + if(!world.isRemote){ EntitySparkBomb sparkBomb = new EntitySparkBomb(world); sparkBomb.aim(player, 1); diff --git a/src/main/java/electroblob/wizardry/item/ItemSpectralArmour.java b/src/main/java/electroblob/wizardry/item/ItemSpectralArmour.java index afa653b8..ae12a9cb 100644 --- a/src/main/java/electroblob/wizardry/item/ItemSpectralArmour.java +++ b/src/main/java/electroblob/wizardry/item/ItemSpectralArmour.java @@ -25,6 +25,11 @@ public class ItemSpectralArmour extends ItemArmor implements IConjuredItem { return this.getMaxDamageFromNBT(stack, Spells.conjure_armour); } + @Override + public int getRGBDurabilityForDisplay(ItemStack stack){ + return IConjuredItem.getTimerBarColour(stack); + } + // Overridden to stop the enchantment trick making the name turn blue. @Override public EnumRarity getRarity(ItemStack stack){ diff --git a/src/main/java/electroblob/wizardry/item/ItemSpectralBow.java b/src/main/java/electroblob/wizardry/item/ItemSpectralBow.java index 2930a583..0bbad2b8 100644 --- a/src/main/java/electroblob/wizardry/item/ItemSpectralBow.java +++ b/src/main/java/electroblob/wizardry/item/ItemSpectralBow.java @@ -64,6 +64,11 @@ public class ItemSpectralBow extends ItemBow implements IConjuredItem { return this.getMaxDamageFromNBT(stack, Spells.conjure_bow); } + @Override + public int getRGBDurabilityForDisplay(ItemStack stack){ + return IConjuredItem.getTimerBarColour(stack); + } + @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. diff --git a/src/main/java/electroblob/wizardry/item/ItemSpectralDust.java b/src/main/java/electroblob/wizardry/item/ItemSpectralDust.java new file mode 100644 index 00000000..30b6504e --- /dev/null +++ b/src/main/java/electroblob/wizardry/item/ItemSpectralDust.java @@ -0,0 +1,39 @@ +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; + +import java.util.Arrays; + +public class ItemSpectralDust extends Item implements IMultiTexturedItem { + + public ItemSpectralDust(){ + 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, "spectral_dust_" + Element.values()[metadata].getName()); + } + + @Override + public void getSubItems(CreativeTabs tab, NonNullList items){ + if(tab == WizardryTabs.WIZARDRY){ + for(Element element : Arrays.copyOfRange(Element.values(), 1, Element.values().length)){ + items.add(new ItemStack(this, 1, element.ordinal())); + } + } + } + +} diff --git a/src/main/java/electroblob/wizardry/item/ItemSpectralPickaxe.java b/src/main/java/electroblob/wizardry/item/ItemSpectralPickaxe.java index 3f40556e..b48ae2b2 100644 --- a/src/main/java/electroblob/wizardry/item/ItemSpectralPickaxe.java +++ b/src/main/java/electroblob/wizardry/item/ItemSpectralPickaxe.java @@ -41,6 +41,11 @@ public class ItemSpectralPickaxe extends ItemPickaxe implements IConjuredItem { return this.getMaxDamageFromNBT(stack, Spells.conjure_pickaxe); } + @Override + public int getRGBDurabilityForDisplay(ItemStack stack){ + return IConjuredItem.getTimerBarColour(stack); + } + @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. diff --git a/src/main/java/electroblob/wizardry/item/ItemSpectralSword.java b/src/main/java/electroblob/wizardry/item/ItemSpectralSword.java index 8a14bbab..345cac72 100644 --- a/src/main/java/electroblob/wizardry/item/ItemSpectralSword.java +++ b/src/main/java/electroblob/wizardry/item/ItemSpectralSword.java @@ -2,7 +2,7 @@ package electroblob.wizardry.item; import com.google.common.collect.Multimap; import electroblob.wizardry.registry.Spells; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.EntityUtils; import net.minecraft.entity.Entity; import net.minecraft.entity.SharedMonsterAttributes; import net.minecraft.entity.ai.attributes.AttributeModifier; @@ -35,7 +35,7 @@ public class ItemSpectralSword extends ItemSword implements IConjuredItem { if(slot == EntityEquipmentSlot.MAINHAND){ multimap.put(SharedMonsterAttributes.ATTACK_DAMAGE.getName(), new AttributeModifier(POTENCY_MODIFIER, - "Potency modifier", IConjuredItem.getDamageMultiplier(stack) - 1, WizardryUtilities.Operations.MULTIPLY_CUMULATIVE)); + "Potency modifier", IConjuredItem.getDamageMultiplier(stack) - 1, EntityUtils.Operations.MULTIPLY_CUMULATIVE)); } return multimap; @@ -56,6 +56,11 @@ public class ItemSpectralSword extends ItemSword implements IConjuredItem { return this.getMaxDamageFromNBT(stack, Spells.conjure_sword); } + @Override + public int getRGBDurabilityForDisplay(ItemStack stack){ + return IConjuredItem.getTimerBarColour(stack); + } + @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. diff --git a/src/main/java/electroblob/wizardry/item/ItemSpellBook.java b/src/main/java/electroblob/wizardry/item/ItemSpellBook.java index 5db1eb9a..1a8cff66 100644 --- a/src/main/java/electroblob/wizardry/item/ItemSpellBook.java +++ b/src/main/java/electroblob/wizardry/item/ItemSpellBook.java @@ -5,6 +5,7 @@ import electroblob.wizardry.Wizardry; import electroblob.wizardry.WizardryGuiHandler; import electroblob.wizardry.constants.Tier; import electroblob.wizardry.data.SpellGlyphData; +import electroblob.wizardry.data.WizardData; import electroblob.wizardry.registry.WizardryTabs; import electroblob.wizardry.spell.Spell; import net.minecraft.creativetab.CreativeTabs; @@ -12,6 +13,8 @@ import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.util.*; +import net.minecraft.util.text.Style; +import net.minecraft.util.text.TextFormatting; import net.minecraft.world.World; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; @@ -57,9 +60,16 @@ public class ItemSpellBook extends Item { return ActionResult.newResult(EnumActionResult.SUCCESS, stack); } + // This is accessed during loading (before we even get to the main menu) for search tree population + // Obviously the world is always null at that point, because no world objects exist! However, outside of a world + // there are no guarantees as to spell metadata order so we just have to give up (and we can't account for discovery) + // TODO: Search trees seem to get reloaded when the mappings change so in theory this should work ok, why doesn't it? @Override @SideOnly(Side.CLIENT) public void addInformation(ItemStack itemstack, World world, List tooltip, net.minecraft.client.util.ITooltipFlag advanced){ + + if(world == null) world = Wizardry.proxy.getTheWorld(); // But... I need the world! + // Tooltip is left blank for wizards buying generic spell books. if(world != null && itemstack.getItemDamage() != OreDictionary.WILDCARD_VALUE){ @@ -73,6 +83,14 @@ public class ItemSpellBook extends Item { tooltip.add(spell.getTier().getDisplayNameWithFormatting()); + EntityPlayer player = Wizardry.proxy.getThePlayer(); + + // If the spell should *appear* discovered but isn't *actually* discovered, show a 'new spell' message + // A bit annoying to check this again but it's the easiest way + if(Wizardry.settings.discoveryMode && !player.isCreative() && discovered && WizardData.get(player) != null && !WizardData.get(player).hasSpellBeenDiscovered(spell)){ + tooltip.add(Wizardry.proxy.translate("item." + this.getRegistryName() + ".new", new Style().setColor(TextFormatting.LIGHT_PURPLE))); + } + // Advanced tooltips display more information, mainly for searching purposes in creative if(discovered && advanced.isAdvanced()){ // No cheating! tooltip.add(spell.getElement().getDisplayName()); diff --git a/src/main/java/electroblob/wizardry/item/ItemWand.java b/src/main/java/electroblob/wizardry/item/ItemWand.java index f87e9e10..1cfae54f 100644 --- a/src/main/java/electroblob/wizardry/item/ItemWand.java +++ b/src/main/java/electroblob/wizardry/item/ItemWand.java @@ -2,6 +2,7 @@ package electroblob.wizardry.item; import com.google.common.collect.Multimap; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.DrawingUtils; import electroblob.wizardry.constants.Constants; import electroblob.wizardry.constants.Element; import electroblob.wizardry.constants.Tier; @@ -27,14 +28,13 @@ 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.*; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.RayTraceResult; import net.minecraft.util.math.Vec3d; +import net.minecraft.util.text.Style; 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.event.entity.player.AttackEntityEvent; @@ -44,6 +44,7 @@ import net.minecraftforge.fml.common.network.simpleimpl.IMessage; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; +import javax.annotation.Nullable; import java.util.List; import java.util.Random; @@ -75,6 +76,8 @@ public class ItemWand extends Item implements IWorkbenchItem, ISpellCastingItem, private static final float ELEMENTAL_PROGRESSION_MODIFIER = 1.2f; /** The increase in progression for casting an undiscovered spell (can only happen once per spell for each player). */ private static final float DISCOVERY_PROGRESSION_MODIFIER = 5f; + /** The increase in progression for tiers that the player has already reached. */ + private static final float SECOND_TIME_PROGRESSION_MODIFIER = 1.5f; /** 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; @@ -89,6 +92,14 @@ public class ItemWand extends Item implements IWorkbenchItem, ISpellCastingItem, this.element = element; setMaxDamage(this.tier.maxCharge); WizardryRecipes.addToManaFlaskCharging(this); + // TODO: Hook to allow addon devs to have this override apply to their own animations + addPropertyOverride(new ResourceLocation("pointing"), + (s, w, e) -> e != null && e.getActiveItemStack() == s + && (s.getItemUseAction() == SpellActions.POINT + || s.getItemUseAction() == SpellActions.POINT_UP + || s.getItemUseAction() == SpellActions.POINT_DOWN + || s.getItemUseAction() == SpellActions.GRAPPLE + || s.getItemUseAction() == SpellActions.SUMMON) ? 1 : 0); } @Override @@ -96,6 +107,16 @@ public class ItemWand extends Item implements IWorkbenchItem, ISpellCastingItem, return WandHelper.getCurrentSpell(stack); } + @Override + public Spell getNextSpell(ItemStack stack){ + return WandHelper.getNextSpell(stack); + } + + @Override + public Spell getPreviousSpell(ItemStack stack){ + return WandHelper.getPreviousSpell(stack); + } + @Override public Spell[] getSpells(ItemStack stack){ return WandHelper.getSpells(stack); @@ -111,6 +132,21 @@ public class ItemWand extends Item implements IWorkbenchItem, ISpellCastingItem, WandHelper.selectPreviousSpell(stack); } + @Override + public boolean selectSpell(ItemStack stack, int index){ + return WandHelper.selectSpell(stack, index); + } + + @Override + public int getCurrentCooldown(ItemStack stack){ + return WandHelper.getCurrentCooldown(stack); + } + + @Override + public int getCurrentMaxCooldown(ItemStack stack){ + return WandHelper.getCurrentMaxCooldown(stack); + } + @Override public boolean showSpellHUD(EntityPlayer player, ItemStack stack){ return true; @@ -168,7 +204,12 @@ public class ItemWand extends Item implements IWorkbenchItem, ISpellCastingItem, @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; + && WandHelper.getProgression(stack) >= tier.next().getProgression(); + } + + @Override + public int getRGBDurabilityForDisplay(ItemStack stack){ + return DrawingUtils.mix(0xff8bfe, 0x8e2ee4, (float)getDurabilityForDisplay(stack)); } // Max damage is modifiable with upgrades. @@ -288,9 +329,9 @@ public class ItemWand extends Item implements IWorkbenchItem, ISpellCastingItem, 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.POTENCY_INCREASE_PER_TIER * 100 + 0.5f) + "%", - element.getDisplayName())); + if(element != null) text.add(Wizardry.proxy.translate("item." + Wizardry.MODID + ":wand.buff", + new Style().setColor(TextFormatting.DARK_GRAY), + (int)((tier.level + 1) * Constants.POTENCY_INCREASE_PER_TIER * 100 + 0.5f), element.getDisplayName())); Spell spell = WandHelper.getCurrentSpell(stack); @@ -300,22 +341,16 @@ public class ItemWand extends Item implements IWorkbenchItem, ISpellCastingItem, 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))); + text.add(Wizardry.proxy.translate("item." + Wizardry.MODID + ":wand.spell", new Style().setColor(TextFormatting.GRAY), + discovered ? spell.getDisplayNameWithFormatting() : "#" + TextFormatting.BLUE + SpellGlyphData.getGlyphName(spell, player.world))); if(advanced.isAdvanced()){ // Advanced tooltips for debugging - text.add("\u00A79" + net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":wand.mana", + text.add(Wizardry.proxy.translate("item." + Wizardry.MODID + ":wand.mana", new Style().setColor(TextFormatting.BLUE), 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()); + text.add(Wizardry.proxy.translate("item." + Wizardry.MODID + ":wand.progression", new Style().setColor(TextFormatting.GRAY), + WandHelper.getProgression(stack), this.tier.level < Tier.MASTER.level ? tier.next().getProgression() : 0)); } } @@ -340,18 +375,22 @@ public class ItemWand extends Item implements IWorkbenchItem, ISpellCastingItem, 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){ + // Need to account for the modifier since it could be zero even if the original charge-up wasn't + int chargeup = (int)(spell.getChargeup() * modifiers.get(SpellModifiers.CHARGEUP)); + + if(spell.isContinuous || chargeup > 0){ + // Spells that need the mouse to be held (continuous, charge-up or both) if(!player.isHandActive()){ player.setActiveHand(hand); - // Store the modifiers for use each tick + // Store the modifiers for use later 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)); + if(chargeup > 0 && world.isRemote) Wizardry.proxy.playChargeupSound(player); + return new ActionResult<>(EnumActionResult.SUCCESS, stack); } }else{ + // All other (instant) spells if(cast(stack, spell, player, hand, 0, modifiers)){ - return new ActionResult<>(EnumActionResult.SUCCESS, player.getHeldItem(hand)); + return new ActionResult<>(EnumActionResult.SUCCESS, stack); } } } @@ -359,7 +398,8 @@ public class ItemWand extends Item implements IWorkbenchItem, ISpellCastingItem, return new ActionResult<>(EnumActionResult.FAIL, stack); } - // For continuous spells. The count argument actually decrements by 1 each tick. + // For continuous spells and spells with a charge-up time. The count argument actually decrements by 1 each tick. + // N.B. The first time this gets called is the tick AFTER onItemRightClick is called, not the same tick @Override public void onUsingTick(ItemStack stack, EntityLivingBase user, int count){ @@ -377,15 +417,30 @@ public class ItemWand extends Item implements IWorkbenchItem, ISpellCastingItem, modifiers = this.calculateModifiers(stack, (EntityPlayer)user, spell); // Fallback to the old way, should never be used } - int castingTick = stack.getMaxItemUseDuration() - count; + int useTick = stack.getMaxItemUseDuration() - count; + int chargeup = (int)(spell.getChargeup() * modifiers.get(SpellModifiers.CHARGEUP)); - // Continuous spells (these must check if they can be cast each tick since the mana changes) - // 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); + if(spell.isContinuous){ + // Continuous spell charge-up is simple, just don't do anything until it's charged + if(useTick >= chargeup){ + // castingTick needs to be relative to when the spell actually started + int castingTick = useTick - chargeup; + // Continuous spells (these must check if they can be cast each tick since the mana changes) + // Don't call canCast when castingTick == 0 because we already did it in onItemRightClick - even + // with charge-up times, because we don't want to trigger events twice + if(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(); + } + } }else{ - // Stops the casting if it was interrupted, either by events or because the wand ran out of mana - player.stopActiveHand(); + // Non-continuous spells need to check they actually have a charge-up since ALL spells call setActiveHand + if(chargeup > 0 && useTick == chargeup){ + // Once the spell is charged, it's exactly the same as in onItemRightClick + cast(stack, spell, player, player.getActiveHand(), 0, modifiers); + } } } } @@ -433,8 +488,6 @@ public class ItemWand extends Item implements IWorkbenchItem, ISpellCastingItem, WizardryPacketHandler.net.sendToDimension(msg, world.provider.getDimension()); } - caster.setActiveHand(hand); - // Mana cost int cost = (int)(spell.getCost() * modifiers.get(SpellModifiers.COST) + 0.1f); // Weird floaty rounding // As of wizardry 4.2 mana cost is only divided over two intervals each second @@ -444,6 +497,8 @@ public class ItemWand extends Item implements IWorkbenchItem, ISpellCastingItem, } + caster.setActiveHand(hand); + // Cooldown if(!spell.isContinuous && !caster.isCreative()){ // Spells only have a cooldown in survival WandHelper.setCurrentCooldown(stack, (int)(spell.getCooldown() * modifiers.get(WizardryItems.cooldown_upgrade))); @@ -458,11 +513,12 @@ public class ItemWand extends Item implements IWorkbenchItem, ISpellCastingItem, 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; + Tier nextTier = tier.next(); + int excess = WandHelper.getProgression(stack) - nextTier.getProgression(); if(excess >= 0 && excess < progression){ // ...display a message above the player's hotbar caster.playSound(WizardrySounds.ITEM_WAND_LEVELUP, 1.25f, 1); + WizardryAdvancementTriggers.wand_levelup.triggerFor(caster); if(!world.isRemote) caster.sendMessage(new TextComponentTranslation("item." + Wizardry.MODID + ":wand.levelup", this.getItemStackDisplayName(stack), nextTier.getNameForTranslationFormatted())); @@ -575,9 +631,17 @@ public class ItemWand extends Item implements IWorkbenchItem, ISpellCastingItem, progressionModifier *= ELEMENTAL_PROGRESSION_MODIFIER; } - if(WizardData.get(player) != null && !WizardData.get(player).hasSpellBeenDiscovered(spell)){ - // Casting an undiscovered spell now grants 5x progression - progressionModifier *= DISCOVERY_PROGRESSION_MODIFIER; + if(WizardData.get(player) != null){ + + if(!WizardData.get(player).hasSpellBeenDiscovered(spell)){ + // Casting an undiscovered spell now grants 5x progression + progressionModifier *= DISCOVERY_PROGRESSION_MODIFIER; + } + + if(!WizardData.get(player).hasReachedTier(this.tier.next())){ + // 1.5x progression for tiers that have already been reached + progressionModifier *= SECOND_TIME_PROGRESSION_MODIFIER; + } } modifiers.set(SpellModifiers.PROGRESSION, progressionModifier, false); @@ -589,7 +653,7 @@ public class ItemWand extends Item implements IWorkbenchItem, ISpellCastingItem, RayTraceResult rayTrace = RayTracer.standardEntityRayTrace(world, player, 16, false); - if(rayTrace != null && WizardryUtilities.isLiving(rayTrace.entityHit)){ + if(rayTrace != null && EntityUtils.isLiving(rayTrace.entityHit)){ EntityLivingBase entity = (EntityLivingBase)rayTrace.entityHit; @@ -619,97 +683,117 @@ public class ItemWand extends Item implements IWorkbenchItem, ISpellCastingItem, } @Override - public boolean onApplyButtonPressed(EntityPlayer player, Slot centre, Slot crystals, Slot upgrade, Slot[] spellBooks){ - - boolean changed = false; - + public ItemStack applyUpgrade(@Nullable EntityPlayer player, ItemStack wand, ItemStack upgrade){ + // Upgrades wand if necessary. Damage is copied, preserving remaining durability, // and also the entire NBT tag compound. - if(upgrade.getStack().getItem() == WizardryItems.arcane_tome){ + if(upgrade.getItem() == WizardryItems.arcane_tome){ - Tier tier = Tier.values()[upgrade.getStack().getItemDamage()]; + Tier tier = Tier.values()[upgrade.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()){ + if((player == null || player.isCreative() || Wizardry.settings.legacyWandLevelling + || WandHelper.getProgression(wand) >= tier.getProgression()) + && tier == this.tier.next() && this.tier != Tier.MASTER){ - // 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); -// } + if(Wizardry.settings.legacyWandLevelling){ + // Progression has little meaning with legacy upgrade mechanics so just reset it + // In theory, you can get 'free' progression when upgrading since progression can't be negative, + // so the flipside of that is you lose any excess + WandHelper.setProgression(wand, 0); + }else{ + // Carry excess progression over to the new stack + WandHelper.setProgression(wand, WandHelper.getProgression(wand) - tier.getProgression()); + } - WandHelper.setProgression(centre.getStack(), 0); + if(player != null) WizardData.get(player).setTierReached(tier); ItemStack newWand = new ItemStack(WizardryItems.getWand(tier, this.element)); - newWand.setTagCompound(centre.getStack().getTagCompound()); + newWand.setTagCompound(wand.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())); + ((IManaStoringItem)newWand.getItem()).setMana(newWand, this.getMana(wand)); - centre.putStack(newWand); - upgrade.decrStackSize(1); - - changed = true; + upgrade.shrink(1); + + return newWand; } - }else if(WandHelper.isWandUpgrade(upgrade.getStack().getItem())){ + }else if(WandHelper.isWandUpgrade(upgrade.getItem())){ // Special upgrades - Item specialUpgrade = upgrade.getStack().getItem(); + Item specialUpgrade = upgrade.getItem(); - if(WandHelper.getTotalUpgrades(centre.getStack()) < this.tier.upgradeLimit - && WandHelper.getUpgradeLevel(centre.getStack(), specialUpgrade) < Constants.UPGRADE_STACK_LIMIT){ + int maxUpgrades = this.tier.upgradeLimit; + if(this.element == Element.MAGIC) maxUpgrades += Constants.NON_ELEMENTAL_UPGRADE_BONUS; + + if(WandHelper.getTotalUpgrades(wand) < maxUpgrades + && WandHelper.getUpgradeLevel(wand, specialUpgrade) < Constants.UPGRADE_STACK_LIMIT){ // Used to preserve existing mana when upgrading storage rather than creating free mana. - int prevMana = this.getMana(centre.getStack()); + int prevMana = this.getMana(wand); - WandHelper.applyUpgrade(centre.getStack(), specialUpgrade); + WandHelper.applyUpgrade(wand, specialUpgrade); // Special behaviours for specific upgrades if(specialUpgrade == WizardryItems.storage_upgrade){ - this.setMana(centre.getStack(), prevMana); - + this.setMana(wand, prevMana); + }else if(specialUpgrade == WizardryItems.attunement_upgrade){ - int newSlotCount = BASE_SPELL_SLOTS + WandHelper.getUpgradeLevel(centre.getStack(), + int newSlotCount = BASE_SPELL_SLOTS + WandHelper.getUpgradeLevel(wand, WizardryItems.attunement_upgrade); - - Spell[] spells = WandHelper.getSpells(centre.getStack()); + + Spell[] spells = WandHelper.getSpells(wand); 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); + WandHelper.setSpells(wand, newSpells); - int[] cooldowns = WandHelper.getCooldowns(centre.getStack()); + int[] cooldowns = WandHelper.getCooldowns(wand); int[] newCooldowns = new int[newSlotCount]; if(cooldowns.length > 0){ System.arraycopy(cooldowns, 0, newCooldowns, 0, cooldowns.length); } - WandHelper.setCooldowns(centre.getStack(), newCooldowns); + WandHelper.setCooldowns(wand, newCooldowns); } - upgrade.decrStackSize(1); - WizardryAdvancementTriggers.special_upgrade.triggerFor(player); + upgrade.shrink(1); - if(WandHelper.getTotalUpgrades(centre.getStack()) == Tier.MASTER.upgradeLimit){ - WizardryAdvancementTriggers.max_out_wand.triggerFor(player); + if(player != null){ + + WizardryAdvancementTriggers.special_upgrade.triggerFor(player); + + if(WandHelper.getTotalUpgrades(wand) == Tier.MASTER.upgradeLimit){ + WizardryAdvancementTriggers.max_out_wand.triggerFor(player); + } } - - changed = true; + } } + return wand; + } + + @Override + public boolean onApplyButtonPressed(EntityPlayer player, Slot centre, Slot crystals, Slot upgrade, Slot[] spellBooks){ + + boolean changed = false; // Used for advancements + + if(upgrade.getHasStack()){ + ItemStack original = centre.getStack().copy(); + centre.putStack(this.applyUpgrade(player, centre.getStack(), upgrade.getStack())); + changed = ItemStack.areItemStacksEqual(centre.getStack(), original); + } + // 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 @@ -783,7 +867,7 @@ public class ItemWand extends Item implements IWorkbenchItem, ISpellCastingItem, if(player.world.isRemote){ - Vec3d origin = new Vec3d(player.posX, player.getEntityBoundingBox().minY + player.getEyeHeight(), player.posZ); + Vec3d origin = player.getPositionEyes(1); 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); diff --git a/src/main/java/electroblob/wizardry/item/ItemWizardArmour.java b/src/main/java/electroblob/wizardry/item/ItemWizardArmour.java index 71cbdc45..82ab6e4e 100644 --- a/src/main/java/electroblob/wizardry/item/ItemWizardArmour.java +++ b/src/main/java/electroblob/wizardry/item/ItemWizardArmour.java @@ -4,6 +4,7 @@ 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.client.DrawingUtils; import electroblob.wizardry.constants.Constants; import electroblob.wizardry.constants.Element; import electroblob.wizardry.event.SpellCastEvent; @@ -11,8 +12,8 @@ import electroblob.wizardry.registry.WizardryAdvancementTriggers; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryRecipes; import electroblob.wizardry.registry.WizardryTabs; +import electroblob.wizardry.util.InventoryUtils; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.enchantment.Enchantment; import net.minecraft.enchantment.EnchantmentMending; import net.minecraft.entity.Entity; @@ -24,11 +25,11 @@ 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.util.text.Style; +import net.minecraft.util.text.TextFormatting; import net.minecraft.world.World; import net.minecraftforge.event.entity.living.LivingSetAttackTargetEvent; import net.minecraftforge.fml.common.Mod; @@ -37,6 +38,7 @@ 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.Arrays; import java.util.List; import java.util.UUID; @@ -83,24 +85,24 @@ public class ItemWizardArmour extends ItemArmor implements IWorkbenchItem, IMana return this.getMaxDamage(stack); } + @Override + public int getRGBDurabilityForDisplay(ItemStack stack){ + return DrawingUtils.mix(0xff8bfe, 0x8e2ee4, (float)getDurabilityForDisplay(stack)); + } + @Override @SideOnly(Side.CLIENT) 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")); + tooltip.add(Wizardry.proxy.translate("item." + Wizardry.MODID + ":wizard_armour.legendary", + new Style().setColor(TextFormatting.LIGHT_PURPLE))); 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(Wizardry.proxy.translate("item." + Wizardry.MODID + ":wizard_armour.buff", + new Style().setColor(TextFormatting.DARK_GRAY), + (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))); - -// ChargeStatus status = ChargeStatus.getChargeStatus(stack); -// -// tooltip.add(status.getFormattingCode() + status.getDisplayName()); } @Override @@ -111,62 +113,12 @@ public class ItemWizardArmour extends ItemArmor implements IWorkbenchItem, IMana @Override @SideOnly(Side.CLIENT) public net.minecraft.client.model.ModelBiped getArmorModel(EntityLivingBase entityLiving, ItemStack itemStack, - EntityEquipmentSlot armourSlot, net.minecraft.client.model.ModelBiped _default){ + EntityEquipmentSlot armourSlot, net.minecraft.client.model.ModelBiped original){ // Legs use modelBiped 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; - model.bipedRightArm.showModel = armourSlot == EntityEquipmentSlot.CHEST; - model.bipedLeftArm.showModel = armourSlot == EntityEquipmentSlot.CHEST; - model.bipedRightLeg.showModel = armourSlot == EntityEquipmentSlot.FEET; - model.bipedLeftLeg.showModel = armourSlot == EntityEquipmentSlot.FEET; - - model.isSneak = entityLiving.isSneaking(); - model.isRiding = entityLiving.isRiding(); - model.isChild = entityLiving.isChild(); - - boolean leftHanded = entityLiving.getPrimaryHand() == EnumHandSide.LEFT; - - ItemStack itemstackR = leftHanded ? entityLiving.getHeldItemOffhand() : entityLiving.getHeldItemMainhand(); - ItemStack itemstackL = leftHanded ? entityLiving.getHeldItemMainhand() : entityLiving.getHeldItemOffhand(); - - if(!itemstackR.isEmpty()){ - model.rightArmPose = net.minecraft.client.model.ModelBiped.ArmPose.ITEM; - - if(entityLiving.getItemInUseCount() > 0){ - EnumAction enumaction = itemstackR.getItemUseAction(); - - if(enumaction == EnumAction.BLOCK){ - model.rightArmPose = net.minecraft.client.model.ModelBiped.ArmPose.BLOCK; - }else if(enumaction == EnumAction.BOW){ - model.rightArmPose = net.minecraft.client.model.ModelBiped.ArmPose.BOW_AND_ARROW; - } - } - } - - if(!itemstackL.isEmpty()){ - model.leftArmPose = net.minecraft.client.model.ModelBiped.ArmPose.ITEM; - - if(entityLiving.getItemInUseCount() > 0){ - EnumAction enumaction1 = itemstackL.getItemUseAction(); - - if(enumaction1 == EnumAction.BLOCK){ - model.leftArmPose = net.minecraft.client.model.ModelBiped.ArmPose.BLOCK; - }else if(enumaction1 == EnumAction.BOW){ - model.leftArmPose = net.minecraft.client.model.ModelBiped.ArmPose.BOW_AND_ARROW; - } - } - } - } - - return model; + return Wizardry.proxy.getWizardArmourModel(); } @Override @@ -241,25 +193,35 @@ public class ItemWizardArmour extends ItemArmor implements IWorkbenchItem, IMana return 0; // Doesn't have any spell slots! } + @Override + public ItemStack applyUpgrade(@Nullable EntityPlayer player, ItemStack stack, ItemStack upgrade){ + + // Applies legendary upgrade + if(upgrade.getItem() == WizardryItems.armour_upgrade){ + + if(!stack.hasTagCompound()){ + stack.setTagCompound(new NBTTagCompound()); + } + + if(!stack.getTagCompound().hasKey("legendary")){ + stack.getTagCompound().setBoolean("legendary", true); + upgrade.shrink(1); + if(player != null) WizardryAdvancementTriggers.legendary.triggerFor(player); + } + } + + return stack; + } + @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; - } + + if(upgrade.getHasStack()){ + ItemStack original = centre.getStack().copy(); + centre.putStack(this.applyUpgrade(player, centre.getStack(), upgrade.getStack())); + changed = ItemStack.areItemStacksEqual(centre.getStack(), original); } // Charges armour by appropriate amount @@ -267,7 +229,11 @@ public class ItemWizardArmour extends ItemArmor implements IWorkbenchItem, IMana int chargeDepleted = this.getManaCapacity(centre.getStack()) - this.getMana(centre.getStack()); - if(crystals.getStack().getCount() * Constants.MANA_PER_CRYSTAL < chargeDepleted){ + 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 armour this.rechargeMana(centre.getStack(), crystals.getStack().getCount() * Constants.MANA_PER_CRYSTAL); crystals.decrStackSize(crystals.getStack().getCount()); @@ -309,13 +275,13 @@ public class ItemWizardArmour extends ItemArmor implements IWorkbenchItem, IMana 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; + if(armourPieces == InventoryUtils.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) + return (int)Arrays.stream(InventoryUtils.ARMOUR_SLOTS) .map(s -> entity.getItemStackFromSlot(s).getItem()) .filter(i -> i instanceof ItemWizardArmour && ((ItemWizardArmour)i).element == element) .count(); diff --git a/src/main/java/electroblob/wizardry/item/ItemWizardHandbook.java b/src/main/java/electroblob/wizardry/item/ItemWizardHandbook.java index e2c6a39b..36ce4817 100644 --- a/src/main/java/electroblob/wizardry/item/ItemWizardHandbook.java +++ b/src/main/java/electroblob/wizardry/item/ItemWizardHandbook.java @@ -9,6 +9,8 @@ import net.minecraft.item.ItemStack; import net.minecraft.util.ActionResult; import net.minecraft.util.EnumActionResult; import net.minecraft.util.EnumHand; +import net.minecraft.util.text.Style; +import net.minecraft.util.text.TextFormatting; import net.minecraft.world.World; import javax.annotation.Nullable; @@ -27,8 +29,8 @@ public class ItemWizardHandbook extends Item { @Override 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)); + tooltip.add(Wizardry.proxy.translate("item." + Wizardry.MODID + ":wizard_handbook.author", + new Style().setColor(TextFormatting.GRAY), AUTHOR)); } @Override diff --git a/src/main/java/electroblob/wizardry/item/SpellActions.java b/src/main/java/electroblob/wizardry/item/SpellActions.java new file mode 100644 index 00000000..fe8aa7c0 --- /dev/null +++ b/src/main/java/electroblob/wizardry/item/SpellActions.java @@ -0,0 +1,63 @@ +package electroblob.wizardry.item; + +import electroblob.wizardry.Wizardry; +import net.minecraft.item.EnumAction; +import net.minecraftforge.common.util.EnumHelper; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Defines and stores wizardry's custom {@link EnumAction}s. + * @author Electroblob + * @since Wizardry 4.3 + */ +public final class SpellActions { + + private static final List spellActions = new ArrayList<>(); + + /** An {@link EnumAction} that causes the player to point in the direction they are looking. */ + public static final EnumAction POINT = createAction("point"); + /** An {@link EnumAction} that causes the player to point directly upwards. */ + public static final EnumAction POINT_UP = createAction("point_up"); + /** An {@link EnumAction} that causes the player to point down towards the ground. */ + public static final EnumAction POINT_DOWN = createAction("point_down"); + /** An {@link EnumAction} that causes the player to stretch both arms out and up slightly, as if summoning. */ + public static final EnumAction SUMMON = createAction("summon"); + /** An {@link EnumAction} that causes the player to hold their item vertically in front of them. */ + public static final EnumAction THRUST = createAction("thrust"); + /** An {@link EnumAction} that causes the player to point the item in use towards their other hand. */ + public static final EnumAction IMBUE = createAction("imbue"); + /** An {@link EnumAction} that causes the player to point towards their grappling target (see + * {@link electroblob.wizardry.spell.Grapple Grapple}). */ + public static final EnumAction GRAPPLE = createAction("grapple"); + + private SpellActions(){} // No instances! + + private static EnumAction createAction(String name){ + return createAction(Wizardry.MODID, name); + } + + /** + * Creates a new {@link EnumAction} with the given mod ID and name and adds it to the internal spell action list. + * Use this method to add extra spell actions for use with wizardry's player animator. + * + * @param modID The ID of the mod adding this action (avoids naming conflicts) + * @param name The name of the action (for Forge to use as the in-code name; should only contain characters that + * can be used in Java identifiers) + * @return The resulting {@code EnumAction} + */ + public static EnumAction createAction(String modID, String name){ + // Using $ because this name will be used as the in-code name of the enum constant, and : wouldn't compile + EnumAction action = EnumHelper.addAction(modID + "$" + name); + spellActions.add(action); + return action; + } + + /** Returns an unmodifiable list of all spell actions. */ + public static List getSpellActions(){ + return Collections.unmodifiableList(spellActions); + } + +} diff --git a/src/main/java/electroblob/wizardry/loot/RandomSpell.java b/src/main/java/electroblob/wizardry/loot/RandomSpell.java index 9db7972c..f2912c32 100644 --- a/src/main/java/electroblob/wizardry/loot/RandomSpell.java +++ b/src/main/java/electroblob/wizardry/loot/RandomSpell.java @@ -5,9 +5,11 @@ import electroblob.wizardry.Wizardry; import electroblob.wizardry.constants.Element; import electroblob.wizardry.constants.Tier; import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.item.ItemArtefact; import electroblob.wizardry.item.ItemScroll; import electroblob.wizardry.item.ItemSpellBook; import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.SpellProperties; import net.minecraft.entity.player.EntityPlayer; @@ -150,17 +152,25 @@ public class RandomSpell extends LootFunction { possibleSpells.removeIf(s -> s.getElement() != element); if(possibleSpells.isEmpty()) return Spells.none; // If it fails anywhere, it'll most likely be here - // Remove either the undiscovered spells or the discovered ones, depending on the bias - if(undiscoveredBias > 0 && player != null){ + if(player != null){ - WizardData data = WizardData.get(player); + float bias = undiscoveredBias; + // Archivist's eyeglass increases undiscovered bias by 0.4 up to a maximum of 0.9 + if(ItemArtefact.isArtefactActive(player, WizardryItems.charm_spell_discovery)) + bias = Math.min(bias + 0.4f, 0.9f); - int discoveredCount = (int)possibleSpells.stream().filter(data::hasSpellBeenDiscovered).count(); - // If none have been discovered or they've all been discovered, don't bother! - if(discoveredCount > 0 && discoveredCount < possibleSpells.size()){ - // Kinda unintuitive but it's very neat! - boolean keepDiscovered = random.nextFloat() < 0.5f + 0.5f * undiscoveredBias; - possibleSpells.removeIf(s -> keepDiscovered != data.hasSpellBeenDiscovered(s)); + // Remove either the undiscovered spells or the discovered ones, depending on the bias + if(bias > 0){ + + WizardData data = WizardData.get(player); + + int discoveredCount = (int)possibleSpells.stream().filter(data::hasSpellBeenDiscovered).count(); + // If none have been discovered or they've all been discovered, don't bother! + if(discoveredCount > 0 && discoveredCount < possibleSpells.size()){ + // Kinda unintuitive but it's very neat! + boolean keepDiscovered = random.nextFloat() > 0.5f + 0.5f * bias; + possibleSpells.removeIf(s -> keepDiscovered != data.hasSpellBeenDiscovered(s)); + } } } diff --git a/src/main/java/electroblob/wizardry/misc/DonationPerksHandler.java b/src/main/java/electroblob/wizardry/misc/DonationPerksHandler.java new file mode 100644 index 00000000..28f54099 --- /dev/null +++ b/src/main/java/electroblob/wizardry/misc/DonationPerksHandler.java @@ -0,0 +1,105 @@ +package electroblob.wizardry.misc; + +import com.google.common.collect.ImmutableMap; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.packet.PacketRequestDonationPerks; +import electroblob.wizardry.packet.PacketSyncDonationPerks; +import electroblob.wizardry.packet.WizardryPacketHandler; +import electroblob.wizardry.util.Box; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerLoggedInEvent; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; + +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +/** Class responsible for storing and handling the donation perk settings for all eligible connected players */ +// We probably could have used WizardData for this, but since this is config-based and only for rendering it makes more +// sense to handle it separately, especially given that the vast majority of players aren't donors +@Mod.EventBusSubscriber +public class DonationPerksHandler { + + // Thanks to everyone who has donated their time and/or money! <3 (These are your UUIDs - don't worry, they're not + // sensitive info, they're just player identifiers that won't ever change, even if you change your username) + // Convert between usernames and UUIDs here https://mcuuid.net/ + // Normally I'd use an EnumMap for this, but somehow making the UUIDs into an enum doesn't seem right + private static final Map> DONOR_UUID_MAP = ImmutableMap.>builder() + .put(UUID.fromString("4b29263e-007b-48ef-b3e6-ce86cca989e9"), new Box<>(Element.MAGIC)) // Me! + .put(UUID.fromString("2b583703-1407-4ec6-93d2-d9c03b0c08fc"), new Box<>(Element.MAGIC)) + .put(UUID.fromString("f6c7001f-4ce8-43f7-b4a1-a9d9d5fadb93"), new Box<>(Element.MAGIC)) + .put(UUID.fromString("01535a73-ff8d-4d6c-851e-c71f89e936aa"), new Box<>(Element.MAGIC)) + .put(UUID.fromString("32ca0099-c1d6-4682-82ab-8f06059bb801"), new Box<>(Element.MAGIC)) + .put(UUID.fromString("0a704e20-1e7a-413e-9122-fae5b244a05e"), new Box<>(Element.MAGIC)) + .put(UUID.fromString("41fec037-12a5-4b19-868e-b62e39952e96"), new Box<>(Element.MAGIC)) + .build(); + + /** Returns true if the given player's UUID appears in the list of donor UUIDs, false otherwise (thread-safe). */ + public static boolean isDonor(EntityPlayer player){ + return DONOR_UUID_MAP.containsKey(player.getUniqueID()); + } + + /** Sets each donor's perk element in order according to the given list. The given list will be modified. */ + public static void setElements(List elements){ + if(elements.size() < DONOR_UUID_MAP.size()){ + Wizardry.logger.warn("Received fewer perk settings than the number of donors, cannot sync!"); + return; + } + DONOR_UUID_MAP.forEach((k, v) -> v.set(elements.remove(0))); + if(!elements.isEmpty()) Wizardry.logger.warn("Received more perk settings than the number of donors, something must have gone wrong!"); + } + + /** Sets the donor perk to the given element for the given player and sends a packet to all clients if it changed. */ + public static void setElement(EntityPlayerMP player, Element element){ + Box box = DONOR_UUID_MAP.get(player.getUniqueID()); + if(box != null && box.get() != element){ + box.set(element); + syncAll(); + } + } + + /** Returns the given player's donor perk element, or null if they are not a donor (may also be null for donors if + * they have disabled the perk). */ + public static Element getElement(EntityPlayer player){ + Box box = DONOR_UUID_MAP.get(player.getUniqueID()); + return box == null ? null : box.get(); + } + + /** Sends the current donor perk information to all connected players (server-side only). */ + private static void syncAll(){ + Wizardry.logger.info("Sending global donation perk settings to all players"); + PacketSyncDonationPerks.Message packet = new PacketSyncDonationPerks.Message(DONOR_UUID_MAP.values().stream().map(Box::get).collect(Collectors.toList())); + WizardryPacketHandler.net.sendToAll(packet); + } + + /** Sends the current donor perk information to the given player (server-side only). */ + private static void syncWith(EntityPlayerMP player){ + Wizardry.logger.info("Sending global donation perk settings to {}", player.getName()); + PacketSyncDonationPerks.Message packet = new PacketSyncDonationPerks.Message(DONOR_UUID_MAP.values().stream().map(Box::get).collect(Collectors.toList())); + WizardryPacketHandler.net.sendTo(packet, player); + } + + /** Sends the client player's donor perk setting to the server (client-side only) */ + public static void sendToServer(EntityPlayer player){ + Wizardry.logger.info("Sending donation perk settings for {} to server", player.getName()); + IMessage message = new PacketRequestDonationPerks.Message(Wizardry.settings.donationPerkElement); + WizardryPacketHandler.net.sendToServer(message); + } + + @SubscribeEvent + public static void onPlayerLoggedInEvent(PlayerLoggedInEvent event){ + if(event.player instanceof EntityPlayerMP){ + // Send donation perks to anyone who logs in + syncWith((EntityPlayerMP)event.player); + }else{ + // Send donation perks setting to the server if the player is a donor + if(isDonor(event.player)) sendToServer(event.player); + } + } + +} diff --git a/src/main/java/electroblob/wizardry/misc/Forfeit.java b/src/main/java/electroblob/wizardry/misc/Forfeit.java index 46e9bb07..3f9da890 100644 --- a/src/main/java/electroblob/wizardry/misc/Forfeit.java +++ b/src/main/java/electroblob/wizardry/misc/Forfeit.java @@ -15,7 +15,8 @@ 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 electroblob.wizardry.util.BlockUtils; +import electroblob.wizardry.util.EntityUtils; import net.minecraft.entity.effect.EntityLightningBolt; import net.minecraft.entity.item.EntityFallingBlock; import net.minecraft.entity.passive.EntitySquid; @@ -112,8 +113,8 @@ public abstract class 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]; + if(f < TIER_CHANGE_CHANCE) tier = tier.previous(); + else if(f > 1 - TIER_CHANGE_CHANCE) tier = tier.next(); List matches = forfeits.get(Pair.of(tier, element)); if(matches.isEmpty()){ Wizardry.logger.warn("No forfeits with tier {} and element {}!", tier, element); @@ -173,7 +174,7 @@ public abstract class Forfeit { WizardryAdvancementTriggers.spell_failure.triggerFor(player); - WizardryUtilities.playSoundAtPlayer(player, forfeit.getSound(), WizardrySounds.SPELLS, 1, 1); + EntityUtils.playSoundAtPlayer(player, forfeit.getSound(), WizardrySounds.SPELLS, 1, 1); if(!event.getWorld().isRemote) player.sendMessage( event.getSource() == SpellCastEvent.Source.WAND ? forfeit.getMessageForWand() : forfeit.getMessageForScroll()); @@ -189,7 +190,7 @@ public abstract class Forfeit { 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)); + Vec3d vec = p.getPositionEyes(1).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); @@ -209,7 +210,7 @@ public abstract class Forfeit { 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); + BlockPos pos = BlockUtils.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); @@ -220,7 +221,7 @@ public abstract class Forfeit { add(Tier.MASTER, Element.FIRE, create("burn_surroundings", (w, p) -> { if(!w.isRemote){ - List sphere = WizardryUtilities.getBlockSphere(p.getPosition(), 6); + List sphere = BlockUtils.getBlockSphere(p.getPosition(), 6); for(BlockPos pos : sphere){ if(w.rand.nextBoolean() && w.isAirBlock(pos)) w.setBlockState(pos, Blocks.FIRE.getDefaultState()); } @@ -230,7 +231,7 @@ public abstract class Forfeit { 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))); + 1, EntityUtils.canDamageBlocks(p, w))); })); add(Tier.NOVICE, Element.ICE, create("freeze_self", (w, p) -> p.addPotionEffect(new PotionEffect(WizardryPotions.frost, 200)))); @@ -243,8 +244,8 @@ public abstract class Forfeit { 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)); + Integer y = BlockUtils.getNearestSurface(w, new BlockPos(x, p.posY, z), EnumFacing.UP, 2, true, + BlockUtils.SurfaceCriteria.basedOn(World::isBlockFullCube)); if(y == null) break; iceSpike.setFacing(EnumFacing.UP); iceSpike.setPosition(x, y, z); @@ -264,7 +265,7 @@ public abstract class Forfeit { 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); + BlockPos pos = BlockUtils.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); @@ -306,7 +307,7 @@ public abstract class Forfeit { if(!w.isRemote){ for(EnumFacing direction : EnumFacing.HORIZONTALS){ BlockPos pos = p.getPosition().offset(direction, 2); - Integer y = WizardryUtilities.getNearestFloor(w, pos, 2); + Integer y = BlockUtils.getNearestFloor(w, pos, 2); if(y == null) continue; EntityLightningSigil sigil = new EntityLightningSigil(w); sigil.setPosition(pos.getX() + 0.5, y, pos.getZ() + 0.5); @@ -322,7 +323,7 @@ public abstract class Forfeit { 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); + BlockPos pos = BlockUtils.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); @@ -347,7 +348,7 @@ public abstract class Forfeit { 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); + BlockPos pos = BlockUtils.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); @@ -375,7 +376,7 @@ public abstract class Forfeit { if(!w.isRemote){ for(EnumFacing direction : EnumFacing.HORIZONTALS){ BlockPos pos = p.getPosition().offset(direction); - if(WizardryUtilities.canBlockBeReplaced(w, pos)) w.setBlockState(pos, WizardryBlocks.snare.getDefaultState()); + if(BlockUtils.canBlockBeReplaced(w, pos)) w.setBlockState(pos, WizardryBlocks.snare.getDefaultState()); } } })); @@ -390,7 +391,7 @@ public abstract class Forfeit { add(Tier.APPRENTICE, Element.EARTH, create("uproot_plants", (w, p) -> { if(!w.isRemote){ - List sphere = WizardryUtilities.getBlockSphere(p.getPosition(), 5); + List sphere = BlockUtils.getBlockSphere(p.getPosition(), 5); sphere.removeIf(pos -> !(w.getBlockState(pos).getBlock() instanceof IPlantable)); sphere.forEach(pos -> w.destroyBlock(pos, true)); } @@ -400,17 +401,17 @@ public abstract class Forfeit { 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)); + List sphere = BlockUtils.getBlockSphere(p.getPosition().up(), 2); + sphere.removeIf(pos -> !BlockUtils.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); + List sphere = BlockUtils.getBlockSphere(p.getPosition(), 4); sphere.removeIf(pos -> !w.getBlockState(pos).isFullCube()); - sphere.removeIf(pos -> WizardryUtilities.isBlockUnbreakable(w, pos)); + sphere.removeIf(pos -> BlockUtils.isBlockUnbreakable(w, pos)); sphere.forEach(pos -> { EntityFallingBlock block = new EntityFallingBlock(w, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, w.getBlockState(pos)); @@ -437,7 +438,7 @@ public abstract class Forfeit { 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); + BlockPos pos = BlockUtils.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); diff --git a/src/main/java/electroblob/wizardry/misc/WildcardTradeList.java b/src/main/java/electroblob/wizardry/misc/WildcardTradeList.java index 991a99fd..d03a376d 100644 --- a/src/main/java/electroblob/wizardry/misc/WildcardTradeList.java +++ b/src/main/java/electroblob/wizardry/misc/WildcardTradeList.java @@ -1,6 +1,6 @@ package electroblob.wizardry.misc; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.InventoryUtils; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTUtil; @@ -16,6 +16,8 @@ import net.minecraftforge.oredict.OreDictionary; @SuppressWarnings("serial") public class WildcardTradeList extends MerchantRecipeList { + private int currentIndex; + public WildcardTradeList(){ super(); } @@ -24,19 +26,26 @@ public class WildcardTradeList extends MerchantRecipeList { super(tag); } + /** Returns the current recipe */ + public MerchantRecipe getCurrentRecipe(){ + return get(currentIndex); // Allows events to access the selected recipe without reflection + } + @Override public MerchantRecipe canRecipeBeUsed(ItemStack offer1, ItemStack offer2, int index){ + + currentIndex = index; // Update the index if(index > 0 && index < this.size()){ - MerchantRecipe merchantrecipe1 = (MerchantRecipe)this.get(index); + MerchantRecipe merchantrecipe1 = this.get(index); return !this.areItemStacksExactlyEqual(offer1, merchantrecipe1.getItemToBuy()) || (!offer2.isEmpty() || merchantrecipe1.hasSecondItemToBuy()) && (!merchantrecipe1.hasSecondItemToBuy() || !this.areItemStacksExactlyEqual(offer2, merchantrecipe1.getSecondItemToBuy())) || offer1.getCount() < merchantrecipe1.getItemToBuy().getCount() || merchantrecipe1.hasSecondItemToBuy() && offer2.getCount() < merchantrecipe1.getSecondItemToBuy().getCount() ? null : merchantrecipe1; }else{ for(int i = 0; i < this.size(); ++i){ - MerchantRecipe merchantrecipe = (MerchantRecipe)this.get(i); + MerchantRecipe merchantrecipe = this.get(i); if (this.areItemStacksExactlyEqual(offer1, merchantrecipe.getItemToBuy()) && offer1.getCount() >= merchantrecipe.getItemToBuy().getCount() && (!merchantrecipe.hasSecondItemToBuy() && offer2.isEmpty() || merchantrecipe.hasSecondItemToBuy() && this.areItemStacksExactlyEqual(offer2, merchantrecipe.getSecondItemToBuy()) && offer2.getCount() >= merchantrecipe.getSecondItemToBuy().getCount())){ return merchantrecipe; @@ -65,18 +74,18 @@ public class WildcardTradeList extends MerchantRecipeList { for(MerchantRecipe merchantrecipe : this){ ItemStack itemToBuy = merchantrecipe.getItemToBuy(); - if(itemToBuy.getMetadata() == OreDictionary.WILDCARD_VALUE) itemToBuy = WizardryUtilities.copyWithMeta(itemToBuy, 0); + if(itemToBuy.getMetadata() == OreDictionary.WILDCARD_VALUE) itemToBuy = InventoryUtils.copyWithMeta(itemToBuy, 0); buffer.writeItemStack(itemToBuy); ItemStack itemToSell = merchantrecipe.getItemToSell(); - if(itemToSell.getMetadata() == OreDictionary.WILDCARD_VALUE) itemToSell = WizardryUtilities.copyWithMeta(itemToSell, 0); + if(itemToSell.getMetadata() == OreDictionary.WILDCARD_VALUE) itemToSell = InventoryUtils.copyWithMeta(itemToSell, 0); buffer.writeItemStack(itemToSell); ItemStack secondItemToBuy = merchantrecipe.getSecondItemToBuy(); buffer.writeBoolean(!secondItemToBuy.isEmpty()); if(!secondItemToBuy.isEmpty()){ - if(secondItemToBuy.getMetadata() == OreDictionary.WILDCARD_VALUE) secondItemToBuy = WizardryUtilities.copyWithMeta(secondItemToBuy, 0); + if(secondItemToBuy.getMetadata() == OreDictionary.WILDCARD_VALUE) secondItemToBuy = InventoryUtils.copyWithMeta(secondItemToBuy, 0); buffer.writeItemStack(secondItemToBuy); } diff --git a/src/main/java/electroblob/wizardry/packet/PacketControlInput.java b/src/main/java/electroblob/wizardry/packet/PacketControlInput.java index f2a43d39..152e89b4 100644 --- a/src/main/java/electroblob/wizardry/packet/PacketControlInput.java +++ b/src/main/java/electroblob/wizardry/packet/PacketControlInput.java @@ -1,14 +1,14 @@ package electroblob.wizardry.packet; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.inventory.ContainerArcaneWorkbench; 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.InventoryUtils; 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; @@ -75,7 +75,7 @@ public class PacketControlInput implements IMessageHandler { if(player.isDead && Resurrection.getRemainingWaitTime(player.deathTime) == 0){ - ItemStack stack = WizardryUtilities.getHotbar(player).stream() + ItemStack stack = InventoryUtils.getHotbar(player).stream() .filter(s -> Resurrection.canStackResurrect(s, player)).findFirst().orElse(null); if(stack != null){ @@ -96,7 +96,7 @@ public class PacketControlInput implements IMessageHandler { if(player.isDead){ - ItemStack stack = WizardryUtilities.getHotbar(player).stream() + ItemStack stack = InventoryUtils.getHotbar(player).stream() .filter(s -> Resurrection.canStackResurrect(s, player)).findFirst().orElse(null); if(stack != null){ diff --git a/src/main/java/electroblob/wizardry/packet/PacketLectern.java b/src/main/java/electroblob/wizardry/packet/PacketLectern.java new file mode 100644 index 00000000..c499aa6e --- /dev/null +++ b/src/main/java/electroblob/wizardry/packet/PacketLectern.java @@ -0,0 +1,73 @@ +package electroblob.wizardry.packet; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.tileentity.TileEntityLectern; +import io.netty.buffer.ByteBuf; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.tileentity.TileEntity; +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; + +/** [Client -> Server] This packet is sent when a player closes the lectern GUI to send the last-viewed spell to + * the server. */ +public class PacketLectern 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(() -> { + + TileEntity tileentity = player.world.getTileEntity(message.pos); + + if(tileentity instanceof TileEntityLectern){ + + ((TileEntityLectern)tileentity).currentSpell = message.spell; + ((TileEntityLectern)tileentity).sync(); // Update other clients with the new state + + }else{ + Wizardry.logger.warn("Received a PacketLectern, but no lectern existed at the position specified!"); + } + + }); + } + + return null; + } + + public static class Message implements IMessage { + + private BlockPos pos; + private Spell spell; + + // This constructor is required otherwise you'll get errors (used somewhere in fml through reflection) + public Message(){} + + public Message(BlockPos pos, Spell spell){ + this.pos = pos; + this.spell = spell; + } + + @Override + public void fromBytes(ByteBuf buf){ + // The order is important + pos = new BlockPos(buf.readInt(), buf.readInt(), buf.readInt()); + spell = Spell.byNetworkID(buf.readInt()); + } + + @Override + public void toBytes(ByteBuf buf){ + buf.writeInt(pos.getX()); + buf.writeInt(pos.getY()); + buf.writeInt(pos.getZ()); + buf.writeInt(spell.networkID()); + } + } +} diff --git a/src/main/java/electroblob/wizardry/packet/PacketRequestDonationPerks.java b/src/main/java/electroblob/wizardry/packet/PacketRequestDonationPerks.java new file mode 100644 index 00000000..f108d776 --- /dev/null +++ b/src/main/java/electroblob/wizardry/packet/PacketRequestDonationPerks.java @@ -0,0 +1,58 @@ +package electroblob.wizardry.packet; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.misc.DonationPerksHandler; +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; + +/** [Client -> Server] This packet is sent when a player logs in and whenever the setting changes to set their + * donation perk element. */ +public class PacketRequestDonationPerks 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; + + // The UUID key set itself is immutable so we can safely access it from the networking thread + if(DonationPerksHandler.isDonor(player)){ + player.getServerWorld().addScheduledTask(() -> DonationPerksHandler.setElement(player, message.element)); + }else{ + Wizardry.logger.warn("Received a donation perk packet from a player that isn't a donor!"); + } + + } + + return null; + } + + public static class Message implements IMessage { + + private Element element; + + // This constructor is required otherwise you'll get errors (used somewhere in fml through reflection) + public Message(){} + + public Message(Element element){ + this.element = element; + } + + @Override + public void fromBytes(ByteBuf buf){ + // The order is important + element = Element.values()[buf.readShort()]; + } + + @Override + public void toBytes(ByteBuf buf){ + buf.writeShort(element.ordinal()); + } + } +} diff --git a/src/main/java/electroblob/wizardry/packet/PacketSpellQuickAccess.java b/src/main/java/electroblob/wizardry/packet/PacketSpellQuickAccess.java new file mode 100644 index 00000000..4828216b --- /dev/null +++ b/src/main/java/electroblob/wizardry/packet/PacketSpellQuickAccess.java @@ -0,0 +1,66 @@ +package electroblob.wizardry.packet; + +import electroblob.wizardry.item.ISpellCastingItem; +import io.netty.buffer.ByteBuf; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +/** [Client -> Server] This packet is for the spell quick access keys. */ +public class PacketSpellQuickAccess 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(() -> { + + ItemStack wand = player.getHeldItemMainhand(); + + if(!(wand.getItem() instanceof ISpellCastingItem)){ + wand = player.getHeldItemOffhand(); + } + + if(wand.getItem() instanceof ISpellCastingItem){ + + ((ISpellCastingItem)wand.getItem()).selectSpell(wand, message.index); + // This line fixes the bug with continuous spells casting when they shouldn't be + player.stopActiveHand(); + } + + }); + } + + return null; + } + + public static class Message implements IMessage { + + private int index; + + // This constructor is required otherwise you'll get errors (used somewhere in fml through reflection) + public Message(){ + } + + public Message(int index){ + this.index = index; + } + + @Override + public void fromBytes(ByteBuf buf){ + // The order is important + this.index = buf.readInt(); + } + + @Override + public void toBytes(ByteBuf buf){ + buf.writeInt(index); + } + } +} diff --git a/src/main/java/electroblob/wizardry/packet/PacketSyncDonationPerks.java b/src/main/java/electroblob/wizardry/packet/PacketSyncDonationPerks.java new file mode 100644 index 00000000..403e65b5 --- /dev/null +++ b/src/main/java/electroblob/wizardry/packet/PacketSyncDonationPerks.java @@ -0,0 +1,55 @@ +package electroblob.wizardry.packet; + +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.misc.DonationPerksHandler; +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 to update the donation perks map for each player that logs in, and for + * all players whenever the map changes. + */ +public class PacketSyncDonationPerks 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(() -> DonationPerksHandler.setElements(message.elements)); + } + + return null; + } + + public static class Message implements IMessage { + + public List elements; + + // This constructor is required otherwise you'll get errors (used somewhere in fml through reflection) + public Message(){} + + public Message(List elements){ + this.elements = elements; + } + + @Override + public void fromBytes(ByteBuf buf){ + // The order is important + this.elements = new ArrayList<>(); + while(buf.isReadable()) elements.add(Element.values()[buf.readShort()]); + } + + @Override + public void toBytes(ByteBuf buf){ + for(Element element : elements) buf.writeShort(element.ordinal()); + } + } +} diff --git a/src/main/java/electroblob/wizardry/packet/PacketSyncSettings.java b/src/main/java/electroblob/wizardry/packet/PacketSyncSettings.java index 2568e7f4..6be7e380 100644 --- a/src/main/java/electroblob/wizardry/packet/PacketSyncSettings.java +++ b/src/main/java/electroblob/wizardry/packet/PacketSyncSettings.java @@ -2,11 +2,18 @@ package electroblob.wizardry.packet; import electroblob.wizardry.Settings; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.constants.Tier; import electroblob.wizardry.packet.PacketSyncSettings.Message; 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 org.apache.commons.lang3.tuple.Pair; + +import java.util.ArrayList; +import java.util.List; /** * [Server -> Client] This packet is sent to synchronise the config settings with clients on player login. @@ -32,7 +39,13 @@ public class PacketSyncSettings implements IMessageHandler { Wizardry.settings.creativeBypassesArcaneLock = message.settings.creativeBypassesArcaneLock; Wizardry.settings.slowTimeAffectsPlayers = message.settings.slowTimeAffectsPlayers; Wizardry.settings.replaceVanillaFireballs = message.settings.replaceVanillaFireballs; + Wizardry.settings.replaceVanillaFallDamage = message.settings.replaceVanillaFallDamage; Wizardry.settings.forfeitChance = message.settings.forfeitChance; + Wizardry.settings.progressionRequirements = message.settings.progressionRequirements; + Wizardry.settings.bookshelfSearchRadius = message.settings.bookshelfSearchRadius; + Wizardry.settings.bookshelfBlocks = message.settings.bookshelfBlocks; + Wizardry.settings.bookItems = message.settings.bookItems; + Wizardry.settings.passiveMobsAreAllies = message.settings.passiveMobsAreAllies; } public static class Message implements IMessage { @@ -58,7 +71,14 @@ public class PacketSyncSettings implements IMessageHandler { settings.creativeBypassesArcaneLock = buf.readBoolean(); settings.slowTimeAffectsPlayers = buf.readBoolean(); settings.replaceVanillaFireballs = buf.readBoolean(); + settings.replaceVanillaFallDamage = buf.readBoolean(); settings.forfeitChance = buf.readFloat(); + settings.progressionRequirements = new int[Tier.values().length - 1]; + for(int i = 0; i < settings.progressionRequirements.length; i++) settings.progressionRequirements[i] = buf.readInt(); + settings.bookshelfSearchRadius = buf.readInt(); + settings.bookshelfBlocks = readMetaItems(buf); + settings.bookItems = readMetaItems(buf); + settings.passiveMobsAreAllies = buf.readBoolean(); } @Override @@ -67,7 +87,32 @@ public class PacketSyncSettings implements IMessageHandler { buf.writeBoolean(settings.creativeBypassesArcaneLock); buf.writeBoolean(settings.slowTimeAffectsPlayers); buf.writeBoolean(settings.replaceVanillaFireballs); + buf.writeBoolean(settings.replaceVanillaFallDamage); buf.writeFloat((float)settings.forfeitChance); // Configs don't have floats but this can only be 0-1 anyway + for(int i = 0; i < settings.progressionRequirements.length; i++) buf.writeInt(settings.progressionRequirements[i]); + buf.writeInt(settings.bookshelfSearchRadius); + writeMetaItems(buf, settings.bookshelfBlocks); + writeMetaItems(buf, settings.bookItems); + buf.writeBoolean(settings.passiveMobsAreAllies); } + + @SuppressWarnings("unchecked") + private static Pair[] readMetaItems(ByteBuf buf){ + int length = buf.readInt(); + List> entries = new ArrayList<>(); + for(int i=0; i[] items){ + buf.writeInt(items.length); + for(Pair entry : items){ + ByteBufUtils.writeUTF8String(buf, entry.getLeft().toString()); + buf.writeShort(entry.getRight()); + } + } + } } diff --git a/src/main/java/electroblob/wizardry/packet/PacketTransportation.java b/src/main/java/electroblob/wizardry/packet/PacketTransportation.java index afb07f7d..1824de64 100644 --- a/src/main/java/electroblob/wizardry/packet/PacketTransportation.java +++ b/src/main/java/electroblob/wizardry/packet/PacketTransportation.java @@ -3,10 +3,14 @@ package electroblob.wizardry.packet; import electroblob.wizardry.Wizardry; import electroblob.wizardry.packet.PacketTransportation.Message; import io.netty.buffer.ByteBuf; +import net.minecraft.entity.Entity; +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; +import javax.annotation.Nullable; + /** * [Server -> Client] This packet is sent when a player is teleported due to the transportation spell to spawn * the particles. @@ -26,26 +30,31 @@ public class PacketTransportation implements IMessageHandler } public static class Message implements IMessage { - /** EntityID of the caster */ - public int casterID; + + /** The destination that was teleported to */ + public BlockPos destination; + public int dismountEntityID; // This constructor is required otherwise you'll get errors (used somewhere in fml through reflection) public Message(){ } - public Message(int casterID){ - this.casterID = casterID; + public Message(BlockPos destination, @Nullable Entity toDismount){ + this.destination = destination; + this.dismountEntityID = toDismount == null ? -1 : toDismount.getEntityId(); } @Override public void fromBytes(ByteBuf buf){ // The order is important - this.casterID = buf.readInt(); + this.destination = BlockPos.fromLong(buf.readLong()); + this.dismountEntityID = buf.readInt(); } @Override public void toBytes(ByteBuf buf){ - buf.writeInt(casterID); + buf.writeLong(destination.toLong()); + buf.writeInt(dismountEntityID); } } } diff --git a/src/main/java/electroblob/wizardry/packet/WizardryPacketHandler.java b/src/main/java/electroblob/wizardry/packet/WizardryPacketHandler.java index 045205bf..2959b5eb 100644 --- a/src/main/java/electroblob/wizardry/packet/WizardryPacketHandler.java +++ b/src/main/java/electroblob/wizardry/packet/WizardryPacketHandler.java @@ -13,24 +13,28 @@ public class WizardryPacketHandler { public static void initPackets(){ net = NetworkRegistry.INSTANCE.newSimpleChannel(Wizardry.MODID.toUpperCase()); - registerMessage(PacketControlInput.class, PacketControlInput.Message.class); - registerMessage(PacketCastSpell.class, PacketCastSpell.Message.class); - registerMessage(PacketTransportation.class, PacketTransportation.Message.class); - registerMessage(PacketPlayerSync.class, PacketPlayerSync.Message.class); - registerMessage(PacketGlyphData.class, PacketGlyphData.Message.class); - registerMessage(PacketCastContinuousSpell.class, PacketCastContinuousSpell.Message.class); - 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(PacketControlInput.class, PacketControlInput.Message.class); + registerMessage(PacketCastSpell.class, PacketCastSpell.Message.class); + registerMessage(PacketTransportation.class, PacketTransportation.Message.class); + registerMessage(PacketPlayerSync.class, PacketPlayerSync.Message.class); + registerMessage(PacketGlyphData.class, PacketGlyphData.Message.class); + registerMessage(PacketCastContinuousSpell.class, PacketCastContinuousSpell.Message.class); + 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(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); + 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); + registerMessage(PacketLectern.class, PacketLectern.Message.class); + registerMessage(PacketSpellQuickAccess.class, PacketSpellQuickAccess.Message.class); + registerMessage(PacketRequestDonationPerks.class, PacketRequestDonationPerks.Message.class); + registerMessage(PacketSyncDonationPerks.class, PacketSyncDonationPerks.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 index 09b9e8ab..48deb8c0 100644 --- a/src/main/java/electroblob/wizardry/potion/Curse.java +++ b/src/main/java/electroblob/wizardry/potion/Curse.java @@ -1,6 +1,7 @@ package electroblob.wizardry.potion; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.WizardryItems; import net.minecraft.item.ItemStack; import net.minecraft.potion.PotionEffect; import net.minecraft.util.ResourceLocation; @@ -27,7 +28,9 @@ public class Curse extends PotionMagicEffect { @Override public List getCurativeItems(){ - return new ArrayList<>(); // Cannot be cured! + List items = new ArrayList<>(); + items.add(new ItemStack(WizardryItems.purifying_elixir)); + return items; } @Override @@ -39,12 +42,12 @@ public class Curse extends PotionMagicEffect { super.renderInventoryEffect(x, y, effect, mc); - String name = net.minecraft.client.resources.I18n.format(this.getName()); + String name = Wizardry.proxy.translate(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)); + name = name + " " + Wizardry.proxy.translate("enchantment.level." + (effect.getAmplifier() + 1)); } List lines = mc.fontRenderer.listFormattedStringToWidth(name, 100); diff --git a/src/main/java/electroblob/wizardry/potion/CurseEnfeeblement.java b/src/main/java/electroblob/wizardry/potion/CurseEnfeeblement.java index 69daa132..234bc8b9 100644 --- a/src/main/java/electroblob/wizardry/potion/CurseEnfeeblement.java +++ b/src/main/java/electroblob/wizardry/potion/CurseEnfeeblement.java @@ -24,7 +24,7 @@ public class CurseEnfeeblement extends Curse { } public CurseEnfeeblement(boolean isBadEffect, int liquiidColour){ - super(isBadEffect, liquiidColour, new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_curse_of_enfeeblement.png")); + super(isBadEffect, liquiidColour, new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/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, diff --git a/src/main/java/electroblob/wizardry/potion/CurseUndeath.java b/src/main/java/electroblob/wizardry/potion/CurseUndeath.java index 6f2d0a9f..4bea1099 100644 --- a/src/main/java/electroblob/wizardry/potion/CurseUndeath.java +++ b/src/main/java/electroblob/wizardry/potion/CurseUndeath.java @@ -10,7 +10,7 @@ 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")); + super(isBadEffect, liquiidColour, new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/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"); } diff --git a/src/main/java/electroblob/wizardry/potion/ICustomPotionParticles.java b/src/main/java/electroblob/wizardry/potion/ICustomPotionParticles.java index ab0ec6e0..5bcc6423 100644 --- a/src/main/java/electroblob/wizardry/potion/ICustomPotionParticles.java +++ b/src/main/java/electroblob/wizardry/potion/ICustomPotionParticles.java @@ -53,7 +53,7 @@ public interface ICustomPotionParticles extends ISyncedPotion { double x = event.getEntityLiving().posX + (event.getEntityLiving().world.rand.nextDouble() - 0.5) * event.getEntityLiving().width; - double y = event.getEntityLiving().getEntityBoundingBox().minY + double y = event.getEntityLiving().posY + event.getEntityLiving().world.rand.nextDouble() * event.getEntityLiving().height; double z = event.getEntityLiving().posZ + (event.getEntityLiving().world.rand.nextDouble() - 0.5) * event.getEntityLiving().width; diff --git a/src/main/java/electroblob/wizardry/potion/ISyncedPotion.java b/src/main/java/electroblob/wizardry/potion/ISyncedPotion.java index 98e4fcab..3aa9d10a 100644 --- a/src/main/java/electroblob/wizardry/potion/ISyncedPotion.java +++ b/src/main/java/electroblob/wizardry/potion/ISyncedPotion.java @@ -4,6 +4,7 @@ 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.minecraft.potion.PotionEffect; import net.minecraftforge.event.entity.living.PotionEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; @@ -51,31 +52,26 @@ public interface ISyncedPotion { @SubscribeEvent public static void onPotionExpiryEvent(PotionEvent.PotionExpiryEvent event){ - - if(event.getPotionEffect() != null && 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()))); - } - } + onPotionEffectEnd(event.getPotionEffect(), event.getEntityLiving()); } @SubscribeEvent public static void onPotionRemoveEvent(PotionEvent.PotionRemoveEvent event){ + onPotionEffectEnd(event.getPotionEffect(), event.getEntityLiving()); + } - if(event.getPotionEffect() != null && event.getPotionEffect().getPotion() instanceof ISyncedPotion - && ((ISyncedPotion)event.getPotionEffect().getPotion()).shouldSync(event.getEntityLiving())){ + static void onPotionEffectEnd(PotionEffect effect, EntityLivingBase host){ - if(!event.getEntityLiving().world.isRemote){ - event.getEntityLiving().world.playerEntities.stream() - .filter(p -> p.getDistanceSq(event.getEntityLiving()) < SYNC_RADIUS * SYNC_RADIUS) + if(effect != null && effect.getPotion() instanceof ISyncedPotion + && ((ISyncedPotion)effect.getPotion()).shouldSync(host)){ + + if(!host.world.isRemote){ + host.world.playerEntities.stream() + .filter(p -> p.getDistanceSq(host) < SYNC_RADIUS * SYNC_RADIUS) .forEach(p -> ((EntityPlayerMP)p).connection.sendPacket(new SPacketRemoveEntityEffect( - event.getEntity().getEntityId(), event.getPotionEffect().getPotion()))); + host.getEntityId(), effect.getPotion()))); } } } + } diff --git a/src/main/java/electroblob/wizardry/potion/PotionContainment.java b/src/main/java/electroblob/wizardry/potion/PotionContainment.java index ec8d269d..ef12241f 100644 --- a/src/main/java/electroblob/wizardry/potion/PotionContainment.java +++ b/src/main/java/electroblob/wizardry/potion/PotionContainment.java @@ -2,14 +2,17 @@ package electroblob.wizardry.potion; import electroblob.wizardry.Wizardry; import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.util.EntityUtils; +import electroblob.wizardry.util.GeometryUtils; import electroblob.wizardry.util.NBTExtras; -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.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.MathHelper; import net.minecraft.util.math.Vec3d; import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent; import net.minecraftforge.fml.common.Mod; @@ -20,8 +23,10 @@ public class PotionContainment extends PotionMagicEffect { public static final String ENTITY_TAG = "containmentPos"; + private static final double RUBBERBAND_THRESHOLD = 0.24; + public PotionContainment(boolean isBadEffect, int liquidColour){ - super(isBadEffect, liquidColour, new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_containment.png")); + super(isBadEffect, liquidColour, new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/containment.png")); this.setPotionName("potion." + Wizardry.MODID + ":containment"); } @@ -44,7 +49,7 @@ public class PotionContainment extends PotionMagicEffect { NBTExtras.storeTagSafely(target.getEntityData(), 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))); + Vec3d origin = GeometryUtils.getCentre(NBTUtil.getPosFromTag(target.getEntityData().getCompoundTag(ENTITY_TAG))); double x = target.posX, y = target.posY, z = target.posZ; @@ -66,7 +71,7 @@ public class PotionContainment extends PotionMagicEffect { // for(int i = 0; i < 20; i++){ // ParticleBuilder.create(ParticleBuilder.Type.DUST).pos( // x, -// target.getEntityBoundingBox().minY + target.height * target.world.rand.nextFloat(), +// target.posY + 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); // } @@ -86,16 +91,23 @@ public class PotionContainment extends PotionMagicEffect { // 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(), +// target.posY + target.height * target.world.rand.nextFloat(), // z) // .face(EnumFacing.SOUTH).clr(0.8f, 0.9f, 1).spawn(target.world); // } // } // } - WizardryUtilities.undoGravity(target); + // Rubberbanding prevention (the 0.25 limit is from NetHandlerPlayServer, just search "moved wrongly" + if(target instanceof EntityPlayer){ + x = MathHelper.clamp(x, target.posX - RUBBERBAND_THRESHOLD, target.posX + RUBBERBAND_THRESHOLD); + y = MathHelper.clamp(y, target.posY - RUBBERBAND_THRESHOLD, target.posY + RUBBERBAND_THRESHOLD); + z = MathHelper.clamp(z, target.posZ - RUBBERBAND_THRESHOLD, target.posZ + RUBBERBAND_THRESHOLD); + } + + EntityUtils.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); + target.setPositionAndUpdate(x, y, z); // FIXME: This line must be causing the server rubberbanding, not sure why // Player motion is handled on that player's client so needs packets if(target instanceof EntityPlayerMP){ ((EntityPlayerMP)target).connection.sendPacket(new SPacketEntityVelocity(target)); @@ -113,7 +125,8 @@ public class PotionContainment extends PotionMagicEffect { @SubscribeEvent public static void onLivingUpdateEvent(LivingUpdateEvent event){ - if(event.getEntityLiving().getEntityData().hasKey(ENTITY_TAG) + // This is LAST-RESORT CLEANUP. It does NOT need checking every tick! We always check for the actual potion anyway. + if(event.getEntity().ticksExisted % 20 == 0 && 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 c4562d3c..085f151e 100644 --- a/src/main/java/electroblob/wizardry/potion/PotionDecay.java +++ b/src/main/java/electroblob/wizardry/potion/PotionDecay.java @@ -19,7 +19,7 @@ import java.util.List; public class PotionDecay extends PotionMagicEffect { public PotionDecay(boolean isBadEffect, int liquidColour){ - super(isBadEffect, liquidColour, new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_decay.png")); + super(isBadEffect, liquidColour, new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/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, @@ -46,9 +46,10 @@ public class PotionDecay extends PotionMagicEffect { // amplifier of the potion effect, and is too slow for this purpose. EntityLivingBase target = event.getEntityLiving(); - - if(!target.world.isRemote && target.isPotionActive(WizardryPotions.decay) && target.onGround - && target.ticksExisted % Constants.DECAY_SPREAD_INTERVAL == 0){ + + // Do the timing check first, it'll cut out 95% of calls to all subsequent conditions + if(target.ticksExisted % Constants.DECAY_SPREAD_INTERVAL == 0 && !target.world.isRemote + && target.isPotionActive(WizardryPotions.decay) && target.onGround){ List entities = target.world.getEntitiesWithinAABBExcludingEntity(target, target.getEntityBoundingBox()); diff --git a/src/main/java/electroblob/wizardry/potion/PotionFrost.java b/src/main/java/electroblob/wizardry/potion/PotionFrost.java index 28b89553..3ce17d8a 100644 --- a/src/main/java/electroblob/wizardry/potion/PotionFrost.java +++ b/src/main/java/electroblob/wizardry/potion/PotionFrost.java @@ -8,6 +8,7 @@ import electroblob.wizardry.util.ParticleBuilder.Type; import net.minecraft.entity.SharedMonsterAttributes; import net.minecraft.util.ResourceLocation; import net.minecraft.world.World; +import net.minecraftforge.event.entity.living.LivingEvent.LivingJumpEvent; import net.minecraftforge.event.entity.player.PlayerEvent.BreakSpeed; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; @@ -16,13 +17,12 @@ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; public class PotionFrost extends PotionMagicEffect implements ICustomPotionParticles { public PotionFrost(boolean isBadEffect, int liquidColour){ - super(isBadEffect, liquidColour, new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_frost.png")); + super(isBadEffect, liquidColour, new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/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 this.registerPotionAttributeModifier(SharedMonsterAttributes.MOVEMENT_SPEED, "35dded48-2f19-4541-8510-b29e2dc2cd51", -Constants.FROST_SLOWNESS_PER_LEVEL, 2); - // More UUIDs: 85602e0b-4801-4a87-94f3-bf617c97014e } @Override @@ -39,4 +39,15 @@ public class PotionFrost extends PotionMagicEffect implements ICustomPotionParti } } + @SubscribeEvent + public static void onLivingJumpEvent(LivingJumpEvent event){ + if(event.getEntityLiving().isPotionActive(WizardryPotions.frost)){ + if(event.getEntityLiving().getActivePotionEffect(WizardryPotions.frost).getAmplifier() == 0){ + event.getEntity().motionY *= 0.5; + }else{ + event.getEntity().motionY = 0; + } + } + } + } diff --git a/src/main/java/electroblob/wizardry/potion/PotionFrostStep.java b/src/main/java/electroblob/wizardry/potion/PotionFrostStep.java index 4c369526..3bdeee3f 100644 --- a/src/main/java/electroblob/wizardry/potion/PotionFrostStep.java +++ b/src/main/java/electroblob/wizardry/potion/PotionFrostStep.java @@ -5,15 +5,14 @@ import electroblob.wizardry.item.ItemArtefact; import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.util.BlockUtils; 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; @@ -32,7 +31,7 @@ public class PotionFrostStep extends PotionMagicEffect implements ICustomPotionP 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")); + super(isBadEffect, liquidColour, new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/frost_step.png")); this.setPotionName("potion." + Wizardry.MODID + ":frost_step"); } @@ -103,7 +102,7 @@ public class PotionFrostStep extends PotionMagicEffect implements ICustomPotionP 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)){ + if(BlockUtils.isLavaSource(state2) && 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/PotionSlowTime.java b/src/main/java/electroblob/wizardry/potion/PotionSlowTime.java index 38224a7c..05a3f6b7 100644 --- a/src/main/java/electroblob/wizardry/potion/PotionSlowTime.java +++ b/src/main/java/electroblob/wizardry/potion/PotionSlowTime.java @@ -7,8 +7,8 @@ import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.spell.SlowTime; import electroblob.wizardry.spell.Spell; +import electroblob.wizardry.util.EntityUtils; 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; @@ -34,7 +34,7 @@ public class PotionSlowTime extends PotionMagicEffect implements ISyncedPotion { public static final String NBT_KEY = "time_slowed"; public PotionSlowTime(boolean isBadEffect, int liquidColour){ - super(isBadEffect, liquidColour, new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_slow_time.png")); + super(isBadEffect, liquidColour, new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/slow_time.png")); this.setPotionName("potion." + Wizardry.MODID + ":slow_time"); } @@ -43,7 +43,7 @@ public class PotionSlowTime extends PotionMagicEffect implements ISyncedPotion { } public static void unblockNearbyEntities(EntityLivingBase host){ - List targetsBeyondRange = WizardryUtilities.getEntitiesWithinRadius(getEffectRadius() + 3, host.posX, host.posY, host.posZ, host.world, Entity.class); + List targetsBeyondRange = EntityUtils.getEntitiesWithinRadius(getEffectRadius() + 3, host.posX, host.posY, host.posZ, host.world, Entity.class); targetsBeyondRange.forEach(e -> e.updateBlocked = false); } @@ -56,7 +56,7 @@ public class PotionSlowTime extends PotionMagicEffect implements ISyncedPotion { 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); + List targetsInRange = EntityUtils.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)); @@ -130,7 +130,7 @@ public class PotionSlowTime extends PotionMagicEffect implements ISyncedPotion { } // 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); + List targetsBeyondRange = EntityUtils.getEntitiesWithinRadius(getEffectRadius() + 3, host.posX, host.posY, host.posZ, host.world, Entity.class); targetsBeyondRange.removeAll(targetsInRange); targetsBeyondRange.forEach(e -> e.updateBlocked = false); @@ -150,7 +150,7 @@ public class PotionSlowTime extends PotionMagicEffect implements ISyncedPotion { for(Entity entity : loadedEntityList){ if(entity.getEntityData().getBoolean(NBT_KEY)){ // Currently only players can cast slow time, but you could apply the effect to NPCs with commands - List nearby = WizardryUtilities.getEntitiesWithinRadius(getEffectRadius(), entity.posX, entity.posY, entity.posZ, entity.world, EntityLivingBase.class); + List nearby = EntityUtils.getLivingWithinRadius(getEffectRadius(), entity.posX, entity.posY, entity.posZ, entity.world); if(nearby.stream().noneMatch(e -> e.isPotionActive(WizardryPotions.slow_time))){ entity.getEntityData().removeTag(NBT_KEY); entity.updateBlocked = false; diff --git a/src/main/java/electroblob/wizardry/registry/Spells.java b/src/main/java/electroblob/wizardry/registry/Spells.java index 85ee05a1..3f1afe3e 100644 --- a/src/main/java/electroblob/wizardry/registry/Spells.java +++ b/src/main/java/electroblob/wizardry/registry/Spells.java @@ -4,10 +4,10 @@ import electroblob.wizardry.Wizardry; import electroblob.wizardry.entity.construct.*; import electroblob.wizardry.entity.living.*; import electroblob.wizardry.entity.projectile.*; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.spell.*; import net.minecraft.entity.projectile.EntitySnowball; 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; @@ -190,8 +190,8 @@ public final class Spells { 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 arcane_jammer = placeholder(); public static final Spell group_heal = placeholder(); public static final Spell hailstorm = placeholder(); @@ -238,6 +238,26 @@ public final class Spells { public static final Spell slow_time = placeholder(); public static final Spell resurrection = placeholder(); + // Wizardry 4.3 spells + + public static final Spell frost_barrier = placeholder(); + public static final Spell blinding_flash = placeholder(); + public static final Spell enrage = placeholder(); + public static final Spell mark_sacrifice = placeholder(); + + public static final Spell permafrost = placeholder(); + public static final Spell stormcloud = placeholder(); + public static final Spell withering_totem = placeholder(); + public static final Spell fangs = placeholder(); + public static final Spell guardian_beam = placeholder(); + public static final Spell radiant_totem = placeholder(); + + public static final Spell firestorm = placeholder(); + public static final Spell flamecatcher = placeholder(); + public static final Spell zombie_apocalypse = placeholder(); + public static final Spell boulder = placeholder(); + public static final Spell celestial_smite = placeholder(); + @SubscribeEvent public static void register(RegistryEvent.Register event){ @@ -265,17 +285,17 @@ public final class Spells { registry.register(new SpellProjectile<>("fireball", EntityMagicFireball::new).addProperties(Spell.DAMAGE, Spell.BURN_DURATION));//new Fireball()); registry.register(new FlameRay()); 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 SpellConstructRanged<>("fire_sigil", EntityFireSigil::new, true).floor(true).addProperties(Spell.EFFECT_RADIUS, 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 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 SpellConstructRanged<>("frost_sigil", EntityFrostSigil::new, true).floor(true).addProperties(Spell.DAMAGE, Spell.EFFECT_DURATION, Spell.EFFECT_STRENGTH)); + registry.register(new SpellConstructRanged<>("frost_sigil", EntityFrostSigil::new, true).floor(true).addProperties(Spell.EFFECT_RADIUS, Spell.DAMAGE, Spell.EFFECT_DURATION, Spell.EFFECT_STRENGTH)); registry.register(new LightningRay()); 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 SpellConstructRanged<>("lightning_sigil", EntityLightningSigil::new, true).floor(true).addProperties(Spell.EFFECT_RADIUS, Spell.DIRECT_DAMAGE, Spell.EFFECT_RADIUS, EntityLightningSigil.SECONDARY_RANGE, 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()); @@ -299,7 +319,7 @@ public final class Spells { registry.register(new HealAlly()); 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 SpellConstruct<>("ring_of_fire", SpellActions.POINT_DOWN, EntityFireRing::new, false).floor(true).addProperties(Spell.EFFECT_RADIUS, Spell.DAMAGE, Spell.BURN_DURATION)); registry.register(new Detonate()); 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)); @@ -337,7 +357,7 @@ public final class Spells { registry.register(new PhaseStep()); registry.register(new VanishingBox()); registry.register(new GreaterHeal()); - registry.register(new SpellConstruct<>("healing_aura", EnumAction.BOW, EntityHealAura::new, false).addProperties(Spell.DAMAGE, Spell.HEALTH)); + registry.register(new SpellConstruct<>("healing_aura", SpellActions.POINT_DOWN, EntityHealAura::new, false).addProperties(Spell.EFFECT_RADIUS, Spell.DAMAGE, Spell.HEALTH)); registry.register(new Forcefield()); registry.register(new SpellBuff("ironflesh", 0.4f, 0.5f, 0.6f, () -> MobEffects.RESISTANCE).soundValues(0.7f, 1.2f, 0.4f)); registry.register(new Transience()); @@ -356,7 +376,7 @@ public final class Spells { registry.register(new ForestsCurse()); registry.register(new Flight()); 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 SpellConstructRanged<>("black_hole", EntityBlackHole::new, false).addProperties(Spell.EFFECT_RADIUS).soundValues(2, 0.7f, 0)); registry.register(new Shockwave()); registry.register(new SummonIronGolem()); registry.register(new ArrowRain()); @@ -389,8 +409,8 @@ public final class Spells { registry.register(new CurseOfSoulbinding()); registry.register(new Cobwebs()); registry.register(new Decoy()); - registry.register(new ArcaneJammer()); registry.register(new ConjureArmour()); + registry.register(new ArcaneJammer()); registry.register(new GroupHeal()); registry.register(new Hailstorm()); @@ -436,6 +456,29 @@ public final class Spells { registry.register(new SpeedTime()); registry.register(new SlowTime()); registry.register(new Resurrection()); + + // Wizardry 4.3 spells + + registry.register(new FrostBarrier()); + + registry.register(new BlindingFlash()); + registry.register(new Enrage()); + registry.register(new MarkSacrifice()); + + registry.register(new Permafrost()); + registry.register(new Stormcloud()); + registry.register(new WitheringTotem()); + registry.register(new Fangs()); + registry.register(new GuardianBeam()); + registry.register(new SpellBuff("mirage", 0.64f, 0.47f, 0.9f, () -> WizardryPotions.mirage)); + registry.register(new RadiantTotem()); + + registry.register(new Firestorm()); + registry.register(new Flamecatcher()); + registry.register(new ZombieApocalypse()); + registry.register(new Boulder()); + registry.register(new CelestialSmite()); + } } \ 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 f5cc4aef..0d40c928 100644 --- a/src/main/java/electroblob/wizardry/registry/WizardryAdvancementTriggers.java +++ b/src/main/java/electroblob/wizardry/registry/WizardryAdvancementTriggers.java @@ -24,11 +24,14 @@ public final class WizardryAdvancementTriggers { 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 spell_failure = new CustomAdvancementTrigger("trigger_spell_failure"); + public static final CustomAdvancementTrigger wand_levelup = new CustomAdvancementTrigger("trigger_wand_levelup"); + public static final CustomAdvancementTrigger restore_imbuement_altar = new CustomAdvancementTrigger("restore_imbuement_altar"); 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 WizardryContainerTrigger arcane_workbench = new WizardryContainerTrigger(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 final WizardryContainerTrigger imbuement_altar = new WizardryContainerTrigger(new ResourceLocation(Wizardry.MODID, "imbuement_altar")); public static void register(){ @@ -39,10 +42,13 @@ public final class WizardryAdvancementTriggers { CriteriaTriggers.register(buy_master_spell); CriteriaTriggers.register(wizard_trade); CriteriaTriggers.register(spell_failure); + CriteriaTriggers.register(wand_levelup); + CriteriaTriggers.register(restore_imbuement_altar); CriteriaTriggers.register(visit_structure); CriteriaTriggers.register(arcane_workbench); CriteriaTriggers.register(cast_spell); CriteriaTriggers.register(discover_spell); + CriteriaTriggers.register(imbuement_altar); } } \ 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 0392545e..3933afb0 100644 --- a/src/main/java/electroblob/wizardry/registry/WizardryBlocks.java +++ b/src/main/java/electroblob/wizardry/registry/WizardryBlocks.java @@ -41,21 +41,44 @@ public final class WizardryBlocks { public static final Block arcane_workbench = placeholder(); public static final Block crystal_ore = placeholder(); + public static final Block crystal_flower = placeholder(); + public static final Block transportation_stone = placeholder(); + public static final Block crystal_block = 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(); + public static final Block crystal_flower_pot = placeholder(); + public static final Block permafrost = placeholder(); + + public static final Block runestone = placeholder(); + public static final Block runestone_pedestal = placeholder(); + + public static final Block gilded_wood = placeholder(); + + public static final Block oak_bookshelf = placeholder(); + public static final Block spruce_bookshelf = placeholder(); + public static final Block birch_bookshelf = placeholder(); + public static final Block jungle_bookshelf = placeholder(); + public static final Block acacia_bookshelf = placeholder(); + public static final Block dark_oak_bookshelf = placeholder(); + + public static final Block oak_lectern = placeholder(); + public static final Block spruce_lectern = placeholder(); + public static final Block birch_lectern = placeholder(); + public static final Block jungle_lectern = placeholder(); + public static final Block acacia_lectern = placeholder(); + public static final Block dark_oak_lectern = placeholder(); + + public static final Block receptacle = placeholder(); + public static final Block imbuement_altar = placeholder(); /** * Sets both the registry and unlocalised names of the given block, then registers it with the given registry. Use @@ -76,25 +99,51 @@ public final class WizardryBlocks { @SubscribeEvent public static void register(RegistryEvent.Register event){ + BlockBookshelf.initBookProperties(); + IForgeRegistry registry = event.getRegistry(); + // TODO: Put everything in block classes wherever possible 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, "crystal_flower", new BlockCrystalFlower(Material.PLANTS).setHardness(0.0F).setCreativeTab(WizardryTabs.WIZARDRY)); 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).setLightOpacity(1).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, "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, "snare", new BlockSnare(Material.PLANTS).setHardness(0.0F)); + registerBlock(registry, "spectral_block", new BlockSpectral(Material.GLASS).setLightOpacity(1).setBlockUnbreakable().setResistance(6000000.0F)); + 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, "thorns", new BlockThorns()); + registerBlock(registry, "obsidian_crust", new BlockObsidianCrust()); registerBlock(registry, "dry_frosted_ice", new BlockDryFrostedIce()); + registerBlock(registry, "crystal_flower_pot", new BlockCrystalFlowerPot()); + registerBlock(registry, "permafrost", new BlockPermafrost()); + + registerBlock(registry, "runestone", new BlockRunestone(Material.ROCK)); + registerBlock(registry, "runestone_pedestal", new BlockPedestal(Material.ROCK)); + + registerBlock(registry, "gilded_wood", new BlockGildedWood()); + + registerBlock(registry, "oak_bookshelf", new BlockBookshelf()); + registerBlock(registry, "spruce_bookshelf", new BlockBookshelf()); + registerBlock(registry, "birch_bookshelf", new BlockBookshelf()); + registerBlock(registry, "jungle_bookshelf", new BlockBookshelf()); + registerBlock(registry, "acacia_bookshelf", new BlockBookshelf()); + registerBlock(registry, "dark_oak_bookshelf", new BlockBookshelf()); + + registerBlock(registry, "oak_lectern", new BlockLectern()); + registerBlock(registry, "birch_lectern", new BlockLectern()); + registerBlock(registry, "spruce_lectern", new BlockLectern()); + registerBlock(registry, "jungle_lectern", new BlockLectern()); + registerBlock(registry, "acacia_lectern", new BlockLectern()); + registerBlock(registry, "dark_oak_lectern", new BlockLectern()); + + registerBlock(registry, "receptacle", new BlockReceptacle()); + registerBlock(registry, "imbuement_altar", new BlockImbuementAltar()); } @@ -106,7 +155,11 @@ public final class WizardryBlocks { 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(TileEntityThorns.class, new ResourceLocation(Wizardry.MODID, "player_save_timed")); GameRegistry.registerTileEntity(TileEntityShrineCore.class, new ResourceLocation(Wizardry.MODID, "shrine_core")); + GameRegistry.registerTileEntity(TileEntityBookshelf.class, new ResourceLocation(Wizardry.MODID, "bookshelf")); + GameRegistry.registerTileEntity(TileEntityLectern.class, new ResourceLocation(Wizardry.MODID, "lectern")); + GameRegistry.registerTileEntity(TileEntityReceptacle.class, new ResourceLocation(Wizardry.MODID, "receptacle")); + GameRegistry.registerTileEntity(TileEntityImbuementAltar.class, new ResourceLocation(Wizardry.MODID, "imbuement_altar")); } } \ 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 7c3014b0..9f33c8f3 100644 --- a/src/main/java/electroblob/wizardry/registry/WizardryEnchantments.java +++ b/src/main/java/electroblob/wizardry/registry/WizardryEnchantments.java @@ -4,7 +4,7 @@ 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 electroblob.wizardry.util.InventoryUtils; import net.minecraft.enchantment.Enchantment; import net.minecraftforge.event.RegistryEvent; import net.minecraftforge.fml.common.Mod; @@ -56,9 +56,9 @@ public final class WizardryEnchantments { 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")); + event.getRegistry().register(new EnchantmentMagicProtection(Enchantment.Rarity.UNCOMMON, EnchantmentMagicProtection.Type.MAGIC, InventoryUtils.ARMOUR_SLOTS).setRegistryName(Wizardry.MODID, "magic_protection")); + event.getRegistry().register(new EnchantmentMagicProtection(Enchantment.Rarity.RARE, EnchantmentMagicProtection.Type.FROST, InventoryUtils.ARMOUR_SLOTS).setRegistryName(Wizardry.MODID, "frost_protection")); + event.getRegistry().register(new EnchantmentMagicProtection(Enchantment.Rarity.RARE, EnchantmentMagicProtection.Type.SHOCK, InventoryUtils.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 index b7bf9839..d5568de0 100644 --- a/src/main/java/electroblob/wizardry/registry/WizardryEntities.java +++ b/src/main/java/electroblob/wizardry/registry/WizardryEntities.java @@ -61,18 +61,18 @@ public class WizardryEntities { 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(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()); + 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) + 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) @@ -94,15 +94,16 @@ public class WizardryEntities { // 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()); + 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(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()); + registry.register(createEntry(EntityRemnant.class, "remnant", TrackingType.LIVING).egg(0x414141, 0xe5daae).build()); // Directed projectiles registry.register(createEntry(EntityMagicMissile.class, "magic_missile", TrackingType.PROJECTILE).build()); @@ -111,21 +112,22 @@ public class WizardryEntities { 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()); + registry.register(createEntry(EntityFlamecatcherArrow.class, "flamecatcher_arrow", 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(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(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(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()); @@ -135,27 +137,33 @@ public class WizardryEntities { 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(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(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()); + registry.register(createEntry(EntityZombieSpawner.class, "zombie_spawner", TrackingType.CONSTRUCT).build()); + registry.register(createEntry(EntityRadiantTotem.class, "radiant_totem", TrackingType.CONSTRUCT).build()); + registry.register(createEntry(EntityWitheringTotem.class, "withering_totem", 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(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()); + registry.register(createEntry(EntityStormcloud.class, "stormcloud", 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(EntityTornado.class, "tornado") .tracker(160, 3, false).build()); registry.register(createEntry(EntityIceSpike.class, "ice_spike") .tracker(160, 1, true).build()); + registry.register(createEntry(EntityBoulder.class, "boulder") .tracker(160, 1, true).build()); // Vertical velocity is not constant + registry.register(createEntry(EntityIceBarrier.class, "ice_barrier") .tracker(160, 1, true).build()); } diff --git a/src/main/java/electroblob/wizardry/registry/WizardryItems.java b/src/main/java/electroblob/wizardry/registry/WizardryItems.java index 6ae881c3..a697589a 100644 --- a/src/main/java/electroblob/wizardry/registry/WizardryItems.java +++ b/src/main/java/electroblob/wizardry/registry/WizardryItems.java @@ -13,6 +13,7 @@ import electroblob.wizardry.registry.WizardryTabs.CreativeTabListed; import electroblob.wizardry.registry.WizardryTabs.CreativeTabSorted; import net.minecraft.block.Block; import net.minecraft.block.BlockDispenser; +import net.minecraft.block.BlockPlanks; import net.minecraft.dispenser.BehaviorProjectileDispense; import net.minecraft.dispenser.IPosition; import net.minecraft.entity.IProjectile; @@ -24,6 +25,7 @@ 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.tileentity.BannerPattern; import net.minecraft.world.World; import net.minecraftforge.common.util.EnumHelper; import net.minecraftforge.event.RegistryEvent; @@ -35,7 +37,9 @@ import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import javax.annotation.Nonnull; +import java.util.Arrays; import java.util.HashMap; +import java.util.Locale; import java.util.Map; /** @@ -80,6 +84,7 @@ public final class WizardryItems { public static final Item arcane_tome = placeholder(); public static final Item spell_book = placeholder(); public static final Item scroll = placeholder(); + public static final Item ruined_spell_book = placeholder(); public static final Item magic_wand = placeholder(); public static final Item apprentice_wand = placeholder(); @@ -152,6 +157,8 @@ public final class WizardryItems { public static final Item smoke_bomb = placeholder(); public static final Item spark_bomb = placeholder(); + public static final Item spectral_dust = placeholder(); + public static final Item wizard_hat = placeholder(); public static final Item wizard_robe = placeholder(); public static final Item wizard_leggings = placeholder(); @@ -198,6 +205,7 @@ public final class WizardryItems { public static final Item spectral_boots = placeholder(); public static final Item lightning_hammer = placeholder(); + public static final Item flamecatcher = placeholder(); public static final Item ring_condensing = placeholder(); public static final Item ring_siphoning = placeholder(); @@ -206,6 +214,7 @@ public final class WizardryItems { 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_meteor = 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(); @@ -214,6 +223,7 @@ public final class WizardryItems { 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_stormcloud = placeholder(); public static final Item ring_soulbinding = placeholder(); public static final Item ring_leeching = placeholder(); public static final Item ring_necromancy_melee = placeholder(); @@ -222,6 +232,7 @@ public final class WizardryItems { 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_evoker = placeholder(); public static final Item ring_extraction = placeholder(); public static final Item ring_mana_return = placeholder(); public static final Item ring_blockwrangler = placeholder(); @@ -237,6 +248,7 @@ public final class WizardryItems { 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_frost_warding = placeholder(); public static final Item amulet_potential = placeholder(); public static final Item amulet_channeling = placeholder(); public static final Item amulet_lich = placeholder(); @@ -248,21 +260,29 @@ public final class WizardryItems { 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 amulet_absorption = placeholder(); public static final Item charm_haggler = placeholder(); public static final Item charm_experience_tome = placeholder(); + public static final Item charm_move_speed = placeholder(); + public static final Item charm_spell_discovery = 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_undead_helmets = placeholder(); + public static final Item charm_hunger_casting = 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_sixth_sense = 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_black_hole = placeholder(); + public static final Item charm_mount_teleporting = placeholder(); public static final Item charm_feeding = placeholder(); private static final Map, Item> WAND_MAP = new HashMap<>(); @@ -357,8 +377,15 @@ public final class WizardryItems { * 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){ + registerItemBlock(registry, block, new ItemBlock(block)); + } + + /** Registers the given ItemBlock for the given block, with the same registry name as that block. 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, ItemBlock itemblock){ // We don't need to keep a reference to the ItemBlock - Item itemblock = new ItemBlock(block).setRegistryName(block.getRegistryName()); + itemblock.setRegistryName(block.getRegistryName()); registry.register(itemblock); if(block.getCreativeTab() instanceof CreativeTabListed){ @@ -366,9 +393,9 @@ public final class WizardryItems { } } - private static void registerMultiTexturedItemBlock(IForgeRegistry registry, Block block, boolean separateNames){ + private static void registerMultiTexturedItemBlock(IForgeRegistry registry, Block block, boolean separateNames, String... prefixes){ // We don't need to keep a reference to the ItemBlock - Item itemblock = new ItemBlockMultiTexturedElemental(block, separateNames).setRegistryName(block.getRegistryName()); + Item itemblock = new ItemBlockMultiTextured(block, separateNames, prefixes).setRegistryName(block.getRegistryName()); registry.register(itemblock); if(block.getCreativeTab() instanceof CreativeTabListed){ @@ -387,11 +414,33 @@ public final class WizardryItems { // Not all blocks need an ItemBlock registerItemBlock(registry, WizardryBlocks.arcane_workbench); registerItemBlock(registry, WizardryBlocks.crystal_ore); - registerItemBlock(registry, WizardryBlocks.crystal_flower); + registerItemBlock(registry, WizardryBlocks.crystal_flower, new ItemCrystalFlower()); registerItemBlock(registry, WizardryBlocks.transportation_stone); - registerMultiTexturedItemBlock(registry, WizardryBlocks.crystal_block, true); - registerMultiTexturedItemBlock(registry, WizardryBlocks.runestone, false); - registerMultiTexturedItemBlock(registry, WizardryBlocks.runestone_pedestal, false); + + String[] elements = Arrays.stream(Element.values()).map(Element::getName).toArray(String[]::new); + String[] woodTypes = Arrays.stream(BlockPlanks.EnumType.values()).map(BlockPlanks.EnumType::getName).toArray(String[]::new); + + registerMultiTexturedItemBlock(registry, WizardryBlocks.crystal_block, true, elements); + registerMultiTexturedItemBlock(registry, WizardryBlocks.runestone, false, elements); + registerMultiTexturedItemBlock(registry, WizardryBlocks.runestone_pedestal, false, elements); + registerMultiTexturedItemBlock(registry, WizardryBlocks.gilded_wood, true, woodTypes); + + registerItemBlock(registry, WizardryBlocks.oak_bookshelf); + registerItemBlock(registry, WizardryBlocks.spruce_bookshelf); + registerItemBlock(registry, WizardryBlocks.birch_bookshelf); + registerItemBlock(registry, WizardryBlocks.jungle_bookshelf); + registerItemBlock(registry, WizardryBlocks.acacia_bookshelf); + registerItemBlock(registry, WizardryBlocks.dark_oak_bookshelf); + + registerItemBlock(registry, WizardryBlocks.oak_lectern); + registerItemBlock(registry, WizardryBlocks.spruce_lectern); + registerItemBlock(registry, WizardryBlocks.birch_lectern); + registerItemBlock(registry, WizardryBlocks.jungle_lectern); + registerItemBlock(registry, WizardryBlocks.acacia_lectern); + registerItemBlock(registry, WizardryBlocks.dark_oak_lectern); + + registerItemBlock(registry, WizardryBlocks.receptacle); + registerItemBlock(registry, WizardryBlocks.imbuement_altar); // Items @@ -400,29 +449,30 @@ public final class WizardryItems { 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, "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, "ruined_spell_book", new Item().setCreativeTab(WizardryTabs.WIZARDRY).setMaxStackSize(16)); registerItem(registry, "magic_wand", new ItemWand(Tier.NOVICE, null)); - registerItem(registry, "apprentice_wand", new ItemWand(Tier.APPRENTICE, 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, "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, "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, "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, "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)); @@ -432,31 +482,31 @@ public final class WizardryItems { 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, "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, "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, "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, "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, "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, "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, "storage_upgrade", new ItemWandUpgrade()); registerItem(registry, "siphon_upgrade", new ItemWandUpgrade()); registerItem(registry, "condenser_upgrade", new ItemWandUpgrade()); registerItem(registry, "range_upgrade", new ItemWandUpgrade()); @@ -466,32 +516,34 @@ public final class WizardryItems { registerItem(registry, "attunement_upgrade", new ItemWandUpgrade()); registerItem(registry, "melee_upgrade", new ItemWandUpgrade()); - registerItem(registry, "flaming_axe", new ItemFlamingAxe(Materials.MAGICAL)); + 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, "purifying_elixir", new ItemPurifyingElixir()); registerItem(registry, "firebomb", new ItemFirebomb()); - registerItem(registry, "poison_bomb", new ItemPoisonBomb()); + registerItem(registry, "poison_bomb", new ItemPoisonBomb()); registerItem(registry, "smoke_bomb", new ItemSmokeBomb()); registerItem(registry, "spark_bomb", new ItemSparkBomb()); + registerItem(registry, "spectral_dust", new ItemSpectralDust()); + 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_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_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_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)); @@ -502,7 +554,7 @@ public final class WizardryItems { 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_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)); @@ -510,87 +562,100 @@ public final class WizardryItems { 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_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_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_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, "flamecatcher", new ItemFlamecatcher()); - 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, "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_meteor", new ItemArtefact(EnumRarity.EPIC, 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_stormcloud", new ItemArtefact(EnumRarity.RARE, 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_evoker", 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, "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_frost_warding", new ItemArtefact(EnumRarity.RARE, 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, "amulet_absorption", new ItemArtefact(EnumRarity.EPIC, 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)); + 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_move_speed", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.CHARM)); + registerItem(registry, "charm_spell_discovery", new ItemArtefact(EnumRarity.UNCOMMON, 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.UNCOMMON, ItemArtefact.Type.CHARM)); + registerItem(registry, "charm_undead_helmets", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.CHARM)); + registerItem(registry, "charm_hunger_casting", 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_sixth_sense", new ItemArtefact(EnumRarity.UNCOMMON, 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_black_hole", new ItemArtefact(EnumRarity.EPIC, ItemArtefact.Type.CHARM)); + registerItem(registry, "charm_mount_teleporting", new ItemArtefact(EnumRarity.RARE, ItemArtefact.Type.CHARM)); + registerItem(registry, "charm_feeding", new ItemArtefact(EnumRarity.UNCOMMON, ItemArtefact.Type.CHARM)); } @@ -643,6 +708,52 @@ public final class WizardryItems { BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(scroll, new BehaviourSpellDispense()); +// BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(spectral_dust, new Bootstrap.BehaviorDispenseOptional(){ +// +// // TODO: Needs packets of some kind, may be able to get away with using the tile entity sync packet +// +// @Override +// protected ItemStack dispenseStack(IBlockSource source, ItemStack stack){ +// +// World world = source.getWorld(); +// EnumFacing direction = source.getBlockState().getValue(BlockDispenser.FACING); +// BlockPos pos = source.getBlockPos().offset(direction); +// TileEntity tileEntity = world.getTileEntity(pos); +// +// if(tileEntity instanceof TileEntityReceptacle && ((TileEntityReceptacle)tileEntity).getElement() == null){ +// ((TileEntityReceptacle)tileEntity).setElement(Element.values()[stack.getMetadata()]); +// stack.shrink(1); +// world.checkLight(pos); +// // TESTME: Do we need this? +// world.notifyBlockUpdate(pos, source.getBlockState(), source.getBlockState(), 3); +// return stack; +// } +// +// return super.dispenseStack(source, stack); +// } +// +// @Override +// protected void playDispenseSound(IBlockSource source){ +// EnumFacing direction = source.getBlockState().getValue(BlockDispenser.FACING); +// BlockPos pos = source.getBlockPos().offset(direction); +// source.getWorld().playSound(pos.getX(), pos.getY(), pos.getZ(), WizardrySounds.BLOCK_RECEPTACLE_IGNITE, +// SoundCategory.BLOCKS, 0.7f, 0.7f, false); +// } +// }); + + } + + /** Called from init() in the main mod class to register wizardry's banner patterns. */ + public static void registerBannerPatterns(){ + for(Element element : Element.values()){ + if(element != Element.MAGIC) addBannerPattern("WIZARDRY_" + element.getName().toUpperCase(Locale.ROOT), + "wizardry_" + element.getName(), "eb" + element.getName().charAt(0), + new ItemStack(WizardryItems.magic_crystal, 1, element.ordinal())); + } + } + + private static void addBannerPattern(String codeName, String fileName, String hashName, ItemStack patternItem){ + EnumHelper.addEnum(BannerPattern.class, codeName, new Class[]{String.class, String.class, ItemStack.class}, fileName, hashName, patternItem); } public static void populateWandMap(){ diff --git a/src/main/java/electroblob/wizardry/registry/WizardryLoot.java b/src/main/java/electroblob/wizardry/registry/WizardryLoot.java index bf2584c7..a0df5807 100644 --- a/src/main/java/electroblob/wizardry/registry/WizardryLoot.java +++ b/src/main/java/electroblob/wizardry/registry/WizardryLoot.java @@ -1,10 +1,9 @@ package electroblob.wizardry.registry; import electroblob.wizardry.Wizardry; -import electroblob.wizardry.constants.Tier; +import electroblob.wizardry.constants.Element; import electroblob.wizardry.loot.RandomSpell; import electroblob.wizardry.loot.WizardSpell; -import electroblob.wizardry.spell.Spell; import net.minecraft.entity.EnumCreatureType; import net.minecraft.util.ResourceLocation; import net.minecraft.world.storage.loot.*; @@ -17,9 +16,6 @@ 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 @@ -33,6 +29,11 @@ public final class WizardryLoot { //public static final String FROM_SPAWNER_NBT_FLAG = "fromSpawner"; + public static final ResourceLocation[] RUINED_SPELL_BOOK_LOOT_TABLES = Arrays.stream(Element.values()) + .filter(e -> e != Element.MAGIC) + .map(e -> new ResourceLocation(Wizardry.MODID, "gameplay/imbuement_altar/ruined_spell_book_" + e.getName())) + .toArray(ResourceLocation[]::new); + private WizardryLoot(){} // No instances! /** Called from the preInit method in the main mod class to register the custom dungeon loot. */ @@ -72,42 +73,12 @@ public final class WizardryLoot { LootTableList.register(new ResourceLocation(Wizardry.MODID, "subsets/epic_artefacts")); LootTableList.register(new ResourceLocation(Wizardry.MODID, "entities/evil_wizard")); LootTableList.register(new ResourceLocation(Wizardry.MODID, "entities/mob_additions")); + LootTableList.register(new ResourceLocation(Wizardry.MODID, "gameplay/fishing/junk_additions")); + LootTableList.register(new ResourceLocation(Wizardry.MODID, "gameplay/fishing/treasure_additions")); + for(ResourceLocation location : RUINED_SPELL_BOOK_LOOT_TABLES) LootTableList.register(location); } - /** - * 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 @@ -138,6 +109,15 @@ public final class WizardryLoot { event.getTable().addPool(getAdditive(Wizardry.MODID + ":entities/mob_additions", Wizardry.MODID + "_additional_mob_drops")); } } + // Fishing loot + // This works slightly differently in that it modifies the existing pools rather than adding new ones + // This is because you are supposed to only catch one item at a time! + if(event.getName().toString().matches("minecraft:gameplay/fishing/junk")){ + // The first (and in this case, only) vanilla loot pool is named "main" + event.getTable().getPool("main").addEntry(getAdditiveEntry(Wizardry.MODID + ":gameplay/fishing/junk_additions", 4)); + }else if(event.getName().toString().matches("minecraft:gameplay/fishing/treasure")){ + event.getTable().getPool("main").addEntry(getAdditiveEntry(Wizardry.MODID + ":gameplay/fishing/treasure_additions", 1)); + } } private static LootPool getAdditive(String entryName, String poolName){ diff --git a/src/main/java/electroblob/wizardry/registry/WizardryPotions.java b/src/main/java/electroblob/wizardry/registry/WizardryPotions.java index 3f799f6a..75535e1a 100644 --- a/src/main/java/electroblob/wizardry/registry/WizardryPotions.java +++ b/src/main/java/electroblob/wizardry/registry/WizardryPotions.java @@ -55,6 +55,8 @@ public final class WizardryPotions { public static final Potion curse_of_undeath = placeholder(); public static final Potion containment = placeholder(); public static final Potion frost_step = placeholder(); + public static final Potion mark_of_sacrifice = placeholder(); + public static final Potion mirage = placeholder(); /** * Sets both the registry and unlocalised names of the given potion, then registers it with the given registry. Use @@ -83,7 +85,7 @@ public final class WizardryPotions { 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")){ + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/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); @@ -91,7 +93,7 @@ public final class WizardryPotions { }.setBeneficial()); // 0xffe89b registerPotion(registry, "fireskin", new PotionMagicEffectParticles(false, 0, - new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_fireskin.png")){ + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/fireskin.png")){ @Override public void spawnCustomParticle(World world, double x, double y, double z){ world.spawnParticle(EnumParticleTypes.FLAME, x, y, z, 0, 0, 0); @@ -105,7 +107,7 @@ public final class WizardryPotions { }.setBeneficial()); // 0xff2f02 registerPotion(registry, "ice_shroud", new PotionMagicEffectParticles(false, 0, - new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_ice_shroud.png")){ + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/ice_shroud.png")){ @Override public void spawnCustomParticle(World world, double x, double y, double z){ float brightness = 0.5f + (world.rand.nextFloat() / 2); @@ -115,7 +117,7 @@ public final class WizardryPotions { }.setBeneficial()); // 0x52f1ff registerPotion(registry, "static_aura", new PotionMagicEffectParticles(false, 0, - new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_static_aura.png")){ + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/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); @@ -125,7 +127,7 @@ public final class WizardryPotions { 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")){ + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/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 @@ -138,22 +140,25 @@ public final class WizardryPotions { }.setBeneficial()); registerPotion(registry, "arcane_jammer", new PotionMagicEffect(true, 0xcf4aa2, - new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_arcane_jammer.png"))); + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/arcane_jammer.png"))); registerPotion(registry, "mind_trick", new PotionMagicEffect(true, 0x601683, - new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_mind_trick.png"))); + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/mind_trick.png"))); - registerPotion(registry, "mind_control", new PotionMagicEffect(true, 0x320b44, - new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_mind_control.png"))); + registerPotion(registry, "mind_control", new PotionMagicEffectParticles(true, 0x320b44, + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/mind_control.png")) { + @Override + public void spawnCustomParticle(World world, double x, double y, double z){} // We only want the syncing + }); registerPotion(registry, "font_of_mana", new PotionMagicEffect(false, 0xffe5bb, - new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_font_of_mana.png")).setBeneficial()); + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/font_of_mana.png")).setBeneficial()); registerPotion(registry, "fear", new PotionMagicEffect(true, 0xbd0100, - new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_fear.png"))); + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/fear.png"))); registerPotion(registry, "curse_of_soulbinding", new Curse(true, 0x0f000f, - new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_curse_of_soulbinding.png")){ + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/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... @@ -161,7 +166,7 @@ public final class WizardryPotions { }); registerPotion(registry, "paralysis", new PotionMagicEffectParticles(true, 0, - new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_paralysis.png")){ + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/paralysis.png")){ @Override public void spawnCustomParticle(World world, double x, double y, double z){ ParticleBuilder.create(Type.SPARK).pos(x, y, z).spawn(world); @@ -169,15 +174,15 @@ public final class WizardryPotions { }); registerPotion(registry, "muffle", new PotionMagicEffect(false, 0x4464d9, - new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_muffle.png")).setBeneficial()); + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/muffle.png")).setBeneficial()); registerPotion(registry, "ward", new PotionMagicEffect(false, 0xc991d0, - new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_ward.png")).setBeneficial()); + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/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()); + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/empowerment.png")).setBeneficial()); registerPotion(registry, "curse_of_enfeeblement", new CurseEnfeeblement(true, 0x36000b)); @@ -187,6 +192,15 @@ public final class WizardryPotions { registerPotion(registry, "frost_step", new PotionFrostStep(false, 0).setBeneficial()); + registerPotion(registry, "mark_of_sacrifice", new PotionMagicEffect(true, 0xe90e48, + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/mark_of_sacrifice.png"))); + + registerPotion(registry, "mirage", new PotionMagicEffectParticles(false, 0, // No particles or we'll see the player's actual position! + new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icons/mirage.png")){ + @Override + public void spawnCustomParticle(World world, double x, double y, double z){} // We only want the syncing + }.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 index 869088ff..5d2b5e7b 100644 --- a/src/main/java/electroblob/wizardry/registry/WizardryRecipes.java +++ b/src/main/java/electroblob/wizardry/registry/WizardryRecipes.java @@ -14,8 +14,9 @@ import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.registries.IForgeRegistry; -import java.util.LinkedList; -import java.util.Queue; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; /** * Class responsible for defining and registering wizardry's non-JSON recipes (i.e. smelting recipes and dynamic @@ -29,14 +30,27 @@ public final class WizardryRecipes { private WizardryRecipes(){} // No instances! - private static final Queue chargingRecipeQueue = new LinkedList<>(); + private static final List chargeableItems = new ArrayList<>(); + + private static boolean registered; /** 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); + + if(registered){ + Wizardry.logger.warn("Tried to add an item to mana flask charging after it was registered, this will do nothing!"); + return; + } + + chargeableItems.add(item); + } + + /** Returns an unmodifiable view of all registered items that can be charged with mana flasks. */ + public static List getChargeableItems(){ + return Collections.unmodifiableList(chargeableItems); } /** Now only deals with the dynamic crafting recipes and the smelting recipes. */ @@ -49,11 +63,7 @@ public final class WizardryRecipes { // Mana flask recipes - Item chargeable; - - while(!chargingRecipeQueue.isEmpty()){ - // Use remove() and not poll() because the queue shouldn't be empty in here - chargeable = chargingRecipeQueue.remove(); + for(Item chargeable : chargeableItems){ registry.register(new RecipeRechargeWithFlask(chargeable, (ItemManaFlask)WizardryItems.small_mana_flask) .setRegistryName(new ResourceLocation(Wizardry.MODID, "recipes/small_flask_" + chargeable.getRegistryName().getPath()))); @@ -65,6 +75,9 @@ public final class WizardryRecipes { .setRegistryName(new ResourceLocation(Wizardry.MODID, "recipes/large_flask_" + chargeable.getRegistryName().getPath()))); } + + registered = true; + } } diff --git a/src/main/java/electroblob/wizardry/registry/WizardrySounds.java b/src/main/java/electroblob/wizardry/registry/WizardrySounds.java index f5a9b447..81295678 100644 --- a/src/main/java/electroblob/wizardry/registry/WizardrySounds.java +++ b/src/main/java/electroblob/wizardry/registry/WizardrySounds.java @@ -1,8 +1,6 @@ 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; @@ -10,6 +8,9 @@ import net.minecraftforge.event.RegistryEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import java.util.ArrayList; +import java.util.List; + /** * Class responsible for defining, storing and registering all of wizardry's sound events. * @@ -24,26 +25,42 @@ public final class WizardrySounds { /** Sound category for all spell-related sounds. This includes inanimate magical entities, but not minions. * @see electroblob.wizardry.util.CustomSoundCategory */ public static SoundCategory SPELLS; + + private static final List sounds = new ArrayList<>(); 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 BLOCK_LECTERN_LOCATE_SPELL = createSound("block.lectern.locate_spell"); + public static final SoundEvent BLOCK_RECEPTACLE_IGNITE = createSound("block.receptacle.ignite"); + public static final SoundEvent BLOCK_IMBUEMENT_ALTAR_IMBUE = createSound("block.imbuement_altar.imbue"); 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_WAND_CHARGEUP = createSound("item.wand.chargeup"); 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 ITEM_MANA_FLASK_USE = createSound("item.mana_flask.use"); + public static final SoundEvent ITEM_MANA_FLASK_RECHARGE = createSound("item.mana_flask.recharge"); + public static final SoundEvent ITEM_FLAMECATCHER_SHOOT = createSound("item.flamecatcher.shoot"); + public static final SoundEvent ITEM_FLAMECATCHER_FLAME = createSound("item.flamecatcher.flame"); 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_BLACK_HOLE_BREAK_BLOCK = createSound("entity.black_hole.break_block"); public static final SoundEvent ENTITY_BLIZZARD_AMBIENT = createSound("entity.blizzard.ambient"); + public static final SoundEvent ENTITY_BOULDER_ROLL = createSound("entity.boulder.roll"); + public static final SoundEvent ENTITY_BOULDER_LAND = createSound("entity.boulder.land"); + public static final SoundEvent ENTITY_BOULDER_HIT = createSound("entity.boulder.hit"); + public static final SoundEvent ENTITY_BOULDER_BREAK_BLOCK = createSound("entity.boulder.break_block"); + public static final SoundEvent ENTITY_BUBBLE_POP = createSound("entity.bubble.pop"); 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_AMBIENT = createSound("entity.forcefield.ambient"); 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"); @@ -51,11 +68,18 @@ public final class WizardrySounds { 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_BARRIER_DEFLECT = createSound("entity.ice_barrier.deflect"); + public static final SoundEvent ENTITY_ICE_BARRIER_EXTEND = createSound("entity.ice_barrier.extend"); 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_RADIANT_TOTEM_AMBIENT = createSound("entity.radiant_totem.ambient"); + public static final SoundEvent ENTITY_RADIANT_TOTEM_VANISH = createSound("entity.radiant_totem.vanish"); 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_WITHERING_TOTEM_AMBIENT = createSound("entity.withering_totem.ambient"); + public static final SoundEvent ENTITY_WITHERING_TOTEM_EXPLODE = createSound("entity.withering_totem.explode"); + public static final SoundEvent ENTITY_ZOMBIE_SPAWNER_SPAWN = createSound("entity.zombie_spawner.spawn"); 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"); @@ -71,6 +95,9 @@ public final class WizardrySounds { 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_REMNANT_AMBIENT = createSound("entity.remnant.ambient"); + public static final SoundEvent ENTITY_REMNANT_HURT = createSound("entity.remnant.hurt"); + public static final SoundEvent ENTITY_REMNANT_DEATH = createSound("entity.remnant.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"); @@ -82,6 +109,8 @@ public final class WizardrySounds { 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_STORMCLOUD_THUNDER = createSound("entity.stormcloud.thunder"); + public static final SoundEvent ENTITY_STORMCLOUD_ATTACK = createSound("entity.stormcloud.attack"); 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"); @@ -97,6 +126,7 @@ public final class WizardrySounds { 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_FLAMECATCHER_ARROW_HIT = createSound("entity.flamecatcher_arrow.hit"); 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"); @@ -132,6 +162,7 @@ public final class WizardrySounds { 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"); + public static final SoundEvent MISC_SPELL_FAIL = createSound("misc.spell_fail"); // Trick borrowed from the Twilight Forest, makes things neater. @@ -140,130 +171,20 @@ public final class WizardrySounds { return createSound(Wizardry.MODID, name); } - /** Creates a sound with the given name, to be read from {@code assets/[modID]/sounds.json}. */ + /** Creates a sound with the given name, to be read from {@code assets/[modID]/sounds.json}. The sound will be + * registered automatically later. */ public static SoundEvent createSound(String modID, String name){ // All the setRegistryName methods delegate to this one, it doesn't matter which you use. - return new SoundEvent(new ResourceLocation(modID, name)).setRegistryName(name); + SoundEvent sound = new SoundEvent(new ResourceLocation(modID, name)).setRegistryName(name); + sounds.add(sound); + return sound; } // 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(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.getAllSpells()){ - event.getRegistry().registerAll(spell.getSounds()); - } - - for(Forfeit forfeit : Forfeit.getForfeits()){ - event.getRegistry().register(forfeit.getSound()); - } + event.getRegistry().registerAll(sounds.toArray(new SoundEvent[0])); } + } \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/spell/Arc.java b/src/main/java/electroblob/wizardry/spell/Arc.java index adfe2670..9ae94cba 100644 --- a/src/main/java/electroblob/wizardry/spell/Arc.java +++ b/src/main/java/electroblob/wizardry/spell/Arc.java @@ -1,15 +1,15 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; +import electroblob.wizardry.util.EntityUtils; 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.EnumFacing; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; @@ -19,7 +19,7 @@ import net.minecraft.world.World; public class Arc extends SpellRay { public Arc(){ - super("arc", false, EnumAction.NONE); + super("arc", SpellActions.POINT, false); this.aimAssist(0.6f); this.soundValues(1, 1.7f, 0.2f); this.addProperties(DAMAGE); @@ -28,13 +28,13 @@ public class Arc extends SpellRay { @Override protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ - if(WizardryUtilities.isLiving(target)){ + if(EntityUtils.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); + ParticleBuilder.spawnShockParticles(world, target.posX, target.posY + target.height/2, target.posZ); } // This is a lot neater than it was, thanks to the damage type system. diff --git a/src/main/java/electroblob/wizardry/spell/ArcaneJammer.java b/src/main/java/electroblob/wizardry/spell/ArcaneJammer.java index cbf02353..eac89a9a 100644 --- a/src/main/java/electroblob/wizardry/spell/ArcaneJammer.java +++ b/src/main/java/electroblob/wizardry/spell/ArcaneJammer.java @@ -1,19 +1,24 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.constants.Constants; import electroblob.wizardry.event.SpellCastEvent; +import electroblob.wizardry.event.SpellCastEvent.Source; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.util.EntityUtils; 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.monster.EntitySpellcasterIllager; -import net.minecraft.item.EnumAction; import net.minecraft.potion.PotionEffect; 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.Vec3d; import net.minecraft.world.World; @@ -24,10 +29,16 @@ import net.minecraftforge.fml.common.eventhandler.EventPriority; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import java.lang.reflect.Field; +import java.util.Random; @Mod.EventBusSubscriber public class ArcaneJammer extends SpellRay { + /** Random number generator used to coordinate whether spellcasting works or not. */ + private static final Random random = new Random(); + /** The number of ticks between updates of whether spellcasting works or not. */ + private static final int UPDATE_INTERVAL = 15; + private static final Field spellTicks; static { // Yay more reflection @@ -35,19 +46,20 @@ public class ArcaneJammer extends SpellRay { } public ArcaneJammer(){ - super("arcane_jammer", false, EnumAction.NONE); + super("arcane_jammer", SpellActions.POINT, false); this.soundValues(0.7f, 1, 0.4f); - this.addProperties(EFFECT_DURATION); + this.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)){ - + if(EntityUtils.isLiving(target)){ if(!world.isRemote){ ((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.arcane_jammer, - (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)), 0)); + (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)), + getProperty(EFFECT_STRENGTH).intValue() + (int)((modifiers.get(SpellModifiers.POTENCY) - 1) + / Constants.POTENCY_INCREASE_PER_TIER + 0.5f))); } } @@ -72,8 +84,41 @@ public class ArcaneJammer extends SpellRay { @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); + // Arcane jammer has a chance to prevent spell casting + // We can't just use a straight-up random number because otherwise people can spam it to get it to cast + // Instead, we're using the world time to make blocks of time when spells will and won't work + random.setSeed(event.getWorld().getTotalWorldTime() / UPDATE_INTERVAL); + // For some unfathomable reason, the first call to this after setting the seed remains the same for long + // sequences of consecutive seeds, so let's clear it out first to get to a more changeable value + random.nextInt(2); + + if(event.getCaster() != null && event.getCaster().isPotionActive(WizardryPotions.arcane_jammer) + // Arcane jammer I has a 1/2 chance, level II has a 2/3 chance, and so on + && random.nextInt(event.getCaster().getActivePotionEffect(WizardryPotions.arcane_jammer).getAmplifier() + 2) > 0){ + + event.setCanceled(true); + + // TODO: This currently doesn't play nicely with continuous or charge-up spells + if(event.getSource() == Source.WAND || event.getSource() == Source.SCROLL){ + event.getCaster().setActiveHand(EnumHand.MAIN_HAND); + } + + // Because we're using a seed that should be consistent, we can do client-side stuff! + event.getWorld().playSound(event.getCaster().posX, event.getCaster().posY, event.getCaster().posZ, + WizardrySounds.MISC_SPELL_FAIL, WizardrySounds.SPELLS, 1, 1, false); + + if(event.getWorld().isRemote){ + + Vec3d centre = event.getCaster().getPositionEyes(1).add(event.getCaster().getLookVec()); + + for(int i = 0; i < 5; i++){ + double x = centre.x + 0.5f * (event.getWorld().rand.nextFloat() - 0.5f); + double y = centre.y + 0.5f * (event.getWorld().rand.nextFloat() - 0.5f); + double z = centre.z + 0.5f * (event.getWorld().rand.nextFloat() - 0.5f); + event.getWorld().spawnParticle(EnumParticleTypes.SMOKE_LARGE, x, y, z, 0, 0, 0); + } + } + } } @SubscribeEvent diff --git a/src/main/java/electroblob/wizardry/spell/ArcaneLock.java b/src/main/java/electroblob/wizardry/spell/ArcaneLock.java index b2c88f5a..60b5534d 100644 --- a/src/main/java/electroblob/wizardry/spell/ArcaneLock.java +++ b/src/main/java/electroblob/wizardry/spell/ArcaneLock.java @@ -1,15 +1,12 @@ 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 electroblob.wizardry.item.SpellActions; +import electroblob.wizardry.util.*; 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.server.MinecraftServer; import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntityDispenser; @@ -17,7 +14,9 @@ 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.LivingDestroyBlockEvent; import net.minecraftforge.event.entity.player.PlayerInteractEvent; +import net.minecraftforge.event.world.BlockEvent; import net.minecraftforge.event.world.ExplosionEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; @@ -29,7 +28,7 @@ public class ArcaneLock extends SpellRay { public static final String NBT_KEY = "arcaneLockOwner"; public ArcaneLock(){ - super("arcane_lock", false, EnumAction.NONE); + super("arcane_lock", SpellActions.POINT, false); } @Override public boolean requiresPacket(){ return true; } @@ -47,52 +46,45 @@ public class ArcaneLock extends SpellRay { 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; - } + if(toggleLock(world, pos, (EntityPlayer)caster)){ + BlockPos otherHalf = BlockUtils.getConnectedChest(world, pos); + if(otherHalf != null) toggleLock(world, otherHalf, (EntityPlayer)caster); + return true; } } return false; } + private boolean toggleLock(World world, BlockPos pos, EntityPlayer player){ + + TileEntity tileentity = world.getTileEntity(pos); + + if(tileentity != null){ + + if(tileentity.getTileData().hasUniqueId(NBT_KEY)){ + // Unlocking + if(world.getPlayerEntityByUUID(tileentity.getTileData().getUniqueId(NBT_KEY)) == player){ + NBTExtras.removeUniqueId(tileentity.getTileData(), NBT_KEY); + return true; + } + }else{ + // Locking + tileentity.getTileData().setUniqueId(NBT_KEY, player.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); - } - } - } - } + // Event Handlers @SubscribeEvent public static void onRightClickBlockEvent(PlayerInteractEvent.RightClickBlock event){ @@ -117,12 +109,51 @@ public class ArcaneLock extends SpellRay { } } - private static boolean canBypassLocks(EntityPlayer player){ + // This event is the ideal, because it's seamless. For normal mining it works great, but it won't work for things + // like the mine spell, mining lasers, destruction gadgets, etc. so we also need to intercept the general + // block break event below, just in case. + @SubscribeEvent + public static void onLeftClickBlockEvent(PlayerInteractEvent.LeftClickBlock event){ + event.setCanceled(checkForLockedBlockBreak(event.getEntityPlayer(), event.getWorld(), event.getPos())); + } + @SubscribeEvent + public static void onBlockBreakEvent(BlockEvent.BreakEvent event){ + event.setCanceled(checkForLockedBlockBreak(event.getPlayer(), event.getWorld(), event.getPos())); + } + + // Yup, this spell even protects your chests from the enderdragon now! + @SubscribeEvent + public static void onLivingDestroyBlockEvent(LivingDestroyBlockEvent event){ + event.setCanceled(checkForLockedBlockBreak(event.getEntityLiving(), event.getEntity().world, event.getPos())); + } + + private static boolean checkForLockedBlockBreak(EntityLivingBase breaker, World world, BlockPos pos){ + + if(!(breaker instanceof EntityPlayer) || !canBypassLocks((EntityPlayer)breaker)){ + + TileEntity tileentity = world.getTileEntity(pos); + + // 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(breaker.getUniqueID() != tileentity.getTileData().getUniqueId(ArcaneLock.NBT_KEY)){ + return true; + } + } + } + + return false; + } + + 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); + return server != null && EntityUtils.isPlayerOp(player, server); } @SubscribeEvent diff --git a/src/main/java/electroblob/wizardry/spell/ArrowRain.java b/src/main/java/electroblob/wizardry/spell/ArrowRain.java index aeb1ef78..733507d4 100644 --- a/src/main/java/electroblob/wizardry/spell/ArrowRain.java +++ b/src/main/java/electroblob/wizardry/spell/ArrowRain.java @@ -11,6 +11,7 @@ public class ArrowRain extends SpellConstructRanged { public ArrowRain(){ super("arrow_rain", EntityArrowRain::new, false); this.floor(true); + addProperties(EFFECT_RADIUS); } @Override diff --git a/src/main/java/electroblob/wizardry/spell/Banish.java b/src/main/java/electroblob/wizardry/spell/Banish.java index e12fe1ff..01281224 100644 --- a/src/main/java/electroblob/wizardry/spell/Banish.java +++ b/src/main/java/electroblob/wizardry/spell/Banish.java @@ -1,15 +1,15 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.BlockUtils; 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.EnumFacing; import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.math.BlockPos; @@ -23,7 +23,7 @@ public class Banish extends SpellRay { public static final String MAXIMUM_TELEPORT_DISTANCE = "maximum_teleport_distance"; public Banish(){ - super("banish", false, EnumAction.NONE); + super("banish", SpellActions.POINT, false); this.addProperties(MINIMUM_TELEPORT_DISTANCE, MAXIMUM_TELEPORT_DISTANCE); } @@ -67,14 +67,14 @@ public class Banish extends SpellRay { 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); + Integer y = BlockUtils.getNearestFloor(world, + new BlockPos(x, (int)entity.posY, z), (int)radius); if(world.isRemote){ for(int i=0; i<10; i++){ double dx1 = entity.posX; - double dy1 = entity.getEntityBoundingBox().minY + entity.height * world.rand.nextFloat(); + double dy1 = entity.posY + 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); diff --git a/src/main/java/electroblob/wizardry/spell/BlindingFlash.java b/src/main/java/electroblob/wizardry/spell/BlindingFlash.java new file mode 100644 index 00000000..21d10dd0 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/BlindingFlash.java @@ -0,0 +1,41 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.item.SpellActions; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.EntityUtils; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import electroblob.wizardry.util.SpellModifiers; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.init.MobEffects; +import net.minecraft.potion.PotionEffect; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public class BlindingFlash extends SpellAreaEffect { + + public BlindingFlash(){ + super("blinding_flash", SpellActions.POINT_UP, false); + this.alwaysSucceed(true); + addProperties(EFFECT_DURATION); // No effect strength, you're either blinded or you're not! + } + + @Override + protected boolean affectEntity(World world, Vec3d origin, @Nullable EntityLivingBase caster, EntityLivingBase target, int targetCount, int ticksInUse, SpellModifiers modifiers){ + + if(EntityUtils.isLiving(target)){ + int duration = (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)); + target.addPotionEffect(new PotionEffect(MobEffects.BLINDNESS, duration, 0)); + } + + return true; + } + + @Override + protected void spawnParticleEffect(World world, Vec3d origin, double radius, @Nullable EntityLivingBase caster, SpellModifiers modifiers){ + if(caster != null) origin = origin.add(0, caster.height + 1, 0); + ParticleBuilder.create(Type.SPHERE).pos(origin).scale((float)radius * 0.8f).spawn(world); + } +} diff --git a/src/main/java/electroblob/wizardry/spell/Blink.java b/src/main/java/electroblob/wizardry/spell/Blink.java index fcc4975e..1092a250 100644 --- a/src/main/java/electroblob/wizardry/spell/Blink.java +++ b/src/main/java/electroblob/wizardry/spell/Blink.java @@ -1,41 +1,47 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.RayTracer; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.*; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.item.EntityBoat; 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.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 Blink extends Spell { public Blink(){ - super("blink", EnumAction.NONE, false); + super("blink", SpellActions.POINT, false); addProperties(RANGE); } @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - RayTraceResult rayTrace = RayTracer.standardBlockRayTrace(world, caster, - getProperty(RANGE).doubleValue() * modifiers.get(WizardryItems.range_upgrade), false); + boolean teleportMount = caster.isRiding() && ItemArtefact.isArtefactActive(caster, WizardryItems.charm_mount_teleporting); + boolean hitLiquids = teleportMount && caster.getRidingEntity() instanceof EntityBoat; // Boats teleport to the surface + + double range = getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade); + + RayTraceResult rayTrace = RayTracer.standardBlockRayTrace(world, caster, range, hitLiquids, !hitLiquids,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. if(world.isRemote){ for(int i = 0; i < 10; i++){ double dx = caster.posX; - double dy = caster.getEntityBoundingBox().minY + 2 * world.rand.nextFloat(); + double dy = caster.posY + 2 * world.rand.nextFloat(); double dz = caster.posZ; // For portal particles, velocity is not velocity but the offset where they start, then drift to // the actual position given. @@ -48,33 +54,21 @@ public class Blink extends Spell { if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){ - BlockPos pos = rayTrace.getBlockPos(); + BlockPos pos = rayTrace.getBlockPos().offset(rayTrace.sideHit); + Entity toTeleport = teleportMount ? caster.getRidingEntity() : caster; - // Leave space for the player's head - if(rayTrace.sideHit == EnumFacing.DOWN) pos = pos.down(); + Vec3d vec = EntityUtils.findSpaceForTeleport(toTeleport, GeometryUtils.getFaceCentre(pos, EnumFacing.DOWN), teleportMount); - // 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(); + if(vec != null){ + // Plays before and after so it is heard from both positions + this.playSound(world, caster, ticksInUse, -1, modifiers); + + if(!teleportMount && caster.isRiding()) caster.dismountRidingEntity(); + if(!world.isRemote) toTeleport.setPositionAndUpdate(vec.x, vec.y, vec.z); + + this.playSound(world, caster, ticksInUse, -1, modifiers); + return true; } - - 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; } return false; @@ -86,12 +80,12 @@ public class Blink extends Spell { 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) + double radius = caster.getDistance(target.posX, target.posY, target.posZ) + world.rand.nextDouble() * 3.0d; 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); + Integer y = BlockUtils.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. @@ -100,7 +94,7 @@ public class Blink extends Spell { if(world.isRemote){ for(int i = 0; i < 10; i++){ double dx1 = caster.posX; - double dy1 = caster.getEntityBoundingBox().minY + caster.height * world.rand.nextFloat(); + double dy1 = caster.posY + caster.height * 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); diff --git a/src/main/java/electroblob/wizardry/spell/Boulder.java b/src/main/java/electroblob/wizardry/spell/Boulder.java new file mode 100644 index 00000000..133677f0 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/Boulder.java @@ -0,0 +1,32 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.entity.construct.EntityBoulder; +import electroblob.wizardry.item.SpellActions; +import electroblob.wizardry.util.GeometryUtils; +import electroblob.wizardry.util.SpellModifiers; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.Vec3d; + +public class Boulder extends SpellConstruct { + + public static final String SPEED = "speed"; + public static final String KNOCKBACK_STRENGTH = "knockback_strength"; + + public Boulder(){ + super("boulder", SpellActions.SUMMON, EntityBoulder::new, false); + addProperties(SPEED, DAMAGE, KNOCKBACK_STRENGTH); + } + + @Override + protected void addConstructExtras(EntityBoulder construct, EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){ + float speed = getProperty(SPEED).floatValue(); + // Unlike tornado, boulder always has the same speed + Vec3d direction = caster == null ? new Vec3d(side.getDirectionVec()) : GeometryUtils.horizontalise(caster.getLookVec()); + construct.setHorizontalVelocity(direction.x * speed, direction.z * speed); + construct.rotationYaw = caster == null ? side.getHorizontalAngle() : caster.rotationYaw; + double yOffset = caster == null ? 0 : 1.6; + construct.setPosition(construct.posX + direction.x, construct.posY + yOffset, construct.posZ + direction.z); + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/Bubble.java b/src/main/java/electroblob/wizardry/spell/Bubble.java index 2ac47406..30561666 100644 --- a/src/main/java/electroblob/wizardry/spell/Bubble.java +++ b/src/main/java/electroblob/wizardry/spell/Bubble.java @@ -1,16 +1,16 @@ package electroblob.wizardry.spell; import electroblob.wizardry.entity.construct.EntityBubble; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.EntityUtils; 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.item.EnumAction; import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.SoundEvent; @@ -21,7 +21,7 @@ import net.minecraft.world.World; public class Bubble extends SpellRay { public Bubble(){ - super("bubble", false, EnumAction.NONE); + super("bubble", SpellActions.POINT, false); this.soundValues(0.5f, 1.1f, 0.2f); addProperties(DURATION); } @@ -34,7 +34,7 @@ public class Bubble extends SpellRay { @Override protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ - if(WizardryUtilities.isLiving(target)){ + if(EntityUtils.isLiving(target)){ if(!world.isRemote){ // Deals a small amount damage so the target counts as being hit by the caster diff --git a/src/main/java/electroblob/wizardry/spell/CelestialSmite.java b/src/main/java/electroblob/wizardry/spell/CelestialSmite.java new file mode 100644 index 00000000..83790453 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/CelestialSmite.java @@ -0,0 +1,66 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.item.SpellActions; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.*; +import electroblob.wizardry.util.MagicDamage.DamageType; +import electroblob.wizardry.util.ParticleBuilder.Type; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +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 javax.annotation.Nullable; +import java.util.List; + +public class CelestialSmite extends SpellRay { + + public CelestialSmite(){ + super("celestial_smite", SpellActions.POINT, false); + addProperties(EFFECT_RADIUS, DAMAGE, BURN_DURATION); + this.ignoreLivingEntities(true); + } + + @Override + protected boolean onEntityHit(World world, Entity target, Vec3d hit, @Nullable EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + return false; + } + + @Override + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, @Nullable EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + double radius = getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade); + + List targets = EntityUtils.getLivingWithinRadius(radius, hit.x, hit.y, hit.z, world); + + DamageSource source = caster == null ? DamageSource.MAGIC : MagicDamage.causeDirectMagicDamage(caster, DamageType.RADIANT); + float damage = getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY); + + for(EntityLivingBase target : targets){ + EntityUtils.attackEntityWithoutKnockback(target, source, damage); + target.setFire(getProperty(BURN_DURATION).intValue()); + } + + if(world.isRemote){ + + ParticleBuilder.create(Type.BEAM).pos(hit.x, world.getActualHeight(), hit.z).target(hit).scale(8) + .clr(0xffbf00).time(10).spawn(world); + ParticleBuilder.create(Type.SPHERE).pos(hit).scale(4).clr(0xfff098).spawn(world); + + if(side == EnumFacing.UP){ + Vec3d vec = hit.add(new Vec3d(side.getDirectionVec()).scale(GeometryUtils.ANTI_Z_FIGHTING_OFFSET)); + ParticleBuilder.create(Type.SCORCH).pos(vec).face(side).scale(3).spawn(world); + } + } + + return true; + } + + @Override + protected boolean onMiss(World world, @Nullable EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ + return false; + } +} diff --git a/src/main/java/electroblob/wizardry/spell/ChainLightning.java b/src/main/java/electroblob/wizardry/spell/ChainLightning.java index 83b3df2f..b0fba78a 100644 --- a/src/main/java/electroblob/wizardry/spell/ChainLightning.java +++ b/src/main/java/electroblob/wizardry/spell/ChainLightning.java @@ -1,12 +1,12 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.util.*; import electroblob.wizardry.util.MagicDamage.DamageType; 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.EnumFacing; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; @@ -28,7 +28,7 @@ public class ChainLightning extends SpellRay { public static final String TERTIARY_MAX_TARGETS = "tertiary_max_targets"; // This is per secondary target public ChainLightning(){ - super("chain_lightning", false, EnumAction.NONE); + super("chain_lightning", SpellActions.POINT, false); this.aimAssist(0.6f); this.soundValues(1, 1.7f, 0.2f); addProperties(PRIMARY_DAMAGE, SECONDARY_DAMAGE, TERTIARY_DAMAGE, SECONDARY_RANGE, TERTIARY_RANGE, @@ -40,17 +40,17 @@ public class ChainLightning extends SpellRay { // 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(WizardryUtilities.isLiving(target)){ + if(EntityUtils.isLiving(target)){ electrocute(world, caster, origin, target, getProperty(PRIMARY_DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY)); // Secondary chaining effect - List secondaryTargets = WizardryUtilities.getEntitiesWithinRadius( + List secondaryTargets = EntityUtils.getLivingWithinRadius( getProperty(SECONDARY_RANGE).doubleValue(), target.posX, target.posY + target.height / 2, target.posZ, world); secondaryTargets.remove(target); - secondaryTargets.removeIf(e -> !WizardryUtilities.isLiving(e)); + secondaryTargets.removeIf(e -> !EntityUtils.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()); @@ -62,13 +62,13 @@ public class ChainLightning extends SpellRay { // Tertiary chaining effect - List tertiaryTargets = WizardryUtilities.getEntitiesWithinRadius( + List tertiaryTargets = EntityUtils.getLivingWithinRadius( getProperty(TERTIARY_RANGE).doubleValue(), 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 -> !EntityUtils.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()); @@ -110,7 +110,7 @@ public class ChainLightning extends SpellRay { 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); + ParticleBuilder.spawnShockParticles(world, target.posX, target.posY + 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 index d6e9a4df..128ad562 100644 --- a/src/main/java/electroblob/wizardry/spell/Charge.java +++ b/src/main/java/electroblob/wizardry/spell/Charge.java @@ -3,17 +3,17 @@ package electroblob.wizardry.spell; import electroblob.wizardry.data.IVariable; import electroblob.wizardry.data.Persistence; import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.EntityUtils; 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; @@ -37,7 +37,7 @@ public class Charge extends Spell { private static final double EXTRA_HIT_MARGIN = 1; public Charge(){ - super("charge", EnumAction.NONE, false); + super("charge", SpellActions.POINT, false); addProperties(CHARGE_SPEED, DURATION, DAMAGE, KNOCKBACK_STRENGTH); this.soundValues(0.6f, 1, 0); } @@ -94,7 +94,7 @@ public class Charge extends Spell { if(collided.isEmpty()) chargeTime--; else{ - WizardryUtilities.playSoundAtPlayer(player, SoundEvents.ENTITY_GENERIC_HURT, 1, 1); + EntityUtils.playSoundAtPlayer(player, SoundEvents.ENTITY_GENERIC_HURT, 1, 1); chargeTime = 0; } } diff --git a/src/main/java/electroblob/wizardry/spell/Clairvoyance.java b/src/main/java/electroblob/wizardry/spell/Clairvoyance.java index c5ffba8b..a2d7c868 100644 --- a/src/main/java/electroblob/wizardry/spell/Clairvoyance.java +++ b/src/main/java/electroblob/wizardry/spell/Clairvoyance.java @@ -4,6 +4,7 @@ import electroblob.wizardry.data.IStoredVariable; import electroblob.wizardry.data.Persistence; import electroblob.wizardry.data.WizardData; import electroblob.wizardry.item.ISpellCastingItem; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.misc.WizardryPathFinder; import electroblob.wizardry.packet.PacketClairvoyance; import electroblob.wizardry.packet.WizardryPacketHandler; @@ -17,7 +18,6 @@ import net.minecraft.entity.SharedMonsterAttributes; import net.minecraft.entity.monster.EntityZombie; 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.pathfinding.Path; import net.minecraft.pathfinding.PathNodeType; @@ -41,7 +41,7 @@ public class Clairvoyance extends Spell { public static final IStoredVariable DIMENSION_KEY = IStoredVariable.StoredVariable.ofInt("clairvoyanceDimension", Persistence.ALWAYS); public Clairvoyance(){ - super("clairvoyance", EnumAction.BOW, false); + super("clairvoyance", SpellActions.POINT_UP, false); addProperties(RANGE, DURATION); WizardData.registerStoredVariables(LOCATION_KEY, DIMENSION_KEY); } diff --git a/src/main/java/electroblob/wizardry/spell/Cobwebs.java b/src/main/java/electroblob/wizardry/spell/Cobwebs.java index 9d2367a1..1397533d 100644 --- a/src/main/java/electroblob/wizardry/spell/Cobwebs.java +++ b/src/main/java/electroblob/wizardry/spell/Cobwebs.java @@ -1,14 +1,14 @@ package electroblob.wizardry.spell; import electroblob.wizardry.constants.Constants; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.tileentity.TileEntityTimer; +import electroblob.wizardry.util.BlockUtils; 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; @@ -19,7 +19,7 @@ import java.util.List; public class Cobwebs extends SpellRay { public Cobwebs(){ - super("cobwebs", false, EnumAction.NONE); + super("cobwebs", SpellActions.POINT, false); this.ignoreLivingEntities(true); addProperties(EFFECT_RADIUS, DURATION); } @@ -42,7 +42,7 @@ public class Cobwebs extends SpellRay { float radius = getProperty(EFFECT_RADIUS).floatValue() + 0.73f * blastUpgradeCount; - List sphere = WizardryUtilities.getBlockSphere(pos, radius * modifiers.get(WizardryItems.blast_upgrade)); + List sphere = BlockUtils.getBlockSphere(pos, radius * modifiers.get(WizardryItems.blast_upgrade)); for(BlockPos pos1 : sphere){ diff --git a/src/main/java/electroblob/wizardry/spell/ConjureArmour.java b/src/main/java/electroblob/wizardry/spell/ConjureArmour.java index bc62c719..b180ad29 100644 --- a/src/main/java/electroblob/wizardry/spell/ConjureArmour.java +++ b/src/main/java/electroblob/wizardry/spell/ConjureArmour.java @@ -3,9 +3,9 @@ package electroblob.wizardry.spell; import com.google.common.collect.ImmutableMap; import electroblob.wizardry.item.IConjuredItem; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.InventoryUtils; import electroblob.wizardry.util.NBTExtras; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.inventory.EntityEquipmentSlot; import net.minecraft.item.Item; @@ -33,10 +33,10 @@ public class ConjureArmour extends SpellConjuration { boolean flag = false; // Used this rather than getArmorInventoryList because I need to access the slot itself - for(EntityEquipmentSlot slot : WizardryUtilities.ARMOUR_SLOTS){ + for(EntityEquipmentSlot slot : InventoryUtils.ARMOUR_SLOTS){ if(caster.getItemStackFromSlot(slot).isEmpty() && - !WizardryUtilities.doesPlayerHaveItem(caster, SPECTRAL_ARMOUR_MAP.get(slot))){ + !InventoryUtils.doesPlayerHaveItem(caster, SPECTRAL_ARMOUR_MAP.get(slot))){ armour = new ItemStack(SPECTRAL_ARMOUR_MAP.get(slot)); IConjuredItem.setDurationMultiplier(armour, modifiers.get(WizardryItems.duration_upgrade)); diff --git a/src/main/java/electroblob/wizardry/spell/ConjureBlock.java b/src/main/java/electroblob/wizardry/spell/ConjureBlock.java index 50ce61d1..72b8780d 100644 --- a/src/main/java/electroblob/wizardry/spell/ConjureBlock.java +++ b/src/main/java/electroblob/wizardry/spell/ConjureBlock.java @@ -1,15 +1,15 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.tileentity.TileEntityTimer; +import electroblob.wizardry.util.BlockUtils; 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; @@ -20,7 +20,7 @@ public class ConjureBlock extends SpellRay { private static final String BLOCK_LIFETIME = "block_lifetime"; public ConjureBlock(){ - super("conjure_block", false, EnumAction.NONE); + super("conjure_block", SpellActions.POINT, false); this.ignoreLivingEntities(true); addProperties(BLOCK_LIFETIME); } @@ -53,7 +53,7 @@ public class ConjureBlock extends SpellRay { .clr(0.75f, 1, 0.85f).spawn(world); } - if(WizardryUtilities.canBlockBeReplaced(world, pos)){ + if(BlockUtils.canBlockBeReplaced(world, pos)){ if(!world.isRemote){ diff --git a/src/main/java/electroblob/wizardry/spell/Containment.java b/src/main/java/electroblob/wizardry/spell/Containment.java index cc9c0b71..1584ba4d 100644 --- a/src/main/java/electroblob/wizardry/spell/Containment.java +++ b/src/main/java/electroblob/wizardry/spell/Containment.java @@ -1,14 +1,14 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.util.EntityUtils; 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; @@ -18,7 +18,7 @@ import net.minecraft.world.World; public class Containment extends SpellRay { public Containment(){ - super("containment", false, EnumAction.NONE); + super("containment", SpellActions.POINT, false); this.soundValues(1, 1, 0.2f); addProperties(EFFECT_DURATION, EFFECT_STRENGTH); } @@ -26,7 +26,7 @@ public class Containment extends SpellRay { @Override protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ - if(WizardryUtilities.isLiving(target)){ + if(EntityUtils.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)))); diff --git a/src/main/java/electroblob/wizardry/spell/CureEffects.java b/src/main/java/electroblob/wizardry/spell/CureEffects.java index 6be6e69a..62d1a44d 100644 --- a/src/main/java/electroblob/wizardry/spell/CureEffects.java +++ b/src/main/java/electroblob/wizardry/spell/CureEffects.java @@ -6,6 +6,8 @@ import net.minecraft.init.Items; import net.minecraft.item.ItemStack; import net.minecraft.potion.PotionEffect; +import java.util.ArrayList; + public class CureEffects extends SpellBuff { public CureEffects(){ @@ -22,7 +24,7 @@ public class CureEffects extends SpellBuff { boolean flag = false; - for(PotionEffect effect : caster.getActivePotionEffects()){ + for(PotionEffect effect : new ArrayList<>(caster.getActivePotionEffects())){ // Get outta here, CMEs // The PotionEffect version (as opposed to Potion) does not call cleanup callbacks if(effect.isCurativeItem(milk)){ caster.removePotionEffect(effect.getPotion()); diff --git a/src/main/java/electroblob/wizardry/spell/CurseOfEnfeeblement.java b/src/main/java/electroblob/wizardry/spell/CurseOfEnfeeblement.java index a6a5f2e1..511e9eb1 100644 --- a/src/main/java/electroblob/wizardry/spell/CurseOfEnfeeblement.java +++ b/src/main/java/electroblob/wizardry/spell/CurseOfEnfeeblement.java @@ -1,14 +1,14 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.util.EntityUtils; 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; @@ -18,7 +18,7 @@ import net.minecraft.world.World; public class CurseOfEnfeeblement extends SpellRay { public CurseOfEnfeeblement(){ - super("curse_of_enfeeblement", false, EnumAction.NONE); + super("curse_of_enfeeblement", SpellActions.POINT, false); this.soundValues(1, 1.1f, 0.2f); addProperties(EFFECT_STRENGTH); } @@ -26,7 +26,7 @@ public class CurseOfEnfeeblement extends SpellRay { @Override protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ - if(WizardryUtilities.isLiving(target)){ + if(EntityUtils.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)))); diff --git a/src/main/java/electroblob/wizardry/spell/CurseOfSoulbinding.java b/src/main/java/electroblob/wizardry/spell/CurseOfSoulbinding.java index e47b7865..d9956466 100644 --- a/src/main/java/electroblob/wizardry/spell/CurseOfSoulbinding.java +++ b/src/main/java/electroblob/wizardry/spell/CurseOfSoulbinding.java @@ -4,6 +4,7 @@ import electroblob.wizardry.data.IStoredVariable; import electroblob.wizardry.data.Persistence; import electroblob.wizardry.data.WizardData; import electroblob.wizardry.integration.DamageSafetyChecker; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.*; @@ -12,7 +13,6 @@ 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.nbt.NBTTagList; import net.minecraft.nbt.NBTUtil; import net.minecraft.potion.PotionEffect; @@ -42,7 +42,7 @@ public class CurseOfSoulbinding extends SpellRay { Persistence.DIMENSION_CHANGE); public CurseOfSoulbinding(){ - super("curse_of_soulbinding", false, EnumAction.NONE); + super("curse_of_soulbinding", SpellActions.POINT, false); this.soundValues(1, 1.1f, 0.2f); WizardData.registerStoredVariables(TARGETS_KEY); } @@ -54,7 +54,7 @@ public class CurseOfSoulbinding extends SpellRay { @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){ + if(EntityUtils.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) @@ -101,7 +101,7 @@ public class CurseOfSoulbinding extends SpellRay { for(Iterator iterator = getSoulboundCreatures(data).iterator(); iterator.hasNext();){ - Entity entity = WizardryUtilities.getEntityByUUID(player.world, iterator.next()); + Entity entity = EntityUtils.getEntityByUUID(player.world, iterator.next()); if(entity == null) iterator.remove(); diff --git a/src/main/java/electroblob/wizardry/spell/CurseOfUndeath.java b/src/main/java/electroblob/wizardry/spell/CurseOfUndeath.java index f97e1148..9e0b51d1 100644 --- a/src/main/java/electroblob/wizardry/spell/CurseOfUndeath.java +++ b/src/main/java/electroblob/wizardry/spell/CurseOfUndeath.java @@ -1,13 +1,13 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.util.EntityUtils; 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; @@ -17,7 +17,7 @@ import net.minecraft.world.World; public class CurseOfUndeath extends SpellRay { public CurseOfUndeath(){ - super("curse_of_undeath", false, EnumAction.NONE); + super("curse_of_undeath", SpellActions.POINT, false); this.soundValues(1, 1.1f, 0.2f); addProperties(EFFECT_STRENGTH); } @@ -25,7 +25,7 @@ public class CurseOfUndeath extends SpellRay { @Override protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ - if(WizardryUtilities.isLiving(target)){ + if(EntityUtils.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, diff --git a/src/main/java/electroblob/wizardry/spell/Decay.java b/src/main/java/electroblob/wizardry/spell/Decay.java index afca662c..be660d3b 100644 --- a/src/main/java/electroblob/wizardry/spell/Decay.java +++ b/src/main/java/electroblob/wizardry/spell/Decay.java @@ -2,8 +2,8 @@ package electroblob.wizardry.spell; import electroblob.wizardry.entity.construct.EntityDecay; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.BlockUtils; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.EntityLivingBase; import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; @@ -37,7 +37,7 @@ public class Decay extends SpellConstructRanged { int verticalRange = (int)(6 * modifiers.get(WizardryItems.blast_upgrade)); for(int i=0; i targets = WizardryUtilities.getEntitiesWithinRadius(getProperty(BLAST_RADIUS).doubleValue() + List targets = EntityUtils.getLivingWithinRadius(getProperty(BLAST_RADIUS).doubleValue() * modifiers.get(WizardryItems.blast_upgrade), pos.getX(), pos.getY(), pos.getZ(), world); for(EntityLivingBase target : targets){ diff --git a/src/main/java/electroblob/wizardry/spell/Disintegration.java b/src/main/java/electroblob/wizardry/spell/Disintegration.java index 45d89781..169f0bcc 100644 --- a/src/main/java/electroblob/wizardry/spell/Disintegration.java +++ b/src/main/java/electroblob/wizardry/spell/Disintegration.java @@ -1,17 +1,14 @@ package electroblob.wizardry.spell; import electroblob.wizardry.entity.projectile.EntityEmber; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.MagicDamage; +import electroblob.wizardry.util.*; 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; @@ -25,8 +22,10 @@ public class Disintegration extends SpellRay { public static final String EMBER_COUNT = "ember_count"; public static final String EMBER_LIFETIME = "ember_lifetime"; + public static final String NBT_KEY = "disintegrating"; + public Disintegration(){ - super("disintegration", false, EnumAction.NONE); + super("disintegration", SpellActions.POINT, false); addProperties(DAMAGE, BURN_DURATION, EMBER_LIFETIME, EMBER_COUNT); } @@ -39,11 +38,11 @@ public class Disintegration extends SpellRay { }else{ target.setFire((int)(getProperty(BURN_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade))); - WizardryUtilities.attackEntityWithoutKnockback(target, caster == null ? DamageSource.MAGIC : + EntityUtils.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){ + if(target instanceof EntityLivingBase && ((EntityLivingBase)target).getHealth() <= 0){ spawnEmbers(world, caster, target, getProperty(EMBER_COUNT).intValue()); } } @@ -53,17 +52,28 @@ public class Disintegration extends SpellRay { 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.motionX = x * speed; - ember.motionY = y * 0.5f * speed; - ember.motionZ = z * speed; - world.spawnEntity(ember); + target.extinguish(); + + if(world.isRemote){ // FIXME: Various syncing issues here! + // Set NBT client-side, it's only for rendering + // Normally dying entities are ignored but we're fiddling with them here so double-check + if(!target.getEntityData().hasKey(NBT_KEY)){ + target.getEntityData().setInteger(NBT_KEY, target.ticksExisted); + } + }else{ + 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); + ember.ticksExisted = world.rand.nextInt(20); + float speed = 0.2f; + ember.motionX = x * speed; + ember.motionY = y * 0.5f * speed; + ember.motionZ = z * speed; + world.spawnEntity(ember); + } } } @@ -77,7 +87,7 @@ public class Disintegration extends SpellRay { } if(world.getBlockState(pos).getMaterial().isSolid()){ - Vec3d vec = hit.add(new Vec3d(side.getDirectionVec()).scale(WizardryUtilities.ANTI_Z_FIGHTING_OFFSET)); + Vec3d vec = hit.add(new Vec3d(side.getDirectionVec()).scale(GeometryUtils.ANTI_Z_FIGHTING_OFFSET)); ParticleBuilder.create(Type.SCORCH).pos(vec).face(side).clr(1, 0.2f, 0).spawn(world); } } diff --git a/src/main/java/electroblob/wizardry/spell/Divination.java b/src/main/java/electroblob/wizardry/spell/Divination.java index 79c69440..55a02d89 100644 --- a/src/main/java/electroblob/wizardry/spell/Divination.java +++ b/src/main/java/electroblob/wizardry/spell/Divination.java @@ -3,17 +3,17 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Settings; import electroblob.wizardry.Wizardry; import electroblob.wizardry.block.BlockCrystalOre; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.BlockUtils; 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; @@ -22,7 +22,6 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.World; -import org.apache.commons.lang3.tuple.Pair; import java.util.Arrays; import java.util.Comparator; @@ -33,7 +32,7 @@ public class Divination extends Spell { private static final float NUDGE_SPEED = 0.2f; public Divination(){ - super("divination", EnumAction.NONE, false); + super("divination", SpellActions.THRUST, false); addProperties(RANGE); } @@ -65,7 +64,7 @@ public class Divination extends Spell { double range = getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade); - List sphere = WizardryUtilities.getBlockSphere(caster.getPosition(), range); + List sphere = BlockUtils.getBlockSphere(caster.getPosition(), range); sphere.removeIf(b -> { Block block = world.getBlockState(b).getBlock(); @@ -88,7 +87,7 @@ public class Divination extends Spell { 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.getY() + 0.5 - (caster.posY + caster.getEyeHeight())), (float)(target.getZ() + 0.5 - caster.posZ)); strength = Strength.forWeight(calculateWeight(world, caster, target, range, modifiers)); diff --git a/src/main/java/electroblob/wizardry/spell/DragonFireball.java b/src/main/java/electroblob/wizardry/spell/DragonFireball.java index c1323866..8732f205 100644 --- a/src/main/java/electroblob/wizardry/spell/DragonFireball.java +++ b/src/main/java/electroblob/wizardry/spell/DragonFireball.java @@ -61,7 +61,7 @@ public class DragonFireball extends Spell { EntityDragonFireball fireball = new EntityDragonFireball(world, caster, 1, 1, 1); double dx = target.posX - caster.posX; - double dy = target.getEntityBoundingBox().minY + (double)(target.height / 2.0F) + double dy = target.posY + (double)(target.height / 2.0F) - (caster.posY + (double)(caster.height / 2.0F)); double dz = target.posZ - caster.posZ; diff --git a/src/main/java/electroblob/wizardry/spell/Earthquake.java b/src/main/java/electroblob/wizardry/spell/Earthquake.java index 2c9ae2b8..c4164392 100644 --- a/src/main/java/electroblob/wizardry/spell/Earthquake.java +++ b/src/main/java/electroblob/wizardry/spell/Earthquake.java @@ -2,14 +2,15 @@ import electroblob.wizardry.Wizardry; import electroblob.wizardry.entity.construct.EntityEarthquake; + import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; + import electroblob.wizardry.util.BlockUtils; + import electroblob.wizardry.util.EntityUtils; 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.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; - import net.minecraft.item.EnumAction; import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumParticleTypes; import net.minecraft.world.World; @@ -19,7 +20,7 @@ public static final String SPREAD_SPEED = "spread_speed"; public Earthquake(){ - super("earthquake", EnumAction.NONE, EntityEarthquake::new, true); + super("earthquake", SpellActions.POINT_DOWN, EntityEarthquake::new, true); this.soundValues(2, 1, 0); this.overlap(true); this.floor(true); @@ -51,12 +52,12 @@ particleX = x - 1.0d + 2 * world.rand.nextDouble(); particleZ = z - 1.0d + 2 * world.rand.nextDouble(); - IBlockState block = WizardryUtilities.getBlockEntityIsStandingOn(caster); + IBlockState block = BlockUtils.getBlockEntityIsStandingOn(caster); world.spawnParticle(EnumParticleTypes.BLOCK_DUST, particleX, y, particleZ, particleX - x, 0, particleZ - z, Block.getStateId(block)); } - WizardryUtilities.getEntitiesWithinRadius(15, x, y, z, world, EntityPlayer.class) + EntityUtils.getEntitiesWithinRadius(15, x, y, z, world, EntityPlayer.class) .forEach(p -> Wizardry.proxy.shakeScreen(p, 12)); } diff --git a/src/main/java/electroblob/wizardry/spell/EmpoweringPresence.java b/src/main/java/electroblob/wizardry/spell/EmpoweringPresence.java index 7fcbaf18..6ff9b08e 100644 --- a/src/main/java/electroblob/wizardry/spell/EmpoweringPresence.java +++ b/src/main/java/electroblob/wizardry/spell/EmpoweringPresence.java @@ -2,71 +2,55 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; import electroblob.wizardry.constants.Constants; +import electroblob.wizardry.entity.living.ISpellCaster; import electroblob.wizardry.event.SpellCastEvent; +import electroblob.wizardry.item.SpellActions; 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.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.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; 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; +import javax.annotation.Nullable; @Mod.EventBusSubscriber -public class EmpoweringPresence extends Spell { +public class EmpoweringPresence extends SpellAreaEffect { public EmpoweringPresence(){ - super("empowering_presence", EnumAction.BOW, false); - addProperties(EFFECT_RADIUS, EFFECT_DURATION, EFFECT_STRENGTH); + super("empowering_presence", SpellActions.POINT_UP, false); + this.alwaysSucceed(true); + this.targetAllies(true); + addProperties(EFFECT_DURATION, EFFECT_STRENGTH); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + protected boolean affectEntity(World world, Vec3d origin, @Nullable EntityLivingBase caster, EntityLivingBase target, int targetCount, 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); + if(target instanceof EntityPlayer || target instanceof ISpellCaster){ // Only useful for spell casters - for(EntityPlayer target : targets){ - if(AllyDesignationSystem.isPlayerAlly(caster, target) || target == caster){ + int bonusAmplifier = SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY)); - 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)); - } + 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; } + @Override + protected void spawnParticle(World world, double x, double y, double z){ + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.03, 0).time(50).clr(0.5f, 0.4f, 0.75f).spawn(world); + } + @Override protected String getTranslationKey(){ return Wizardry.tisTheSeason ? super.getTranslationKey() + "_festive" : super.getTranslationKey(); diff --git a/src/main/java/electroblob/wizardry/spell/Enrage.java b/src/main/java/electroblob/wizardry/spell/Enrage.java new file mode 100644 index 00000000..45003b49 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/Enrage.java @@ -0,0 +1,52 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.item.SpellActions; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import electroblob.wizardry.util.SpellModifiers; +import net.minecraft.entity.EntityCreature; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.tileentity.TileEntityDispenser; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import net.minecraftforge.fml.common.Mod; + +import javax.annotation.Nullable; + +@Mod.EventBusSubscriber +public class Enrage extends SpellAreaEffect { + + public Enrage(){ + super("enrage", SpellActions.SUMMON, false); + this.alwaysSucceed(true); + } + + @Override + public boolean canBeCastBy(TileEntityDispenser dispenser){ + return false; + } + + @Override + protected boolean affectEntity(World world, Vec3d origin, @Nullable EntityLivingBase caster, EntityLivingBase target, int targetCount, int ticksInUse, SpellModifiers modifiers){ + + if(caster != null && target instanceof EntityCreature){ + target.setRevengeTarget(caster); // Yours truly, angry mobs + } + + return true; + } + + @Override + protected void spawnParticleEffect(World world, Vec3d origin, double radius, @Nullable EntityLivingBase caster, SpellModifiers modifiers){ + + if(caster != null) origin = caster.getPositionEyes(1); + + for(int i = 0; i < 30; i++){ + double x = origin.x - 1 + world.rand.nextDouble() * 2; + double y = origin.y - 0.25 + world.rand.nextDouble() * 0.5; + double z = origin.z - 1 + world.rand.nextDouble() * 2; + ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).clr(0.9f, 0.1f, 0).spawn(world); + } + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/Entrapment.java b/src/main/java/electroblob/wizardry/spell/Entrapment.java index 7f20fbd8..478d3323 100644 --- a/src/main/java/electroblob/wizardry/spell/Entrapment.java +++ b/src/main/java/electroblob/wizardry/spell/Entrapment.java @@ -1,16 +1,16 @@ package electroblob.wizardry.spell; import electroblob.wizardry.entity.construct.EntityBubble; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.EntityUtils; 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.item.EnumAction; import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.math.BlockPos; @@ -22,7 +22,7 @@ public class Entrapment extends SpellRay { public static final String DAMAGE_INTERVAL = "damage_interval"; public Entrapment(){ - super("entrapment", false, EnumAction.NONE); + super("entrapment", SpellActions.POINT, false); this.soundValues(1, 0.85f, 0.3f); addProperties(EFFECT_DURATION, DAMAGE_INTERVAL); } @@ -30,7 +30,7 @@ public class Entrapment extends SpellRay { @Override protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ - if(WizardryUtilities.isLiving(target)){ + if(EntityUtils.isLiving(target)){ if(!world.isRemote){ // Deals a small amount damage so the target counts as being hit by the caster diff --git a/src/main/java/electroblob/wizardry/spell/Evade.java b/src/main/java/electroblob/wizardry/spell/Evade.java index 9df46093..279d0277 100644 --- a/src/main/java/electroblob/wizardry/spell/Evade.java +++ b/src/main/java/electroblob/wizardry/spell/Evade.java @@ -26,6 +26,8 @@ public class Evade extends Spell { @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + if(!caster.onGround) return false; // Prevents cheesing with cooldown upgrades to effectively fly super-fast + Vec3d look = caster.getLookVec(); // We want a horizontal only vector look = look.subtract(0, look.y, 0).normalize(); diff --git a/src/main/java/electroblob/wizardry/spell/Fangs.java b/src/main/java/electroblob/wizardry/spell/Fangs.java new file mode 100644 index 00000000..1a6390f5 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/Fangs.java @@ -0,0 +1,136 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.item.SpellActions; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.*; +import electroblob.wizardry.util.ParticleBuilder.Type; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.projectile.EntityEvokerFangs; +import net.minecraft.tileentity.TileEntityDispenser; +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.Vec3d; +import net.minecraft.world.World; +import net.minecraftforge.event.entity.living.LivingAttackEvent; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber; +import net.minecraftforge.fml.common.eventhandler.EventPriority; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +import javax.annotation.Nullable; + +@EventBusSubscriber +public class Fangs extends Spell { + + private static final double FANG_SPACING = 1.25; + + public Fangs(){ + super("fangs", SpellActions.SUMMON, false); + addProperties(RANGE); + this.npcSelector((e, o) -> true); + } + + @Override + public boolean canBeCastBy(TileEntityDispenser dispenser){ + return true; + } + + @Override + public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + if(!spawnFangs(world, caster.getPositionVector(), GeometryUtils.horizontalise(caster.getLookVec()), 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(!spawnFangs(world, caster.getPositionVector(), target.getPositionVector().subtract(caster.getPositionVector()).normalize(), 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){ + if(!spawnFangs(world, new Vec3d(x, y, z), new Vec3d(direction.getDirectionVec()), null, modifiers)) return false; + this.playSound(world, x, y, z, ticksInUse, -1, modifiers); + return true; + } + + protected boolean spawnFangs(World world, Vec3d origin, Vec3d direction, @Nullable EntityLivingBase caster, SpellModifiers modifiers){ + + boolean defensiveCircle = caster instanceof EntityPlayer && caster.isSneaking() + && ItemArtefact.isArtefactActive((EntityPlayer)caster, WizardryItems.ring_evoker); + + if(!defensiveCircle && direction.lengthSquared() == 0) return false; // Prevent casting directly down/up + + boolean flag = false; + + if(world.isRemote){ + + double x = origin.x; + double y = caster == null ? origin.y : origin.y + caster.getEyeHeight(); + double z = origin.z; + + for(int i = 0; i < 12; i++){ + ParticleBuilder.create(Type.DARK_MAGIC, world.rand, x, y, z, 0.5, false) + .clr(0.4f, 0.3f, 0.35f).spawn(world); // Colour from EntitySpellcasterIllager + } + + }else{ + + if(defensiveCircle){ + + for(int i = 0; i < 5; i++){ + float yaw = i * (float)Math.PI * 0.4f; + flag |= this.spawnFangsAt(world, caster, modifiers, yaw, 0, origin.add(MathHelper.cos(yaw) * 1.5, 0, MathHelper.sin(yaw) * 1.5)); + } + + for(int k = 0; k < 8; k++){ + float yaw = k * (float)Math.PI * 2f / 8f + ((float)Math.PI * 2f / 5f); + flag |= this.spawnFangsAt(world, caster, modifiers, yaw, 3, origin.add(MathHelper.cos(yaw) * 2.5, 0, MathHelper.sin(yaw) * 2.5)); + } + + }else{ + + int count = (int)(getProperty(RANGE).doubleValue() * modifiers.get(WizardryItems.range_upgrade)); + float yaw = (float)MathHelper.atan2(direction.z, direction.x); // Yes, this is the right way round! + + for(int i = 0; i < count; i++){ + Vec3d vec = origin.add(direction.scale((i + 1) * FANG_SPACING)); + flag |= spawnFangsAt(world, caster, modifiers, yaw, i, vec); + } + } + } + + return flag; + } + + private boolean spawnFangsAt(World world, @Nullable EntityLivingBase caster, SpellModifiers modifiers, float yaw, int delay, Vec3d vec){ + + // Not exactly the same as evokers but it's how constructs work so it kinda fits + Integer y = BlockUtils.getNearestFloor(world, new BlockPos(vec), 5); + + if(y != null){ + EntityEvokerFangs fangs = new EntityEvokerFangs(world, vec.x, y, vec.z, yaw, delay, caster); // null is fine here + fangs.getEntityData().setFloat(SpellThrowable.DAMAGE_MODIFIER_NBT_KEY, modifiers.get(SpellModifiers.POTENCY)); + world.spawnEntity(fangs); + return true; + } + + return false; + } + + @SubscribeEvent(priority = EventPriority.HIGH) + public static void onLivingAttackEvent(LivingAttackEvent event){ + if(event.getSource().getImmediateSource() instanceof EntityEvokerFangs){ + if(!AllyDesignationSystem.isValidTarget(event.getSource().getTrueSource(), event.getEntityLiving())){ + event.setCanceled(true); // Don't attack allies + } + } + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/FireBreath.java b/src/main/java/electroblob/wizardry/spell/FireBreath.java index 338f375d..28301d94 100644 --- a/src/main/java/electroblob/wizardry/spell/FireBreath.java +++ b/src/main/java/electroblob/wizardry/spell/FireBreath.java @@ -1,17 +1,14 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.MagicDamage; +import electroblob.wizardry.util.*; 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; @@ -22,11 +19,12 @@ import net.minecraft.world.World; public class FireBreath extends SpellRay { public FireBreath(){ - super("fire_breath", true, EnumAction.NONE); + super("fire_breath", SpellActions.POINT, true); this.particleVelocity(1); this.particleJitter(0.3); this.particleSpacing(0.25); addProperties(DAMAGE, BURN_DURATION); + this.soundValues(3f, 1, 0); } @Override @@ -57,7 +55,7 @@ public class FireBreath extends SpellRay { // 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, + EntityUtils.attackEntityWithoutKnockback(target, MagicDamage.causeDirectMagicDamage(caster, DamageType.FIRE), getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY)); } @@ -69,12 +67,12 @@ public class FireBreath extends SpellRay { @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; + if(!EntityUtils.canDamageBlocks(caster, world)) return false; pos = pos.offset(side); if(world.isAirBlock(pos)){ - if(!world.isRemote) world.setBlockState(pos, Blocks.FIRE.getDefaultState()); + if(!world.isRemote && BlockUtils.canPlaceBlock(caster, world, pos)) world.setBlockState(pos, Blocks.FIRE.getDefaultState()); return true; } diff --git a/src/main/java/electroblob/wizardry/spell/Firestorm.java b/src/main/java/electroblob/wizardry/spell/Firestorm.java new file mode 100644 index 00000000..3933bceb --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/Firestorm.java @@ -0,0 +1,113 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.client.DrawingUtils; +import electroblob.wizardry.item.SpellActions; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.BlockUtils; +import electroblob.wizardry.util.BlockUtils.SurfaceCriteria; +import electroblob.wizardry.util.EntityUtils; +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.init.Blocks; +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 javax.annotation.Nullable; + +public class Firestorm extends SpellAreaEffect { + + public Firestorm(){ + super("firestorm", SpellActions.POINT_DOWN, false); + this.soundValues(2f, 1.0f, 0); + this.alwaysSucceed(true); + addProperties(BURN_DURATION); + } + + @Override + public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + burnNearbyBlocks(world, caster.getPositionVector(), caster, modifiers); + return super.cast(world, caster, hand, ticksInUse, modifiers); + } + + @Override + public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, SpellModifiers modifiers){ + burnNearbyBlocks(world, caster.getPositionVector(), caster, modifiers); + return super.cast(world, caster, hand, ticksInUse, target, modifiers); + } + + @Override + public boolean cast(World world, double x, double y, double z, EnumFacing direction, int ticksInUse, int duration, SpellModifiers modifiers){ + burnNearbyBlocks(world, new Vec3d(x, y, z), null, modifiers); + return super.cast(world, x, y, z, direction, ticksInUse, duration, modifiers); + } + + @Override + protected boolean affectEntity(World world, Vec3d origin, @Nullable EntityLivingBase caster, EntityLivingBase target, int targetCount, int ticksInUse, SpellModifiers modifiers){ + target.setFire(getProperty(BURN_DURATION).intValue()); + return true; + } + + @Override + protected void spawnParticleEffect(World world, Vec3d origin, double radius, @Nullable EntityLivingBase caster, SpellModifiers modifiers){ + + for(int i=0; i<100; i++){ + float r = world.rand.nextFloat(); + double speed = 0.02/r * (1 + world.rand.nextDouble());//(world.rand.nextBoolean() ? 1 : -1) * (0.05 + 0.02 * world.rand.nextDouble()); + ParticleBuilder.create(Type.MAGIC_FIRE) + .pos(origin.x, origin.y + world.rand.nextDouble() * 3, origin.z) + .vel(0, 0, 0) + .scale(2) + .time(40 + world.rand.nextInt(10)) + .spin(world.rand.nextDouble() * (radius - 0.5) + 0.5, speed) + .spawn(world); + } + + for(int i=0; i<60; i++){ + float r = world.rand.nextFloat(); + double speed = 0.02/r * (1 + world.rand.nextDouble());//(world.rand.nextBoolean() ? 1 : -1) * (0.05 + 0.02 * world.rand.nextDouble()); + ParticleBuilder.create(Type.CLOUD) + .pos(origin.x, origin.y + world.rand.nextDouble() * 2.5, origin.z) + .clr(DrawingUtils.mix(DrawingUtils.mix(0xffbe00, 0xff3600, r/0.6f), 0x222222, (r - 0.6f)/0.4f)) + .spin(r * (radius - 1) + 0.5, speed) + .spawn(world); + } + } + + private void burnNearbyBlocks(World world, Vec3d origin, @Nullable EntityLivingBase caster, SpellModifiers modifiers){ + + if(!world.isRemote && EntityUtils.canDamageBlocks(caster, world)){ + + double radius = getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade); + + for(int i = -(int)radius; i <= (int)radius; i++){ + for(int j = -(int)radius; j <= (int)radius; j++){ + + BlockPos pos = new BlockPos(origin).add(i, 0, j); + + Integer y = BlockUtils.getNearestSurface(world, new BlockPos(pos), EnumFacing.UP, (int)radius, true, SurfaceCriteria.NOT_AIR_TO_AIR); + + if(y != null){ + + pos = new BlockPos(pos.getX(), y, pos.getZ()); + + double dist = origin.distanceTo(new Vec3d(origin.x + i, y, origin.z + j)); + + // Randomised with weighting so that the nearer the block the more likely it is to be set alight. + if(y != -1 && world.rand.nextInt((int)(dist * 2) + 1) < radius && dist < radius && dist > 1.5 + && BlockUtils.canPlaceBlock(caster, world, pos)){ + world.setBlockState(pos, Blocks.FIRE.getDefaultState()); + } + } + } + } + } + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/FlameRay.java b/src/main/java/electroblob/wizardry/spell/FlameRay.java index b13e2e45..3a2acaf4 100644 --- a/src/main/java/electroblob/wizardry/spell/FlameRay.java +++ b/src/main/java/electroblob/wizardry/spell/FlameRay.java @@ -1,16 +1,16 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.EntityUtils; 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.EnumFacing; import net.minecraft.util.SoundEvent; import net.minecraft.util.math.BlockPos; @@ -21,11 +21,11 @@ import net.minecraft.world.World; public class FlameRay extends SpellRay { public FlameRay(){ - super("flame_ray", true, EnumAction.NONE); + super("flame_ray", SpellActions.POINT, true); this.particleVelocity(1); this.particleSpacing(0.5); addProperties(DAMAGE, BURN_DURATION); - this.soundValues(1.5f, 1, 0); + this.soundValues(2.5f, 1, 0); } // The following three methods serve as a good example of how to implement continuous spell sounds (hint: it's easy) @@ -58,7 +58,7 @@ public class FlameRay extends SpellRay { // 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, + EntityUtils.attackEntityWithoutKnockback(target, MagicDamage.causeDirectMagicDamage(caster, DamageType.FIRE), getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY)); } diff --git a/src/main/java/electroblob/wizardry/spell/Flamecatcher.java b/src/main/java/electroblob/wizardry/spell/Flamecatcher.java new file mode 100644 index 00000000..fb2035fe --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/Flamecatcher.java @@ -0,0 +1,43 @@ +package electroblob.wizardry.spell; + +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.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.EnumParticleTypes; +import net.minecraft.world.World; + +public class Flamecatcher extends SpellConjuration { + + public static final String SHOT_COUNT = "shot_count"; + public static final String SHOTS_REMAINING_NBT_KEY = "shotsRemaining"; + + public Flamecatcher(){ + super("flamecatcher", WizardryItems.flamecatcher); + addProperties(RANGE, SHOT_COUNT, DAMAGE, BURN_DURATION); + } + + @Override + protected void addItemExtras(EntityPlayer caster, ItemStack stack, SpellModifiers modifiers){ + if(stack.getTagCompound() == null) stack.setTagCompound(new NBTTagCompound()); + stack.getTagCompound().setInteger(SHOTS_REMAINING_NBT_KEY, (int)(getProperty(SHOT_COUNT).intValue() * modifiers.get(SpellModifiers.POTENCY))); + } + + @Override + protected void spawnParticles(World world, EntityLivingBase caster, SpellModifiers modifiers){ + + ParticleBuilder.create(Type.BUFF).entity(caster).clr(0xff6d00).spawn(world); + + for(int i=0; i<10; i++){ + double x = caster.posX + world.rand.nextDouble() * 2 - 1; + double y = caster.posY + 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); + } + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/FlamingAxe.java b/src/main/java/electroblob/wizardry/spell/FlamingAxe.java index 1f87a883..732c86d5 100644 --- a/src/main/java/electroblob/wizardry/spell/FlamingAxe.java +++ b/src/main/java/electroblob/wizardry/spell/FlamingAxe.java @@ -18,7 +18,7 @@ public class FlamingAxe extends SpellConjuration { 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 y = caster.posY + 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); } diff --git a/src/main/java/electroblob/wizardry/spell/FlamingWeapon.java b/src/main/java/electroblob/wizardry/spell/FlamingWeapon.java index b77f4e9a..03c52e6a 100644 --- a/src/main/java/electroblob/wizardry/spell/FlamingWeapon.java +++ b/src/main/java/electroblob/wizardry/spell/FlamingWeapon.java @@ -2,15 +2,15 @@ package electroblob.wizardry.spell; import electroblob.wizardry.constants.Constants; import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryEnchantments; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.InventoryUtils; 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.EnchantmentHelper; 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; @@ -18,7 +18,7 @@ import net.minecraft.world.World; public class FlamingWeapon extends Spell { public FlamingWeapon(){ - super("flaming_weapon", EnumAction.BOW, false); + super("flaming_weapon", SpellActions.IMBUE, false); addProperties(EFFECT_DURATION); } @@ -29,7 +29,7 @@ public class FlamingWeapon extends Spell { if(WizardData.get(caster) != null && WizardData.get(caster).getImbuementDuration(WizardryEnchantments.flaming_weapon) <= 0){ - for(ItemStack stack : WizardryUtilities.getPrioritisedHotbarAndOffhand(caster)){ + for(ItemStack stack : InventoryUtils.getPrioritisedHotbarAndOffhand(caster)){ if((ImbueWeapon.isSword(stack) || ImbueWeapon.isBow(stack)) && !EnchantmentHelper.getEnchantments(stack).containsKey(WizardryEnchantments.flaming_weapon)){ @@ -46,7 +46,7 @@ public class FlamingWeapon extends Spell { if(world.isRemote){ 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 y = caster.posY + 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); } diff --git a/src/main/java/electroblob/wizardry/spell/Flight.java b/src/main/java/electroblob/wizardry/spell/Flight.java index 9e2d0d8a..4826ca53 100644 --- a/src/main/java/electroblob/wizardry/spell/Flight.java +++ b/src/main/java/electroblob/wizardry/spell/Flight.java @@ -1,11 +1,11 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.util.ParticleBuilder; import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; import net.minecraft.util.EnumHand; import net.minecraft.world.World; @@ -17,7 +17,7 @@ public class Flight extends Spell { private static final double Y_NUDGE_ACCELERATION = 0.075; public Flight(){ - super("flight", EnumAction.NONE, true); + super("flight", SpellActions.POINT, true); addProperties(SPEED, ACCELERATION); } @@ -46,11 +46,11 @@ public class Flight extends Spell { if(world.isRemote){ double x = caster.posX - 1 + world.rand.nextDouble() * 2; - double y = caster.getEntityBoundingBox().minY + caster.getEyeHeight() - 0.5 + world.rand.nextDouble(); + double y = caster.posY + 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(); + y = caster.posY + 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); } diff --git a/src/main/java/electroblob/wizardry/spell/FontOfMana.java b/src/main/java/electroblob/wizardry/spell/FontOfMana.java index 4dc20f74..04c53db7 100644 --- a/src/main/java/electroblob/wizardry/spell/FontOfMana.java +++ b/src/main/java/electroblob/wizardry/spell/FontOfMana.java @@ -1,71 +1,52 @@ package electroblob.wizardry.spell; import electroblob.wizardry.event.SpellCastEvent; +import electroblob.wizardry.item.SpellActions; 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.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.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; import net.minecraftforge.fml.common.eventhandler.EventPriority; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -import java.util.List; +import javax.annotation.Nullable; -public class FontOfMana extends Spell { +public class FontOfMana extends SpellAreaEffect { public FontOfMana(){ - super("font_of_mana", EnumAction.BOW, false); + super("font_of_mana", SpellActions.POINT_UP, false); this.soundValues(0.7f, 1.2f, 0.4f); - addProperties(EFFECT_RADIUS, EFFECT_DURATION, EFFECT_STRENGTH); + this.particleDensity(1.25f); + this.targetAllies(true); + this.alwaysSucceed(true); + addProperties(EFFECT_DURATION, EFFECT_STRENGTH); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + protected boolean affectEntity(World world, Vec3d origin, @Nullable EntityLivingBase caster, EntityLivingBase target, int targetCount, int ticksInUse, SpellModifiers modifiers){ - double maxRadius = getProperty(EFFECT_RADIUS).doubleValue(); - - List targets = WizardryUtilities.getEntitiesWithinRadius( - maxRadius * 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){ - target.addPotionEffect(new PotionEffect(WizardryPotions.font_of_mana, - (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)), - (int)(getProperty(EFFECT_STRENGTH).intValue() + (modifiers.get(SpellModifiers.POTENCY) - 1) * 2))); - } + if(target instanceof EntityPlayer){ // Font of mana is only useful to players + target.addPotionEffect(new PotionEffect(WizardryPotions.font_of_mana, + (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() * (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); - } - } - - playSound(world, caster, ticksInUse, -1, modifiers); - return true; } + @Override + protected void spawnParticle(World world, double x, double y, double z){ + float hue = world.rand.nextFloat() * 0.4f; + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.03, 0).time(50) + .clr(1, 1 - hue, 0.6f + hue).spawn(world); + } + @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!) diff --git a/src/main/java/electroblob/wizardry/spell/Forcefield.java b/src/main/java/electroblob/wizardry/spell/Forcefield.java index bfc298b2..f3413c30 100644 --- a/src/main/java/electroblob/wizardry/spell/Forcefield.java +++ b/src/main/java/electroblob/wizardry/spell/Forcefield.java @@ -1,16 +1,16 @@ package electroblob.wizardry.spell; import electroblob.wizardry.entity.construct.EntityForcefield; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.util.SpellModifiers; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.item.EnumAction; import net.minecraft.util.EnumFacing; public class Forcefield extends SpellConstruct { public Forcefield(){ - super("forcefield", EnumAction.BOW, EntityForcefield::new, false); + super("forcefield", SpellActions.THRUST, EntityForcefield::new, false); addProperties(Spell.EFFECT_RADIUS); } diff --git a/src/main/java/electroblob/wizardry/spell/ForestOfThorns.java b/src/main/java/electroblob/wizardry/spell/ForestOfThorns.java index 9a8a6e49..6826fd55 100644 --- a/src/main/java/electroblob/wizardry/spell/ForestOfThorns.java +++ b/src/main/java/electroblob/wizardry/spell/ForestOfThorns.java @@ -1,15 +1,15 @@ package electroblob.wizardry.spell; import electroblob.wizardry.block.BlockThorns; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.tileentity.TileEntityPlayerSaveTimed; +import electroblob.wizardry.tileentity.TileEntityThorns; +import electroblob.wizardry.util.BlockUtils; 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.tileentity.TileEntityDispenser; import net.minecraft.util.EnumFacing; @@ -25,7 +25,7 @@ import java.util.List; public class ForestOfThorns extends Spell { public ForestOfThorns(){ - super("forest_of_thorns", EnumAction.BOW, false); + super("forest_of_thorns", SpellActions.SUMMON, false); addProperties(EFFECT_RADIUS, DURATION, DAMAGE); } @@ -70,7 +70,7 @@ public class ForestOfThorns extends Spell { 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); + Integer y = BlockUtils.getNearestSurface(world, origin.add(x, 0, z), EnumFacing.UP, (int)radius, true, BlockUtils.SurfaceCriteria.BUILDABLE); if(y != null) ring.add(new BlockPos(origin.getX() + x, y, origin.getZ() + z)); } } @@ -80,16 +80,21 @@ public class ForestOfThorns extends Spell { // 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); + if(BlockUtils.canBlockBeReplaced(world, pos) && BlockUtils.canBlockBeReplaced(world, pos.up())){ - for(int i=0; i<2; i++){ + ((BlockThorns)WizardryBlocks.thorns).placeAt(world, pos, 3); - TileEntity tileentity = world.getTileEntity(pos.up(i)); + TileEntity tileentity = world.getTileEntity(pos); - if(tileentity instanceof TileEntityPlayerSaveTimed){ - ((TileEntityPlayerSaveTimed)tileentity).setLifetime((int)(getProperty(DURATION).floatValue() + if(tileentity instanceof TileEntityThorns){ + + ((TileEntityThorns)tileentity).setLifetime((int)(getProperty(DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade))); - if(caster != null) ((TileEntityPlayerSaveTimed)tileentity).setCaster(caster); + + if(caster != null) ((TileEntityThorns)tileentity).setCaster(caster); + ((TileEntityThorns)tileentity).damageMultiplier = modifiers.get(SpellModifiers.POTENCY); + + ((TileEntityThorns)tileentity).sync(); } } } diff --git a/src/main/java/electroblob/wizardry/spell/ForestsCurse.java b/src/main/java/electroblob/wizardry/spell/ForestsCurse.java index edb3f8d4..e83931ef 100644 --- a/src/main/java/electroblob/wizardry/spell/ForestsCurse.java +++ b/src/main/java/electroblob/wizardry/spell/ForestsCurse.java @@ -1,33 +1,38 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.EntityUtils; 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.EntityLivingBase; import net.minecraft.init.MobEffects; -import net.minecraft.item.EnumAction; import net.minecraft.potion.PotionEffect; +import net.minecraft.util.DamageSource; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; +import javax.annotation.Nullable; + public class ForestsCurse extends SpellAreaEffect { public ForestsCurse(){ - super("forests_curse", EnumAction.BOW); + super("forests_curse", SpellActions.POINT_UP, false); + this.alwaysSucceed(true); this.soundValues(1, 1.1f, 0.2f); addProperties(DAMAGE, EFFECT_DURATION, EFFECT_STRENGTH); } @Override - protected void affectEntity(World world, EntityLivingBase caster, EntityLivingBase target, SpellModifiers modifiers){ + protected boolean affectEntity(World world, Vec3d origin, @Nullable EntityLivingBase caster, EntityLivingBase target, int targetCount, int ticksInUse, 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)); + if(!MagicDamage.isEntityImmune(DamageType.POISON, target) && EntityUtils.isLiving(target)){ + + DamageSource source = caster != null ? MagicDamage.causeDirectMagicDamage(caster, DamageType.POISON) : DamageSource.MAGIC; + target.attackEntityFrom(source, getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY)); int bonusAmplifier = SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY)); int duration = (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)); @@ -37,6 +42,8 @@ public class ForestsCurse extends SpellAreaEffect { target.addPotionEffect(new PotionEffect(MobEffects.SLOWNESS, duration, amplifier)); target.addPotionEffect(new PotionEffect(MobEffects.WEAKNESS, duration, amplifier)); } + + return true; } @Override diff --git a/src/main/java/electroblob/wizardry/spell/Freeze.java b/src/main/java/electroblob/wizardry/spell/Freeze.java index d0d59939..af9154fd 100644 --- a/src/main/java/electroblob/wizardry/spell/Freeze.java +++ b/src/main/java/electroblob/wizardry/spell/Freeze.java @@ -1,20 +1,16 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryPotions; -import electroblob.wizardry.util.MagicDamage; +import electroblob.wizardry.util.*; 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.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.potion.PotionEffect; import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; @@ -25,16 +21,17 @@ import net.minecraft.world.World; public class Freeze extends SpellRay { public Freeze(){ - super("freeze", false, EnumAction.NONE); + super("freeze", SpellActions.POINT, false); this.soundValues(1, 1.4f, 0.4f); addProperties(DAMAGE, EFFECT_DURATION, EFFECT_STRENGTH); this.hitLiquids(true); + this.ignoreUncollidables(false); } @Override protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ - if(WizardryUtilities.isLiving(target)){ + if(EntityUtils.isLiving(target)){ if(target instanceof EntityBlaze || target instanceof EntityMagmaCube){ target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.FROST), @@ -61,18 +58,8 @@ public class Freeze extends SpellRay { @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)){ - - 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 && BlockUtils.canPlaceBlock(caster, world, pos)){ + BlockUtils.freeze(world, pos, true); } return true; // Always succeeds if it hits a block diff --git a/src/main/java/electroblob/wizardry/spell/FreezingWeapon.java b/src/main/java/electroblob/wizardry/spell/FreezingWeapon.java index 46cc6111..c22cc6a2 100644 --- a/src/main/java/electroblob/wizardry/spell/FreezingWeapon.java +++ b/src/main/java/electroblob/wizardry/spell/FreezingWeapon.java @@ -2,15 +2,15 @@ package electroblob.wizardry.spell; import electroblob.wizardry.constants.Constants; import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryEnchantments; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.InventoryUtils; 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.EnchantmentHelper; 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; @@ -24,7 +24,7 @@ public class FreezingWeapon extends Spell { public static final String FREEZING_ARROW_NBT_KEY = "frostLevel"; public FreezingWeapon(){ - super("freezing_weapon", EnumAction.BOW, false); + super("freezing_weapon", SpellActions.IMBUE, false); addProperties(EFFECT_DURATION); } @@ -35,7 +35,7 @@ public class FreezingWeapon extends Spell { if(WizardData.get(caster) != null && WizardData.get(caster).getImbuementDuration(WizardryEnchantments.freezing_weapon) <= 0){ - for(ItemStack stack : WizardryUtilities.getPrioritisedHotbarAndOffhand(caster)){ + for(ItemStack stack : InventoryUtils.getPrioritisedHotbarAndOffhand(caster)){ if((ImbueWeapon.isSword(stack) || ImbueWeapon.isBow(stack)) && !EnchantmentHelper.getEnchantments(stack).containsKey(WizardryEnchantments.freezing_weapon)){ @@ -52,7 +52,7 @@ public class FreezingWeapon extends Spell { if(world.isRemote){ 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 y = caster.posY + 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); } diff --git a/src/main/java/electroblob/wizardry/spell/FrostAxe.java b/src/main/java/electroblob/wizardry/spell/FrostAxe.java index 054b77f7..ee3d6fb0 100644 --- a/src/main/java/electroblob/wizardry/spell/FrostAxe.java +++ b/src/main/java/electroblob/wizardry/spell/FrostAxe.java @@ -19,7 +19,7 @@ public class FrostAxe extends SpellConjuration { 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 y = caster.posY + 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); } diff --git a/src/main/java/electroblob/wizardry/spell/FrostBarrier.java b/src/main/java/electroblob/wizardry/spell/FrostBarrier.java new file mode 100644 index 00000000..42980f2f --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/FrostBarrier.java @@ -0,0 +1,135 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.constants.Constants; +import electroblob.wizardry.entity.construct.EntityIceBarrier; +import electroblob.wizardry.item.SpellActions; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.BlockUtils; +import electroblob.wizardry.util.GeometryUtils; +import electroblob.wizardry.util.SpellModifiers; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.tileentity.TileEntityDispenser; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumFacing.Axis; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + +public class FrostBarrier extends Spell { + + private static final double BARRIER_DISTANCE = 2; + private static final double BARRIER_ARC_RADIUS = 10; + private static final double BARRIER_SPACING = 1.4; + + public FrostBarrier(){ + super("frost_barrier", SpellActions.SUMMON, false); + this.npcSelector((e, o) -> true); + addProperties(DURATION); + } + + @Override + public boolean canBeCastBy(TileEntityDispenser dispenser){ + return true; + } + + @Override + public boolean requiresPacket(){ + return false; + } + + @Override + public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + + if(caster.onGround){ + if(!createBarriers(world, caster.getPositionVector(), caster.getLookVec(), 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(caster.onGround){ + if(!createBarriers(world, caster.getPositionVector(), target.getPositionVector().subtract(caster.getPositionVector()), + 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){ + if(!createBarriers(world, new Vec3d(x, y, z), new Vec3d(direction.getDirectionVec()), 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; + } + + private boolean createBarriers(World world, Vec3d origin, Vec3d direction, @Nullable EntityLivingBase caster, SpellModifiers modifiers){ + + if(!world.isRemote){ + + direction = GeometryUtils.horizontalise(direction); + Vec3d centre = origin.add(direction.scale(BARRIER_DISTANCE - BARRIER_ARC_RADIUS)); // Arc centred behind caster + + // Don't spawn them yet or the anti-overlap will prevent the rest from spawning + List barriers = new ArrayList<>(); + + int barrierCount = 1 + Math.max(1, (int)((modifiers.get(SpellModifiers.POTENCY) - 1) / Constants.POTENCY_INCREASE_PER_TIER + 0.5f)); + + for(int i = 0; i < barrierCount; i++){ + + EntityIceBarrier barrier = createBarrier(world, centre, direction.rotateYaw((float)(BARRIER_SPACING / BARRIER_ARC_RADIUS) * i), caster, modifiers, barrierCount, i); + if(barrier != null) barriers.add(barrier); + + if(i == 0) continue; // Only one in the middle + barrier = createBarrier(world, centre, direction.rotateYaw(-(float)(BARRIER_SPACING / BARRIER_ARC_RADIUS) * i), caster, modifiers, barrierCount, i); + if(barrier != null) barriers.add(barrier); + } + + if(barriers.isEmpty()) return false; + + barriers.forEach(world::spawnEntity); // Finally spawn them all + } + + return true; + } + + private EntityIceBarrier createBarrier(World world, Vec3d centre, Vec3d direction, @Nullable EntityLivingBase caster, SpellModifiers modifiers, int barrierCount, int index){ + + Vec3d position = centre.add(direction.scale(BARRIER_ARC_RADIUS)); + Integer floor = BlockUtils.getNearestFloor(world, new BlockPos(position), 3); + if(floor == null) return null; + position = GeometryUtils.replaceComponent(position, Axis.Y, floor); + + float scale = 1.5f - (float)index/barrierCount * 0.5f; + double yOffset = 1.5 * scale; + + EntityIceBarrier barrier = new EntityIceBarrier(world); + barrier.setPosition(position.x, position.y - yOffset, position.z); + barrier.setCaster(caster); + barrier.lifetime = (int)(getProperty(DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)); + barrier.damageMultiplier = modifiers.get(SpellModifiers.POTENCY); + barrier.setRotation((float)Math.toDegrees(MathHelper.atan2(-direction.x, direction.z)), barrier.rotationPitch); + barrier.setSizeMultiplier(scale); + barrier.setDelay(1 + 3 * index); // Delay 0 seems to move it down 1 block, no idea why + + if(!world.getEntitiesWithinAABB(barrier.getClass(), barrier.getEntityBoundingBox().offset(0, yOffset, 0)).isEmpty()) return null; + + return barrier; + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/FrostRay.java b/src/main/java/electroblob/wizardry/spell/FrostRay.java index 7f4dbcaa..a03fcda4 100644 --- a/src/main/java/electroblob/wizardry/spell/FrostRay.java +++ b/src/main/java/electroblob/wizardry/spell/FrostRay.java @@ -1,19 +1,19 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.util.EntityUtils; 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.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.EnumFacing; import net.minecraft.util.SoundEvent; @@ -25,7 +25,7 @@ import net.minecraft.world.World; public class FrostRay extends SpellRay { public FrostRay(){ - super("frost_ray", true, EnumAction.NONE); + super("frost_ray", SpellActions.POINT, true); this.particleVelocity(1); this.particleSpacing(0.5); addProperties(DAMAGE, EFFECT_DURATION, EFFECT_STRENGTH); @@ -49,7 +49,7 @@ public class FrostRay extends SpellRay { @Override protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ - if(WizardryUtilities.isLiving(target)){ + if(EntityUtils.isLiving(target)){ if(target.isBurning()) target.extinguish(); @@ -68,7 +68,7 @@ public class FrostRay extends SpellRay { float damage = getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY); if(target instanceof EntityBlaze || target instanceof EntityMagmaCube) damage *= 2; - WizardryUtilities.attackEntityWithoutKnockback(target, MagicDamage.causeDirectMagicDamage(caster, + EntityUtils.attackEntityWithoutKnockback(target, MagicDamage.causeDirectMagicDamage(caster, DamageType.FROST), damage); } diff --git a/src/main/java/electroblob/wizardry/spell/Glide.java b/src/main/java/electroblob/wizardry/spell/Glide.java index 8239f676..17d4ff62 100644 --- a/src/main/java/electroblob/wizardry/spell/Glide.java +++ b/src/main/java/electroblob/wizardry/spell/Glide.java @@ -1,13 +1,13 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.ParticleBuilder; import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; 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; @@ -18,7 +18,7 @@ public class Glide extends Spell { public static final String ACCELERATION = "acceleration"; public Glide(){ - super("glide", EnumAction.NONE, true); + super("glide", SpellActions.POINT_DOWN, true); addProperties(SPEED, FALL_SPEED, ACCELERATION); } @@ -57,11 +57,11 @@ public class Glide extends Spell { if(world.isRemote){ double x = caster.posX - 0.25 + world.rand.nextDouble() / 2; - double y = caster.getEntityBoundingBox().minY + world.rand.nextDouble(); + double y = caster.posY + 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(); + y = caster.posY + world.rand.nextDouble(); z = caster.posZ - 0.25 + world.rand.nextDouble() / 2; ParticleBuilder.create(Type.LEAF).pos(x, y, z).time(20).spawn(world); } diff --git a/src/main/java/electroblob/wizardry/spell/Grapple.java b/src/main/java/electroblob/wizardry/spell/Grapple.java index 22b0df43..38c7f61d 100644 --- a/src/main/java/electroblob/wizardry/spell/Grapple.java +++ b/src/main/java/electroblob/wizardry/spell/Grapple.java @@ -5,19 +5,16 @@ import electroblob.wizardry.data.IVariable; import electroblob.wizardry.data.Persistence; import electroblob.wizardry.data.WizardData; import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.*; 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.tileentity.TileEntityDispenser; import net.minecraft.util.EnumFacing; @@ -54,7 +51,7 @@ public class Grapple extends Spell { protected static final double PARTICLE_JITTER = 0.04; public Grapple(){ - super("grapple", EnumAction.NONE, true); + super("grapple", SpellActions.GRAPPLE, true); addProperties(RANGE, EXTENSION_SPEED, REEL_SPEED); } @@ -78,7 +75,7 @@ public class Grapple extends Spell { WizardData data = WizardData.get(caster); - Vec3d origin = new Vec3d(caster.posX, caster.getEntityBoundingBox().minY + caster.getEyeHeight(), caster.posZ); + Vec3d origin = caster.getPositionEyes(1); float extensionSpeed = getProperty(EXTENSION_SPEED).floatValue() * modifiers.get(SpellModifiers.POTENCY); @@ -101,7 +98,7 @@ public class Grapple extends Spell { 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); + target = GeometryUtils.getCentre(hit.entityHit); } double distance = origin.distanceTo(target); @@ -212,11 +209,15 @@ public class Grapple extends Spell { if(target == null) return false; - Vec3d origin = new Vec3d(caster.posX, caster.getEntityBoundingBox().minY + caster.getEyeHeight(), caster.posZ); + Vec3d origin = caster.getPositionEyes(1); // 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); + Vec3d targetVec = GeometryUtils.getCentre(target); + + RayTraceResult hit = findTarget(world, caster, origin, targetVec.subtract(origin).normalize(), modifiers); + + if(hit.typeOfHit != RayTraceResult.Type.ENTITY || hit.entityHit != target) return false; // Something was in the way double distance = origin.distanceTo(targetVec); @@ -283,7 +284,7 @@ public class Grapple extends Spell { // 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); + Vec3d target = GeometryUtils.getCentre(entity); double distance = origin.distanceTo(target); Vec3d vec = target.subtract(origin).normalize(); @@ -343,7 +344,7 @@ public class Grapple extends Spell { if(caster != null){ - origin = new Vec3d(caster.posX, caster.getEntityBoundingBox().minY + caster.getEyeHeight(), caster.posZ); + origin = caster.getPositionEyes(1); if(caster instanceof EntityPlayer){ WizardData data = WizardData.get((EntityPlayer)caster); @@ -353,7 +354,7 @@ public class Grapple extends Spell { } }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(entity != null) target = GeometryUtils.getCentre(entity); } if(target != null) direction = target.subtract(origin).normalize(); @@ -412,7 +413,7 @@ public class Grapple extends Spell { private static RayTraceResult update(EntityPlayer player, RayTraceResult grapplingTarget){ - if(grapplingTarget != null && (!WizardryUtilities.isCasting(player, Spells.grapple) + if(grapplingTarget != null && (!EntityUtils.isCasting(player, Spells.grapple) || (grapplingTarget.entityHit != null && !grapplingTarget.entityHit.isEntityAlive()))){ return null; } diff --git a/src/main/java/electroblob/wizardry/spell/GreaterHeal.java b/src/main/java/electroblob/wizardry/spell/GreaterHeal.java index 9d7055fc..af6c8bc4 100644 --- a/src/main/java/electroblob/wizardry/spell/GreaterHeal.java +++ b/src/main/java/electroblob/wizardry/spell/GreaterHeal.java @@ -15,7 +15,7 @@ public class GreaterHeal extends SpellBuff { protected boolean applyEffects(EntityLivingBase caster, SpellModifiers modifiers){ if(caster.getHealth() < caster.getMaxHealth() && caster.getHealth() > 0){ - caster.heal(getProperty(HEALTH).floatValue() * modifiers.get(SpellModifiers.POTENCY)); + Heal.heal(caster, getProperty(HEALTH).floatValue() * modifiers.get(SpellModifiers.POTENCY)); return true; } diff --git a/src/main/java/electroblob/wizardry/spell/GreaterTelekinesis.java b/src/main/java/electroblob/wizardry/spell/GreaterTelekinesis.java index 1bc1e45b..4d269b4f 100644 --- a/src/main/java/electroblob/wizardry/spell/GreaterTelekinesis.java +++ b/src/main/java/electroblob/wizardry/spell/GreaterTelekinesis.java @@ -3,18 +3,19 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; import electroblob.wizardry.entity.EntityLevitatingBlock; import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.BlockUtils; +import electroblob.wizardry.util.EntityUtils; 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.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.tileentity.TileEntityDispenser; import net.minecraft.util.EnumFacing; @@ -35,7 +36,7 @@ public class GreaterTelekinesis extends SpellRay { private static final float UNDERSHOOT = 0.2f; public GreaterTelekinesis(){ - super("greater_telekinesis", true, EnumAction.NONE); + super("greater_telekinesis", SpellActions.POINT, true); this.aimAssist(0.4f); this.particleSpacing(1); this.particleJitter(0.05); @@ -92,7 +93,7 @@ public class GreaterTelekinesis extends SpellRay { }else{ - WizardryUtilities.undoGravity(target); + EntityUtils.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. @@ -132,7 +133,7 @@ public class GreaterTelekinesis extends SpellRay { @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) + if(EntityUtils.canDamageBlocks(caster, world) && !BlockUtils.isBlockUnbreakable(world, pos) && world.getBlockState(pos).getMaterial().isSolid() && (world.getTileEntity(pos) == null || !world.getTileEntity(pos).getTileData().hasUniqueId(ArcaneLock.NBT_KEY))){ diff --git a/src/main/java/electroblob/wizardry/spell/GroupHeal.java b/src/main/java/electroblob/wizardry/spell/GroupHeal.java index cc2677e0..a93e9404 100644 --- a/src/main/java/electroblob/wizardry/spell/GroupHeal.java +++ b/src/main/java/electroblob/wizardry/spell/GroupHeal.java @@ -1,50 +1,41 @@ package electroblob.wizardry.spell; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.AllyDesignationSystem; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.util.ParticleBuilder; 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.Vec3d; import net.minecraft.world.World; -import java.util.List; +import javax.annotation.Nullable; -public class GroupHeal extends Spell { +public class GroupHeal extends SpellAreaEffect { public GroupHeal(){ - super("group_heal", EnumAction.BOW, false); + super("group_heal", SpellActions.POINT_UP, false); this.soundValues(0.7f, 1.2f, 0.4f); - addProperties(EFFECT_RADIUS, HEALTH); + this.targetAllies(true); + addProperties(HEALTH); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + protected boolean affectEntity(World world, Vec3d origin, @Nullable EntityLivingBase caster, EntityLivingBase target, int targetCount, int ticksInUse, SpellModifiers modifiers){ - boolean flag = false; + if(target.getHealth() < target.getMaxHealth() && target.getHealth() > 0){ - List targets = WizardryUtilities.getEntitiesWithinRadius(getProperty(EFFECT_RADIUS).floatValue() - * modifiers.get(WizardryItems.blast_upgrade), caster.posX, caster.posY, caster.posZ, world); + Heal.heal(target, getProperty(HEALTH).floatValue() * modifiers.get(SpellModifiers.POTENCY)); - for(EntityLivingBase target : targets){ - - if(target == caster || AllyDesignationSystem.isAllied(caster, target)){ - - if(target.getHealth() < target.getMaxHealth() && target.getHealth() > 0){ - - target.heal(getProperty(HEALTH).floatValue() * modifiers.get(SpellModifiers.POTENCY)); - - if(world.isRemote) ParticleBuilder.spawnHealParticles(world, target); - playSound(world, target, ticksInUse, -1, modifiers); - flag = true; - } - } + if(world.isRemote) ParticleBuilder.spawnHealParticles(world, target); + playSound(world, target, ticksInUse, -1, modifiers); + return true; } - return flag; + return false; // Only succeeds if something was healed + } + + @Override + protected void spawnParticleEffect(World world, Vec3d origin, double radius, @Nullable EntityLivingBase caster, SpellModifiers modifiers){ + // We're spawning particles above so don't bother with this method } } diff --git a/src/main/java/electroblob/wizardry/spell/GrowthAura.java b/src/main/java/electroblob/wizardry/spell/GrowthAura.java index 929c803f..678ccd44 100644 --- a/src/main/java/electroblob/wizardry/spell/GrowthAura.java +++ b/src/main/java/electroblob/wizardry/spell/GrowthAura.java @@ -1,13 +1,13 @@ package electroblob.wizardry.spell; import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.BlockUtils; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.block.IGrowable; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; import net.minecraft.item.ItemDye; import net.minecraft.util.EnumHand; import net.minecraft.util.math.BlockPos; @@ -18,7 +18,7 @@ import java.util.List; public class GrowthAura extends Spell { public GrowthAura(){ - super("growth_aura", EnumAction.NONE, false); + super("growth_aura", SpellActions.POINT_DOWN, false); addProperties(EFFECT_RADIUS); soundValues(0.7f, 1.2f, 0.2f); } @@ -33,7 +33,7 @@ public class GrowthAura extends Spell { boolean flag = false; - List sphere = WizardryUtilities.getBlockSphere(caster.getPosition(), + List sphere = BlockUtils.getBlockSphere(caster.getPosition(), getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade)); for(BlockPos pos : sphere){ diff --git a/src/main/java/electroblob/wizardry/spell/GuardianBeam.java b/src/main/java/electroblob/wizardry/spell/GuardianBeam.java new file mode 100644 index 00000000..c1b8684e --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/GuardianBeam.java @@ -0,0 +1,79 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.item.SpellActions; +import electroblob.wizardry.util.*; +import electroblob.wizardry.util.MagicDamage.DamageType; +import electroblob.wizardry.util.ParticleBuilder.Type; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.init.MobEffects; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +public class GuardianBeam extends SpellRay { + + public static final String AIR_DEPLETION = "air_depletion"; + + public GuardianBeam(){ + super("guardian_beam", SpellActions.POINT, true); + addProperties(DAMAGE, AIR_DEPLETION); + } + + @Override + protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){ + if(ticksInUse % 50 == 1) super.playSound(world, x, y, z, ticksInUse, duration, modifiers, sounds); + } + + @Override + protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + if(EntityUtils.isLiving(target)){ + + if(ticksInUse % 50 == 1){ + + EntityUtils.attackEntityWithoutKnockback(target, + MagicDamage.causeDirectMagicDamage(caster, DamageType.MAGIC), + getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY)); + + if(!((EntityLivingBase)target).canBreatheUnderwater() && !((EntityLivingBase)target).isPotionActive(MobEffects.WATER_BREATHING)){ + target.setAir(Math.max(-20, target.getAir() - getProperty(AIR_DEPLETION).intValue())); + } + } + + if(world.isRemote){ + + float t = (ticksInUse % 50) / 50f; + float yellowness = t * t; + int r = 64 + (int)(yellowness * 191.0F); + int g = 32 + (int)(yellowness * 191.0F); + int b = 128 - (int)(yellowness * 64.0F); + + if(ticksInUse % 3 == 0) ParticleBuilder.create(Type.GUARDIAN_BEAM).entity(caster) + .pos(caster != null ? origin.subtract(caster.getPositionVector()) : origin).target(target) + .clr(r, g, b).spawn(world); + + Vec3d direction = GeometryUtils.getCentre(target).subtract(origin); + Vec3d pos = origin.add(direction.scale(world.rand.nextFloat())); + ParticleBuilder.create(Type.MAGIC_BUBBLE, world.rand, pos.x, pos.y, pos.z, 0.15, false).spawn(world); + } + } + + 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 false; // Only works on hit + } + + @Override + 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/Hailstorm.java b/src/main/java/electroblob/wizardry/spell/Hailstorm.java index 3e67f964..51f9e81b 100644 --- a/src/main/java/electroblob/wizardry/spell/Hailstorm.java +++ b/src/main/java/electroblob/wizardry/spell/Hailstorm.java @@ -13,6 +13,7 @@ public class Hailstorm extends SpellConstructRanged { public Hailstorm(){ super("hailstorm", EntityHailstorm::new, false); this.floor(true); + addProperties(EFFECT_RADIUS); } @Override diff --git a/src/main/java/electroblob/wizardry/spell/Heal.java b/src/main/java/electroblob/wizardry/spell/Heal.java index 8bc201da..8baf906b 100644 --- a/src/main/java/electroblob/wizardry/spell/Heal.java +++ b/src/main/java/electroblob/wizardry/spell/Heal.java @@ -1,7 +1,10 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.util.SpellModifiers; import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; public class Heal extends SpellBuff { @@ -15,11 +18,31 @@ public class Heal extends SpellBuff { protected boolean applyEffects(EntityLivingBase caster, SpellModifiers modifiers){ if(caster.getHealth() < caster.getMaxHealth() && caster.getHealth() > 0){ - caster.heal(getProperty(HEALTH).floatValue() * modifiers.get(SpellModifiers.POTENCY)); + heal(caster, getProperty(HEALTH).floatValue() * modifiers.get(SpellModifiers.POTENCY)); return true; } return false; } + /** + * Heals the given entity by the given amount, accounting for special behaviour from artefacts. This does not check + * whether the entity is already on full health or not. + * @param entity The entity to heal + * @param health The number of half-hearts to heal + */ + public static void heal(EntityLivingBase entity, float health){ + + float excessHealth = entity.getHealth() + health - entity.getMaxHealth(); + + entity.heal(health); + + // If the player is able to heal, they can't possibly have absorption hearts, so no need to check! + if(excessHealth > 0 && entity instanceof EntityPlayer + && ItemArtefact.isArtefactActive((EntityPlayer)entity, WizardryItems.amulet_absorption)){ + entity.setAbsorptionAmount(excessHealth); + } + + } + } diff --git a/src/main/java/electroblob/wizardry/spell/HealAlly.java b/src/main/java/electroblob/wizardry/spell/HealAlly.java index 2a31ad2e..31e3831b 100644 --- a/src/main/java/electroblob/wizardry/spell/HealAlly.java +++ b/src/main/java/electroblob/wizardry/spell/HealAlly.java @@ -1,12 +1,12 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; +import electroblob.wizardry.util.EntityUtils; import electroblob.wizardry.util.ParticleBuilder; 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.item.EnumAction; import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; @@ -15,7 +15,7 @@ import net.minecraft.world.World; public class HealAlly extends SpellRay { public HealAlly(){ - super("heal_ally", false, EnumAction.NONE); + super("heal_ally", SpellActions.POINT, false); this.soundValues(0.7f, 1.2f, 0.4f); addProperties(HEALTH); } @@ -28,7 +28,7 @@ public class HealAlly extends SpellRay { @Override protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ - if(WizardryUtilities.isLiving(target)){ + if(EntityUtils.isLiving(target)){ EntityLivingBase entity = (EntityLivingBase)target; diff --git a/src/main/java/electroblob/wizardry/spell/IceAge.java b/src/main/java/electroblob/wizardry/spell/IceAge.java index 8c94d203..f10582f1 100644 --- a/src/main/java/electroblob/wizardry/spell/IceAge.java +++ b/src/main/java/electroblob/wizardry/spell/IceAge.java @@ -1,90 +1,129 @@ package electroblob.wizardry.spell; import electroblob.wizardry.block.BlockStatue; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.registry.WizardrySounds; -import electroblob.wizardry.util.AllyDesignationSystem; +import electroblob.wizardry.util.BlockUtils; +import electroblob.wizardry.util.EntityUtils; +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.EntityLiving; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; -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.Vec3d; import net.minecraft.world.World; -import java.util.List; +import javax.annotation.Nullable; -public class IceAge extends Spell { +public class IceAge extends SpellAreaEffect { + + public static final String FREEZE_DURATION = "freeze_duration"; public IceAge(){ - super("ice_age", EnumAction.BOW, false); - this.soundValues(0.7f, 1.0f, 0); - addProperties(EFFECT_RADIUS, EFFECT_DURATION); - } - - @Override - public boolean requiresPacket(){ - return false; + super("ice_age", SpellActions.POINT_DOWN, false); + this.soundValues(1.5f, 1.0f, 0); + this.alwaysSucceed(true); + addProperties(FREEZE_DURATION, EFFECT_DURATION, EFFECT_STRENGTH); } @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + freezeNearbyBlocks(world, caster.getPositionVector(), caster, modifiers); + return super.cast(world, caster, hand, ticksInUse, modifiers); + } - float radius = getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade); + @Override + public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, SpellModifiers modifiers){ + freezeNearbyBlocks(world, caster.getPositionVector(), caster, modifiers); + return super.cast(world, caster, hand, ticksInUse, target, modifiers); + } - List targets = WizardryUtilities.getEntitiesWithinRadius(radius, caster.posX, caster.posY, caster.posZ, world); + @Override + public boolean cast(World world, double x, double y, double z, EnumFacing direction, int ticksInUse, int duration, SpellModifiers modifiers){ + freezeNearbyBlocks(world, new Vec3d(x, y, z), null, modifiers); + return super.cast(world, x, y, z, direction, ticksInUse, duration, modifiers); + } - for(EntityLivingBase target : targets){ - if(AllyDesignationSystem.isValidTarget(caster, target)){ - if(!world.isRemote){ + @Override + protected boolean affectEntity(World world, Vec3d origin, @Nullable EntityLivingBase caster, EntityLivingBase target, int targetCount, int ticksInUse, SpellModifiers modifiers){ - if(target instanceof EntityLiving){ - 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(target instanceof EntityLiving){ + if(((BlockStatue)WizardryBlocks.ice_statue).convertToStatue((EntityLiving)target, + caster, (int)(getProperty(FREEZE_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)))){ + target.playSound(WizardrySounds.MISC_FREEZE, 1.0F, world.rand.nextFloat() * 0.4F + 0.8F); } + }else if(target instanceof EntityPlayer){ + target.addPotionEffect(new PotionEffect(WizardryPotions.frost, + (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)), + getProperty(EFFECT_STRENGTH).intValue())); } - 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); + return true; + } - Integer y = WizardryUtilities.getNearestSurface(world, new BlockPos(pos), EnumFacing.UP, (int)radius, true, WizardryUtilities.SurfaceCriteria.BUILDABLE); + @Override + protected void spawnParticleEffect(World world, Vec3d origin, double radius, @Nullable EntityLivingBase caster, SpellModifiers modifiers){ + + for(int i=0; i<100; i++){ + float r = world.rand.nextFloat(); + double speed = 0.02/r * (1 + world.rand.nextDouble());//(world.rand.nextBoolean() ? 1 : -1) * (0.05 + 0.02 * world.rand.nextDouble()); + ParticleBuilder.create(Type.SNOW) + .pos(origin.x, origin.y + world.rand.nextDouble() * 3, origin.z) + .vel(0, 0, 0) + .scale(2) + .spin(world.rand.nextDouble() * (radius - 0.5) + 0.5, speed) + .shaded(true) + .spawn(world); + } + + for(int i=0; i<60; i++){ + float r = world.rand.nextFloat(); + double speed = 0.02/r * (1 + world.rand.nextDouble());//(world.rand.nextBoolean() ? 1 : -1) * (0.05 + 0.02 * world.rand.nextDouble()); + ParticleBuilder.create(Type.CLOUD) + .pos(origin.x, origin.y + world.rand.nextDouble() * 2.5, origin.z) + .clr(0xffffff) + .spin(world.rand.nextDouble() * (radius - 1) + 0.5, speed) + .shaded(true) + .spawn(world); + } + } + + private void freezeNearbyBlocks(World world, Vec3d origin, @Nullable EntityLivingBase caster, SpellModifiers modifiers){ + + if(!world.isRemote && EntityUtils.canDamageBlocks(caster, world)){ + + double radius = getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade); + + for(int i = -(int)radius; i <= (int)radius; i++){ + for(int j = -(int)radius; j <= (int)radius; j++){ + + BlockPos pos = new BlockPos(origin).add(i, 0, j); + + Integer y = BlockUtils.getNearestSurface(world, new BlockPos(pos), EnumFacing.UP, (int)radius, true, BlockUtils.SurfaceCriteria.SOLID_LIQUID_TO_AIR); if(y != null){ pos = new BlockPos(pos.getX(), y, pos.getZ()); - double dist = caster.getDistance((int)caster.posX + i, y, (int)caster.posZ + j); + double dist = origin.distanceTo(new Vec3d(origin.x + i, y, origin.z + 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()); - } + if(y != -1 && world.rand.nextInt((int)(dist * 2) + 1) < radius && dist < radius + && BlockUtils.canPlaceBlock(caster, world, pos)){ + BlockUtils.freeze(world, pos.down(), true); } } } } } - - this.playSound(world, caster, ticksInUse, -1, modifiers); - return true; } } diff --git a/src/main/java/electroblob/wizardry/spell/IceSpikes.java b/src/main/java/electroblob/wizardry/spell/IceSpikes.java index 6fe93b3e..16f6b922 100644 --- a/src/main/java/electroblob/wizardry/spell/IceSpikes.java +++ b/src/main/java/electroblob/wizardry/spell/IceSpikes.java @@ -2,8 +2,10 @@ package electroblob.wizardry.spell; import electroblob.wizardry.entity.construct.EntityIceSpike; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.BlockUtils; +import electroblob.wizardry.util.GeometryUtils; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.block.state.IBlockState; import net.minecraft.entity.EntityLivingBase; import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; @@ -48,15 +50,15 @@ public class IceSpikes extends SpellConstructRanged { // First, generate a random vector of length radius with a z component of zero // Then rotate it so that what was south is now the side that was hit Vec3d offset = Vec3d.fromPitchYaw(world.rand.nextFloat() * 180 - 90, world.rand.nextBoolean() ? 0 : 180) - .scale(radius).rotateYaw(side.getHorizontalAngle() * (float)Math.PI/180).rotatePitch(WizardryUtilities.getPitch(side) * (float)Math.PI/180); + .scale(radius).rotateYaw(side.getHorizontalAngle() * (float)Math.PI/180).rotatePitch(GeometryUtils.getPitch(side) * (float)Math.PI/180); 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)); + Integer surface = BlockUtils.getNearestSurface(world, new BlockPos(origin.add(offset)), side, + (int)maxRadius, true, BlockUtils.SurfaceCriteria.basedOn(IBlockState::isNormalCube)); if(surface != null){ - Vec3d vec = WizardryUtilities.replaceComponent(origin.add(offset), side.getAxis(), surface) + Vec3d vec = GeometryUtils.replaceComponent(origin.add(offset), side.getAxis(), surface) .subtract(new Vec3d(side.getDirectionVec())); super.spawnConstruct(world, vec.x, vec.y, vec.z, side, caster, modifiers); } diff --git a/src/main/java/electroblob/wizardry/spell/IceStatue.java b/src/main/java/electroblob/wizardry/spell/IceStatue.java index 35665491..37ba24ad 100644 --- a/src/main/java/electroblob/wizardry/spell/IceStatue.java +++ b/src/main/java/electroblob/wizardry/spell/IceStatue.java @@ -1,6 +1,7 @@ package electroblob.wizardry.spell; import electroblob.wizardry.block.BlockStatue; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.util.ParticleBuilder; @@ -9,7 +10,6 @@ import electroblob.wizardry.util.SpellModifiers; 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.SoundEvent; import net.minecraft.util.math.BlockPos; @@ -19,7 +19,7 @@ import net.minecraft.world.World; public class IceStatue extends SpellRay { public IceStatue(){ - super("ice_statue", false, EnumAction.NONE); + super("ice_statue", SpellActions.POINT, false); this.soundValues(1, 1.4f, 0.4f); addProperties(EFFECT_DURATION); } @@ -35,7 +35,7 @@ public class IceStatue extends SpellRay { 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)))){ + caster, (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)))){ //target.playSound(WizardrySounds.SPELL_FREEZE, 1.0F, world.rand.nextFloat() * 0.4F + 0.8F); } diff --git a/src/main/java/electroblob/wizardry/spell/Ignite.java b/src/main/java/electroblob/wizardry/spell/Ignite.java index b686d89d..8b28a9dd 100644 --- a/src/main/java/electroblob/wizardry/spell/Ignite.java +++ b/src/main/java/electroblob/wizardry/spell/Ignite.java @@ -1,15 +1,15 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.BlockUtils; 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.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.EnumParticleTypes; import net.minecraft.util.math.BlockPos; @@ -20,7 +20,7 @@ import net.minecraft.world.World; public class Ignite extends SpellRay { public Ignite(){ - super("ignite", false, EnumAction.NONE); + super("ignite", SpellActions.POINT, false); this.soundValues(1, 1, 0.4f); addProperties(BURN_DURATION); } @@ -46,13 +46,11 @@ public class Ignite extends SpellRay { @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){ + if(!world.isRemote && BlockUtils.canPlaceBlock(caster, world, pos)){ world.setBlockState(pos, Blocks.FIRE.getDefaultState()); } diff --git a/src/main/java/electroblob/wizardry/spell/ImbueWeapon.java b/src/main/java/electroblob/wizardry/spell/ImbueWeapon.java index 631b9222..a1e8d13f 100644 --- a/src/main/java/electroblob/wizardry/spell/ImbueWeapon.java +++ b/src/main/java/electroblob/wizardry/spell/ImbueWeapon.java @@ -4,28 +4,25 @@ import electroblob.wizardry.Settings; import electroblob.wizardry.Wizardry; import electroblob.wizardry.constants.Constants; import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryEnchantments; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.InventoryUtils; 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.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; -import org.apache.commons.lang3.tuple.Pair; - -import java.util.Arrays; public class ImbueWeapon extends Spell { public ImbueWeapon(){ - super("imbue_weapon", EnumAction.BOW, false); + super("imbue_weapon", SpellActions.IMBUE, false); addProperties(EFFECT_DURATION); } @@ -35,7 +32,7 @@ public class ImbueWeapon extends Spell { // Won't work if the weapon already has the enchantment if(WizardData.get(caster) != null){ - for(ItemStack stack : WizardryUtilities.getPrioritisedHotbarAndOffhand(caster)){ + for(ItemStack stack : InventoryUtils.getPrioritisedHotbarAndOffhand(caster)){ if(isSword(stack) && !EnchantmentHelper.getEnchantments(stack).containsKey(WizardryEnchantments.magic_sword) @@ -68,7 +65,7 @@ public class ImbueWeapon extends Spell { if(world.isRemote){ 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 y = caster.posY + 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); } diff --git a/src/main/java/electroblob/wizardry/spell/Intimidate.java b/src/main/java/electroblob/wizardry/spell/Intimidate.java index 6c9889ea..33594e7c 100644 --- a/src/main/java/electroblob/wizardry/spell/Intimidate.java +++ b/src/main/java/electroblob/wizardry/spell/Intimidate.java @@ -1,31 +1,30 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.util.EntityUtils; 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.EntityCreature; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.ai.RandomPositionGenerator; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.pathfinding.PathPoint; import net.minecraft.potion.PotionEffect; -import net.minecraft.util.EnumHand; +import net.minecraft.tileentity.TileEntityDispenser; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; 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; +import javax.annotation.Nullable; @Mod.EventBusSubscriber -public class Intimidate extends Spell { +public class Intimidate extends SpellAreaEffect { /** The NBT tag name for storing the feared entity's UUID in the target's tag compound. */ public static final String NBT_KEY = "fearedEntity"; @@ -36,45 +35,47 @@ public class Intimidate extends Spell { private static final double AVOID_DISTANCE_PER_LEVEL = 4; public Intimidate(){ - super("intimidate", EnumAction.BOW, false); - addProperties(EFFECT_RADIUS, EFFECT_DURATION, EFFECT_STRENGTH); + super("intimidate", SpellActions.SUMMON, false); + this.alwaysSucceed(true); + addProperties(EFFECT_DURATION, EFFECT_STRENGTH); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + public boolean canBeCastBy(TileEntityDispenser dispenser){ + return false; + } - if(!world.isRemote){ + @Override + protected boolean affectEntity(World world, Vec3d origin, @Nullable EntityLivingBase caster, EntityLivingBase target, int targetCount, int ticksInUse, SpellModifiers modifiers){ - List entities = WizardryUtilities.getEntitiesWithinRadius( - getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.range_upgrade), - caster.posX, caster.posY, caster.posZ, world, EntityCreature.class); + if(caster != null && target instanceof EntityCreature){ - for(EntityCreature target : entities){ - // Why do we need this here? - //runAway(target, caster); + int bonusAmplifier = SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY)); - int bonusAmplifier = SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY)); + NBTTagCompound entityNBT = target.getEntityData(); + if(entityNBT != null) entityNBT.setUniqueId(NBT_KEY, caster.getUniqueID()); - NBTTagCompound entityNBT = target.getEntityData(); - if(entityNBT != null) entityNBT.setUniqueId(NBT_KEY, caster.getUniqueID()); - - 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++){ - 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); - } + target.addPotionEffect(new PotionEffect(WizardryPotions.fear, + (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)), + getProperty(EFFECT_STRENGTH).intValue() + bonusAmplifier)); } - this.playSound(world, caster, ticksInUse, -1, modifiers); + return true; } + @Override + protected void spawnParticleEffect(World world, Vec3d origin, double radius, @Nullable EntityLivingBase caster, SpellModifiers modifiers){ + + if(caster != null) origin = caster.getPositionEyes(1); + + for(int i = 0; i < 30; i++){ + double x = origin.x - 1 + world.rand.nextDouble() * 2; + double y = origin.y - 0.25 + world.rand.nextDouble() * 0.5; + double z = origin.z - 1 + world.rand.nextDouble() * 2; + ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).clr(0.9f, 0.1f, 0).spawn(world); + } + } + /** * Finds a random position away from the caster and sets the given target's AI path to that location. Defined here * so it can be used both in the spell itself and in the potion effect (event handler). @@ -118,7 +119,8 @@ public class Intimidate extends Spell { @SubscribeEvent public static void onLivingUpdateEvent(LivingUpdateEvent event){ - if(event.getEntityLiving().isPotionActive(WizardryPotions.fear) + // No need to do this every tick either + if(event.getEntity().ticksExisted % 50 == 0 && event.getEntityLiving().isPotionActive(WizardryPotions.fear) && event.getEntityLiving() instanceof EntityCreature){ NBTTagCompound entityNBT = event.getEntityLiving().getEntityData(); @@ -126,7 +128,7 @@ public class Intimidate extends Spell { if(entityNBT != null && entityNBT.hasUniqueId(NBT_KEY)){ - Entity caster = WizardryUtilities.getEntityByUUID(creature.world, entityNBT.getUniqueId(NBT_KEY)); + Entity caster = EntityUtils.getEntityByUUID(creature.world, entityNBT.getUniqueId(NBT_KEY)); if(caster instanceof EntityLivingBase){ double distance = BASE_AVOID_DISTANCE + AVOID_DISTANCE_PER_LEVEL diff --git a/src/main/java/electroblob/wizardry/spell/InvigoratingPresence.java b/src/main/java/electroblob/wizardry/spell/InvigoratingPresence.java index a90b7a44..be00fa99 100644 --- a/src/main/java/electroblob/wizardry/spell/InvigoratingPresence.java +++ b/src/main/java/electroblob/wizardry/spell/InvigoratingPresence.java @@ -1,68 +1,46 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; -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.entity.EntityLivingBase; 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.util.math.Vec3d; import net.minecraft.world.World; -import java.util.List; +import javax.annotation.Nullable; -public class InvigoratingPresence extends Spell { +public class InvigoratingPresence extends SpellAreaEffect { public InvigoratingPresence(){ - super("invigorating_presence", EnumAction.BOW, false); + super("invigorating_presence", SpellActions.POINT_UP, false); this.soundValues(0.7f, 1.2f, 0.4f); - addProperties(EFFECT_RADIUS, EFFECT_DURATION, EFFECT_STRENGTH); + this.alwaysSucceed(true); + this.targetAllies(true); + addProperties(EFFECT_DURATION, EFFECT_STRENGTH); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + protected boolean affectEntity(World world, Vec3d origin, @Nullable EntityLivingBase caster, EntityLivingBase target, int targetCount, int ticksInUse, SpellModifiers modifiers){ - List targets = WizardryUtilities.getEntitiesWithinRadius( - getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade), - caster.posX, caster.posY, caster.posZ, world, EntityPlayer.class); + int bonusAmplifier = SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY)); - for(EntityPlayer target : targets){ - if(AllyDesignationSystem.isPlayerAlly(caster, target) || target == caster){ + target.addPotionEffect(new PotionEffect(MobEffects.STRENGTH, + (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)), + getProperty(EFFECT_STRENGTH).intValue() + bonusAmplifier)); - int bonusAmplifier = SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY)); - - target.addPotionEffect(new PotionEffect(MobEffects.STRENGTH, - (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(1, 0.2f, 0.2f).spawn(world); - - } - } - - playSound(world, caster, ticksInUse, -1, modifiers); return true; } + @Override + protected void spawnParticle(World world, double x, double y, double z){ + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.03, 0).time(50).clr(1, 0.2f, 0.2f).spawn(world); + } + @Override protected String getTranslationKey(){ return Wizardry.tisTheSeason ? super.getTranslationKey() + "_festive" : super.getTranslationKey(); diff --git a/src/main/java/electroblob/wizardry/spell/InvokeWeather.java b/src/main/java/electroblob/wizardry/spell/InvokeWeather.java index e62718ac..1d35e879 100644 --- a/src/main/java/electroblob/wizardry/spell/InvokeWeather.java +++ b/src/main/java/electroblob/wizardry/spell/InvokeWeather.java @@ -1,12 +1,12 @@ package electroblob.wizardry.spell; import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.item.SpellActions; 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.player.EntityPlayer; -import net.minecraft.item.EnumAction; import net.minecraft.util.EnumHand; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.World; @@ -18,7 +18,7 @@ public class InvokeWeather extends Spell { public static final String THUNDERSTORM_CHANCE = "thunderstorm_chance"; public InvokeWeather(){ - super("invoke_weather", EnumAction.BOW, false); + super("invoke_weather", SpellActions.POINT_UP, false); addProperties(THUNDERSTORM_CHANCE); soundValues(0.5f, 1, 0); } @@ -54,7 +54,7 @@ public class InvokeWeather extends Spell { if(world.isRemote){ 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 y = caster.posY + 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); } diff --git a/src/main/java/electroblob/wizardry/spell/Leap.java b/src/main/java/electroblob/wizardry/spell/Leap.java index 16b69acc..05a6c255 100644 --- a/src/main/java/electroblob/wizardry/spell/Leap.java +++ b/src/main/java/electroblob/wizardry/spell/Leap.java @@ -30,7 +30,7 @@ public class Leap extends Spell { if(world.isRemote){ for(int i = 0; i < 10; i++){ double x = caster.posX + world.rand.nextFloat() - 0.5F; - double y = caster.getEntityBoundingBox().minY; + double y = caster.posY; double z = caster.posZ + world.rand.nextFloat() - 0.5F; world.spawnParticle(EnumParticleTypes.CLOUD, x, y, z, 0, 0, 0); } diff --git a/src/main/java/electroblob/wizardry/spell/Levitation.java b/src/main/java/electroblob/wizardry/spell/Levitation.java index f70e8f29..bafa7555 100644 --- a/src/main/java/electroblob/wizardry/spell/Levitation.java +++ b/src/main/java/electroblob/wizardry/spell/Levitation.java @@ -1,12 +1,12 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.util.ParticleBuilder; import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; 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; @@ -17,7 +17,7 @@ public class Levitation extends Spell { public static final String ACCELERATION = "acceleration"; public Levitation(){ - super("levitation", EnumAction.BOW, true); + super("levitation", SpellActions.POINT_DOWN, true); addProperties(SPEED, ACCELERATION); soundValues(0.5f, 1, 0); } @@ -47,7 +47,7 @@ public class Levitation extends Spell { if(world.isRemote){ double x = caster.posX - 0.25 + world.rand.nextDouble() * 0.5; - double y = caster.getEntityBoundingBox().minY; + double y = caster.getPositionEyes(1).y; 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); } diff --git a/src/main/java/electroblob/wizardry/spell/LifeDrain.java b/src/main/java/electroblob/wizardry/spell/LifeDrain.java index a4cf6add..81e7a8e5 100644 --- a/src/main/java/electroblob/wizardry/spell/LifeDrain.java +++ b/src/main/java/electroblob/wizardry/spell/LifeDrain.java @@ -1,14 +1,14 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; +import electroblob.wizardry.util.EntityUtils; 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.item.EnumAction; import net.minecraft.util.EnumFacing; import net.minecraft.util.SoundEvent; import net.minecraft.util.math.BlockPos; @@ -20,7 +20,7 @@ public class LifeDrain extends SpellRay { public static final String HEAL_FACTOR = "heal_factor"; public LifeDrain(){ - super("life_drain", true, EnumAction.NONE); + super("life_drain", SpellActions.POINT, true); this.particleVelocity(-0.5); this.particleSpacing(0.4); addProperties(DAMAGE, HEAL_FACTOR); @@ -45,13 +45,13 @@ public class LifeDrain extends SpellRay { @Override protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ - if(WizardryUtilities.isLiving(target)){ + if(EntityUtils.isLiving(target)){ if(ticksInUse % 12 == 0){ float damage = getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY); - WizardryUtilities.attackEntityWithoutKnockback(target, MagicDamage.causeDirectMagicDamage(caster, + EntityUtils.attackEntityWithoutKnockback(target, MagicDamage.causeDirectMagicDamage(caster, DamageType.MAGIC), damage); if(caster != null) caster.heal(damage * getProperty(HEAL_FACTOR).floatValue()); diff --git a/src/main/java/electroblob/wizardry/spell/Light.java b/src/main/java/electroblob/wizardry/spell/Light.java index 95571fb5..216da264 100644 --- a/src/main/java/electroblob/wizardry/spell/Light.java +++ b/src/main/java/electroblob/wizardry/spell/Light.java @@ -1,13 +1,13 @@ package electroblob.wizardry.spell; import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.tileentity.TileEntityTimer; import electroblob.wizardry.util.RayTracer; 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.BlockPos; import net.minecraft.util.math.RayTraceResult; @@ -16,7 +16,7 @@ import net.minecraft.world.World; public class Light extends Spell { public Light(){ - super("light", EnumAction.NONE, false); + super("light", SpellActions.POINT, false); addProperties(RANGE, DURATION); } @@ -47,7 +47,6 @@ public class Light extends Spell { } } - caster.swingArm(hand); this.playSound(world, caster, ticksInUse, -1, modifiers); return true; } @@ -68,7 +67,7 @@ public class Light extends Spell { ((TileEntityTimer)world.getTileEntity(pos)).setLifetime(lifetime); } } - caster.swingArm(hand); + this.playSound(world, caster, ticksInUse, -1, modifiers); return true; } diff --git a/src/main/java/electroblob/wizardry/spell/LightningBolt.java b/src/main/java/electroblob/wizardry/spell/LightningBolt.java index 461cb126..79610e61 100644 --- a/src/main/java/electroblob/wizardry/spell/LightningBolt.java +++ b/src/main/java/electroblob/wizardry/spell/LightningBolt.java @@ -1,27 +1,41 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.item.SpellActions; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.util.EntityUtils; +import electroblob.wizardry.util.MagicDamage; +import electroblob.wizardry.util.MagicDamage.DamageType; import electroblob.wizardry.util.SpellModifiers; import net.minecraft.entity.Entity; 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.nbt.NBTTagCompound; +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.EntityStruckByLightningEvent; +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; -//@Mod.EventBusSubscriber +@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"; + /** The NBT key used to store the UUID of the entity that summoned the lightning bolt. */ + public static final String SUMMONER_NBT_KEY = "summoner"; + /** The NBT key used to store the damage modifier for the lightning bolt. */ + public static final String DAMAGE_MODIFIER_NBT_KEY = "damageModifier"; + /** An NBT key used to cancel vanilla's implementation of lightning damage; since there's no way of extracting the + * lightning bolt entity from {@code LivingAttackEvent} this has to be done using NBT. */ + public static final String IMMUNE_TO_LIGHTNING_NBT_KEY = "immuneToLightning"; public LightningBolt(){ - super("lightning_bolt", false, EnumAction.NONE); + super("lightning_bolt", SpellActions.POINT, false); this.ignoreLivingEntities(true); + addProperties(DAMAGE); } @Override public boolean requiresPacket(){ return false; } @@ -41,17 +55,14 @@ public class LightningBolt extends SpellRay { // 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); + + EntityLightningBolt lightning = new EntityLightningBolt(world, pos.getX(), pos.getY(), pos.getZ(), false); + if(caster != null) lightning.getEntityData().setUniqueId(SUMMONER_NBT_KEY, caster.getUniqueID()); + lightning.getEntityData().setFloat(DAMAGE_MODIFIER_NBT_KEY, modifiers.get(SpellModifiers.POTENCY)); + world.addWeatherEffect(lightning); + // Reset doFireTick to true if it was true before if(doFireTick && !Wizardry.settings.playerBlockDamage) world.getGameRules().setOrCreateGameRule("doFireTick", "true"); - - // Code for eventhandler recognition for achievements - if(caster instanceof EntityPlayer){ - NBTTagCompound entityNBT = entitylightning.getEntityData(); - entityNBT.setUniqueId(NBT_KEY, caster.getUniqueID()); - } } return true; @@ -65,22 +76,40 @@ public class LightningBolt extends SpellRay { return false; } -// @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(priority = EventPriority.LOW) + public static void onEntityStruckByLightningEvent(EntityStruckByLightningEvent event){ + + float damageModifier = event.getLightning().getEntityData().getFloat(DAMAGE_MODIFIER_NBT_KEY); + + Entity summoner = null; + + if(event.getLightning().getEntityData().hasUniqueId(SUMMONER_NBT_KEY)){ + + summoner = EntityUtils.getEntityByUUID(event.getLightning().world, + event.getLightning().getEntityData().getUniqueId(SUMMONER_NBT_KEY)); + + if(!(summoner instanceof EntityLivingBase)) summoner = null; + } + + if(damageModifier > 0 || summoner != null){ + + DamageSource source = summoner == null ? DamageSource.LIGHTNING_BOLT + : MagicDamage.causeIndirectMagicDamage(event.getLightning(), summoner, DamageType.SHOCK); + float damage = Spells.lightning_bolt.getProperty(DAMAGE).floatValue() * damageModifier; + + // Don't need DamageSafetyChecker here because this isn't an attack event + EntityUtils.attackEntityWithoutKnockback(event.getEntity(), source, damage); + event.getEntity().getEntityData().setBoolean(IMMUNE_TO_LIGHTNING_NBT_KEY, true); + } + } + + @SubscribeEvent(priority = EventPriority.HIGHEST) + public static void onLivingAttackEvent(LivingAttackEvent event){ + if(event.getEntity().getEntityData().hasKey(IMMUNE_TO_LIGHTNING_NBT_KEY) + && event.getSource() == DamageSource.LIGHTNING_BOLT){ + event.setCanceled(true); + event.getEntity().getEntityData().removeTag(IMMUNE_TO_LIGHTNING_NBT_KEY); + } + } } diff --git a/src/main/java/electroblob/wizardry/spell/LightningPulse.java b/src/main/java/electroblob/wizardry/spell/LightningPulse.java index 42b8037b..b4f7341f 100644 --- a/src/main/java/electroblob/wizardry/spell/LightningPulse.java +++ b/src/main/java/electroblob/wizardry/spell/LightningPulse.java @@ -1,5 +1,6 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.util.*; import electroblob.wizardry.util.MagicDamage.DamageType; @@ -7,7 +8,6 @@ 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; @@ -21,7 +21,7 @@ public class LightningPulse extends Spell { public static final String REPULSION_VELOCITY = "repulsion_velocity"; public LightningPulse(){ - super("lightning_pulse", EnumAction.NONE, false); + super("lightning_pulse", SpellActions.POINT_DOWN, false); addProperties(EFFECT_RADIUS, DAMAGE, REPULSION_VELOCITY); this.soundValues(2, 1, 0); } @@ -38,7 +38,7 @@ public class LightningPulse extends Spell { if(caster.onGround){ - List targets = WizardryUtilities.getEntitiesWithinRadius( + List targets = EntityUtils.getLivingWithinRadius( getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade), caster.posX, caster.posY, caster.posZ, world); @@ -70,12 +70,11 @@ public class LightningPulse extends Spell { } if(world.isRemote){ - ParticleBuilder.create(Type.LIGHTNING_PULSE).pos(caster.posX, caster.getEntityBoundingBox().minY - + WizardryUtilities.ANTI_Z_FIGHTING_OFFSET, caster.posZ) + ParticleBuilder.create(Type.LIGHTNING_PULSE).pos(caster.posX, caster.posY + + GeometryUtils.ANTI_Z_FIGHTING_OFFSET, caster.posZ) .scale(modifiers.get(WizardryItems.blast_upgrade)).spawn(world); } - - caster.swingArm(hand); + this.playSound(world, caster, ticksInUse, -1, modifiers); return true; } diff --git a/src/main/java/electroblob/wizardry/spell/LightningRay.java b/src/main/java/electroblob/wizardry/spell/LightningRay.java index 21bb5c0b..3e56a1ad 100644 --- a/src/main/java/electroblob/wizardry/spell/LightningRay.java +++ b/src/main/java/electroblob/wizardry/spell/LightningRay.java @@ -1,15 +1,15 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; +import electroblob.wizardry.util.EntityUtils; 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.EnumFacing; import net.minecraft.util.SoundEvent; import net.minecraft.util.math.BlockPos; @@ -20,7 +20,7 @@ import net.minecraft.world.World; public class LightningRay extends SpellRay { public LightningRay(){ - super("lightning_ray", true, EnumAction.NONE); + super("lightning_ray", SpellActions.POINT, true); this.aimAssist(0.6f); addProperties(DAMAGE); } @@ -43,7 +43,7 @@ public class LightningRay extends SpellRay { @Override protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ - if(WizardryUtilities.isLiving(target)){ + if(EntityUtils.isLiving(target)){ if(MagicDamage.isEntityImmune(DamageType.SHOCK, target)){ if(!world.isRemote && ticksInUse == 1 && caster instanceof EntityPlayer) @@ -52,7 +52,7 @@ public class LightningRay extends SpellRay { // 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, + EntityUtils.attackEntityWithoutKnockback(target, MagicDamage.causeDirectMagicDamage(caster, DamageType.SHOCK), getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY)); } diff --git a/src/main/java/electroblob/wizardry/spell/LightningWeb.java b/src/main/java/electroblob/wizardry/spell/LightningWeb.java index 759ea581..8bb275e1 100644 --- a/src/main/java/electroblob/wizardry/spell/LightningWeb.java +++ b/src/main/java/electroblob/wizardry/spell/LightningWeb.java @@ -1,12 +1,12 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.util.*; import electroblob.wizardry.util.MagicDamage.DamageType; 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.EnumFacing; import net.minecraft.util.SoundEvent; import net.minecraft.util.math.BlockPos; @@ -29,7 +29,7 @@ public class LightningWeb extends SpellRay { public static final String TERTIARY_MAX_TARGETS = "tertiary_max_targets"; // This is per secondary target public LightningWeb(){ - super("lightning_web", true, EnumAction.NONE); + super("lightning_web", SpellActions.POINT, true); this.aimAssist(0.6f); addProperties(PRIMARY_DAMAGE, SECONDARY_DAMAGE, TERTIARY_DAMAGE, SECONDARY_RANGE, TERTIARY_RANGE, SECONDARY_MAX_TARGETS, TERTIARY_MAX_TARGETS); @@ -53,19 +53,19 @@ public class LightningWeb extends SpellRay { @Override protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ - if(WizardryUtilities.isLiving(target)){ + if(EntityUtils.isLiving(target)){ electrocute(world, caster, origin, target, getProperty(PRIMARY_DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY), ticksInUse); // Secondary chaining effect - List secondaryTargets = WizardryUtilities.getEntitiesWithinRadius( + List secondaryTargets = EntityUtils.getLivingWithinRadius( getProperty(SECONDARY_RANGE).floatValue(), target.posX, target.posY + target.height / 2, target.posZ, world); secondaryTargets.remove(target); - secondaryTargets.removeIf(e -> !WizardryUtilities.isLiving(e)); + secondaryTargets.removeIf(e -> !EntityUtils.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()); @@ -77,13 +77,13 @@ public class LightningWeb extends SpellRay { // Tertiary chaining effect - List tertiaryTargets = WizardryUtilities.getEntitiesWithinRadius( + List tertiaryTargets = EntityUtils.getLivingWithinRadius( 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 -> !EntityUtils.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()); @@ -139,7 +139,7 @@ public class LightningWeb extends SpellRay { ((EntityPlayer)caster).sendStatusMessage(new TextComponentTranslation("spell.resist", target.getName(), this.getNameForTranslationFormatted()), true); }else{ - WizardryUtilities.attackEntityWithoutKnockback(target, + EntityUtils.attackEntityWithoutKnockback(target, MagicDamage.causeDirectMagicDamage(caster, DamageType.SHOCK), damage); } diff --git a/src/main/java/electroblob/wizardry/spell/MarkSacrifice.java b/src/main/java/electroblob/wizardry/spell/MarkSacrifice.java new file mode 100644 index 00000000..60217c5a --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/MarkSacrifice.java @@ -0,0 +1,71 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.item.SpellActions; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.util.EntityUtils; +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.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 net.minecraftforge.event.entity.living.LivingHurtEvent; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +@EventBusSubscriber +public class MarkSacrifice extends SpellRay { + + private static final float DAMAGE_INCREASE_PER_LEVEL = 0.6f; + + public MarkSacrifice(){ + super("mark_sacrifice", SpellActions.POINT, false); + this.soundValues(1, 1.1f, 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(EntityUtils.isLiving(target)){ + ((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.mark_of_sacrifice, + (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(0xe90e48).spawn(world); + ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).time(12 + world.rand.nextInt(8)).clr(0xff7bbb).spawn(world); + } + + @SubscribeEvent + public static void onLivingHurtEvent(LivingHurtEvent event){ + + PotionEffect effect = event.getEntityLiving().getActivePotionEffect(WizardryPotions.mark_of_sacrifice); + + if(effect != null && event.getSource().isMagicDamage()){ + event.setAmount(event.getAmount() * (1 + effect.getAmplifier() * DAMAGE_INCREASE_PER_LEVEL)); + event.getEntityLiving().removePotionEffect(WizardryPotions.mark_of_sacrifice); + } + } + +} diff --git a/src/main/java/electroblob/wizardry/spell/Metamorphosis.java b/src/main/java/electroblob/wizardry/spell/Metamorphosis.java index 2270da29..9c857db5 100644 --- a/src/main/java/electroblob/wizardry/spell/Metamorphosis.java +++ b/src/main/java/electroblob/wizardry/spell/Metamorphosis.java @@ -4,17 +4,17 @@ import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import electroblob.wizardry.Wizardry; import electroblob.wizardry.entity.living.*; +import electroblob.wizardry.item.SpellActions; +import electroblob.wizardry.util.EntityUtils; 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.WizardryUtilities; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.monster.*; import net.minecraft.entity.passive.*; -import net.minecraft.item.EnumAction; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; @@ -49,7 +49,7 @@ public class Metamorphosis extends SpellRay { } public Metamorphosis(){ - super("metamorphosis", false, EnumAction.NONE); + super("metamorphosis", SpellActions.POINT, false); this.soundValues(0.5f, 1f, 0); } @@ -58,7 +58,7 @@ public class Metamorphosis extends SpellRay { @Override protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ - if(WizardryUtilities.isLiving(target)){ + if(EntityUtils.isLiving(target)){ double xPos = target.posX; double yPos = target.posY; diff --git a/src/main/java/electroblob/wizardry/spell/Meteor.java b/src/main/java/electroblob/wizardry/spell/Meteor.java index a89f1173..f00ab1ad 100644 --- a/src/main/java/electroblob/wizardry/spell/Meteor.java +++ b/src/main/java/electroblob/wizardry/spell/Meteor.java @@ -1,13 +1,16 @@ package electroblob.wizardry.spell; import electroblob.wizardry.entity.EntityMeteor; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.EntityUtils; 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.entity.player.EntityPlayer; 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; @@ -18,12 +21,38 @@ public class Meteor extends SpellRay { public static final String BLAST_STRENGTH = "blast_strength"; public Meteor(){ - super("meteor", false, EnumAction.NONE); + super("meteor", SpellActions.POINT, false); this.soundValues(3, 1, 0); this.ignoreLivingEntities(true); addProperties(BLAST_STRENGTH); } + @Override + public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + + if(ItemArtefact.isArtefactActive(caster, WizardryItems.ring_meteor)){ + + if(!world.isRemote){ + + EntityMeteor meteor = new EntityMeteor(world, caster.posX, caster.posY + caster.getEyeHeight(), caster.posZ, + modifiers.get(WizardryItems.blast_upgrade), EntityUtils.canDamageBlocks(caster, world)); + + Vec3d direction = caster.getLookVec().scale(2 * modifiers.get(WizardryItems.range_upgrade)); + meteor.motionX = direction.x; + meteor.motionY = direction.y; + meteor.motionZ = direction.z; + + world.spawnEntity(meteor); + } + + this.playSound(world, caster, ticksInUse, -1, modifiers); + return true; + + }else{ + return super.cast(world, caster, hand, ticksInUse, modifiers); + } + } + @Override public boolean requiresPacket(){ return false; } @Override @@ -38,7 +67,7 @@ public class Meteor extends SpellRay { if(!world.isRemote){ EntityMeteor meteor = new EntityMeteor(world, pos.getX(), pos.getY() + 50, pos.getZ(), - modifiers.get(WizardryItems.blast_upgrade), WizardryUtilities.canDamageBlocks(caster, world)); + modifiers.get(WizardryItems.blast_upgrade), EntityUtils.canDamageBlocks(caster, world)); world.spawnEntity(meteor); } diff --git a/src/main/java/electroblob/wizardry/spell/MindControl.java b/src/main/java/electroblob/wizardry/spell/MindControl.java index 039b642c..6ce3f206 100644 --- a/src/main/java/electroblob/wizardry/spell/MindControl.java +++ b/src/main/java/electroblob/wizardry/spell/MindControl.java @@ -2,20 +2,20 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; import electroblob.wizardry.entity.living.EntityEvilWizard; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.AllyDesignationSystem; +import electroblob.wizardry.util.EntityUtils; 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.*; 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; @@ -27,6 +27,7 @@ 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.LivingSetAttackTargetEvent; +import net.minecraftforge.event.entity.living.PotionEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; @@ -40,7 +41,7 @@ public class MindControl extends SpellRay { public static final String NBT_KEY = "controllingEntity"; public MindControl(){ - super("mind_control", false, EnumAction.NONE); + super("mind_control", SpellActions.POINT, false); addProperties(EFFECT_DURATION); } @@ -50,7 +51,7 @@ public class MindControl extends SpellRay { @Override protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ - if(WizardryUtilities.isLiving(target)){ + if(EntityUtils.isLiving(target)){ if(!canControl(target)){ if(!world.isRemote){ @@ -71,7 +72,7 @@ public class MindControl extends SpellRay { } if(target instanceof EntitySheep && ((EntitySheep)target).getFleeceColor() == EnumDyeColor.BLUE - && WizardryUtilities.canDamageBlocks(caster, world)){ + && EntityUtils.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); } @@ -84,10 +85,10 @@ public class MindControl extends SpellRay { 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) + target.posY + 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) + target.posY + target.getEyeHeight(), target.posZ, 0.25, false) .clr(0.2f, 0.04f, 0.25f).spawn(world); } } @@ -135,11 +136,12 @@ public class MindControl extends SpellRay { // As of 1.1, this now uses the creature's follow range, like normal targeting. It also // 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( + List possibleTargets = EntityUtils.getLivingWithinRadius( target.getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE).getAttributeValue(), target.posX, target.posY, target.posZ, world); possibleTargets.remove(target); + possibleTargets.remove(target.getRidingEntity()); possibleTargets.removeIf(e -> e instanceof EntityArmorStand); EntityLivingBase newAITarget = null; @@ -152,10 +154,7 @@ public class MindControl extends SpellRay { } 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.) target.setAttackTarget(newAITarget); - return true; } @@ -172,7 +171,7 @@ public class MindControl extends SpellRay { if(entityNBT != null && entityNBT.hasUniqueId(MindControl.NBT_KEY)){ - Entity caster = WizardryUtilities.getEntityByUUID(world, entityNBT.getUniqueId(MindControl.NBT_KEY)); + Entity caster = EntityUtils.getEntityByUUID(world, entityNBT.getUniqueId(MindControl.NBT_KEY)); if(caster instanceof EntityLivingBase){ @@ -197,7 +196,9 @@ public class MindControl extends SpellRay { // more efficient (because it only fires when the entity tries to set a target) // Of course, in survival this code is unlikely to be used much because the entity will always try to target the // player and hence will rarely have no target. - if(event.getEntityLiving().isPotionActive(WizardryPotions.mind_control) && event.getEntityLiving() instanceof EntityLiving){ + // No need to do it every tick either! + if(event.getEntity().ticksExisted % 50 == 0 && event.getEntityLiving().isPotionActive(WizardryPotions.mind_control) + && event.getEntityLiving() instanceof EntityLiving){ EntityLiving entity = (EntityLiving)event.getEntityLiving(); @@ -205,7 +206,7 @@ public class MindControl extends SpellRay { if(((EntityLiving)event.getEntityLiving()).getAttackTarget() == null || !((EntityLiving)event.getEntityLiving()).getAttackTarget().isEntityAlive()){ - processTargeting(entity.world, entity, entity.getAttackTarget()); + processTargeting(entity.world, entity, null); } } } @@ -217,4 +218,21 @@ public class MindControl extends SpellRay { processTargeting(event.getEntity().world, (EntityLiving)event.getEntityLiving(), event.getTarget()); } + @SubscribeEvent + public static void onPotionExpiryEvent(PotionEvent.PotionExpiryEvent event){ + onEffectEnd(event.getPotionEffect(), event.getEntity()); + } + + @SubscribeEvent + public static void onPotionExpiryEvent(PotionEvent.PotionRemoveEvent event){ + onEffectEnd(event.getPotionEffect(), event.getEntity()); + } + + private static void onEffectEnd(PotionEffect effect, Entity entity){ + if(effect != null && effect.getPotion() == WizardryPotions.mind_control && entity instanceof EntityLiving){ + ((EntityLiving)entity).setAttackTarget(null); // End effect + ((EntityLiving)entity).setRevengeTarget(null); // End effect + } + } + } \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/spell/MindTrick.java b/src/main/java/electroblob/wizardry/spell/MindTrick.java index 7aeb5cde..80b62722 100644 --- a/src/main/java/electroblob/wizardry/spell/MindTrick.java +++ b/src/main/java/electroblob/wizardry/spell/MindTrick.java @@ -1,17 +1,17 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryPotions; +import electroblob.wizardry.util.EntityUtils; 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.init.MobEffects; -import net.minecraft.item.EnumAction; import net.minecraft.potion.PotionEffect; import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; @@ -26,7 +26,7 @@ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; public class MindTrick extends SpellRay { public MindTrick(){ - super("mind_trick", false, EnumAction.NONE); + super("mind_trick", SpellActions.POINT, false); this.soundValues(0.7f, 1, 0.4f); addProperties(EFFECT_DURATION); } @@ -34,7 +34,7 @@ public class MindTrick extends SpellRay { @Override protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ - if(WizardryUtilities.isLiving(target)){ + if(EntityUtils.isLiving(target)){ if(!world.isRemote){ @@ -53,7 +53,7 @@ public class MindTrick extends SpellRay { }else{ 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) + target.posY + target.getEyeHeight(), target.posZ, 0.25, false) .clr(0.8f, 0.2f, 1.0f).spawn(world); } } diff --git a/src/main/java/electroblob/wizardry/spell/Mine.java b/src/main/java/electroblob/wizardry/spell/Mine.java index 30389fb9..19f71323 100644 --- a/src/main/java/electroblob/wizardry/spell/Mine.java +++ b/src/main/java/electroblob/wizardry/spell/Mine.java @@ -1,36 +1,44 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.Wizardry; import electroblob.wizardry.constants.Constants; import electroblob.wizardry.item.ISpellCastingItem; import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.BlockUtils; +import electroblob.wizardry.util.EntityUtils; 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 net.minecraftforge.fml.common.ObfuscationReflectionHelper; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.List; public class Mine extends SpellRay { + private static final Method getSilkTouchDrop; + + static { + getSilkTouchDrop = ObfuscationReflectionHelper.findMethod(Block.class, "func_180643_i", ItemStack.class, IBlockState.class); + } + public Mine(){ - super("mine", false, EnumAction.NONE); + super("mine", SpellActions.POINT, false); this.ignoreLivingEntities(true); this.particleSpacing(0.5); } @@ -54,14 +62,9 @@ public class Mine extends SpellRay { 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; + if(BlockUtils.isBlockUnbreakable(world, pos)) return false; + // Reworked to respect the rules, but since we might break multiple blocks this is left as an optimisation + if(!EntityUtils.canDamageBlocks(caster, world)) return false; IBlockState state = world.getBlockState(pos); // The maximum harvest level as determined by the potency multiplier. The + 0.5f is so that @@ -75,7 +78,7 @@ public class Mine extends SpellRay { boolean flag = false; - int blastUpgradeCount = (int)((modifiers.get(WizardryItems.blast_upgrade) - 1) / Constants.RANGE_INCREASE_PER_LEVEL + 0.5f); + int blastUpgradeCount = (int)((modifiers.get(WizardryItems.blast_upgrade) - 1) / Constants.BLAST_RADIUS_INCREASE_PER_LEVEL + 0.5f); // Results in the following patterns: // 0 blast upgrades: single block // 1 blast upgrade: 3x3 without corners or edges @@ -83,11 +86,11 @@ public class Mine extends SpellRay { // 3 blast upgrades: 5x5 without corners or edges float radius = 0.5f + 0.73f * blastUpgradeCount; - List sphere = WizardryUtilities.getBlockSphere(pos, radius); + List sphere = BlockUtils.getBlockSphere(pos, radius); for(BlockPos pos1 : sphere){ - if(WizardryUtilities.isBlockUnbreakable(world, pos1)) continue; + if(BlockUtils.isBlockUnbreakable(world, pos1)) continue; IBlockState state1 = world.getBlockState(pos1); @@ -98,23 +101,22 @@ public class Mine extends SpellRay { 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 = BlockUtils.checkBlockBreakXP(caster, world, pos); - int xp = ForgeHooks.onBlockBreakEvent(world, - ((EntityPlayerMP)caster).interactionManager.getGameType(), (EntityPlayerMP)caster, pos1); - - if(xp == -1) continue; // Event was cancelled + if(xp < 0) continue; // Not allowed to break the block if(silkTouch){ flag = world.destroyBlock(pos1, false); - if(flag) Block.spawnAsEntity(world, pos1, getSilkTouchDrop(state1)); + if(flag){ + ItemStack stack = getSilkTouchDrop(state1); + if(stack != null) Block.spawnAsEntity(world, pos1, stack); + } }else{ flag = world.destroyBlock(pos1, true); if(flag) state1.getBlock().dropXpOnBlockBreak(world, pos1, xp); } - }else{ + }else if(BlockUtils.canBreakBlock(caster, world, pos)){ // NPCs can dig the block under the target's feet flag = world.destroyBlock(pos1, true) || flag; } @@ -141,17 +143,15 @@ public class Mine extends SpellRay { .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); + try { + return (ItemStack)getSilkTouchDrop.invoke(state.getBlock(), state); + }catch(IllegalAccessException | InvocationTargetException e){ + Wizardry.logger.error("Error while reflectively retrieving silk touch drop", e); } - return new ItemStack(item, 1, i); + return null; } } diff --git a/src/main/java/electroblob/wizardry/spell/Paralysis.java b/src/main/java/electroblob/wizardry/spell/Paralysis.java index 795a884a..5c57e313 100644 --- a/src/main/java/electroblob/wizardry/spell/Paralysis.java +++ b/src/main/java/electroblob/wizardry/spell/Paralysis.java @@ -1,19 +1,16 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryPotions; -import electroblob.wizardry.util.MagicDamage; +import electroblob.wizardry.util.*; 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; @@ -34,14 +31,14 @@ public class Paralysis extends SpellRay { private static final String CRITICAL_HEALTH = "critical_health"; public Paralysis(){ - super("paralysis", false, EnumAction.NONE); + super("paralysis", SpellActions.POINT, false); 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(EntityUtils.isLiving(target)){ if(world.isRemote){ // Rather neatly, the entity can be set here and if it's null nothing will happen. @@ -74,7 +71,7 @@ public class Paralysis extends SpellRay { if(world.isRemote){ if(world.getBlockState(pos).getMaterial().isSolid()){ - Vec3d vec = hit.add(new Vec3d(side.getDirectionVec()).scale(WizardryUtilities.ANTI_Z_FIGHTING_OFFSET)); + Vec3d vec = hit.add(new Vec3d(side.getDirectionVec()).scale(GeometryUtils.ANTI_Z_FIGHTING_OFFSET)); ParticleBuilder.create(Type.SCORCH).pos(vec).face(side).clr(0.4f, 0.8f, 1).spawn(world); } } @@ -98,8 +95,6 @@ public class Paralysis extends SpellRay { // 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 diff --git a/src/main/java/electroblob/wizardry/spell/Permafrost.java b/src/main/java/electroblob/wizardry/spell/Permafrost.java new file mode 100644 index 00000000..3bcb3380 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/Permafrost.java @@ -0,0 +1,109 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.constants.Constants; +import electroblob.wizardry.item.SpellActions; +import electroblob.wizardry.registry.WizardryBlocks; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.BlockUtils; +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.util.EnumFacing; +import net.minecraft.util.SoundEvent; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import java.util.List; + +public class Permafrost extends SpellRay { + + public Permafrost(){ + super("permafrost", SpellActions.POINT, true); + this.particleVelocity(1); + this.particleSpacing(0.5); + soundValues(0.5f, 1, 0); + addProperties(DURATION, DAMAGE, EFFECT_DURATION, EFFECT_STRENGTH); + this.ignoreLivingEntities(true); + } + + @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){ + return false; + } + + @Override + protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ + + boolean flag = false; + + if(!world.isRemote){ + + int blastUpgradeCount = (int)((modifiers.get(WizardryItems.blast_upgrade) - 1) / Constants.BLAST_RADIUS_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; + + int duration = (int)(getProperty(DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)); + + List sphere = BlockUtils.getBlockSphere(pos.up(), radius); + + for(BlockPos pos1 : sphere){ + flag |= tryToPlaceIce(world, pos1, caster, duration); + } + + return flag; + + } + + return true; + + } + + private boolean tryToPlaceIce(World world, BlockPos pos, EntityLivingBase caster, int duration){ + + if(world.getBlockState(pos.down()).isSideSolid(world, pos.down(), EnumFacing.UP) && BlockUtils.canBlockBeReplaced(world, pos)){ + if(BlockUtils.canPlaceBlock(caster, world, pos)){ + world.setBlockState(pos, WizardryBlocks.permafrost.getDefaultState()); + world.scheduleUpdate(pos.toImmutable(), WizardryBlocks.permafrost, duration); + 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){ + float brightness = world.rand.nextFloat(); + ParticleBuilder.create(Type.DUST).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/Petrify.java b/src/main/java/electroblob/wizardry/spell/Petrify.java index de5e6d4b..6638cfb0 100644 --- a/src/main/java/electroblob/wizardry/spell/Petrify.java +++ b/src/main/java/electroblob/wizardry/spell/Petrify.java @@ -1,6 +1,7 @@ package electroblob.wizardry.spell; import electroblob.wizardry.block.BlockStatue; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.util.ParticleBuilder; @@ -9,7 +10,6 @@ import electroblob.wizardry.util.SpellModifiers; 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.math.BlockPos; import net.minecraft.util.math.Vec3d; @@ -21,7 +21,7 @@ public class Petrify extends SpellRay { public static final String MINIMUM_EFFECT_DURATION = "minimum_effect_duration"; public Petrify(){ - super("petrify", false, EnumAction.NONE); + super("petrify", SpellActions.POINT, false); this.soundValues(1, 1.1f, 0.2f); addProperties(MINIMUM_EFFECT_DURATION); } @@ -32,7 +32,7 @@ public class Petrify extends SpellRay { 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)))){ + caster, (int)(getProperty(MINIMUM_EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)))){ } } diff --git a/src/main/java/electroblob/wizardry/spell/PhaseStep.java b/src/main/java/electroblob/wizardry/spell/PhaseStep.java index 9f21eac8..b6cf0b8d 100644 --- a/src/main/java/electroblob/wizardry/spell/PhaseStep.java +++ b/src/main/java/electroblob/wizardry/spell/PhaseStep.java @@ -2,12 +2,13 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; import electroblob.wizardry.constants.Constants; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.RayTracer; -import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.*; +import net.minecraft.entity.Entity; +import net.minecraft.entity.item.EntityBoat; 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.EnumParticleTypes; @@ -21,15 +22,19 @@ public class PhaseStep extends Spell { public static final String WALL_THICKNESS = "wall_thickness"; public PhaseStep(){ - super("phase_step", EnumAction.NONE, false); + super("phase_step", SpellActions.POINT, false); addProperties(RANGE, WALL_THICKNESS); } @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + boolean teleportMount = caster.isRiding() && ItemArtefact.isArtefactActive(caster, WizardryItems.charm_mount_teleporting); + boolean hitLiquids = teleportMount && caster.getRidingEntity() instanceof EntityBoat; // Boats teleport to the surface + double range = getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade); - RayTraceResult rayTrace = RayTracer.standardBlockRayTrace(world, caster, range, false); + + RayTraceResult rayTrace = RayTracer.standardBlockRayTrace(world, caster, range, hitLiquids, !hitLiquids, false); // This is here because the conditions are false on the client for whatever reason. (see the Javadoc for cast() // for an explanation) @@ -37,7 +42,7 @@ public class PhaseStep extends Spell { for(int i = 0; i < 10; i++){ double dx1 = caster.posX; - double dy1 = caster.getEntityBoundingBox().minY + 2 * world.rand.nextFloat(); + double dy1 = caster.posY + 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); @@ -46,6 +51,8 @@ public class PhaseStep extends Spell { Wizardry.proxy.playBlinkEffect(caster); } + Entity toTeleport = teleportMount ? caster.getRidingEntity() : caster; + if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){ BlockPos pos = rayTrace.getBlockPos(); @@ -64,71 +71,44 @@ public class PhaseStep extends Spell { // 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())) + if((BlockUtils.isBlockUnbreakable(world, pos1) || BlockUtils.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; - } + Vec3d vec = GeometryUtils.getFaceCentre(pos1, EnumFacing.DOWN); + if(attemptTeleport(world, toTeleport, vec, teleportMount, caster, ticksInUse, 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; - } + Vec3d vec = GeometryUtils.getFaceCentre(pos, EnumFacing.DOWN); + if(attemptTeleport(world, toTeleport, vec, teleportMount, caster, ticksInUse, modifiers)) return true; + }else{ // The ray trace missed + Vec3d vec = caster.getPositionVector().add(caster.getLookVec().scale(range)); + if(attemptTeleport(world, toTeleport, vec, teleportMount, caster, ticksInUse, modifiers)) return true; + } + + return false; + } + + protected boolean attemptTeleport(World world, Entity toTeleport, Vec3d destination, boolean teleportMount, EntityPlayer caster, int ticksInUse, SpellModifiers modifiers){ + + destination = EntityUtils.findSpaceForTeleport(toTeleport, destination, teleportMount); + + if(destination != null){ // 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); + if(!teleportMount && caster.isRiding()) caster.dismountRidingEntity(); + if(!world.isRemote) toTeleport.setPositionAndUpdate(destination.x, destination.y, destination.z); 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; } + + return false; } } diff --git a/src/main/java/electroblob/wizardry/spell/PlagueOfDarkness.java b/src/main/java/electroblob/wizardry/spell/PlagueOfDarkness.java index bf1c9d32..4bbac020 100644 --- a/src/main/java/electroblob/wizardry/spell/PlagueOfDarkness.java +++ b/src/main/java/electroblob/wizardry/spell/PlagueOfDarkness.java @@ -1,84 +1,76 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.*; +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 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.item.EnumAction; import net.minecraft.potion.PotionEffect; -import net.minecraft.util.EnumHand; import net.minecraft.util.EnumParticleTypes; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; -import java.util.List; +import javax.annotation.Nullable; -public class PlagueOfDarkness extends Spell { +public class PlagueOfDarkness extends SpellAreaEffect { public PlagueOfDarkness(){ - super("plague_of_darkness", EnumAction.BOW, false); - addProperties(EFFECT_RADIUS, DAMAGE, EFFECT_DURATION, EFFECT_STRENGTH); + super("plague_of_darkness", SpellActions.POINT_DOWN, false); + this.alwaysSucceed(true); + addProperties(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){ + protected boolean affectEntity(World world, Vec3d origin, @Nullable EntityLivingBase caster, EntityLivingBase target, int targetCount, int ticksInUse, SpellModifiers modifiers){ - 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(AllyDesignationSystem.isValidTarget(caster, target) - && !MagicDamage.isEntityImmune(DamageType.WITHER, target)){ - target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.WITHER), - getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY)); - 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(!MagicDamage.isEntityImmune(DamageType.WITHER, target)){ + target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.WITHER), + getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY)); + 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){ - - 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){ - world.spawnParticle(EnumParticleTypes.BLOCK_DUST, particleX, caster.getEntityBoundingBox().minY, - 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); - this.playSound(world, caster, ticksInUse, -1, modifiers); return true; } + @Override + protected void spawnParticleEffect(World world, Vec3d origin, double radius, @Nullable EntityLivingBase caster, SpellModifiers modifiers){ + + double particleX, particleZ; + + for(int i = 0; i < 40 * modifiers.get(WizardryItems.blast_upgrade); i++){ + + particleX = origin.x - 1.0d + 2 * world.rand.nextDouble(); + particleZ = origin.z - 1.0d + 2 * world.rand.nextDouble(); + ParticleBuilder.create(Type.DARK_MAGIC).pos(particleX, origin.y, particleZ) + .vel(particleX - origin.x, 0, particleZ - origin.z).clr(0.1f, 0, 0).spawn(world); + + particleX = origin.x - 1.0d + 2 * world.rand.nextDouble(); + particleZ = origin.z - 1.0d + 2 * world.rand.nextDouble(); + ParticleBuilder.create(Type.SPARKLE).pos(particleX, origin.y, particleZ) + .vel(particleX - origin.x, 0, particleZ - origin.z).time(30).clr(0.1f, 0, 0.05f).spawn(world); + + particleX = origin.x - 1.0d + 2 * world.rand.nextDouble(); + particleZ = origin.z - 1.0d + 2 * world.rand.nextDouble(); + + IBlockState block = world.getBlockState(new BlockPos(origin.x, origin.y - 0.5, origin.z)); + + if(block != null){ + world.spawnParticle(EnumParticleTypes.BLOCK_DUST, particleX, origin.y, + particleZ, particleX - origin.x, 0, particleZ - origin.z, Block.getStateId(block)); + } + } + + ParticleBuilder.create(Type.SPHERE).pos(origin.add(0, 0.1, 0)).scale((float)radius * 0.8f).clr(0.8f, 0, 0.05f).spawn(world); + } + } diff --git a/src/main/java/electroblob/wizardry/spell/PocketFurnace.java b/src/main/java/electroblob/wizardry/spell/PocketFurnace.java index 29a2bdb8..2663056c 100644 --- a/src/main/java/electroblob/wizardry/spell/PocketFurnace.java +++ b/src/main/java/electroblob/wizardry/spell/PocketFurnace.java @@ -2,10 +2,14 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Settings; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.item.SpellActions; +import electroblob.wizardry.util.InventoryUtils; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.*; +import net.minecraft.item.ItemArmor; +import net.minecraft.item.ItemStack; +import net.minecraft.item.ItemSword; +import net.minecraft.item.ItemTool; import net.minecraft.item.crafting.FurnaceRecipes; import net.minecraft.util.EnumHand; import net.minecraft.util.EnumParticleTypes; @@ -16,7 +20,7 @@ public class PocketFurnace extends Spell { public static final String ITEMS_SMELTED = "items_smelted"; public PocketFurnace(){ - super("pocket_furnace", EnumAction.BOW, false); + super("pocket_furnace", SpellActions.IMBUE, false); addProperties(ITEMS_SMELTED); soundValues(1, 0.75f, 0); } @@ -42,7 +46,7 @@ public class PocketFurnace extends Spell { if(stack.getCount() <= usesLeft){ ItemStack stack2 = new ItemStack(result.getItem(), stack.getCount(), result.getItemDamage()); - if(WizardryUtilities.doesPlayerHaveItem(caster, result.getItem())){ + if(InventoryUtils.doesPlayerHaveItem(caster, result.getItem())){ caster.inventory.addItemStackToInventory(stack2); caster.inventory.setInventorySlotContents(i, ItemStack.EMPTY); }else{ @@ -64,7 +68,7 @@ public class PocketFurnace extends Spell { 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.getEntityBoundingBox().minY + caster.getEyeHeight() - 0.5F + world.rand.nextFloat()); + 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); 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 fb4ea0d1..d9600ca3 100644 --- a/src/main/java/electroblob/wizardry/spell/PocketWorkbench.java +++ b/src/main/java/electroblob/wizardry/spell/PocketWorkbench.java @@ -2,16 +2,16 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; import electroblob.wizardry.WizardryGuiHandler; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.util.SpellModifiers; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; import net.minecraft.util.EnumHand; import net.minecraft.world.World; public class PocketWorkbench extends Spell { public PocketWorkbench(){ - super("pocket_workbench", EnumAction.BOW, false); + super("pocket_workbench", SpellActions.IMBUE, false); } @Override @@ -22,7 +22,6 @@ public class PocketWorkbench extends Spell { @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - // TODO: Investigate possible item duplication bug with this spell. So far I have been unable to recreate it. if(!world.isRemote){ caster.openGui(Wizardry.instance, WizardryGuiHandler.PORTABLE_CRAFTING, world, (int)caster.posX, (int)caster.posY, (int)caster.posZ); diff --git a/src/main/java/electroblob/wizardry/spell/Poison.java b/src/main/java/electroblob/wizardry/spell/Poison.java index 0e747f0f..3b1d976d 100644 --- a/src/main/java/electroblob/wizardry/spell/Poison.java +++ b/src/main/java/electroblob/wizardry/spell/Poison.java @@ -1,18 +1,18 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.EntityUtils; 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.init.MobEffects; -import net.minecraft.item.EnumAction; import net.minecraft.potion.PotionEffect; import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; @@ -23,7 +23,7 @@ import net.minecraft.world.World; public class Poison extends SpellRay { public Poison(){ - super("poison", false, EnumAction.NONE); + super("poison", SpellActions.POINT, false); this.soundValues(1, 1.1f, 0.2f); addProperties(DAMAGE, EFFECT_DURATION, EFFECT_STRENGTH); } @@ -36,7 +36,7 @@ public class Poison extends SpellRay { @Override protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ - if(WizardryUtilities.isLiving(target)){ + if(EntityUtils.isLiving(target)){ // Has no effect on undead or spiders. if(MagicDamage.isEntityImmune(DamageType.POISON, target)){ diff --git a/src/main/java/electroblob/wizardry/spell/Possession.java b/src/main/java/electroblob/wizardry/spell/Possession.java index c94d26dc..7d4f600e 100644 --- a/src/main/java/electroblob/wizardry/spell/Possession.java +++ b/src/main/java/electroblob/wizardry/spell/Possession.java @@ -11,16 +11,18 @@ import electroblob.wizardry.data.WizardData; import electroblob.wizardry.entity.living.*; import electroblob.wizardry.entity.projectile.*; import electroblob.wizardry.integration.DamageSafetyChecker; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.packet.PacketControlInput; import electroblob.wizardry.packet.PacketPossession; import electroblob.wizardry.packet.WizardryPacketHandler; +import electroblob.wizardry.potion.PotionSlowTime; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.EntityUtils; 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.WizardryUtilities; import net.minecraft.enchantment.Enchantment; import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.entity.*; @@ -38,7 +40,6 @@ 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; @@ -68,9 +69,7 @@ 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.*; import java.util.function.BiConsumer; import java.util.function.Function; @@ -80,8 +79,8 @@ 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 possessed flag in the target's tag compound, used only for rendering. */ + public static final String NBT_KEY = "possessed"; /** The NBT tag name for storing the possessor's previous inventory in their tag compound. */ public static final String INVENTORY_NBT_KEY = "prevInventory"; @@ -96,6 +95,7 @@ public class Possession extends SpellRay { private static final Multimap, BiConsumer> abilities = HashMultimap.create(); private static final Map, Function> projectiles = new HashMap<>(); + private static final Set> blacklist = new HashSet<>(); private static final Map INHERITED_ATTRIBUTES; @@ -118,10 +118,11 @@ public class Possession extends SpellRay { addProjectile(EntityStormElemental.class, EntityLightningDisc::new); addProjectile(EntityWitch.class, EntityPotion::new); + blacklist.add(EntityDecoy.class); } public Possession(){ - super("possession", false, EnumAction.NONE); + super("possession", SpellActions.POINT, false); addProperties(EFFECT_DURATION, CRITICAL_HEALTH); } @@ -142,12 +143,12 @@ public class Possession extends SpellRay { 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); + Vec3d origin = new Vec3d(caster.posX, caster.posY + 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... +// if(casterSwingsArm(world, caster, hand, ticksInUse, modifiers)) caster.swingArm(hand); + this.playSound(world, caster, ticksInUse, -1, modifiers, "possess"); return true; } @@ -155,7 +156,8 @@ public class Possession extends SpellRay { 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)){ + if(target instanceof EntityLiving && !blacklist.contains(target.getClass()) && caster instanceof EntityPlayer + && !isPossessing((EntityPlayer)caster)){ EntityPlayer player = (EntityPlayer)caster; @@ -216,6 +218,7 @@ public class Possession extends SpellRay { target.setDead(); target.setNoAI(true); target.setAttackTarget(null); + target.getEntityData().setBoolean(NBT_KEY, true); // Attributes @@ -248,7 +251,7 @@ public class Possession extends SpellRay { possessor.getAttributeMap().getAttributeInstance(attribute).applyModifier(new AttributeModifier( INHERITED_ATTRIBUTES.get(attribute), "possessionModifier", targetValue / currentValue, - WizardryUtilities.Operations.MULTIPLY_FLAT)); + EntityUtils.Operations.MULTIPLY_FLAT)); } } @@ -261,7 +264,7 @@ public class Possession extends SpellRay { // Targeting - for(EntityLiving creature : WizardryUtilities.getEntitiesWithinRadius(16, possessor.posX, + for(EntityLiving creature : EntityUtils.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 @@ -323,10 +326,12 @@ public class Possession extends SpellRay { victim.isDead = false; victim.setNoAI(false); + victim.getEntityData().removeTag(NBT_KEY); victim.setPosition(player.posX, player.posY, player.posZ); if(!player.world.isRemote) player.world.spawnEntity(victim); for(PotionEffect effect : player.getActivePotionEffects()){ + if(effect.getPotion() instanceof PotionSlowTime) continue; // Don't transfer slow time victim.addPotionEffect(effect); } } @@ -417,7 +422,7 @@ public class Possession extends SpellRay { /** 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 */ + * can pick up blocks, creepers explode, etc. Other mods may use this method to add their own possession abilities. */ public static void addAbility(Class entityType, BiConsumer ability){ abilities.put(entityType, ability); } @@ -606,7 +611,7 @@ public class Possession extends SpellRay { 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())); + projectile.shoot(look.x, look.y, look.z, 1.6f, EntityUtils.getDefaultAimingError(possessor.world.getDifficulty())); if(projectile instanceof EntityMagicProjectile) ((EntityMagicProjectile)projectile).setCaster(possessor); else if(projectile instanceof EntityMagicArrow) ((EntityMagicArrow)projectile).setCaster(possessor); diff --git a/src/main/java/electroblob/wizardry/spell/RadiantTotem.java b/src/main/java/electroblob/wizardry/spell/RadiantTotem.java new file mode 100644 index 00000000..c78bef4f --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/RadiantTotem.java @@ -0,0 +1,24 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.entity.construct.EntityRadiantTotem; +import electroblob.wizardry.util.SpellModifiers; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.util.EnumFacing; + +import javax.annotation.Nullable; + +public class RadiantTotem extends SpellConstructRanged { + + public static final String MAX_TARGETS = "max_targets"; + + public RadiantTotem(){ + super("radiant_totem", EntityRadiantTotem::new, false); + this.addProperties(EFFECT_RADIUS, MAX_TARGETS, HEALTH, DAMAGE); + this.floor(false); + } + + @Override + protected void addConstructExtras(EntityRadiantTotem construct, EnumFacing side, @Nullable EntityLivingBase caster, SpellModifiers modifiers){ + construct.posY += 1.2; + } +} diff --git a/src/main/java/electroblob/wizardry/spell/RayOfPurification.java b/src/main/java/electroblob/wizardry/spell/RayOfPurification.java index 4367fd39..07c0c3ce 100644 --- a/src/main/java/electroblob/wizardry/spell/RayOfPurification.java +++ b/src/main/java/electroblob/wizardry/spell/RayOfPurification.java @@ -1,17 +1,18 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.EntityUtils; 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; @@ -27,8 +28,8 @@ public class RayOfPurification extends SpellRay { 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); + super("ray_of_purification", SpellActions.POINT, true); + addProperties(DAMAGE, EFFECT_DURATION, UNDEAD_DAMAGE_MULTIPLIER); } // The following three methods serve as a good example of how to implement continuous spell sounds (hint: it's easy) @@ -51,7 +52,7 @@ public class RayOfPurification extends SpellRay { @Override protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ - if(WizardryUtilities.isLiving(target)){ + if(EntityUtils.isLiving(target)){ if(MagicDamage.isEntityImmune(DamageType.RADIANT, target)){ if(!world.isRemote && ticksInUse == 1 && caster instanceof EntityPlayer) ((EntityPlayer)caster) @@ -62,11 +63,10 @@ public class RayOfPurification extends SpellRay { 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, + EntityUtils.attackEntityWithoutKnockback(target, MagicDamage.causeDirectMagicDamage(caster, DamageType.RADIANT), damage); // Blindness ((EntityLivingBase)target).addPotionEffect(new PotionEffect(MobEffects.BLINDNESS, @@ -93,11 +93,11 @@ public class RayOfPurification extends SpellRay { 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); + .scale(MathHelper.sin(caster.ticksExisted * 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); + .scale(MathHelper.sin(Wizardry.proxy.getThePlayer().ticksExisted * 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 index 927d0b3a..7dd94829 100644 --- a/src/main/java/electroblob/wizardry/spell/RemoveCurse.java +++ b/src/main/java/electroblob/wizardry/spell/RemoveCurse.java @@ -8,6 +8,8 @@ import net.minecraft.entity.EntityLivingBase; import net.minecraft.potion.PotionEffect; import net.minecraft.world.World; +import java.util.ArrayList; + public class RemoveCurse extends SpellBuff { public RemoveCurse(){ @@ -22,7 +24,7 @@ public class RemoveCurse extends SpellBuff { boolean flag = false; - for(PotionEffect effect : caster.getActivePotionEffects()){ + for(PotionEffect effect : new ArrayList<>(caster.getActivePotionEffects())){ // Get outta here, CMEs // The PotionEffect version (as opposed to Potion) does not call cleanup callbacks if(effect.getPotion() instanceof Curse){ caster.removePotionEffect(effect.getPotion()); @@ -43,7 +45,7 @@ public class RemoveCurse extends SpellBuff { 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 y = caster.posY + 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); diff --git a/src/main/java/electroblob/wizardry/spell/Resurrection.java b/src/main/java/electroblob/wizardry/spell/Resurrection.java index a28d0fe9..f5511d56 100644 --- a/src/main/java/electroblob/wizardry/spell/Resurrection.java +++ b/src/main/java/electroblob/wizardry/spell/Resurrection.java @@ -2,6 +2,7 @@ package electroblob.wizardry.spell; import electroblob.wizardry.data.WizardData; import electroblob.wizardry.item.ISpellCastingItem; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.packet.PacketResurrection; import electroblob.wizardry.packet.WizardryPacketHandler; import electroblob.wizardry.registry.Spells; @@ -10,7 +11,6 @@ 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; @@ -28,7 +28,7 @@ public class Resurrection extends Spell { private static final String POTION_CORE_FIX_NBT_KEY = "Potion Core - Health Fix"; public Resurrection(){ - super("resurrection", EnumAction.NONE, false); + super("resurrection", SpellActions.SUMMON, false); addProperties(EFFECT_RADIUS, WAIT_TIME); } diff --git a/src/main/java/electroblob/wizardry/spell/Reversal.java b/src/main/java/electroblob/wizardry/spell/Reversal.java index c763fe91..8ea9e5d6 100644 --- a/src/main/java/electroblob/wizardry/spell/Reversal.java +++ b/src/main/java/electroblob/wizardry/spell/Reversal.java @@ -1,12 +1,12 @@ package electroblob.wizardry.spell; import electroblob.wizardry.constants.Constants; +import electroblob.wizardry.item.SpellActions; 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.tileentity.TileEntityDispenser; import net.minecraft.util.EnumFacing; @@ -24,7 +24,7 @@ public class Reversal extends SpellRay { public static final String REVERSED_EFFECTS = "reversed_effects"; public Reversal(){ - super("reversal", false, EnumAction.NONE); + super("reversal", SpellActions.POINT, false); addProperties(REVERSED_EFFECTS); } diff --git a/src/main/java/electroblob/wizardry/spell/ShadowWard.java b/src/main/java/electroblob/wizardry/spell/ShadowWard.java index 60e42bf2..7037e1b3 100644 --- a/src/main/java/electroblob/wizardry/spell/ShadowWard.java +++ b/src/main/java/electroblob/wizardry/spell/ShadowWard.java @@ -2,11 +2,11 @@ package electroblob.wizardry.spell; import electroblob.wizardry.integration.DamageSafetyChecker; import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.util.EntityUtils; 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.WizardryUtilities; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.EnumAction; @@ -53,7 +53,7 @@ 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, caster.getEntityBoundingBox().minY + caster.getEyeHeight(), caster.posZ, dx, dy, dz); + world.spawnParticle(EnumParticleTypes.PORTAL, caster.posX, caster.posY + caster.getEyeHeight(), caster.posZ, dx, dy, dz); } if(ticksInUse % 50 == 0){ @@ -68,7 +68,7 @@ public class ShadowWard extends Spell { if(event.getSource() != null && event.getSource().getTrueSource() instanceof EntityLivingBase){ - if(WizardryUtilities.isCasting(event.getEntityLiving(), Spells.shadow_ward) && !event.getSource().isUnblockable() + if(EntityUtils.isCasting(event.getEntityLiving(), Spells.shadow_ward) && !event.getSource().isUnblockable() && !(event.getSource() instanceof IElementalDamage && ((IElementalDamage)event.getSource()).isRetaliatory())){ event.setCanceled(true); diff --git a/src/main/java/electroblob/wizardry/spell/Shockwave.java b/src/main/java/electroblob/wizardry/spell/Shockwave.java index 2d3d89c7..e710351f 100644 --- a/src/main/java/electroblob/wizardry/spell/Shockwave.java +++ b/src/main/java/electroblob/wizardry/spell/Shockwave.java @@ -2,131 +2,115 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.*; +import electroblob.wizardry.util.EntityUtils; +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 net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; 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.EnumParticleTypes; +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.List; +import javax.annotation.Nullable; -public class Shockwave extends Spell { +public class Shockwave extends SpellAreaEffect { 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("shockwave", EnumAction.BOW, false); + super("shockwave", SpellActions.POINT_DOWN, false); this.soundValues(2, 0.5f, 0); - addProperties(BLAST_RADIUS, DAMAGE, MAX_REPULSION_VELOCITY); + this.alwaysSucceed(true); + addProperties(DAMAGE, MAX_REPULSION_VELOCITY); } @Override - public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + protected boolean affectEntity(World world, Vec3d origin, @Nullable EntityLivingBase caster, EntityLivingBase target, int targetCount, int ticksInUse, SpellModifiers modifiers){ - double radius = getProperty(BLAST_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade); + float radius = getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade); - List targets = WizardryUtilities.getEntitiesWithinRadius(radius, caster.posX, caster.posY, caster.posZ, world); + if(target instanceof EntityPlayer){ - for(EntityLivingBase target : targets){ + if(!Wizardry.settings.playersMoveEachOther) return false; - if(target instanceof EntityPlayer){ - - Wizardry.proxy.shakeScreen((EntityPlayer)target, 10); - - if(!Wizardry.settings.playersMoveEachOther) continue; - - if(ItemArtefact.isArtefactActive((EntityPlayer)target, WizardryItems.amulet_anchoring)){ - if(!world.isRemote) caster.sendStatusMessage(new TextComponentTranslation("spell.resist", - target.getName(), this.getNameForTranslationFormatted()), true); - continue; - } - } - - 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). - target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.BLAST), - 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 = proximity * getProperty(MAX_REPULSION_VELOCITY).floatValue(); - - double dx = target.posX - caster.posX; - double dy = target.getEntityBoundingBox().minY + 1 - caster.posY; - double dz = target.posZ - caster.posZ; - - target.motionX = velocityFactor * dx; - target.motionY = velocityFactor * dy; - target.motionZ = velocityFactor * dz; - - // Player motion is handled on that player's client so needs packets - if(target instanceof EntityPlayerMP){ - ((EntityPlayerMP)target).connection.sendPacket(new SPacketEntityVelocity(target)); - } - } + if(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(world.isRemote){ - double particleX, particleZ; - - for(int i = 0; i < 40; i++){ + // 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(origin.distanceTo(target.getPositionVector()) - EPICENTRE_RADIUS, 0))/(radius - EPICENTRE_RADIUS)); -// 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); + // Damage increases closer to player up to a maximum of 4 hearts (at 1 block distance). + target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.BLAST), + getProperty(DAMAGE).floatValue() * proximity * modifiers.get(SpellModifiers.POTENCY)); - particleX = caster.posX - 1.0d + 2 * world.rand.nextDouble(); - particleZ = caster.posZ - 1.0d + 2 * world.rand.nextDouble(); - IBlockState block = WizardryUtilities.getBlockEntityIsStandingOn(caster); + if(!world.isRemote){ - if(block != null){ - world.spawnParticle(EnumParticleTypes.BLOCK_DUST, particleX, caster.getEntityBoundingBox().minY, - particleZ, particleX - caster.posX, 0, particleZ - caster.posZ, Block.getStateId(block)); - } + // 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 = proximity * getProperty(MAX_REPULSION_VELOCITY).floatValue(); + + double dx = target.posX - origin.x; + double dy = target.posY + 1 - origin.y; + double dz = target.posZ - origin.z; + + target.motionX = velocityFactor * dx; + target.motionY = velocityFactor * dy; + target.motionZ = velocityFactor * dz; + + // Player motion is handled on that player's client so needs packets + if(target instanceof EntityPlayerMP){ + ((EntityPlayerMP)target).connection.sendPacket(new SPacketEntityVelocity(target)); } - - 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); - playSound(world, caster, ticksInUse, -1, modifiers); + return true; } + @Override + protected void spawnParticleEffect(World world, Vec3d origin, double radius, @Nullable EntityLivingBase caster, SpellModifiers modifiers){ + + // Can't put this in affectEntity(...) because it's only called for non-allies, plus here is client-side already + EntityUtils.getEntitiesWithinRadius(radius, origin.x, origin.y, origin.z, world, EntityPlayer.class) + .forEach(p -> Wizardry.proxy.shakeScreen(p, 10)); + + double particleX, particleZ; + + for(int i = 0; i < 40; i++){ + + particleX = origin.x - 1.0d + 2 * world.rand.nextDouble(); + particleZ = origin.z - 1.0d + 2 * world.rand.nextDouble(); + + IBlockState block = world.getBlockState(new BlockPos(origin.x, origin.y - 0.5, origin.z)); + + if(block != null){ + world.spawnParticle(EnumParticleTypes.BLOCK_DUST, particleX, origin.y, + particleZ, particleX - origin.x, 0, particleZ - origin.z, Block.getStateId(block)); + } + } + + ParticleBuilder.create(Type.SPHERE).pos(origin.add(0, 0.1, 0)).scale((float)radius * 0.8f).clr(0.8f, 0.9f, 1).spawn(world); + + world.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, origin.x, origin.y + 0.1, origin.z, 0, 0, 0); + } + } diff --git a/src/main/java/electroblob/wizardry/spell/ShulkerBullet.java b/src/main/java/electroblob/wizardry/spell/ShulkerBullet.java index 7998a426..d2c57b88 100644 --- a/src/main/java/electroblob/wizardry/spell/ShulkerBullet.java +++ b/src/main/java/electroblob/wizardry/spell/ShulkerBullet.java @@ -1,16 +1,16 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.EntityUtils; import electroblob.wizardry.util.NBTExtras; 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.tileentity.TileEntityDispenser; @@ -26,7 +26,7 @@ import java.util.List; public class ShulkerBullet extends Spell { public ShulkerBullet(){ - super("shulker_bullet", EnumAction.NONE, false); + super("shulker_bullet", SpellActions.POINT_DOWN, false); this.soundValues(2, 1, 0.3f); addProperties(RANGE); } @@ -62,7 +62,7 @@ public class ShulkerBullet extends Spell { double range = getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade); - List possibleTargets = WizardryUtilities.getEntitiesWithinRadius(range, x, y, z, world); + List possibleTargets = EntityUtils.getLivingWithinRadius(range, x, y, z, world); possibleTargets.remove(caster); possibleTargets.removeIf(t -> t instanceof EntityArmorStand); @@ -78,7 +78,7 @@ public class ShulkerBullet extends Spell { 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 + // Can't use the normal constructor because it doesn't accept null for the owner EntityShulkerBullet bullet = new EntityShulkerBullet(world); bullet.setLocationAndAngles(x, y, z, bullet.rotationYaw, bullet.rotationPitch); @@ -94,6 +94,8 @@ public class ShulkerBullet extends Spell { NBTExtras.storeTagSafely(nbt, "Target", targetTag); bullet.readFromNBT(nbt); // LOL I just modified private fields without reflection + bullet.getEntityData().setFloat(SpellThrowable.DAMAGE_MODIFIER_NBT_KEY, modifiers.get(SpellModifiers.POTENCY)); + world.spawnEntity(bullet); } } diff --git a/src/main/java/electroblob/wizardry/spell/SixthSense.java b/src/main/java/electroblob/wizardry/spell/SixthSense.java index e8f5f926..46eae1ac 100644 --- a/src/main/java/electroblob/wizardry/spell/SixthSense.java +++ b/src/main/java/electroblob/wizardry/spell/SixthSense.java @@ -2,11 +2,11 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; import electroblob.wizardry.constants.Constants; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.util.SpellModifiers; 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.ResourceLocation; @@ -22,7 +22,7 @@ public class SixthSense extends Spell { public static final ResourceLocation SHADER = new ResourceLocation(Wizardry.MODID, "shaders/post/sixth_sense.json"); public SixthSense(){ - super("sixth_sense", EnumAction.BOW, false); + super("sixth_sense", SpellActions.POINT_UP, false); addProperties(EFFECT_DURATION, EFFECT_RADIUS); soundValues(1, 1.1f, 0.2f); } diff --git a/src/main/java/electroblob/wizardry/spell/Slime.java b/src/main/java/electroblob/wizardry/spell/Slime.java index 14bbfaf0..964afde3 100644 --- a/src/main/java/electroblob/wizardry/spell/Slime.java +++ b/src/main/java/electroblob/wizardry/spell/Slime.java @@ -1,18 +1,19 @@ package electroblob.wizardry.spell; import electroblob.wizardry.entity.living.EntityMagicSlime; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.EntityUtils; 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.monster.EntitySlime; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.EnumAction; import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumParticleTypes; +import net.minecraft.util.SoundEvent; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; import net.minecraft.util.text.TextComponentTranslation; @@ -21,14 +22,19 @@ import net.minecraft.world.World; public class Slime extends SpellRay { public Slime(){ - super("slime", false, EnumAction.NONE); + super("slime", SpellActions.POINT, false); addProperties(DURATION); } + @Override + protected SoundEvent[] createSounds(){ + return this.createSoundsWithSuffixes("shoot", "squelch"); + } + @Override 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(EntityUtils.isLiving(target) && !(target instanceof EntityMagicSlime)){ if(target instanceof EntitySlime){ if(!world.isRemote && caster instanceof EntityPlayer) ((EntityPlayer)caster).sendStatusMessage( diff --git a/src/main/java/electroblob/wizardry/spell/Snare.java b/src/main/java/electroblob/wizardry/spell/Snare.java index 4b70e60d..19beca2f 100644 --- a/src/main/java/electroblob/wizardry/spell/Snare.java +++ b/src/main/java/electroblob/wizardry/spell/Snare.java @@ -1,14 +1,14 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.tileentity.TileEntityPlayerSave; +import electroblob.wizardry.util.BlockUtils; 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; @@ -17,7 +17,7 @@ import net.minecraft.world.World; public class Snare extends SpellRay { public Snare(){ - super("snare", false, EnumAction.NONE); + super("snare", SpellActions.POINT, false); this.soundValues(1, 1.4f, 0.4f); this.ignoreLivingEntities(true); addProperties(DAMAGE, EFFECT_DURATION, EFFECT_STRENGTH); @@ -31,14 +31,12 @@ public class Snare extends SpellRay { @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(side == EnumFacing.UP && world.isSideSolid(pos, EnumFacing.UP) && BlockUtils.canBlockBeReplaced(world, pos.up())){ if(!world.isRemote){ world.setBlockState(pos.up(), WizardryBlocks.snare.getDefaultState()); ((TileEntityPlayerSave)world.getTileEntity(pos.up())).setCaster(caster); + ((TileEntityPlayerSave)world.getTileEntity(pos.up())).sync(); } - return true; } diff --git a/src/main/java/electroblob/wizardry/spell/SpectralPathway.java b/src/main/java/electroblob/wizardry/spell/SpectralPathway.java index adc6c73d..e09d6068 100644 --- a/src/main/java/electroblob/wizardry/spell/SpectralPathway.java +++ b/src/main/java/electroblob/wizardry/spell/SpectralPathway.java @@ -1,13 +1,13 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.tileentity.TileEntityTimer; +import electroblob.wizardry.util.BlockUtils; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; 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.EnumFacing.AxisDirection; import net.minecraft.util.EnumHand; @@ -20,7 +20,7 @@ public class SpectralPathway extends Spell { public static final String LENGTH = "length"; public SpectralPathway(){ - super("spectral_pathway", EnumAction.BOW, false); + super("spectral_pathway", SpellActions.POINT, false); addProperties(LENGTH, DURATION); } @@ -33,8 +33,8 @@ public class SpectralPathway extends Spell { public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ // Won't work if caster is airborne or if they are already on a bridge (prevents infinite bridges) - if(WizardryUtilities.getBlockEntityIsStandingOn(caster).getBlock() == Blocks.AIR - || WizardryUtilities.getBlockEntityIsStandingOn(caster).getBlock() == WizardryBlocks.spectral_block){ + if(BlockUtils.getBlockEntityIsStandingOn(caster).getBlock() == Blocks.AIR + || BlockUtils.getBlockEntityIsStandingOn(caster).getBlock() == WizardryBlocks.spectral_block){ return false; } @@ -46,7 +46,7 @@ public class SpectralPathway extends Spell { // 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, + BlockPos origin = new BlockPos(Math.round(caster.posX), (int)caster.posY - 1, Math.round(caster.posZ)); int startPoint = direction.getAxisDirection() == AxisDirection.POSITIVE ? -1 : 0; @@ -68,7 +68,7 @@ public class SpectralPathway extends Spell { } private boolean placePathwayBlockIfPossible(World world, BlockPos pos, float durationMultiplier){ - if(WizardryUtilities.canBlockBeReplaced(world, pos, true)){ + if(BlockUtils.canBlockBeReplaced(world, pos, true)){ world.setBlockState(pos, WizardryBlocks.spectral_block.getDefaultState()); if(world.getTileEntity(pos) instanceof TileEntityTimer){ ((TileEntityTimer)world.getTileEntity(pos)).setLifetime((int)(getProperty(DURATION).floatValue() * durationMultiplier)); diff --git a/src/main/java/electroblob/wizardry/spell/SpeedTime.java b/src/main/java/electroblob/wizardry/spell/SpeedTime.java index 4692b176..3215c544 100644 --- a/src/main/java/electroblob/wizardry/spell/SpeedTime.java +++ b/src/main/java/electroblob/wizardry/spell/SpeedTime.java @@ -1,14 +1,14 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.BlockUtils; 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; @@ -19,11 +19,6 @@ 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. */ @@ -32,7 +27,7 @@ public class SpeedTime extends Spell { public static final String EXTRA_TICKS = "extra_ticks"; public SpeedTime(){ - super("speed_time", EnumAction.BOW, true); + super("speed_time", SpellActions.POINT_UP, true); addProperties(EFFECT_RADIUS, TIME_INCREMENT, EXTRA_TICKS); } @@ -93,7 +88,7 @@ public class SpeedTime extends Spell { if(!world.isRemote){ - List sphere = WizardryUtilities.getBlockSphere(caster.getPosition(), radius); + List sphere = BlockUtils.getBlockSphere(caster.getPosition(), radius); for(BlockPos pos : sphere){ @@ -113,7 +108,7 @@ public class SpeedTime extends Spell { double particleSpread = 2; double x = caster.posX + 2; - double y = caster.getEntityBoundingBox().minY + caster.height / 2; + double y = caster.posY + caster.height / 2; double z = caster.posZ; ParticleBuilder.create(ParticleBuilder.Type.SPARKLE, world.rand, x, y, z, particleSpread, false) diff --git a/src/main/java/electroblob/wizardry/spell/Spell.java b/src/main/java/electroblob/wizardry/spell/Spell.java index 863bd31c..65962b30 100644 --- a/src/main/java/electroblob/wizardry/spell/Spell.java +++ b/src/main/java/electroblob/wizardry/spell/Spell.java @@ -34,8 +34,6 @@ import net.minecraftforge.event.world.WorldEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.network.FMLNetworkEvent; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; import net.minecraftforge.registries.ForgeRegistry; import net.minecraftforge.registries.IForgeRegistry; import net.minecraftforge.registries.IForgeRegistryEntry; @@ -117,6 +115,22 @@ public abstract class Spell extends IForgeRegistryEntry.Impl implements C public static final String SPLASH_EFFECT_DURATION = "splash_effect_duration"; public static final String SPLASH_EFFECT_STRENGTH = "splash_effect_strength"; + public static final String TIER_MATCH_PREFIX = "tier"; + public static final String ELEMENT_MATCH_PREFIX = "element"; + public static final String TYPE_MATCH_PREFIX = "type"; + public static final String DISCOVERED_MATCH_PREFIX = "discovered"; + public static final String MODID_MATCH_PREFIX = "modid"; + + public static final String TIER_MATCH_ALIAS = "t"; + public static final String ELEMENT_MATCH_ALIAS = "e"; + public static final String TYPE_MATCH_ALIAS = "p"; + public static final String DISCOVERED_MATCH_ALIAS = "d"; + public static final String MODID_MATCH_ALIAS = "m"; + + public static final String MATCH_CONDITION_SEPARATOR = ";"; + public static final String MATCH_KEY_VALUE_SEPARATOR = "="; + public static final String MATCH_VALUE_SEPARATOR = ","; + /** Forge registry-based replacement for the internal spells list. */ public static IForgeRegistry registry; @@ -176,8 +190,9 @@ public abstract class Spell extends IForgeRegistryEntry.Impl implements C * 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. * @param isContinuous Whether this spell is continuous, meaning you cast it for a length of time by holding the + * use item button. */ - public Spell(String name, EnumAction action, boolean isContinuous){ + Spell(String name, EnumAction action, boolean isContinuous){ this(Wizardry.MODID, name, action, isContinuous); } @@ -190,6 +205,7 @@ public abstract class Spell extends IForgeRegistryEntry.Impl implements C * 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 + * use item button. */ public Spell(String modID, String name, EnumAction action, boolean isContinuous){ this.setRegistryName(modID, name); @@ -200,7 +216,7 @@ public abstract class Spell extends IForgeRegistryEntry.Impl implements C this.sounds = createSounds(); this.id = nextSpellId++; this.items(WizardryItems.spell_book, WizardryItems.scroll); - this.npcSelector((e, o) -> canBeCastByNPCs()); // Fallback to old behaviour until we remove it entirely + this.npcSelector((e, o) -> false); } // ========================================= Initialisation methods =========================================== @@ -289,7 +305,7 @@ public abstract class Spell extends IForgeRegistryEntry.Impl implements C .map(s -> s.properties).toArray(SpellProperties[]::new))); }else{ // On the client side, wipe the spell properties so the new ones can be set - // TESTME: Can we guarantee this happens before the packet arrives? + // Not sure if we can guarantee this happens before the packet arrives, but it hasn't caused any problems yet! clearProperties(); } } @@ -477,17 +493,6 @@ public abstract class Spell extends IForgeRegistryEntry.Impl implements C return npcSelector.test(npc, override); } - /** - * 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 - * this to return true. - * @deprecated Use the entity-sensitive version {@link Spell#canBeCastBy(EntityLiving, boolean)}. - */ - @Deprecated - public boolean canBeCastByNPCs(){ - return false; - } - /** * Whether the given dispenser can cast this spell. If you have overridden * {@link Spell#cast(World, double, double, double, EnumFacing, int, int, SpellModifiers)}, you should override this @@ -495,17 +500,6 @@ public abstract class Spell extends IForgeRegistryEntry.Impl implements C * @param dispenser The dispenser to query. */ public boolean canBeCastBy(TileEntityDispenser dispenser){ - return canBeCastByDispensers(); - } - - /** - * 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. - * @deprecated Use the tileentity-sensitive version {@link Spell#canBeCastBy(TileEntityDispenser)}. - */ - @Deprecated - public boolean canBeCastByDispensers(){ return false; } @@ -541,6 +535,9 @@ public abstract class Spell extends IForgeRegistryEntry.Impl implements C return ((ForgeRegistry)registry).getID(this); } + // N.B. You don't *have* to use the network ID for networking, metadata is consistent within a world and will work + // fine, *however* when syncing a list/array of spells it is much more convenient to use network IDs. + /** 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. @@ -592,23 +589,34 @@ public abstract class Spell extends IForgeRegistryEntry.Impl implements C return properties.cooldown; } + /** + * Returns whether a property has been defined for the given identifier. + * @param identifier The JSON identifier to check for. + * @return True if a property has been defined for the given identifier, false if not. + */ + public final boolean hasProperty(String identifier){ + return properties.hasBaseValue(identifier); + } + /** * 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. + * {@link Spell#addProperties(String...)} or an exception will be thrown. To check if an + * identifier exists, use {@link Spell#hasProperty(String)}. * @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. */ + * @throws IllegalArgumentException if no property was defined with the given identifier. + */ public final Number getProperty(String identifier){ return properties.getBaseValue(identifier); } /** - * Returns the unlocalised name of the spell, without any prefixes or suffixes, e.g. "flame_ray". This should - * only be used for translation purposes. + * Returns the unlocalised name of the spell, which is now its registry name as a string, e.g. "ebwizardry:flame_ray". + * This should only be used for translation purposes. */ public final String getUnlocalisedName(){ return unlocalisedName; @@ -636,9 +644,8 @@ public abstract class Spell extends IForgeRegistryEntry.Impl implements C * Returns the translated display name of the spell, without formatting (i.e. not coloured). Client-side * only! On the server side, use {@link TextComponentTranslation} (see {@link Spell#getNameForTranslation()}). */ - @SideOnly(Side.CLIENT) public String getDisplayName(){ - return net.minecraft.client.resources.I18n.format(getTranslationKey()); + return Wizardry.proxy.translate(getTranslationKey()); } /** @@ -653,9 +660,8 @@ public abstract class Spell extends IForgeRegistryEntry.Impl implements C * Returns the translated display name of the spell, with formatting (i.e. coloured). Client-side only! On * the server side, use {@link TextComponentTranslation} (see {@link Spell#getNameForTranslationFormatted()}). */ - @SideOnly(Side.CLIENT) public String getDisplayNameWithFormatting(){ - return this.getElement().getFormattingCode() + net.minecraft.client.resources.I18n.format(getTranslationKey()); + return Wizardry.proxy.translate(getTranslationKey(), getElement().getColour()); } /** @@ -670,9 +676,92 @@ public abstract class Spell extends IForgeRegistryEntry.Impl implements C * Returns the translated description of the spell, without formatting. Client-side only! You should not need * to use this on the server side. */ - @SideOnly(Side.CLIENT) public String getDescription(){ - return net.minecraft.client.resources.I18n.format(getDescriptionTranslationKey()); + return Wizardry.proxy.translate(getDescriptionTranslationKey()); + } + + /** + * Returns whether this spell matches the given string. Client-side only! Matches are determined as follows: + *

+ * First, attempt to parse the string as one or more conditions separated by semicolons ({@code ;}). Each + * condition has the syntax {@code key=value1,value2,value3,...}, where {@code key} is one of several predefined + * strings (or aliases) pointing to a particular property of the spell, and each {@code value} is a string to match + * to that property. Valid keys are: + *

+ * - {@code tier} (alias {@code t}), specifies the tiers that the spell can match
+ * - {@code element} (alias {@code e}), specifies the elements that the spell can match
+ * - {@code type} (alias {@code p}), specifies the spell types that the spell can match
+ * - {@code modid} (alias {@code m}), specifies the mod IDs that the spell can be from
+ * - {@code discovered} (alias {@code d}), specifies the discovery status ({@code true} or {@code false}) that the + * spell can match
+ *

+ * The spell must match all conditions to count as a match, but only needs to match one of the values for each + * condition. + *

+ * If, at any point, a condition is not of valid syntax (or if the string does not contain {@code ;} or {@code =}), + * the input is instead treated as verbatim, and the spell matches if it has been discovered and its localised name + * contains the given string. + *

+ * Keys are case-sensitive, but everything else is not. + * @param text The string to tested + * @return True if this spell matches the given string, false otherwise. + */ + public boolean matches(@Nonnull String text){ + + if(text.isEmpty()) return true; + + boolean discovered = Wizardry.proxy.shouldDisplayDiscovered(this, null); + + String[] conditions = text.split(MATCH_CONDITION_SEPARATOR); + + for(String condition : conditions){ + + String[] args = condition.split(MATCH_KEY_VALUE_SEPARATOR, 2); + + // Invalid condition, treat the whole lot as a spell name instead + if(args.length < 2) return discovered && getDisplayName().toLowerCase(Locale.ROOT).contains(text); + + String key = args[0]; + String[] values = args[1].split(MATCH_VALUE_SEPARATOR); + + String target; + + switch(key){ + case TIER_MATCH_PREFIX: + case TIER_MATCH_ALIAS: + // Tier IS known for undiscovered spells so we can match it + target = getTier().getDisplayName().toLowerCase(Locale.ROOT); + break; + case ELEMENT_MATCH_PREFIX: + case ELEMENT_MATCH_ALIAS: + if(!discovered) return false; // Element is unknown so doesn't match + target = getElement().getDisplayName().toLowerCase(Locale.ROOT); + break; + case TYPE_MATCH_PREFIX: + case TYPE_MATCH_ALIAS: + if(!discovered) return false; // Type is unknown so doesn't match + target = getType().getDisplayName().toLowerCase(Locale.ROOT); + break; + case MODID_MATCH_PREFIX: + case MODID_MATCH_ALIAS: + if(!discovered) return false; // Mod ID is unknown so doesn't match + target = getRegistryName().getNamespace().toLowerCase(Locale.ROOT); + break; + case DISCOVERED_MATCH_PREFIX: + case DISCOVERED_MATCH_ALIAS: + target = Boolean.toString(discovered); + break; + default: + // Invalid condition, treat the whole lot as a spell name instead + return discovered && getDisplayName().toLowerCase(Locale.ROOT).contains(text); + } + + if(Arrays.stream(values).noneMatch(target::contains)) return false; // Didn't match + + } + + return true; // Matched all the conditions, yay! + } // ============================================ Sound methods ============================================== @@ -870,7 +959,7 @@ public abstract class Spell extends IForgeRegistryEntry.Impl implements C return spell == null ? Spells.none : spell; } - /** Gets a spell instance from its network ID. Or the {@link None} spell if no such spell exists. */ + /** 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; @@ -916,26 +1005,11 @@ public abstract class Spell extends IForgeRegistryEntry.Impl implements C return registry.getValuesCollection().stream().filter(filter.and(s -> s != Spells.none)).collect(Collectors.toList()); } - /** Returns all registered spells, except the {@link None} spell. */ + /** Returns all registered spells, excluding the {@link None} spell. */ public static List getAllSpells(){ return getSpells(s -> true); } - /** Predicate which allows all spells. - * @deprecated Use {@link Spell#getAllSpells()}. */ - @Deprecated - public static Predicate allSpells = s -> true; - - /** Predicate which allows all non-continuous spells, even those that have been disabled. - * @deprecated Nobody ever uses this now we have continuous scrolls, if you really need it just use a lambda. */ - @Deprecated - public static Predicate nonContinuousSpells = s -> !s.isContinuous; - - /** Predicate which allows all enabled spells for which {@link Spell#canBeCastBy(EntityLiving, boolean)} returns true. - * @deprecated in favour of entity-sensitive version, use a lambda expression directly. */ - @Deprecated - 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 * time you want to use it). This is somewhat useless now that Wizardry uses Java 8, but it is more readable than a diff --git a/src/main/java/electroblob/wizardry/spell/SpellAreaEffect.java b/src/main/java/electroblob/wizardry/spell/SpellAreaEffect.java index f310ee2f..deced663 100644 --- a/src/main/java/electroblob/wizardry/spell/SpellAreaEffect.java +++ b/src/main/java/electroblob/wizardry/spell/SpellAreaEffect.java @@ -3,35 +3,90 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.util.AllyDesignationSystem; +import electroblob.wizardry.util.EntityUtils; 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.TileEntityDispenser; +import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumHand; import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; +import javax.annotation.Nullable; +import java.util.Comparator; import java.util.List; -/** [NYI] */ +/** + * Generic superclass for all spells which affect entities in a radius around the caster. 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 spells of this kind 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 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 this method, be sure to check whether the caster is {@code null} + * and deal with it appropriately. + *

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

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

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

+ * By default, this type of spell requires a packet to be sent. {@link Spell#requiresPacket()} + * + * @author Electroblob + * @since Wizardry 4.3 + */ 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. + /** True if this spell should target allies of the caster instead of hostiles. Positional casting always targets + * everything, regardless of this setting. */ + protected boolean targetAllies = false; + /** True if this spell should succeed even if no entities are affected, false if at least one entity must be affected. */ + protected boolean alwaysSucceed = false; /** 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 name, EnumAction action, boolean continuous){ + this(Wizardry.MODID, name, action, continuous); } - public SpellAreaEffect(String modID, String name, EnumAction action){ - super(modID, name, action, false); + public SpellAreaEffect(String modID, String name, EnumAction action, boolean continuous){ + super(modID, name, action, continuous); this.addProperties(EFFECT_RADIUS); + this.npcSelector((e, o) -> true); } + /** + * Sets whether this spell should target allies of the caster instead of hostiles. Positional casting always targets + * everything, regardless of this setting. + * @param targetAllies True to call {@link SpellAreaEffect#affectEntity(World, Vec3d, EntityLivingBase, EntityLivingBase, int, int, SpellModifiers)} + * on allies of the caster, false to call it on entities considered hostile to the caster (not + * necessarily collectively-exhaustive; some entities may belong to neither category). + * @return The spell instance, allowing this method to be chained onto the constructor. + */ + public SpellAreaEffect targetAllies(boolean targetAllies) { + this.targetAllies = targetAllies; + return this; + } + + /** + * Sets whether this spell this spell should succeed even if no entities are affected. + * @param alwaysSucceed True if this spell should succeed even if no entities are affected, false if + * {@link SpellAreaEffect#affectEntity(World, Vec3d, EntityLivingBase, EntityLivingBase, int, int, SpellModifiers)} + * must return true at least once for the spell to succeed + * @return The spell instance, allowing this method to be chained onto the constructor. + */ + public SpellAreaEffect alwaysSucceed(boolean alwaysSucceed) { + this.alwaysSucceed = alwaysSucceed; + return this; + } + /** * 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. @@ -43,56 +98,92 @@ public abstract class SpellAreaEffect extends Spell { } @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); + public boolean canBeCastBy(TileEntityDispenser dispenser){ return true; - } - + + @Override + public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + boolean result = findAndAffectEntities(world, caster.getPositionVector(), + caster, ticksInUse, modifiers); + if(result) this.playSound(world, caster, ticksInUse, -1, modifiers); + return result; + } + + @Override + public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, SpellModifiers modifiers){ + boolean result = findAndAffectEntities(world, caster.getPositionVector(), caster, ticksInUse, modifiers); + if(result) this.playSound(world, caster, ticksInUse, -1, modifiers); + return result; + } + + @Override + public boolean cast(World world, double x, double y, double z, EnumFacing direction, int ticksInUse, int duration, SpellModifiers modifiers){ + boolean result = findAndAffectEntities(world, new Vec3d(x, y, z), null, ticksInUse, modifiers); + if(result) this.playSound(world, x, y, z, ticksInUse, -1, modifiers); + return result; + } + + /** Takes care of the shared stuff for the three casting methods. This is mainly for internal use. */ + protected boolean findAndAffectEntities(World world, Vec3d origin, @Nullable EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){ + + double radius = getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade); + List targets = EntityUtils.getLivingWithinRadius(radius, origin.x, origin.y, origin.z, world); + + if(targetAllies){ + targets.removeIf(target -> target != caster && !AllyDesignationSystem.isAllied(caster, target)); + }else{ + targets.removeIf(target -> !AllyDesignationSystem.isValidTarget(caster, target)); + } + + // Sort by distance from the origin for consistency in ordering for spells with a limit + targets.sort(Comparator.comparingDouble(e -> e.getDistanceSq(origin.x, origin.y, origin.z))); + + boolean result = alwaysSucceed; + int i = 0; + + for(EntityLivingBase target : targets){ + if(affectEntity(world, origin, caster, target, i++, ticksInUse, modifiers)) result = true; + } + + if(world.isRemote) spawnParticleEffect(world, origin, radius, caster, modifiers); + return result; + } + /** * 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 origin The position the spell was cast from. + * @param caster The entity that cast the spell, or null if it was cast from a position. * @param target The entity to do something to. + * @param targetCount The number of targets that have already been affected, useful for spells with a target limit. + * Targets will be called in order of distance from the caster/origin, + * @param ticksInUse The number of ticks the spell has already been cast for. * @param modifiers The modifiers the spell was cast with. + * @return True if whatever was done to the entity was successful, false if not. */ - protected abstract void affectEntity(World world, EntityLivingBase caster, EntityLivingBase target, SpellModifiers modifiers); + protected abstract boolean affectEntity(World world, Vec3d origin, @Nullable EntityLivingBase caster, EntityLivingBase target, int targetCount, int ticksInUse, 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 origin The position the spell was cast from. + * @param radius The radius around the origin that was affected by this spell. + * @param caster The entity that cast the spell, or null if it was cast from a position. * @param modifiers The modifiers the spell was cast with. - */ - protected void spawnParticleEffect(World world, EntityLivingBase caster, SpellModifiers modifiers){ + */ + protected void spawnParticleEffect(World world, Vec3d origin, double radius, @Nullable 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); + int particleCount = (int)Math.round(particleDensity * Math.PI * radius * radius); for(int i=0; i extends Spell { } public SpellArrow(String modID, String name, Function arrowFactory){ - super(modID, name, EnumAction.NONE, false); + super(modID, name, SpellActions.POINT, false); this.arrowFactory = arrowFactory; this.addProperties(RANGE); this.npcSelector((e, o) -> true); @@ -101,8 +101,6 @@ public class SpellArrow extends Spell { // Spawns the projectile in the world world.spawnEntity(projectile); } - - caster.swingArm(hand); this.playSound(world, caster, ticksInUse, -1, modifiers); @@ -119,7 +117,7 @@ public class SpellArrow extends Spell { T projectile = arrowFactory.apply(world); // Sets the necessary parameters int aimingError = caster instanceof ISpellCaster ? ((ISpellCaster)caster).getAimingError(world.getDifficulty()) - : WizardryUtilities.getDefaultAimingError(world.getDifficulty()); + : EntityUtils.getDefaultAimingError(world.getDifficulty()); projectile.aim(caster, target, calculateVelocity(projectile, modifiers, caster.getEyeHeight() - (float)EntityMagicProjectile.LAUNCH_Y_OFFSET), aimingError); projectile.damageMultiplier = modifiers.get(SpellModifiers.POTENCY); diff --git a/src/main/java/electroblob/wizardry/spell/SpellBuff.java b/src/main/java/electroblob/wizardry/spell/SpellBuff.java index 2e3faec2..30fb464e 100644 --- a/src/main/java/electroblob/wizardry/spell/SpellBuff.java +++ b/src/main/java/electroblob/wizardry/spell/SpellBuff.java @@ -1,6 +1,7 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.util.ParticleBuilder; import electroblob.wizardry.util.ParticleBuilder.Type; @@ -8,7 +9,6 @@ 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.tileentity.TileEntityDispenser; @@ -19,6 +19,7 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Set; import java.util.function.Supplier; @@ -63,7 +64,7 @@ public class SpellBuff extends Spell { @SafeVarargs public SpellBuff(String modID, String name, float r, float g, float b, Supplier... effects){ - super(modID, name, EnumAction.BOW, false); + super(modID, name, SpellActions.POINT_UP, false); this.effects = effects; this.r = r; this.g = g; @@ -85,6 +86,11 @@ public class SpellBuff extends Spell { } } + /** Returns an unmodifiable view of the set of {@link Potion} objects that this spell applies to its caster. */ + public Set getPotionSet(){ + return Collections.unmodifiableSet(potionSet); + } + // Potion-specific equivalent to defining the identifiers as constants protected static String getDurationKey(Potion potion){ @@ -195,7 +201,7 @@ public class SpellBuff extends Spell { 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 y = caster.posY + 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); } diff --git a/src/main/java/electroblob/wizardry/spell/SpellConjuration.java b/src/main/java/electroblob/wizardry/spell/SpellConjuration.java index 1d86c527..91ab8b59 100644 --- a/src/main/java/electroblob/wizardry/spell/SpellConjuration.java +++ b/src/main/java/electroblob/wizardry/spell/SpellConjuration.java @@ -2,14 +2,14 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; import electroblob.wizardry.item.IConjuredItem; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.InventoryUtils; 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; @@ -46,7 +46,7 @@ public class SpellConjuration extends Spell { } public SpellConjuration(String modID, String name, Item item){ - super(modID, name, EnumAction.BOW, false); + super(modID, name, SpellActions.IMBUE, false); this.item = item; addProperties(ITEM_LIFETIME); } @@ -70,7 +70,7 @@ public class SpellConjuration extends Spell { 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 y = caster.posY + 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); } @@ -83,17 +83,29 @@ public class SpellConjuration extends Spell { ItemStack stack = new ItemStack(item); + if(InventoryUtils.doesPlayerHaveItem(caster, item)) return false; + IConjuredItem.setDurationMultiplier(stack, modifiers.get(WizardryItems.duration_upgrade)); IConjuredItem.setDamageMultiplier(stack, modifiers.get(SpellModifiers.POTENCY)); - - if(WizardryUtilities.doesPlayerHaveItem(caster, item)) return false; - + + addItemExtras(caster, stack, modifiers); + if(caster.getHeldItemMainhand().isEmpty()){ caster.setHeldItem(EnumHand.MAIN_HAND, stack); - return true; }else{ - return caster.inventory.addItemStackToInventory(stack); + if(!caster.inventory.addItemStackToInventory(stack)) return false; } + + return true; } + /** + * Called directly before the conjured item is added to the inventory to perform additional behaviour (such + * as NBT modification). Does nothing by default. + * @param caster The player that cast this spell + * @param stack The item stack being conjured + * @param modifiers The modifiers this spell was cast with + */ + protected void addItemExtras(EntityPlayer caster, ItemStack stack, SpellModifiers modifiers){} + } diff --git a/src/main/java/electroblob/wizardry/spell/SpellConstruct.java b/src/main/java/electroblob/wizardry/spell/SpellConstruct.java index 4ff0fed7..71123fb8 100644 --- a/src/main/java/electroblob/wizardry/spell/SpellConstruct.java +++ b/src/main/java/electroblob/wizardry/spell/SpellConstruct.java @@ -2,9 +2,10 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; import electroblob.wizardry.entity.construct.EntityMagicConstruct; +import electroblob.wizardry.entity.construct.EntityScaledConstruct; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.BlockUtils; 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; @@ -122,7 +123,7 @@ public class SpellConstruct extends Spell { Integer floor = (int)y; if(requiresFloor){ - floor = WizardryUtilities.getNearestFloor(world, new BlockPos(x, y, z), 1); + floor = BlockUtils.getNearestFloor(world, new BlockPos(x, y, z), 1); direction = EnumFacing.UP; } @@ -160,6 +161,7 @@ public class SpellConstruct extends Spell { construct.setCaster(caster); construct.lifetime = permanent ? -1 : (int)(getProperty(DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)); construct.damageMultiplier = modifiers.get(SpellModifiers.POTENCY); + if(construct instanceof EntityScaledConstruct) ((EntityScaledConstruct)construct).setSizeMultiplier(modifiers.get(WizardryItems.blast_upgrade)); 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 diff --git a/src/main/java/electroblob/wizardry/spell/SpellConstructRanged.java b/src/main/java/electroblob/wizardry/spell/SpellConstructRanged.java index b48a28ac..5422b364 100644 --- a/src/main/java/electroblob/wizardry/spell/SpellConstructRanged.java +++ b/src/main/java/electroblob/wizardry/spell/SpellConstructRanged.java @@ -2,14 +2,14 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; import electroblob.wizardry.entity.construct.EntityMagicConstruct; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.BlockUtils; 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.tileentity.TileEntityDispenser; import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumHand; @@ -53,7 +53,7 @@ public class SpellConstructRanged extends SpellC } public SpellConstructRanged(String modID, String name, Function constructFactory, boolean permanent){ - super(modID, name, EnumAction.NONE, constructFactory, permanent); + super(modID, name, SpellActions.POINT, constructFactory, permanent); this.addProperties(RANGE); this.npcSelector((e, o) -> true); } @@ -109,7 +109,7 @@ public class SpellConstructRanged extends SpellC Vec3d look = caster.getLookVec(); double x = caster.posX + look.x * range; - double y = caster.getEntityBoundingBox().minY + caster.getEyeHeight() + look.y * range; + double y = caster.posY + caster.getEyeHeight() + look.y * range; double z = caster.posZ + look.z * range; if(!spawnConstruct(world, x, y, z, null, caster, modifiers)) return false; @@ -118,8 +118,7 @@ public class SpellConstructRanged extends SpellC }else{ return false; } - - caster.swingArm(hand); + this.playSound(world, caster, ticksInUse, -1, modifiers); return true; } @@ -129,7 +128,8 @@ public class SpellConstructRanged extends SpellC SpellModifiers modifiers){ double range = getProperty(RANGE).doubleValue() * modifiers.get(WizardryItems.range_upgrade); - + Vec3d origin = caster.getPositionEyes(1); + if(target != null && caster.getDistance(target) <= range){ if(!world.isRemote){ @@ -138,12 +138,18 @@ public class SpellConstructRanged extends SpellC double y = target.posY; double z = target.posZ; + RayTraceResult hit = world.rayTraceBlocks(origin, new Vec3d(x, y, z), hitLiquids, ignoreUncollidables, false); + + if(hit != null && hit.typeOfHit == RayTraceResult.Type.BLOCK && !hit.getBlockPos().equals(new BlockPos(x, y, z))){ + return false; // Something was in the way + } + 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); + Integer floor = BlockUtils.getNearestFloor(world, new BlockPos(x, y, z), 3); if(floor == null) return false; y = floor; side = EnumFacing.UP; diff --git a/src/main/java/electroblob/wizardry/spell/SpellMinion.java b/src/main/java/electroblob/wizardry/spell/SpellMinion.java index 21dcf505..609326af 100644 --- a/src/main/java/electroblob/wizardry/spell/SpellMinion.java +++ b/src/main/java/electroblob/wizardry/spell/SpellMinion.java @@ -2,10 +2,11 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; import electroblob.wizardry.entity.living.ISummonedCreature; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.BlockUtils; +import electroblob.wizardry.util.EntityUtils; 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; @@ -13,7 +14,6 @@ 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.tileentity.TileEntityDispenser; import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumHand; @@ -52,7 +52,7 @@ public class SpellMinion extends Spe * 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"; + public static final String POTENCY_ATTRIBUTE_MODIFIER = "potency"; /** A factory that creates summoned creature entities. */ protected final Function minionFactory; @@ -64,7 +64,7 @@ public class SpellMinion extends Spe } public SpellMinion(String modID, String name, Function minionFactory){ - super(modID, name, EnumAction.BOW, false); + super(modID, name, SpellActions.SUMMON, false); this.minionFactory = minionFactory; addProperties(MINION_LIFETIME, MINION_COUNT, SUMMON_RADIUS); this.npcSelector((e, o) -> true); @@ -148,7 +148,7 @@ public class SpellMinion extends Spe int range = getProperty(SUMMON_RADIUS).intValue(); // Try and find a nearby floor space - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, range, range*2); + BlockPos pos = BlockUtils.findNearbyFloorSpace(caster, range, range*2); if(flying){ if(pos != null){ @@ -174,10 +174,10 @@ public class SpellMinion extends Spe minion.setLifetime((int)(getProperty(MINION_LIFETIME).floatValue() * modifiers.get(WizardryItems.duration_upgrade))); IAttributeInstance attribute = minion.getEntityAttribute(SharedMonsterAttributes.ATTACK_DAMAGE); if(attribute != null) attribute.applyModifier( // Apparently some things don't have an attack damage - new AttributeModifier(POTENCY_ATTRIBUTE_MODIFIER, modifiers.get(SpellModifiers.POTENCY) - 1, Operations.MULTIPLY_CUMULATIVE)); + new AttributeModifier(POTENCY_ATTRIBUTE_MODIFIER, modifiers.get(SpellModifiers.POTENCY) - 1, EntityUtils.Operations.MULTIPLY_CUMULATIVE)); // This is only used for artefacts, but it's a nice example of custom spell modifiers minion.getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).applyModifier( - new AttributeModifier(HEALTH_MODIFIER, modifiers.get(HEALTH_MODIFIER) - 1, Operations.MULTIPLY_CUMULATIVE)); + new AttributeModifier(HEALTH_MODIFIER, modifiers.get(HEALTH_MODIFIER) - 1, EntityUtils.Operations.MULTIPLY_CUMULATIVE)); minion.setHealth(minion.getMaxHealth()); // Need to set this because we may have just modified the value this.addMinionExtras(minion, pos, caster, modifiers, i); diff --git a/src/main/java/electroblob/wizardry/spell/SpellProjectile.java b/src/main/java/electroblob/wizardry/spell/SpellProjectile.java index 21624770..16d8402f 100644 --- a/src/main/java/electroblob/wizardry/spell/SpellProjectile.java +++ b/src/main/java/electroblob/wizardry/spell/SpellProjectile.java @@ -5,8 +5,8 @@ import electroblob.wizardry.entity.living.ISpellCaster; import electroblob.wizardry.entity.projectile.EntityBomb; import electroblob.wizardry.entity.projectile.EntityMagicProjectile; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.EntityUtils; 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; @@ -128,7 +128,7 @@ public class SpellProjectile extends Spell { T projectile = projectileFactory.apply(world); // Sets the necessary parameters int aimingError = caster instanceof ISpellCaster ? ((ISpellCaster)caster).getAimingError(world.getDifficulty()) - : WizardryUtilities.getDefaultAimingError(world.getDifficulty()); + : EntityUtils.getDefaultAimingError(world.getDifficulty()); projectile.aim(caster, target, calculateVelocity(projectile, modifiers, caster.getEyeHeight() - (float)EntityMagicProjectile.LAUNCH_Y_OFFSET), aimingError); projectile.damageMultiplier = modifiers.get(SpellModifiers.POTENCY); diff --git a/src/main/java/electroblob/wizardry/spell/SpellRay.java b/src/main/java/electroblob/wizardry/spell/SpellRay.java index 25271427..db02b2f0 100644 --- a/src/main/java/electroblob/wizardry/spell/SpellRay.java +++ b/src/main/java/electroblob/wizardry/spell/SpellRay.java @@ -2,9 +2,9 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.EntityUtils; 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; @@ -66,11 +66,11 @@ public abstract class SpellRay extends Spell { /** 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 name, EnumAction action, boolean isContinuous){ + this(Wizardry.MODID, name, action, isContinuous); } - public SpellRay(String modID, String name, boolean isContinuous, EnumAction action){ + public SpellRay(String modID, String name, EnumAction action, boolean isContinuous){ super(modID, name, action, isContinuous); this.addProperties(RANGE); this.npcSelector((e, o) -> true); @@ -160,8 +160,11 @@ public abstract class SpellRay extends Spell { 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); - + Vec3d origin = new Vec3d(caster.posX, caster.posY + caster.getEyeHeight() - Y_OFFSET, caster.posZ); + if(!this.isContinuous && world.isRemote && !Wizardry.proxy.isFirstPerson(caster)){ + origin = origin.add(look.scale(1.2)); + } + if(!shootSpell(world, origin, look, caster, ticksInUse, modifiers)) return false; if(casterSwingsArm(world, caster, hand, ticksInUse, modifiers)) caster.swingArm(hand); @@ -172,45 +175,29 @@ public abstract class SpellRay extends Spell { @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){ + Vec3d origin = new Vec3d(caster.posX, caster.posY + caster.getEyeHeight() - Y_OFFSET, caster.posZ); + Vec3d targetPos = 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; - } + if(!ignoreLivingEntities || !EntityUtils.isLiving(target)){ + targetPos = new Vec3d(target.posX, target.posY + target.height / 2, target.posZ); + + }else{ + + int x = MathHelper.floor(target.posX); + int y = (int)target.posY - 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)){ + targetPos = new Vec3d(x + 0.5, y + 1, z + 0.5); } } - - // Wizards don't miss... yet - if(!flag) return false; - - // Particle spawning - if(world.isRemote){ - spawnParticleRay(world, origin, direction.normalize(), caster, direction.length()); - } + + if(targetPos == null) return false; // If there was nothing to aim at (e.g. snare when the target is in the air) + + if(!shootSpell(world, origin, targetPos.subtract(origin).normalize(), caster, ticksInUse, modifiers)) return false; if(casterSwingsArm(world, caster, hand, ticksInUse, modifiers)) caster.swingArm(hand); this.playSound(world, caster, ticksInUse, -1, modifiers); @@ -251,7 +238,7 @@ public abstract class SpellRay extends Spell { /** * 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. + * returns true for non-continuous spells without an action. * * @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. @@ -263,18 +250,18 @@ public abstract class SpellRay extends Spell { * @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; + return !this.isContinuous && this.action == EnumAction.NONE; } - - /** 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){ + + /** Takes care of the shared stuff for the three casting methods. This is mainly for internal use. */ + protected boolean shootSpell(World world, Vec3d origin, Vec3d direction, @Nullable EntityLivingBase 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 + ignoreUncollidables, false, Entity.class, ignoreLivingEntities ? EntityUtils::isLiving : RayTracer.ignoreEntityFilter(caster)); boolean flag = false; @@ -283,6 +270,7 @@ public abstract class SpellRay extends Spell { // 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 + // FIXME: Some spells (e.g. lightning web) seem to not render when aimed at item frames 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); @@ -378,7 +366,7 @@ public abstract class SpellRay extends Spell { * @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){ + protected void spawnParticleRay(World world, Vec3d origin, Vec3d direction, @Nullable EntityLivingBase caster, double distance){ Vec3d velocity = direction.scale(particleVelocity); diff --git a/src/main/java/electroblob/wizardry/spell/SpellThrowable.java b/src/main/java/electroblob/wizardry/spell/SpellThrowable.java index d3145dd6..1cdb0bf6 100644 --- a/src/main/java/electroblob/wizardry/spell/SpellThrowable.java +++ b/src/main/java/electroblob/wizardry/spell/SpellThrowable.java @@ -2,9 +2,13 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; import electroblob.wizardry.entity.living.ISpellCaster; +import electroblob.wizardry.integration.DamageSafetyChecker; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.EntityUtils; +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.WizardryUtilities; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; @@ -12,9 +16,13 @@ import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.projectile.EntityThrowable; import net.minecraft.item.EnumAction; import net.minecraft.tileentity.TileEntityDispenser; +import net.minecraft.util.DamageSource; import net.minecraft.util.EnumHand; import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; +import net.minecraftforge.event.entity.living.LivingAttackEvent; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import java.util.function.BiFunction; @@ -39,8 +47,14 @@ import java.util.function.BiFunction; * @since Wizardry 4.2.8 */ // TODO: Use events to make these projectiles seek targets when the caster is wearing a ring of attraction (is this possible?) +@EventBusSubscriber public class SpellThrowable extends Spell { + /** NBT key for storing a damage modifier in an external entity (i.e. from vanilla or another mod). Entities with a + * float value stored under this key will have their damage dealt multiplied by that value. This is not just for + * projectiles; it will work for any entity that uses a damage source with itself as the immediate source. */ + public static final String DAMAGE_MODIFIER_NBT_KEY = Wizardry.MODID + "DamageModifier"; + private static final float LAUNCH_Y_OFFSET = 0.1f; protected final BiFunction projectileFactory; @@ -75,6 +89,7 @@ public class SpellThrowable extends Spell { float velocity = calculateVelocity(modifiers, caster.getEyeHeight() - LAUNCH_Y_OFFSET); T projectile = projectileFactory.apply(world, caster); projectile.shoot(caster, caster.rotationPitch, caster.rotationYaw, 0.0f, velocity, 1.0f); + projectile.getEntityData().setFloat(DAMAGE_MODIFIER_NBT_KEY, modifiers.get(SpellModifiers.POTENCY)); addProjectileExtras(projectile, caster, modifiers); world.spawnEntity(projectile); } @@ -93,7 +108,7 @@ public class SpellThrowable extends Spell { float velocity = calculateVelocity(modifiers, caster.getEyeHeight() - LAUNCH_Y_OFFSET); T projectile = projectileFactory.apply(world, caster); int aimingError = caster instanceof ISpellCaster ? ((ISpellCaster)caster).getAimingError(world.getDifficulty()) - : WizardryUtilities.getDefaultAimingError(world.getDifficulty()); + : EntityUtils.getDefaultAimingError(world.getDifficulty()); aim(projectile, caster, target, velocity, aimingError); addProjectileExtras(projectile, caster, modifiers); world.spawnEntity(projectile); @@ -112,10 +127,10 @@ public class SpellThrowable extends Spell { throwable.ignoreEntity = caster; - throwable.posY = caster.getEntityBoundingBox().minY + (double)caster.getEyeHeight() - LAUNCH_Y_OFFSET; + throwable.posY = caster.posY + (double)caster.getEyeHeight() - LAUNCH_Y_OFFSET; double dx = target.posX - caster.posX; - double dy = !throwable.hasNoGravity() ? target.getEntityBoundingBox().minY + (double)(target.height / 3.0f) - throwable.posY - : target.getEntityBoundingBox().minY + (double)(target.height / 2.0f) - throwable.posY; + double dy = !throwable.hasNoGravity() ? target.posY + (double)(target.height / 3.0f) - throwable.posY + : target.posY + (double)(target.height / 2.0f) - throwable.posY; double dz = target.posZ - caster.posZ; double horizontalDistance = MathHelper.sqrt(dx * dx + dz * dz); @@ -139,4 +154,35 @@ public class SpellThrowable extends Spell { */ protected void addProjectileExtras(T projectile, EntityLivingBase caster, SpellModifiers modifiers){} + @SubscribeEvent + public static void onLivingAttackEvent(LivingAttackEvent event){ + + // TODO: Annoyingly, wither skulls apply 'direct' damage from their shooter so they won't be recognised here + if(event.getSource().getImmediateSource() != null && !(event.getSource() instanceof IElementalDamage)){ // Prevent infinite looping + + float damageModifier = event.getSource().getImmediateSource().getEntityData().getFloat(DAMAGE_MODIFIER_NBT_KEY); + + // Really, we just want to increase the damage without modifying the source, but that would cause an + // infinite loop so we need some way of identifying it - the easiest way is to use MagicDamage, which fits + // quite nicely since we can only get here if the entity was from a spell anyway + if(damageModifier > 0){ + + Entity projectile = event.getSource().getImmediateSource(); + Entity shooter = event.getSource().getTrueSource(); + + DamageSource newSource = shooter == projectile + ? MagicDamage.causeDirectMagicDamage(projectile, DamageType.MAGIC) + : MagicDamage.causeIndirectMagicDamage(projectile, shooter, DamageType.MAGIC); + + // Copy over any relevant 'attributes' the original DamageSource might have had. + if(event.getSource().isExplosion()) newSource.setExplosion(); + if(event.getSource().isFireDamage()) newSource.setFireDamage(); + if(event.getSource().isProjectile()) newSource.setProjectile(); + + DamageSafetyChecker.attackEntitySafely(event.getEntity(), newSource, + event.getAmount() * damageModifier, event.getSource(), true); + } + } + } + } \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/spell/Stormcloud.java b/src/main/java/electroblob/wizardry/spell/Stormcloud.java new file mode 100644 index 00000000..5be5ec34 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/Stormcloud.java @@ -0,0 +1,22 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.entity.construct.EntityStormcloud; +import electroblob.wizardry.util.SpellModifiers; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.util.EnumFacing; + +import javax.annotation.Nullable; + +public class Stormcloud extends SpellConstructRanged { + + public Stormcloud(){ + super("stormcloud", EntityStormcloud::new, false); + this.addProperties(DAMAGE, EFFECT_RADIUS); + this.floor(true); + } + + @Override + protected void addConstructExtras(EntityStormcloud construct, EnumFacing side, @Nullable EntityLivingBase caster, SpellModifiers modifiers){ + construct.posY += 5; + } +} diff --git a/src/main/java/electroblob/wizardry/spell/SummonIronGolem.java b/src/main/java/electroblob/wizardry/spell/SummonIronGolem.java index 9feaca7d..a93c57b8 100644 --- a/src/main/java/electroblob/wizardry/spell/SummonIronGolem.java +++ b/src/main/java/electroblob/wizardry/spell/SummonIronGolem.java @@ -1,12 +1,12 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; +import electroblob.wizardry.util.BlockUtils; 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.monster.EntityIronGolem; 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; @@ -14,7 +14,7 @@ import net.minecraft.world.World; public class SummonIronGolem extends Spell { public SummonIronGolem(){ - super("summon_iron_golem", EnumAction.BOW, false); + super("summon_iron_golem", SpellActions.SUMMON, false); addProperties(SpellMinion.SUMMON_RADIUS); soundValues(1, 1.1f, 0.2f); } @@ -22,7 +22,7 @@ public class SummonIronGolem extends Spell { @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, getProperty(SpellMinion.SUMMON_RADIUS).intValue(), + BlockPos pos = BlockUtils.findNearbyFloorSpace(caster, getProperty(SpellMinion.SUMMON_RADIUS).intValue(), getProperty(SpellMinion.SUMMON_RADIUS).intValue()); if(pos == null) return false; diff --git a/src/main/java/electroblob/wizardry/spell/SummonSnowGolem.java b/src/main/java/electroblob/wizardry/spell/SummonSnowGolem.java index 8e7c9476..c383fe68 100644 --- a/src/main/java/electroblob/wizardry/spell/SummonSnowGolem.java +++ b/src/main/java/electroblob/wizardry/spell/SummonSnowGolem.java @@ -1,13 +1,13 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.item.SpellActions; +import electroblob.wizardry.util.BlockUtils; 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.monster.EntitySnowman; 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; @@ -15,7 +15,7 @@ import net.minecraft.world.World; public class SummonSnowGolem extends Spell { public SummonSnowGolem(){ - super("summon_snow_golem", EnumAction.BOW, false); + super("summon_snow_golem", SpellActions.SUMMON, false); this.soundValues(1, 1, 0.4f); addProperties(SpellMinion.SUMMON_RADIUS); } @@ -23,7 +23,7 @@ public class SummonSnowGolem extends Spell { @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 2, 4); + BlockPos pos = BlockUtils.findNearbyFloorSpace(caster, 2, 4); if(pos == null) return false; if(!world.isRemote){ diff --git a/src/main/java/electroblob/wizardry/spell/SummonSpiritHorse.java b/src/main/java/electroblob/wizardry/spell/SummonSpiritHorse.java index a9f02984..917ed376 100644 --- a/src/main/java/electroblob/wizardry/spell/SummonSpiritHorse.java +++ b/src/main/java/electroblob/wizardry/spell/SummonSpiritHorse.java @@ -4,20 +4,17 @@ import electroblob.wizardry.data.IStoredVariable; import electroblob.wizardry.data.Persistence; import electroblob.wizardry.data.WizardData; import electroblob.wizardry.entity.living.EntitySpiritHorse; +import electroblob.wizardry.item.SpellActions; +import electroblob.wizardry.util.BlockUtils; +import electroblob.wizardry.util.EntityUtils; 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; @@ -26,17 +23,10 @@ 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("summon_spirit_horse", EnumAction.BOW, false); + super("summon_spirit_horse", SpellActions.SUMMON, false); addProperties(SpellMinion.SUMMON_RADIUS); soundValues(0.7f, 1.2f, 0.4f); WizardData.registerStoredVariables(UUID_KEY); @@ -54,11 +44,11 @@ public class SummonSpiritHorse extends Spell { if(!world.isRemote){ - Entity oldHorse = WizardryUtilities.getEntityByUUID(world, data.getVariable(UUID_KEY)); + Entity oldHorse = EntityUtils.getEntityByUUID(world, data.getVariable(UUID_KEY)); if(oldHorse != null) oldHorse.setDead(); - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 2, 4); + BlockPos pos = BlockUtils.findNearbyFloorSpace(caster, 2, 4); if(pos == null) return false; EntitySpiritHorse horse = new EntitySpiritHorse(world); @@ -68,10 +58,10 @@ public class SummonSpiritHorse extends Spell { world.spawnEntity(horse); horse.getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).applyModifier( - new AttributeModifier(POTENCY_ATTRIBUTE_MODIFIER, modifiers.get(SpellModifiers.POTENCY) - 1, Operations.MULTIPLY_CUMULATIVE)); + new AttributeModifier(POTENCY_ATTRIBUTE_MODIFIER, modifiers.get(SpellModifiers.POTENCY) - 1, EntityUtils.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)); + horse.getEntityAttribute(EntitySpiritHorse.JUMP_STRENGTH).applyModifier(new AttributeModifier(POTENCY_ATTRIBUTE_MODIFIER, + modifiers.amplified(SpellModifiers.POTENCY, 0.25f) - 1, EntityUtils.Operations.MULTIPLY_CUMULATIVE)); data.setVariable(UUID_KEY, horse.getUniqueID()); } diff --git a/src/main/java/electroblob/wizardry/spell/SummonSpiritWolf.java b/src/main/java/electroblob/wizardry/spell/SummonSpiritWolf.java index f4a8d6eb..70a13c6d 100644 --- a/src/main/java/electroblob/wizardry/spell/SummonSpiritWolf.java +++ b/src/main/java/electroblob/wizardry/spell/SummonSpiritWolf.java @@ -4,14 +4,14 @@ import electroblob.wizardry.data.IStoredVariable; import electroblob.wizardry.data.Persistence; import electroblob.wizardry.data.WizardData; import electroblob.wizardry.entity.living.EntitySpiritWolf; +import electroblob.wizardry.item.SpellActions; +import electroblob.wizardry.util.BlockUtils; +import electroblob.wizardry.util.EntityUtils; 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; @@ -26,7 +26,7 @@ public class SummonSpiritWolf extends Spell { public static final IStoredVariable UUID_KEY = IStoredVariable.StoredVariable.ofUUID("spiritWolfUUID", Persistence.ALWAYS); public SummonSpiritWolf(){ - super("summon_spirit_wolf", EnumAction.BOW, false); + super("summon_spirit_wolf", SpellActions.SUMMON, false); addProperties(SpellMinion.SUMMON_RADIUS); soundValues(0.7f, 1.2f, 0.4f); WizardData.registerStoredVariables(UUID_KEY); @@ -44,11 +44,11 @@ public class SummonSpiritWolf extends Spell { if(!world.isRemote){ - Entity oldWolf = WizardryUtilities.getEntityByUUID(world, data.getVariable(UUID_KEY)); + Entity oldWolf = EntityUtils.getEntityByUUID(world, data.getVariable(UUID_KEY)); if(oldWolf != null) oldWolf.setDead(); - BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, 2, 4); + BlockPos pos = BlockUtils.findNearbyFloorSpace(caster, 2, 4); if(pos == null) return false; EntitySpiritWolf wolf = new EntitySpiritWolf(world); @@ -57,9 +57,9 @@ public class SummonSpiritWolf extends Spell { 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)); + new AttributeModifier(POTENCY_ATTRIBUTE_MODIFIER, modifiers.get(SpellModifiers.POTENCY) - 1, EntityUtils.Operations.MULTIPLY_CUMULATIVE)); wolf.getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).applyModifier( - new AttributeModifier(POTENCY_ATTRIBUTE_MODIFIER, modifiers.amplified(SpellModifiers.POTENCY, 1.5f) - 1, Operations.MULTIPLY_CUMULATIVE)); + new AttributeModifier(POTENCY_ATTRIBUTE_MODIFIER, modifiers.amplified(SpellModifiers.POTENCY, 1.5f) - 1, EntityUtils.Operations.MULTIPLY_CUMULATIVE)); wolf.setHealth(wolf.getMaxHealth()); world.spawnEntity(wolf); diff --git a/src/main/java/electroblob/wizardry/spell/Telekinesis.java b/src/main/java/electroblob/wizardry/spell/Telekinesis.java index 9d97aaa6..760979bc 100644 --- a/src/main/java/electroblob/wizardry/spell/Telekinesis.java +++ b/src/main/java/electroblob/wizardry/spell/Telekinesis.java @@ -1,13 +1,13 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.util.SpellModifiers; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.Entity; 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; @@ -18,7 +18,8 @@ import net.minecraft.world.World; public class Telekinesis extends SpellRay { public Telekinesis(){ - super("telekinesis", false, EnumAction.NONE); + super("telekinesis", SpellActions.POINT, false); + this.aimAssist(0.4f); // Helps with aiming at items } @Override public boolean requiresPacket(){ return false; } diff --git a/src/main/java/electroblob/wizardry/spell/Thunderstorm.java b/src/main/java/electroblob/wizardry/spell/Thunderstorm.java index 05e7884f..a0feef28 100644 --- a/src/main/java/electroblob/wizardry/spell/Thunderstorm.java +++ b/src/main/java/electroblob/wizardry/spell/Thunderstorm.java @@ -1,5 +1,6 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.util.*; import electroblob.wizardry.util.MagicDamage.DamageType; @@ -8,7 +9,6 @@ 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.math.BlockPos; import net.minecraft.util.math.MathHelper; @@ -32,7 +32,7 @@ public class Thunderstorm extends Spell { private static final float CENTRE_RADIUS_FRACTION = 0.5f; public Thunderstorm(){ - super("thunderstorm", EnumAction.BOW, false); + super("thunderstorm", SpellActions.POINT_UP, 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); @@ -63,21 +63,19 @@ public class Thunderstorm extends Spell { 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); + Integer y = BlockUtils.getNearestFloor(world, new BlockPos(x, caster.posY, z), (int)maxRadius); if(y != null){ if(!world.isRemote){ - EntityLightningBolt entitylightning = new EntityLightningBolt(world, x, y, z, false); - world.addWeatherEffect(entitylightning); + EntityLightningBolt lightning = new EntityLightningBolt(world, x, y, z, false); + lightning.getEntityData().setUniqueId(LightningBolt.SUMMONER_NBT_KEY, caster.getUniqueID()); + lightning.getEntityData().setFloat(LightningBolt.DAMAGE_MODIFIER_NBT_KEY, modifiers.get(SpellModifiers.POTENCY)); + world.addWeatherEffect(lightning); } - // Code for eventhandler recognition; for achievements and such like. Left in for future use. - // NBTTagCompound entityNBT = entitylightning.getEntityData(); - // entityNBT.setInteger("summoningPlayer", entityplayer.entityId); - // Secondary chaining effect - List secondaryTargets = WizardryUtilities.getEntitiesWithinRadius( + List secondaryTargets = EntityUtils.getLivingWithinRadius( getProperty(SECONDARY_RANGE).doubleValue(), x, y + 1, z, world); for(int j = 0; j < Math.min(secondaryTargets.size(), getProperty(SECONDARY_MAX_TARGETS).intValue()); j++){ @@ -91,7 +89,7 @@ public class Thunderstorm extends Spell { ParticleBuilder.create(Type.LIGHTNING).pos(x, y, z).target(secondaryTarget).spawn(world); ParticleBuilder.spawnShockParticles(world, secondaryTarget.posX, - secondaryTarget.getEntityBoundingBox().minY + secondaryTarget.height / 2, + secondaryTarget.posY + secondaryTarget.height / 2, secondaryTarget.posZ); } @@ -102,7 +100,7 @@ public class Thunderstorm extends Spell { // Tertiary chaining effect - List tertiaryTargets = WizardryUtilities.getEntitiesWithinRadius( + List tertiaryTargets = EntityUtils.getLivingWithinRadius( getProperty(TERTIARY_RANGE).doubleValue(), secondaryTarget.posX, secondaryTarget.posY + secondaryTarget.height / 2, secondaryTarget.posZ, world); @@ -117,7 +115,7 @@ public class Thunderstorm extends Spell { 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.posY + tertiaryTarget.height / 2, tertiaryTarget.posZ); } diff --git a/src/main/java/electroblob/wizardry/spell/Tornado.java b/src/main/java/electroblob/wizardry/spell/Tornado.java index b79d3f0b..b9378c6f 100644 --- a/src/main/java/electroblob/wizardry/spell/Tornado.java +++ b/src/main/java/electroblob/wizardry/spell/Tornado.java @@ -1,9 +1,9 @@ package electroblob.wizardry.spell; import electroblob.wizardry.entity.construct.EntityTornado; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.util.SpellModifiers; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.item.EnumAction; import net.minecraft.util.EnumFacing; import net.minecraft.util.math.Vec3d; @@ -13,7 +13,7 @@ public class Tornado extends SpellConstruct { public static final String UPWARD_ACCELERATION = "upward_acceleration"; public Tornado(){ - super("tornado", EnumAction.NONE, EntityTornado::new, false); + super("tornado", SpellActions.POINT, EntityTornado::new, false); addProperties(EFFECT_RADIUS, SPEED, DAMAGE, UPWARD_ACCELERATION); } diff --git a/src/main/java/electroblob/wizardry/spell/Transience.java b/src/main/java/electroblob/wizardry/spell/Transience.java index e20f1ac5..e6f17353 100644 --- a/src/main/java/electroblob/wizardry/spell/Transience.java +++ b/src/main/java/electroblob/wizardry/spell/Transience.java @@ -1,13 +1,13 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.util.SpellModifiers; 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; @@ -26,7 +26,7 @@ public class Transience extends Spell { public static final ResourceLocation SHADER = new ResourceLocation(Wizardry.MODID, "shaders/post/transience.json"); public Transience(){ - super("transience", EnumAction.BOW, false); + super("transience", SpellActions.POINT_UP, false); addProperties(EFFECT_DURATION); } diff --git a/src/main/java/electroblob/wizardry/spell/Transportation.java b/src/main/java/electroblob/wizardry/spell/Transportation.java index 07e7d9a6..19a810f8 100644 --- a/src/main/java/electroblob/wizardry/spell/Transportation.java +++ b/src/main/java/electroblob/wizardry/spell/Transportation.java @@ -5,16 +5,17 @@ import electroblob.wizardry.data.IStoredVariable; import electroblob.wizardry.data.Persistence; import electroblob.wizardry.data.WizardData; import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.packet.PacketTransportation; import electroblob.wizardry.packet.WizardryPacketHandler; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.GeometryUtils; import electroblob.wizardry.util.Location; import electroblob.wizardry.util.NBTExtras; import electroblob.wizardry.util.SpellModifiers; -import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.MobEffects; -import net.minecraft.item.EnumAction; import net.minecraft.nbt.NBTTagList; import net.minecraft.potion.PotionEffect; import net.minecraft.util.EnumHand; @@ -41,7 +42,7 @@ public class Transportation extends Spell { public static final IStoredVariable COUNTDOWN_KEY = IStoredVariable.StoredVariable.ofInt("tpCountdown", Persistence.NEVER).withTicker(Transportation::update); public Transportation(){ - super("transportation", EnumAction.BOW, false); + super("transportation", SpellActions.POINT_UP, false); addProperties(TELEPORT_COUNTDOWN); WizardData.registerStoredVariables(LOCATIONS_KEY, COUNTDOWN_KEY); } @@ -128,7 +129,7 @@ public class Transportation extends Spell { public static boolean isLocationAimedAt(EntityPlayer player, BlockPos pos, float partialTicks){ Vec3d origin = player.getPositionEyes(partialTicks); - Vec3d centre = WizardryUtilities.getCentre(pos); + Vec3d centre = GeometryUtils.getCentre(pos); Vec3d direction = centre.subtract(origin); double distance = direction.length(); @@ -139,7 +140,7 @@ public class Transportation extends Spell { Vec3d origin = player.getPositionEyes(partialTicks); Vec3d look = player.getLook(partialTicks); - Vec3d centre = WizardryUtilities.getCentre(pos); + Vec3d centre = GeometryUtils.getCentre(pos); Vec3d direction = centre.subtract(origin); double distance = direction.length(); @@ -180,9 +181,21 @@ public class Transportation extends Spell { Location destination = locations.get(locations.size() - 1); if(countdown == 1 && destination.dimension == player.dimension){ + + Entity mount = player.getRidingEntity(); + if(mount != null) player.dismountRidingEntity(); + player.setPositionAndUpdate(destination.pos.getX() + 0.5, destination.pos.getY(), destination.pos.getZ() + 0.5); + + boolean teleportMount = mount != null && ItemArtefact.isArtefactActive(player, WizardryItems.charm_mount_teleporting); + + if(teleportMount){ + mount.setPositionAndUpdate(destination.pos.getX() + 0.5, destination.pos.getY(), destination.pos.getZ() + 0.5); + player.startRiding(mount); + } + player.addPotionEffect(new PotionEffect(MobEffects.BLINDNESS, 50, 0)); - IMessage msg = new PacketTransportation.Message(player.getEntityId()); + IMessage msg = new PacketTransportation.Message(destination.pos, teleportMount ? null : player); WizardryPacketHandler.net.sendToDimension(msg, player.world.provider.getDimension()); } diff --git a/src/main/java/electroblob/wizardry/spell/VanishingBox.java b/src/main/java/electroblob/wizardry/spell/VanishingBox.java index 471450de..06c57933 100644 --- a/src/main/java/electroblob/wizardry/spell/VanishingBox.java +++ b/src/main/java/electroblob/wizardry/spell/VanishingBox.java @@ -1,16 +1,16 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.util.SpellModifiers; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.inventory.InventoryEnderChest; -import net.minecraft.item.EnumAction; import net.minecraft.util.EnumHand; import net.minecraft.world.World; public class VanishingBox extends Spell { public VanishingBox(){ - super("vanishing_box", EnumAction.BOW, false); + super("vanishing_box", SpellActions.POINT_UP, false); } @Override public boolean requiresPacket(){ return false; } diff --git a/src/main/java/electroblob/wizardry/spell/WallOfFrost.java b/src/main/java/electroblob/wizardry/spell/WallOfFrost.java index 2c059bef..7efe9013 100644 --- a/src/main/java/electroblob/wizardry/spell/WallOfFrost.java +++ b/src/main/java/electroblob/wizardry/spell/WallOfFrost.java @@ -1,17 +1,18 @@ package electroblob.wizardry.spell; import electroblob.wizardry.block.BlockStatue; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardrySounds; +import electroblob.wizardry.util.BlockUtils; +import electroblob.wizardry.util.EntityUtils; 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.item.EnumAction; import net.minecraft.util.EnumFacing; import net.minecraft.util.SoundEvent; import net.minecraft.util.math.BlockPos; @@ -23,7 +24,7 @@ public class WallOfFrost extends SpellRay { private static final int MINIMUM_PLACEMENT_RANGE = 2; public WallOfFrost(){ - super("wall_of_frost", true, EnumAction.NONE); + super("wall_of_frost", SpellActions.POINT, true); this.particleVelocity(1); this.particleSpacing(0.5); addProperties(DURATION); @@ -51,7 +52,7 @@ public class WallOfFrost extends SpellRay { 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)))){ + caster, (int)(getProperty(DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)))){ target.playSound(WizardrySounds.MISC_FREEZE, 1.0F, world.rand.nextFloat() * 0.4F + 0.8F); } @@ -63,11 +64,11 @@ public class WallOfFrost extends SpellRay { @Override protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ - if(!world.isRemote && WizardryUtilities.canDamageBlocks(caster, world)){ + if(!world.isRemote && EntityUtils.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)){ + if(BlockUtils.canBlockBeReplaced(world, pos)){ // Moves the blockpos back into the block pos = pos.offset(side.getOpposite()); } @@ -79,7 +80,7 @@ public class WallOfFrost extends SpellRay { int duration = (int)(getProperty(DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)); - if(WizardryUtilities.canBlockBeReplaced(world, pos)){ + if(BlockUtils.canBlockBeReplaced(world, pos) && BlockUtils.canPlaceBlock(caster, world, pos)){ world.setBlockState(pos, WizardryBlocks.dry_frosted_ice.getDefaultState()); world.scheduleUpdate(pos.toImmutable(), WizardryBlocks.dry_frosted_ice, duration); } @@ -88,7 +89,7 @@ public class WallOfFrost extends SpellRay { if(side == EnumFacing.UP){ pos = pos.offset(side); - if(WizardryUtilities.canBlockBeReplaced(world, pos)){ + if(BlockUtils.canBlockBeReplaced(world, pos) && BlockUtils.canPlaceBlock(caster, world, pos)){ world.setBlockState(pos, WizardryBlocks.dry_frosted_ice.getDefaultState()); world.scheduleUpdate(pos.toImmutable(), WizardryBlocks.dry_frosted_ice, duration); } diff --git a/src/main/java/electroblob/wizardry/spell/Whirlwind.java b/src/main/java/electroblob/wizardry/spell/Whirlwind.java index 733c891a..d015b57e 100644 --- a/src/main/java/electroblob/wizardry/spell/Whirlwind.java +++ b/src/main/java/electroblob/wizardry/spell/Whirlwind.java @@ -2,13 +2,13 @@ package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.util.SpellModifiers; 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.EnumFacing; import net.minecraft.util.EnumParticleTypes; @@ -22,7 +22,7 @@ public class Whirlwind extends SpellRay { public static final String REPULSION_VELOCITY = "repulsion_velocity"; public Whirlwind(){ - super("whirlwind", false, EnumAction.NONE); + super("whirlwind", SpellActions.POINT, false); this.soundValues(0.8f, 0.7f, 0.2f); addProperties(REPULSION_VELOCITY); } @@ -41,7 +41,7 @@ public class Whirlwind extends SpellRay { // Left as EntityLivingBase because why not be able to move armour stands around? if(target instanceof EntityLivingBase){ - Vec3d vec = target.getPositionVector().add(0, target.getEyeHeight(), 0).subtract(origin).normalize(); + Vec3d vec = target.getPositionEyes(1).subtract(origin).normalize(); if(!world.isRemote){ diff --git a/src/main/java/electroblob/wizardry/spell/Wither.java b/src/main/java/electroblob/wizardry/spell/Wither.java index f649d9b8..934fcd9d 100644 --- a/src/main/java/electroblob/wizardry/spell/Wither.java +++ b/src/main/java/electroblob/wizardry/spell/Wither.java @@ -1,17 +1,17 @@ package electroblob.wizardry.spell; +import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.EntityUtils; 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.math.BlockPos; @@ -22,7 +22,7 @@ import net.minecraft.world.World; public class Wither extends SpellRay { public Wither(){ - super("wither", false, EnumAction.NONE); + super("wither", SpellActions.POINT, false); this.soundValues(1, 1.1f, 0.2f); addProperties(DAMAGE, EFFECT_DURATION, EFFECT_STRENGTH); } @@ -30,7 +30,7 @@ public class Wither extends SpellRay { @Override protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ - if(WizardryUtilities.isLiving(target)){ + if(EntityUtils.isLiving(target)){ // Has no effect on withers or wither skeletons. if(MagicDamage.isEntityImmune(DamageType.WITHER, target)){ diff --git a/src/main/java/electroblob/wizardry/spell/WitherSkull.java b/src/main/java/electroblob/wizardry/spell/WitherSkull.java index c71af6ec..9c79ce20 100644 --- a/src/main/java/electroblob/wizardry/spell/WitherSkull.java +++ b/src/main/java/electroblob/wizardry/spell/WitherSkull.java @@ -74,7 +74,7 @@ public class WitherSkull extends Spell { EntityWitherSkull witherskull = new EntityWitherSkull(world, caster, 1, 1, 1); double dx = target.posX - caster.posX; - double dy = target.getEntityBoundingBox().minY + (double)(target.height / 2.0F) + double dy = target.posY + (double)(target.height / 2.0F) - (caster.posY + (double)(caster.height / 2.0F)); double dz = target.posZ - caster.posZ; diff --git a/src/main/java/electroblob/wizardry/spell/WitheringTotem.java b/src/main/java/electroblob/wizardry/spell/WitheringTotem.java new file mode 100644 index 00000000..93c69f64 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/WitheringTotem.java @@ -0,0 +1,25 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.entity.construct.EntityWitheringTotem; +import electroblob.wizardry.util.SpellModifiers; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.util.EnumFacing; + +import javax.annotation.Nullable; + +public class WitheringTotem extends SpellConstructRanged { + + public static final String MAX_TARGETS = "max_targets"; + public static final String MAX_EXPLOSION_DAMAGE = "max_explosion_damage"; + + public WitheringTotem(){ + super("withering_totem", EntityWitheringTotem::new, false); + this.addProperties(EFFECT_RADIUS, MAX_TARGETS, DAMAGE, MAX_EXPLOSION_DAMAGE, EFFECT_DURATION, EFFECT_STRENGTH); + this.floor(false); + } + + @Override + protected void addConstructExtras(EntityWitheringTotem construct, EnumFacing side, @Nullable EntityLivingBase caster, SpellModifiers modifiers){ + construct.posY += 1.2; + } +} diff --git a/src/main/java/electroblob/wizardry/spell/ZombieApocalypse.java b/src/main/java/electroblob/wizardry/spell/ZombieApocalypse.java new file mode 100644 index 00000000..26415079 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/ZombieApocalypse.java @@ -0,0 +1,57 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.entity.construct.EntityZombieSpawner; +import electroblob.wizardry.item.ItemArtefact; +import electroblob.wizardry.item.SpellActions; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.BlockUtils; +import electroblob.wizardry.util.SpellModifiers; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.SoundEvent; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public class ZombieApocalypse extends SpellConstruct { + + public static final String MINION_SPAWN_INTERVAL = "minion_spawn_interval"; + + private static final int SPAWNER_HEIGHT = 8; + private static final int MIN_SPAWNER_HEIGHT = 3; + + public ZombieApocalypse(){ + super("zombie_apocalypse", SpellActions.POINT_UP, EntityZombieSpawner::new, false); + addProperties(SpellMinion.MINION_LIFETIME, MINION_SPAWN_INTERVAL); + this.soundValues(1.3f, 1, 0); + } + + @Override + protected SoundEvent[] createSounds(){ + return createContinuousSpellSounds(); + } + + @Override + protected boolean spawnConstruct(World world, double x, double y, double z, EnumFacing side, @Nullable EntityLivingBase caster, SpellModifiers modifiers){ + + Integer ceiling = BlockUtils.getNearestSurface(world, new BlockPos(x, y + MIN_SPAWNER_HEIGHT, z), + EnumFacing.UP, SPAWNER_HEIGHT - MIN_SPAWNER_HEIGHT, false, BlockUtils.SurfaceCriteria.COLLIDABLE.flip()); + + if(ceiling == null) y += SPAWNER_HEIGHT; + else y = ceiling - 0.5; + + return super.spawnConstruct(world, x, y, z, side, caster, modifiers); + } + + @Override + protected void addConstructExtras(EntityZombieSpawner construct, EnumFacing side, @Nullable EntityLivingBase caster, SpellModifiers modifiers){ + construct.spawnHusks = caster instanceof EntityPlayer && ItemArtefact.isArtefactActive((EntityPlayer)caster, WizardryItems.charm_minion_variants); + } + + @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 + SPAWNER_HEIGHT, z, 0, (int)(getProperty(DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade))); + } +} diff --git a/src/main/java/electroblob/wizardry/tileentity/ContainerArcaneWorkbench.java b/src/main/java/electroblob/wizardry/tileentity/ContainerArcaneWorkbench.java deleted file mode 100644 index 7582ba0e..00000000 --- a/src/main/java/electroblob/wizardry/tileentity/ContainerArcaneWorkbench.java +++ /dev/null @@ -1,273 +0,0 @@ -package electroblob.wizardry.tileentity; - -import electroblob.wizardry.Wizardry; -import electroblob.wizardry.event.SpellBindEvent; -import electroblob.wizardry.item.IWorkbenchItem; -import electroblob.wizardry.item.ItemSpellBook; -import electroblob.wizardry.registry.WizardryAdvancementTriggers; -import electroblob.wizardry.registry.WizardryItems; -import electroblob.wizardry.util.WandHelper; -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.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 int CRYSTAL_SLOT = 8; - public static final int CENTRE_SLOT = 9; - public static final int UPGRADE_SLOT = 10; - - public static final int SLOT_RADIUS = 42; - - public ContainerArcaneWorkbench(IInventory inventory, TileEntityArcaneWorkbench tileentity){ - - this.tileentity = tileentity; - - ItemStack wand = tileentity.getStackInSlot(CENTRE_SLOT); - - for(int i = 0; i < 8; i++){ - Slot slot = new SlotItemClassList(tileentity, i, -999, -999, 1, ItemSpellBook.class); - this.addSlotToContainer(slot); - } - - 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 SlotWorkbenchItem(tileentity, CENTRE_SLOT, 80, 64, this)); - - 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, 147, 17, 1, upgrades.toArray(new Item[0]))) - .setBackgroundName(EMPTY_SLOT_UPGRADE.toString()); - - for(int x = 0; x < 9; x++){ - this.addSlotToContainer(new Slot(inventory, x, 8 + x * 18, 196)); - } - - for(int y = 0; y < 3; y++){ - for(int x = 0; x < 9; x++){ - this.addSlotToContainer(new Slot(inventory, 9 + x + y * 9, 8 + x * 18, 138 + y * 18)); - } - } - - 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 == CENTRE_SLOT){ - - if(stack.isEmpty()){ - // If the stack has been removed, hide all the spell book slots - for(int i = 0; i < CRYSTAL_SLOT; i++){ - this.hideSlot(i, player); - } - - }else{ - - 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); - } - - // 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. - // 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 = 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 - - // Workbench -> inventory - if(clickedSlotId <= UPGRADE_SLOT){ - // Tries to move the stack into the player's inventory. If this fails... - if(!this.mergeItemStack(stack, UPGRADE_SLOT + 1, this.inventorySlots.size(), true)){ - return ItemStack.EMPTY; // ...nothing else happens. - } - } - // Inventory -> workbench - else{ - // The following logic prevents shift-clicking transferring the items to the wrong slot. - int minSlotId = 0; - int maxSlotId = UPGRADE_SLOT; - - if(stack.getItem() instanceof ItemSpellBook){ - minSlotId = 0; - maxSlotId = CRYSTAL_SLOT - 1; - }else if(getSlot(CRYSTAL_SLOT).isItemValid(stack)){ - minSlotId = CRYSTAL_SLOT; - maxSlotId = CRYSTAL_SLOT; - }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{ - // 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)){ - return ItemStack.EMPTY; - } - } - - if(stack.getCount() == 0){ - slot.putStack(ItemStack.EMPTY); - }else{ - slot.onSlotChanged(); - } - - if(stack.getCount() == remainder.getCount()){ - return ItemStack.EMPTY; - } - - slot.onTake(player, stack); - } - - return remainder; - } - - // Overridden to stop stacks merging into 'removed' slots. - @Override - protected boolean mergeItemStack(ItemStack stack, int minSlotID, int maxSlotID, boolean p_75135_4_){ - - for(int i = minSlotID; i < maxSlotID; i++){ - // System.out.println(this.getSlot(i).xDisplayPosition); - if(this.getSlot(i).xPos >= 0 && this.getSlot(i).yPos >= 0 && !this.getSlot(i).getHasStack()){ - return super.mergeItemStack(stack, minSlotID, maxSlotID, p_75135_4_); - } - } - // Only returns false if none of the slots given are enabled and empty - return false; - } - - /** - * Called (via {@link electroblob.wizardry.packet.PacketControlInput PacketControlInput}) when the apply button in - * the arcane workbench GUI is pressed. - */ - // 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){ - - 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)){ - - if(player instanceof EntityPlayerMP){ - WizardryAdvancementTriggers.arcane_workbench.trigger((EntityPlayerMP)player, centre.getStack()); - } - } - } - } - -} diff --git a/src/main/java/electroblob/wizardry/tileentity/TileEntityArcaneWorkbench.java b/src/main/java/electroblob/wizardry/tileentity/TileEntityArcaneWorkbench.java index cfc6dc09..95fada8b 100644 --- a/src/main/java/electroblob/wizardry/tileentity/TileEntityArcaneWorkbench.java +++ b/src/main/java/electroblob/wizardry/tileentity/TileEntityArcaneWorkbench.java @@ -1,6 +1,7 @@ package electroblob.wizardry.tileentity; import electroblob.wizardry.Wizardry; +import electroblob.wizardry.inventory.ContainerArcaneWorkbench; import electroblob.wizardry.item.IManaStoringItem; import electroblob.wizardry.item.IWorkbenchItem; import electroblob.wizardry.item.ItemCrystal; @@ -36,8 +37,13 @@ public class TileEntityArcaneWorkbench extends TileEntity implements IInventory, /** Controls the rotating rune and floating wand animations. */ public float timer = 0; + private boolean doNotSync; + public TileEntityArcaneWorkbench(){ inventory = NonNullList.withSize(ContainerArcaneWorkbench.UPGRADE_SLOT + 1, ItemStack.EMPTY); + // Prevent sync() happening when loading from NBT the first time or weirdness ensues when loading a world + // Normally I'd pass this as a flag to setInventorySlotContents but we can't change the method signature + this.doNotSync = true; } @Override @@ -47,12 +53,14 @@ public class TileEntityArcaneWorkbench extends TileEntity implements IInventory, /** Called to manually sync the tile entity with clients. */ public void sync(){ - this.world.notifyBlockUpdate(pos, world.getBlockState(pos), world.getBlockState(pos), 3); + if(!doNotSync) this.world.markAndNotifyBlock(pos, null, world.getBlockState(pos), world.getBlockState(pos), 3); } @Override public void update(){ + this.doNotSync = false; + ItemStack stack = this.getStackInSlot(ContainerArcaneWorkbench.CENTRE_SLOT); // Decrements wand damage (increases mana) every 1.5 seconds if it has a condenser upgrade @@ -112,8 +120,12 @@ public class TileEntityArcaneWorkbench extends TileEntity implements IInventory, @Override public void setInventorySlotContents(int slot, ItemStack stack){ - - inventory.set(slot, stack); + + ItemStack previous = inventory.set(slot, stack); + + // Only the central slot affects the in-world rendering, so only sync if that changes + // This must be done in the tile entity because containers only exist for player interaction, not hoppers etc. + if(slot == ContainerArcaneWorkbench.CENTRE_SLOT && previous.isEmpty() != stack.isEmpty()) this.sync(); if(!stack.isEmpty() && stack.getCount() > getInventoryStackLimit()){ stack.setCount(getInventoryStackLimit()); @@ -156,7 +168,17 @@ public class TileEntityArcaneWorkbench extends TileEntity implements IInventory, if(itemstack == ItemStack.EMPTY) return true; if(slotNumber >= 0 && slotNumber < ContainerArcaneWorkbench.CRYSTAL_SLOT){ - return itemstack.getItem() instanceof ItemSpellBook; + + if(!(itemstack.getItem() instanceof ItemSpellBook)) return false; + + ItemStack centreStack = getStackInSlot(ContainerArcaneWorkbench.CENTRE_SLOT); + + if(centreStack.getItem() instanceof IWorkbenchItem){ + int spellSlots = ((IWorkbenchItem)centreStack.getItem()).getSpellSlotCount(centreStack); + return slotNumber < spellSlots; + } + + return false; }else if(slotNumber == ContainerArcaneWorkbench.CRYSTAL_SLOT){ return itemstack.getItem() instanceof ItemCrystal; @@ -198,12 +220,10 @@ public class TileEntityArcaneWorkbench extends TileEntity implements IInventory, NBTTagList itemList = new NBTTagList(); for(int i = 0; i < getSizeInventory(); i++){ ItemStack stack = getStackInSlot(i); - if(!stack.isEmpty()){ - NBTTagCompound tag = new NBTTagCompound(); - tag.setByte("Slot", (byte)i); - stack.writeToNBT(tag); - itemList.appendTag(tag); - } + NBTTagCompound tag = new NBTTagCompound(); + tag.setByte("Slot", (byte)i); + stack.writeToNBT(tag); + itemList.appendTag(tag); } NBTExtras.storeTagSafely(tagCompound, "Inventory", itemList); @@ -217,7 +237,7 @@ public class TileEntityArcaneWorkbench extends TileEntity implements IInventory, @Override public SPacketUpdateTileEntity getUpdatePacket(){ - return new SPacketUpdateTileEntity(pos, getBlockMetadata(), this.getUpdateTag()); + return new SPacketUpdateTileEntity(pos, 0, this.getUpdateTag()); } @Override diff --git a/src/main/java/electroblob/wizardry/tileentity/TileEntityBookshelf.java b/src/main/java/electroblob/wizardry/tileentity/TileEntityBookshelf.java new file mode 100644 index 00000000..dca10eb4 --- /dev/null +++ b/src/main/java/electroblob/wizardry/tileentity/TileEntityBookshelf.java @@ -0,0 +1,298 @@ +package electroblob.wizardry.tileentity; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.block.BlockBookshelf; +import electroblob.wizardry.inventory.ContainerBookshelf; +import electroblob.wizardry.util.NBTExtras; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.inventory.Container; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.play.server.SPacketUpdateTileEntity; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntityLockableLoot; +import net.minecraft.util.ITickable; +import net.minecraft.util.NonNullList; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraftforge.common.util.Constants.NBT; + +import javax.annotation.Nullable; + +public class TileEntityBookshelf extends TileEntityLockableLoot implements ITickable { + + /** NBT key for the boolean flag specifying if this bookshelf was generated naturally as part of a structure or not. + * This flag is set via {@link TileEntityBookshelf#markAsNatural(NBTTagCompound)}. */ + private static final String NATURAL_NBT_KEY = "NaturallyGenerated"; + /** When a non-spectating player comes within this distance of a naturally-generated bookshelf, it will automatically + * generate its loot if a loot table was set. This means the bookshelves do not incorrectly appear empty before a + * player looks inside. */ // Kind of a trade-off between not seeing them appear and not triggering from miles away + private static final int LOOT_GEN_DISTANCE = 32; // Nobody is likely to be looking from more than 32 blocks away + + /** The inventory of the bookshelf. */ + private NonNullList inventory; + + /** Whether this bookshelf was generated naturally as part of a structure. This determines whether the loot is + * automatically generated (and synced) when a non-spectating player comes within {@value LOOT_GEN_DISTANCE} blocks, + * so the bookshelf doesn't look empty until opened (loot is generated with that player in the context). */ + private boolean natural; + private boolean doNotSync; + + public TileEntityBookshelf(){ + inventory = NonNullList.withSize(BlockBookshelf.SLOT_COUNT, ItemStack.EMPTY); + // Prevent sync() happening when loading from NBT or weirdness ensues when loading a world + // Normally I'd pass this as a flag to setInventorySlotContents but we can't change the method signature + this.doNotSync = true; + } + + /** Called to manually sync the tile entity with clients. */ + public void sync(){ + if(!this.doNotSync) + this.world.markAndNotifyBlock(pos, null, world.getBlockState(pos), world.getBlockState(pos), 3); + } + + @Override + public void update(){ + + this.doNotSync = false; + + // When a player gets near, generate the books so they can actually see them (if it was generated naturally) + if(lootTable != null && natural){ + EntityPlayer player = world.getClosestPlayer(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, + LOOT_GEN_DISTANCE, false); + if(player != null){ + natural = false; // It's a normal bookshelf now (unlikely to matter but you never know) + fillWithLoot(player); + sync(); + } + } + } + + @Override + public int getSizeInventory(){ + return inventory.size(); + } + + // Still better to override these three because then we can sync only when necessary + + @Override + public ItemStack decrStackSize(int slot, int amount){ + + this.fillWithLoot(null); + + ItemStack stack = getStackInSlot(slot); + + if(!stack.isEmpty()){ + if(stack.getCount() <= amount){ + setInventorySlotContents(slot, ItemStack.EMPTY); + }else{ + stack = stack.splitStack(amount); + if(stack.getCount() == 0){ + setInventorySlotContents(slot, ItemStack.EMPTY); + } + } + this.markDirty(); + } + + return stack; + } + + @Override + public ItemStack removeStackFromSlot(int slot){ + + this.fillWithLoot(null); + + ItemStack stack = getStackInSlot(slot); + + if(!stack.isEmpty()){ + setInventorySlotContents(slot, ItemStack.EMPTY); + } + + return stack; + } + + @Override + public void setInventorySlotContents(int slot, ItemStack stack){ + this.fillWithLoot(null); + boolean wasEmpty = inventory.get(slot).isEmpty(); + super.setInventorySlotContents(slot, stack); + // This must be done in the tile entity because containers only exist for player interaction, not hoppers etc. + if(wasEmpty != stack.isEmpty()) this.sync(); + this.markDirty(); + } + + /** Sets the {@value NATURAL_NBT_KEY} flag to true in the given NBT tag compound, if the compound belongs to + * a bookshelf tile entity (more specifically, if it has an "id" tag matching the bookshelf TE's registry name). */ + public static void markAsNatural(NBTTagCompound nbt){ + if(nbt != null && nbt.getString("id").equals(TileEntity.getKey(TileEntityBookshelf.class).toString())){ + nbt.setBoolean(NATURAL_NBT_KEY, true); + } + } + + @Override + public void fillWithLoot(@Nullable EntityPlayer player){ + if(world != null && world.getLootTableManager() != null) super.fillWithLoot(player); // IntelliJ is wrong, it can be null + } + + @Override + public String getName(){ + return "container." + Wizardry.MODID + ":bookshelf"; + } + + @Override + public boolean hasCustomName(){ + return false; + } + + @Override + public ITextComponent getDisplayName(){ + return this.hasCustomName() ? new TextComponentString(this.getName()) : new TextComponentTranslation(this.getName()); + } + + @Override + public int getInventoryStackLimit(){ + return 64; + } + + @Override + public boolean isUsableByPlayer(EntityPlayer player){ + return world.getTileEntity(pos) == this && player.getDistanceSqToCenter(pos) < 64; + } + + @Override + public void openInventory(EntityPlayer player){ + + } + + @Override + public void closeInventory(EntityPlayer player){ + + } + + @Override + public boolean isItemValidForSlot(int slotNumber, ItemStack stack){ + return stack.isEmpty() || ContainerBookshelf.isBook(stack); + } + + @Override + public void readFromNBT(NBTTagCompound nbt){ + + super.readFromNBT(nbt); + + natural = nbt.getBoolean(NATURAL_NBT_KEY); + + if(!this.checkLootAndRead(nbt)){ + + NBTTagList tagList = nbt.getTagList("Inventory", NBT.TAG_COMPOUND); + + for(int i = 0; i < tagList.tagCount(); i++){ + NBTTagCompound tag = tagList.getCompoundTagAt(i); + byte slot = tag.getByte("Slot"); + if(slot >= 0 && slot < getSizeInventory()){ + setInventorySlotContents(slot, new ItemStack(tag)); + } + } + } + + if(nbt.hasKey("CustomName", NBT.TAG_STRING)) this.customName = nbt.getString("CustomName"); + } + + @Override + public NBTTagCompound writeToNBT(NBTTagCompound nbt){ + + super.writeToNBT(nbt); + + // Need to save this in case the block was generated but no players came near enough to trigger loot gen + nbt.setBoolean(NATURAL_NBT_KEY, natural); + + if(!this.checkLootAndWrite(nbt)){ + + NBTTagList itemList = new NBTTagList(); + + for(int i = 0; i < getSizeInventory(); i++){ + ItemStack stack = getStackInSlot(i); + NBTTagCompound tag = new NBTTagCompound(); + tag.setByte("Slot", (byte)i); + stack.writeToNBT(tag); + itemList.appendTag(tag); + } + + NBTExtras.storeTagSafely(nbt, "Inventory", itemList); + } + + if(this.hasCustomName()) nbt.setString("CustomName", this.customName); + + return nbt; + } + + @Override + public final NBTTagCompound getUpdateTag(){ + return this.writeToNBT(new NBTTagCompound()); + } + + @Override + public SPacketUpdateTileEntity getUpdatePacket(){ + return new SPacketUpdateTileEntity(pos, 0, this.getUpdateTag()); + } + + @Override + public void onDataPacket(NetworkManager net, SPacketUpdateTileEntity pkt){ + readFromNBT(pkt.getNbtCompound()); + Wizardry.proxy.notifyBookshelfChange(world, pos); + } + + // What are all these for? + + @Override + public int getField(int id){ + return 0; + } + + @Override + public void setField(int id, int value){ + + } + + @Override + public int getFieldCount(){ + return 0; + } + + @Override + public void clear(){ + for(int i = 0; i < getSizeInventory(); i++){ + setInventorySlotContents(i, ItemStack.EMPTY); + } + } + + @Override + public boolean isEmpty(){ + for(int i = 0; i < getSizeInventory(); i++){ + if(!getStackInSlot(i).isEmpty()){ + return false; + } + } + return true; + } + + @Override + protected NonNullList getItems(){ + return inventory; + } + + @Override + public Container createContainer(InventoryPlayer playerInventory, EntityPlayer player){ + this.fillWithLoot(player); + return new ContainerBookshelf(playerInventory, this); + } + + @Override + public String getGuiID(){ + return Wizardry.MODID + ":bookshelf"; + } + +} diff --git a/src/main/java/electroblob/wizardry/tileentity/TileEntityImbuementAltar.java b/src/main/java/electroblob/wizardry/tileentity/TileEntityImbuementAltar.java new file mode 100644 index 00000000..919699cf --- /dev/null +++ b/src/main/java/electroblob/wizardry/tileentity/TileEntityImbuementAltar.java @@ -0,0 +1,285 @@ +package electroblob.wizardry.tileentity; + +import electroblob.wizardry.block.BlockReceptacle; +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.item.IManaStoringItem; +import electroblob.wizardry.item.ItemWizardArmour; +import electroblob.wizardry.registry.*; +import electroblob.wizardry.util.GeometryUtils; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.play.server.SPacketUpdateTileEntity; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.ITickable; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import net.minecraft.world.WorldServer; +import net.minecraft.world.storage.loot.LootContext; +import net.minecraft.world.storage.loot.LootTable; +import org.apache.commons.lang3.ArrayUtils; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +public class TileEntityImbuementAltar extends TileEntity implements ITickable { + + private static final int IMBUEMENT_DURATION = 140; + + private ItemStack stack; + private int imbuementTimer; + private Element displayElement; + private EntityPlayer lastUser; + /** For loading purposes only. This does not get updated once loaded! */ + private UUID lastUserUUID; + + public TileEntityImbuementAltar(){ + stack = ItemStack.EMPTY; + } + + public void setStack(ItemStack stack){ + this.stack = stack; + checkRecipe(); + } + + public void setLastUser(EntityPlayer player){ + this.lastUser = player; + } + + public void checkRecipe(){ + + if(getResult().isEmpty()){ + imbuementTimer = 0; + }else if(imbuementTimer == 0){ + imbuementTimer = 1; + }else{ + return; // Don't sync if nothing changed + } + + world.notifyBlockUpdate(pos, world.getBlockState(pos), world.getBlockState(pos), 3); // Sync + } + + public ItemStack getStack(){ + return stack; + } + + @Override + public void update(){ + + if(lastUserUUID != null && lastUser == null) lastUser = world.getPlayerEntityByUUID(lastUserUUID); + + if(imbuementTimer > 0){ + + if(imbuementTimer == 1){ // Has to be done here because of syncing + world.playSound(pos.getX() + 0.5, pos.getY() + 1, pos.getZ() + 0.5, + WizardrySounds.BLOCK_IMBUEMENT_ALTAR_IMBUE, SoundCategory.BLOCKS, 1, 1, false); + } + + ItemStack result = getResult(); + + if(result.isEmpty()){ + imbuementTimer = 0; + + }else{ + + if(imbuementTimer++ >= IMBUEMENT_DURATION){ + this.stack = result; + consumeReceptacleContents(); + imbuementTimer = 0; + displayElement = null; + if(lastUser instanceof EntityPlayerMP){ + WizardryAdvancementTriggers.imbuement_altar.trigger((EntityPlayerMP)lastUser, this.stack); + } + } + + if(world.isRemote && world.rand.nextInt(2) == 0){ + + Element[] elements = getReceptacleElements(); + + Vec3d centre = GeometryUtils.getCentre(this.pos.up()); + + for(int i = 0; i < elements.length; i++){ + + if(elements[i] == null) continue; + + Vec3d offset = new Vec3d(EnumFacing.byHorizontalIndex(i).getDirectionVec()); + Vec3d vec = GeometryUtils.getCentre(this.pos).add(0, 0.3, 0).add(offset.scale(0.7)); + + int[] colours = BlockReceptacle.PARTICLE_COLOURS.get(elements[i]); + + ParticleBuilder.create(Type.DUST, world.rand, vec.x, vec.y, vec.z, 0.1, false) + .vel(centre.subtract(vec).scale(0.02)).clr(colours[1]).fade(colours[2]).time(50).spawn(world); + } + } + } + } + } + + /** Returns the element to use for the visual ray effect colours, or null if they should not be displayed. */ + public Element getDisplayElement(){ + return displayElement; + } + + /** Returns how complete the current action is (from 0 to 1), or 0 if no action is being performed. */ + public float getImbuementProgress(){ + return (float)imbuementTimer / IMBUEMENT_DURATION; + } + + private ItemStack getResult(){ + + boolean actuallyCrafting = imbuementTimer >= IMBUEMENT_DURATION - 1 && world instanceof WorldServer; + Element[] elements = getReceptacleElements(); + + ItemStack result = getImbuementResult(stack, elements, actuallyCrafting, world, lastUser); + + if(result.isEmpty()){ + displayElement = null; + }else if(Arrays.stream(elements).distinct().count() == 1){ // All the same element + displayElement = elements[0]; + }else{ + displayElement = Element.MAGIC; + } + + return result; + } + + /** Returns the elements of the 4 adjacent receptacles, in SWNE order. Null means an empty or missing receptacle. */ + private Element[] getReceptacleElements(){ + + Element[] elements = new Element[4]; + + for(EnumFacing side : EnumFacing.HORIZONTALS){ + + TileEntity tileEntity = world.getTileEntity(pos.offset(side)); + + if(tileEntity instanceof TileEntityReceptacle){ + elements[side.getHorizontalIndex()] = ((TileEntityReceptacle)tileEntity).getElement(); + }else{ + elements[side.getHorizontalIndex()] = null; + } + } + + return elements; + } + + /** Empties the 4 adjacent receptacles. */ + private void consumeReceptacleContents(){ + + for(EnumFacing side : EnumFacing.HORIZONTALS){ + + TileEntity tileEntity = world.getTileEntity(pos.offset(side)); + + if(tileEntity instanceof TileEntityReceptacle){ + ((TileEntityReceptacle)tileEntity).setElement(null); + } + } + } + + @Override + public NBTTagCompound writeToNBT(NBTTagCompound nbt){ + super.writeToNBT(nbt); + NBTTagCompound itemTag = new NBTTagCompound(); + stack.writeToNBT(itemTag); + nbt.setTag("item", itemTag); + nbt.setInteger("imbuementTimer", imbuementTimer); + if(lastUser != null) nbt.setUniqueId("lastUser", lastUser.getUniqueID()); + return nbt; + } + + @Override + public void readFromNBT(NBTTagCompound nbt){ + super.readFromNBT(nbt); + NBTTagCompound itemTag = nbt.getCompoundTag("item"); + this.stack = new ItemStack(itemTag); + this.imbuementTimer = nbt.getInteger("imbuementTimer"); + this.lastUserUUID = nbt.getUniqueId("lastUser"); + } + + @Override + public NBTTagCompound getUpdateTag(){ + return this.writeToNBT(new NBTTagCompound()); + } + + @Nullable + @Override + public SPacketUpdateTileEntity getUpdatePacket(){ + return new SPacketUpdateTileEntity(pos, 0, this.getUpdateTag()); + } + + @Override + public void onDataPacket(NetworkManager net, SPacketUpdateTileEntity pkt){ + readFromNBT(pkt.getNbtCompound()); + } + + /** + * Returns the stack that results from imbuing the given input with the given elements. + * @param input The item stack being imbued + * @param receptacleElements The elements of the four receptacles + * @param fullLootGen True to perform full loot generation from loot tables, false if this is just being queried for + * visuals or other purposes. + * @param world A reference to the current world object (may be null if {@code fullLootGen} is false) + * @param lastUser The player that last interacted with the imbuement altar, or null if there isn't one (or if this + * is being queried for other reasons, e.g. JEI) + * @return The resulting item stack, or an empty stack if the given combination is not a valid imbuement + */ + public static ItemStack getImbuementResult(ItemStack input, Element[] receptacleElements, boolean fullLootGen, World world, EntityPlayer lastUser){ + + if(input.getItem() instanceof ItemWizardArmour && ((ItemWizardArmour)input.getItem()).element == null){ + + if(Arrays.stream(receptacleElements).distinct().count() == 1 && receptacleElements[0] != null){ // All the same element + + ItemStack result = new ItemStack(WizardryItems.getArmour(receptacleElements[0], ((ItemWizardArmour)input.getItem()).armorType)); + + result.setTagCompound(input.getTagCompound()); + ((IManaStoringItem)result.getItem()).setMana(result, ((ItemWizardArmour)input.getItem()).getMana(input)); + + return result; + } + } + + if((input.getItem() == WizardryItems.magic_crystal || input.getItem() == Item.getItemFromBlock(WizardryBlocks.crystal_block)) + && input.getMetadata() == 0){ + + if(Arrays.stream(receptacleElements).distinct().count() == 1 && receptacleElements[0] != null){ // All the same element + return new ItemStack(input.getItem(), input.getCount(), receptacleElements[0].ordinal()); + } + } + + if(input.getItem() == WizardryItems.ruined_spell_book){ + + if(!ArrayUtils.contains(receptacleElements, null)){ // All receptacles filled (any element) + + if(fullLootGen){ + + // Pick a random element out of the receptacles and use its loot table + // This is an elegant way of having the dust elements affect the spell outcome without hardcoding + // any actual numbers, thus allowing packmakers as much control as possible over the weighting + // The probabilities are a little complicated but work out quite nicely at 57% chance with 4 of the + // same element of spectral dust + Element element = receptacleElements[world.rand.nextInt(receptacleElements.length)]; + LootTable table = world.getLootTableManager().getLootTableFromLocation( + WizardryLoot.RUINED_SPELL_BOOK_LOOT_TABLES[element.ordinal() - 1]); + LootContext context = new LootContext.Builder((WorldServer)world).withPlayer(lastUser) + .withLuck(lastUser == null ? 0 : lastUser.getLuck()).build(); + + List stacks = table.generateLootForPools(world.rand, context); + return stacks.isEmpty() ? ItemStack.EMPTY : stacks.get(0); + } + + return new ItemStack(WizardryItems.spell_book); // No point generating loot every tick just to check the recipe + } + } + + return ItemStack.EMPTY; + } + +} diff --git a/src/main/java/electroblob/wizardry/tileentity/TileEntityLectern.java b/src/main/java/electroblob/wizardry/tileentity/TileEntityLectern.java new file mode 100644 index 00000000..25bad27b --- /dev/null +++ b/src/main/java/electroblob/wizardry/tileentity/TileEntityLectern.java @@ -0,0 +1,98 @@ +package electroblob.wizardry.tileentity; + +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.spell.Spell; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.play.server.SPacketUpdateTileEntity; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.ITickable; +import net.minecraft.util.math.MathHelper; + +import java.util.Random; + +/** Controls the book animations and remembers the GUI state when not in use. */ +public class TileEntityLectern extends TileEntity implements ITickable { + + public static final double BOOK_OPEN_DISTANCE = 5; + + private static final Random rand = new Random(); + + public int ticksExisted; + public float pageFlip; + public float pageFlipPrev; + public float flipT; + public float flipA; + public float bookSpread; + public float bookSpreadPrev; + + public Spell currentSpell = Spells.none; + + @Override + public void update(){ + + this.bookSpreadPrev = this.bookSpread; + + EntityPlayer entityplayer = this.world.getClosestPlayer(this.pos.getX() + 0.5, this.pos.getY() + 0.5, + this.pos.getZ() + 0.5, BOOK_OPEN_DISTANCE, false); + + if(entityplayer != null){ + + this.bookSpread += 0.1f; + + if(this.bookSpread < 0.5f || rand.nextInt(40) == 0){ + float f1 = this.flipT; + while(f1 == flipT) this.flipT += (float)(rand.nextInt(4) - rand.nextInt(4)); + } + + }else{ + this.bookSpread -= 0.1f; + } + + this.bookSpread = MathHelper.clamp(this.bookSpread, 0.0f, 1.0f); + + this.ticksExisted++; + + this.pageFlipPrev = this.pageFlip; + float f = (this.flipT - this.pageFlip) * 0.4f; + f = MathHelper.clamp(f, -0.2f, 0.2f); + this.flipA += (f - this.flipA) * 0.9f; + this.pageFlip += this.flipA; + + } + + /** Called to manually sync the tile entity with clients. */ + public void sync(){ + this.world.markAndNotifyBlock(pos, null, world.getBlockState(pos), world.getBlockState(pos), 3); + } + + @Override + public NBTTagCompound writeToNBT(NBTTagCompound compound){ + super.writeToNBT(compound); // Confusingly, this method both writes to the supplied compound and returns it + compound.setInteger("spell", currentSpell.metadata()); + return compound; + } + + @Override + public void readFromNBT(NBTTagCompound compound){ + super.readFromNBT(compound); + currentSpell = Spell.byMetadata(compound.getInteger("spell")); + } + + @Override + public final NBTTagCompound getUpdateTag(){ + return this.writeToNBT(new NBTTagCompound()); + } + + @Override + public SPacketUpdateTileEntity getUpdatePacket(){ + return new SPacketUpdateTileEntity(pos, 0, this.getUpdateTag()); + } + + @Override + public void onDataPacket(NetworkManager net, SPacketUpdateTileEntity pkt){ + readFromNBT(pkt.getNbtCompound()); + } + +} diff --git a/src/main/java/electroblob/wizardry/tileentity/TileEntityMagicLight.java b/src/main/java/electroblob/wizardry/tileentity/TileEntityMagicLight.java index 2fa0ab68..112aa1b3 100644 --- a/src/main/java/electroblob/wizardry/tileentity/TileEntityMagicLight.java +++ b/src/main/java/electroblob/wizardry/tileentity/TileEntityMagicLight.java @@ -2,6 +2,9 @@ package electroblob.wizardry.tileentity; import net.minecraft.nbt.NBTTagCompound; +// TODO: Remove this class entirely, it can just be a TileEntityTimer (see RenderImbuementAltar for a better solution) +// Either that or use a FastTESR +@Deprecated public class TileEntityMagicLight extends TileEntityTimer { public int[] randomiser; diff --git a/src/main/java/electroblob/wizardry/tileentity/TileEntityPlayerSave.java b/src/main/java/electroblob/wizardry/tileentity/TileEntityPlayerSave.java index 6e91f534..f19ed2a1 100644 --- a/src/main/java/electroblob/wizardry/tileentity/TileEntityPlayerSave.java +++ b/src/main/java/electroblob/wizardry/tileentity/TileEntityPlayerSave.java @@ -1,10 +1,12 @@ package electroblob.wizardry.tileentity; import electroblob.wizardry.Wizardry; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.util.EntityUtils; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.play.server.SPacketUpdateTileEntity; import net.minecraft.tileentity.TileEntity; import javax.annotation.Nullable; @@ -19,14 +21,15 @@ public class TileEntityPlayerSave extends TileEntity { public TileEntityPlayerSave(){} - public TileEntityPlayerSave(EntityLivingBase caster){ - this.casterUUID = caster.getUniqueID(); + /** Called to manually sync the tile entity with clients. */ + public void sync(){ + this.world.markAndNotifyBlock(pos, null, world.getBlockState(pos), world.getBlockState(pos), 3); } @Override public void readFromNBT(NBTTagCompound tagCompound){ super.readFromNBT(tagCompound); - casterUUID = tagCompound.getUniqueId("casterUUID"); + if(tagCompound.hasUniqueId("casterUUID")) casterUUID = tagCompound.getUniqueId("casterUUID"); } @Override @@ -34,7 +37,7 @@ public class TileEntityPlayerSave extends TileEntity { super.writeToNBT(tagCompound); - if(this.getCaster() != null){ + if(casterUUID != null){ tagCompound.setUniqueId("casterUUID", casterUUID); } @@ -49,7 +52,7 @@ public class TileEntityPlayerSave extends TileEntity { @Nullable public EntityLivingBase getCaster(){ - Entity entity = WizardryUtilities.getEntityByUUID(world, casterUUID); + Entity entity = EntityUtils.getEntityByUUID(world, casterUUID); if(entity != null && !(entity instanceof EntityLivingBase)){ // Should never happen Wizardry.logger.warn("{} has a non-living owner!", this); @@ -63,4 +66,19 @@ public class TileEntityPlayerSave extends TileEntity { this.casterUUID = caster == null ? null : caster.getUniqueID(); } + @Override + public final NBTTagCompound getUpdateTag(){ + return this.writeToNBT(new NBTTagCompound()); + } + + @Override + public SPacketUpdateTileEntity getUpdatePacket(){ + return new SPacketUpdateTileEntity(pos, 0, this.getUpdateTag()); + } + + @Override + public void onDataPacket(NetworkManager net, SPacketUpdateTileEntity pkt){ + readFromNBT(pkt.getNbtCompound()); + } + } diff --git a/src/main/java/electroblob/wizardry/tileentity/TileEntityPlayerSaveTimed.java b/src/main/java/electroblob/wizardry/tileentity/TileEntityPlayerSaveTimed.java deleted file mode 100644 index 92f4ab37..00000000 --- a/src/main/java/electroblob/wizardry/tileentity/TileEntityPlayerSaveTimed.java +++ /dev/null @@ -1,69 +0,0 @@ -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/TileEntityReceptacle.java b/src/main/java/electroblob/wizardry/tileentity/TileEntityReceptacle.java new file mode 100644 index 00000000..9967f91c --- /dev/null +++ b/src/main/java/electroblob/wizardry/tileentity/TileEntityReceptacle.java @@ -0,0 +1,64 @@ +package electroblob.wizardry.tileentity; + +import electroblob.wizardry.constants.Element; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.play.server.SPacketUpdateTileEntity; +import net.minecraft.tileentity.TileEntity; + +import javax.annotation.Nullable; + +public class TileEntityReceptacle extends TileEntity { + + private Element element; + + public TileEntityReceptacle(){ + this(null); + } + + public TileEntityReceptacle(Element element){ + this.element = element; + } + + @Nullable + public Element getElement(){ + return element; + } + + public void setElement(@Nullable Element element){ + this.element = element; + world.notifyNeighborsRespectDebug(pos, blockType, true); // Update altar if connected + world.checkLight(pos); + } + + @Override + public NBTTagCompound writeToNBT(NBTTagCompound compound){ + super.writeToNBT(compound); + compound.setInteger("Element", element == null ? -1 : element.ordinal()); + return compound; + } + + @Override + public void readFromNBT(NBTTagCompound compound){ + super.readFromNBT(compound); + int i = compound.getInteger("Element"); + element = i == -1 ? null : Element.values()[i]; + } + + @Override + public NBTTagCompound getUpdateTag(){ + return this.writeToNBT(new NBTTagCompound()); + } + + @Nullable + @Override + public SPacketUpdateTileEntity getUpdatePacket(){ + return new SPacketUpdateTileEntity(pos, 0, this.getUpdateTag()); + } + + @Override + public void onDataPacket(NetworkManager net, SPacketUpdateTileEntity pkt){ + readFromNBT(pkt.getNbtCompound()); + } + +} diff --git a/src/main/java/electroblob/wizardry/tileentity/TileEntityShrineCore.java b/src/main/java/electroblob/wizardry/tileentity/TileEntityShrineCore.java index f34a834b..6bde7079 100644 --- a/src/main/java/electroblob/wizardry/tileentity/TileEntityShrineCore.java +++ b/src/main/java/electroblob/wizardry/tileentity/TileEntityShrineCore.java @@ -11,10 +11,8 @@ 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.*; 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; @@ -54,7 +52,7 @@ public class TileEntityShrineCore extends TileEntity implements ITickable { private void initContainmentField(BlockPos pos){ float r = PotionContainment.getContainmentDistance(0); - this.containmentField = new AxisAlignedBB(-r, -r, -r, r, r, r).offset(WizardryUtilities.getCentre(pos)); + this.containmentField = new AxisAlignedBB(-r, -r, -r, r, r, r).offset(GeometryUtils.getCentre(pos)); } public void linkContainer(TileEntity container){ @@ -94,7 +92,7 @@ public class TileEntityShrineCore extends TileEntity implements ITickable { 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); + Integer y1 = BlockUtils.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 @@ -128,7 +126,7 @@ public class TileEntityShrineCore extends TileEntity implements ITickable { private boolean areWizardsDead(){ for(UUID uuid : linkedWizards){ - Entity entity = WizardryUtilities.getEntityByUUID(world, uuid); + Entity entity = EntityUtils.getEntityByUUID(world, uuid); if(entity instanceof EntityEvilWizard && entity.isEntityAlive()) return false; } diff --git a/src/main/java/electroblob/wizardry/tileentity/TileEntityStatue.java b/src/main/java/electroblob/wizardry/tileentity/TileEntityStatue.java index 4f07e8e1..86580482 100644 --- a/src/main/java/electroblob/wizardry/tileentity/TileEntityStatue.java +++ b/src/main/java/electroblob/wizardry/tileentity/TileEntityStatue.java @@ -1,7 +1,7 @@ package electroblob.wizardry.tileentity; +import electroblob.wizardry.util.BlockUtils; import electroblob.wizardry.util.NBTExtras; -import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.block.Block; import net.minecraft.entity.EntityList; import net.minecraft.entity.EntityLiving; @@ -46,6 +46,8 @@ public class TileEntityStatue extends TileEntity implements ITickable { /** * The position within the petrified creature this particular tileentity holds. 1 is at the bottom. */ + // TODO: Remove this, there is no need for more than 1 TE per statue - actually, there is in the case of petrified + // creatures, which should show the block breaking animation on all parts... except this is also broken! public int position = 1; public void setCreatureAndPart(EntityLiving entity, int position, int parts){ @@ -108,7 +110,7 @@ public class TileEntityStatue extends TileEntity implements ITickable { // Breaks the block at light levels of 7 or below, with a higher chance the lower the light level. // The chance is (8 - light level)/12, so at light 0 the chance is 3/4 and at light 7 the chance is 1/12. if(!this.world.isRemote && this.timer % 200 == 0 && this.timer > lifetime && !this.isIce && this.position == 1){ - if(WizardryUtilities.getLightLevel(world, pos) < this.world.rand.nextInt(12) - 3){ + if(BlockUtils.getLightLevel(world, pos) < this.world.rand.nextInt(12) - 3){ // This is all that is needed because destroyBlock invokes the breakBlock function in // BlockPetrifiedStone // and that function handles all the spawning and stuff. diff --git a/src/main/java/electroblob/wizardry/tileentity/TileEntityThorns.java b/src/main/java/electroblob/wizardry/tileentity/TileEntityThorns.java new file mode 100644 index 00000000..614a60b6 --- /dev/null +++ b/src/main/java/electroblob/wizardry/tileentity/TileEntityThorns.java @@ -0,0 +1,61 @@ +package electroblob.wizardry.tileentity; + +import electroblob.wizardry.block.BlockThorns; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.ITickable; + +public class TileEntityThorns extends TileEntityPlayerSave implements ITickable { + + private int ticksExisted = 0; + private int lifetime; + private int age; + + public float damageMultiplier = 1; + + public TileEntityThorns(){ + this.lifetime = 600; + } + + @Override + public void update(){ + + ticksExisted++; + + if(ticksExisted > lifetime && !this.world.isRemote){ + this.world.destroyBlock(pos, false); + } + + if(ticksExisted % BlockThorns.GROWTH_STAGE_DURATION == 0 && age < BlockThorns.GROWTH_STAGES - 1){ + age++; + sync(); // Update displayed block + } + } + + public int getAge(){ + return age; + } + + public void setLifetime(int lifetime){ + this.lifetime = lifetime; + } + + @Override + public void readFromNBT(NBTTagCompound tagCompound){ + super.readFromNBT(tagCompound); + ticksExisted = tagCompound.getInteger("timer"); + lifetime = tagCompound.getInteger("maxTimer"); // Left as maxTimer for backwards compatibility + age = tagCompound.getInteger("age"); + damageMultiplier = tagCompound.getFloat("damageMultiplier"); + } + + @Override + public NBTTagCompound writeToNBT(NBTTagCompound tagCompound){ + super.writeToNBT(tagCompound); + tagCompound.setInteger("timer", ticksExisted); + tagCompound.setInteger("maxTimer", lifetime); + tagCompound.setInteger("age", age); + tagCompound.setFloat("damageMultiplier", damageMultiplier); + return tagCompound; + } + +} diff --git a/src/main/java/electroblob/wizardry/tileentity/TileEntityTimer.java b/src/main/java/electroblob/wizardry/tileentity/TileEntityTimer.java index 185bbd55..d2e8ecc3 100644 --- a/src/main/java/electroblob/wizardry/tileentity/TileEntityTimer.java +++ b/src/main/java/electroblob/wizardry/tileentity/TileEntityTimer.java @@ -40,6 +40,10 @@ public class TileEntityTimer extends TileEntity implements ITickable { this.maxTimer = lifetime; } + public int getLifetime(){ + return maxTimer; + } + @Override public void readFromNBT(NBTTagCompound tagCompound){ super.readFromNBT(tagCompound); diff --git a/src/main/java/electroblob/wizardry/util/AllyDesignationSystem.java b/src/main/java/electroblob/wizardry/util/AllyDesignationSystem.java index 0734ed5d..c5079bcc 100644 --- a/src/main/java/electroblob/wizardry/util/AllyDesignationSystem.java +++ b/src/main/java/electroblob/wizardry/util/AllyDesignationSystem.java @@ -4,10 +4,7 @@ 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.*; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; import net.minecraftforge.common.util.FakePlayer; @@ -17,7 +14,7 @@ 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 + * setting. This was split off from {@code WizardryUtilities} as of wizardry 4.2 in an effort to make the code easier to * navigate. */ @Mod.EventBusSubscriber @@ -111,6 +108,9 @@ public final class AllyDesignationSystem { */ public static boolean isValidTarget(Entity attacker, Entity target){ + // Owned entities inherit their owner's allies + if(attacker instanceof IEntityOwnable && !isValidTarget(((IEntityOwnable)attacker).getOwner(), target)) return false; + // Always return false if the target is null if(target == null) return false; @@ -123,6 +123,14 @@ public final class AllyDesignationSystem { // I really shouldn't need to do this, but fake players seem to break stuff... if(target instanceof FakePlayer) return false; + // Use a positive check for these rather than a negative check for monsters, because we only want mobs + // that are definitely passive + if(Wizardry.settings.passiveMobsAreAllies && (target.isCreatureType(EnumCreatureType.AMBIENT, false) + || target.isCreatureType(EnumCreatureType.CREATURE, false) + || target.isCreatureType(EnumCreatureType.WATER_CREATURE, false))){ + 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; @@ -139,7 +147,7 @@ public final class AllyDesignationSystem { NBTTagCompound entityNBT = target.getEntityData(); if(entityNBT != null && entityNBT.hasUniqueId(MindControl.NBT_KEY)){ - if(attacker == WizardryUtilities.getEntityByUUID(target.world, + if(attacker == EntityUtils.getEntityByUUID(target.world, entityNBT.getUniqueId(MindControl.NBT_KEY))){ return false; } @@ -164,7 +172,7 @@ public final class AllyDesignationSystem { }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)); + if(isOwnerAlly((EntityPlayer)attacker, (IEntityOwnable)target)) 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 @@ -172,7 +180,7 @@ public final class AllyDesignationSystem { if(entityNBT != null && entityNBT.hasKey(MindControl.NBT_KEY)){ - Entity controller = WizardryUtilities.getEntityByUUID(target.world, entityNBT.getUniqueId(MindControl.NBT_KEY)); + Entity controller = EntityUtils.getEntityByUUID(target.world, entityNBT.getUniqueId(MindControl.NBT_KEY)); if(controller instanceof EntityPlayer && WizardData.get((EntityPlayer)attacker).isPlayerAlly((EntityPlayer)controller)){ return false; @@ -185,13 +193,29 @@ public final class AllyDesignationSystem { } /** 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))); + * {@link AllyDesignationSystem#isOwnerAlly(EntityPlayer, IEntityOwnable)}, returning true if the second entity is + * either owned by the first entity, an ally of the first entity, or owned by an ally of the first entity. This is + * generally used to determine targets for healing or other group buffs. */ + public static boolean isAllied(EntityLivingBase allyOf, EntityLivingBase possibleAlly){ + + // Owned entities inherit their owner's allies + if(allyOf instanceof IEntityOwnable){ + Entity owner = ((IEntityOwnable)allyOf).getOwner(); + if(owner instanceof EntityLivingBase && isAllied((EntityLivingBase)owner, possibleAlly)) return true; + } + + if(allyOf instanceof EntityPlayer && possibleAlly instanceof EntityPlayer + && isPlayerAlly((EntityPlayer)allyOf, (EntityPlayer)possibleAlly)){ + return true; + } + + if(possibleAlly instanceof IEntityOwnable){ + IEntityOwnable pet = (IEntityOwnable)possibleAlly; + if(pet.getOwner() == allyOf) return true; + if(allyOf instanceof EntityPlayer && isOwnerAlly((EntityPlayer)allyOf, pet)) return true; + } + + return false; } /** Helper method for testing if the second player is an ally of the first player. Makes the code neater. diff --git a/src/main/java/electroblob/wizardry/util/BlockUtils.java b/src/main/java/electroblob/wizardry/util/BlockUtils.java new file mode 100644 index 00000000..8390353c --- /dev/null +++ b/src/main/java/electroblob/wizardry/util/BlockUtils.java @@ -0,0 +1,652 @@ +package electroblob.wizardry.util; + +import electroblob.wizardry.Settings; +import electroblob.wizardry.Wizardry; +import net.minecraft.block.*; +import net.minecraft.block.material.Material; +import net.minecraft.block.properties.IProperty; +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.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.init.Biomes; +import net.minecraft.init.Blocks; +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 net.minecraft.world.WorldServer; +import net.minecraft.world.biome.Biome; +import net.minecraftforge.common.BiomeDictionary; +import net.minecraftforge.common.ForgeHooks; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.util.BlockSnapshot; +import net.minecraftforge.common.util.FakePlayerFactory; +import net.minecraftforge.event.ForgeEventFactory; +import net.minecraftforge.event.world.BlockEvent; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiPredicate; +import java.util.function.Predicate; + +/** + * Contains useful static methods for interacting with blocks and block states. These methods used to be part of + * {@code WizardryUtilities}. + * @see GeometryUtils + * @see EntityUtils + * @author Electroblob + * @since Wizardry 4.3 + */ +public final class BlockUtils { + + private BlockUtils(){} // No instances! + + // General + // =============================================================================================================== + + /** + * 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", "rawtypes"}) // 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; + } + + /** + * Returns the actual light level at the given position, taking natural light (skylight) and artificial light + * (block light) into account. This uses the same logic as mob spawning. + * @param world The world the position is in + * @param pos The position to query + * @return The light level, from 0 (pitch darkness) to 15 (full daylight/at a torch). + */ + public static int getLightLevel(World world, BlockPos pos){ + + int i = world.getLightFromNeighbors(pos); + + if(world.isThundering()){ + int j = world.getSkylightSubtracted(); + world.setSkylightSubtracted(10); + i = world.getLightFromNeighbors(pos); + world.setSkylightSubtracted(j); + } + + return i; + } + + /** + * 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(), with a few extra bits. + * + * @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 BlockUtils#canBlockBeReplaced(World, BlockPos) + */ + 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 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 BlockUtils#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 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:

+ * {@code world.getBlockState(pos).getBlockHardness(world, pos) == -1.0f} + */ + public static boolean isBlockUnbreakable(World world, BlockPos pos){ + return !world.isAirBlock(new BlockPos(pos)) && world.getBlockState(pos).getBlockHardness(world, pos) == -1.0f; + } + + /** + * Gets the blockstate of the block the specified entity is standing on. Uses + * {@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){ + BlockPos pos = new BlockPos(MathHelper.floor(entity.posX), (int)entity.posY - 1, + MathHelper.floor(entity.posZ)); + 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; + } + + // Region protection compatibility + // =============================================================================================================== + + // This is the best I can do to play nicely with world protection mods/plugins like WorldGuard. As before, the + // playerBlockDamage config option is the ONLY anti-grief feature that is guaranteed to be 100% watertight. + + // For Forge mods, it's not too bad, we can just make sure we trigger the appropriate Forge events (or call other + // methods that subsequently trigger them). For breaking, there are four events that claim mods might hook into: + // - PlayerInteractEvent.LeftClickBlock (I actually use this one myself for arcane lock, it's completely seamless) + // - PlayerEvent.BreakSpeed (not really what it's for, but it is possible to prevent it this way. Can show the first + // frame of the breaking animation but otherwise seamless. Not fired in creative!) + // - LivingDestroyBlockEvent (useful for wizards, probably not intended for players) + // - BlockEvent.BreakEvent (the most obvious one but also the last to be fired. Shows the entire animation before + // cancelling, looks super ugly) + // As long as we trigger the last three, it should be fine! Also note that PlayerInteractEvent.LeftClickBlock is fired + // every tick during breaking (only once in creative). + + // For Bukkit plugins, it's exceedingly difficult! We have to make sure we call the VANILLA methods that will then + // trigger Bukkit's hooks for plugins to respond to. These are likely to also trigger the relevant Forge events, but + // to be safe we should trigger everything we possibly can that won't have too many side-effects. + // Building Gadgets' implementation is a useful resource which I'm pretty sure works with WorldGuard: + // https://github.com/Direwolf20-MC/BuildingGadgets/blob/1.12.x/src/main/java/com/direwolf20/buildinggadgets/common/items/gadgets/GadgetBuilding.java + // See also: https://worldguard.enginehub.org/en/latest/regions/scope/#mod-and-plugin-support + + /** + * Tests whether the given entity may place the given block at the given location. Does not actually place the block + * (this gives callers more flexibility in how they use it, for example it allows different block placement methods + * in {@link World} to be used depending on the situation). This should only be called server-side. + *

+ * Be aware that this method triggers a variety of vanilla methods and Forge events, and depending on other + * installed mods these may have side-effects. Use of this method just to query is therefore not advised - if it + * returns true, callers should actually place the block to ensure behaviour is consistent for other mods. + *

+ * Also note that this method does not do blockstate-specific checks (notice it doesn't even take the + * block being placed as a parameter; this is in line with the block place events). It is up to the caller to + * determine whether the position is suitable for placing whatever kind of block is to be placed. + * @param placer The entity doing the placing, or null if the block is not being placed by an entity + * @param world The world in which the block is being placed + * @param pos The position of the block being placed + * @return True if the given entity is allowed to place the block, false otherwise. + */ + public static boolean canPlaceBlock(@Nullable Entity placer, World world, BlockPos pos){ + + if(world.isRemote){ + Wizardry.logger.warn("BlockUtils#canPlaceBlock called from the client side! Blocks should be modified server-side only"); + return true; + } + + if(!EntityUtils.canDamageBlocks(placer, world)) return false; // General block damage prevention comes first + + if(world.isOutsideBuildHeight(pos)) return false; + // This line *should* trigger bukkit plugin hooks + if(placer instanceof EntityPlayer && !world.isBlockModifiable((EntityPlayer)placer, pos)) return false; + + BlockSnapshot snapshot = BlockSnapshot.getBlockSnapshot(world, pos); + // Despite there being a separate event for players, BOTH events seem to be fired for players during normal placement + if(ForgeEventFactory.onBlockPlace(placer, snapshot, EnumFacing.UP).isCanceled()) return false; + + if(placer instanceof EntityPlayer && ForgeEventFactory.onPlayerBlockPlace( + (EntityPlayer)placer, snapshot, EnumFacing.UP, EnumHand.MAIN_HAND).isCanceled()){ + return false; + } + + return true; + } + + /** + * Tests whether the given entity may break the block at the given location. Does not actually break the block (this + * gives callers more flexibility in how they use it, for example it allows different block break methods in + * {@link World} to be used depending on whether particles, sound, drops, etc. are desired). This should only be + * called server-side. + *

+ * This method is a shorthand for {@link BlockUtils#checkBlockBreakXP(Entity, World, BlockPos)} {@code >= 0}. + *

+ * Be aware that this method triggers a variety of vanilla methods and Forge events, and depending on other + * installed mods these may have side-effects. Use of this method just to query is therefore not advised - if it + * returns true, callers should actually break the block to ensure behaviour is consistent for other mods. + * @param breaker The entity doing the breaking, or null if the block is not being broken by an entity + * @param world The world in which the block is being broken + * @param pos The position of the block being broken + * @return True if the given entity is allowed to break the block, false otherwise. + */ + public static boolean canBreakBlock(@Nullable Entity breaker, World world, BlockPos pos){ + return checkBlockBreakXP(breaker, world, pos) >= 0; + } + + /** + * Tests whether the given entity may break the block at the given location and returns the xp it should drop. Does + * not actually break the block or drop the xp (this gives callers more flexibility in how they use it, for example + * it allows different block break methods in {@link World} to be used depending on whether particles, sound, drops, + * etc. are desired). This should only be called server-side. + *

+ * Be aware that this method triggers a variety of vanilla methods and Forge events, and depending on other + * installed mods these may have side-effects. Use of this method just to query is therefore not advised - if it + * returns true, callers should actually break the block to ensure behaviour is consistent for other mods. + * @param breaker The entity doing the breaking, or null if the block is not being broken by an entity + * @param world The world in which the block is being broken + * @param pos The position of the block being broken + * @return The xp to be dropped if the given entity is allowed to break the block, -1 if the block cannot be broken. + */ + public static int checkBlockBreakXP(@Nullable Entity breaker, World world, BlockPos pos){ + + if(world.isRemote){ + Wizardry.logger.warn("BlockUtils#checkBlockBreakXP called from the client side! Blocks should be modified server-side only"); + return 0; + } + + if(!EntityUtils.canDamageBlocks(breaker, world)) return -1; // General block damage prevention comes first + + if(world.isOutsideBuildHeight(pos)) return -1; + + EntityPlayer fakeplayer = FakePlayerFactory.getMinecraft((WorldServer)world); + + if(breaker instanceof EntityPlayer){ + // This line *should* trigger bukkit plugin hooks + if(!world.isBlockModifiable((EntityPlayer)breaker, pos)) return -1; + }else if(breaker == null){ + // Also need this for dispensers + if(!world.isBlockModifiable(fakeplayer, pos)) return -1; + } + + // I think this is irrelevant in forge because it's only for vanilla entities, but bukkit might use it + IBlockState state = world.getBlockState(pos); + if(!state.getBlock().canEntityDestroy(state, world, pos, breaker)) return -1; + // Although the forge event only needs an EntityLivingBase, it seems it's not supposed to be for players + if(breaker instanceof EntityLiving && ForgeEventFactory.onEntityDestroyBlock((EntityLivingBase)breaker, pos, state)) return -1; + // Need to trigger PlayerEvent.BreakSpeed as some claim mods (e.g. LandManager) use it instead of BlockEvent.BreakEvent + if(breaker instanceof EntityPlayer && ForgeEventFactory.getBreakSpeed((EntityPlayer)breaker, state, 1, pos) < 0) return -1; + + if(breaker == null){ // Dispensers need to trigger something, so use a fake player as per BreakEvent's comment + if(MinecraftForge.EVENT_BUS.post(new BlockEvent.BreakEvent(world, pos, state, fakeplayer))){ + return -1; + } + } + + int xp = 0; + + if(breaker instanceof EntityPlayerMP){ + xp = ForgeHooks.onBlockBreakEvent(world, ((EntityPlayerMP)breaker).interactionManager.getGameType(), (EntityPlayerMP)breaker, pos); + } + + return xp; + } + + // Specific blocks + // =============================================================================================================== + + /** + * Returns true if the block at the given position is a tree block (or other 'solid' vegetation, such as cacti). + * Used for structure generation. + * @param world The world the block is in + * @param pos The position of the block to be tested + * @return True if the given block is a tree block, false if not. + */ + public static boolean isTreeBlock(World world, BlockPos pos){ + Block block = world.getBlockState(pos).getBlock(); + return block instanceof BlockLog || block instanceof BlockCactus + || block.isLeaves(world.getBlockState(pos), world, pos) || block.isFoliage(world, pos) + || Settings.containsMetaBlock(Wizardry.settings.treeBlocks, world.getBlockState(pos)); + } + + /** + * Returns the predominant wood type (see {@link net.minecraft.block.BlockPlanks.EnumType}) for the given biome, or + * the default {@link net.minecraft.block.BlockPlanks.EnumType#OAK} for biomes with no trees. This is accurate for + * most vanilla biomes + * @param biome The biome to query + * @return The wood type for the given biome + */ + public 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; + } + + /** + * Given the position of one half of a double chest, returns the position of the other half of that double chest. + * @param world The world in which the chest is located + * @param pos The position of one half of the double chest + * @return The position of the other half of the double chest, or null if the given position is not part of a + * double chest. + */ + public static BlockPos getConnectedChest(World world, BlockPos pos){ + + Block block = world.getBlockState(pos).getBlock(); + + if(block instanceof BlockChest){ + for(EnumFacing enumfacing : EnumFacing.Plane.HORIZONTAL){ + BlockPos pos1 = pos.offset(enumfacing); + if(world.getBlockState(pos1).getBlock() == block){ + return pos1; + } + } + } + + return null; + } + + /** + * Returns true if the given block is a water source block (specifically, water or flowing water with a level of 0). + * @param state The block state to query + * @return True if the given block state is a water source block, false otherwise. + */ + public static boolean isWaterSource(IBlockState state){ + return state.getMaterial() == Material.WATER && (state.getBlock() == Blocks.WATER || state.getBlock() == Blocks.FLOWING_WATER) && state.getValue(BlockLiquid.LEVEL) == 0; + } + + /** + * Returns true if the given block is a lava source block (specifically, lava or flowing lava with a level of 0). + * @param state The block state to query + * @return True if the given block state is a lava source block, false otherwise. + */ + public static boolean isLavaSource(IBlockState state){ + return state.getMaterial() == Material.LAVA && (state.getBlock() == Blocks.LAVA || state.getBlock() == Blocks.FLOWING_LAVA) && state.getValue(BlockLiquid.LEVEL) == 0; + } + + /** + * Freezes the given block, either by turning water to ice, lava to obsidian/cobblestone or by placing snow on top + * of it if possible. + * @param world The world the block is in + * @param pos The position of the block to freeze + * @param freezeLava True to freeze lava into obsidian or cobblestone, false to leave it unchanged + * @return True if any blocks were changed, false if not. + */ + public static boolean freeze(World world, BlockPos pos, boolean freezeLava){ + + IBlockState state = world.getBlockState(pos); + Block block = state.getBlock(); + + if(isWaterSource(state)){ + world.setBlockState(pos, Blocks.ICE.getDefaultState()); + }else if(freezeLava && isLavaSource(state)){ + world.setBlockState(pos, Blocks.OBSIDIAN.getDefaultState()); + }else if(freezeLava && (block == Blocks.LAVA || block == Blocks.FLOWING_LAVA)){ + world.setBlockState(pos, Blocks.COBBLESTONE.getDefaultState()); + }else if(block.isReplaceable(world, pos.up()) && Blocks.SNOW_LAYER.canPlaceBlockAt(world, pos.up())){ + world.setBlockState(pos.up(), Blocks.SNOW_LAYER.getDefaultState()); + }else{ + return false; + } + + return true; + } + + // Floor finding + // =============================================================================================================== + + /** + * 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)GeometryUtils.component(GeometryUtils.getFaceCentre(testPos, direction), direction.getAxis()); + currentBest = Math.abs(i); + } + } + + return surface; + } + + /** + * Finds the nearest floor level in the given direction from the given position, + * within the range specified. This is a shorthand for + * {@link BlockUtils#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); + } + + /** + * 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 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. + * @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(Entity entity, int horizontalRange, int verticalRange){ + + World world = entity.world; + 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){ + return findNearbyFloorSpace(world, origin, horizontalRange, verticalRange, true); + } + + /** + * 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. + * @param lineOfSight Whether to require line-of-sight from the origin to the returned 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, boolean lineOfSight){ + + List possibleLocations = new ArrayList<>(); + + final Vec3d centre = GeometryUtils.getCentre(origin); + + for(int x = -horizontalRange; x <= horizontalRange; x++){ + for(int z = -horizontalRange; z <= horizontalRange; z++){ + + Integer y = getNearestFloor(world, origin.add(x, 0, z), verticalRange); + + if(y != null){ + + BlockPos location = new BlockPos(origin.getX() + x, y, origin.getZ() + z); + + if(lineOfSight){ + // Since we're only using finding collidable surfaces, it doesn't make much sense to include + // non-collidable blocks here! + RayTraceResult rayTrace = world.rayTraceBlocks(centre, GeometryUtils.getCentre(location), + false, true, false); + if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK) continue; + } + + possibleLocations.add(location); + } + } + } + + if(possibleLocations.isEmpty()){ + return null; + }else{ + return possibleLocations.get(world.rand.nextInt(possibleLocations.size())); + } + } + + /** + * A {@code SurfaceCriteria} object is used to define a 'surface', a boundary between two blocks which differ in + * some way, for use in {@link BlockUtils#getNearestSurface(World, BlockPos, EnumFacing, int, boolean, SurfaceCriteria)}. + * This provides a more flexible replacement for the old {@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.offset(side)); + + /** 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, and is also good for placing fire. */ + // 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() && !isTreeBlock(world, pos)); + + } + +} diff --git a/src/main/java/electroblob/wizardry/util/Box.java b/src/main/java/electroblob/wizardry/util/Box.java new file mode 100644 index 00000000..291b0967 --- /dev/null +++ b/src/main/java/electroblob/wizardry/util/Box.java @@ -0,0 +1,33 @@ +package electroblob.wizardry.util; + +/** + * A very simple container for a single object of a given type {@code T} (referred to as the contents). Allows + * fixed-size collections to be made using their immutable (or unmodifiable) counterparts. This is particularly useful + * for maps with a fixed set of keys but whose values may be changed. + */ +public class Box { + + private T contents; + + public Box(T contents){ + this.contents = contents; + } + + /** Returns the contents of this box. */ + public T get(){ + return contents; + } + + /** Sets the contents of this box to the given object. */ + public void set(T contents){ + this.contents = contents; + } + + // Probably misleading since methods like this generally imply immutability, which is exactly what we're not doing + +// /** Creates a new box with the given contents. This is just a static factory method that calls the constructor. */ +// public static Box of(T contents){ +// return new Box<>(contents); +// } + +} diff --git a/src/main/java/electroblob/wizardry/util/EntityUtils.java b/src/main/java/electroblob/wizardry/util/EntityUtils.java new file mode 100644 index 00000000..0386188c --- /dev/null +++ b/src/main/java/electroblob/wizardry/util/EntityUtils.java @@ -0,0 +1,460 @@ +package electroblob.wizardry.util; + +import com.google.common.collect.Streams; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.data.WizardData; +import electroblob.wizardry.entity.living.ISpellCaster; +import electroblob.wizardry.item.ISpellCastingItem; +import electroblob.wizardry.spell.Spell; +import net.minecraft.entity.Entity; +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.item.ItemStack; +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.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.Vec3d; +import net.minecraft.world.EnumDifficulty; +import net.minecraft.world.World; +import net.minecraftforge.event.ForgeEventFactory; +import net.minecraftforge.fml.common.ObfuscationReflectionHelper; + +import javax.annotation.Nullable; +import java.util.Comparator; +import java.util.List; +import java.util.UUID; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * Contains useful static methods for retrieving and interacting with players, mobs and other entities. These methods + * used to be part of {@code WizardryUtilities}. + * @see BlockUtils + * @see MagicDamage + * @see AllyDesignationSystem + * @author Electroblob + * @since Wizardry 4.3 + */ +public final class EntityUtils { + + private EntityUtils(){} // No instances! + + /** Changed to a constant in wizardry 2.1, since this is a lot more efficient. */ + private static final DataParameter POWERED; + + static { + // Null is passed in deliberately since POWERED is a static field. + POWERED = ObfuscationReflectionHelper.getPrivateValue(EntityCreeper.class, null, "field_184714_b"); + } + + /** Stores constant values for attribute modifier operations (and javadoc for what they actually do!) */ + // I'm fed up with remembering these... + public static final class Operations { + + private Operations(){} // No instances! + + /** 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; + } + + // Entity retrieval + // =============================================================================================================== + + /** + * Shorthand for {@link EntityUtils#getEntitiesWithinRadius(double, double, double, double, World, Class)} + * 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 + * @param y The y coordinate to search around + * @param z The z coordinate to search around + * @param world The world to search in + */ + public static List getLivingWithinRadius(double radius, double x, double y, double z, World world){ + return getEntitiesWithinRadius(radius, x, y, z, world, EntityLivingBase.class); + } + + /** + * Returns all entities of the specified type within the specified radius of the given coordinates. This is + * different to using a raw AABB because a raw AABB will search in a cube volume rather than a sphere. Note that + * this does not exclude any entities; if any specific entities are to be excluded this must be checked when + * iterating through the list. + * + * @see EntityUtils#getLivingWithinRadius(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 + * @param z The z coordinate to search around + * @param world The world to search in + * @param entityType The class of entity to search for; pass in Entity.class for all entities + */ + public static List getEntitiesWithinRadius(double radius, double x, double y, double z, World world, Class entityType){ + AxisAlignedBB aabb = new AxisAlignedBB(x - radius, y - radius, z - radius, x + radius, y + radius, z + radius); + List entityList = world.getEntitiesWithinAABB(entityType, aabb); + for(int i = 0; i < entityList.size(); i++){ + if(entityList.get(i).getDistance(x, y, z) > radius){ + entityList.remove(i); + break; + } + } + return entityList; + } + + /** + * 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, @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, + // this was unnoticeable until world reload because the UUID instance or entity instance is stored. + // Fixed now though. + if(entity != null && entity.getUniqueID() != null && entity.getUniqueID().equals(id)){ + return entity; + } + } + return null; + } + + /** + * 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 empty first. + */ + @Nullable + public static Entity getRider(Entity entity){ + return !entity.getPassengers().isEmpty() ? entity.getPassengers().get(0) : null; + } + + // Motion + // =============================================================================================================== + + /** + * 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 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; + } + } + + /** + * Applies the standard (non-enchanted) amount of knockback to the given target, using the same calculation and + * strength value (0.4) as {@link EntityLivingBase#attackEntityFrom(DamageSource, float)}. Use in conjunction with + * {@link EntityUtils#attackEntityWithoutKnockback(Entity, DamageSource, float)} to change the source of + * knockback for an attack. + * + * @param attacker The entity that caused the knockback; the target will be pushed away from this entity + * @param target The entity to be knocked back + */ + public static void applyStandardKnockback(Entity attacker, EntityLivingBase target){ + applyStandardKnockback(attacker, target, 0.4f); + } + + /** + * Applies the standard knockback calculation to the given target, using the same calculation as + * {@link EntityLivingBase#attackEntityFrom(DamageSource, float)}. + * + * @param attacker The entity that caused the knockback; the target will be pushed away from this entity + * @param target The entity to be knocked back + * @param strength The strength of the knockback + */ + public static void applyStandardKnockback(Entity attacker, EntityLivingBase target, float strength){ + double dx = attacker.posX - target.posX; + double dz; + for(dz = attacker.posZ - target.posZ; dx * dx + dz * dz < 1.0E-4D; dz = (Math.random() - Math.random()) + * 0.01D){ + dx = (Math.random() - Math.random()) * 0.01D; + } + target.knockBack(attacker, strength, dx, dz); + } + + /** + * Finds the nearest space to the specified position that the given entity can teleport to without being inside one + * or more solid blocks. The search volume is twice the size of the entity's bounding box (meaning that when + * teleported to the returned position, the original destination remains within the entity's bounding box). + * @param entity The entity being teleported + * @param destination The target position to search around + * @param accountForPassengers True to take passengers into account when searching for a space, false to ignore them + * @return The resulting position, or null if no space was found. + */ + public static Vec3d findSpaceForTeleport(Entity entity, Vec3d destination, boolean accountForPassengers){ + + World world = entity.world; + AxisAlignedBB box = entity.getEntityBoundingBox(); + + if(accountForPassengers){ + for(Entity passenger : entity.getPassengers()){ + box = box.union(passenger.getEntityBoundingBox()); + } + } + + box = box.offset(destination.subtract(entity.posX, entity.posY, entity.posZ)); + + // All the parameters of this method are INCLUSIVE, so even the max coordinates should be rounded down + Iterable cuboid = BlockPos.getAllInBox(MathHelper.floor(box.minX), MathHelper.floor(box.minY), + MathHelper.floor(box.minZ), MathHelper.floor(box.maxX), MathHelper.floor(box.maxY), MathHelper.floor(box.maxZ)); + + if(Streams.stream(cuboid).noneMatch(b -> world.collidesWithAnyBlock(new AxisAlignedBB(b)))){ + // Nothing in the way + return destination; + + }else{ + // Nearby position search + double dx = box.maxX - box.minX; + double dy = box.maxY - box.minY; + double dz = box.maxZ - box.minZ; + + // Minimum space required is (nx + px) blocks * (ny + py) blocks * (nz + pz) blocks + int nx = MathHelper.ceil(dx) / 2; + int px = MathHelper.ceil(dx) - nx; + int ny = MathHelper.ceil(dy) / 2; + int py = MathHelper.ceil(dy) - ny; + int nz = MathHelper.ceil(dz) / 2; + int pz = MathHelper.ceil(dz) - nz; + + // Check all the blocks in and around the bounding box... + List nearby = Streams.stream(BlockPos.getAllInBox(MathHelper.floor(box.minX) - 1, + MathHelper.floor(box.minY) - 1, MathHelper.floor(box.minZ) - 1, + MathHelper.floor(box.maxX) + 1, MathHelper.floor(box.maxY) + 1, + MathHelper.floor(box.maxZ) + 1)).collect(Collectors.toList()); + + // ... but only return positions actually inside the box + List possiblePositions = Streams.stream(cuboid).collect(Collectors.toList()); + + // Rather than iterate over each position and check if the box fits, find all solid blocks and cut out all + // positions whose corresponding box would include them - this is waaay more efficient! + while(!nearby.isEmpty()){ + + BlockPos pos = nearby.remove(0); + + if(world.collidesWithAnyBlock(new AxisAlignedBB(pos))){ + Predicate nearSolidBlock = b -> b.getX() >= pos.getX() - nx && b.getX() <= pos.getX() + px + && b.getY() >= pos.getY() - ny && b.getY() <= pos.getY() + py + && b.getZ() >= pos.getZ() - nz && b.getZ() <= pos.getZ() + pz; + nearby.removeIf(nearSolidBlock); + possiblePositions.removeIf(nearSolidBlock); + } + } + + if(possiblePositions.isEmpty()) return null; // No space nearby + + BlockPos nearest = possiblePositions.stream().min(Comparator.comparingDouble(b -> destination.squareDistanceTo( + b.getX() + 0.5, b.getY() + 0.5, b.getZ() + 0.5))).get(); // The list can't be empty + + return GeometryUtils.getFaceCentre(nearest, EnumFacing.DOWN); + } + } + + // Damage + // =============================================================================================================== + + /** + * Attacks the given entity with the given damage source and amount, but preserving the entity's original velocity + * instead of applying knockback, as would happen with + * {@link EntityLivingBase#attackEntityFrom(DamageSource, float)} (More accurately, calls that method as normal + * and then resets the entity's velocity to what it was before). Handy for when you need to damage an entity + * repeatedly in a short space of time. + * + * @param entity The entity to attack + * @param source The source of the damage + * @param amount The amount of damage to apply + * @return True if the attack succeeded, false if not. + */ + public static boolean attackEntityWithoutKnockback(Entity entity, DamageSource source, float amount){ + double vx = entity.motionX; + double vy = entity.motionY; + double vz = entity.motionZ; + boolean succeeded = entity.attackEntityFrom(source, amount); + entity.motionX = vx; + entity.motionY = vy; + entity.motionZ = vz; + return succeeded; + } + + /** + * 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. + */ + 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; + } + + // Boolean checks + // =============================================================================================================== + + /** + * Returns true if the given entity is an EntityLivingBase and not an armour stand; makes the code a bit neater. + * This was added because armour stands are a subclass of EntityLivingBase, but shouldn't necessarily be treated + * as living entities - this depends on the situation. The given entity can safely be cast to EntityLivingBase + * if this method returns true. + */ + // In my opinion, it's a bad design choice to have armour stands extend EntityLivingBase directly - it would be + // better to make a parent class which is extended by both armour stands and EntityLivingBase and contains only + // the code required by both. + public static boolean isLiving(Entity entity){ + return entity instanceof EntityLivingBase && !(entity instanceof EntityArmorStand); + } + + /** + * Checks if the given player is opped on the given server. If the server is a singleplayer or LAN server, this + * means they have cheats enabled. + */ + 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 or null, + * this checks the player block damage config setting and (for players only) the {@code PlayerCapabilities} object, + * otherwise it posts a mob griefing event and returns the result. + *

+ * This method only checks whether the entity can damage blocks in general. It can be useful in certain + * situations where breaking of individual blocks is out of the caller's control (e.g. explosions, which have their + * own events anyway) or as an optimisation where multiple blocks are affected. In most cases, however, it is + * advisable to use the location-specific methods in {@link BlockUtils}, as they call this method anyway and allow + * for better compatibility with other mods, particularly region protection mods. + */ + public static boolean canDamageBlocks(@Nullable Entity entity, World world){ + if(entity == null) return Wizardry.settings.dispenserBlockDamage; + else if(entity instanceof EntityPlayer) return ((EntityPlayer)entity).isAllowEdit() && Wizardry.settings.playerBlockDamage; + return ForgeEventFactory.getMobGriefingEvent(world, entity); + } + + /** + * 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. + */ + // 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; + + if(caster.isHandActive() && caster.getItemInUseMaxCount() >= spell.getChargeup()){ + + ItemStack stack = caster.getHeldItem(caster.getActiveHand()); + + if(stack.getItem() instanceof ISpellCastingItem && ((ISpellCastingItem)stack.getItem()).getCurrentSpell(stack) == spell){ + // Don't do this, it interferes with stuff! We effectively already tested this with caster.isHandActive() anyway +// && ((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; + } + + return false; + } + + // Misc + // =============================================================================================================== + + /** 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. + } + } + + /** + * 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(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); + } + + // No point allowing anything other than players for these methods since other entities can use Entity#playSound. + + /** + * Shortcut for + * {@link World#playSound(EntityPlayer, double, double, double, SoundEvent, SoundCategory, float, float)} where the + * player is null but the x, y and z coordinates are those of the passed in player. Use in preference to + * {@link EntityPlayer#playSound(SoundEvent, float, float)} if there are client-server discrepancies. + */ + public static void playSoundAtPlayer(EntityPlayer player, SoundEvent sound, SoundCategory category, float volume, + float pitch){ + player.world.playSound(null, player.posX, player.posY, player.posZ, sound, category, volume, pitch); + } + + /** + * See {@link EntityUtils#playSoundAtPlayer(EntityPlayer, SoundEvent, SoundCategory, float, float)}. Category + * defaults to {@link SoundCategory#PLAYERS}. + */ + public static void playSoundAtPlayer(EntityPlayer player, SoundEvent sound, float volume, float pitch){ + player.world.playSound(null, player.posX, player.posY, player.posZ, sound, SoundCategory.PLAYERS, volume, pitch); + } + +} diff --git a/src/main/java/electroblob/wizardry/util/GeometryUtils.java b/src/main/java/electroblob/wizardry/util/GeometryUtils.java new file mode 100644 index 00000000..0b14fb76 --- /dev/null +++ b/src/main/java/electroblob/wizardry/util/GeometryUtils.java @@ -0,0 +1,147 @@ +package electroblob.wizardry.util; + +import net.minecraft.entity.Entity; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumFacing.Axis; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.math.Vec3i; +import net.minecraft.world.World; + +/** + * Contains useful static methods for performing geometry operations on vectors, bounding boxes, {@code BlockPos} + * objects, etc. These methods used to be part of {@code WizardryUtilities}. + * @see Vec3d + * @see BlockPos + * @see EnumFacing + * @see RelativeFacing + * @see Location + * @see RayTracer + * @author Electroblob + * @since Wizardry 4.3 + */ +public final class GeometryUtils { + + private GeometryUtils(){} // No instances! + + /** 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; + + // Why is there a distanceSqToCentre method in Vec3i but not a getCentre method? + + /** + * 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 entity's bounding box. This is more + * efficient than {@code GeometryUtils.getCentre(entity.getEntityBoundingBox())} as it can use the entity's fields. + */ + public static Vec3d getCentre(Entity entity){ + return new Vec3d(entity.posX, entity.posY + entity.height/2, entity.posZ); + } + + /** + * 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 a normalised {@link Vec3d} with the same yaw angle as the given vector, but with a y component of zero. + */ + public static Vec3d horizontalise(Vec3d vec){ + return replaceComponent(vec, Axis.Y, 0).normalize(); + } + + /** + * 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; + } + +} diff --git a/src/main/java/electroblob/wizardry/util/IElementalDamage.java b/src/main/java/electroblob/wizardry/util/IElementalDamage.java index a3d12aeb..266ec5bd 100644 --- a/src/main/java/electroblob/wizardry/util/IElementalDamage.java +++ b/src/main/java/electroblob/wizardry/util/IElementalDamage.java @@ -40,7 +40,12 @@ public interface IElementalDamage { && !((EntityCreeper)event.getEntityLiving()).getPowered() && ((IElementalDamage)event.getSource()).getType() == DamageType.SHOCK){ // Charges creepers when they are hit by shock damage - WizardryUtilities.chargeCreeper((EntityCreeper)event.getEntityLiving()); + EntityUtils.chargeCreeper((EntityCreeper)event.getEntityLiving()); + } + + if(event.getEntityLiving().isEntityUndead() + && ((IElementalDamage)event.getSource()).getType() == DamageType.RADIANT){ + event.getEntityLiving().setFire(8); // Same as zombies/skeletons in sunlight } } } diff --git a/src/main/java/electroblob/wizardry/util/ISpellSortable.java b/src/main/java/electroblob/wizardry/util/ISpellSortable.java new file mode 100644 index 00000000..e6dd1ce7 --- /dev/null +++ b/src/main/java/electroblob/wizardry/util/ISpellSortable.java @@ -0,0 +1,35 @@ +package electroblob.wizardry.util; + +import electroblob.wizardry.spell.Spell; + +import java.util.Comparator; + +/** Interface for things that have a list of spells that can be sorted using {@link SortType}. This allows + * {@link electroblob.wizardry.client.gui.GuiButtonSpellSort GuiButtonSpellSort} to change its appearance based on the + * current sort setting. */ +public interface ISpellSortable { + + /** Returns the current sort type this {@code ISpellSortable} is set to sort by. */ + SortType getSortType(); + + /** Returns true if this {@code ISpellSortable} is currently set to sort in descending order, false if it is + * set to sort in ascending order. */ + boolean isSortDescending(); + + enum SortType { + + TIER("tier", Comparator.naturalOrder()), + ELEMENT("element", Comparator.comparing(Spell::getElement).thenComparing(Spell::getTier)), + ALPHABETICAL("alphabetical", Comparator.comparing(Spell::getUnlocalisedName)); + + public String name; + public Comparator comparator; + + SortType(String name, Comparator comparator){ + this.name = name; + this.comparator = comparator; + } + + } + +} diff --git a/src/main/java/electroblob/wizardry/util/IndirectMagicDamage.java b/src/main/java/electroblob/wizardry/util/IndirectMagicDamage.java index 826a23fa..957a6b46 100644 --- a/src/main/java/electroblob/wizardry/util/IndirectMagicDamage.java +++ b/src/main/java/electroblob/wizardry/util/IndirectMagicDamage.java @@ -13,6 +13,9 @@ public class IndirectMagicDamage extends EntityDamageSourceIndirect implements I super(name, magic, caster); this.type = type; this.isRetaliatory = isRetaliatory; + this.setMagicDamage(); + if(type == DamageType.FIRE) this.setFireDamage(); + if(type == DamageType.BLAST) this.setExplosion(); } @Override diff --git a/src/main/java/electroblob/wizardry/util/InventoryUtils.java b/src/main/java/electroblob/wizardry/util/InventoryUtils.java new file mode 100644 index 00000000..438458e0 --- /dev/null +++ b/src/main/java/electroblob/wizardry/util/InventoryUtils.java @@ -0,0 +1,145 @@ +package electroblob.wizardry.util; + +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +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.NBTTagCompound; +import net.minecraft.util.EnumHand; +import net.minecraft.util.EnumHandSide; +import net.minecraft.util.NonNullList; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Contains useful static methods for interacting with item stacks and entity inventories. These methods used to be part + * of {@code WizardryUtilities}. + * @author Electroblob + * @since Wizardry 4.3 + */ +public final class InventoryUtils { + + private InventoryUtils(){} // No instances! + + /** 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; + + static { + // The list of slots needs to be mutable. + List slots = new ArrayList<>(Arrays.asList(EntityEquipmentSlot.values())); + slots.removeIf(slot -> slot.getSlotType() != Type.ARMOR); + ARMOUR_SLOTS = slots.toArray(new EntityEquipmentSlot[0]); + } + + /** + * Returns a list of the itemstacks in the given player's hotbar. Defined here for convenience and to centralise the + * (unfortunately unavoidable) use of hardcoded numbers to reference the inventory slots. The returned list is a + * modifiable 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. + * + * @since Wizardry 1.2 + */ + public static List getHotbar(EntityPlayer player){ + NonNullList hotbar = NonNullList.create(); + hotbar.addAll(player.inventory.mainInventory.subList(0, 9)); + return hotbar; + } + + /** + * 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 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. + * + * @since Wizardry 1.2 + */ + public static List getPrioritisedHotbarAndOffhand(EntityPlayer player){ + List hotbar = getHotbar(player); + // Adds the offhand item to the beginning of the list so it is processed before the hotbar + hotbar.add(0, player.getHeldItemOffhand()); + // Moves the item in the main hand to the beginning of the list so it is processed first + hotbar.remove(player.getHeldItemMainhand()); + hotbar.add(0, player.getHeldItemMainhand()); + return hotbar; + } + + /** Returns which {@link EnumHandSide} the given {@link EnumHand} is on for the given entity. */ + public static EnumHandSide getSideForHand(EntityLivingBase entity, EnumHand hand){ + return hand == EnumHand.MAIN_HAND ? entity.getPrimaryHand() : entity.getPrimaryHand().opposite(); + } + + /** Returns which {@link EnumHand} is on the given {@link EnumHandSide} for the given entity. */ + public static EnumHand getHandForSide(EntityLivingBase entity, EnumHandSide side){ + return side == entity.getPrimaryHand() ? EnumHand.MAIN_HAND : EnumHand.OFF_HAND; + } + + /** Returns the opposite {@link EnumHand} to the one given. */ + public static EnumHand getOpposite(EnumHand hand){ + return hand == EnumHand.OFF_HAND ? EnumHand.MAIN_HAND : EnumHand.OFF_HAND; + } + + /** + * Tests whether the specified player has any of the specified item in their entire inventory, including armour + * slots and offhand. + */ + public static boolean doesPlayerHaveItem(EntityPlayer player, Item item){ + + for(ItemStack stack : player.inventory.mainInventory){ + if(stack.getItem() == item){ + return true; + } + } + + for(ItemStack stack : player.inventory.armorInventory){ + if(stack.getItem() == item){ + return true; + } + } + + for(ItemStack stack : player.inventory.offHandInventory){ + if(stack.getItem() == item){ + return true; + } + } + + return false; + } + + /** + * Returns a new {@link ItemStack} that is identical to the supplied one, except with the metadata changed to the + * new value given. + * @param toCopy The stack to copy + * @param newMetadata The new metadata value + * @return The resulting {@link ItemStack} + */ + public static ItemStack copyWithMeta(ItemStack toCopy, int newMetadata){ + ItemStack copy = new ItemStack(toCopy.getItem(), toCopy.getCount(), newMetadata); + NBTTagCompound compound = toCopy.getTagCompound(); + if(compound != null) copy.setTagCompound(compound.copy()); + return copy; + } + + /** + * Returns whether the two given item stacks can be merged, i.e. if they both contain the same (stackable) item, + * metadata and NBT. Importantly, the number of items in each stack need not be the same. No actual merging is + * performed by this method; the input stacks will not be modified. + * @param stack1 The first stack to be tested for mergeability + * @param stack2 The second stack to be tested for mergeability (order does not matter) + * @return True if the two stacks can be merged, false if not + */ + public static boolean canMerge(ItemStack stack1, ItemStack stack2){ + return !stack1.isEmpty() && !stack2.isEmpty() + && stack1.isStackable() && stack2.isStackable() + && stack1.getItem() == stack2.getItem() + && (!stack1.getHasSubtypes() || stack1.getMetadata() == stack2.getMetadata()) + && ItemStack.areItemStackTagsEqual(stack1, stack2); + } + +} diff --git a/src/main/java/electroblob/wizardry/util/JavaUtils.java b/src/main/java/electroblob/wizardry/util/JavaUtils.java new file mode 100644 index 00000000..5cf40028 --- /dev/null +++ b/src/main/java/electroblob/wizardry/util/JavaUtils.java @@ -0,0 +1,47 @@ +package electroblob.wizardry.util; + +import java.lang.reflect.Field; +import java.util.*; + +/** + * Contains useful static methods for interacting with Java itself, rather than Minecraft. These methods used to be part + * of {@code WizardryUtilities}. + * + * @author Electroblob + * @since Wizardry 4.3 + */ +public final class JavaUtils { + + /** + * 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 Collection flatten(Collection> collection){ + Collection result = new ArrayList<>(); + collection.forEach(result::addAll); + return Collections.unmodifiableCollection(result); + } + + // 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(); +// } + + /** + * Returns a list of all fields belonging to the given class and all those belonging to all of its superclasses. + * @param c The class to query + * @return The resulting {@link List} of fields + */ + public static List getAllFields(Class c){ + List fields = new ArrayList<>(Arrays.asList(c.getDeclaredFields())); + if(c.getSuperclass() != null) fields.addAll(getAllFields(c.getSuperclass())); + return fields; + } + +} diff --git a/src/main/java/electroblob/wizardry/util/MagicDamage.java b/src/main/java/electroblob/wizardry/util/MagicDamage.java index 8c0429c2..787c3cb3 100644 --- a/src/main/java/electroblob/wizardry/util/MagicDamage.java +++ b/src/main/java/electroblob/wizardry/util/MagicDamage.java @@ -71,7 +71,7 @@ public class MagicDamage extends EntityDamageSource implements IElementalDamage FIRE, /** Frost (ice) damage from wizardry. Snow golems, ice wraiths and ice giants are immune. */ FROST, - /** Shock (lightning) damage from wizardry. Lightning wraiths and storm elementals are immune. */ + /** Shock (lightning) damage from wizardry. Charges creepers. Lightning wraiths and storm elementals are immune. */ SHOCK, /** Wither damage from wizardry. Withers, wither skeletons and shadow wraiths are immune. */ WITHER, @@ -81,7 +81,7 @@ public class MagicDamage extends EntityDamageSource implements IElementalDamage FORCE, /** Blast damage from wizardry. Affected by the blast protection enchantment. */ BLAST, - /** Radiant damage from wizardry. */ + /** Radiant damage from wizardry. Sets fire to undead entities. */ RADIANT } diff --git a/src/main/java/electroblob/wizardry/util/NBTExtras.java b/src/main/java/electroblob/wizardry/util/NBTExtras.java index eac5464d..0d2fc636 100644 --- a/src/main/java/electroblob/wizardry/util/NBTExtras.java +++ b/src/main/java/electroblob/wizardry/util/NBTExtras.java @@ -10,7 +10,7 @@ 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. + * This was split off from {@code WizardryUtilities} as of wizardry 4.2 in an effort to make the code easier to navigate. * * @author Electroblob * @since Wizardry 4.2 @@ -250,27 +250,4 @@ public final class NBTExtras { return false; } - /** - * 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 index 273875b2..618be8b8 100644 --- a/src/main/java/electroblob/wizardry/util/ParticleBuilder.java +++ b/src/main/java/electroblob/wizardry/util/ParticleBuilder.java @@ -44,8 +44,9 @@ import java.util.Random; // 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. +// it's a singleton, because it's likely to be called very frequently and since this only happens from a single (client) +// thread, 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. */ @@ -93,15 +94,21 @@ public final class ParticleBuilder { 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"); + /** Square block face highlight particle.

Defaults:
Lifetime: 160 ticks
Colour: white */ + public static final ResourceLocation BLOCK_HIGHLIGHT = new ResourceLocation(Wizardry.MODID,"block_highlight"); /** 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"); + /** Large, thick cloud.

Defaults:
Lifetime: 48-60 ticks
Colour: dark grey */ + public static final ResourceLocation CLOUD = new ResourceLocation(Wizardry.MODID,"cloud"); /** 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"); + /** Particle that looks like the guardian's beam attack.

Defaults:
Lifetime: 1 tick */ + public static final ResourceLocation GUARDIAN_BEAM = new ResourceLocation(Wizardry.MODID,"guardian_beam"); /** 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) @@ -128,8 +135,6 @@ public final class ParticleBuilder { 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"); } @@ -227,7 +232,7 @@ public final class ParticleBuilder { * 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 + * Affects: All particle types * @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. @@ -324,7 +329,7 @@ public final class ParticleBuilder { * @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 + return this.fade(r/255f, g/255f, b/255f); // Yes, 255 is correct and not 256, or else we can't have pure white } /** @@ -341,7 +346,7 @@ public final class ParticleBuilder { int r = (hex & 0xFF0000) >> 16; int g = (hex & 0xFF00) >> 8; int b = (hex & 0xFF); - return this.clr(r, g, b); + return this.fade(r, g, b); } /** @@ -603,9 +608,9 @@ public final class ParticleBuilder { 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(x == 0 && y == 0 && z == 0 && entity == null) Wizardry.logger.warn("Spawning particle at (0, 0, 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 " @@ -766,7 +771,7 @@ public final class ParticleBuilder { 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 y = entity.posY + 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); } diff --git a/src/main/java/electroblob/wizardry/util/RayTracer.java b/src/main/java/electroblob/wizardry/util/RayTracer.java index 6079251d..09b675a1 100644 --- a/src/main/java/electroblob/wizardry/util/RayTracer.java +++ b/src/main/java/electroblob/wizardry/util/RayTracer.java @@ -14,7 +14,7 @@ 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. + * {@code WizardryUtilities} as of wizardry 4.2 in an effort to make the code easier to navigate. * * @author Electroblob * @since Wizardry 4.2 @@ -32,7 +32,10 @@ public final class RayTracer { * 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. + * not there. {@code ignoreUncollidables} must be set to false for this setting to have an effect. + * @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) * @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. */ @@ -40,7 +43,7 @@ public final class RayTracer { 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 origin = entity.getPositionEyes(1); Vec3d endpoint = origin.add(entity.getLookVec().scale(range)); return world.rayTraceBlocks(origin, endpoint, hitLiquids, ignoreUncollidables, returnLastUncollidable); } @@ -66,29 +69,31 @@ public final class RayTracer { * 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. + * not there. {@code ignoreUncollidables} must be set to false for this setting to have an effect. * @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 origin = entity.getPositionEyes(1); 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. + * which returns a {@link Predicate} that returns true for the given entity, plus any entities that 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); + // Use deathTime > 0 so we still hit stuff that has *just* died, otherwise SpellRay#onEntityHit doesn't get + // called on the client side when the entity is killed + return e -> e == entity || (e instanceof EntityLivingBase && ((EntityLivingBase)e).deathTime > 0); } /** @@ -105,7 +110,8 @@ public final class RayTracer { * @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 hitLiquids True to return hits on the surfaces of liquids, false to ignore liquid blocks as if they were + * not there. {@code ignoreUncollidables} must be set to false for this setting to have an effect. * @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) @@ -158,7 +164,7 @@ public final class RayTracer { // 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 + float fuzziness = EntityUtils.isLiving(entity) ? aimAssist : 0; // Only living entities have aim assist if(entity instanceof ICustomHitbox){ // Custom hitboxes intercept = ((ICustomHitbox)entity).calculateIntercept(origin, endpoint, fuzziness); diff --git a/src/main/java/electroblob/wizardry/util/SpellModifiers.java b/src/main/java/electroblob/wizardry/util/SpellModifiers.java index 93ea7e96..a0c30416 100644 --- a/src/main/java/electroblob/wizardry/util/SpellModifiers.java +++ b/src/main/java/electroblob/wizardry/util/SpellModifiers.java @@ -41,6 +41,8 @@ public final class SpellModifiers { 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 charge-up modifier. */ + public static final String CHARGEUP = "chargeup"; /** Constant string identifier for the wand progression modifier. */ public static final String PROGRESSION = "progression"; @@ -56,20 +58,15 @@ public final class SpellModifiers { 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; -// } + private SpellModifiers(Map multiplierMap, Map syncedMultiplierMap){ + this.multiplierMap = multiplierMap; + this.syncedMultiplierMap = syncedMultiplierMap; + } + + /** Returns a deep copy (with copies of the underlying maps) of this {@code SpellModifiers} object. */ + public SpellModifiers copy(){ + return new SpellModifiers(new HashMap<>(this.multiplierMap), new HashMap<>(this.syncedMultiplierMap)); + } /** * Adds the given multiplier to this SpellModifiers object, using the string identifier that the given wand upgrade diff --git a/src/main/java/electroblob/wizardry/util/SpellProperties.java b/src/main/java/electroblob/wizardry/util/SpellProperties.java index de12eb52..535c60a4 100644 --- a/src/main/java/electroblob/wizardry/util/SpellProperties.java +++ b/src/main/java/electroblob/wizardry/util/SpellProperties.java @@ -224,11 +224,23 @@ public final class SpellProperties { } /** - * Returns the base value for this spell that corresponds to the given identifier. + * Returns whether a base value was defined with the given identifier. + * @param identifier The string identifier to check for. + * @return True if a base value was defined with the given identifier, false otherwise. + */ + public boolean hasBaseValue(String identifier){ + return baseValues.containsKey(identifier); + } + + /** + * Returns the base value for this spell that corresponds to the given identifier. To check whether an identifier + * exists, use {@link SpellProperties#hasBaseValue(String)}. * @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. */ + // Better to throw an exception than make this nullable because the vast majority of uses are for retrieving + // specific spells' properties that are known to exist, and IntelliJ would scream at us for not checking public Number getBaseValue(String identifier){ if(!baseValues.containsKey(identifier)){ throw new IllegalArgumentException("Base value with identifier '" + identifier + "' is not defined."); diff --git a/src/main/java/electroblob/wizardry/util/WandHelper.java b/src/main/java/electroblob/wizardry/util/WandHelper.java index 677761a3..526500a7 100644 --- a/src/main/java/electroblob/wizardry/util/WandHelper.java +++ b/src/main/java/electroblob/wizardry/util/WandHelper.java @@ -52,18 +52,6 @@ public final class WandHelper { private static final HashMap upgradeMap = new HashMap<>(); - static { - upgradeMap.put(WizardryItems.condenser_upgrade, "condenser"); - upgradeMap.put(WizardryItems.storage_upgrade, "storage"); - upgradeMap.put(WizardryItems.siphon_upgrade, "siphon"); - upgradeMap.put(WizardryItems.range_upgrade, "range"); - upgradeMap.put(WizardryItems.duration_upgrade, "duration"); - 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 =================================================== /** @@ -170,6 +158,26 @@ public final class WandHelper { wand.getTagCompound().setInteger(SELECTED_SPELL_KEY, getPreviousSpellIndex(wand)); } } + + /** + * Selects the spell at the given index in this wand's list of spells. + * @param wand The stack to set the spell of + * @param index The spell index to set the wand to + * @return False if the index was out-of-bounds, true otherwise + */ + public static boolean selectSpell(ItemStack wand, int index){ + + if(index < 0 || index > getSpells(wand).length) return false; // Out-of-bounds + + // 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[ItemWand.BASE_SPELL_SLOTS]); + + if(wand.getTagCompound() != null){ + wand.getTagCompound().setInteger(SELECTED_SPELL_KEY, index); + } + + return true; + } private static int getNextSpellIndex(ItemStack wand){ @@ -197,7 +205,7 @@ public final class WandHelper { int spellIndex = wand.getTagCompound().getInteger(SELECTED_SPELL_KEY); if(spellIndex <= 0){ - spellIndex = numberOfSpells - 1; + spellIndex = Math.max(0, numberOfSpells - 1); }else{ spellIndex--; } @@ -350,7 +358,7 @@ public final class WandHelper { int selectedSpell = wand.getTagCompound().getInteger(SELECTED_SPELL_KEY); - if(cooldowns.length <= selectedSpell) return 0; + if(selectedSpell < 0 || cooldowns.length <= selectedSpell) return 0; return cooldowns[selectedSpell]; } @@ -433,7 +441,7 @@ public final class WandHelper { /** * Registers a special upgrade with wizardry. Not used in the base mod, but I've put it here to make it easy for - * add-ons to add new wand upgrades. + * add-ons to add new wand upgrades. This should be called during the init() phase of mod loading. * * @param upgrade The wand upgrade item * @param identifier A unique string, used as a key for wand nbt tags @@ -447,6 +455,19 @@ public final class WandHelper { upgradeMap.put(upgrade, identifier); } + /** Called from the init() method in wizardry's main mod class to populate the special wand upgrade map. */ + public static void populateUpgradeMap(){ + upgradeMap.put(WizardryItems.condenser_upgrade, "condenser"); + upgradeMap.put(WizardryItems.storage_upgrade, "storage"); + upgradeMap.put(WizardryItems.siphon_upgrade, "siphon"); + upgradeMap.put(WizardryItems.range_upgrade, "range"); + upgradeMap.put(WizardryItems.duration_upgrade, "duration"); + upgradeMap.put(WizardryItems.cooldown_upgrade, "cooldown"); + upgradeMap.put(WizardryItems.blast_upgrade, "blast"); + upgradeMap.put(WizardryItems.attunement_upgrade, "attunement"); + upgradeMap.put(WizardryItems.melee_upgrade, "melee"); + } + // ================================================= Progression ================================================= /** Sets the given wand's progression to the given value. */ diff --git a/src/main/java/electroblob/wizardry/util/WizardryUtilities.java b/src/main/java/electroblob/wizardry/util/WizardryUtilities.java deleted file mode 100644 index 93727027..00000000 --- a/src/main/java/electroblob/wizardry/util/WizardryUtilities.java +++ /dev/null @@ -1,971 +0,0 @@ -package electroblob.wizardry.util; - -import electroblob.wizardry.CommonProxy; -import electroblob.wizardry.Settings; -import electroblob.wizardry.Wizardry; -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.BlockCactus; -import net.minecraft.block.BlockLog; -import net.minecraft.block.properties.IProperty; -import net.minecraft.block.state.IBlockState; -import net.minecraft.entity.Entity; -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.NBTTagCompound; -import net.minecraft.network.datasync.DataParameter; -import net.minecraft.server.MinecraftServer; -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.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
- * - NBT and data storage utilities
- * - Interaction with the ally designation system
- * - Loot and weighting utilities - * - * @see CommonProxy - * @see WandHelper - * @since Wizardry 1.0 - * @author Electroblob - */ -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...) */ - 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 { - // The list of slots needs to be mutable. - 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 = 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; - } - - // World, Blocks and Coordinates - // =============================================================================================================== - - /** - * Returns the actual light level, taking natural light (skylight) and artificial light (block light) into account. - * This uses the same logic as mob spawning. - * - * @return The light level, from 0 (pitch darkness) to 15 (full daylight/at a torch). - */ - public static int getLightLevel(World world, BlockPos pos){ - - int i = world.getLightFromNeighbors(pos); - - if(world.isThundering()){ - int j = world.getSkylightSubtracted(); - world.setSkylightSubtracted(10); - i = world.getLightFromNeighbors(pos); - world.setSkylightSubtracted(j); - } - - return i; - } - - /** - * 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(). - * - * @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, 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 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 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:

- * {@code world.getBlockState(pos).getBlockHardness(world, pos) == -1.0f} - */ - public static boolean isBlockUnbreakable(World world, BlockPos pos){ - 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.offset(side)); - - /** 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() && !isTreeBlock(world, pos)); - - } - - /** - * Returns true if the block at the given position is a tree block (or other 'solid' vegetation, such as cacti). - * Used for structure generation. - * @param world The world the block is in - * @param pos The position of the block to be tested - * @return True if the given block is a tree block, false if not. - */ - public static boolean isTreeBlock(World world, BlockPos pos){ - Block block = world.getBlockState(pos).getBlock(); - return block instanceof BlockLog || block instanceof BlockCactus - || block.isLeaves(world.getBlockState(pos), world, pos) || block.isFoliage(world, pos) - || Settings.containsMetaBlock(Wizardry.settings.treeBlocks, world.getBlockState(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. - * 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. - * @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){ - 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 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. - * @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){ - 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 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. - * @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){ - 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 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 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. - * @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(Entity entity, int horizontalRange, int verticalRange){ - - World world = entity.world; - 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++){ - 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)); - } - } - - if(possibleLocations.isEmpty()){ - return null; - }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)} because casting to int will not return the correct coordinate when x or z - * is negative. - */ - public static IBlockState getBlockEntityIsStandingOn(Entity entity){ - BlockPos pos = new BlockPos(MathHelper.floor(entity.posX), (int)entity.getEntityBoundingBox().minY - 1, - MathHelper.floor(entity.posZ)); - 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. - * - * @param radius The search radius - * @param x The x coordinate to search around - * @param y The y coordinate to search around - * @param z The z coordinate to search around - * @param world The world to search in - */ - public static List getEntitiesWithinRadius(double radius, double x, double y, double z, - World world){ - return getEntitiesWithinRadius(radius, x, y, z, world, EntityLivingBase.class); - } - - /** - * Returns all entities of the specified type within the specified radius of the given coordinates. This is - * different to using a raw AABB because a raw AABB will search in a cube volume rather than a sphere. Note that - * this does not exclude any entities; if any specific entities are to be excluded this must be checked when - * iterating through the list. - * - * @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 - * @param z The z coordinate to search around - * @param world The world to search in - * @param entityType The class of entity to search for; pass in Entity.class for all entities - */ - public static List getEntitiesWithinRadius(double radius, double x, double y, double z, - World world, Class entityType){ - AxisAlignedBB aabb = new AxisAlignedBB(x - radius, y - radius, z - radius, x + radius, y + radius, z + radius); - List entityList = world.getEntitiesWithinAABB(entityType, aabb); - 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? - - /** - * 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, @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, - // this was unnoticeable until world reload because the UUID instance or entity instance is stored. - // Fixed now though. - if(entity != null && entity.getUniqueID() != null && entity.getUniqueID().equals(id)){ - return entity; - } - } - return null; - } - - // No point allowing anything other than players for these methods since other entities can use Entity#playSound. - - /** - * Shortcut for - * {@link World#playSound(EntityPlayer, double, double, double, SoundEvent, SoundCategory, float, float)} where the - * player is null but the x, y and z coordinates are those of the passed in player. Use in preference to - * {@link EntityPlayer#playSound(SoundEvent, float, float)} if there are client-server discrepancies. - */ - public static void playSoundAtPlayer(EntityPlayer player, SoundEvent sound, SoundCategory category, float volume, - float pitch){ - player.world.playSound(null, player.posX, player.posY, player.posZ, sound, category, volume, pitch); - } - - /** - * See {@link WizardryUtilities#playSoundAtPlayer(EntityPlayer, SoundEvent, SoundCategory, float, float)}. Category - * defaults to {@link SoundCategory#PLAYERS}. - */ - public static void playSoundAtPlayer(EntityPlayer player, SoundEvent sound, float volume, float pitch){ - 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 empty first. - */ - @Nullable - public static Entity getRider(Entity entity){ - return !entity.getPassengers().isEmpty() ? entity.getPassengers().get(0) : null; - } - - /** - * Attacks the given entity with the given damage source and amount, but preserving the entity's original velocity - * instead of applying knockback, as would happen with - * {@link EntityLivingBase#attackEntityFrom(DamageSource, float)} (More accurately, calls that method as normal - * and then resets the entity's velocity to what it was before). Handy for when you need to damage an entity - * repeatedly in a short space of time. - * - * @param entity The entity to attack - * @param source The source of the damage - * @param amount The amount of damage to apply - * @return True if the attack succeeded, false if not. - */ - public static boolean attackEntityWithoutKnockback(Entity entity, DamageSource source, float amount){ - double vx = entity.motionX; - double vy = entity.motionY; - double vz = entity.motionZ; - boolean succeeded = entity.attackEntityFrom(source, amount); - entity.motionX = vx; - entity.motionY = vy; - entity.motionZ = vz; - return succeeded; - } - - /** - * Applies the standard (non-enchanted) amount of knockback to the given target, using the same calculation as - * {@link EntityLivingBase#attackEntityFrom(DamageSource, float)}. Use in conjunction with - * {@link WizardryUtilities#attackEntityWithoutKnockback(Entity, DamageSource, float)} to change the source of - * knockback for an attack. - * - * @param attacker The entity that caused the knockback; the target will be pushed away from this entity. - * @param target The entity to be knocked back. - */ - public static void applyStandardKnockback(Entity attacker, EntityLivingBase target){ - double dx = attacker.posX - target.posX; - double dz; - for(dz = attacker.posZ - target.posZ; dx * dx + dz * dz < 1.0E-4D; dz = (Math.random() - Math.random()) - * 0.01D){ - dx = (Math.random() - Math.random()) * 0.01D; - } - target.knockBack(attacker, 0.4f, dx, dz); - } - - /** - * 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 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; - } - } - - /** - * Returns a list of the itemstacks in the given player's hotbar. Defined here for convenience and to centralise the - * (unfortunately unavoidable) use of hardcoded numbers to reference the inventory slots. The returned list is a - * modifiable 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. - * - * @since Wizardry 1.2 - */ - public static List getHotbar(EntityPlayer player){ - NonNullList hotbar = NonNullList.create(); - hotbar.addAll(player.inventory.mainInventory.subList(0, 9)); - return hotbar; - } - - /** - * 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 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. - * - * @since Wizardry 1.2 - */ - public static List getPrioritisedHotbarAndOffhand(EntityPlayer player){ - List hotbar = WizardryUtilities.getHotbar(player); - // Adds the offhand item to the beginning of the list so it is processed before the hotbar - hotbar.add(0, player.getHeldItemOffhand()); - // Moves the item in the main hand to the beginning of the list so it is processed first - hotbar.remove(player.getHeldItemMainhand()); - hotbar.add(0, player.getHeldItemMainhand()); - return hotbar; - } - - /** - * Tests whether the specified player has any of the specified item in their entire inventory, including armour - * slots and offhand. - */ - public static boolean doesPlayerHaveItem(EntityPlayer player, Item item){ - - for(ItemStack stack : player.inventory.mainInventory){ - if(stack.getItem() == item){ - return true; - } - } - - for(ItemStack stack : player.inventory.armorInventory){ - if(stack.getItem() == item){ - return true; - } - } - - for(ItemStack stack : player.inventory.offHandInventory){ - if(stack.getItem() == item){ - return true; - } - } - - return false; - } - - /** - * Returns a new {@link ItemStack} that is identical to the supplied one, except with the metadata changed to the - * new value given. - * @param toCopy The stack to copy - * @param newMetadata The new metadata value - * @return The resulting {@link ItemStack} - */ - public static ItemStack copyWithMeta(ItemStack toCopy, int newMetadata){ - ItemStack copy = new ItemStack(toCopy.getItem(), toCopy.getCount(), newMetadata); - NBTTagCompound compound = toCopy.getTagCompound(); - if(compound != null) copy.setTagCompound(compound.copy()); - return copy; - } - - /** - * Checks if the given player is opped on the given server. If the server is a singleplayer or LAN server, this - * means they have cheats enabled. - */ - 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. - * This was added because armour stands are a subclass of EntityLivingBase, but shouldn't necessarily be treated - * as living entities - this depends on the situation. The given entity can safely be cast to EntityLivingBase - * if this method returns true. - */ - // In my opinion, it's a bad design choice to have armour stands extend EntityLivingBase directly - it would be - // better to make a parent class which is extended by both armour stands and EntityLivingBase and contains only - // the code required by both. - public static boolean isLiving(Entity entity){ - return entity instanceof EntityLivingBase && !(entity instanceof EntityArmorStand); - } - - /** - * 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(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); - } - - /** - * 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. - */ - // 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; - - if(caster.isHandActive()){ - - ItemStack stack = caster.getHeldItem(caster.getActiveHand()); - - if(stack.getItem() instanceof ISpellCastingItem && ((ISpellCastingItem)stack.getItem()).getCurrentSpell(stack) == spell){ - // Don't do this, it interferes with stuff! We effectively already tested this with caster.isHandActive() anyway -// && ((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; - } - - return false; - } - - /** - * 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. - */ - 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; - } - - // 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 - * {@link IllegalArgumentException} from occurring when calling {@link UUID#fromString(String)}. - * - * @param string The string to be checked - * @return Whether the given string is a valid string representation of a UUID - * @deprecated UUIDs can now be stored in NBT directly; use that in preference to storing them as strings. - */ - @Deprecated - public static boolean verifyUUIDString(String string){ - return string != null - && string.matches("/^[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}$/"); - } - - /** - * 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 Collection flatten(Collection> collection){ - Collection result = new ArrayList<>(); - collection.forEach(result::addAll); - return Collections.unmodifiableCollection(result); - } - - // 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(); -// } - -} diff --git a/src/main/java/electroblob/wizardry/worldgen/WoodTypeTemplateProcessor.java b/src/main/java/electroblob/wizardry/worldgen/WoodTypeTemplateProcessor.java index a900c929..99ac6d99 100644 --- a/src/main/java/electroblob/wizardry/worldgen/WoodTypeTemplateProcessor.java +++ b/src/main/java/electroblob/wizardry/worldgen/WoodTypeTemplateProcessor.java @@ -1,6 +1,7 @@ package electroblob.wizardry.worldgen; -import electroblob.wizardry.util.WizardryUtilities; +import electroblob.wizardry.registry.WizardryBlocks; +import electroblob.wizardry.util.BlockUtils; import net.minecraft.block.Block; import net.minecraft.block.BlockPlanks; import net.minecraft.block.BlockWoodSlab; @@ -18,10 +19,13 @@ public class WoodTypeTemplateProcessor implements ITemplateProcessor { private final BlockPlanks.EnumType woodType; + // Not all of these are used in wizardry's structures, but they're included for completeness private final EnumMap DOORS; private final EnumMap STAIRS; private final EnumMap FENCES; private final EnumMap FENCE_GATES; + private final EnumMap BOOKSHELVES; + private final EnumMap LECTERNS; /** * Creates a new {@code WoodTypeTemplateProcessor} of the given type. @@ -32,36 +36,52 @@ public class WoodTypeTemplateProcessor implements ITemplateProcessor { 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); + 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); + 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); + 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); + 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); + + BOOKSHELVES = new EnumMap<>(BlockPlanks.EnumType.class); + BOOKSHELVES.put(BlockPlanks.EnumType.OAK, WizardryBlocks.oak_bookshelf); + BOOKSHELVES.put(BlockPlanks.EnumType.SPRUCE, WizardryBlocks.spruce_bookshelf); + BOOKSHELVES.put(BlockPlanks.EnumType.BIRCH, WizardryBlocks.birch_bookshelf); + BOOKSHELVES.put(BlockPlanks.EnumType.JUNGLE, WizardryBlocks.jungle_bookshelf); + BOOKSHELVES.put(BlockPlanks.EnumType.ACACIA, WizardryBlocks.acacia_bookshelf); + BOOKSHELVES.put(BlockPlanks.EnumType.DARK_OAK, WizardryBlocks.dark_oak_bookshelf); + + LECTERNS = new EnumMap<>(BlockPlanks.EnumType.class); + LECTERNS.put(BlockPlanks.EnumType.OAK, WizardryBlocks.oak_lectern); + LECTERNS.put(BlockPlanks.EnumType.SPRUCE, WizardryBlocks.spruce_lectern); + LECTERNS.put(BlockPlanks.EnumType.BIRCH, WizardryBlocks.birch_lectern); + LECTERNS.put(BlockPlanks.EnumType.JUNGLE, WizardryBlocks.jungle_lectern); + LECTERNS.put(BlockPlanks.EnumType.ACACIA, WizardryBlocks.acacia_lectern); + LECTERNS.put(BlockPlanks.EnumType.DARK_OAK, WizardryBlocks.dark_oak_lectern); } @Nullable @@ -69,19 +89,23 @@ public class WoodTypeTemplateProcessor implements ITemplateProcessor { 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){ + if(info.blockState.getBlock() instanceof BlockPlanks){ // This covers gilded wood too 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); + return new Template.BlockInfo(info.pos, BlockUtils.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); + return new Template.BlockInfo(info.pos, BlockUtils.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); + return new Template.BlockInfo(info.pos, BlockUtils.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 new Template.BlockInfo(info.pos, BlockUtils.copyState(FENCE_GATES.get(woodType), info.blockState), info.tileentityData); + }else if(BOOKSHELVES.containsValue(info.blockState.getBlock())){ + return new Template.BlockInfo(info.pos, BlockUtils.copyState(BOOKSHELVES.get(woodType), info.blockState), info.tileentityData); + }else if(LECTERNS.containsValue(info.blockState.getBlock())){ + return new Template.BlockInfo(info.pos, BlockUtils.copyState(LECTERNS.get(woodType), info.blockState), info.tileentityData); } return info; diff --git a/src/main/java/electroblob/wizardry/worldgen/WorldGenLibraryRuins.java b/src/main/java/electroblob/wizardry/worldgen/WorldGenLibraryRuins.java new file mode 100644 index 00000000..9a854b6b --- /dev/null +++ b/src/main/java/electroblob/wizardry/worldgen/WorldGenLibraryRuins.java @@ -0,0 +1,100 @@ +package electroblob.wizardry.worldgen; + +import com.google.common.collect.ImmutableList; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.integration.antiqueatlas.WizardryAntiqueAtlasIntegration; +import electroblob.wizardry.tileentity.TileEntityBookshelf; +import electroblob.wizardry.util.BlockUtils; +import net.minecraft.block.BlockPlanks; +import net.minecraft.block.BlockStoneBrick; +import net.minecraft.block.BlockStoneSlab; +import net.minecraft.block.BlockStoneSlab.EnumType; +import net.minecraft.init.Blocks; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +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 net.minecraftforge.common.BiomeDictionary.Type; +import org.apache.commons.lang3.ArrayUtils; + +import java.util.List; +import java.util.Random; + +public class WorldGenLibraryRuins extends WorldGenSurfaceStructure { + + private static final List BIOME_TYPES = ImmutableList.of(Type.FOREST, Type.JUNGLE, Type.SWAMP); + + @Override + public String getStructureName(){ + return "library_ruins"; + } + + @Override + public long getRandomSeedModifier(){ + return 10149523L; + } + + @Override + public boolean canGenerate(Random random, World world, int chunkX, int chunkZ){ + return ArrayUtils.contains(Wizardry.settings.libraryDimensions, world.provider.getDimension()) + // +8 for the anti-cascading offset, and +8 for the middle of the generated area makes +16 in total + && BiomeDictionary.getTypes(world.getBiome(new BlockPos(chunkX * 16 + 16, 0, chunkZ * 16 + 16))) + .stream().anyMatch(BIOME_TYPES::contains) + && Wizardry.settings.libraryRarity > 0 && random.nextInt(Wizardry.settings.libraryRarity) == 0; + } + + @Override + public ResourceLocation getStructureFile(Random random){ + return Wizardry.settings.libraryFiles[random.nextInt(Wizardry.settings.libraryFiles.length)]; + } + + @Override + public void spawnStructure(Random random, World world, BlockPos origin, Template template, PlacementSettings settings, ResourceLocation structureFile){ + + final Biome biome = world.getBiome(origin); + final float stoneBrickChance = random.nextFloat(); + final float mossiness = 0.4f; + final BlockPlanks.EnumType woodType = BlockUtils.getBiomeWoodVariant(biome); + + ITemplateProcessor processor = new MultiTemplateProcessor(true, + // Cobblestone/stone brick + (w, p, i) -> { + if(w.rand.nextFloat() > stoneBrickChance){ + // Behold, three different ways of doing the same thing, because this is pre-flattening! + // Also, stone bricks are about the least consistently-named thing in the entire game, so yay + if(i.blockState.getBlock() == Blocks.COBBLESTONE){ + return new Template.BlockInfo(i.pos, Blocks.STONEBRICK.getDefaultState(), i.tileentityData); + }else if(i.blockState.getBlock() == Blocks.STONE_SLAB + && i.blockState.getValue(BlockStoneSlab.VARIANT) == EnumType.COBBLESTONE){ + return new Template.BlockInfo(i.pos, i.blockState.withProperty(BlockStoneSlab.VARIANT, EnumType.SMOOTHBRICK), i.tileentityData); + }else if(i.blockState.getBlock() == Blocks.STONE_STAIRS){ // "Stone" stairs are actually cobblestone + return new Template.BlockInfo(i.pos, BlockUtils.copyState(Blocks.STONE_BRICK_STAIRS, i.blockState), i.tileentityData); + } + } + return i; + }, + // Wood type + new WoodTypeTemplateProcessor(woodType), + // Mossifier + new MossifierTemplateProcessor(mossiness, 0.04f, origin.getY() + 1), + // Stone brick smasher-upper + (w, p, i) -> i.blockState.getBlock() == Blocks.STONEBRICK && w.rand.nextFloat() < 0.1f ? + new Template.BlockInfo(i.pos, Blocks.STONEBRICK.getDefaultState().withProperty( + BlockStoneBrick.VARIANT, BlockStoneBrick.EnumType.CRACKED), i.tileentityData) : i, + // Bookshelf marker + (w, p, i) -> { + TileEntityBookshelf.markAsNatural(i.tileentityData); + return i; + } + ); + + template.addBlocksToWorld(world, origin, processor, settings, 2 | 16); + + WizardryAntiqueAtlasIntegration.markLibrary(world, origin.getX(), origin.getZ(), false); + } + +} diff --git a/src/main/java/electroblob/wizardry/worldgen/WorldGenObelisk.java b/src/main/java/electroblob/wizardry/worldgen/WorldGenObelisk.java index e8d6d1d2..486e8606 100644 --- a/src/main/java/electroblob/wizardry/worldgen/WorldGenObelisk.java +++ b/src/main/java/electroblob/wizardry/worldgen/WorldGenObelisk.java @@ -3,12 +3,15 @@ package electroblob.wizardry.worldgen; import electroblob.wizardry.Wizardry; import electroblob.wizardry.block.BlockRunestone; import electroblob.wizardry.constants.Element; +import electroblob.wizardry.entity.living.EntityRemnant; import electroblob.wizardry.integration.antiqueatlas.WizardryAntiqueAtlasIntegration; import net.minecraft.init.Blocks; +import net.minecraft.nbt.NBTTagCompound; import net.minecraft.tileentity.MobSpawnerBaseLogic; import net.minecraft.tileentity.TileEntityMobSpawner; import net.minecraft.util.Mirror; import net.minecraft.util.ResourceLocation; +import net.minecraft.util.WeightedSpawnerEntity; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import net.minecraft.world.gen.structure.template.ITemplateProcessor; @@ -16,7 +19,6 @@ 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; @@ -24,17 +26,7 @@ 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")); - } + private static final ResourceLocation REMNANT_ID = new ResourceLocation(Wizardry.MODID, "remnant"); @Override public String getStructureName(){ @@ -70,7 +62,7 @@ public class WorldGenObelisk extends WorldGenSurfaceStructure { 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); + template.addBlocksToWorld(world, origin, processor, settings, 2 | 16); WizardryAntiqueAtlasIntegration.markObelisk(world, origin.getX(), origin.getZ()); @@ -81,12 +73,25 @@ public class WorldGenObelisk extends WorldGenSurfaceStructure { if(entry.getValue().equals(SPAWNER_DATA_BLOCK_TAG)){ - world.setBlockState(entry.getKey(), Blocks.MOB_SPAWNER.getDefaultState()); + BlockPos pos = entry.getKey(); - if(world.getTileEntity(entry.getKey()) instanceof TileEntityMobSpawner){ + world.setBlockState(pos, Blocks.MOB_SPAWNER.getDefaultState()); - MobSpawnerBaseLogic spawnerLogic = ((TileEntityMobSpawner)world.getTileEntity(entry.getKey())).getSpawnerBaseLogic(); - spawnerLogic.setEntityId(MOB_TYPES.get(element)); + if(world.getTileEntity(pos) instanceof TileEntityMobSpawner){ + + MobSpawnerBaseLogic spawnerLogic = ((TileEntityMobSpawner)world.getTileEntity(pos)).getSpawnerBaseLogic(); + + EntityRemnant remant = new EntityRemnant(world); + remant.setElement(element); + remant.setBoundOrigin(pos); + + NBTTagCompound entityTag = new NBTTagCompound(); + entityTag.setString("id", REMNANT_ID.toString()); + remant.writeEntityToNBT(entityTag); + NBTTagCompound nbt = new NBTTagCompound(); + nbt.setTag("Entity", entityTag); + + spawnerLogic.setNextSpawnData(new WeightedSpawnerEntity(nbt)); }else{ Wizardry.logger.info("Tried to set the mob spawned by an obelisk, but the expected TileEntityMobSpawner was not present"); diff --git a/src/main/java/electroblob/wizardry/worldgen/WorldGenShrine.java b/src/main/java/electroblob/wizardry/worldgen/WorldGenShrine.java index 076a811d..7e9b4d5d 100644 --- a/src/main/java/electroblob/wizardry/worldgen/WorldGenShrine.java +++ b/src/main/java/electroblob/wizardry/worldgen/WorldGenShrine.java @@ -54,7 +54,7 @@ public class WorldGenShrine extends WorldGenSurfaceStructure { 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); + template.addBlocksToWorld(world, origin, processor, settings, 2 | 16); WizardryAntiqueAtlasIntegration.markShrine(world, origin.getX(), origin.getZ()); diff --git a/src/main/java/electroblob/wizardry/worldgen/WorldGenSurfaceStructure.java b/src/main/java/electroblob/wizardry/worldgen/WorldGenSurfaceStructure.java index 5af256f9..a83cd54d 100644 --- a/src/main/java/electroblob/wizardry/worldgen/WorldGenSurfaceStructure.java +++ b/src/main/java/electroblob/wizardry/worldgen/WorldGenSurfaceStructure.java @@ -2,153 +2,63 @@ package electroblob.wizardry.worldgen; import com.google.common.math.Quantiles; import electroblob.wizardry.Wizardry; -import electroblob.wizardry.registry.WizardryAdvancementTriggers; -import electroblob.wizardry.util.NBTExtras; -import electroblob.wizardry.util.WizardryUtilities; -import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import electroblob.wizardry.util.BlockUtils; 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.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; 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.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.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; -/** 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 { +/** + * Base class for all wizardry's above-ground structures, which handles various shared functions such as calculating + * the median ground level and removing floating trees. This class implements + * {@link WorldGenWizardryStructure#attemptPosition(Template, PlacementSettings, Random, World, int, int, String)} + * but leaves the rest of the abstract methods to be implemented by subclasses. + * + * @author Electroblob + * @since Wizardry 4.2 + */ +public abstract class WorldGenSurfaceStructure extends WorldGenWizardryStructure { /** 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 - * @param structureFile The name of the structure file, used for error messages. - * @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. - */ + // 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. + @Override @Nullable - protected BlockPos findValidPosition(Template template, PlacementSettings settings, Random random, World world, - int chunkX, int chunkZ, String structureFile){ - - // 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)); + protected BlockPos attemptPosition(Template template, PlacementSettings settings, Random random, World world, + int chunkX, int chunkZ, String structureFile){ BlockPos size = template.transformedSize(settings.getRotation()); - if(size.getX() == 0 || size.getY() == 0 || size.getZ() == 0){ - Wizardry.logger.warn("Structure template file {} is missing or empty! If you're trying to disable structure spawning, pointing to a non-existent location is NOT the correct way to do so; use the structure dimension lists instead.", structureFile); - } + // Offset by (8, 8) to minimise cascading worldgen lag, MINUS half the width of the structure (important!) + // 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((chunkX << 4) + random.nextInt(16) + 8 - size.getX()/2, 0, (chunkZ << 4) + random.nextInt(16) + 8 - size.getZ()/2); // Estimate a starting height for searching for the floor BlockPos centre = world.getTopSolidOrLiquidBlock(new BlockPos(origin.add(size.getX()/2, 0, size.getZ()/2))); // Check if we're at the top of the world, and if so just randomise the y pos (accounts for 'cavern' dimensions) if(centre.getY() >= world.getActualHeight()) centre = new BlockPos(centre.getX(), random.nextInt(world.getActualHeight()), centre.getZ()); - Integer startingHeight = WizardryUtilities.getNearestSurface(world, centre, EnumFacing.UP, 32, true, - WizardryUtilities.SurfaceCriteria.COLLIDABLE_IGNORING_TREES); + Integer startingHeight = BlockUtils.getNearestSurface(world, centre, EnumFacing.UP, 32, true, + BlockUtils.SurfaceCriteria.COLLIDABLE_IGNORING_TREES); if(startingHeight == null) return null; @@ -167,8 +77,8 @@ public abstract class WorldGenSurfaceStructure implements IWorldGenerator { // 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); + Integer floor = BlockUtils.getNearestSurface(world, pos.up(startingHeight), EnumFacing.UP, 32, true, + BlockUtils.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. @@ -193,125 +103,8 @@ public abstract class WorldGenSurfaceStructure implements IWorldGenerator { } @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, structureFile.toString()); - 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, random); - - structureMap.put(ChunkPos.asLong(origin.getX() >> 4, origin.getZ() >> 4), settings.getBoundingBox()); - - NBTTagCompound tag = new NBTTagCompound(); - tag.setInteger("ChunkX", chunkX); - tag.setInteger("ChunkZ", chunkZ); - NBTExtras.storeTagSafely(tag, "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)); - } - } - } - } - } + protected void postGenerate(Random random, World world, PlacementSettings settings){ + if(!Wizardry.settings.fastWorldgen) removeFloatingTrees(world, settings.getBoundingBox(), random); } /** Finds and removes any floating bits of tree in and above the given structure bounding box. */ @@ -336,7 +129,7 @@ public abstract class WorldGenSurfaceStructure implements IWorldGenerator { Block below = world.getBlockState(pos.down()).getBlock(); if(block instanceof BlockLog){ - if(below != Blocks.GRASS && below != Blocks.DIRT && !WizardryUtilities.isTreeBlock(world, pos.down())){ + if(below != Blocks.GRASS && below != Blocks.DIRT && !BlockUtils.isTreeBlock(world, pos.down())){ world.setBlockToAir(pos); changed = true; } @@ -357,6 +150,9 @@ public abstract class WorldGenSurfaceStructure implements IWorldGenerator { 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); + // Skip blocks that haven't been generated yet + // If you think about it, there can't possibly be floating trees in unloaded chunks anyway + if(!world.isBlockLoaded(pos)) continue; if(world.getBlockState(pos).getBlock() instanceof BlockLeaves) leaves.add(pos); } } @@ -374,85 +170,4 @@ public abstract class WorldGenSurfaceStructure implements IWorldGenerator { } - /** 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/WorldGenUndergroundLibraryRuins.java b/src/main/java/electroblob/wizardry/worldgen/WorldGenUndergroundLibraryRuins.java new file mode 100644 index 00000000..14730ed8 --- /dev/null +++ b/src/main/java/electroblob/wizardry/worldgen/WorldGenUndergroundLibraryRuins.java @@ -0,0 +1,104 @@ +package electroblob.wizardry.worldgen; + +import com.google.common.collect.ImmutableSet; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.integration.antiqueatlas.WizardryAntiqueAtlasIntegration; +import electroblob.wizardry.registry.WizardryBlocks; +import electroblob.wizardry.tileentity.TileEntityBookshelf; +import electroblob.wizardry.util.BlockUtils; +import net.minecraft.block.Block; +import net.minecraft.block.BlockPlanks; +import net.minecraft.block.BlockStoneBrick; +import net.minecraft.block.BlockStoneSlab; +import net.minecraft.block.BlockStoneSlab.EnumType; +import net.minecraft.init.Blocks; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +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 org.apache.commons.lang3.ArrayUtils; + +import java.util.Random; +import java.util.Set; + +public class WorldGenUndergroundLibraryRuins extends WorldGenUndergroundStructure { + + private final Set nonReplaceableBlocks; + + public WorldGenUndergroundLibraryRuins(){ + nonReplaceableBlocks = ImmutableSet.of(WizardryBlocks.imbuement_altar, WizardryBlocks.receptacle, + WizardryBlocks.oak_bookshelf); + } + + @Override + public String getStructureName(){ + return "underground_library_ruins"; + } + + @Override + public long getRandomSeedModifier(){ + return 13012834L; + } + + @Override + public ResourceLocation getStructureFile(Random random){ + return Wizardry.settings.undergroundLibraryFiles[random.nextInt(Wizardry.settings.undergroundLibraryFiles.length)]; + } + + @Override + public boolean canGenerate(Random random, World world, int chunkX, int chunkZ){ + return ArrayUtils.contains(Wizardry.settings.libraryDimensions, world.provider.getDimension()) + && Wizardry.settings.libraryRarity > 0 && random.nextInt(Wizardry.settings.libraryRarity) == 0; + } + + @Override + public void spawnStructure(Random random, World world, BlockPos origin, Template template, PlacementSettings settings, ResourceLocation structureFile){ + + final Biome biome = world.getBiome(origin); + final float stoneBrickChance = random.nextFloat(); + final float mossiness = 0.6f; // Underground is a mossier place! + final BlockPlanks.EnumType woodType = BlockUtils.getBiomeWoodVariant(biome); + + ITemplateProcessor processor = new MultiTemplateProcessor(true, + // Erase walls and ceilings where caves were + (w, p, i) -> w.isAirBlock(p) && !nonReplaceableBlocks.contains(i.blockState.getBlock()) ? null : i, + // Cobblestone/stone brick + (w, p, i) -> { + if(w.rand.nextFloat() > stoneBrickChance){ + // Behold, three different ways of doing the same thing, because this is pre-flattening! + // Also, stone bricks are about the least consistently-named thing in the entire game, so yay + if(i.blockState.getBlock() == Blocks.COBBLESTONE){ + return new Template.BlockInfo(i.pos, Blocks.STONEBRICK.getDefaultState(), i.tileentityData); + }else if(i.blockState.getBlock() == Blocks.STONE_SLAB + && i.blockState.getValue(BlockStoneSlab.VARIANT) == EnumType.COBBLESTONE){ + return new Template.BlockInfo(i.pos, i.blockState.withProperty(BlockStoneSlab.VARIANT, EnumType.SMOOTHBRICK), i.tileentityData); + }else if(i.blockState.getBlock() == Blocks.STONE_STAIRS){ // "Stone" stairs are actually cobblestone + return new Template.BlockInfo(i.pos, BlockUtils.copyState(Blocks.STONE_BRICK_STAIRS, i.blockState), i.tileentityData); + } + } + return i; + }, + // Wood type + new WoodTypeTemplateProcessor(woodType), + // Mossifier + new MossifierTemplateProcessor(mossiness, 0.04f, origin.getY() + 1), + // Stone brick smasher-upper + (w, p, i) -> i.blockState.getBlock() == Blocks.STONEBRICK && w.rand.nextFloat() < 0.1f ? + new Template.BlockInfo(i.pos, Blocks.STONEBRICK.getDefaultState().withProperty( + BlockStoneBrick.VARIANT, BlockStoneBrick.EnumType.CRACKED), i.tileentityData) : i, + // Bookshelf marker + (w, p, i) -> { + TileEntityBookshelf.markAsNatural(i.tileentityData); + return i; + } + ); + + template.addBlocksToWorld(world, origin, processor, settings, 2 | 16); + + WizardryAntiqueAtlasIntegration.markLibrary(world, origin.getX(), origin.getZ(), true); + } + +} diff --git a/src/main/java/electroblob/wizardry/worldgen/WorldGenUndergroundStructure.java b/src/main/java/electroblob/wizardry/worldgen/WorldGenUndergroundStructure.java new file mode 100644 index 00000000..7e223f58 --- /dev/null +++ b/src/main/java/electroblob/wizardry/worldgen/WorldGenUndergroundStructure.java @@ -0,0 +1,62 @@ +package electroblob.wizardry.worldgen; + +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.gen.structure.template.PlacementSettings; +import net.minecraft.world.gen.structure.template.Template; + +import javax.annotation.Nullable; +import java.util.Random; + +/** + * Base class for all wizardry's underground structures, which handles finding a location with a cave entrance (based + * on {@code WorldGenDungeons}, with some tweaks). This class implements + * {@link WorldGenWizardryStructure#attemptPosition(Template, PlacementSettings, Random, World, int, int, String)} + * but leaves the rest of the abstract methods to be implemented by subclasses. + * + * @author Electroblob + * @since Wizardry 4.3 + */ +public abstract class WorldGenUndergroundStructure extends WorldGenWizardryStructure { + + private static final int MAX_ENTRANCES = 5; + + @Nullable + @Override + protected BlockPos attemptPosition(Template template, PlacementSettings settings, Random random, World world, int chunkX, int chunkZ, String structureFile){ + + BlockPos size = template.transformedSize(settings.getRotation()); + + // Offset by (8, 8) to minimise cascading worldgen lag, MINUS half the width of the structure (important!) + // 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((chunkX << 4) + random.nextInt(16) + 8 - size.getX()/2, 20 + random.nextInt(40), (chunkZ << 4) + random.nextInt(16) + 8 - size.getZ()/2); + + BlockPos corner = origin.add(size.getX(), 1, size.getZ()); // Need not iterate through everything + + // Criteria for a valid position: + // - There must be at least one entrance at floor level that a player can fit through, and no more than 5 + // - All blocks that the floor will replace must be solid + // - Unlike with dungeons, there CAN be holes in the ceiling (mainly because it's not flat!) + + int entrances = 0; + + for(BlockPos pos : BlockPos.getAllInBox(origin, corner)){ + + // Assume the bottom layer of the structure to be the floor + if(pos.getY() == origin.getY() && !world.getBlockState(pos).getMaterial().isSolid()){ + return null; // Can't generate with space underneath + } + + if((pos.getX() == origin.getX() || pos.getX() == corner.getX() || pos.getZ() == origin.getZ() || pos.getZ() == corner.getZ()) + && pos.getY() == origin.getY() + 1 && world.isAirBlock(pos) && world.isAirBlock(pos.up())){ + if(entrances++ > MAX_ENTRANCES) return null; + } + } + + if(entrances == 0) return null; // No way in! + + return origin; + } + +} diff --git a/src/main/java/electroblob/wizardry/worldgen/WorldGenWizardTower.java b/src/main/java/electroblob/wizardry/worldgen/WorldGenWizardTower.java index 64882da9..dc4bf11f 100644 --- a/src/main/java/electroblob/wizardry/worldgen/WorldGenWizardTower.java +++ b/src/main/java/electroblob/wizardry/worldgen/WorldGenWizardTower.java @@ -5,11 +5,11 @@ 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 electroblob.wizardry.util.BlockUtils; +import electroblob.wizardry.util.GeometryUtils; 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; @@ -37,11 +37,11 @@ public class WorldGenWizardTower extends WorldGenSurfaceStructure { 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; + private final Map specialWallBlocks; public WorldGenWizardTower(){ // These are initialised here because it's a convenient point after the blocks are registered - SPECIAL_WALL_BLOCKS = ImmutableMap.of( + specialWallBlocks = ImmutableMap.of( BiomeDictionary.Type.MESA, Blocks.RED_SANDSTONE.getDefaultState(), BiomeDictionary.Type.MOUNTAIN, Blocks.STONEBRICK.getDefaultState(), BiomeDictionary.Type.NETHER, Blocks.NETHER_BRICK.getDefaultState(), @@ -78,11 +78,11 @@ public class WorldGenWizardTower extends WorldGenSurfaceStructure { 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 IBlockState wallMaterial = specialWallBlocks.keySet().stream().filter(t -> BiomeDictionary.hasType(biome, t)) + .findFirst().map(specialWallBlocks::get).orElse(Blocks.COBBLESTONE.getDefaultState()); final float mossiness = getBiomeMossiness(biome); - final BlockPlanks.EnumType woodType = getBiomeWoodVariant(biome); + final BlockPlanks.EnumType woodType = BlockUtils.getBiomeWoodVariant(biome); final Set blocksPlaced = new HashSet<>(); @@ -101,7 +101,7 @@ public class WorldGenWizardTower extends WorldGenSurfaceStructure { (w, p, i) -> {if(i.blockState.getBlock() != Blocks.AIR) blocksPlaced.add(p); return i;} ); - template.addBlocksToWorld(world, origin, processor, settings, 2); + template.addBlocksToWorld(world, origin, processor, settings, 2 | 16); WizardryAntiqueAtlasIntegration.markTower(world, origin.getX(), origin.getZ()); @@ -110,7 +110,7 @@ public class WorldGenWizardTower extends WorldGenSurfaceStructure { for(Map.Entry entry : dataBlocks.entrySet()){ - Vec3d vec = WizardryUtilities.getCentre(entry.getKey()); + Vec3d vec = GeometryUtils.getCentre(entry.getKey()); if(entry.getValue().equals(WIZARD_DATA_BLOCK_TAG)){ @@ -150,16 +150,4 @@ public class WorldGenWizardTower extends WorldGenSurfaceStructure { 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/java/electroblob/wizardry/worldgen/WorldGenWizardryStructure.java b/src/main/java/electroblob/wizardry/worldgen/WorldGenWizardryStructure.java new file mode 100644 index 00000000..33abe1d0 --- /dev/null +++ b/src/main/java/electroblob/wizardry/worldgen/WorldGenWizardryStructure.java @@ -0,0 +1,349 @@ +package electroblob.wizardry.worldgen; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.registry.WizardryAdvancementTriggers; +import electroblob.wizardry.util.NBTExtras; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.Mirror; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.Rotation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.math.Vec3i; +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.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.HashMap; +import java.util.Map; +import java.util.Random; + +/** + * Base structure generation class which handles code common to all wizardry's structures. This class was generalised + * from {@link WorldGenSurfaceStructure} in Wizardry 4.3 to allow underground structures to share much of the code. + * + * @author Electroblob + * @since Wizardry 4.3 + */ +@Mod.EventBusSubscriber +public abstract class WorldGenWizardryStructure implements IWorldGenerator { + + /** 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 WorldGenWizardryStructure(){ + 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(); + + /** 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(); + + /** 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}; + } + + /** 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 WorldGenWizardryStructure#spawnStructure(Random, World, BlockPos, Template, PlacementSettings, ResourceLocation)} */ + public abstract boolean canGenerate(Random random, World world, int chunkX, int chunkZ); + + /** + * Picks a random position within the given chunk at which to generate the given template, returning null if that + * position is found to be unsuitable. In an effort to make structure rarity more uniform, they now get a number of + * attempts 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.), or otherwise satisfying spawning conditions. This method performs a + * single attempt at finding a suitable position. + * + * @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 + * @param structureFile The name of the structure file, used for error messages. + * @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. + */ + // It may seem more logical for this class to generate a position and then have subclasses only check if it is + // valid, but different types of structure pick positions differently so that has to be left to subclasses + @Nullable + protected abstract BlockPos attemptPosition(Template template, PlacementSettings settings, Random random, World world, + int chunkX, int chunkZ, String structureFile); + + /** + * Spawns the structure at the given origin with the given placement settings. This method should handle template + * processors and the actual placing of the blocks in the world. + * @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, already set to a valid position and adjusted + * so that it is the NW corner (-X, -Z) regardless of rotation/mirror settings + * @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); + + /** + * Called after the structure is generated to perform any post-generation extras (optional). For example, + * {@link WorldGenSurfaceStructure} uses this to clean up floating trees. + * @param random The random number generator that was used to generate the structure + * @param world The world in which the structure was generated + * @param settings The placement settings that the structure was generated with (including its bounding box) + */ + protected void postGenerate(Random random, World world, PlacementSettings settings){} + + @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); + + BlockPos size = template.getSize(); + + if(size.getX() == 0 || size.getY() == 0 || size.getZ() == 0){ + Wizardry.logger.warn("Structure template file {} is missing or empty! If you're trying to disable structure spawning, pointing to a non-existent location is NOT the correct way to do so; use the structure dimension lists instead.", 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 = attemptPosition(template, settings, random, world, chunkX, chunkZ, structureFile.toString()); + 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(WorldGenWizardryStructure 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); + + postGenerate(random, world, settings); + + structureMap.put(ChunkPos.asLong(origin.getX() >> 4, origin.getZ() >> 4), settings.getBoundingBox()); + + NBTTagCompound tag = new NBTTagCompound(); + tag.setInteger("ChunkX", chunkX); + tag.setInteger("ChunkZ", chunkZ); + NBTExtras.storeTagSafely(tag, "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)); + } + } + } + } + } + } + + /** 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 WorldGenWizardryStructure 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/resources/assets/ebwizardry/advancements/all_artefacts.json b/src/main/resources/assets/ebwizardry/advancements/all_artefacts.json index 1ad658b9..741f7cd0 100644 --- a/src/main/resources/assets/ebwizardry/advancements/all_artefacts.json +++ b/src/main/resources/assets/ebwizardry/advancements/all_artefacts.json @@ -83,6 +83,16 @@ ] } }, + "ring_meteor": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_meteor" + } + ] + } + }, "ring_ice_melee": { "trigger": "minecraft:inventory_changed", "conditions": { @@ -163,6 +173,16 @@ ] } }, + "ring_stormcloud": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_stormcloud" + } + ] + } + }, "ring_soulbinding": { "trigger": "minecraft:inventory_changed", "conditions": { @@ -243,6 +263,16 @@ ] } }, + "ring_evoker": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_evoker" + } + ] + } + }, "ring_extraction": { "trigger": "minecraft:inventory_changed", "conditions": { @@ -383,6 +413,16 @@ ] } }, + "amulet_frost_warding": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_frost_warding" + } + ] + } + }, "amulet_potential": { "trigger": "minecraft:inventory_changed", "conditions": { @@ -493,6 +533,16 @@ ] } }, + "amulet_absorption": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_absorption" + } + ] + } + }, "charm_haggler": { "trigger": "minecraft:inventory_changed", "conditions": { @@ -513,6 +563,26 @@ ] } }, + "charm_move_speed": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_move_speed" + } + ] + } + }, + "charm_spell_discovery": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_spell_discovery" + } + ] + } + }, "charm_auto_smelt": { "trigger": "minecraft:inventory_changed", "conditions": { @@ -563,6 +633,26 @@ ] } }, + "charm_undead_helmets": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_undead_helmets" + } + ] + } + }, + "charm_hunger_casting": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_hunger_casting" + } + ] + } + }, "charm_flight": { "trigger": "minecraft:inventory_changed", "conditions": { @@ -603,6 +693,16 @@ ] } }, + "charm_sixth_sense": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_sixth_sense" + } + ] + } + }, "charm_stop_time": { "trigger": "minecraft:inventory_changed", "conditions": { @@ -633,6 +733,26 @@ ] } }, + "charm_black_hole": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_black_hole" + } + ] + } + }, + "charm_mount_teleporting": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_mount_teleporting" + } + ] + } + }, "charm_feeding": { "trigger": "minecraft:inventory_changed", "conditions": { diff --git a/src/main/resources/assets/ebwizardry/advancements/all_spells.json b/src/main/resources/assets/ebwizardry/advancements/all_spells.json index 16a35949..62beb2fe 100644 --- a/src/main/resources/assets/ebwizardry/advancements/all_spells.json +++ b/src/main/resources/assets/ebwizardry/advancements/all_spells.json @@ -69,6 +69,14 @@ } } }, + "blinding_flash": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "blinding_flash" + } + } + }, "blink": { "trigger": "ebwizardry:cast_spell", "conditions": { @@ -85,6 +93,14 @@ } } }, + "boulder": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "boulder" + } + } + }, "bubble": { "trigger": "ebwizardry:cast_spell", "conditions": { @@ -93,6 +109,14 @@ } } }, + "celestial_smite": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "celestial_smite" + } + } + }, "chain_lightning": { "trigger": "ebwizardry:cast_spell", "conditions": { @@ -317,6 +341,14 @@ } } }, + "enrage": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "enrage" + } + } + }, "evade": { "trigger": "ebwizardry:cast_spell", "conditions": { @@ -325,6 +357,14 @@ } } }, + "fangs": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "fangs" + } + } + }, "fireball": { "trigger": "ebwizardry:cast_spell", "conditions": { @@ -373,6 +413,14 @@ } } }, + "firestorm": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "firestorm" + } + } + }, "fire_breath": { "trigger": "ebwizardry:cast_spell", "conditions": { @@ -381,6 +429,14 @@ } } }, + "flamecatcher": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "flamecatcher" + } + } + }, "flame_ray": { "trigger": "ebwizardry:cast_spell", "conditions": { @@ -493,6 +549,14 @@ } } }, + "frost_barrier": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "frost_barrier" + } + } + }, "frost_ray": { "trigger": "ebwizardry:cast_spell", "conditions": { @@ -581,6 +645,14 @@ } } }, + "guardian_beam": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "guardian_beam" + } + } + }, "hailstorm": { "trigger": "ebwizardry:cast_spell", "conditions": { @@ -845,6 +917,14 @@ } } }, + "mark_sacrifice": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "mark_sacrifice" + } + } + }, "metamorphosis": { "trigger": "ebwizardry:cast_spell", "conditions": { @@ -885,6 +965,14 @@ } } }, + "mirage": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "mirage" + } + } + }, "muffle": { "trigger": "ebwizardry:cast_spell", "conditions": { @@ -909,6 +997,14 @@ } } }, + "permafrost": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "permafrost" + } + } + }, "petrify": { "trigger": "ebwizardry:cast_spell", "conditions": { @@ -973,6 +1069,14 @@ } } }, + "radiant_totem": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "radiant_totem" + } + } + }, "ray_of_purification": { "trigger": "ebwizardry:cast_spell", "conditions": { @@ -1157,6 +1261,14 @@ } } }, + "stormcloud": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "stormcloud" + } + } + }, "summon_blaze": { "trigger": "ebwizardry:cast_spell", "conditions": { @@ -1388,6 +1500,22 @@ "spell": "wither_skull" } } + }, + "withering_totem": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "withering_totem" + } + } + }, + "zombie_apocalypse": { + "trigger": "ebwizardry:cast_spell", + "conditions": { + "spell": { + "spell": "zombie_apocalypse" + } + } } } } \ 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 index a5287409..e8e23c66 100644 --- a/src/main/resources/assets/ebwizardry/advancements/artefact.json +++ b/src/main/resources/assets/ebwizardry/advancements/artefact.json @@ -82,6 +82,16 @@ ] } }, + "ring_meteor": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_meteor" + } + ] + } + }, "ring_ice_melee": { "trigger": "minecraft:inventory_changed", "conditions": { @@ -162,6 +172,16 @@ ] } }, + "ring_stormcloud": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_stormcloud" + } + ] + } + }, "ring_soulbinding": { "trigger": "minecraft:inventory_changed", "conditions": { @@ -242,6 +262,16 @@ ] } }, + "ring_evoker": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:ring_evoker" + } + ] + } + }, "ring_extraction": { "trigger": "minecraft:inventory_changed", "conditions": { @@ -382,6 +412,16 @@ ] } }, + "amulet_frost_warding": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_frost_warding" + } + ] + } + }, "amulet_potential": { "trigger": "minecraft:inventory_changed", "conditions": { @@ -492,6 +532,16 @@ ] } }, + "amulet_absorption": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:amulet_absorption" + } + ] + } + }, "charm_haggler": { "trigger": "minecraft:inventory_changed", "conditions": { @@ -512,6 +562,26 @@ ] } }, + "charm_move_speed": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_move_speed" + } + ] + } + }, + "charm_spell_discovery": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_spell_discovery" + } + ] + } + }, "charm_auto_smelt": { "trigger": "minecraft:inventory_changed", "conditions": { @@ -562,6 +632,26 @@ ] } }, + "charm_undead_helmets": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_undead_helmets" + } + ] + } + }, + "charm_hunger_casting": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_hunger_casting" + } + ] + } + }, "charm_flight": { "trigger": "minecraft:inventory_changed", "conditions": { @@ -602,6 +692,16 @@ ] } }, + "charm_sixth_sense": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_sixth_sense" + } + ] + } + }, "charm_stop_time": { "trigger": "minecraft:inventory_changed", "conditions": { @@ -632,6 +732,26 @@ ] } }, + "charm_black_hole": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_black_hole" + } + ] + } + }, + "charm_mount_teleporting": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:charm_mount_teleporting" + } + ] + } + }, "charm_feeding": { "trigger": "minecraft:inventory_changed", "conditions": { @@ -652,6 +772,7 @@ "ring_fire_melee", "ring_fire_biome", "ring_disintegration", + "ring_meteor", "ring_ice_melee", "ring_ice_biome", "ring_arcane_frost", @@ -660,6 +781,7 @@ "ring_storm", "ring_seeking", "ring_hammer", + "ring_stormcloud", "ring_soulbinding", "ring_leeching", "ring_necromancy_melee", @@ -668,6 +790,7 @@ "ring_earth_melee", "ring_earth_biome", "ring_full_moon", + "ring_evoker", "ring_extraction", "ring_mana_return", "ring_blockwrangler", @@ -682,6 +805,7 @@ "amulet_fire_cloaking", "amulet_ice_immunity", "amulet_ice_protection", + "amulet_frost_warding", "amulet_potential", "amulet_channeling", "amulet_lich", @@ -693,20 +817,28 @@ "amulet_transience", "amulet_resurrection", "amulet_auto_shield", + "amulet_absorption", "charm_haggler", "charm_experience_tome", + "charm_move_speed", + "charm_spell_discovery", "charm_auto_smelt", "charm_lava_walking", "charm_storm", "charm_minion_health", "charm_minion_variants", + "charm_undead_helmets", + "charm_hunger_casting", "charm_flight", "charm_growth", "charm_abseiling", "charm_silk_touch", + "charm_sixth_sense", "charm_stop_time", "charm_light", "charm_transportation", + "charm_black_hole", + "charm_mount_teleporting", "charm_feeding" ] ] diff --git a/src/main/resources/assets/ebwizardry/advancements/craft_lectern.json b/src/main/resources/assets/ebwizardry/advancements/craft_lectern.json new file mode 100644 index 00000000..a12d608b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/advancements/craft_lectern.json @@ -0,0 +1,86 @@ +{ + "display": { + "title": { + "translate": "advancement.ebwizardry:craft_lectern" + }, + "description": { + "translate": "advancement.ebwizardry:craft_lectern.desc" + }, + "icon": { + "item": "ebwizardry:spruce_lectern" + } + }, + "parent": "ebwizardry:find_library_ruins", + "criteria": { + "oak": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:oak_lectern" + } + ] + } + }, + "spruce": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:spruce_lectern" + } + ] + } + }, + "birch": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:birch_lectern" + } + ] + } + }, + "jungle": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:jungle_lectern" + } + ] + } + }, + "acacia": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:acacia_lectern" + } + ] + } + }, + "dark_oak": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:dark_oak_lectern" + } + ] + } + } + }, + "requirements": [ + [ + "oak", + "spruce", + "birch", + "jungle", + "acacia", + "dark_oak" + ] + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/create_elemental_armour.json b/src/main/resources/assets/ebwizardry/advancements/create_elemental_armour.json new file mode 100644 index 00000000..1ad9e35f --- /dev/null +++ b/src/main/resources/assets/ebwizardry/advancements/create_elemental_armour.json @@ -0,0 +1,272 @@ +{ + "display": { + "title": { + "translate": "advancement.ebwizardry:create_elemental_armour" + }, + "description": { + "translate": "advancement.ebwizardry:create_elemental_armour.desc" + }, + "icon": { + "item": "ebwizardry:wizard_hat_lightning" + } + }, + "parent": "ebwizardry:restore_imbuement_altar", + "criteria": { + "fire_hat": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_hat_fire" + } + } + }, + "fire_robe": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_robe_fire" + } + } + }, + "fire_leggings": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_leggings_fire" + } + } + }, + "fire_boots": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_boots_fire" + } + } + }, + "ice_hat": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_hat_ice" + } + } + }, + "ice_robe": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_robe_ice" + } + } + }, + "ice_leggings": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_leggings_ice" + } + } + }, + "ice_boots": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_boots_ice" + } + } + }, + "lightning_hat": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_hat_lightning" + } + } + }, + "lightning_robe": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_robe_lightning" + } + } + }, + "lightning_leggings": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_leggings_lightning" + } + } + }, + "lightning_boots": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_boots_lightning" + } + } + }, + "necromancy_hat": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_hat_necromancy" + } + } + }, + "necromancy_robe": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_robe_necromancy" + } + } + }, + "necromancy_leggings": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_leggings_necromancy" + } + } + }, + "necromancy_boots": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_boots_necromancy" + } + } + }, + "earth_hat": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_hat_earth" + } + } + }, + "earth_robe": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_robe_earth" + } + } + }, + "earth_leggings": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_leggings_earth" + } + } + }, + "earth_boots": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_boots_earth" + } + } + }, + "sorcery_hat": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_hat_sorcery" + } + } + }, + "sorcery_robe": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_robe_sorcery" + } + } + }, + "sorcery_leggings": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_leggings_sorcery" + } + } + }, + "sorcery_boots": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_boots_sorcery" + } + } + }, + "healing_hat": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_hat_healing" + } + } + }, + "healing_robe": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_robe_healing" + } + } + }, + "healing_leggings": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_leggings_healing" + } + } + }, + "healing_boots": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:wizard_boots_healing" + } + } + } + }, + "requirements": [ + [ + "fire_hat", + "fire_robe", + "fire_leggings", + "fire_boots", + "ice_hat", + "ice_robe", + "ice_leggings", + "ice_boots", + "lightning_hat", + "lightning_robe", + "lightning_leggings", + "lightning_boots", + "necromancy_hat", + "necromancy_robe", + "necromancy_leggings", + "necromancy_boots", + "earth_hat", + "earth_robe", + "earth_leggings", + "earth_boots", + "sorcery_hat", + "sorcery_robe", + "sorcery_leggings", + "sorcery_boots", + "healing_hat", + "healing_robe", + "healing_leggings", + "healing_boots" + ] + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/defeat_remnant.json b/src/main/resources/assets/ebwizardry/advancements/defeat_remnant.json new file mode 100644 index 00000000..c5203c08 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/advancements/defeat_remnant.json @@ -0,0 +1,105 @@ +{ + "display": { + "title": { + "translate": "advancement.ebwizardry:defeat_remnant" + }, + "description": { + "translate": "advancement.ebwizardry:defeat_remnant.desc" + }, + "icon": { + "item": "ebwizardry:spectral_dust", + "data": 6 + } + }, + "parent": "ebwizardry:arcane_initiate", + "criteria": { + "fire": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:spectral_dust", + "data": 1 + } + ] + } + }, + "ice": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:spectral_dust", + "data": 2 + } + ] + } + }, + "lightning": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:spectral_dust", + "data": 3 + } + ] + } + }, + "necromancy": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:spectral_dust", + "data": 4 + } + ] + } + }, + "earth": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:spectral_dust", + "data": 5 + } + ] + } + }, + "sorcery": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:spectral_dust", + "data": 6 + } + ] + } + }, + "healing": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "item": "ebwizardry:spectral_dust", + "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/find_library_ruins.json b/src/main/resources/assets/ebwizardry/advancements/find_library_ruins.json new file mode 100644 index 00000000..df097d8a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/advancements/find_library_ruins.json @@ -0,0 +1,34 @@ +{ + "display": { + "title": { + "translate": "advancement.ebwizardry:find_library_ruins" + }, + "description": { + "translate": "advancement.ebwizardry:find_library_ruins.desc" + }, + "icon": { + "item": "ebwizardry:spruce_bookshelf" + } + }, + "parent": "ebwizardry:defeat_remnant", + "criteria": { + "surface": { + "trigger": "ebwizardry:visit_structure", + "conditions": { + "structure_type": "library_ruins" + } + }, + "underground": { + "trigger": "ebwizardry:visit_structure", + "conditions": { + "structure_type": "underground_library_ruins" + } + } + }, + "requirements": [ + [ + "surface", + "underground" + ] + ] +} \ 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 index 40ee0c46..806ac654 100644 --- a/src/main/resources/assets/ebwizardry/advancements/handbook/tome_of_arcana.json +++ b/src/main/resources/assets/ebwizardry/advancements/handbook/tome_of_arcana.json @@ -9,6 +9,9 @@ } ] } + }, + "criteria_1": { + "trigger": "ebwizardry:trigger_wand_levelup" } } } \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/restore_imbuement_altar.json b/src/main/resources/assets/ebwizardry/advancements/restore_imbuement_altar.json new file mode 100644 index 00000000..935d894c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/advancements/restore_imbuement_altar.json @@ -0,0 +1,19 @@ +{ + "display": { + "title": { + "translate": "advancement.ebwizardry:restore_imbuement_altar" + }, + "description": { + "translate": "advancement.ebwizardry:restore_imbuement_altar.desc" + }, + "icon": { + "item": "ebwizardry:imbuement_altar" + } + }, + "parent": "ebwizardry:find_library_ruins", + "criteria": { + "criteria_0": { + "trigger": "ebwizardry:restore_imbuement_altar" + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/advancements/restore_ruined_book.json b/src/main/resources/assets/ebwizardry/advancements/restore_ruined_book.json new file mode 100644 index 00000000..95934434 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/advancements/restore_ruined_book.json @@ -0,0 +1,24 @@ +{ + "display": { + "title": { + "translate": "advancement.ebwizardry:restore_ruined_book" + }, + "description": { + "translate": "advancement.ebwizardry:restore_ruined_book.desc" + }, + "icon": { + "item": "ebwizardry:ruined_spell_book" + } + }, + "parent": "ebwizardry:restore_imbuement_altar", + "criteria": { + "criteria_0": { + "trigger": "ebwizardry:imbuement_altar", + "conditions": { + "item": { + "item": "ebwizardry:spell_book" + } + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/blockstates/acacia_bookshelf.json b/src/main/resources/assets/ebwizardry/blockstates/acacia_bookshelf.json new file mode 100644 index 00000000..4aa96488 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/acacia_bookshelf.json @@ -0,0 +1,8 @@ +{ + "variants": { + "facing=north": { "model": "ebwizardry:smart_model/acacia_bookshelf#facing=north" }, + "facing=east": { "model": "ebwizardry:smart_model/acacia_bookshelf#facing=east" }, + "facing=south": { "model": "ebwizardry:smart_model/acacia_bookshelf#facing=south" }, + "facing=west": { "model": "ebwizardry:smart_model/acacia_bookshelf#facing=west" } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/acacia_gilded_wood.json b/src/main/resources/assets/ebwizardry/blockstates/acacia_gilded_wood.json new file mode 100644 index 00000000..a5759461 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/acacia_gilded_wood.json @@ -0,0 +1,6 @@ +{ + "forge_marker": 1, + "variants": { + "normal": { "model": "ebwizardry:gilded_acacia_wood" } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/acacia_lectern.json b/src/main/resources/assets/ebwizardry/blockstates/acacia_lectern.json new file mode 100644 index 00000000..20fb6530 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/acacia_lectern.json @@ -0,0 +1,14 @@ +{ + "forge_marker": 1, + "defaults": { + "model": "ebwizardry:acacia_lectern" + }, + "variants": { + "facing": { + "north": {}, + "east": { "y": 90 }, + "south": { "y": 180 }, + "west": { "y": 270 } + } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/birch_bookshelf.json b/src/main/resources/assets/ebwizardry/blockstates/birch_bookshelf.json new file mode 100644 index 00000000..656889cc --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/birch_bookshelf.json @@ -0,0 +1,8 @@ +{ + "variants": { + "facing=north": { "model": "ebwizardry:smart_model/birch_bookshelf#facing=north" }, + "facing=east": { "model": "ebwizardry:smart_model/birch_bookshelf#facing=east" }, + "facing=south": { "model": "ebwizardry:smart_model/birch_bookshelf#facing=south" }, + "facing=west": { "model": "ebwizardry:smart_model/birch_bookshelf#facing=west" } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/birch_gilded_wood.json b/src/main/resources/assets/ebwizardry/blockstates/birch_gilded_wood.json new file mode 100644 index 00000000..f023d78f --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/birch_gilded_wood.json @@ -0,0 +1,6 @@ +{ + "forge_marker": 1, + "variants": { + "normal": { "model": "ebwizardry:gilded_birch_wood" } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/birch_lectern.json b/src/main/resources/assets/ebwizardry/blockstates/birch_lectern.json new file mode 100644 index 00000000..1acb6ae4 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/birch_lectern.json @@ -0,0 +1,14 @@ +{ + "forge_marker": 1, + "defaults": { + "model": "ebwizardry:birch_lectern" + }, + "variants": { + "facing": { + "north": {}, + "east": { "y": 90 }, + "south": { "y": 180 }, + "west": { "y": 270 } + } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/acacia_bookshelf.json b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/acacia_bookshelf.json new file mode 100644 index 00000000..c517daf9 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/acacia_bookshelf.json @@ -0,0 +1,14 @@ +{ + "forge_marker": 1, + "defaults": { + "model": "ebwizardry:acacia_bookshelf" + }, + "variants": { + "facing": { + "north": {}, + "east": { "y": 90 }, + "south": { "y": 180 }, + "west": { "y": 270 } + } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/birch_bookshelf.json b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/birch_bookshelf.json new file mode 100644 index 00000000..f86ffd00 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/birch_bookshelf.json @@ -0,0 +1,14 @@ +{ + "forge_marker": 1, + "defaults": { + "model": "ebwizardry:birch_bookshelf" + }, + "variants": { + "facing": { + "north": {}, + "east": { "y": 90 }, + "south": { "y": 180 }, + "west": { "y": 270 } + } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books0.json b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books0.json new file mode 100644 index 00000000..690e777d --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books0.json @@ -0,0 +1,14 @@ +{ + "forge_marker": 1, + "defaults": { + "model": "ebwizardry:books0" + }, + "variants": { + "facing": { + "north": {}, + "east": { "y": 90 }, + "south": { "y": 180 }, + "west": { "y": 270 } + } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books1.json b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books1.json new file mode 100644 index 00000000..f0bf298d --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books1.json @@ -0,0 +1,14 @@ +{ + "forge_marker": 1, + "defaults": { + "model": "ebwizardry:books1" + }, + "variants": { + "facing": { + "north": {}, + "east": { "y": 90 }, + "south": { "y": 180 }, + "west": { "y": 270 } + } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books10.json b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books10.json new file mode 100644 index 00000000..d0c3b676 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books10.json @@ -0,0 +1,14 @@ +{ + "forge_marker": 1, + "defaults": { + "model": "ebwizardry:books10" + }, + "variants": { + "facing": { + "north": {}, + "east": { "y": 90 }, + "south": { "y": 180 }, + "west": { "y": 270 } + } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books11.json b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books11.json new file mode 100644 index 00000000..f0564d0e --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books11.json @@ -0,0 +1,14 @@ +{ + "forge_marker": 1, + "defaults": { + "model": "ebwizardry:books11" + }, + "variants": { + "facing": { + "north": {}, + "east": { "y": 90 }, + "south": { "y": 180 }, + "west": { "y": 270 } + } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books2.json b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books2.json new file mode 100644 index 00000000..c6fbbb2d --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books2.json @@ -0,0 +1,14 @@ +{ + "forge_marker": 1, + "defaults": { + "model": "ebwizardry:books2" + }, + "variants": { + "facing": { + "north": {}, + "east": { "y": 90 }, + "south": { "y": 180 }, + "west": { "y": 270 } + } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books3.json b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books3.json new file mode 100644 index 00000000..fe640e1a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books3.json @@ -0,0 +1,14 @@ +{ + "forge_marker": 1, + "defaults": { + "model": "ebwizardry:books3" + }, + "variants": { + "facing": { + "north": {}, + "east": { "y": 90 }, + "south": { "y": 180 }, + "west": { "y": 270 } + } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books4.json b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books4.json new file mode 100644 index 00000000..8f5f64c3 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books4.json @@ -0,0 +1,14 @@ +{ + "forge_marker": 1, + "defaults": { + "model": "ebwizardry:books4" + }, + "variants": { + "facing": { + "north": {}, + "east": { "y": 90 }, + "south": { "y": 180 }, + "west": { "y": 270 } + } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books5.json b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books5.json new file mode 100644 index 00000000..8cbd4b8b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books5.json @@ -0,0 +1,14 @@ +{ + "forge_marker": 1, + "defaults": { + "model": "ebwizardry:books5" + }, + "variants": { + "facing": { + "north": {}, + "east": { "y": 90 }, + "south": { "y": 180 }, + "west": { "y": 270 } + } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books6.json b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books6.json new file mode 100644 index 00000000..23b29036 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books6.json @@ -0,0 +1,14 @@ +{ + "forge_marker": 1, + "defaults": { + "model": "ebwizardry:books6" + }, + "variants": { + "facing": { + "north": {}, + "east": { "y": 90 }, + "south": { "y": 180 }, + "west": { "y": 270 } + } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books7.json b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books7.json new file mode 100644 index 00000000..e9cba50e --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books7.json @@ -0,0 +1,14 @@ +{ + "forge_marker": 1, + "defaults": { + "model": "ebwizardry:books7" + }, + "variants": { + "facing": { + "north": {}, + "east": { "y": 90 }, + "south": { "y": 180 }, + "west": { "y": 270 } + } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books8.json b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books8.json new file mode 100644 index 00000000..981fdc32 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books8.json @@ -0,0 +1,14 @@ +{ + "forge_marker": 1, + "defaults": { + "model": "ebwizardry:books8" + }, + "variants": { + "facing": { + "north": {}, + "east": { "y": 90 }, + "south": { "y": 180 }, + "west": { "y": 270 } + } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books9.json b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books9.json new file mode 100644 index 00000000..46bbd413 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/books9.json @@ -0,0 +1,14 @@ +{ + "forge_marker": 1, + "defaults": { + "model": "ebwizardry:books9" + }, + "variants": { + "facing": { + "north": {}, + "east": { "y": 90 }, + "south": { "y": 180 }, + "west": { "y": 270 } + } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/dark_oak_bookshelf.json b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/dark_oak_bookshelf.json new file mode 100644 index 00000000..bfb7e91e --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/dark_oak_bookshelf.json @@ -0,0 +1,14 @@ +{ + "forge_marker": 1, + "defaults": { + "model": "ebwizardry:dark_oak_bookshelf" + }, + "variants": { + "facing": { + "north": {}, + "east": { "y": 90 }, + "south": { "y": 180 }, + "west": { "y": 270 } + } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/jungle_bookshelf.json b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/jungle_bookshelf.json new file mode 100644 index 00000000..9a9bd237 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/jungle_bookshelf.json @@ -0,0 +1,14 @@ +{ + "forge_marker": 1, + "defaults": { + "model": "ebwizardry:jungle_bookshelf" + }, + "variants": { + "facing": { + "north": {}, + "east": { "y": 90 }, + "south": { "y": 180 }, + "west": { "y": 270 } + } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/oak_bookshelf.json b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/oak_bookshelf.json new file mode 100644 index 00000000..d13b08c1 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/oak_bookshelf.json @@ -0,0 +1,14 @@ +{ + "forge_marker": 1, + "defaults": { + "model": "ebwizardry:oak_bookshelf" + }, + "variants": { + "facing": { + "north": {}, + "east": { "y": 90 }, + "south": { "y": 180 }, + "west": { "y": 270 } + } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/spruce_bookshelf.json b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/spruce_bookshelf.json new file mode 100644 index 00000000..66cf20b1 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/bookshelf_parts/spruce_bookshelf.json @@ -0,0 +1,14 @@ +{ + "forge_marker": 1, + "defaults": { + "model": "ebwizardry:spruce_bookshelf" + }, + "variants": { + "facing": { + "north": {}, + "east": { "y": 90 }, + "south": { "y": 180 }, + "west": { "y": 270 } + } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/crystal_flower_pot.json b/src/main/resources/assets/ebwizardry/blockstates/crystal_flower_pot.json new file mode 100644 index 00000000..446174e2 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/crystal_flower_pot.json @@ -0,0 +1,6 @@ +{ + "forge_marker": 1, + "variants": { + "normal": { "model": "ebwizardry:crystal_flower_pot" } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/dark_oak_bookshelf.json b/src/main/resources/assets/ebwizardry/blockstates/dark_oak_bookshelf.json new file mode 100644 index 00000000..8a838825 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/dark_oak_bookshelf.json @@ -0,0 +1,8 @@ +{ + "variants": { + "facing=north": { "model": "ebwizardry:smart_model/dark_oak_bookshelf#facing=north" }, + "facing=east": { "model": "ebwizardry:smart_model/dark_oak_bookshelf#facing=east" }, + "facing=south": { "model": "ebwizardry:smart_model/dark_oak_bookshelf#facing=south" }, + "facing=west": { "model": "ebwizardry:smart_model/dark_oak_bookshelf#facing=west" } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/dark_oak_gilded_wood.json b/src/main/resources/assets/ebwizardry/blockstates/dark_oak_gilded_wood.json new file mode 100644 index 00000000..da105e21 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/dark_oak_gilded_wood.json @@ -0,0 +1,6 @@ +{ + "forge_marker": 1, + "variants": { + "normal": { "model": "ebwizardry:gilded_dark_oak_wood" } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/dark_oak_lectern.json b/src/main/resources/assets/ebwizardry/blockstates/dark_oak_lectern.json new file mode 100644 index 00000000..d193e745 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/dark_oak_lectern.json @@ -0,0 +1,14 @@ +{ + "forge_marker": 1, + "defaults": { + "model": "ebwizardry:dark_oak_lectern" + }, + "variants": { + "facing": { + "north": {}, + "east": { "y": 90 }, + "south": { "y": 180 }, + "west": { "y": 270 } + } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/imbuement_altar.json b/src/main/resources/assets/ebwizardry/blockstates/imbuement_altar.json new file mode 100644 index 00000000..ac08f939 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/imbuement_altar.json @@ -0,0 +1,7 @@ +{ + "forge_marker": 1, + "variants": { + "active=false": { "model": "ebwizardry:imbuement_altar_inactive" }, + "active=true": { "model": "ebwizardry:imbuement_altar" } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/jungle_bookshelf.json b/src/main/resources/assets/ebwizardry/blockstates/jungle_bookshelf.json new file mode 100644 index 00000000..105b170f --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/jungle_bookshelf.json @@ -0,0 +1,8 @@ +{ + "variants": { + "facing=north": { "model": "ebwizardry:smart_model/jungle_bookshelf#facing=north" }, + "facing=east": { "model": "ebwizardry:smart_model/jungle_bookshelf#facing=east" }, + "facing=south": { "model": "ebwizardry:smart_model/jungle_bookshelf#facing=south" }, + "facing=west": { "model": "ebwizardry:smart_model/jungle_bookshelf#facing=west" } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/jungle_gilded_wood.json b/src/main/resources/assets/ebwizardry/blockstates/jungle_gilded_wood.json new file mode 100644 index 00000000..c5479ce0 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/jungle_gilded_wood.json @@ -0,0 +1,6 @@ +{ + "forge_marker": 1, + "variants": { + "normal": { "model": "ebwizardry:gilded_jungle_wood" } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/jungle_lectern.json b/src/main/resources/assets/ebwizardry/blockstates/jungle_lectern.json new file mode 100644 index 00000000..e12d2b53 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/jungle_lectern.json @@ -0,0 +1,14 @@ +{ + "forge_marker": 1, + "defaults": { + "model": "ebwizardry:jungle_lectern" + }, + "variants": { + "facing": { + "north": {}, + "east": { "y": 90 }, + "south": { "y": 180 }, + "west": { "y": 270 } + } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/oak_bookshelf.json b/src/main/resources/assets/ebwizardry/blockstates/oak_bookshelf.json new file mode 100644 index 00000000..dca53b64 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/oak_bookshelf.json @@ -0,0 +1,8 @@ +{ + "variants": { + "facing=north": { "model": "ebwizardry:smart_model/oak_bookshelf#facing=north" }, + "facing=east": { "model": "ebwizardry:smart_model/oak_bookshelf#facing=east" }, + "facing=south": { "model": "ebwizardry:smart_model/oak_bookshelf#facing=south" }, + "facing=west": { "model": "ebwizardry:smart_model/oak_bookshelf#facing=west" } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/oak_gilded_wood.json b/src/main/resources/assets/ebwizardry/blockstates/oak_gilded_wood.json new file mode 100644 index 00000000..99d49f6b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/oak_gilded_wood.json @@ -0,0 +1,6 @@ +{ + "forge_marker": 1, + "variants": { + "normal": { "model": "ebwizardry:gilded_oak_wood" } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/oak_lectern.json b/src/main/resources/assets/ebwizardry/blockstates/oak_lectern.json new file mode 100644 index 00000000..eb8141e5 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/oak_lectern.json @@ -0,0 +1,14 @@ +{ + "forge_marker": 1, + "defaults": { + "model": "ebwizardry:oak_lectern" + }, + "variants": { + "facing": { + "north": {}, + "east": { "y": 90 }, + "south": { "y": 180 }, + "west": { "y": 270 } + } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/permafrost.json b/src/main/resources/assets/ebwizardry/blockstates/permafrost.json new file mode 100644 index 00000000..50446ad0 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/permafrost.json @@ -0,0 +1,9 @@ +{ + "forge_marker": 1, + "variants": { + "age=0": { "model": "ebwizardry:permafrost_0" }, + "age=1": { "model": "ebwizardry:permafrost_1" }, + "age=2": { "model": "ebwizardry:permafrost_2" }, + "age=3": { "model": "ebwizardry:permafrost_3" } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/receptacle.json b/src/main/resources/assets/ebwizardry/blockstates/receptacle.json new file mode 100644 index 00000000..d272bf0a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/receptacle.json @@ -0,0 +1,9 @@ +{ + "variants": { + "facing=up": { "model": "ebwizardry:receptacle" }, + "facing=east": { "model": "ebwizardry:wall_receptacle", "y": 270 }, + "facing=south": { "model": "ebwizardry:wall_receptacle" }, + "facing=west": { "model": "ebwizardry:wall_receptacle", "y": 90 }, + "facing=north": { "model": "ebwizardry:wall_receptacle", "y": 180 } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/spruce_bookshelf.json b/src/main/resources/assets/ebwizardry/blockstates/spruce_bookshelf.json new file mode 100644 index 00000000..3e52fe5b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/spruce_bookshelf.json @@ -0,0 +1,8 @@ +{ + "variants": { + "facing=north": { "model": "ebwizardry:smart_model/spruce_bookshelf#facing=north" }, + "facing=east": { "model": "ebwizardry:smart_model/spruce_bookshelf#facing=east" }, + "facing=south": { "model": "ebwizardry:smart_model/spruce_bookshelf#facing=south" }, + "facing=west": { "model": "ebwizardry:smart_model/spruce_bookshelf#facing=west" } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/spruce_gilded_wood.json b/src/main/resources/assets/ebwizardry/blockstates/spruce_gilded_wood.json new file mode 100644 index 00000000..989ea5f6 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/spruce_gilded_wood.json @@ -0,0 +1,6 @@ +{ + "forge_marker": 1, + "variants": { + "normal": { "model": "ebwizardry:gilded_spruce_wood" } + } +} diff --git a/src/main/resources/assets/ebwizardry/blockstates/spruce_lectern.json b/src/main/resources/assets/ebwizardry/blockstates/spruce_lectern.json new file mode 100644 index 00000000..dffef19a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/blockstates/spruce_lectern.json @@ -0,0 +1,14 @@ +{ + "forge_marker": 1, + "defaults": { + "model": "ebwizardry:spruce_lectern" + }, + "variants": { + "facing": { + "north": {}, + "east": { "y": 90 }, + "south": { "y": 180 }, + "west": { "y": 270 } + } + } +} diff --git a/src/main/resources/assets/ebwizardry/lang/en_gb.lang b/src/main/resources/assets/ebwizardry/lang/en_gb.lang index 983a92aa..f635d8ee 100644 --- a/src/main/resources/assets/ebwizardry/lang/en_gb.lang +++ b/src/main/resources/assets/ebwizardry/lang/en_gb.lang @@ -16,6 +16,8 @@ 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 +tile.ebwizardry\:crystal_flower_pot.name=Flower Pot +tile.ebwizardry\:permafrost.name=Permafrost tile.ebwizardry\:magic_crystal_block.name=Block of Crystal tile.ebwizardry\:fire_crystal_block.name=Block of Fiery Crystal @@ -26,32 +28,65 @@ 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 +tile.ebwizardry\:oak_gilded_wood.name=Gilded Oak Wood +tile.ebwizardry\:spruce_gilded_wood.name=Gilded Spruce Wood +tile.ebwizardry\:birch_gilded_wood.name=Gilded Birch Wood +tile.ebwizardry\:jungle_gilded_wood.name=Gilded Jungle Wood +tile.ebwizardry\:acacia_gilded_wood.name=Gilded Acacia Wood +tile.ebwizardry\:dark_oak_gilded_wood.name=Gilded Dark Oak Wood + +tile.ebwizardry\:oak_bookshelf.name=Oak Bookshelf +tile.ebwizardry\:spruce_bookshelf.name=Spruce Bookshelf +tile.ebwizardry\:birch_bookshelf.name=Birch Bookshelf +tile.ebwizardry\:jungle_bookshelf.name=Jungle Bookshelf +tile.ebwizardry\:acacia_bookshelf.name=Acacia Bookshelf +tile.ebwizardry\:dark_oak_bookshelf.name=Dark Oak Bookshelf + +tile.ebwizardry\:oak_lectern.name=Oak Lectern +tile.ebwizardry\:spruce_lectern.name=Spruce Lectern +tile.ebwizardry\:birch_lectern.name=Birch Lectern +tile.ebwizardry\:jungle_lectern.name=Jungle Lectern +tile.ebwizardry\:acacia_lectern.name=Acacia Lectern +tile.ebwizardry\:dark_oak_lectern.name=Dark Oak Lectern + +tile.ebwizardry\:receptacle.name=Receptacle +tile.ebwizardry\:imbuement_altar.name=Imbuement Altar + item.ebwizardry\:crystal_magic.name=Magic Crystal +item.ebwizardry\:crystal_magic.desc=The iridescent colours of this crystal seem to slowly shift and swirl, hinting at a magical energy within. Perhaps this magic could be harnessed in some way? item.ebwizardry\:crystal_fire.name=Fiery Crystal +item.ebwizardry\:crystal_fire.desc=A crystal that glows a fiery orange. Maybe it is the key to unlocking the element of fire... item.ebwizardry\:crystal_ice.name=Icy Crystal +item.ebwizardry\:crystal_ice.desc=A crystal with a cold, bluish tint. Perhaps it holds the secrets to the power of frost... item.ebwizardry\:crystal_lightning.name=Stormy Crystal +item.ebwizardry\:crystal_lightning.desc=A crystal that seems to swirl and flash with a chaotic energy. Maybe it hides a greater power within... item.ebwizardry\:crystal_necromancy.name=Dark Crystal +item.ebwizardry\:crystal_necromancy.desc=A crystal with a deep, purplish appearance that seems to draw you in. Perhaps it is a gateway to something darker... item.ebwizardry\:crystal_earth.name=Verdant Crystal +item.ebwizardry\:crystal_earth.desc=A crystal patterened with a great many greens and browns. Perhaps it holds a connection to the forces of nature... item.ebwizardry\:crystal_sorcery.name=Mystical Crystal +item.ebwizardry\:crystal_sorcery.desc=A crystal that glimmers with a bluish-green light. Maybe it conceals the secrets of an ancient force... item.ebwizardry\:crystal_healing.name=Radiant Crystal +item.ebwizardry\:crystal_healing.desc=A crystal that glints a golden yellow in the sunlight. Perhaps it possesses the link to a divine power... -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\:spell_book.desc=A book filled with the runes and glyphs of an ancient script. Perhaps with dedication, one might be able to decipher its contents...\n\nRight-click while holding this book to read it. +item.ebwizardry\:spell_book.new=Not yet discovered! item.ebwizardry\:spell_book.apply_to_wizard=Replaced %1$s's spell %2$s with %3$s +item.ebwizardry\:ruined_spell_book.name=Ruined Spell Book +item.ebwizardry\:ruined_spell_book.desc=The pages of this tattered spell book are too damaged to be read properly, but somehow, you feel there is still magic left within it - if only it could be restored... + item.ebwizardry\:arcane_tome.name=Tome of Arcana item.ebwizardry\:arcane_tome.desc=Upgrades any %1$s wand with sufficient progression to %2$s tier item.ebwizardry\:wizard_handbook.name=The Wizard's Handbook -item.ebwizardry\:wizard_handbook.desc=by %1$s +item.ebwizardry\:wizard_handbook.author=by %1$s +item.ebwizardry\:wizard_handbook.desc=Your not-so-pocket-sized guide to the arcane arts! A must-have for any aspiring wizard who seeks to learn the secrets of magic.\n\nRight-click while holding this book to read it. item.ebwizardry\:wand.generic=wand -item.ebwizardry\:wand.buff=+%1$s %2$s potency +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 @@ -61,6 +96,11 @@ item.ebwizardry\:wand.levelup=%1$s is ready to upgrade to %2$s tier 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\: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\: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 @@ -103,14 +143,19 @@ item.ebwizardry\:spectral_pickaxe_upgraded.name=Spectral Pickaxe 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\:mana_flask.desc=Craft with a wand or piece of wizard armour to restore %s mana item.ebwizardry\:grand_crystal.name=Grand Magic Crystal +item.ebwizardry\:grand_crystal.desc=A huge magic crystal, several times the size of those found naturally. Someone must have grown it artificially - but how? item.ebwizardry\:crystal_shard.name=Magic Crystal Shard +item.ebwizardry\:crystal_shard.desc=A small piece of a shattered magic crystal. It contains little mana on its own, but it might have other uses... item.ebwizardry\:astral_diamond.name=Astral Diamond +item.ebwizardry\:astral_diamond.desc=A highly-prized gemstone that sparkles brightly under starlight. It is said that the most powerful wizards seek astral diamonds in return for the most elusive arcane knowledge. item.ebwizardry\:purifying_elixir.name=Purifying Elixir item.ebwizardry\:purifying_elixir.desc=Removes curses when consumed +item.ebwizardry\:purifying_elixir.desc_extended=A small vial of exotic-looking golden liquid, which clearly has powerful healing properties. It would probably be wise to save this for when it is needed. item.ebwizardry\:storage_upgrade.name=Wand Storage Upgrade item.ebwizardry\:siphon_upgrade.name=Wand Siphon Upgrade @@ -122,6 +167,8 @@ 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\:wand_upgrade.generic.desc=Apply this upgrade to a wand in the arcane workbench. + 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 @@ -147,18 +194,22 @@ 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\:scroll.desc=An enchanted sheet of parchment bound loosely with string. Upon the parchment is written an incantation of some kind, presumably to be read aloud.\n\nRight-click while holding this scroll to use it. item.ebwizardry\:identification_scroll.name=Scroll of Identification item.ebwizardry\:identification_scroll.desc=Identifies an unknown spell book or scroll +item.ebwizardry\:identification_scroll.desc_extended=Right-click while holding this scroll to identify the first undiscovered spell book or scroll on your hotbar. item.ebwizardry\:identification_scroll.nothing_to_identify=Nothing to identify! 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\:armour_upgrade.desc=Upgrades any wizard armour to make it %1$slegendary +item.ebwizardry\:armour_upgrade.desc_extended=Apply this upgrade to a piece of wizard armour in the arcane workbench. item.ebwizardry\:magic_silk.name=Magical Silk +item.ebwizardry\:spectral_dust.name=Spectral Dust + item.ebwizardry\:wizard_armour.legendary=Legendary -item.ebwizardry\:wizard_armour.buff=-%1$s %2$s cost +item.ebwizardry\:wizard_armour.buff=-%1$s%% %2$s cost item.ebwizardry\:wizard_armour.mana=Mana\: %1$s/%2$s item.ebwizardry\:wizard_hat.name=Wizard Hat @@ -210,6 +261,13 @@ item.ebwizardry\:spectral_leggings.name=Spectral Leggings item.ebwizardry\:spectral_boots.name=Spectral Boots item.ebwizardry\:lightning_hammer.name=Lightning Hammer +item.ebwizardry\:flamecatcher.name=Flamecatcher + +item.ebwizardry\:generic.disabled=This item has been disabled in the config + +item.ebwizardry\:ring.generic.desc=Equip this ring in the Baubles inventory or by placing it on your hotbar. Up to 2 rings may be equipped at once. +item.ebwizardry\:amulet.generic.desc=Equip this amulet in the Baubles inventory or by placing it on your hotbar. Only one amulet may be equipped at a time. +item.ebwizardry\:charm.generic.desc=Equip this charm in the Baubles inventory or by placing it on your hotbar. Only one charm may be equipped at a time. item.ebwizardry\:ring_condensing.name=Ring of Condensing item.ebwizardry\:ring_condensing.desc=Slowly regenerates mana for all wands on your hotbar @@ -225,6 +283,8 @@ 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_meteor.name=Ring of Volthraa +item.ebwizardry\:ring_meteor.desc=Instead of falling from the sky, meteor can be aimed like a projectile 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 @@ -241,6 +301,8 @@ 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_stormcloud.name=Lokenzen's Ring +item.ebwizardry\:ring_stormcloud.desc=Stormcloud moves towards targets and lasts longer with every strike 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 @@ -257,6 +319,8 @@ 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_evoker.name=Evoker's Ring +item.ebwizardry\:ring_evoker.desc=Sneaking while casting fangs summons a defensive circle instead of a line 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 @@ -286,6 +350,8 @@ 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_frost_warding.name=Amulet of Esundor +item.ebwizardry\:amulet_frost_warding.desc=Grants a warding effect when standing behind a wall of frost 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 @@ -308,11 +374,17 @@ 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\:amulet_absorption.name=Amulet of Vitality +item.ebwizardry\:amulet_absorption.desc=Excess health from healing spells is converted into absorption hearts 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 50%% +item.ebwizardry\:charm_move_speed.name=Icarus Medallion +item.ebwizardry\:charm_move_speed.desc=Move at near-full speed when casting spells +item.ebwizardry\:charm_spell_discovery.name=Archivist's Eyeglass +item.ebwizardry\:charm_spell_discovery.desc=Spell books found in chests are much more likely to be ones you have not yet discovered 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 @@ -323,6 +395,10 @@ 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_undead_helmets.name=Cursed Helm +item.ebwizardry\:charm_undead_helmets.desc=Summoned zombies and skeletons have leather caps to protect them from sunlight +item.ebwizardry\:charm_hunger_casting.name=Demonic Seal +item.ebwizardry\:charm_hunger_casting.desc=When out of mana, hunger can be used to cast spells 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 @@ -331,14 +407,20 @@ 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_sixth_sense.name=Dire Wolf Pelt +item.ebwizardry\:charm_sixth_sense.desc=Sixth sense displays different colours for passive, neutral and hostile mobs 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_black_hole.name=Void Opal +item.ebwizardry\:charm_black_hole.desc=Black hole sucks in and destroys blocks +item.ebwizardry\:charm_mount_teleporting.name=Spectral Tether +item.ebwizardry\:charm_mount_teleporting.desc=Teleporting while riding an animal or vehicle brings it with you 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.ebwizardry\:charm_feeding.desc=Replenish hunger and satiety trigger automatically when bound to a wand on your hotbar item.charge_status.full=Full item.charge_status.almost_full=Almost full @@ -348,6 +430,125 @@ item.charge_status.mostly_empty=Mostly empty item.charge_status.almost_empty=Almost empty item.charge_status.empty=Empty +item.banner.wizardry_fire.black=Black Fire Emblem +item.banner.wizardry_fire.red=Red Fire Emblem +item.banner.wizardry_fire.green=Green Fire Emblem +item.banner.wizardry_fire.brown=Brown Fire Emblem +item.banner.wizardry_fire.blue=Blue Fire Emblem +item.banner.wizardry_fire.purple=Purple Fire Emblem +item.banner.wizardry_fire.cyan=Cyan Fire Emblem +item.banner.wizardry_fire.silver=Light Grey Fire Emblem +item.banner.wizardry_fire.gray=Grey Fire Emblem +item.banner.wizardry_fire.pink=Pink Fire Emblem +item.banner.wizardry_fire.lime=Lime Fire Emblem +item.banner.wizardry_fire.yellow=Yellow Fire Emblem +item.banner.wizardry_fire.lightBlue=Light Blue Fire Emblem +item.banner.wizardry_fire.magenta=Magenta Fire Emblem +item.banner.wizardry_fire.orange=Orange Fire Emblem +item.banner.wizardry_fire.white=White Fire Emblem + +item.banner.wizardry_ice.black=Black Ice Emblem +item.banner.wizardry_ice.red=Red Ice Emblem +item.banner.wizardry_ice.green=Green Ice Emblem +item.banner.wizardry_ice.brown=Brown Ice Emblem +item.banner.wizardry_ice.blue=Blue Ice Emblem +item.banner.wizardry_ice.purple=Purple Ice Emblem +item.banner.wizardry_ice.cyan=Cyan Ice Emblem +item.banner.wizardry_ice.silver=Light Grey Ice Emblem +item.banner.wizardry_ice.gray=Grey Ice Emblem +item.banner.wizardry_ice.pink=Pink Ice Emblem +item.banner.wizardry_ice.lime=Lime Ice Emblem +item.banner.wizardry_ice.yellow=Yellow Ice Emblem +item.banner.wizardry_ice.lightBlue=Light Blue Ice Emblem +item.banner.wizardry_ice.magenta=Magenta Ice Emblem +item.banner.wizardry_ice.orange=Orange Ice Emblem +item.banner.wizardry_ice.white=White Ice Emblem + +item.banner.wizardry_lightning.black=Black Lightning Emblem +item.banner.wizardry_lightning.red=Red Lightning Emblem +item.banner.wizardry_lightning.green=Green Lightning Emblem +item.banner.wizardry_lightning.brown=Brown Lightning Emblem +item.banner.wizardry_lightning.blue=Blue Lightning Emblem +item.banner.wizardry_lightning.purple=Purple Lightning Emblem +item.banner.wizardry_lightning.cyan=Cyan Lightning Emblem +item.banner.wizardry_lightning.silver=Light Grey Lightning Emblem +item.banner.wizardry_lightning.gray=Grey Lightning Emblem +item.banner.wizardry_lightning.pink=Pink Lightning Emblem +item.banner.wizardry_lightning.lime=Lime Lightning Emblem +item.banner.wizardry_lightning.yellow=Yellow Lightning Emblem +item.banner.wizardry_lightning.lightBlue=Light Blue Lightning Emblem +item.banner.wizardry_lightning.magenta=Magenta Lightning Emblem +item.banner.wizardry_lightning.orange=Orange Lightning Emblem +item.banner.wizardry_lightning.white=White Lightning Emblem + +item.banner.wizardry_necromancy.black=Black Necromancy Emblem +item.banner.wizardry_necromancy.red=Red Necromancy Emblem +item.banner.wizardry_necromancy.green=Green Necromancy Emblem +item.banner.wizardry_necromancy.brown=Brown Necromancy Emblem +item.banner.wizardry_necromancy.blue=Blue Necromancy Emblem +item.banner.wizardry_necromancy.purple=Purple Necromancy Emblem +item.banner.wizardry_necromancy.cyan=Cyan Necromancy Emblem +item.banner.wizardry_necromancy.silver=Light Grey Necromancy Emblem +item.banner.wizardry_necromancy.gray=Grey Necromancy Emblem +item.banner.wizardry_necromancy.pink=Pink Necromancy Emblem +item.banner.wizardry_necromancy.lime=Lime Necromancy Emblem +item.banner.wizardry_necromancy.yellow=Yellow Necromancy Emblem +item.banner.wizardry_necromancy.lightBlue=Light Blue Necromancy Emblem +item.banner.wizardry_necromancy.magenta=Magenta Necromancy Emblem +item.banner.wizardry_necromancy.orange=Orange Necromancy Emblem +item.banner.wizardry_necromancy.white=White Necromancy Emblem + +item.banner.wizardry_earth.black=Black Earth Emblem +item.banner.wizardry_earth.red=Red Earth Emblem +item.banner.wizardry_earth.green=Green Earth Emblem +item.banner.wizardry_earth.brown=Brown Earth Emblem +item.banner.wizardry_earth.blue=Blue Earth Emblem +item.banner.wizardry_earth.purple=Purple Earth Emblem +item.banner.wizardry_earth.cyan=Cyan Earth Emblem +item.banner.wizardry_earth.silver=Light Grey Earth Emblem +item.banner.wizardry_earth.gray=Grey Earth Emblem +item.banner.wizardry_earth.pink=Pink Earth Emblem +item.banner.wizardry_earth.lime=Lime Earth Emblem +item.banner.wizardry_earth.yellow=Yellow Earth Emblem +item.banner.wizardry_earth.lightBlue=Light Blue Earth Emblem +item.banner.wizardry_earth.magenta=Magenta Earth Emblem +item.banner.wizardry_earth.orange=Orange Earth Emblem +item.banner.wizardry_earth.white=White Earth Emblem + +item.banner.wizardry_sorcery.black=Black Sorcery Emblem +item.banner.wizardry_sorcery.red=Red Sorcery Emblem +item.banner.wizardry_sorcery.green=Green Sorcery Emblem +item.banner.wizardry_sorcery.brown=Brown Sorcery Emblem +item.banner.wizardry_sorcery.blue=Blue Sorcery Emblem +item.banner.wizardry_sorcery.purple=Purple Sorcery Emblem +item.banner.wizardry_sorcery.cyan=Cyan Sorcery Emblem +item.banner.wizardry_sorcery.silver=Light Grey Sorcery Emblem +item.banner.wizardry_sorcery.gray=Grey Sorcery Emblem +item.banner.wizardry_sorcery.pink=Pink Sorcery Emblem +item.banner.wizardry_sorcery.lime=Lime Sorcery Emblem +item.banner.wizardry_sorcery.yellow=Yellow Sorcery Emblem +item.banner.wizardry_sorcery.lightBlue=Light Blue Sorcery Emblem +item.banner.wizardry_sorcery.magenta=Magenta Sorcery Emblem +item.banner.wizardry_sorcery.orange=Orange Sorcery Emblem +item.banner.wizardry_sorcery.white=White Sorcery Emblem + +item.banner.wizardry_healing.black=Black Healing Emblem +item.banner.wizardry_healing.red=Red Healing Emblem +item.banner.wizardry_healing.green=Green Healing Emblem +item.banner.wizardry_healing.brown=Brown Healing Emblem +item.banner.wizardry_healing.blue=Blue Healing Emblem +item.banner.wizardry_healing.purple=Purple Healing Emblem +item.banner.wizardry_healing.cyan=Cyan Healing Emblem +item.banner.wizardry_healing.silver=Light Grey Healing Emblem +item.banner.wizardry_healing.gray=Grey Healing Emblem +item.banner.wizardry_healing.pink=Pink Healing Emblem +item.banner.wizardry_healing.lime=Lime Healing Emblem +item.banner.wizardry_healing.yellow=Yellow Healing Emblem +item.banner.wizardry_healing.lightBlue=Light Blue Healing Emblem +item.banner.wizardry_healing.magenta=Magenta Healing Emblem +item.banner.wizardry_healing.orange=Orange Healing Emblem +item.banner.wizardry_healing.white=White Healing Emblem + entity.ebwizardry\:summonedcreature.nameplate=%1$s's %2$s entity.ebwizardry\:summonedcreature.nameplate_fallback=Someone's %1$s @@ -396,6 +597,7 @@ 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\:remnant.name=Remnant entity.ebwizardry\:magic_missile.name=Magic entity.ebwizardry\:arc.name=Magic @@ -421,6 +623,7 @@ 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\:flamecatcher_arrow.name=Magic entity.ebwizardry\:black_hole.name=Black Hole entity.ebwizardry\:shield.name=Shield @@ -436,6 +639,11 @@ 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 +entity.ebwizardry\:stormcloud.name=Stormcloud +entity.ebwizardry\:zombie_spawner.name=Zombie Spawner +entity.ebwizardry\:boulder.name=Boulder +entity.ebwizardry\:radiant_totem.name=Radiant Totem +entity.ebwizardry\:withering_totem.name=Withering Totem itemGroup.ebwizardry=Wizardry itemGroup.ebwizardryspells=Spells @@ -488,7 +696,20 @@ advancement.ebwizardry\:artefact.desc=Lift the protective enchantments from a sh 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\:defeat_remnant=Dust Off Your Arrows +advancement.ebwizardry\:defeat_remnant.desc=Hunt down the remnants guarding an obelisk and collect their spectral dust +advancement.ebwizardry\:find_library_ruins=I Think My Books Are Overdue +advancement.ebwizardry\:find_library_ruins.desc=Uncover the ruins of a forgotten library +advancement.ebwizardry\:restore_imbuement_altar=Good As New +advancement.ebwizardry\:restore_imbuement_altar.desc=Restore an imbuement altar to its former glory +advancement.ebwizardry\:create_elemental_armour=Changing Clothes +advancement.ebwizardry\:create_elemental_armour.desc=Imbue a piece of wizard armour with spectral dust of one of the arcane elements +advancement.ebwizardry\:restore_ruined_book=Arcane Archaeologist +advancement.ebwizardry\:restore_ruined_book.desc=Restore a ruined spell book at an imbuement altar +advancement.ebwizardry\:craft_lectern=Arcane Encyclopaedia +advancement.ebwizardry\:craft_lectern.desc=Craft a lectern to keep track of your spell library + +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 @@ -505,10 +726,19 @@ gui.ebwizardry\:spell_book.mana_cost=Mana Cost: %s gui.ebwizardry\:spell_book.mana_cost_continuous=Mana Cost: %s/second gui.ebwizardry\:spell_book.mana_cost_undiscovered=Mana Cost: ? +gui.ebwizardry\:lectern.title=Spell Lookup + 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\: +container.ebwizardry\:arcane_workbench.mana=Mana\: %s/%s +container.ebwizardry\:arcane_workbench.upgrades=Applied Upgrades + +container.ebwizardry\:arcane_workbench.bookshelves=Bookshelves +container.ebwizardry\:arcane_workbench.sort_tier=Sort by Tier +container.ebwizardry\:arcane_workbench.sort_element=Sort by Element +container.ebwizardry\:arcane_workbench.sort_alphabetical=Sort Alphabetically +container.ebwizardry\:arcane_workbench.search_tooltip=%1$sSearch Tips\n\ntier=%2$s, %1$st=%2$s: match tiers\n%1$selement=%2$s, %1$se=%2$s: match elements\n%1$stype=%2$s, %1$sp=%2$s: match spell types\n%1$sdiscovered=true/false%2$s, %1$sd=t/f%2$s: include only (un)discovered\n%1$smodid=%2$s, %1$sm=%2$s: match mod IDs\n\nSeparate multiple values with %1$s,%2$s\nSeparate multiple criteria with %1$s; + +container.ebwizardry\:bookshelf=Bookshelf tier.novice=Novice tier.apprentice=Apprentice @@ -553,9 +783,12 @@ spell.ebwizardry\:arcane_lock=Arcane Lock spell.ebwizardry\:arrow_rain=Arrow Rain spell.ebwizardry\:banish=Banish spell.ebwizardry\:black_hole=Black Hole +spell.ebwizardry\:blinding_flash=Blinding Flash spell.ebwizardry\:blink=Blink spell.ebwizardry\:blizzard=Blizzard +spell.ebwizardry\:boulder=Boulder spell.ebwizardry\:bubble=Bubble +spell.ebwizardry\:celestial_smite=Celestial Smite spell.ebwizardry\:chain_lightning=Chain Lightning spell.ebwizardry\:charge=Charge spell.ebwizardry\:clairvoyance=Clairvoyance @@ -583,15 +816,19 @@ spell.ebwizardry\:divination=Divination spell.ebwizardry\:dragon_fireball=Dragon Fireball spell.ebwizardry\:earthquake=Earthquake spell.ebwizardry\:empowering_presence=Empowering Presence +spell.ebwizardry\:enrage=Enrage spell.ebwizardry\:entrapment=Entrapment spell.ebwizardry\:evade=Evade +spell.ebwizardry\:fangs=Fangs 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\:fire_breath=Fire Breath +spell.ebwizardry\:flamecatcher=Flamecatcher spell.ebwizardry\:flame_ray=Flame Ray spell.ebwizardry\:flaming_axe=Flaming Axe spell.ebwizardry\:flaming_weapon=Flaming Weapon @@ -606,6 +843,7 @@ 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_barrier=Frost Barrier spell.ebwizardry\:frost_ray=Frost Ray spell.ebwizardry\:frost_sigil=Frost Sigil spell.ebwizardry\:frost_step=Frost Step @@ -617,6 +855,7 @@ 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\:guardian_beam=Guardian Beam spell.ebwizardry\:hailstorm=Hailstorm spell.ebwizardry\:heal=Heal spell.ebwizardry\:heal_ally=Heal Ally @@ -650,15 +889,18 @@ 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\:mark_sacrifice=Mark Sacrifice 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\:mirage=Mirage spell.ebwizardry\:muffle=Muffle spell.ebwizardry\:none=[Empty Slot] spell.ebwizardry\:oakflesh=Oakflesh spell.ebwizardry\:paralysis=Paralysis +spell.ebwizardry\:permafrost=Permafrost spell.ebwizardry\:petrify=Petrify spell.ebwizardry\:phase_step=Phase Step spell.ebwizardry\:plague_of_darkness=Plague of Darkness @@ -667,6 +909,7 @@ spell.ebwizardry\:pocket_workbench=Pocket Workbench spell.ebwizardry\:poison=Poison spell.ebwizardry\:poison_bomb=Poison Bomb spell.ebwizardry\:possession=Possession +spell.ebwizardry\:radiant_totem=Radiant Totem spell.ebwizardry\:ray_of_purification=Ray of Purification spell.ebwizardry\:remove_curse=Remove Curse spell.ebwizardry\:replenish_hunger=Replenish Hunger @@ -690,6 +933,7 @@ 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\:stormcloud=Stormcloud spell.ebwizardry\:summon_blaze=Summon Blaze spell.ebwizardry\:summon_ice_giant=Summon Ice Giant spell.ebwizardry\:summon_ice_wraith=Summon Ice Wraith @@ -719,6 +963,8 @@ spell.ebwizardry\:water_breathing=Water Breathing spell.ebwizardry\:whirlwind=Whirlwind spell.ebwizardry\:wither=Wither spell.ebwizardry\:wither_skull=Wither Skull +spell.ebwizardry\:withering_totem=Withering Totem +spell.ebwizardry\:zombie_apocalypse=Zombie Apocalypse 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. @@ -727,9 +973,12 @@ spell.ebwizardry\:arcane_lock.desc=Creates an impenetrable barrier of force arou 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\:blinding_flash.desc=Emits an intense flash of light that blinds all nearby creatures. 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\:boulder.desc="Let the world be my bowling alley, and my foes be the pins!" 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\:celestial_smite.desc="Oh powers divine, I call upon thee now! Rid this land of dark creatures most foul!" 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 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. @@ -757,15 +1006,19 @@ spell.ebwizardry\:divination.desc=Guides the caster to nearby ores and resources 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\:enrage.desc=Causes nearby creatures to become enraged, stopping whatever they are doing to attack the caster instead. 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\:fangs.desc=Summons a row of evoker fangs in front of the caster that rise from the ground, biting mobs in their path. 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="Purge the land and purge the soil!\nTrees shall burn and lakes shall boil!\nTorch the wood and scorch the stone!\nAll bow before the infernal throne!" spell.ebwizardry\:fire_breath.desc="I am the dragon." +spell.ebwizardry\:flamecatcher.desc=It is said that in times of need, Flamecatcher offers her power to worthy pyromancers, only to vanish back into the night once her deed is done. 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. @@ -780,6 +1033,7 @@ spell.ebwizardry\:forest_of_thorns.desc=Amidst the wood lies a hidden glade,\nIn 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_barrier.desc=Creates a barricade of ice in front of the caster that shields them from incoming attacks. The barrier lasts for 20 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. @@ -791,6 +1045,7 @@ spell.ebwizardry\:greater_telekinesis.desc=Allows the caster to pick up a block 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\:guardian_beam.desc=Fires a guardian eye beam in the direction you are pointing, which damages targets and depletes their air supply when underwater. 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. @@ -824,15 +1079,18 @@ spell.ebwizardry\:lightning_ray.desc=Creates a stream of lightning in the direct 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\:mark_sacrifice.desc=Marks the target as a divine sacrifice. The next magical attack on a marked creature deals 60%% extra damage for each level of the effect. 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\:mirage.desc=Creates an illusion where the caster appears to be a few blocks away from their actual position, blinking around every few seconds. The illusion lasts for 20 seconds. 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\:permafrost.desc=Spreads a layer of slippery ice on the ground where you are pointing, the extreme cold of which causes creatures to slowly take damage. 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... @@ -841,6 +1099,7 @@ spell.ebwizardry\:pocket_workbench.desc=Allows the caster to craft items as if t 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\:radiant_totem.desc=Creates a radiant totem which shoots a beam of light at nearby creatures, healing allies but damaging mobs. Lasts for 30 seconds. Potency will increase the number of targets. 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 3 hunger points. @@ -864,6 +1123,7 @@ spell.ebwizardry\:spectral_pathway.desc=Creates an indestructible magical bridge 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\:stormcloud.desc=Conjures a floating stormcloud where you are pointing that strikes lightning beneath it. 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. @@ -893,6 +1153,8 @@ spell.ebwizardry\:water_breathing.desc=Allows the caster to breathe underwater f 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\:withering_totem.desc=Summons a withering totem where you are pointing, which slowly drains nearby creatures' health. The totem explodes after 30 seconds, dealing more damage the more health it drained. +spell.ebwizardry\:zombie_apocalypse.desc=Opening a portal to the underworld is easy. The difficult part is shutting it again when things go wrong... spell.ebwizardry\:invoke_weather.sun=The rain begins to stop... spell.ebwizardry\:invoke_weather.rain=The heavens open... @@ -998,6 +1260,8 @@ 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 +potion.ebwizardry\:mark_of_sacrifice=Mark of Sacrifice +potion.ebwizardry\:mirage=Mirage enchantment.ebwizardry\:magic_sword=Imbuement enchantment.ebwizardry\:magic_bow=Imbuement @@ -1023,6 +1287,14 @@ key.categories.ebwizardry=Wizardry key.ebwizardry.next_spell=Next Spell key.ebwizardry.previous_spell=Previous Spell +key.ebwizardry.spell_1=Spell Slot 1 +key.ebwizardry.spell_2=Spell Slot 2 +key.ebwizardry.spell_3=Spell Slot 3 +key.ebwizardry.spell_4=Spell Slot 4 +key.ebwizardry.spell_5=Spell Slot 5 +key.ebwizardry.spell_6=Spell Slot 6 +key.ebwizardry.spell_7=Spell Slot 7 +key.ebwizardry.spell_8=Spell Slot 8 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 @@ -1072,31 +1344,26 @@ 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.artefacts=Artefact Configuration +config.ebwizardry.category.artefacts.tooltip=Select which artefacts are enabled +config.ebwizardry.title.artefacts=Artefact Configuration +config.ebwizardry.subtitle.artefacts=Disable individual rings, amulets or charms here. + 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.tooltip=Whether spells cast by players can destroy blocks in the world. Wizardry makes every attempt to respect protection mods and plugins, but cannot guarantee it will work in all cases for every mod. If you need absolutely watertight anti-grief, disable this setting. (N.B. This setting only affects players. To prevent mobs 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.dispenser_block_damage=Dispenser Block Damage +config.ebwizardry.dispenser_block_damage.tooltip=Whether spells cast by dispensers can destroy blocks in the world. Wizardry makes every attempt to respect protection mods and plugins, but cannot guarantee it will work in all cases for every mod. If you need absolutely watertight anti-grief, disable this setting. 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 @@ -1109,14 +1376,6 @@ 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 @@ -1125,6 +1384,58 @@ 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.bonemeal_grows_crystal_flowers=Bonemeal Grows Crystal Flowers config.ebwizardry.bonemeal_grows_crystal_flowers.tooltip=Whether using bonemeal on grass blocks has a chance to grow crystal flowers. + +config.ebwizardry.category.difficulty=Difficulty Settings +config.ebwizardry.category.difficulty.tooltip=Configure wizardry's difficulty +config.ebwizardry.title.difficulty=Difficulty Settings +config.ebwizardry.subtitle.difficulty=Settings that affect the mod's difficulty. + +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.passive_mobs_are_allies=Passive Mobs Are Allies +config.ebwizardry.passive_mobs_are_allies.tooltip=Whether all passive mobs should count as allies, i.e. they should not be damaged indirectly by spells. Tamed mobs always count as allies. +config.ebwizardry.passive_mobs_are_allies.true=Yes - don't hurt the animals! +config.ebwizardry.passive_mobs_are_allies.false=No - I like my steak lightning-grilled! +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.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.progression_requirements=Progression Requirements +config.ebwizardry.progression_requirements.tooltip=The progression required to upgrade a wand to each tier (apprentice, advanced and master respectively). +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.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.category.tweaks=Tweaks Settings +config.ebwizardry.category.tweaks.tooltip=Tweak wizardry's behaviour +config.ebwizardry.title.tweaks=Tweaks Settings +config.ebwizardry.subtitle.tweaks=Assorted settings for tweaking the mod's behaviour. + +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.blindness_tweak=Blindness Tweak +config.ebwizardry.blindness_tweak.tooltip=Whether to tweak the blindness effect to reduce follow distance when used on non-players. This automatically disables itself in favour of Potion Core's implementation if installed. 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 @@ -1133,18 +1444,6 @@ 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 @@ -1159,6 +1458,12 @@ 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.bookshelf_blocks=Bookshelf Blocks +config.ebwizardry.bookshelf_blocks.tooltip=List of registry names of blocks that count as bookshelves for the arcane workbench and lectern. Block names are not case sensitive. For mod blocks, prefix with the mod ID (e.g. ebwizardry:oak_bookshelf). +config.ebwizardry.book_items=Book Items +config.ebwizardry.book_items.tooltip=List of registry names of items which can be placed in a bookshelf, in addition to the defaults. Item names are not case sensitive. For mod items, prefix with the mod ID (e.g. thaumcraft:thaumonomicon). +config.ebwizardry.bookshelf_search_radius=Bookshelf Search Radius +config.ebwizardry.bookshelf_search_radius.tooltip=The maximum number of blocks a bookshelf can be from an arcane workbench or lectern to be able to link to it. 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. @@ -1193,6 +1498,14 @@ 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. This list should not be empty; to disable shrines, use the shrine dimensions setting. +config.ebwizardry.library_dimensions=Library Ruin Dimensions +config.ebwizardry.library_dimensions.tooltip=List of ids of dimensions in which library ruins will generate. Remove all dimensions to disable library ruins completely. +config.ebwizardry.library_rarity=Library Ruin Rarity +config.ebwizardry.library_rarity.tooltip=Rarity of library ruins. 1 in this many chunks will contain a shrine, meaning higher numbers are rarer. +config.ebwizardry.library_files=Library Ruin Structure Files +config.ebwizardry.library_files.tooltip=List of structure file locations for surface library ruins. One of these files will be randomly selected each time a library ruin 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. This list should not be empty; to disable library ruins, use the library ruin dimensions setting. +config.ebwizardry.underground_library_files=Underground Library Ruin Structure Files +config.ebwizardry.underground_library_files.tooltip=List of structure file locations for underground library ruins. One of these files will be randomly selected each time a library ruin 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. This list should not be empty; to disable library ruins, use the library ruin dimensions setting. config.ebwizardry.tree_blocks=Tree Blocks config.ebwizardry.tree_blocks.tooltip=List of registry names of blocks which can be overwritten by wizardry's structure generators, affecting both fast and fancy structure generation. Most tree blocks and other foliage should work automatically, but those that don't can be added manually here. Block names are not case sensitive. For mod blocks, prefix with the mod ID (e.g. dynamictrees:oakbranch). config.ebwizardry.ore_dimensions=Ore Dimensions @@ -1213,6 +1526,14 @@ 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.show_spell_hud=Spell HUD +config.ebwizardry.show_spell_hud.tooltip=Whether to show the spell HUD in the corner of the screen when holding a wand. +config.ebwizardry.show_spell_hud.true=Shown +config.ebwizardry.show_spell_hud.false=Hidden +config.ebwizardry.show_charge_meter=Spell Charge Meter +config.ebwizardry.show_charge_meter.tooltip=Whether to show the spell charge-up meter around the crosshairs when charging up a spell. +config.ebwizardry.show_charge_meter.true=Shown +config.ebwizardry.show_charge_meter.false=Hidden 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 @@ -1238,6 +1559,10 @@ config.ebwizardry.screen_shake=Screen Shake config.ebwizardry.screen_shake.tooltip=Whether to use the screen shake effect for certain spells. config.ebwizardry.blink_effect=Blink Effect config.ebwizardry.blink_effect.tooltip=Whether to use the screen blink effect for teleportation spells. +config.ebwizardry.spellcasting_animations=Spellcasting Animations +config.ebwizardry.spellcasting_animations.tooltip=Whether to use custom animations for players casting spells. Disable this option if it conflicts with other mods that add player animations. +config.ebwizardry.donation_perk_element=Donation Perk Element +config.ebwizardry.donation_perk_element.tooltip=The element of the flying companion orb rendered on donor and contributor players, leave empty to disable the effect. If you're one of them, this setting will change how all players see you (if not, it won't do anything). config.ebwizardry.category.commands=Command Settings config.ebwizardry.category.commands.tooltip=Configure wizardry's commands @@ -1292,12 +1617,18 @@ 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. +config.ebwizardry.auto_place_library_markers=Auto-Place Library Markers +config.ebwizardry.auto_place_library_markers.tooltip=Controls whether wizardry automatically places antique atlas markers at the locations of surface library ruins. +config.ebwizardry.auto_place_underground_library_markers=Auto-Place Underground Library Markers +config.ebwizardry.auto_place_underground_library_markers.tooltip=Controls whether wizardry automatically places antique atlas markers at the locations of underground library ruins. integration.jei.category.ebwizardry\:arcane_workbench=Arcane Workbench +integration.jei.category.ebwizardry\:imbuement_altar=Imbuement Altar integration.antiqueatlas.marker.ebwizardry.wizard_tower=Wizard Tower integration.antiqueatlas.marker.ebwizardry.obelisk=Obelisk integration.antiqueatlas.marker.ebwizardry.shrine=Shrine +integration.antiqueatlas.marker.ebwizardry.library_ruins=Library Ruins spell.ebwizardry\:magic_missile_festive=Magic Mistletoe spell.ebwizardry\:summon_snow_golem_festive=Summon Frosty The Snowman diff --git a/src/main/resources/assets/ebwizardry/lang/en_us.lang b/src/main/resources/assets/ebwizardry/lang/en_us.lang index 33f94a68..86fcbbf4 100644 --- a/src/main/resources/assets/ebwizardry/lang/en_us.lang +++ b/src/main/resources/assets/ebwizardry/lang/en_us.lang @@ -1,4 +1,4 @@ -#PARSE_ESCAPES +# PARSE_ESCAPES tile.ebwizardry\:arcane_workbench.name=Arcane Workbench tile.ebwizardry\:crystal_ore.name=Crystal Ore tile.ebwizardry\:petrified_stone.name=Petrified Stone @@ -16,6 +16,8 @@ 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 +tile.ebwizardry\:crystal_flower_pot.name=Flower Pot +tile.ebwizardry\:permafrost.name=Permafrost tile.ebwizardry\:magic_crystal_block.name=Block of Crystal tile.ebwizardry\:fire_crystal_block.name=Block of Fiery Crystal @@ -26,32 +28,65 @@ 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 +tile.ebwizardry\:oak_gilded_wood.name=Gilded Oak Wood +tile.ebwizardry\:spruce_gilded_wood.name=Gilded Spruce Wood +tile.ebwizardry\:birch_gilded_wood.name=Gilded Birch Wood +tile.ebwizardry\:jungle_gilded_wood.name=Gilded Jungle Wood +tile.ebwizardry\:acacia_gilded_wood.name=Gilded Acacia Wood +tile.ebwizardry\:dark_oak_gilded_wood.name=Gilded Dark Oak Wood + +tile.ebwizardry\:oak_bookshelf.name=Oak Bookshelf +tile.ebwizardry\:spruce_bookshelf.name=Spruce Bookshelf +tile.ebwizardry\:birch_bookshelf.name=Birch Bookshelf +tile.ebwizardry\:jungle_bookshelf.name=Jungle Bookshelf +tile.ebwizardry\:acacia_bookshelf.name=Acacia Bookshelf +tile.ebwizardry\:dark_oak_bookshelf.name=Dark Oak Bookshelf + +tile.ebwizardry\:oak_lectern.name=Oak Lectern +tile.ebwizardry\:spruce_lectern.name=Spruce Lectern +tile.ebwizardry\:birch_lectern.name=Birch Lectern +tile.ebwizardry\:jungle_lectern.name=Jungle Lectern +tile.ebwizardry\:acacia_lectern.name=Acacia Lectern +tile.ebwizardry\:dark_oak_lectern.name=Dark Oak Lectern + +tile.ebwizardry\:receptacle.name=Receptacle +tile.ebwizardry\:imbuement_altar.name=Imbuement Altar + item.ebwizardry\:crystal_magic.name=Magic Crystal +item.ebwizardry\:crystal_magic.desc=The iridescent colors of this crystal seem to slowly shift and swirl, hinting at a magical energy within. Perhaps this magic could be harnessed, somehow... item.ebwizardry\:crystal_fire.name=Fiery Crystal +item.ebwizardry\:crystal_fire.desc=A crystal that glows a fiery orange. Maybe it is the key to unlocking the element of fire... item.ebwizardry\:crystal_ice.name=Icy Crystal +item.ebwizardry\:crystal_ice.desc=A crystal with a cold, bluish tint. Perhaps it holds the secrets to the power of frost... item.ebwizardry\:crystal_lightning.name=Stormy Crystal +item.ebwizardry\:crystal_lightning.desc=A crystal that seems to swirl and flash with a chaotic energy. Maybe it hides a greater power within... item.ebwizardry\:crystal_necromancy.name=Dark Crystal +item.ebwizardry\:crystal_necromancy.desc=A crystal with a deep, purplish appearance that seems to draw you in. Perhaps it is a gateway to something darker... item.ebwizardry\:crystal_earth.name=Verdant Crystal +item.ebwizardry\:crystal_earth.desc=A crystal patterened with a great many greens and browns. Perhaps it holds a connection to the forces of nature... item.ebwizardry\:crystal_sorcery.name=Mystical Crystal +item.ebwizardry\:crystal_sorcery.desc=A crystal that glimmers with a bluish-green light. Maybe it conceals the secrets of an ancient force... item.ebwizardry\:crystal_healing.name=Radiant Crystal +item.ebwizardry\:crystal_healing.desc=A crystal that glints a golden yellow in the sunlight. Perhaps it possesses the link to a divine power... -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\:spell_book.desc=A book filled with the runes and glyphs of an ancient script. Perhaps with dedication, one might be able to decipher its contents...\n\nRight-click while holding this book to read it. +item.ebwizardry\:spell_book.new=Not yet discovered! item.ebwizardry\:spell_book.apply_to_wizard=Replaced %1$s's spell %2$s with %3$s +item.ebwizardry\:ruined_spell_book.name=Ruined Spell Book +item.ebwizardry\:ruined_spell_book.desc=The pages of this tattered spell book are too damaged to be read properly, but somehow, you feel there is still magic left within it - if only it could be restored... + item.ebwizardry\:arcane_tome.name=Tome of Arcana item.ebwizardry\:arcane_tome.desc=Upgrades any %1$s wand with sufficient progression to %2$s tier item.ebwizardry\:wizard_handbook.name=The Wizard's Handbook -item.ebwizardry\:wizard_handbook.desc=by %1$s +item.ebwizardry\:wizard_handbook.author=by %1$s +item.ebwizardry\:wizard_handbook.desc=Your not-so-pocket-sized guide to the arcane arts! A must-have for any aspiring wizard who seeks to learn the secrets of magic.\n\nRight-click while holding this book to read it. item.ebwizardry\:wand.generic=wand -item.ebwizardry\:wand.buff=+%1$s %2$s potency +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 @@ -61,6 +96,11 @@ item.ebwizardry\:wand.levelup=%1$s is ready to upgrade to %2$s tier 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\: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\: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 @@ -103,14 +143,19 @@ item.ebwizardry\:spectral_pickaxe_upgraded.name=Spectral Pickaxe 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\:mana_flask.desc=Craft with a wand or piece of wizard armor to restore %s mana item.ebwizardry\:grand_crystal.name=Grand Magic Crystal +item.ebwizardry\:grand_crystal.desc=A huge magic crystal, several times the size of those found naturally. Someone must have grown it artificially - but how? item.ebwizardry\:crystal_shard.name=Magic Crystal Shard +item.ebwizardry\:crystal_shard.desc=A small piece of a shattered magic crystal. It contains little mana on its own, but it might have other uses... item.ebwizardry\:astral_diamond.name=Astral Diamond +item.ebwizardry\:astral_diamond.desc=A highly-prized gemstone that sparkles brightly under starlight. It is said that the most powerful wizards seek astral diamonds in return for the most elusive arcane knowledge. item.ebwizardry\:purifying_elixir.name=Purifying Elixir item.ebwizardry\:purifying_elixir.desc=Removes curses when consumed +item.ebwizardry\:purifying_elixir.desc_extended=A small vial of exotic-looking golden liquid, which clearly has powerful healing properties. It would probably be wise to save this for when it is needed. item.ebwizardry\:storage_upgrade.name=Wand Storage Upgrade item.ebwizardry\:siphon_upgrade.name=Wand Siphon Upgrade @@ -122,6 +167,8 @@ 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\:wand_upgrade.generic.desc=Apply this upgrade to a wand in the arcane workbench. + 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 @@ -147,18 +194,22 @@ 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\:scroll.desc=An enchanted sheet of parchment bound loosely with string. Upon the parchment is written an incantation of some kind, presumably to be read aloud.\n\nRight-click while holding this scroll to use it. item.ebwizardry\:identification_scroll.name=Scroll of Identification item.ebwizardry\:identification_scroll.desc=Identifies an unknown spell book or scroll +item.ebwizardry\:identification_scroll.desc_extended=Right-click while holding this scroll to identify the first undiscovered spell book or scroll on your hotbar. item.ebwizardry\:identification_scroll.nothing_to_identify=Nothing to identify! 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\:armour_upgrade.desc=Upgrades any wizard armour to make it %1$slegendary +item.ebwizardry\:armour_upgrade.desc_extended=Apply this upgrade to a piece of wizard armour in the arcane workbench. item.ebwizardry\:magic_silk.name=Magical Silk +item.ebwizardry\:spectral_dust.name=Spectral Dust + item.ebwizardry\:wizard_armour.legendary=Legendary -item.ebwizardry\:wizard_armour.buff=-%1$s %2$s cost +item.ebwizardry\:wizard_armour.buff=-%1$s%% %2$s cost item.ebwizardry\:wizard_armour.mana=Mana\: %1$s/%2$s item.ebwizardry\:wizard_hat.name=Wizard Hat @@ -210,6 +261,13 @@ item.ebwizardry\:spectral_leggings.name=Spectral Leggings item.ebwizardry\:spectral_boots.name=Spectral Boots item.ebwizardry\:lightning_hammer.name=Lightning Hammer +item.ebwizardry\:flamecatcher.name=Flamecatcher + +item.ebwizardry\:generic.disabled=This item has been disabled in the config + +item.ebwizardry\:ring.generic.desc=Equip this ring in the Baubles inventory or by placing it on your hotbar. Up to 2 rings may be equipped at once. +item.ebwizardry\:amulet.generic.desc=Equip this amulet in the Baubles inventory or by placing it on your hotbar. Only one amulet may be equipped at a time. +item.ebwizardry\:charm.generic.desc=Equip this charm in the Baubles inventory or by placing it on your hotbar. Only one charm may be equipped at a time. item.ebwizardry\:ring_condensing.name=Ring of Condensing item.ebwizardry\:ring_condensing.desc=Slowly regenerates mana for all wands on your hotbar @@ -225,6 +283,8 @@ 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_meteor.name=Ring of Volthraa +item.ebwizardry\:ring_meteor.desc=Instead of falling from the sky, meteor can be aimed like a projectile 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 @@ -241,6 +301,8 @@ 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_stormcloud.name=Lokenzen's Ring +item.ebwizardry\:ring_stormcloud.desc=Stormcloud moves towards targets and lasts longer with every strike 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 @@ -257,6 +319,8 @@ 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_evoker.name=Evoker's Ring +item.ebwizardry\:ring_evoker.desc=Sneaking while casting fangs summons a defensive circle instead of a line 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 @@ -286,6 +350,8 @@ 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_frost_warding.name=Amulet of Esundor +item.ebwizardry\:amulet_frost_warding.desc=Grants a warding effect when standing behind a wall of frost 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 @@ -308,11 +374,17 @@ 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\:amulet_absorption.name=Amulet of Vitality +item.ebwizardry\:amulet_absorption.desc=Excess health from healing spells is converted into absorption hearts 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 50%% +item.ebwizardry\:charm_move_speed.name=Icarus Medallion +item.ebwizardry\:charm_move_speed.desc=Move at near-full speed when casting spells +item.ebwizardry\:charm_spell_discovery.name=Archivist's Eyeglass +item.ebwizardry\:charm_spell_discovery.desc=Spell books found in chests are much more likely to be ones you have not yet discovered 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 @@ -323,6 +395,10 @@ 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_undead_helmets.name=Cursed Helm +item.ebwizardry\:charm_undead_helmets.desc=Summoned zombies and skeletons have leather caps to protect them from sunlight +item.ebwizardry\:charm_hunger_casting.name=Demonic Seal +item.ebwizardry\:charm_hunger_casting.desc=When out of mana, hunger can be used to cast spells 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 @@ -331,14 +407,20 @@ 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_sixth_sense.name=Dire Wolf Pelt +item.ebwizardry\:charm_sixth_sense.desc=Sixth sense displays different colors for passive, neutral and hostile mobs 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_black_hole.name=Void Opal +item.ebwizardry\:charm_black_hole.desc=Black hole sucks in and destroys blocks +item.ebwizardry\:charm_mount_teleporting.name=Spectral Tether +item.ebwizardry\:charm_mount_teleporting.desc=Teleporting while riding an animal or vehicle brings it with you 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.ebwizardry\:charm_feeding.desc=Replenish hunger and satiety trigger automatically when bound to a wand on your hotbar item.charge_status.full=Full item.charge_status.almost_full=Almost full @@ -348,6 +430,125 @@ item.charge_status.mostly_empty=Mostly empty item.charge_status.almost_empty=Almost empty item.charge_status.empty=Empty +item.banner.wizardry_fire.black=Black Fire Emblem +item.banner.wizardry_fire.red=Red Fire Emblem +item.banner.wizardry_fire.green=Green Fire Emblem +item.banner.wizardry_fire.brown=Brown Fire Emblem +item.banner.wizardry_fire.blue=Blue Fire Emblem +item.banner.wizardry_fire.purple=Purple Fire Emblem +item.banner.wizardry_fire.cyan=Cyan Fire Emblem +item.banner.wizardry_fire.silver=Light Gray Fire Emblem +item.banner.wizardry_fire.gray=Gray Fire Emblem +item.banner.wizardry_fire.pink=Pink Fire Emblem +item.banner.wizardry_fire.lime=Lime Fire Emblem +item.banner.wizardry_fire.yellow=Yellow Fire Emblem +item.banner.wizardry_fire.lightBlue=Light Blue Fire Emblem +item.banner.wizardry_fire.magenta=Magenta Fire Emblem +item.banner.wizardry_fire.orange=Orange Fire Emblem +item.banner.wizardry_fire.white=White Fire Emblem + +item.banner.wizardry_ice.black=Black Ice Emblem +item.banner.wizardry_ice.red=Red Ice Emblem +item.banner.wizardry_ice.green=Green Ice Emblem +item.banner.wizardry_ice.brown=Brown Ice Emblem +item.banner.wizardry_ice.blue=Blue Ice Emblem +item.banner.wizardry_ice.purple=Purple Ice Emblem +item.banner.wizardry_ice.cyan=Cyan Ice Emblem +item.banner.wizardry_ice.silver=Light Gray Ice Emblem +item.banner.wizardry_ice.gray=Gray Ice Emblem +item.banner.wizardry_ice.pink=Pink Ice Emblem +item.banner.wizardry_ice.lime=Lime Ice Emblem +item.banner.wizardry_ice.yellow=Yellow Ice Emblem +item.banner.wizardry_ice.lightBlue=Light Blue Ice Emblem +item.banner.wizardry_ice.magenta=Magenta Ice Emblem +item.banner.wizardry_ice.orange=Orange Ice Emblem +item.banner.wizardry_ice.white=White Ice Emblem + +item.banner.wizardry_lightning.black=Black Lightning Emblem +item.banner.wizardry_lightning.red=Red Lightning Emblem +item.banner.wizardry_lightning.green=Green Lightning Emblem +item.banner.wizardry_lightning.brown=Brown Lightning Emblem +item.banner.wizardry_lightning.blue=Blue Lightning Emblem +item.banner.wizardry_lightning.purple=Purple Lightning Emblem +item.banner.wizardry_lightning.cyan=Cyan Lightning Emblem +item.banner.wizardry_lightning.silver=Light Gray Lightning Emblem +item.banner.wizardry_lightning.gray=Gray Lightning Emblem +item.banner.wizardry_lightning.pink=Pink Lightning Emblem +item.banner.wizardry_lightning.lime=Lime Lightning Emblem +item.banner.wizardry_lightning.yellow=Yellow Lightning Emblem +item.banner.wizardry_lightning.lightBlue=Light Blue Lightning Emblem +item.banner.wizardry_lightning.magenta=Magenta Lightning Emblem +item.banner.wizardry_lightning.orange=Orange Lightning Emblem +item.banner.wizardry_lightning.white=White Lightning Emblem + +item.banner.wizardry_necromancy.black=Black Necromancy Emblem +item.banner.wizardry_necromancy.red=Red Necromancy Emblem +item.banner.wizardry_necromancy.green=Green Necromancy Emblem +item.banner.wizardry_necromancy.brown=Brown Necromancy Emblem +item.banner.wizardry_necromancy.blue=Blue Necromancy Emblem +item.banner.wizardry_necromancy.purple=Purple Necromancy Emblem +item.banner.wizardry_necromancy.cyan=Cyan Necromancy Emblem +item.banner.wizardry_necromancy.silver=Light Gray Necromancy Emblem +item.banner.wizardry_necromancy.gray=Gray Necromancy Emblem +item.banner.wizardry_necromancy.pink=Pink Necromancy Emblem +item.banner.wizardry_necromancy.lime=Lime Necromancy Emblem +item.banner.wizardry_necromancy.yellow=Yellow Necromancy Emblem +item.banner.wizardry_necromancy.lightBlue=Light Blue Necromancy Emblem +item.banner.wizardry_necromancy.magenta=Magenta Necromancy Emblem +item.banner.wizardry_necromancy.orange=Orange Necromancy Emblem +item.banner.wizardry_necromancy.white=White Necromancy Emblem + +item.banner.wizardry_earth.black=Black Earth Emblem +item.banner.wizardry_earth.red=Red Earth Emblem +item.banner.wizardry_earth.green=Green Earth Emblem +item.banner.wizardry_earth.brown=Brown Earth Emblem +item.banner.wizardry_earth.blue=Blue Earth Emblem +item.banner.wizardry_earth.purple=Purple Earth Emblem +item.banner.wizardry_earth.cyan=Cyan Earth Emblem +item.banner.wizardry_earth.silver=Light Gray Earth Emblem +item.banner.wizardry_earth.gray=Gray Earth Emblem +item.banner.wizardry_earth.pink=Pink Earth Emblem +item.banner.wizardry_earth.lime=Lime Earth Emblem +item.banner.wizardry_earth.yellow=Yellow Earth Emblem +item.banner.wizardry_earth.lightBlue=Light Blue Earth Emblem +item.banner.wizardry_earth.magenta=Magenta Earth Emblem +item.banner.wizardry_earth.orange=Orange Earth Emblem +item.banner.wizardry_earth.white=White Earth Emblem + +item.banner.wizardry_sorcery.black=Black Sorcery Emblem +item.banner.wizardry_sorcery.red=Red Sorcery Emblem +item.banner.wizardry_sorcery.green=Green Sorcery Emblem +item.banner.wizardry_sorcery.brown=Brown Sorcery Emblem +item.banner.wizardry_sorcery.blue=Blue Sorcery Emblem +item.banner.wizardry_sorcery.purple=Purple Sorcery Emblem +item.banner.wizardry_sorcery.cyan=Cyan Sorcery Emblem +item.banner.wizardry_sorcery.silver=Light Gray Sorcery Emblem +item.banner.wizardry_sorcery.gray=Gray Sorcery Emblem +item.banner.wizardry_sorcery.pink=Pink Sorcery Emblem +item.banner.wizardry_sorcery.lime=Lime Sorcery Emblem +item.banner.wizardry_sorcery.yellow=Yellow Sorcery Emblem +item.banner.wizardry_sorcery.lightBlue=Light Blue Sorcery Emblem +item.banner.wizardry_sorcery.magenta=Magenta Sorcery Emblem +item.banner.wizardry_sorcery.orange=Orange Sorcery Emblem +item.banner.wizardry_sorcery.white=White Sorcery Emblem + +item.banner.wizardry_healing.black=Black Healing Emblem +item.banner.wizardry_healing.red=Red Healing Emblem +item.banner.wizardry_healing.green=Green Healing Emblem +item.banner.wizardry_healing.brown=Brown Healing Emblem +item.banner.wizardry_healing.blue=Blue Healing Emblem +item.banner.wizardry_healing.purple=Purple Healing Emblem +item.banner.wizardry_healing.cyan=Cyan Healing Emblem +item.banner.wizardry_healing.silver=Light Gray Healing Emblem +item.banner.wizardry_healing.gray=Gray Healing Emblem +item.banner.wizardry_healing.pink=Pink Healing Emblem +item.banner.wizardry_healing.lime=Lime Healing Emblem +item.banner.wizardry_healing.yellow=Yellow Healing Emblem +item.banner.wizardry_healing.lightBlue=Light Blue Healing Emblem +item.banner.wizardry_healing.magenta=Magenta Healing Emblem +item.banner.wizardry_healing.orange=Orange Healing Emblem +item.banner.wizardry_healing.white=White Healing Emblem + entity.ebwizardry\:summonedcreature.nameplate=%1$s's %2$s entity.ebwizardry\:summonedcreature.nameplate_fallback=Someone's %1$s @@ -396,6 +597,7 @@ 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\:remnant.name=Remnant entity.ebwizardry\:magic_missile.name=Magic entity.ebwizardry\:arc.name=Magic @@ -421,6 +623,7 @@ 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\:flamecatcher_arrow.name=Magic entity.ebwizardry\:black_hole.name=Black Hole entity.ebwizardry\:shield.name=Shield @@ -436,6 +639,11 @@ 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 +entity.ebwizardry\:stormcloud.name=Stormcloud +entity.ebwizardry\:zombie_spawner.name=Zombie Spawner +entity.ebwizardry\:boulder.name=Boulder +entity.ebwizardry\:radiant_totem.name=Radiant Totem +entity.ebwizardry\:withering_totem.name=Withering Totem itemGroup.ebwizardry=Wizardry itemGroup.ebwizardryspells=Spells @@ -488,7 +696,20 @@ advancement.ebwizardry\:artefact.desc=Lift the protective enchantments from a sh 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\:defeat_remnant=Dust Off Your Arrows +advancement.ebwizardry\:defeat_remnant.desc=Hunt down the remnants guarding an obelisk and collect their spectral dust +advancement.ebwizardry\:find_library_ruins=I Think My Books Are Overdue +advancement.ebwizardry\:find_library_ruins.desc=Uncover the ruins of a forgotten library +advancement.ebwizardry\:restore_imbuement_altar=Good As New +advancement.ebwizardry\:restore_imbuement_altar.desc=Restore an imbuement altar to its former glory +advancement.ebwizardry\:create_elemental_armour=Changing Clothes +advancement.ebwizardry\:create_elemental_armour.desc=Imbue a piece of wizard armor with spectral dust of one of the arcane elements +advancement.ebwizardry\:restore_ruined_book=Arcane Archaeologist +advancement.ebwizardry\:restore_ruined_book.desc=Restore a ruined spell book at an imbuement altar +advancement.ebwizardry\:craft_lectern=Arcane Encyclopedia +advancement.ebwizardry\:craft_lectern.desc=Craft a lectern to keep track of your spell library + +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 @@ -505,10 +726,19 @@ gui.ebwizardry\:spell_book.mana_cost=Mana Cost: %s gui.ebwizardry\:spell_book.mana_cost_continuous=Mana Cost: %s/second gui.ebwizardry\:spell_book.mana_cost_undiscovered=Mana Cost: ? +gui.ebwizardry\:lectern.title=Spell Lookup + 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\: +container.ebwizardry\:arcane_workbench.mana=Mana\: %s/%s +container.ebwizardry\:arcane_workbench.upgrades=Applied Upgrades + +container.ebwizardry\:arcane_workbench.bookshelves=Bookshelves +container.ebwizardry\:arcane_workbench.sort_tier=Sort by Tier +container.ebwizardry\:arcane_workbench.sort_element=Sort by Element +container.ebwizardry\:arcane_workbench.sort_alphabetical=Sort Alphabetically +container.ebwizardry\:arcane_workbench.search_tooltip=%1$sSearch Tips\n\ntier=%2$s, %1$st=%2$s: match tiers\n%1$selement=%2$s, %1$se=%2$s: match elements\n%1$stype=%2$s, %1$sp=%2$s: match spell types\n%1$sdiscovered=true/false%2$s, %1$sd=t/f%2$s: include only (un)discovered\n%1$smodid=%2$s, %1$sm=%2$s: match mod IDs\n\nSeparate multiple values with %1$s,%2$s\nSeparate multiple criteria with %1$s; + +container.ebwizardry\:bookshelf=Bookshelf tier.novice=Novice tier.apprentice=Apprentice @@ -553,9 +783,12 @@ spell.ebwizardry\:arcane_lock=Arcane Lock spell.ebwizardry\:arrow_rain=Arrow Rain spell.ebwizardry\:banish=Banish spell.ebwizardry\:black_hole=Black Hole +spell.ebwizardry\:blinding_flash=Blinding Flash spell.ebwizardry\:blink=Blink spell.ebwizardry\:blizzard=Blizzard +spell.ebwizardry\:boulder=Boulder spell.ebwizardry\:bubble=Bubble +spell.ebwizardry\:celestial_smite=Celestial Smite spell.ebwizardry\:chain_lightning=Chain Lightning spell.ebwizardry\:charge=Charge spell.ebwizardry\:clairvoyance=Clairvoyance @@ -583,15 +816,19 @@ spell.ebwizardry\:divination=Divination spell.ebwizardry\:dragon_fireball=Dragon Fireball spell.ebwizardry\:earthquake=Earthquake spell.ebwizardry\:empowering_presence=Empowering Presence +spell.ebwizardry\:enrage=Enrage spell.ebwizardry\:entrapment=Entrapment spell.ebwizardry\:evade=Evade +spell.ebwizardry\:fangs=Fangs 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\:fire_breath=Fire Breath +spell.ebwizardry\:flamecatcher=Flamecatcher spell.ebwizardry\:flame_ray=Flame Ray spell.ebwizardry\:flaming_axe=Flaming Axe spell.ebwizardry\:flaming_weapon=Flaming Weapon @@ -606,6 +843,7 @@ 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_barrier=Frost Barrier spell.ebwizardry\:frost_ray=Frost Ray spell.ebwizardry\:frost_sigil=Frost Sigil spell.ebwizardry\:frost_step=Frost Step @@ -617,6 +855,7 @@ 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\:guardian_beam=Guardian Beam spell.ebwizardry\:hailstorm=Hailstorm spell.ebwizardry\:heal=Heal spell.ebwizardry\:heal_ally=Heal Ally @@ -650,15 +889,18 @@ 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\:mark_sacrifice=Mark Sacrifice 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\:mirage=Mirage spell.ebwizardry\:muffle=Muffle spell.ebwizardry\:none=[Empty Slot] spell.ebwizardry\:oakflesh=Oakflesh spell.ebwizardry\:paralysis=Paralysis +spell.ebwizardry\:permafrost=Permafrost spell.ebwizardry\:petrify=Petrify spell.ebwizardry\:phase_step=Phase Step spell.ebwizardry\:plague_of_darkness=Plague of Darkness @@ -667,6 +909,7 @@ spell.ebwizardry\:pocket_workbench=Pocket Workbench spell.ebwizardry\:poison=Poison spell.ebwizardry\:poison_bomb=Poison Bomb spell.ebwizardry\:possession=Possession +spell.ebwizardry\:radiant_totem=Radiant Totem spell.ebwizardry\:ray_of_purification=Ray of Purification spell.ebwizardry\:remove_curse=Remove Curse spell.ebwizardry\:replenish_hunger=Replenish Hunger @@ -690,6 +933,7 @@ 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\:stormcloud=Stormcloud spell.ebwizardry\:summon_blaze=Summon Blaze spell.ebwizardry\:summon_ice_giant=Summon Ice Giant spell.ebwizardry\:summon_ice_wraith=Summon Ice Wraith @@ -719,6 +963,8 @@ spell.ebwizardry\:water_breathing=Water Breathing spell.ebwizardry\:whirlwind=Whirlwind spell.ebwizardry\:wither=Wither spell.ebwizardry\:wither_skull=Wither Skull +spell.ebwizardry\:withering_totem=Withering Totem +spell.ebwizardry\:zombie_apocalypse=Zombie Apocalypse 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. @@ -727,9 +973,12 @@ spell.ebwizardry\:arcane_lock.desc=Creates an impenetrable barrier of force arou 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\:blinding_flash.desc=Emits an intense flash of light that blinds all nearby creatures. 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\:boulder.desc="Let the world be my bowling alley, and my foes be the pins!" 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\:celestial_smite.desc="Oh powers divine, I call upon thee now! Rid this land of dark creatures most foul!" 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 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. @@ -757,15 +1006,19 @@ spell.ebwizardry\:divination.desc=Guides the caster to nearby ores and resources 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\:enrage.desc=Causes nearby creatures to become enraged, stopping whatever they are doing to attack the caster instead. 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\:fangs.desc=Summons a row of evoker fangs in front of the caster that rise from the ground, biting mobs in their path. 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="Purge the land and purge the soil!\nTrees shall burn and lakes shall boil!\nTorch the wood and scorch the stone!\nAll bow before the infernal throne!" spell.ebwizardry\:fire_breath.desc="I am the dragon." +spell.ebwizardry\:flamecatcher.desc=It is said that in times of need, Flamecatcher offers her power to worthy pyromancers, only to vanish back into the night once her deed is done. 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. @@ -780,6 +1033,7 @@ spell.ebwizardry\:forest_of_thorns.desc=Amidst the wood lies a hidden glade,\nIn 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_barrier.desc=Creates a barricade of ice in front of the caster that shields them from incoming attacks. The barrier lasts for 20 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. @@ -791,6 +1045,7 @@ spell.ebwizardry\:greater_telekinesis.desc=Allows the caster to pick up a block 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\:guardian_beam.desc=Fires a guardian eye beam in the direction you are pointing, which damages targets and depletes their air supply when underwater. 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. @@ -824,15 +1079,18 @@ spell.ebwizardry\:lightning_ray.desc=Creates a stream of lightning in the direct 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\:mark_sacrifice.desc=Marks the target as a divine sacrifice. The next magical attack on a marked creature deals 60%% extra damage for each level of the effect. 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\:mirage.desc=Creates an illusion where the caster appears to be a few blocks away from their actual position, blinking around every few seconds. The illusion lasts for 20 seconds. 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\:permafrost.desc=Spreads a layer of slippery ice on the ground where you are pointing, the extreme cold of which causes creatures to slowly take damage. 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... @@ -841,6 +1099,7 @@ spell.ebwizardry\:pocket_workbench.desc=Allows the caster to craft items as if t 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\:radiant_totem.desc=Creates a radiant totem which shoots a beam of light at nearby creatures, healing allies but damaging mobs. Lasts for 30 seconds. Potency will increase the number of targets. 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 3 hunger points. @@ -864,6 +1123,7 @@ spell.ebwizardry\:spectral_pathway.desc=Creates an indestructible magical bridge 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\:stormcloud.desc=Conjures a floating stormcloud where you are pointing that strikes lightning beneath it. 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. @@ -893,6 +1153,8 @@ spell.ebwizardry\:water_breathing.desc=Allows the caster to breathe underwater f 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\:withering_totem.desc=Summons a withering totem where you are pointing, which slowly drains nearby creatures' health. The totem explodes after 30 seconds, dealing more damage the more health it drained. +spell.ebwizardry\:zombie_apocalypse.desc=Opening a portal to the underworld is easy. The difficult part is shutting it again when things go wrong... spell.ebwizardry\:invoke_weather.sun=The rain begins to stop... spell.ebwizardry\:invoke_weather.rain=The heavens open... @@ -998,6 +1260,8 @@ 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 +potion.ebwizardry\:mark_of_sacrifice=Mark of Sacrifice +potion.ebwizardry\:mirage=Mirage enchantment.ebwizardry\:magic_sword=Imbuement enchantment.ebwizardry\:magic_bow=Imbuement @@ -1023,6 +1287,14 @@ key.categories.ebwizardry=Wizardry key.ebwizardry.next_spell=Next Spell key.ebwizardry.previous_spell=Previous Spell +key.ebwizardry.spell_1=Spell Slot 1 +key.ebwizardry.spell_2=Spell Slot 2 +key.ebwizardry.spell_3=Spell Slot 3 +key.ebwizardry.spell_4=Spell Slot 4 +key.ebwizardry.spell_5=Spell Slot 5 +key.ebwizardry.spell_6=Spell Slot 6 +key.ebwizardry.spell_7=Spell Slot 7 +key.ebwizardry.spell_8=Spell Slot 8 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 @@ -1072,31 +1344,26 @@ 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.artefacts=Artifact Configuration +config.ebwizardry.category.artefacts.tooltip=Select which artifacts are enabled +config.ebwizardry.title.artefacts=Artifact Configuration +config.ebwizardry.subtitle.artefacts=Disable individual rings, amulets or charms here. + 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.tooltip=Whether spells cast by players can destroy blocks in the world. Wizardry makes every attempt to respect protection mods and plugins, but cannot guarantee it will work in all cases for every mod. If you need absolutely watertight anti-grief, disable this setting. (N.B. This setting only affects players. To prevent mobs 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.dispenser_block_damage=Dispenser Block Damage +config.ebwizardry.dispenser_block_damage.tooltip=Whether spells cast by dispensers can destroy blocks in the world. Wizardry makes every attempt to respect protection mods and plugins, but cannot guarantee it will work in all cases for every mod. If you need absolutely watertight anti-grief, disable this setting. 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 @@ -1109,14 +1376,6 @@ 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 @@ -1125,6 +1384,58 @@ 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.bonemeal_grows_crystal_flowers=Bonemeal Grows Crystal Flowers config.ebwizardry.bonemeal_grows_crystal_flowers.tooltip=Whether using bonemeal on grass blocks has a chance to grow crystal flowers. + +config.ebwizardry.category.difficulty=Difficulty Settings +config.ebwizardry.category.difficulty.tooltip=Configure wizardry's difficulty +config.ebwizardry.title.difficulty=Difficulty Settings +config.ebwizardry.subtitle.difficulty=Settings that affect the mod's difficulty. + +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.passive_mobs_are_allies=Passive Mobs Are Allies +config.ebwizardry.passive_mobs_are_allies.tooltip=Whether all passive mobs should count as allies, i.e. they should not be damaged indirectly by spells. Tamed mobs always count as allies. +config.ebwizardry.passive_mobs_are_allies.true=Yes - don't hurt the animals! +config.ebwizardry.passive_mobs_are_allies.false=No - I like my steak lightning-grilled! +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.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.progression_requirements=Progression Requirements +config.ebwizardry.progression_requirements.tooltip=The progression required to upgrade a wand to each tier (apprentice, advanced and master respectively). +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.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.category.tweaks=Tweaks Settings +config.ebwizardry.category.tweaks.tooltip=Tweak wizardry's behaviour +config.ebwizardry.title.tweaks=Tweaks Settings +config.ebwizardry.subtitle.tweaks=Assorted settings for tweaking the mod's behaviour. + +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.blindness_tweak=Blindness Tweak +config.ebwizardry.blindness_tweak.tooltip=Whether to tweak the blindness effect to reduce follow distance when used on non-players. This automatically disables itself in favour of Potion Core's implementation if installed. 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 @@ -1133,18 +1444,6 @@ 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 @@ -1159,6 +1458,12 @@ 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.bookshelf_blocks=Bookshelf Blocks +config.ebwizardry.bookshelf_blocks.tooltip=List of registry names of blocks that count as bookshelves for the arcane workbench and lectern. Block names are not case sensitive. For mod blocks, prefix with the mod ID (e.g. ebwizardry:oak_bookshelf). +config.ebwizardry.book_items=Book Items +config.ebwizardry.book_items.tooltip=List of registry names of items which can be placed in a bookshelf, in addition to the defaults. Item names are not case sensitive. For mod items, prefix with the mod ID (e.g. thaumcraft:thaumonomicon). +config.ebwizardry.bookshelf_search_radius=Bookshelf Search Radius +config.ebwizardry.bookshelf_search_radius.tooltip=The maximum number of blocks a bookshelf can be from an arcane workbench or lectern to be able to link to it. 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. @@ -1193,6 +1498,14 @@ 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. This list should not be empty; to disable shrines, use the shrine dimensions setting. +config.ebwizardry.library_dimensions=Library Ruin Dimensions +config.ebwizardry.library_dimensions.tooltip=List of ids of dimensions in which library ruins will generate. Remove all dimensions to disable library ruins completely. +config.ebwizardry.library_rarity=Library Ruin Rarity +config.ebwizardry.library_rarity.tooltip=Rarity of library ruins. 1 in this many chunks will contain a shrine, meaning higher numbers are rarer. +config.ebwizardry.library_files=Library Ruin Structure Files +config.ebwizardry.library_files.tooltip=List of structure file locations for surface library ruins. One of these files will be randomly selected each time a library ruin 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. This list should not be empty; to disable library ruins, use the library ruin dimensions setting. +config.ebwizardry.underground_library_files=Underground Library Ruin Structure Files +config.ebwizardry.underground_library_files.tooltip=List of structure file locations for underground library ruins. One of these files will be randomly selected each time a library ruin 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. This list should not be empty; to disable library ruins, use the library ruin dimensions setting. config.ebwizardry.tree_blocks=Tree Blocks config.ebwizardry.tree_blocks.tooltip=List of registry names of blocks which can be overwritten by wizardry's structure generators, affecting both fast and fancy structure generation. Most tree blocks and other foliage should work automatically, but those that don't can be added manually here. Block names are not case sensitive. For mod blocks, prefix with the mod ID (e.g. dynamictrees:oakbranch). config.ebwizardry.ore_dimensions=Ore Dimensions @@ -1213,6 +1526,14 @@ 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.show_spell_hud=Spell HUD +config.ebwizardry.show_spell_hud.tooltip=Whether to show the spell HUD in the corner of the screen when holding a wand. +config.ebwizardry.show_spell_hud.true=Shown +config.ebwizardry.show_spell_hud.false=Hidden +config.ebwizardry.show_charge_meter=Spell Charge Meter +config.ebwizardry.show_charge_meter.tooltip=Whether to show the spell charge-up meter around the crosshairs when charging up a spell. +config.ebwizardry.show_charge_meter.true=Shown +config.ebwizardry.show_charge_meter.false=Hidden 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 @@ -1238,6 +1559,10 @@ config.ebwizardry.screen_shake=Screen Shake config.ebwizardry.screen_shake.tooltip=Whether to use the screen shake effect for certain spells. config.ebwizardry.blink_effect=Blink Effect config.ebwizardry.blink_effect.tooltip=Whether to use the screen blink effect for teleportation spells. +config.ebwizardry.spellcasting_animations=Spellcasting Animations +config.ebwizardry.spellcasting_animations.tooltip=Whether to use custom animations for players casting spells. Disable this option if it conflicts with other mods that add player animations. +config.ebwizardry.donation_perk_element=Donation Perk Element +config.ebwizardry.donation_perk_element.tooltip=The element of the flying companion orb rendered on donor and contributor players, leave empty to disable the effect. If you're one of them, this setting will change how all players see you (if not, it won't do anything). config.ebwizardry.category.commands=Command Settings config.ebwizardry.category.commands.tooltip=Configure wizardry's commands @@ -1292,12 +1617,18 @@ 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. +config.ebwizardry.auto_place_library_markers=Auto-Place Library Markers +config.ebwizardry.auto_place_library_markers.tooltip=Controls whether wizardry automatically places antique atlas markers at the locations of surface library ruins. +config.ebwizardry.auto_place_underground_library_markers=Auto-Place Underground Library Markers +config.ebwizardry.auto_place_underground_library_markers.tooltip=Controls whether wizardry automatically places antique atlas markers at the locations of underground library ruins. integration.jei.category.ebwizardry\:arcane_workbench=Arcane Workbench +integration.jei.category.ebwizardry\:imbuement_altar=Imbuement Altar integration.antiqueatlas.marker.ebwizardry.wizard_tower=Wizard Tower integration.antiqueatlas.marker.ebwizardry.obelisk=Obelisk integration.antiqueatlas.marker.ebwizardry.shrine=Shrine +integration.antiqueatlas.marker.ebwizardry.library_ruins=Library Ruins spell.ebwizardry\:magic_missile_festive=Magic Mistletoe spell.ebwizardry\:summon_snow_golem_festive=Summon Frosty The Snowman diff --git a/src/main/resources/assets/ebwizardry/lang/fr_fr.lang b/src/main/resources/assets/ebwizardry/lang/fr_fr.lang index b51fa23d..2993099b 100644 --- a/src/main/resources/assets/ebwizardry/lang/fr_fr.lang +++ b/src/main/resources/assets/ebwizardry/lang/fr_fr.lang @@ -1,30 +1,61 @@ 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:crystal_ore.name=Minerai de cristal +tile.ebwizardry:petrified_stone.name=Pierre 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 +tile.ebwizardry:transportation_stone.name=Pierre de téléportation +tile.ebwizardry:transportation_stone.confirm=Vous serez transporté ici en lançant le sort %1$s +tile.ebwizardry:transportation_stone.remember=La position %s, %s, %s a été marquée dans la dimension %s +tile.ebwizardry:transportation_stone.forget=La marque %s, %s, %s a été effacée dans la dimension %s +tile.ebwizardry:transportation_stone.invalid=Vous devez d'abord créer un cercle avec 8 pierres de téléportation! +tile.ebwizardry:spectral_block.name=Bloc spectral +tile.ebwizardry:runestone.name=Pierre runique +tile.ebwizardry:runestone_pedestal.name=Piédestal runique +tile.ebwizardry:thorns.name=Épines +tile.ebwizardry:obsidian_crust.name=Croûte d'obsidienne +tile.ebwizardry:dry_frosted_ice.name=Glace dépolie + +tile.ebwizardry:magic_crystal_block.name=Bloc de cristal +tile.ebwizardry:fire_crystal_block.name=Bloc de cristal embrasé +tile.ebwizardry:ice_crystal_block.name=Bloc de cristal gelé +tile.ebwizardry:lightning_crystal_block.name=Bloc de cristal électrique +tile.ebwizardry:necromancy_crystal_block.name=Bloc de cristal ténébreux +tile.ebwizardry:earth_crystal_block.name=Bloc de cristal verdoyant +tile.ebwizardry:sorcery_crystal_block.name=Bloc de cristal mystique +tile.ebwizardry:healing_crystal_block.name=Bloc de cristal éclatant + +item.ebwizardry:crystal_magic.name=Cristal magique +item.ebwizardry:crystal_fire.name=Cristal embrasé +item.ebwizardry:crystal_ice.name=Cristal gelé +item.ebwizardry:crystal_lightning.name=Cristal électrique +item.ebwizardry:crystal_necromancy.name=Cristal ténébreux +item.ebwizardry:crystal_earth.name=Cristal verdoyant +item.ebwizardry:crystal_sorcery.name=Cristal mystique +item.ebwizardry:crystal_healing.name=Cristal éclatant -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:spell_book.name=Livre de sorts + +item.ebwizardry:spell_book.apply_to_wizard=Le sort %2$s a été remplacé par %3$s sur %1$s 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:arcane_tome.desc=Améliore n'importe quelle baguette du tier %1$s avec assez de progression au tier %2$s -item.ebwizardry:wizard_handbook.name=Le manuel du sorcier -item.ebwizardry:wizard_handbook.desc=par %1$s +item.ebwizardry:wizard_handbook.name=The Wizard's Handbook +item.ebwizardry:wizard_handbook.desc=by %1$s + +item.ebwizardry:wand.generic=baguette 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.progression=Progression\: %1$s/%2$s + +item.ebwizardry:wand.levelup=%1$s est prête à être améliorée au tier %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 @@ -39,136 +70,329 @@ 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_lightning_wand.name=Baguette d'apprenti électromancien +item.ebwizardry:apprentice_necromancy_wand.name=Baguette d'apprenti nécromancien +item.ebwizardry:apprentice_earth_wand.name=Baguette d'apprenti géomancien 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_fire_wand.name=Baguette de pyromancien +item.ebwizardry:advanced_ice_wand.name=Baguette de cryomancien +item.ebwizardry:advanced_lightning_wand.name=Baguette d'électromancien +item.ebwizardry:advanced_necromancy_wand.name=Baguette de nécromancien +item.ebwizardry:advanced_earth_wand.name=Baguette de géomancien 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_lightning_wand.name=Baguette de maître électromancien +item.ebwizardry:master_necromancy_wand.name=Baguette de maître nécromancien +item.ebwizardry:master_earth_wand.name=Baguette de maître géomancien 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_sword.name=Épé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:spectral_sword_upgraded.name=Épée spectrale +item.ebwizardry:spectral_pickaxe_upgraded.name=Pioche spectrale + +item.ebwizardry:small_mana_flask.name=Petite fiole de mana +item.ebwizardry:medium_mana_flask.name=Fiole de mana +item.ebwizardry:large_mana_flask.name=Grande fiole de mana + +item.ebwizardry:grand_crystal.name=Cristal magique majeur +item.ebwizardry:crystal_shard.name=Éclat de cristal magique + +item.ebwizardry:astral_diamond.name=Diamant astral + +item.ebwizardry:purifying_elixir.name=Élixir purifiant +item.ebwizardry:purifying_elixir.desc=Élimine toutes les malédictions + 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:cooldown_upgrade.name=Amélioration de temps de récupération 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:melee_upgrade.name=Amélioration de mêlée de baguette + +item.ebwizardry:storage_upgrade.desc=Améliore la capacité en mana de la baguette +item.ebwizardry:siphon_upgrade.desc=Améliore la baguette pour extraire du mana des monstres tués +item.ebwizardry:condenser_upgrade.desc=Améliore la baguette pour qu'elle régénère lentement du mana sur la durée +item.ebwizardry:range_upgrade.desc=Améliore la baguette pour augmenter la portée effective des sorts lancés +item.ebwizardry:duration_upgrade.desc=Améliore la baguette pour faire durer plus longtemps ses sorts +item.ebwizardry:cooldown_upgrade.desc=Améliore les délais de récupération de la baguette +item.ebwizardry:blast_upgrade.desc=Améliore les zones d'impact de la baguette +item.ebwizardry:attunement_upgrade.desc=Augmente le nombre de sorts pouvant être attachés à la baguette +item.ebwizardry:melee_upgrade.desc=Améliore la baguette pour utiliser du mana pour faire plus de dégâts en frappant avec item.ebwizardry:flaming_axe.name=Hache enflammée item.ebwizardry:frost_axe.name=Hache gelée +item.ebwizardry:flaming_axe_upgraded.name=Hache enflamée +item.ebwizardry:frost_axe_upgraded.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:spark_bomb.name=Bombe d'étincelles item.ebwizardry:blank_scroll.name=Parchemin vierge +item.ebwizardry:scroll.generic=parchemin 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:scroll.undiscovered.name=Parchemin "%1$s" +item.ebwizardry:identification_scroll.name=Parchemin d'dentification +item.ebwizardry:identification_scroll.desc=Identifie un livre ou un parchemin 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:armour_upgrade.desc2=%1$srobe au tier %2$slégendaire item.ebwizardry:magic_silk.name=Soie magique -item.ebwizardry:wizard_armour.legendary=Legendaire +item.ebwizardry:wizard_armour.legendary=Légendaire 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_leggings.name=Guêtres 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_leggings_fire.name=Guêtres 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_leggings_ice.name=Guêtres 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_lightning.name=Chapeau d'éectomancien +item.ebwizardry:wizard_robe_lightning.name=Robe d'électromancien +item.ebwizardry:wizard_leggings_lightning.name=Guêtres d'électromancien +item.ebwizardry:wizard_boots_lightning.name=Bottes d'électromancien -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_hat_necromancy.name=Chapeau de nécromancien +item.ebwizardry:wizard_robe_necromancy.name=Robe de nécromancien +item.ebwizardry:wizard_leggings_necromancy.name=Guêtres 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_earth.name=Chapeau de géomancien +item.ebwizardry:wizard_robe_earth.name=Robe de géomancien +item.ebwizardry:wizard_leggings_earth.name=Guêtres de géomancien +item.ebwizardry:wizard_boots_earth.name=Bottes de géomancien 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_leggings_sorcery.name=Guêtres 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_leggings_healing.name=Guêtres 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:spawn_wizard.name=Oeuf de mage +item.ebwizardry:spawn_evil_wizard.name=Oeuf de mage noir 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 +item.ebwizardry:ring_condensing.name=Anneau de condensation +item.ebwizardry:ring_condensing.desc=Régénère lentement le mana de toutes les baguettes de la barre d'inventaire +item.ebwizardry:ring_siphoning.name=Anneau de siphon +item.ebwizardry:ring_siphoning.desc=Augmente de 30%% le mana siphoné +item.ebwizardry:ring_battlemage.name=Anneau du mage de guerre +item.ebwizardry:ring_battlemage.desc=Tenir une épée dans votre main principale et une baguette dans l'autre octroie 10%% de dégâts magiques supplémentaires +item.ebwizardry:ring_combustion.name=Anneau de combustion +item.ebwizardry:ring_combustion.desc=Les créatures tuées par des sorts de feu explosent +item.ebwizardry:ring_fire_melee.name=Anneau de touché embrasé +item.ebwizardry:ring_fire_melee.desc=Frapper une créature avec un baton de feu l'embrase +item.ebwizardry:ring_fire_biome.name=Anneau du désert +item.ebwizardry:ring_fire_biome.desc=Les sorts de feu sont 30%% plus puissants dans les biomes chauds +item.ebwizardry:ring_disintegration.name=Anneau des braises ardentes +item.ebwizardry:ring_disintegration.desc=Tous les sorts de feu d'attaque désintègrent leur victime +item.ebwizardry:ring_ice_melee.name=Anneau de touché glacial +item.ebwizardry:ring_ice_melee.desc=Frapper une entité avec une baguette de glace lui donne l'effet Morsure du froid +item.ebwizardry:ring_ice_biome.name=Anneau de glaciation +item.ebwizardry:ring_ice_biome.desc=Les sorts de glace sont 30%% plus puissants dans les biomes enneigés +item.ebwizardry:ring_arcane_frost.name=Anneau des arcanes gelées +item.ebwizardry:ring_arcane_frost.desc=Les créatures tuées par des sorts de glace projettent des éclats de glace +item.ebwizardry:ring_shattering.name=Anneau écrasant +item.ebwizardry:ring_shattering.desc=Les attaques de mêlée sur des entités avec Morsure du froid ont une chance de les faires éclater +item.ebwizardry:ring_lightning_melee.name=Anneau de conduction +item.ebwizardry:ring_lightning_melee.desc=Frapper une entité avec une baguette de foudre lance des éclairs sur les créatures à proximité +item.ebwizardry:ring_storm.name=Anneau des tempêtes +item.ebwizardry:ring_storm.desc=Pendant les orages, les sorts de foudre ont un délai de récupération drastiquement réduit +item.ebwizardry:ring_seeking.name=Anneau d'attraction +item.ebwizardry:ring_seeking.desc=Tous les sorts de projectile poursuivent leur cible +item.ebwizardry:ring_hammer.name=Anneau de Thalek le Tout-puissant +item.ebwizardry:ring_hammer.desc=Les marteaux de foudre peuvent être ramassés et lancés +item.ebwizardry:ring_soulbinding.name=L'anneau du Marcheur d'âme +item.ebwizardry:ring_soulbinding.desc=L'âme des créatures touchées par un sort de nécromancie est liée à la vôtre +item.ebwizardry:ring_leeching.name=Anneau de sangsue +item.ebwizardry:ring_leeching.desc=Tous les sorts de nécromancie ont 30%% de chance de déclencher un drain de vie +item.ebwizardry:ring_necromancy_melee.name=Anneau de touché nécrotique +item.ebwizardry:ring_necromancy_melee.desc=Frapper une entité avec une baguette de nécromancie lui inflige l'effet Wither +item.ebwizardry:ring_mind_control.name=Anneau du Psychomancien +item.ebwizardry:ring_mind_control.desc=Les créatures hypnotisées ont une chance d'affecter les entités aux alentours +item.ebwizardry:ring_poison.name=Anneau du serpent +item.ebwizardry:ring_poison.desc=Tous les sorts de géomancie empoisonnent leur cible +item.ebwizardry:ring_earth_melee.name=Anneau de touché venimeux +item.ebwizardry:ring_earth_melee.desc=Frapper une entité avec une baguette de géomancie l'empoisonne +item.ebwizardry:ring_earth_biome.name=Anneau de la dryade +item.ebwizardry:ring_earth_biome.desc=Les sorts de géomancie sont 30%% plus forts dans les forêts et canopées +item.ebwizardry:ring_full_moon.name=Anneau du loup +item.ebwizardry:ring_full_moon.desc=Les sorts de géomancie ont un délai de récupération drastiquement réduit à la pleine lune +item.ebwizardry:ring_extraction.name=Anneau d'extraction +item.ebwizardry:ring_extraction.desc=Tuer avec un sort de Sorcellerie offre un bonus de mana +item.ebwizardry:ring_mana_return.name=Anneau du perfectionniste +item.ebwizardry:ring_mana_return.desc=Les flèches de force qui ne touchent pas leur cible rendent leur mana +item.ebwizardry:ring_blockwrangler.name=Anneau du jongleur +item.ebwizardry:ring_blockwrangler.desc=Les blocs jetés infligent deux fois plus de dégâts +item.ebwizardry:ring_conjurer.name=Anneau du conjurateur +item.ebwizardry:ring_conjurer.desc=Les objets conjurés durent deux fois plus longtemps +item.ebwizardry:ring_defender.name=Anneau du défenseur +item.ebwizardry:ring_defender.desc=Vos projectiles traversent les champs de force alliés +item.ebwizardry:ring_paladin.name=Anneau du paladin +item.ebwizardry:ring_paladin.desc=Quand vous vous soignez vous ou un allié, tous les alliés autour en profitent +item.ebwizardry:ring_interdiction.name=Anneau d'interdiction +item.ebwizardry:ring_interdiction.desc=Vos champs de force infligent des dégâts à ceux qui les touchent + +item.ebwizardry:amulet_arcane_defence.name=Amulette de défense arcanique +item.ebwizardry:amulet_arcane_defence.desc=Régénère lentement le mana de tous les vêtements magiques +item.ebwizardry:amulet_warding.name=Amulette de barrière +item.ebwizardry:amulet_warding.desc=Réduits les dégâts magiques reçus 10%% +item.ebwizardry:amulet_wisdom.name=Amulette du sage +item.ebwizardry:amulet_wisdom.desc=Réduit grandement les risques d'effet négatifs en utilisant un sort inconnu +item.ebwizardry:amulet_fire_protection.name=Amulette d'ignifugation +item.ebwizardry:amulet_fire_protection.desc=Réduit les dégâts de feu 30%% +item.ebwizardry:amulet_fire_cloaking.name=Amulette de manteau de feu +item.ebwizardry:amulet_fire_cloaking.desc=Réduits les dégâts reçus de 75%% si vous êtes dans un cercle de feu personnel ou allié +item.ebwizardry:amulet_ice_immunity.name=Amulette de permafrost +item.ebwizardry:amulet_ice_immunity.desc=Octroie une immunité aux morsures du froid +item.ebwizardry:amulet_ice_protection.name=Amulette gelée +item.ebwizardry:amulet_ice_protection.desc=Réduit les dégâts de glace reçus de 30%% +item.ebwizardry:amulet_potential.name=Amulette chargée +item.ebwizardry:amulet_potential.desc=Offre 15%% de chance t'électrocuter les entités qui vous attaquent en mêlée +item.ebwizardry:amulet_channeling.name=Amulette paratonnerre +item.ebwizardry:amulet_channeling.desc=Offre 30%% de chance d'annuler les dégâts de foudre reçus +item.ebwizardry:amulet_lich.name=Amulette de la liche +item.ebwizardry:amulet_lich.desc=Offre 15%% de chance d'infliger à un sbire à proximité, les dégâts qui vous étaient destinés +item.ebwizardry:amulet_wither_immunity.name=Amulette de perles nécrosées +item.ebwizardry:amulet_wither_immunity.desc=Octroie une immunité totale à l'effet Wither +item.ebwizardry:amulet_glide.name=Amulette de plume +item.ebwizardry:amulet_glide.desc=Tomber de plus de 3 blocs a 50%% de chance d'activer un vol plané +item.ebwizardry:amulet_banishing.name=Amulette du néant +item.ebwizardry:amulet_banishing.desc=Les créatures qui vous attaquent en mêlée ont 15%% de chance d'être téléportés aléatoirement à proximité +item.ebwizardry:amulet_anchoring.name=Amulette d'ancrage +item.ebwizardry:amulet_anchoring.desc=Octroie une immunité aux déplacements magiques +item.ebwizardry:amulet_recovery.name=Amulette de guérison +item.ebwizardry:amulet_recovery.desc=Quand vous tombez en dessous de 50%% de vie, le mana de votre armure sera utilisé pour vous soigner +item.ebwizardry:amulet_transience.name=Amulet éthérée +item.ebwizardry:amulet_transience.desc=Octroie 25%% de chance de vous rendre éthéré si vous êtes mortellement blessé +item.ebwizardry:amulet_resurrection.name=Amulette de l'immortel +item.ebwizardry:amulet_resurrection.desc=Vous pouvez lancer le sort de résurrection sur vous-même depuis votre tombe +item.ebwizardry:amulet_auto_shield.name=Amulette de Hyeleth +item.ebwizardry:amulet_auto_shield.desc=Octroie 25%% de chance de lancer un sort de bouclier s'il est attaché à une baguette de votre barre d'inventaire + +item.ebwizardry:charm_haggler.name=Charme du marchand +item.ebwizardry:charm_haggler.desc=Les sorciers proposeront de nouveaux échanges à chaque interaction +item.ebwizardry:charm_experience_tome.name=Tome de l'apprenti +item.ebwizardry:charm_experience_tome.desc=Augmente la vitesse de progression des baguettes 50%% +item.ebwizardry:charm_auto_smelt.name=Marque du métallurgiste +item.ebwizardry:charm_auto_smelt.desc=Le Fourneau de poche agit automatiquement s'il est attaché à une baguette de votre barre d'inventaire +item.ebwizardry:charm_lava_walking.name=Cœur de glace infernal +item.ebwizardry:charm_lava_walking.desc=Le sort Pied gelé transforme la lave en croûte d'obsidienne +item.ebwizardry:charm_storm.name=Cumulonimbus en bouteille +item.ebwizardry:charm_storm.desc=Invoquer une tempête provoquera toujours un orage +item.ebwizardry:charm_minion_health.name=Tête de zombie en obsidienne +item.ebwizardry:charm_minion_health.desc=Les créatures invoquées sont 25%% plus fortes +item.ebwizardry:charm_minion_variants.name=Talisman de transformation +item.ebwizardry:charm_minion_variants.desc=Les zombies invoqués sont momifiés; les squelettes invoqués sont des vagabonds +item.ebwizardry:charm_flight.name=Aile de scarabée d'émeraude +item.ebwizardry:charm_flight.desc=Le vol et le vol plané sont 50%% plus rapides +item.ebwizardry:charm_growth.name=Charme de la fleur de cristal +item.ebwizardry:charm_growth.desc=L'aura de croissance a 35%% de chance de faire grandir complètement les cultures +item.ebwizardry:charm_abseiling.name=Vigne enchantée +item.ebwizardry:charm_abseiling.desc=Maintenir sneak tout en tenant une liane permet de l'étendre +item.ebwizardry:charm_silk_touch.name=Orbre de pierre-de-lune +item.ebwizardry:charm_silk_touch.desc=Les blocs brisés avec le sort Minage sont conservés indemnes +item.ebwizardry:charm_stop_time.name=Montre à gousset péliculaire +item.ebwizardry:charm_stop_time.desc=Le ralentissement temporel arrête complètement le temps +item.ebwizardry:charm_light.name=La lanterne d'Elevinia +item.ebwizardry:charm_light.desc=Les sources de lumières invoquées sont éternelles et peuvent être éteintes en réactivant le sort dessus +item.ebwizardry:charm_transportation.name=Bousolle antique +item.ebwizardry:charm_transportation.desc=Permet de marquer jusqu'à 4 cercles de téléportation +item.ebwizardry:charm_feeding.name=Provisions sans fond +item.ebwizardry:charm_feeding.desc=Le sort Restauration est lancé automatiquement s'il est attaché à une baguette dans la barre d'inventaire + +item.charge_status.full=Plein +item.charge_status.almost_full=Quasiment plein +item.charge_status.mostly_full=Assez plein +item.charge_status.half_full=À moitié plein +item.charge_status.mostly_empty=Assez vide +item.charge_status.almost_empty=Quasiment vide +item.charge_status.empty=Vide + entity.ebwizardry:summonedcreature.nameplate=%1$s's %2$s entity.ebwizardry:summonedcreature.nameplate_fallback=Someone's %1$s +entity.ebwizardry:wizard.greeting_0=Bonjour, voyageur. +entity.ebwizardry:wizard.greeting_1=Salutations, voyageur. Qu'est-ce qui vous amène? +entity.ebwizardry:wizard.greeting_2=Ah, il est bon de voir quelqu'un d'autre par ici. +entity.ebwizardry:wizard.speech_0=Pourrais-je vous proposer quelques sorts? +entity.ebwizardry:wizard.speech_1=La magie est partout, si vous savez où regarder... +entity.ebwizardry:wizard.speech_2=Il reste beaucoup à découvrir sur les arcanes, voyageur. +entity.ebwizardry:wizard.speech_3=Peut-être voulez-vous partager votre savoir? +entity.ebwizardry:wizard.speech_4=Étudier la magie est la chose la plus intéressante au monde, vous ne trouvez pas? +entity.ebwizardry:wizard.farewell_0=Bonne chance dans votre quête, aventurier. +entity.ebwizardry:wizard.farewell_1=Nous nous reverrons très bientôt, mon ami. +entity.ebwizardry:wizard.farewell_2=Alors adieu, voyageur. +entity.ebwizardry:wizard.combat_0=Arrière, viles créatures! +entity.ebwizardry:wizard.combat_1=Disparaissez, peste! +entity.ebwizardry:wizard.combat_2=Les morts-vivants ne sont pas les bienvenus, zou! +entity.ebwizardry:wizard.combat_3=Recule, créature des ténèbres! +entity.ebwizardry:wizard.combat_4=C'est bien ma veine! Hors d'ici, monstres! +entity.ebwizardry:wizard.combat_5=Retournez dans vos limbes sombres, monstres! +entity.ebwizardry:wizard.player_combat_0=Tu vas le regretter, voyageur! +entity.ebwizardry:wizard.player_combat_1=Que crois-tu faire?! +entity.ebwizardry:wizard.player_combat_2=Seul un idiot mettrait un magicien en colère! +entity.ebwizardry:wizard.player_combat_3=Tu va payer pour ton arrogance, aventurier! +entity.ebwizardry:wizard.player_combat_4=Prépare toi à combattre, mécréant! +entity.ebwizardry:wizard.player_combat_5=Affrontez ma colère! + entity.ebwizardry:zombie_minion.name=Zombie +entity.ebwizardry:husk_minion.name=Momie entity.ebwizardry:skeleton_minion.name=Squelette +entity.ebwizardry:stray_minion.name=Vagabond 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:spirit_wolf.name=Loup spirituel +entity.ebwizardry:spirit_horse.name=Cheval spirituel +entity.ebwizardry:ice_giant.name=Géant de glace entity.ebwizardry:phoenix.name=Phoenix -entity.ebwizardry:wizard.name=Sorcier -entity.ebwizardry:magic_slime.name=Slime magique +entity.ebwizardry:wizard.name=Mage +entity.ebwizardry:magic_slime.name=Limon 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:storm_elemental.name=Tempête élémentaire +entity.ebwizardry:evil_wizard.name=Mage noir entity.ebwizardry:decoy.name=Leurre +entity.ebwizardry:vex_minion.name=Vex entity.ebwizardry:magic_missile.name=Magique entity.ebwizardry:arc.name=Magique @@ -196,7 +420,7 @@ 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:meteor.name=Météor entity.ebwizardry:blizzard.name=Blizzard entity.ebwizardry:bubble.name=Bulle entity.ebwizardry:tornado.name=Tornade @@ -205,22 +429,22 @@ 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:earthquake.name=Séisme entity.ebwizardry:falling_grass.name=Chute d'herbe entity.ebwizardry:hailstorm.name=Grêle -entity.ebwizardry:lightning_pulse.name=Pulsation electrique +entity.ebwizardry:lightning_pulse.name=Impulsion electrique itemGroup.ebwizardry=Wizardry itemGroup.ebwizardryspells=Spells -advancement.ebwizardry:root=Sorcier +advancement.ebwizardry:root=Sorcellerie 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:apprentice=Apprenti sorcier +advancement.ebwizardry:apprentice.desc=Utilisez un tome des arcanes 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 @@ -265,12 +489,12 @@ 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! +tile.ebwizardry:transportation_stone.invalid=Vous devez d'abord 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: +container.ebwizardry:arcane_workbench.upgrades=Améliorations appliquées: tier.basic=Novice tier.apprentice=Apprenti @@ -281,23 +505,23 @@ element.simple=Aucun element.fire=Feu element.ice=Glace element.lightning=Foudre -element.necromancy=Necromancie -element.earth=Terra +element.necromancy=Nécromancie +element.earth=Terre 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.lightning.wizard=Électromancien +element.necromancy.wizard=Nécromancien +element.earth.wizard=Géomancien element.sorcery.wizard=Sorcier element.healing.wizard=Soigneur spelltype.attack=Attaque spelltype.defence=Defense -spelltype.utility=Utilité +spelltype.utility=Utilitaire spelltype.minion=Sbire spell.disabled=%1$s a été désactivé dans la config! @@ -307,40 +531,52 @@ 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:arcane_lock=Verrou 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:blink=Transfert spell.ebwizardry:blizzard=Blizzard spell.ebwizardry:bubble=Bulle spell.ebwizardry:chain_lightning=Chaîne d'éclair +spell.ebwizardry:charge=Ruée spell.ebwizardry:clairvoyance=Clairvoyance -spell.ebwizardry:cobwebs=Toile +spell.ebwizardry:cobwebs=Toiles +spell.ebwizardry:combustion_rune=Rune de combustion spell.ebwizardry:conjure_armour=Conjuration d'armure spell.ebwizardry:conjure_bow=Conjuration d'arc +spell.ebwizardry:conjure_block=Conjuration de bloc spell.ebwizardry:conjure_pickaxe=Conjuration de pioche spell.ebwizardry:conjure_sword=Conjuration d'épée -spell.ebwizardry:cure_effects=Curation +spell.ebwizardry:containment=Restriction +spell.ebwizardry:cure_effects=Purification +spell.ebwizardry:curse_of_enfeeblement=Malédiction affaiblissante spell.ebwizardry:curse_of_soulbinding=Malédiction de liaison d'âme -spell.ebwizardry:darkness_orb=Orbe de ténèbre +spell.ebwizardry\:curse_of_undeath=Malédiction de non-vie +spell.ebwizardry:darkness_orb=Orbe des ténèbres spell.ebwizardry:darkvision=Nyctalopie spell.ebwizardry:dart=Dard -spell.ebwizardry:decay=Decomposition +spell.ebwizardry:decay=Décomposition spell.ebwizardry:decoy=Leurre -spell.ebwizardry:detonate=Detonation +spell.ebwizardry:detonate=Détonation spell.ebwizardry:diamondflesh=Peau de diamant -spell.ebwizardry:earthquake=Seisme -spell.ebwizardry:entrapment=Piège de ténèbre +spell.ebwizardry:disintegration=Désintégration +spell.ebwizardry:divination=Divination +spell.ebwizardry:dragon_fireball=Flammes de dragon +spell.ebwizardry:earthquake=Séisme +spell.ebwizardry:empowering_presence=Présence fortifiante +spell.ebwizardry:entrapment=Piège de ténèbres +spell.ebwizardry:evade=Évasion spell.ebwizardry:fireball=Boule de feu -spell.ebwizardry:firebolt=Eclair de feu +spell.ebwizardry:firebolt=Éclair de feu spell.ebwizardry:firebomb=Bombe de feu -spell.ebwizardry:fire_resistance=Resistance aux flammes +spell.ebwizardry:fire_resistance=Résistance aux flammes spell.ebwizardry:fire_sigil=Rune de feu spell.ebwizardry:fireskin=Aura de feu -spell.ebwizardry:firestorm=Tempête de feu +spell.ebwizardry:fire_breath=Souffle de feu spell.ebwizardry:flame_ray=Trait de feu -spell.ebwizardry:flaming_axe=Hache enflammé -spell.ebwizardry:flaming_weapon=Enflamment d'arme +spell.ebwizardry:flaming_axe=Hache enflammée +spell.ebwizardry:flaming_weapon=Embrasement d'arme spell.ebwizardry:flight=Vol spell.ebwizardry:font_of_mana=Fontaine de mana spell.ebwizardry:font_of_vitality=Fontaine de vitalité @@ -348,99 +584,119 @@ 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:forest_of_thorns=Forêt épineuse 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:frost_step=Pied de glace spell.ebwizardry:glide=Vol plané -spell.ebwizardry:greater_fireball=Grosse boule de feu +spell.ebwizardry:grapple=Liane +spell.ebwizardry:greater_fireball=Boule de feu majeure spell.ebwizardry:greater_heal=Soin magistral +spell.ebwizardry:greater_telekinesis=Télékinésie majeure +spell.ebwizardry:greater_ward=Protection majeure 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:homing_spark=Étincelle chasseresse +spell.ebwizardry:iceball=Boule de glace 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_shard=Éclat 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:ignite=Embraser +spell.ebwizardry:imbue_weapon=Imprégnation d'arme spell.ebwizardry:intimidate=Intimidation -spell.ebwizardry:invigorating_presence=Présance tonifiante +spell.ebwizardry:invigorating_presence=Présence 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:leap=Bond +spell.ebwizardry:levitation=Lévitation 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_bolt=Éclair spell.ebwizardry:lightning_disc=Disque de foudre spell.ebwizardry:lightning_hammer=Marteau de foudre -spell.ebwizardry:lightning_pulse=Pulsation electrique +spell.ebwizardry:lightning_pulse=Impulsion électrique 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:lightning_web=Réseau de foudre +spell.ebwizardry:magic_missile=Projectile magique +spell.ebwizardry:metamorphosis=Métamorphose +spell.ebwizardry:meteor=Météor spell.ebwizardry:mind_control=Hypnose spell.ebwizardry:mind_trick=Confusion +spell.ebwizardry:mine=Minage +spell.ebwizardry:muffle=Amortissement spell.ebwizardry:none=[Emplacement vide] spell.ebwizardry:oakflesh=Peau de chêne -spell.ebwizardry:petrify=Petrification +spell.ebwizardry:paralysis=Paralysie +spell.ebwizardry:petrify=Pétrification 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:pocket_furnace=Conjuration de fourneau +spell.ebwizardry:pocket_workbench=Conjuration d'établi spell.ebwizardry:poison=Poison spell.ebwizardry:poison_bomb=Bombe de poison -spell.ebwizardry:replenish_hunger=Restoration +spell.ebwizardry:possession=Possession +spell.ebwizardry:ray_of_purification=Rayon purificateur +spell.ebwizardry:remove_curse=Purge arcanique +spell.ebwizardry:replenish_hunger=Restauration +spell.ebwizardry:resurrection=Résurrection +spell.ebwizardry:reversal=Échange spell.ebwizardry:ring_of_fire=Anneau de feu +spell.ebwizardry:satiety=Satiété 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:shulker_bullet=Projectile de shulker +spell.ebwizardry:silverfish_swarm=Essaim de poissons d'argent spell.ebwizardry:sixth_sense=Sixième sense -spell.ebwizardry:slime=Slime +spell.ebwizardry:slime=Limon +spell.ebwizardry:slow_time=Ralentissement temporel 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:spark_bomb=Bombe électrique spell.ebwizardry:spectral_pathway=Chemin spectral +spell.ebwizardry:speed_time=Accélération temporelle 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:static_aura=Aura électrique +spell.ebwizardry:summon_blaze=Invocation de blaze +spell.ebwizardry:summon_ice_giant=Invocation de géant 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_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_snow_golem=Invocation de bonhomme 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:telekinesis=Télékinésie 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:transportation=Téléportation +spell.ebwizardry:vanishing_box=Conjuration de coffre du néant +spell.ebwizardry:vex_swarm=Essaim de vex spell.ebwizardry:wall_of_frost=Mur de glace +spell.ebwizardry:ward=Protection spell.ebwizardry:water_breathing=Respiration aquatique spell.ebwizardry:whirlwind=Bourrasque spell.ebwizardry:wither=Wither @@ -449,6 +705,7 @@ 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:arcane_lock.desc=Crée une barrière impénétrable autour du conteneur, prévenant son ouverture ou sa destruction. Le lanceur et ses alliés peuvent toujours l'ouvrir, ceci dit. 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é. @@ -456,14 +713,20 @@ spell.ebwizardry:blink.desc=Téléporte le lanceur sur une courte distance à l' 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:charge.desc=Permet au lanceur de charger dans la direction où il regarde, repoussant et infligeant des dommages à ce qui se trouve sur son chemin. 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:combustion_rune.desc=Place une rune magique, à l'endroit visé, qui explose au contact d'un ennemi. 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_block.desc=Crée un bloc spectral à l'endroit visé. Il disparaît au bout de 45 secondes, ou en lançant à nouveau le sort dessus. 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:containment.desc=Restreint la cible dans un périmètre étroit autour de sa position initiale. spell.ebwizardry:cure_effects.desc=Supprime tous les effets de potion affectant actuellement le lanceur, bons ou mauvais. +spell.ebwizardry:curse_of_enfeeblement.desc="Il fut soudainement affaiblit, comme si on arrachait une partie de son énergie vitale" - Témoignage de la seule personne connue à avoir rencontré un membre du culte des Marcheurs d'âme... et à avoir survécu. 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:curse_of_undeath.desc=Inflige la non-vie à la cible, la faisant brûler au Soleil comme un zombie. Dure jusqu'à ce que la victime ou le lanceur meure. N'a aucun effet sur les créatures mort-vivantes. 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. @@ -471,15 +734,20 @@ spell.ebwizardry:decay.desc=Crée une flaque de décomposition sur le sol qui in 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:disintegration.desc=Crée un puissant rayon embrasé sur une courte distance, faisant exploser la cible en une multitude de braises. Les créatures qui marchent sur les braises s'enflamment. +spell.ebwizardry:divination.desc=Guide le lanceur vers les ressources et minerais à proximité. La puissance augmentera les chances de trouver des ressources plus rares. +spell.ebwizardry:dragon_fireball.desc=Lance une boule de feu du néant qui propage des nuées de brume toxique. spell.ebwizardry:earthquake.desc=Un vrai maître de la magie de la terre peut déplacer des montagnes. +spell.ebwizardry:empowering_presence.desc=Améliore les sorts du lanceur et de ses alliés à proximité pendant 30 secondes. 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:evade.desc=Permet au lanceur de se déplacer rapidement de côté pour éviter une attaque. 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:fire_breath.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. @@ -490,14 +758,19 @@ spell.ebwizardry:force_arrow.desc=Tire une flèche de force dans la direction qu 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:forest_of_thorns.desc=Au milieu des bois se trouve une clairière,\nDans la clairière, un mage pratique,\nLes arts magique d'une antique ère,\nAu plus profond de la forêt d'épines. 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:frost_step.desc=Permet au lanceur, pendant 30 secondes, de geler l'eau lorsqu'il marche à proximité. +spell.ebwizardry:grapple.desc=Lance une liane magique pour se rapprocher d'un bloc ou attirer une entité. Relâchez le bouton pour arrêter le sort. 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:greater_telekinesis.desc=Permet au lanceur de saisir un bloc ou une entité pendant aussi longtemps que le bouton reste enfoncé. S'accroupir permet de lancer la cible sur une courte distance. +spell.ebwizardry:greater_ward.desc=Offre au lanceur un puissant effet de protection qui réduit grandement les dégâts reçus pendant 30 secondes. 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. @@ -505,6 +778,7 @@ 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:iceball.desc=Lance une sphère gelée dans la direction ciblée. 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. @@ -521,7 +795,7 @@ 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:life_drain.desc=Crée un flux d’énergie malsaine 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. @@ -536,8 +810,11 @@ spell.ebwizardry:metamorphosis.desc=Change la cible en une autre variante. Ne fo 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:mine.desc=Détruit le bloc visé. La puissance permet de détruire des blocs plus résistants. +spell.ebwizardry:muffle.desc=Atténue les bruits émis par le lanceur pendant 30 secondes. Tant que le sort est actif, les monstres ne peuvent vous détecter qu'en vous regardant directement. 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:paralysis.desc=Délivre un puissant choc électrique qui paralyse la cible pendant 5 secondes. Les entités paralysées ne peuvent plus bouger, mais accusent toujours les dégâts. Attention cependant, trop de dégâts fait sortir la cible de la paralysie. 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 ... @@ -545,19 +822,28 @@ spell.ebwizardry:pocket_furnace.desc=Fait cuire jusqu'à 5 objets dans l'inventa 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:possession.desc="Glisse toi dans la peau de ton ennemi." +spell.ebwizardry:ray_of_purification.desc=Émet un rayon de lumière qui aveugle et inflige des dommages à ceux qu'il touche. Les morts-vivants subissent deux fois plus de dégâts et prennent feu. +spell.ebwizardry:remove_curse.desc=Supprime toutes les malédictions infligées au lanceur. spell.ebwizardry:replenish_hunger.desc=Remonte le niveau de nourriture du lanceur de 6 points de stamina. +spell.ebwizardry:resurrection.desc=Avec un maître soigneur à vos côtés, la mort devient... Optionnelle. +spell.ebwizardry:reversal.desc=Retire un effet négatif aléatoire du lanceur pour le placer sur la cible qui en souffrira jusqu'à la fin de l'effet. La puissance augment le nombre d'effets transférés. 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:satiety.desc=Remplit 8 points de la barre de faim du lanceur. 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:shulker_bullet.desc=Lance un projectile de shulker qui poursuit une cible et les fait léviter à l'impact. 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:slow_time.desc=La chronomancie est l'art de manipuler l'écoulement du temps à sa guise. Des siècles durant, on a cru cet art perdu... Jusqu'à maintenant. 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:speed_time.desc=...jour, nuit, aube, crépuscule...\: ainsi passe le temps, nous entravant dans un cycle éternel de... 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é. @@ -568,11 +854,11 @@ spell.ebwizardry:summon_lightning_wraith.desc=Invoque un spectre de foudre pour 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_skeleton_legion.desc="Levez-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 de neige. 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_storm_elemental.desc="Élementaire 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 II 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. @@ -581,41 +867,130 @@ 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:vanishing_box.desc=Donne au lanceur accès à son coffre du néant. +spell.ebwizardry:vex_swarm.desc=Invoque un essaim de vex pour qu'il se batte pour vous. Les vex disparaissent au bout de 30 secondes ou s'ils sont tués. spell.ebwizardry:wall_of_frost.desc=L'hiver au bout des doigts. +spell.ebwizardry:ward.desc=Donne au lanceur un effet protecteur qui réduit les dégâts magiques reçus. 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.desc=Tire un rayon ténébreux 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.sun=La pluie s'estompe... 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.missing=Vos pierres de téléportation sont obstruées ou inexistantes! 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:transportation.wrongdimension=Vous n'avez aucun cercle de téléportation dans cette 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... +spell.ebwizardry:possession.insufficienthealth=Vous n'avez pas assez de vie pour posséder %1$s! +spell.ebwizardry:possession.success=Appuyez sur %s pour arrêter la possession. +spell.ebwizardry:divination.nothing=Rien ne se passe. +spell.ebwizardry:divination.weak=Vous sentez votre baguette vibrer légèrement vers %s. +spell.ebwizardry:divination.moderate=Vous sentez votre baguette vibrer sérieusement vers %s. +spell.ebwizardry:divination.strong=Votre baguette vibre énergiquement vers %s. +spell.ebwizardry:divination.very_strong=Votre baguette tend très fortement vers %s. +spell.ebwizardry:divination.down=le bas +spell.ebwizardry:divination.up=le haut +spell.ebwizardry:divination.front=l'avant +spell.ebwizardry:divination.back=l'arrière +spell.ebwizardry:divination.left=la gauche +spell.ebwizardry:divination.right=la droite +spell.ebwizardry:resurrection.resurrect_ally=%s a été ressuscité par %s +spell.ebwizardry:resurrection.resurrect_self=%s est revenu à la vie +spell.ebwizardry:resurrection.button_wait=Ressusciter (%ss) +spell.ebwizardry:resurrection.button_ready=Ressusciter + +forfeit.ebwizardry:do_nothing=Rien ne se passe. + +forfeit.ebwizardry:burn_self=Le %s s'embrase entre vos mains! +forfeit.ebwizardry:fireball=Une boule de feu apparaît devant vous! +forfeit.ebwizardry:firebomb=Une bombe de feu apparaît au dessus de vous! +forfeit.ebwizardry:explode=Une soudaine explosion vous projette du sol! +forfeit.ebwizardry:blazes=Soudainement, un blaze hostile apparaît devant vous! +forfeit.ebwizardry:burn_surroundings=La terre brûle autour de vous! +forfeit.ebwizardry:meteors=L'Armageddon est sur vous! + +forfeit.ebwizardry:freeze_self=Vous sentez un courant glacial à travers vous! +forfeit.ebwizardry:freeze_self_2=Un froid si intense émane de %s qu'il vous gèle sur place! +forfeit.ebwizardry:ice_spikes=Des pieux de glace apparaissent sous vos pieds! +forfeit.ebwizardry:blizzard=Vous êtes soudainement entouré d'un blizzard mordant! +forfeit.ebwizardry:ice_wraiths=Soudainement, des spectres gelés hostiles apparaissent devant vous! +forfeit.ebwizardry:hailstorm=Des pointes de glace acérées se matérialisent au-dessus de vous! +forfeit.ebwizardry:ice_giant=Un géant de glace hostile apparaît devant vous! + +forfeit.ebwizardry:thunder=Le parchemin émet un bruit assourdissant et vous projette au sol! +forfeit.ebwizardry:storm=Il semble qu'une tempête se prépare! +forfeit.ebwizardry:lightning_sigils=Regardez où vous marchez! +forfeit.ebwizardry:lightning=Vous êtes frappés par la foudre! +forfeit.ebwizardry:paralyse_self=Vous êtes paralysé! +forfeit.ebwizardry:lightning_wraiths=Soudainement, des spectres de foudre apparaissent devant vous! +forfeit.ebwizardry:storm_elementals=Des élémentaires de tempête apparaissent autour de vous! + +forfeit.ebwizardry:nausea=...hein? Que s'est-il passé? Où suis-je? +forfeit.ebwizardry:zombie_horde=Une horde de zombies se lèvent contre vous! +forfeit.ebwizardry:wither_self=Les ténèbres emplissent votre âme! +forfeit.ebwizardry:cripple_self=Votre %s pousse un hurlement et vous laisse de profondes marques! +forfeit.ebwizardry:shadow_wraiths=Des spectres ténébreux apparaissent autour de vous! + +forfeit.ebwizardry:snares=C'est un piège! +forfeit.ebwizardry:squid=POULPE! +forfeit.ebwizardry:uproot_plants=Les plantes aux alentours sont déracinées! +forfeit.ebwizardry:poison_self=Vous êtes empoisonné! +forfeit.ebwizardry:flood=De l'eau jaillit autour de vous! +forfeit.ebwizardry:bury_self=Le sol s'effondre sous vos pieds! + +forfeit.ebwizardry:spill_inventory=Vos objets s'éparpillent d'eux-même autour de vous! +forfeit.ebwizardry:teleport_self=Vous êtes instantanément téléporté ailleurs! +forfeit.ebwizardry:levitate_self=Vous commencez à flotter désespérément dans les airs! +forfeit.ebwizardry:vex_horde=Une horde de vex apparaît autour de vous! +forfeit.ebwizardry:black_hole=Un vortex tourbillonant apparaît devant vous! +forfeit.ebwizardry:arrow_rain=Un barrage de flèches apparaît devant vous! + +forfeit.ebwizardry:damage_self=Aïe! +forfeit.ebwizardry:spill_armour=Votre armure tombe sans explication! +forfeit.ebwizardry:hunger=Vous vous sentez soudainement affamé! +forfeit.ebwizardry:blind_self=Vous êtes aveugle! +forfeit.ebwizardry:weaken_self=Vous êtes passablement affaibli! +forfeit.ebwizardry:jam_self=Votre magie est réduite à néant! +forfeit.ebwizardry:curse_self=Vous êtes devenu un mort-vivant! + 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:static_aura=Aura électrique +potion.ebwizardry:transience=Forme éthérée +potion.ebwizardry:decay=Décomposition 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 +potion.ebwizardry:curse_of_soulbinding=Malédiction de liaison d'âme +potion.ebwizardry:paralysis=Paralysie +potion.ebwizardry:muffle=Pied léger +potion.ebwizardry:ward=Barrière +potion.ebwizardry:slow_time=Ralentissement temporel +potion.ebwizardry:empowerment=Fortification +potion.ebwizardry:curse_of_enfeeblement=Malédiction affaiblissante +potion.ebwizardry:curse_of_undeath=Mort-vie +potion.ebwizardry:containment=Restriction +potion.ebwizardry:frost_step=Pied de glace 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 +enchantment.ebwizardry:shocking_weapon=Imprégné de foudre + +enchantment.ebwizardry:magic_protection=Protection magique +enchantment.ebwizardry:frost_protection=Protection au froid +enchantment.ebwizardry:shock_protection=Protection à la foudre key.categories.ebwizardry=Wizardry @@ -625,14 +1000,19 @@ 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.usage=/%1$s [joueur | direction x y z] [durée] [modificateurs] +commands.ebwizardry:cast.success=Sort %s lancé avec succès +commands.ebwizardry:cast.success_continuous=Sort %s lancé avec succès pour %s secondes +commands.ebwizardry:cast.success_remote=Sort %s lancé avec succès en tant que %s +commands.ebwizardry:cast.success_remote_continuous=Sort %s lancé avec succès en tant que %s pour %s secondes +commands.ebwizardry:cast.success_position=Sort %s lancé avec succès à la position %s, %s, %s +commands.ebwizardry:cast.success_position_continuous=Sort %s lancé avec succès à la position %s, %s, %s pour %s secondes 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:cast.not_found=Il n'y a pas de sort avec l'ID %1$s +commands.ebwizardry:cast.invalid_direction=Direction invalide: %1$s (Les directions valides sont: up, down, north, south, east, west) +commands.ebwizardry:cast.tag_error=La lecture des données a échoué\: %s +commands.ebwizardry:cast.origin_not_specified=Vous devez spécifier un joueur ou une position pour lancer ce sort +commands.ebwizardry:cast.duration_not_specified=Vous devez spécifier une durée pour ce sort commands.ebwizardry:ally.usage=/%1$s [joueur] commands.ebwizardry:ally.addally=%1$s a été ajouté à la liste d'alliés de %2$s @@ -655,6 +1035,14 @@ commands.ebwizardry:discoverspell.removespell=Retrait de %1$s à la mémoire %2$ config.ebwizardry.title.general=Mod Options +config.ebwizardry.generic.true=Activé +config.ebwizardry.generic.false=Désactivé + +config.ebwizardry.category.spells=Configuration des sorts +config.ebwizardry.category.spells.tooltip=Sélectionner les sorts activés +config.ebwizardry.title.spells=Configuration des sorts +config.ebwizardry.subtitle.spells=Désactiver un sort de manière générale, ici, va écraser les spécification dans les fichiers JSON. + 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 diff --git a/src/main/resources/assets/ebwizardry/lang/hu_hu.lang b/src/main/resources/assets/ebwizardry/lang/hu_hu.lang new file mode 100644 index 00000000..2bd1e7cc --- /dev/null +++ b/src/main/resources/assets/ebwizardry/lang/hu_hu.lang @@ -0,0 +1,1307 @@ +#PARSE_ESCAPES +tile.ebwizardry\:arcane_workbench.name=Arkán Munkaasztal +tile.ebwizardry\:crystal_ore.name=Kristályérc +tile.ebwizardry\:petrified_stone.name=Kővé Vált +tile.ebwizardry\:ice_statue.name=Jégszobor +tile.ebwizardry\:crystal_flower.name=Kristályvirág +tile.ebwizardry\:snare.name=Csapda +tile.ebwizardry\:transportation_stone.name=Transzportáció köve +tile.ebwizardry\:transportation_stone.confirm=Mostantól ide leszel visszaküldve %1$s használatakor +tile.ebwizardry\:transportation_stone.remember=Megjegyezve a hely: %s, %s, %s, a %s dimenzióban +tile.ebwizardry\:transportation_stone.forget=Elfelejtve a hely: %s, %s, %s, a %s dimenzióban +tile.ebwizardry\:transportation_stone.invalid=Előbb egy kört kell építened 8 Transzportáció Kövéből! +tile.ebwizardry\:spectral_block.name=Spektrálblokk +tile.ebwizardry\:runestone.name=Rúnakő +tile.ebwizardry\:runestone_pedestal.name=Rúnakő Pedesztál +tile.ebwizardry\:thorns.name=Tövisek +tile.ebwizardry\:obsidian_crust.name=Obszidián Kéreg +tile.ebwizardry\:dry_frosted_ice.name=Száraz Fagyott Jég + +tile.ebwizardry\:magic_crystal_block.name=Kristályblokk +tile.ebwizardry\:fire_crystal_block.name=Tüzes Kristályblokk +tile.ebwizardry\:ice_crystal_block.name=Jeges Kristályblokk +tile.ebwizardry\:lightning_crystal_block.name=Viharos Kristályblokk +tile.ebwizardry\:necromancy_crystal_block.name=Sötét Kristályblokk +tile.ebwizardry\:earth_crystal_block.name=Viruló Kristályblokk +tile.ebwizardry\:sorcery_crystal_block.name=Misztikus Kristályblokk +tile.ebwizardry\:healing_crystal_block.name=Sugárzó Kristályblokk + +item.ebwizardry\:crystal_magic.name=Varázskristály +item.ebwizardry\:crystal_fire.name=Tüzes Kristály +item.ebwizardry\:crystal_ice.name=Jeges Kristály +item.ebwizardry\:crystal_lightning.name=Viharos Kristály +item.ebwizardry\:crystal_necromancy.name=Sötét Kristály +item.ebwizardry\:crystal_earth.name=Viruló Kristály +item.ebwizardry\:crystal_sorcery.name=Misztikus Kristály +item.ebwizardry\:crystal_healing.name=Sugárzó Kristály + +item.ebwizardry\:magic_wand.name=Varázspálca +item.ebwizardry\:apprentice_wand.name=Tanítvány Pálca +item.ebwizardry\:advanced_wand.name=Haladó Pálca +item.ebwizardry\:master_wand.name=Mester Pálca +item.ebwizardry\:spell_book.name=Varázskönyv + +item.ebwizardry\:spell_book.apply_to_wizard=%1$s %2$s varázslata lecserélve, helyette immár %3$s van + +item.ebwizardry\:arcane_tome.name=Arcanum Kötet +item.ebwizardry\:arcane_tome.desc=Bármilyen megfeledő haladással rendelkező %1$s pálcát %2$s szintre fejleszt + +item.ebwizardry\:wizard_handbook.name=A Varázsló Kézikönyve +item.ebwizardry\:wizard_handbook.desc=írta %1$s + +item.ebwizardry\:wand.generic=pálca + +item.ebwizardry\:wand.buff=+%1$s %2$s hatóerő +item.ebwizardry\:wand.spell=Kiválasztott Varázslat\: %1$s +item.ebwizardry\:wand.mana=Mana\: %1$s/%2$s +item.ebwizardry\:wand.progression=Haladás\: %1$s/%2$s + +item.ebwizardry\:wand.levelup=%1$s készen áll, hogy a %2$s szintre fejlődjön + +item.ebwizardry\:wand.addally=%1$s hozzá lett adva szövetségeseid listájához +item.ebwizardry\:wand.removeally=%1$s el lett távolítva szövetségeseid listájából + +item.ebwizardry\:novice_fire_wand.name=Parazsak Pálcája +item.ebwizardry\:novice_ice_wand.name=Fagycsípés Pálcája +item.ebwizardry\:novice_lightning_wand.name=Szikrák Pálcája +item.ebwizardry\:novice_necromancy_wand.name=Árnyékok Pálcája +item.ebwizardry\:novice_earth_wand.name=Az Erdő Pálcája +item.ebwizardry\:novice_sorcery_wand.name=Rejtélyek Pálcája +item.ebwizardry\:novice_healing_wand.name=Gyógyítás Pálcája + +item.ebwizardry\:apprentice_fire_wand.name=Pyromágustanonc Pálca +item.ebwizardry\:apprentice_ice_wand.name=Jégmágustanonc Pálca +item.ebwizardry\:apprentice_lightning_wand.name=Viharmágustanonc Pálca +item.ebwizardry\:apprentice_necromancy_wand.name=Nekromágustanonc Pálca +item.ebwizardry\:apprentice_earth_wand.name=Földmágustanonc Pálca +item.ebwizardry\:apprentice_sorcery_wand.name=Misztikus Mágustanonc Pálca +item.ebwizardry\:apprentice_healing_wand.name=Gyógyítótanonc Pálca + +item.ebwizardry\:advanced_fire_wand.name=Pyromágus Pálcája +item.ebwizardry\:advanced_ice_wand.name=Jégmágus Pálcája +item.ebwizardry\:advanced_lightning_wand.name=Viharmágus Pálcája +item.ebwizardry\:advanced_necromancy_wand.name=Nekromágus Pálcája +item.ebwizardry\:advanced_earth_wand.name=Földmágus Pálcája +item.ebwizardry\:advanced_sorcery_wand.name=Misztikus Mágus Pálcája +item.ebwizardry\:advanced_healing_wand.name=Gyógyító Pálcája + +item.ebwizardry\:master_fire_wand.name=Pyromágusmester Pálcája +item.ebwizardry\:master_ice_wand.name=Jégmágusmester Pálcája +item.ebwizardry\:master_lightning_wand.name=Viharmágusmester Pálcája +item.ebwizardry\:master_necromancy_wand.name=Nekromágusmester Pálcája +item.ebwizardry\:master_earth_wand.name=Földmágusmester Pálcája +item.ebwizardry\:master_sorcery_wand.name=Misztikus Mágusmester Pálcája +item.ebwizardry\:master_healing_wand.name=Gyógyítómester Pálcája + +item.ebwizardry\:spectral_sword.name=Spektrál Kard +item.ebwizardry\:spectral_pickaxe.name=Spektrál Csákány +item.ebwizardry\:spectral_bow.name=Spektrál Íj + +item.ebwizardry\:spectral_sword_upgraded.name=Spektrál Kard +item.ebwizardry\:spectral_pickaxe_upgraded.name=Spektrál Csákány + +item.ebwizardry\:small_mana_flask.name=Kis Manapalack +item.ebwizardry\:medium_mana_flask.name=Közepes Manapalack +item.ebwizardry\:large_mana_flask.name=Nagy Manapalack + +item.ebwizardry\:grand_crystal.name=Pompás Varázskristály +item.ebwizardry\:crystal_shard.name=Varázskristályszilánk + +item.ebwizardry\:astral_diamond.name=Asztrálgyémánt + +item.ebwizardry\:purifying_elixir.name=Tisztító Elixír +item.ebwizardry\:purifying_elixir.desc=Fogyasztáskor semlegesíti az átkokat + +item.ebwizardry\:storage_upgrade.name=Tárhely Pálcafejlesztés +item.ebwizardry\:siphon_upgrade.name=Szívó Pálcafejlesztés +item.ebwizardry\:condenser_upgrade.name=Sűrítő Pálcafejlesztés +item.ebwizardry\:range_upgrade.name=Hatótáv Pálcafejlesztés +item.ebwizardry\:duration_upgrade.name=Hatásidő Pálcafejlesztés +item.ebwizardry\:cooldown_upgrade.name=Újrahasználhatósági Idő Pálcafejlesztés +item.ebwizardry\:blast_upgrade.name=Hatáskör Pálcafejlesztés +item.ebwizardry\:attunement_upgrade.name=Ráhangolódás Pálcafejlesztés +item.ebwizardry\:melee_upgrade.name=Közelharci Pálcafejlesztés + +item.ebwizardry\:storage_upgrade.desc=A pálca manakapacitását fejleszti +item.ebwizardry\:siphon_upgrade.desc=Manát szív el a pálca a megölt mobokból +item.ebwizardry\:condenser_upgrade.desc=Lassan manát regenerál a pálca +item.ebwizardry\:range_upgrade.desc=A pálcával megidézett varázslatok hatótávját növeli +item.ebwizardry\:duration_upgrade.desc=A pálcával megidézett effektusok idejét növeli +item.ebwizardry\:cooldown_upgrade.desc=A pálcával megidézett varázslatok újrahasználhatóságáig eltelő időt csökkenti +item.ebwizardry\:blast_upgrade.desc=A pálcával megidézett varázslatok hatáskörét növeli +item.ebwizardry\:attunement_upgrade.desc=A pálcához köthető varázslatok számát növeli +item.ebwizardry\:melee_upgrade.desc=A pálca közelharci sebzését növeli + +item.ebwizardry\:flaming_axe.name=Lángoló Balta +item.ebwizardry\:frost_axe.name=Fagyott Fejsze + +item.ebwizardry\:flaming_axe_upgraded.name=Lángoló Balta +item.ebwizardry\:frost_axe_upgraded.name=Fagyott Fejsze + +item.ebwizardry\:firebomb.name=Tűzbomba +item.ebwizardry\:poison_bomb.name=Méregbomba +item.ebwizardry\:smoke_bomb.name=Füstbomba +item.ebwizardry\:spark_bomb.name=Szikrabomba + +item.ebwizardry\:blank_scroll.name=Üres Tekercs +item.ebwizardry\:scroll.generic=tekercs +item.ebwizardry\:scroll.name=%1$s Tekercse +item.ebwizardry\:scroll.undiscovered.name="%1$s" Tekercs +item.ebwizardry\:identification_scroll.name=Azonosítás Tekercse +item.ebwizardry\:identification_scroll.desc=Beazonosít egy ismeretlen könyvet vagy tekercset. +item.ebwizardry\:identification_scroll.nothing_to_identify=Nincs mit beazonosítani! + +item.ebwizardry\:armour_upgrade.name=Védelem Arkán Pecsétje +item.ebwizardry\:armour_upgrade.desc1=%1$sBármilyen varázslóruhát +item.ebwizardry\:armour_upgrade.desc2=%1$sfelfejleszt %2$slegendássá + +item.ebwizardry\:magic_silk.name=Mágikus Selyem + +item.ebwizardry\:wizard_armour.legendary=Legendás +item.ebwizardry\:wizard_armour.buff=-%1$s %2$s ár +item.ebwizardry\:wizard_armour.mana=Mana\: %1$s/%2$s + +item.ebwizardry\:wizard_hat.name=Varázslókalap +item.ebwizardry\:wizard_robe.name=Varázslóköntös +item.ebwizardry\:wizard_leggings.name=Varázslónadrág +item.ebwizardry\:wizard_boots.name=Varázslócsizma + +item.ebwizardry\:wizard_hat_fire.name=Pyromágus Kalap +item.ebwizardry\:wizard_robe_fire.name=Pyromágus Köntös +item.ebwizardry\:wizard_leggings_fire.name=Pyromágus Nadrág +item.ebwizardry\:wizard_boots_fire.name=Pyromágus Csizma + +item.ebwizardry\:wizard_hat_ice.name=Jégmágus Kalap +item.ebwizardry\:wizard_robe_ice.name=Jégmágus Köntös +item.ebwizardry\:wizard_leggings_ice.name=Jégmágus Nadrág +item.ebwizardry\:wizard_boots_ice.name=Jégmágus Csizma + +item.ebwizardry\:wizard_hat_lightning.name=Viharmágus Kalap +item.ebwizardry\:wizard_robe_lightning.name=Viharmágus Köntös +item.ebwizardry\:wizard_leggings_lightning.name=Viharmágus Nadrág +item.ebwizardry\:wizard_boots_lightning.name=Viharmágus Csizma + +item.ebwizardry\:wizard_hat_necromancy.name=Nekromágus Kalap +item.ebwizardry\:wizard_robe_necromancy.name=Nekromágus Köntös +item.ebwizardry\:wizard_leggings_necromancy.name=Nekromágus Nadrág +item.ebwizardry\:wizard_boots_necromancy.name=Nekromágus Csizma + +item.ebwizardry\:wizard_hat_earth.name=Földmágus Kalap +item.ebwizardry\:wizard_robe_earth.name=Földmágus Köntös +item.ebwizardry\:wizard_leggings_earth.name=Földmágus Nadrág +item.ebwizardry\:wizard_boots_earth.name=Földmágus Csizma + +item.ebwizardry\:wizard_hat_sorcery.name=Misztikus Mágus Kalap +item.ebwizardry\:wizard_robe_sorcery.name=Misztikus Mágus Köntös +item.ebwizardry\:wizard_leggings_sorcery.name=Misztikus Mágus Nadrág +item.ebwizardry\:wizard_boots_sorcery.name=Misztikus Mágus Csizma + +item.ebwizardry\:wizard_hat_healing.name=Gyógyító Kalap +item.ebwizardry\:wizard_robe_healing.name=Gyógyító Köntös +item.ebwizardry\:wizard_leggings_healing.name=Gyógyító Nadrág +item.ebwizardry\:wizard_boots_healing.name=Gyógyító Csizma + +item.ebwizardry\:spawn_wizard.name=Idézés: Varázsló +item.ebwizardry\:spawn_evil_wizard.name=Idézés: Gonosz Varázsló + +item.ebwizardry\:spectral_helmet.name=Spektrál Sisak +item.ebwizardry\:spectral_chestplate.name=Spektrál Mellvért +item.ebwizardry\:spectral_leggings.name=Spektrál Lábszárvédő +item.ebwizardry\:spectral_boots.name=Spektrál Csizma + +item.ebwizardry\:lightning_hammer.name=Villámpöröly + +item.ebwizardry\:ring_condensing.name=Sűrítés Gyűrűje +item.ebwizardry\:ring_condensing.desc=Minden gyorstárban lévő pálcának lassan manát regenerál +item.ebwizardry\:ring_siphoning.name=Szipóka Gyűrűje +item.ebwizardry\:ring_siphoning.desc=A kiszívással gyűjtött mana megnövelése 30%%-al +item.ebwizardry\:ring_battlemage.name=Csatamágus Gyűrűje +item.ebwizardry\:ring_battlemage.desc=Ha kardot tartasz a fő kezedben és pálcát a másikban, 10%% extra mágikus sebzést nyersz +item.ebwizardry\:ring_combustion.name=Robbanás Gyűrűje +item.ebwizardry\:ring_combustion.desc=A tűzvarázslatokkal megölt lények felrobbannak +item.ebwizardry\:ring_fire_melee.name=Tüzes Érintés Gyűrűje +item.ebwizardry\:ring_fire_melee.desc=Egy tűzpálcával megütés felgyújtja az elszenvedőt +item.ebwizardry\:ring_fire_biome.name=A Sivatagi Nap Gyűrűje +item.ebwizardry\:ring_fire_biome.desc=Tűzalapú varázslatok 30%%-al erősebbek forró biomokban +item.ebwizardry\:ring_disintegration.name=Perzselő Parázs Gyűrűje +item.ebwizardry\:ring_disintegration.desc=Minden tűzalapú támadó varázslat dezintegrálja áldozatát +item.ebwizardry\:ring_ice_melee.name=Jeges Érintés Tekercse +item.ebwizardry\:ring_ice_melee.desc=Egy jégpálcával megütés a fagycsípés effektet ruházza az elszenvedőre +item.ebwizardry\:ring_ice_biome.name=Eljegesedés Gyűrűje +item.ebwizardry\:ring_ice_biome.desc=A jégalapú varázslatok 30%%-al erősebbek havas biomokban +item.ebwizardry\:ring_arcane_frost.name=Az Arkán Fagy Gyűrűje +item.ebwizardry\:ring_arcane_frost.desc=A jégalapú varázslatokkal megölt lények jégszilánkokat szórnak minden irányba +item.ebwizardry\:ring_shattering.name=Összetörés Gyűrűje +item.ebwizardry\:ring_shattering.desc=A közelharci támadásoknak van esélye szilánkokra törni a fagycsípés effekt alatt álló lényeket +item.ebwizardry\:ring_lightning_melee.name=Láncolás Gyűrűje +item.ebwizardry\:ring_lightning_melee.desc=Egy villámpálcával egy lény megütése villámot küld egy másik közeli lényhez +item.ebwizardry\:ring_storm.name=Gyülekező Viharfelhők Gyűrűje +item.ebwizardry\:ring_storm.desc=Viharok alatt a villámvarázslatok drámaian gyakrabban használhatóak +item.ebwizardry\:ring_seeking.name=Vonzás Gyűrűje +item.ebwizardry\:ring_seeking.desc=Minden lövedékszerű varázslat célkeresővé válik +item.ebwizardry\:ring_hammer.name=Thalek a Hatalmas Gyűrűje +item.ebwizardry\:ring_hammer.desc=A Villámpöröly felvehető és eldobható +item.ebwizardry\:ring_soulbinding.name=Lélekjáró Gyűrűje +item.ebwizardry\:ring_soulbinding.desc=A nekromágiai varázslatokkal megsebzett lények lélekkötöttek lesznek hozzád +item.ebwizardry\:ring_leeching.name=Vérszívás Gyűrűje +item.ebwizardry\:ring_leeching.desc=Minden nekromágiai varázslat 30%% eséllyel elindítja az életelszívás effektet +item.ebwizardry\:ring_necromancy_melee.name=Üszkös Érintés Gyűrűje +item.ebwizardry\:ring_necromancy_melee.desc=Egy nekromágusi pálcával megütés sorvadást ad az elszenvedőnek +item.ebwizardry\:ring_mind_control.name=A Gondolatirányító Gyűrűje +item.ebwizardry\:ring_mind_control.desc=Az agymosott lényeknek van esélye, hogy más lényeket is agymosottakká tegyenek +item.ebwizardry\:ring_poison.name=Kígyózó Gyűrű +item.ebwizardry\:ring_poison.desc=Minden földvarázslat megmérgezi a célpontot +item.ebwizardry\:ring_earth_melee.name=Mérges Érintés Gyűrűje +item.ebwizardry\:ring_earth_melee.desc=Egy földpálcával megütés megmérgezi az elszenvedőjét +item.ebwizardry\:ring_earth_biome.name=Driád Gyűrűje +item.ebwizardry\:ring_earth_biome.desc=A földvarázslatok 30%%-al erősebbek erdőkben és sötéttölgy erdőkben +item.ebwizardry\:ring_full_moon.name=Az Üvöltő Farkas Gyűrűje +item.ebwizardry\:ring_full_moon.desc=Teliholdkor a földvarázslatok drámaian gyakrabban használhatóak +item.ebwizardry\:ring_extraction.name=Kivonás Gyűrűje +item.ebwizardry\:ring_extraction.desc=A misztikus varázslatokkal ölések bónusz manát adnak +item.ebwizardry\:ring_mana_return.name=A Perfekcionista Gyűrűje +item.ebwizardry\:ring_mana_return.desc=Az erőnyilak, melyek célt tévesztenek, visszatérítik a megidézésükhöz szükséges manát +item.ebwizardry\:ring_blockwrangler.name=Blokkbírkózó Gyűrűje +item.ebwizardry\:ring_blockwrangler.desc=Az eldobott blokkok kétszer annyit sebeznek +item.ebwizardry\:ring_conjurer.name=Az Idéző Gyűrűje +item.ebwizardry\:ring_conjurer.desc=A megidézett tárgyak kétszer olyan sokáig megmaradnak +item.ebwizardry\:ring_defender.name=A Védelmező Gyűrűje +item.ebwizardry\:ring_defender.desc=A hozzád vagy szövetségesedhez tartozó erőtereken áthatolnak lövedékeid +item.ebwizardry\:ring_paladin.name=A Paplovag Gyűrűje +item.ebwizardry\:ring_paladin.desc=Mikor magadat vagy szövetségesedet gyógyítod, a környező szövetségesek is visszanyernek egy kevés életerőt +item.ebwizardry\:ring_interdiction.name=Interdikció Gyűrűje +item.ebwizardry\:ring_interdiction.desc=Erőtered sebzi az azt érintő lényeket + +item.ebwizardry\:amulet_arcane_defence.name=Arkán Védelem Amuletje +item.ebwizardry\:amulet_arcane_defence.desc=Lassan manát regenerál minden viselt varázslóruhának +item.ebwizardry\:amulet_warding.name=Hárítás Amuletje +item.ebwizardry\:amulet_warding.desc=Minden beérkező mágikus sebzést 10%%-al csökkent +item.ebwizardry\:amulet_wisdom.name=Bölcsesség Amuletje +item.ebwizardry\:amulet_wisdom.desc=Nagymértékben lecsökkenti a negatív effektek esélyét ismeretlen varázslatok használatakor +item.ebwizardry\:amulet_fire_protection.name=Lángálló Amulet +item.ebwizardry\:amulet_fire_protection.desc=Csökkenti a tűz által okozott sebzést 30%%-al +item.ebwizardry\:amulet_fire_cloaking.name=Körülölelő Lángok Amuletje +item.ebwizardry\:amulet_fire_cloaking.desc=A beérkező sebzést 75%%-al csökkenti, míg egy hozzád vagy szövetségesedhez tartozó tűzgyűrűben állsz +item.ebwizardry\:amulet_ice_immunity.name=Örök Fagy Amuletje +item.ebwizardry\:amulet_ice_immunity.desc=Teljes immunitást ad a fagycsípés effekt ellen +item.ebwizardry\:amulet_ice_protection.name=Befagyott Amulet +item.ebwizardry\:amulet_ice_protection.desc=Lecsökkenti a fagy általi sebzést 30%%-al +item.ebwizardry\:amulet_potential.name=Feszültség Amuletje +item.ebwizardry\:amulet_potential.desc=15%% eséllyel villámmal sújtjta a téged megütő mobokat +item.ebwizardry\:amulet_channeling.name=Vezetés Amuletje +item.ebwizardry\:amulet_channeling.desc=30%% eséllyel semlegesíti a beérkező sokksebzést +item.ebwizardry\:amulet_lich.name=A Lich amuletje +item.ebwizardry\:amulet_lich.desc=A beérkező sebzést 15%% eséllyel átirányítja viselője egy szolgájára +item.ebwizardry\:amulet_wither_immunity.name=Sorvasztógyöngy Amulet +item.ebwizardry\:amulet_wither_immunity.desc=Teljes immunitást nyújt sorvasztó effektek ellen +item.ebwizardry\:amulet_glide.name=Széltollú Amulet +item.ebwizardry\:amulet_glide.desc=A 3 blokknál többet zuhanás 50%%-os eséllyel aktiválja a siklás effektet +item.ebwizardry\:amulet_banishing.name=Végkötött Amulet +item.ebwizardry\:amulet_banishing.desc=A téged megütő lények 15%% eséllyel egy random közeli helyre teleportálnak +item.ebwizardry\:amulet_anchoring.name=Horgonyzás Amuletje +item.ebwizardry\:amulet_anchoring.desc=Immunitást nyújt a mágiával mozgatás ellen +item.ebwizardry\:amulet_recovery.name=Felépülés Amuletje +item.ebwizardry\:amulet_recovery.desc=Ha kevesebb mint 50%%-on van az életerő, a páncélodból származó manával lassan gyógyulni kezdesz +item.ebwizardry\:amulet_transience.name=Mulandóság Amuletje +item.ebwizardry\:amulet_transience.desc=25%%-os eséllyel aktiválja az alaktalanság effektet, ha kritikusan megsebeznek +item.ebwizardry\:amulet_resurrection.name=A Halhatatlan Amuletje +item.ebwizardry\:amulet_resurrection.desc=Képessé tesz a feltámasztás varázslat használatára önmagadon a síron túlról +item.ebwizardry\:amulet_auto_shield.name=Hyeleth Amuletje +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=Az Alkudozó Pecsétje +item.ebwizardry\:charm_haggler.desc=A varázslók garantáltan új lehetőséget ajánlanak fel minden alkalommal, mikor kereskedsz velük +item.ebwizardry\:charm_experience_tome.name=A Szorgalmas Kódexe +item.ebwizardry\:charm_experience_tome.desc=50%%-al megnöveli a pálcák fejlődési sebességét +item.ebwizardry\:charm_auto_smelt.name=A Fémműves Jele +item.ebwizardry\:charm_auto_smelt.desc=A zsebkemence automatikusan aktiválódik, ha egy a gyorstáradban lévő pálcához van kötve +item.ebwizardry\:charm_lava_walking.name=Alvilági Jégmag +item.ebwizardry\:charm_lava_walking.desc=A fagylépés lehűti a lávát, egy obszidián kérget alkotva +item.ebwizardry\:charm_storm.name=Palackozott Viharfelhő +item.ebwizardry\:charm_storm.desc=Az időjárás invokációja garantáltan vihart idéz meg +item.ebwizardry\:charm_minion_health.name=Obszidián Zombifej +item.ebwizardry\:charm_minion_health.desc=A megidézett lények 25%%-al erősebbek +item.ebwizardry\:charm_minion_variants.name=Transzformáció Talizmánja +item.ebwizardry\:charm_minion_variants.desc=A megidézett zombik kiszáradtak lesznek, a csontvázak pedig kóborlók +item.ebwizardry\:charm_flight.name=Smaragd Bogárszárny +item.ebwizardry\:charm_flight.desc=A Repülés és Siklás 50%%-al gyorsabb +item.ebwizardry\:charm_growth.name=Kristályvirág Ékesség +item.ebwizardry\:charm_growth.desc=A növesztő aura 35%%-os eséllyel azonnal arathatóra érleli a termést +item.ebwizardry\:charm_abseiling.name=Megbűvölt Kötél +item.ebwizardry\:charm_abseiling.desc=A guggolás segítségével csáklyázás közben lassan ereszkedni lehet +item.ebwizardry\:charm_silk_touch.name=Holdkő Gömb +item.ebwizardry\:charm_silk_touch.desc=A bányászás varázslattal kiütött blokkok mindig önmagukat dobják +item.ebwizardry\:charm_stop_time.name=Különös Zsebóra +item.ebwizardry\:charm_stop_time.desc=Az idő lassítása teljesen megállítja az időt +item.ebwizardry\:charm_light.name=Elevinia Örökkönégő Lámpása +item.ebwizardry\:charm_light.desc=A megidézett fényforrások örökké tartanak, és egy a pálcával való jobb-kattintással lehet őket eltüntetni +item.ebwizardry\:charm_transportation.name=Ősi Iránytű +item.ebwizardry\:charm_transportation.desc=Egyszerre akár négy kőkörre is emlékezni lehet, és transzportációkor ezek közül lehet választani +item.ebwizardry\:charm_feeding.name=Feneketlen Tartalékok +item.ebwizardry\:charm_feeding.desc=Az éhség feltöltése automatikusan aktiválódik, ha egy a gyorstáradban lévő pálcához van kötve + +item.charge_status.full=Tele +item.charge_status.almost_full=Majdnem tele +item.charge_status.mostly_full=Többnyire tele +item.charge_status.half_full=Félig tele +item.charge_status.mostly_empty=Többnyire üres +item.charge_status.almost_empty=Majdnem üres +item.charge_status.empty=Üres + +entity.ebwizardry\:summonedcreature.nameplate=%1$st szolgáló %2$s +entity.ebwizardry\:summonedcreature.nameplate_fallback=Ismeretlent szolgáló %1$s + +entity.ebwizardry\:wizard.greeting_0=Szép napot, vándor. +entity.ebwizardry\:wizard.greeting_1=Üdvözlégy, utazó. Mi szél hozott? +entity.ebwizardry\:wizard.greeting_2=Ah, öröm végre másokkal találkozni. +entity.ebwizardry\:wizard.speech_0=Érdekel valamely varázsigém, esetleg? +entity.ebwizardry\:wizard.speech_1=A mágia mindenhol ott van, ha tudod hol keresd. +entity.ebwizardry\:wizard.speech_2=Még sokat kell tanulnod az arkán művészetekről, kalandor. +entity.ebwizardry\:wizard.speech_3=Tán te magad is tanultál valamit, mit meg kívánsz osztani? +entity.ebwizardry\:wizard.speech_4=Az arkánum tanulmányozása igazán lenyűgöző, nem igaz? +entity.ebwizardry\:wizard.farewell_0=Sok sikert küldetésedben, kalandor. +entity.ebwizardry\:wizard.farewell_1=Remélem hamarosan újra találkozunk, barátom. +entity.ebwizardry\:wizard.farewell_2=Viszlát hát, utazó. +entity.ebwizardry\:wizard.combat_0=Eredjetek innen, mocskos lények! +entity.ebwizardry\:wizard.combat_1=Hagyjatok magamra, férgek! +entity.ebwizardry\:wizard.combat_2=Az élőholtakat nem látjuk itt szívesen, hess! +entity.ebwizardry\:wizard.combat_3=El veletek, sötétség szolgái! +entity.ebwizardry\:wizard.combat_4=Ez minden mire szükségem van! Tűnés innen, szörnyetegek! +entity.ebwizardry\:wizard.combat_5=Térjetek vissza barlangjaitokba, ahonnan jöttetek, gonosz lények! +entity.ebwizardry\:wizard.player_combat_0=Még megbánod ezt, utazó! +entity.ebwizardry\:wizard.player_combat_1=Mégis mi a fenét gondolsz?! +entity.ebwizardry\:wizard.player_combat_2=Csak egy bolond merészel magára haragítani egy varázslót! +entity.ebwizardry\:wizard.player_combat_3=Megfizetsz óvatlanságodért, kalandor! +entity.ebwizardry\:wizard.player_combat_4=Készülj fel védelmedre, gonosztevő! +entity.ebwizardry\:wizard.player_combat_5=Készülj fel s érezd haragom! + +entity.ebwizardry\:zombie_minion.name=Zombi +entity.ebwizardry\:husk_minion.name=Kiszáradt Zombi +entity.ebwizardry\:skeleton_minion.name=Csontváz +entity.ebwizardry\:stray_minion.name=Kóborló +entity.ebwizardry\:spider_minion.name=Pók +entity.ebwizardry\:blaze_minion.name=Őrláng +entity.ebwizardry\:wither_skeleton_minion.name=Sorvasztó Csontváz +entity.ebwizardry\:ice_wraith.name=Jéglidérc +entity.ebwizardry\:lightning_wraith.name=Villámlidérc +entity.ebwizardry\:shadow_wraith.name=Árnylidérc +entity.ebwizardry\:spirit_wolf.name=Lélekfarkas +entity.ebwizardry\:spirit_horse.name=Lélekparipa +entity.ebwizardry\:ice_giant.name=Jégóriás +entity.ebwizardry\:phoenix.name=Főnix +entity.ebwizardry\:wizard.name=Varázsló +entity.ebwizardry\:magic_slime.name=Mágikus Nyálka +entity.ebwizardry\:silverfish_minion.name=Ezüstmoly +entity.ebwizardry\:storm_elemental.name=Vihar Elementál +entity.ebwizardry\:evil_wizard.name=Varázsló +entity.ebwizardry\:decoy.name=Eltérítés +entity.ebwizardry\:vex_minion.name=Bosszancs + +entity.ebwizardry\:magic_missile.name=Varázslat +entity.ebwizardry\:arc.name=Varázslat +entity.ebwizardry\:spark_bomb.name=Varázslat +entity.ebwizardry\:ice_shard.name=Varázslat +entity.ebwizardry\:firebomb.name=Varázslat +entity.ebwizardry\:poison_bomb.name=Varázslat +entity.ebwizardry\:force_orb.name=Varázslat +entity.ebwizardry\:spark.name=Varázslat +entity.ebwizardry\:darkness_orb.name=Varázslat +entity.ebwizardry\:fire_sigil.name=Varázslat +entity.ebwizardry\:frost_sigil.name=Varázslat +entity.ebwizardry\:lightning_sigil.name=Varázslat +entity.ebwizardry\:lightning_arrow.name=Varázslat +entity.ebwizardry\:firebolt.name=Varázslat +entity.ebwizardry\:ice_charge.name=Varázslat +entity.ebwizardry\:force_arrow.name=Varázslat +entity.ebwizardry\:dart.name=Varázslat +entity.ebwizardry\:lightning_disc.name=Varázslat +entity.ebwizardry\:thunderbolt.name=Varázslat +entity.ebwizardry\:decay.name=Varázslat +entity.ebwizardry\:ice_lance.name=Varázslat +entity.ebwizardry\:smoke_bomb.name=Varázslat +entity.ebwizardry\:ice_spike.name=Varázslat +entity.ebwizardry\:combustion_rune.name=Varázslat + +entity.ebwizardry\:black_hole.name=Feketelyuk +entity.ebwizardry\:shield.name=Pajzs +entity.ebwizardry\:meteor.name=Meteor +entity.ebwizardry\:blizzard.name=Hóvihar +entity.ebwizardry\:bubble.name=Buborék +entity.ebwizardry\:tornado.name=Tornádó +entity.ebwizardry\:lightning_hammer.name=Villámpöröly +entity.ebwizardry\:arrow_rain.name=Nyílzápor +entity.ebwizardry\:healing_aura.name=Gyógyító Aure +entity.ebwizardry\:forcefield.name=Erőtér +entity.ebwizardry\:ring_of_fire.name=Tűzgyűrű +entity.ebwizardry\:earthquake.name=Földrengés +entity.ebwizardry\:hailstorm.name=Jégzápor +entity.ebwizardry\:lightning_pulse.name=Villámdobbantás + +itemGroup.ebwizardry=Wizardry +itemGroup.ebwizardryspells=Varázslatok +itemGroup.ebwizardrygear=Varázslófelszerelés + +advancement.ebwizardry\:root=Varázslás +advancement.ebwizardry\:root.desc=Egy varázsló útja az arkán megismerésében +advancement.ebwizardry\:crystal=Egy Érdekes kristály... +advancement.ebwizardry\:crystal.desc=Bányássz egy kevés varázskristályt +advancement.ebwizardry\:arcane_initiate=Arkán Beavatás +advancement.ebwizardry\:arcane_initiate.desc=Alkoss egy varázspálcát egy aranyröggel, bottal és varázskristállyal + +advancement.ebwizardry\:apprentice=A Varázsló Tanítványa +advancement.ebwizardry\:apprentice.desc=Használj egy Arcanum kötetet pálcád fejlesztésére +advancement.ebwizardry\:special_upgrade=Arkán Bütykölgetés +advancement.ebwizardry\:special_upgrade.desc=Rakj egy különleges fejlesztést pálcádra +advancement.ebwizardry\:advanced=Gyorsan Fejlődés +advancement.ebwizardry\:advanced.desc=Fejleszd pálcád Haladó szintre +advancement.ebwizardry\:master=Arkán Mester +advancement.ebwizardry\:master.desc=Tégy szert egy mesterszintű pálcára +advancement.ebwizardry\:max_out_wand=Teljesen Felszerelt +advancement.ebwizardry\:max_out_wand.desc=Szerelj fel egy mesteri pálcát a maximális számú fejlesztéssel +advancement.ebwizardry\:all_spells=Minden Mesterség Mágusa +advancement.ebwizardry\:all_spells.desc=Idézz meg minden a játékban meglelhető varázslatot + +advancement.ebwizardry\:wizard_tower=Ki Él Itt? +advancement.ebwizardry\:wizard_tower.desc=Látogass meg egy varázslótornyot +advancement.ebwizardry\:wizard_trade=Mágikus Üzletelés +advancement.ebwizardry\:wizard_trade.desc=Végy egy tárgyat egy varázslótól +advancement.ebwizardry\:anger_wizard=Meg Fogod Bánni +advancement.ebwizardry\:anger_wizard.desc=Haragíts meg egy varázslót +advancement.ebwizardry\:defeat_evil_wizard=Igazságosság +advancement.ebwizardry\:defeat_evil_wizard.desc=Győzz le egy gonosz varázslót +advancement.ebwizardry\:buy_master_spell=A Tudás Hatalom +advancement.ebwizardry\:buy_master_spell.desc=Végy egy mesterszintű varázslatot egy varázslótól + +advancement.ebwizardry\:discover_spell=Próba, Szerencse +advancement.ebwizardry\:discover_spell.desc=Azonosíts egy ismeretlen varázslatot megidézésével, lesve mi történik +advancement.ebwizardry\:identify_spell=Arkán Felbecsülés +advancement.ebwizardry\:identify_spell.desc=Találj egy megbízhatóbb módszert a varázsigék azonosítására +advancement.ebwizardry\:spell_failure=Ez Visszafele Sült +advancement.ebwizardry\:spell_failure.desc=Találd ki a nehezebbik úton, hogy mehetnek félre a varázslatok +advancement.ebwizardry\:discover_master_spell=A Veszéllyel Táncolni +advancement.ebwizardry\:discover_master_spell.desc=Sikeresen azonosíts egy mesterszintű varázslatot próbálkozással + +advancement.ebwizardry\:visit_shrine=Elzárva +advancement.ebwizardry\:visit_shrine.desc=Lépj be egy szentélybe és ébreszd fel ősi mágiáját +advancement.ebwizardry\:artefact=Egy Elfeledett Ereklye +advancement.ebwizardry\:artefact.desc=Emeld fel a szentély védelmező bűbájait és tégy szert az ottrejlő kincsekre +advancement.ebwizardry\:all_artefacts=Egy Valóságos Múzeum +advancement.ebwizardry\:all_artefacts.desc=Gyűjtsd össze az összes gyűrűt, amuletet és éket + +advancement.ebwizardry\:armour_set=Most Már Rendes Varázsló Vagy +advancement.ebwizardry\:armour_set.desc=Alkoss és végy fel egy teljes szett varázslóruhát +advancement.ebwizardry\:legendary=Legendás +advancement.ebwizardry\:legendary.desc=Tégy szert egy darab legendás varázslóruhára + +advancement.ebwizardry\:enchant_scroll=Tekerj Fel! +advancement.ebwizardry\:enchant_scroll.desc=Barkácsolj egy üres tekercset és bájold meg egy varázsigével az arkán munkaasztalban + +handbook.toast.title=Új Kézikönyvrészlet Feloldva! + +gui.ebwizardry\:spell_book.tier=Szint: %s +gui.ebwizardry\:spell_book.element=Elem: %s +gui.ebwizardry\:spell_book.element_undiscovered=Elem: ? +gui.ebwizardry\:spell_book.mana_cost=Mana Ár: %s +gui.ebwizardry\:spell_book.mana_cost_continuous=Mana Ár: %s/másodperc +gui.ebwizardry\:spell_book.mana_cost_undiscovered=Mana Ár: ? + +container.ebwizardry\:arcane_workbench=Arkán Munkaasztal +container.ebwizardry\:arcane_workbench.apply=Alkalmazás +container.ebwizardry\:arcane_workbench.mana=Mana\: +container.ebwizardry\:arcane_workbench.upgrades=Felszerelt Fejlesztések\: + +tier.novice=Kezdő +tier.apprentice=Tanonc +tier.advanced=Haladó +tier.master=Mester + +element.magic=Nincs +element.fire=Tűz +element.ice=Jég +element.lightning=Villám +element.necromancy=Nekromancia +element.earth=Föld +element.sorcery=Misztika +element.healing=Gyógyítás + +element.magic.wizard=Varázsló +element.fire.wizard=Pyromágus +element.ice.wizard=Jégmágus +element.lightning.wizard=Viharmágus +element.necromancy.wizard=Nekromágus +element.earth.wizard=Földmágus +element.sorcery.wizard=Miszikus Mágus +element.healing.wizard=Gyógyító + +spelltype.attack=Támadó +spelltype.defence=Védekező +spelltype.utility=Kisegítő +spelltype.minion=Szolga +spelltype.buff=Erősítő +spelltype.construct=Alkotó +spelltype.projectile=Lövedék +spelltype.alteration=Módosító + +spell.disabled=%1$s le lett tiltva a konfigurációban +spell.resist=%1$s ellenállt a %2$s varázslatnak +spell.discover=Felfedezted a %1$s varázslatot! + +spell.ebwizardry\:agility=Fürgeség +spell.ebwizardry\:arc=Ív +spell.ebwizardry\:arcane_jammer=Arkán Zavarás +spell.ebwizardry\:arcane_lock=Arkán Zár +spell.ebwizardry\:arrow_rain=Nyílzápor +spell.ebwizardry\:banish=Száműzetés +spell.ebwizardry\:black_hole=Feketelyuk +spell.ebwizardry\:blink=Pislantás +spell.ebwizardry\:blizzard=Hóvihar +spell.ebwizardry\:bubble=Buborék +spell.ebwizardry\:chain_lightning=Villámlánc +spell.ebwizardry\:charge=Töltés +spell.ebwizardry\:clairvoyance=Útmutatás +spell.ebwizardry\:cobwebs=Pókhálók +spell.ebwizardry\:combustion_rune=Robbanó Rúna +spell.ebwizardry\:conjure_armour=Páncél Idézése +spell.ebwizardry\:conjure_block=Blokk Idézése +spell.ebwizardry\:conjure_bow=Íj Idézése +spell.ebwizardry\:conjure_pickaxe=Csákány Idézése +spell.ebwizardry\:conjure_sword=Kard Idézése +spell.ebwizardry\:containment=Bezárás +spell.ebwizardry\:cure_effects=Effektek Tisztítása +spell.ebwizardry\:curse_of_enfeeblement=Elgyengülés Átka +spell.ebwizardry\:curse_of_soulbinding=Lélekkötés Átka +spell.ebwizardry\:curse_of_undeath=Élőhalál Átka +spell.ebwizardry\:darkness_orb=Sötétséggömb +spell.ebwizardry\:darkvision=Sötétlátás +spell.ebwizardry\:dart=Tű +spell.ebwizardry\:decay=Rothadás +spell.ebwizardry\:decoy=Elterelés +spell.ebwizardry\:detonate=Robbantás +spell.ebwizardry\:diamondflesh=Gyémánthús +spell.ebwizardry\:disintegration=Dezintegráció +spell.ebwizardry\:divination=Jövendölés +spell.ebwizardry\:dragon_fireball=Sárkány Tűzgolyó +spell.ebwizardry\:earthquake=Földrengés +spell.ebwizardry\:empowering_presence=Megerősítő Jelenlét +spell.ebwizardry\:entrapment=Csapdába Ejtés +spell.ebwizardry\:evade=Kitérés +spell.ebwizardry\:fireball=Tűzgolyó +spell.ebwizardry\:firebolt=Lángtű +spell.ebwizardry\:firebomb=Tűzbomba +spell.ebwizardry\:fire_resistance=Tűzállóság +spell.ebwizardry\:fire_sigil=Lángpecsét +spell.ebwizardry\:fireskin=Tűzbőr +spell.ebwizardry\:fire_breath=Tüzes Lehellet +spell.ebwizardry\:flame_ray=Lángsugár +spell.ebwizardry\:flaming_axe=Lángoló Balta +spell.ebwizardry\:flaming_weapon=Lángoló Fegyver +spell.ebwizardry\:flight=Repülés +spell.ebwizardry\:font_of_mana=Mana Forrása +spell.ebwizardry\:font_of_vitality=Élet Forrása +spell.ebwizardry\:force_arrow=Erőnyíl +spell.ebwizardry\:forcefield=Erőtér +spell.ebwizardry\:force_orb=Erőgömb +spell.ebwizardry\:forests_curse=Az Erdő Átka +spell.ebwizardry\:forest_of_thorns=Tüskék Erdeje +spell.ebwizardry\:freeze=Fagyasztás +spell.ebwizardry\:freezing_weapon=Fagyos Fegyver +spell.ebwizardry\:frost_axe=Fagyott Fejsze +spell.ebwizardry\:frost_ray=Fagysugár +spell.ebwizardry\:frost_sigil=Fagypecsét +spell.ebwizardry\:frost_step=Fagylépés +spell.ebwizardry\:glide=Siklás +spell.ebwizardry\:grapple=Csáklyázás +spell.ebwizardry\:greater_fireball=Nagyobb Tűzgolyó +spell.ebwizardry\:greater_heal=Nagyobb Gyógyítás +spell.ebwizardry\:greater_telekinesis=Nagyobb Telekinézis +spell.ebwizardry\:greater_ward=Nagyobb Hárítás +spell.ebwizardry\:group_heal=Csoportos Gyógyítás +spell.ebwizardry\:growth_aura=Növesztő Aura +spell.ebwizardry\:hailstorm=Jégzápor +spell.ebwizardry\:heal=Gyógyítás +spell.ebwizardry\:heal_ally=Szövetséges Gyógyítása +spell.ebwizardry\:healing_aura=Gyógyító Aura +spell.ebwizardry\:homing_spark=Nyomkövető Szikra +spell.ebwizardry\:iceball=Jéggömb +spell.ebwizardry\:ice_age=Jégkorszak +spell.ebwizardry\:ice_charge=Jégtöltet +spell.ebwizardry\:ice_lance=Jégdárda +spell.ebwizardry\:ice_shard=Jégszilánk +spell.ebwizardry\:ice_shroud=Jéglepel +spell.ebwizardry\:ice_spikes=Jégtüskék +spell.ebwizardry\:ice_statue=Jégszobor +spell.ebwizardry\:ignite=Gyújtás +spell.ebwizardry\:imbue_weapon=Átitatott Fegyver +spell.ebwizardry\:intimidate=Megfélemlítés +spell.ebwizardry\:invigorating_presence=Élénkítő Jelenlét +spell.ebwizardry\:invisibility=Láthatatlanság +spell.ebwizardry\:invoke_weather=Időjárás Invokációja +spell.ebwizardry\:ironflesh=Vashús +spell.ebwizardry\:leap=Ugrás +spell.ebwizardry\:levitation=Levitáció +spell.ebwizardry\:life_drain=Életleszívás +spell.ebwizardry\:light=Fény +spell.ebwizardry\:lightning_arrow=Villámnyíl +spell.ebwizardry\:lightning_bolt=Villámtű +spell.ebwizardry\:lightning_disc=Villámkorong +spell.ebwizardry\:lightning_hammer=Villámpöröly +spell.ebwizardry\:lightning_pulse=Villámdobbantás +spell.ebwizardry\:lightning_ray=Villámsugár +spell.ebwizardry\:lightning_sigil=Villámpecsét +spell.ebwizardry\:lightning_web=Villámháló +spell.ebwizardry\:magic_missile=Mágikus Lövedék +spell.ebwizardry\:metamorphosis=Metamorfózis +spell.ebwizardry\:meteor=Meteor +spell.ebwizardry\:mind_control=Elmeirányítás +spell.ebwizardry\:mind_trick=Elmetrükk +spell.ebwizardry\:mine=Bányászás +spell.ebwizardry\:muffle=Némítás +spell.ebwizardry\:none=[Üres Hely] +spell.ebwizardry\:oakflesh=Tölgyhús +spell.ebwizardry\:paralysis=Bénítás +spell.ebwizardry\:petrify=Kővéváltotatás +spell.ebwizardry\:phase_step=Fázislépés +spell.ebwizardry\:plague_of_darkness=Sötétség Csapása +spell.ebwizardry\:pocket_furnace=Zsebkemence +spell.ebwizardry\:pocket_workbench=Zsebbarkácsasztal +spell.ebwizardry\:poison=Méreg +spell.ebwizardry\:poison_bomb=Méregbomba +spell.ebwizardry\:possession=Megszállás +spell.ebwizardry\:ray_of_purification=Tisztító Sugár +spell.ebwizardry\:remove_curse=Átok Feloldása +spell.ebwizardry\:replenish_hunger=Éhség Feltöltése +spell.ebwizardry\:resurrection=Feltámasztás +spell.ebwizardry\:reversal=Visszafordítás +spell.ebwizardry\:ring_of_fire=Tűzgyűrű +spell.ebwizardry\:satiety=Jóllakottság +spell.ebwizardry\:shadow_ward=Árnyhárítás +spell.ebwizardry\:shield=Pajzs +spell.ebwizardry\:shockwave=Sokkhullám +spell.ebwizardry\:shulker_bullet=Shulkergolyó +spell.ebwizardry\:silverfish_swarm=Ezüstmolyraj +spell.ebwizardry\:sixth_sense=Hatodik Érzék +spell.ebwizardry\:slime=Nyálka +spell.ebwizardry\:slow_time=Időlassítás +spell.ebwizardry\:smoke_bomb=Füstbomba +spell.ebwizardry\:snare=Csapda +spell.ebwizardry\:snowball=Hógolyó +spell.ebwizardry\:spark_bomb=Szikrabomba +spell.ebwizardry\:spectral_pathway=Spektrálösvény +spell.ebwizardry\:speed_time=Időgyorsítás +spell.ebwizardry\:spider_swarm=Pókraj +spell.ebwizardry\:static_aura=Statikus Aura +spell.ebwizardry\:summon_blaze=Őrlány Idézése +spell.ebwizardry\:summon_ice_giant=Jégóriás Idézése +spell.ebwizardry\:summon_ice_wraith=Jéglidérc Idézése +spell.ebwizardry\:summon_iron_golem=Vasgólem Idézése +spell.ebwizardry\:summon_lightning_wraith=Villámlidérc Idézése +spell.ebwizardry\:summon_phoenix=Főnix Idézése +spell.ebwizardry\:summon_shadow_wraith=Árnylidérc Idézése +spell.ebwizardry\:summon_skeleton=Csontváz Idézése +spell.ebwizardry\:summon_skeleton_legion=Csontvázlégió Idézése +spell.ebwizardry\:summon_snow_golem=Hógólem Idézése +spell.ebwizardry\:summon_spirit_horse=Lélekparipa Idézése +spell.ebwizardry\:summon_spirit_wolf=Lélekfarkas Idézése +spell.ebwizardry\:summon_storm_elemental=Vihar Elementál Idézése +spell.ebwizardry\:summon_wither_skeleton=Sorvasztócsontváz Idézése +spell.ebwizardry\:summon_zombie=Zombi Idézése +spell.ebwizardry\:telekinesis=Telekinézis +spell.ebwizardry\:thunderbolt=Villámcsapás +spell.ebwizardry\:thunderstorm=Vihar +spell.ebwizardry\:tornado=Tornádó +spell.ebwizardry\:transience=Alaktalanság +spell.ebwizardry\:transportation=Transzportáció +spell.ebwizardry\:vanishing_box=Eltűnő Doboz +spell.ebwizardry\:vex_swarm=Bosszancsraj +spell.ebwizardry\:wall_of_frost=Fagyfal +spell.ebwizardry\:ward=Hárítás +spell.ebwizardry\:water_breathing=Vízlégzés +spell.ebwizardry\:whirlwind=Fergeteg +spell.ebwizardry\:wither=Sorvasztás +spell.ebwizardry\:wither_skull=Sorvasztókoponya + +spell.ebwizardry\:agility.desc=30 másodpercig gyorsabb mozgást és magasabb ugrást tesz lehetővé idézőjének. +spell.ebwizardry\:arc.desc=Egy elektromos szikrát lő a célpont felé. +spell.ebwizardry\:arcane_jammer.desc=Lehetetlenné teszi a célpontnak a mágia használatát 15 másodpercig. +spell.ebwizardry\:arcane_lock.desc=Egy áthatolhatatlan akadályt alkot egy tartály körül, megvédve azt a kiütéstől és kinyitástól. Az idéző és szövetségesei továbbra is hozzáférnek tartalmához. +spell.ebwizardry\:arrow_rain.desc="Íjászok, tűz!" +spell.ebwizardry\:banish.desc=Akarata ellenére elteleportálja a célpontot egy közeli helyre, adott sugaron belül. +spell.ebwizardry\:black_hole.desc=Tépd szét a valóságot. +spell.ebwizardry\:blink.desc=Rövid hatótávú teleportáció. +spell.ebwizardry\:blizzard.desc=Örvénylő jeges szelet idéz meg, mely lelassít és folyamatosan sebez mindent, mit foglyul ejt. Az idézője immunis a sebzésre de ő is lelassul. +spell.ebwizardry\:bubble.desc=Buborékok sugarát lövelli ki, mely ha eltalál valamit, az tehetetlenül lebegni kezd. A célpont leesik, ha megsebződik, vagy adott idő eltelt. +spell.ebwizardry\:chain_lightning.desc=Elektromos szikrát lő a célpont felé, mely arról maximum kétszer továbbugorhat több másik ellenfélre. +spell.ebwizardry\:charge.desc=Rohamozd meg ellenfeleid, hátralökve és megsebezve mindent mi utadba kerül. +spell.ebwizardry\:clairvoyance.desc=Felfedi az utat egy megjegyzett helyhez. A varázslatot kiválasztva guggolva kattints egy blokkra a hely megjegyzéséhez, majd a varázslat rendes használatával fedd fel az odavezető utat. Az út jelei 90 másodperc után elhalványodnak. +spell.ebwizardry\:cobwebs.desc=Pókhálókat idéz meg ahova mutatsz, melyek bármely közéjük keveredett lény mozgását nagyban akadályoztatják. A hálók 20 másodperc után eltűnnek, hacsak nem ütik ki őket hamarabb. +spell.ebwizardry\:combustion_rune.desc=Egy mágikus aknát tesz le oda, ahova mutatsz. Konzervatív, ha rálépnek, felrobban. +spell.ebwizardry\:conjure_armour.desc=Spektrál páncélzatot idéz meg a használója körül, mely vas szetthez hasonlatos védelmet nyújt. A páncél 60 másodpercig tart ki, és az idézőnek üres páncélhelyekkel kell bírnia. +spell.ebwizardry\:conjure_block.desc=Spektrálblokkot idéz meg előtted. A blokk eltűnik 45 másodperc után, vagy ha mégegyszer használod a varázslatot. +spell.ebwizardry\:conjure_bow.desc=Spektrálíjat idéz meg végtelen nyílvesszővel, 30 másodpercig tart ki. +spell.ebwizardry\:conjure_pickaxe.desc=Vascsákányhoz hasonlatos erejű spektrálcsákányt idéz meg, mely 30 másodperc után eltűnik. +spell.ebwizardry\:conjure_sword.desc=Vaskardhoz hasonlatos erejű spektrálkardot idéz meg, mely 30 másodperc után eltűnik. +spell.ebwizardry\:containment.desc=Foglyul ejti a célpontot pillanatnyi pozíciója körül egy szűk területen, 20 másodpercig. +spell.ebwizardry\:cure_effects.desc=Eltávolít minden effektet a használóról, legyenek azok jók vagy rosszak. +spell.ebwizardry\:curse_of_enfeeblement.desc="Hirtelen elgyengült, mintha magát az életet facsarták volna ki belőle." - Vallomás az egyetlen ismert személytől, ki találkozott a lélekjáró kultusz egy tagjával, és túlélte. +spell.ebwizardry\:curse_of_soulbinding.desc=Elválaszthatatlanul hozzáköti a célpont lelkét a varázsigét használóéhoz, s így minden a használót ért sebzés az áldozatot is éri. Addig tart míg vagy az áldozat, vagy a használó meghal. +spell.ebwizardry\:curse_of_undeath.desc=Élő halállal átkozza meg a célpontot, mely így égni kezd a napon, akárcsak a zombik és csontvázak. Normál esetben addig tart, míg az áldozat meg nem hal. Nincs hatása az eleve élőholt lényeken. +spell.ebwizardry\:darkness_orb.desc=Egy lassan mozgó, sötét energiából álló lövedéket lő ki, mely sorvasztani kezdi amit eltalál. +spell.ebwizardry\:darkvision.desc=Éjjellátást nyújt használójának 45 másodpercre. +spell.ebwizardry\:dart.desc=Egy tűt lő ki, mely megsebzi és legyengíti a célpontot. +spell.ebwizardry\:decay.desc=Egy rothasztó pocsolyát idéz a földre, mely megfertőz bármit, mi hozzáér, lassú időnkénti sebződést okozva. A fertőzött lények még több rothadást hagynak maguk után, amerre járnak. +spell.ebwizardry\:decoy.desc=Egy illuzív klónt alkot az idézőről, mely átveri a mobokat, hogy inkább azt támadják. Az elterelés 30 másodperc után köddé válik. +spell.ebwizardry\:detonate.desc=Robbanást idéz meg ott, ahova a használó mutat, mely minden közeli lényt megsebez - beleértve a használót is, ha túl közel van. +spell.ebwizardry\:diamondflesh.desc="Nyilaidnak esélye sincs ellenem!" +spell.ebwizardry\:disintegration.desc=Rövid hatótávú, de erős lángsugarat lövell, mely áldozatát izzó parazsakra robbantja szét. A parazsakra lépő lények felgyulladnak. +spell.ebwizardry\:divination.desc=Közeli ércekhez és nyersanyagokhoz vezeti az idézőt. A potenciál növelésével nő az értékesebb ércek találásának esélye. +spell.ebwizardry\:dragon_fireball.desc=Egy végsárkány-tűzgolyót lövell ki, mely időző mérges felhőket hagy a becsapódás helyén. +spell.ebwizardry\:earthquake.desc=Egy igaz mestere a földmágiának hegyeket mozgathat meg. +spell.ebwizardry\:empowering_presence.desc=Megnövelt mágikus sebzést nyújt az idézőnek és közeli szövetségeseinek 30 másodpercig. +spell.ebwizardry\:entrapment.desc=Csapdába ejti a célpontot egy sötétségből alkotott gömbben, mely tehetetlenül felfele emeli az áldozatot, miközben folyamatosan sebzi. +spell.ebwizardry\:evade.desc=Gyorsan oldalirányba félrerántja a használót, hogy kikerülje a közelgő támadásokat. +spell.ebwizardry\:fireball.desc=Egy tűzgolyót lő ki az irányba, merre mutatsz. +spell.ebwizardry\:firebolt.desc=Egy rövid hatótávú lángtűt lő ki eléd. +spell.ebwizardry\:firebomb.desc=Egy tűzbombát lő ki az irányba, merre mutatsz, mely becsapódáskor felrobban és lángra gyújtja a közeli célpontokat. +spell.ebwizardry\:fire_resistance.desc=Tűzállóságot nyújt a használójának 30 másodpercre. +spell.ebwizardry\:fire_sigil.desc=Egy mágikus tűzcsapdát helyez a földre, mely megsebzi és felgyújtja az azt aktiváló lényeket. +spell.ebwizardry\:fireskin.desc=Lángokba öleli a használót 30 másodpercre, mely miatt minden mi megtámadja őt lángra kap. +spell.ebwizardry\:fire_breath.desc="Én vagyok a sárkány." +spell.ebwizardry\:flame_ray.desc=Lángok folyamatos áradatát alkotja amerre nézel, mely felgyújtja és folyamatosan sebzi a célpontokat. +spell.ebwizardry\:flaming_axe.desc=Egy lángoló baltát idéz meg, mely felgyújtja ellenfeleid, ha megütöd őket. 30 másodperc után porrá vál. +spell.ebwizardry\:flaming_weapon.desc=Ideiglenesen átitatja az első fegyvert a használó gyorstárában a lángok erejével, mely így felgyújtja áldozatait. A mágia 45 másodperc után elhalványodik. +spell.ebwizardry\:flight.desc=Szárnyalj mint egy sas. +spell.ebwizardry\:font_of_mana.desc="Izzó mágikus energiával teltünk el, mely eredőpontja látszólag nem más volt mint a..." - Kivonat egy elfeledett mágus naplójából; a lap többi része elégett. +spell.ebwizardry\:font_of_vitality.desc=Fantasztikus érzés. +spell.ebwizardry\:force_arrow.desc=Egy színtiszta erőből álló nyilat lő tekinteted irányába. +spell.ebwizardry\:forcefield.desc=Egy erőteret alkot az idéző körül, mely visszalöki a támadókat és eltéríti a lövedékeket. +spell.ebwizardry\:force_orb.desc=Egy színtiszta erőből álló gömböt lő ki, mely megsebzi és hátraveti a becsapódás pontja körüli lényeket. +spell.ebwizardry\:forests_curse.desc="Hogy merészelsz belépni az erdőmbe!" +spell.ebwizardry\:forest_of_thorns.desc=Az erdő mélyén bújik egy tisztás, rejtett,\nA tisztáson egy varázsló tanulmányoz, rendületlen,\nMágikus művészeteket, elmondatlan méretekben,\nMélyen a tövisek erdejében. +spell.ebwizardry\:freeze.desc=Megfagyasztja a célpontot 10 másodpercre. Képes a vizet is megfagyasztani, vagy havat hagyni a földön. +spell.ebwizardry\:freezing_weapon.desc=Ideiglenesen átitatja az első fegyvert a használó gyorstárában a fagy erejével, mely így megfagyasztja áldozatait. A mágia 45 másodperc után elhalványodik. +spell.ebwizardry\:frost_axe.desc=Egy fagyott fejszét idéz meg, mely megfagyasztja a megütött ellenfeleket. 30 másodperc után szilánkokra hasad. +spell.ebwizardry\:frost_ray.desc=Fagy folyamatos áradatát alkotja amerre nézel, mely lelassítja és folyamatosan sebzi a célpontokat. +spell.ebwizardry\:frost_sigil.desc=Egy mágikus jégcsapdát helyez a földre, mely megsebzi és megfagyasztja az azt aktiváló lényeket. +spell.ebwizardry\:frost_step.desc=Segítségével a használója lába körül megfagy a víz, ahogy jár. Hatása 30 másodpercig tart. +spell.ebwizardry\:grapple.desc=Egy mágikus indát lövell ki, melynek segítségével használója megcsáklyázhat blokkokat, vagy maga felé húzhat entitásokat. Engedd el az egérgombot, hogy az inda elengedje a blokkot vagy entitást. +spell.ebwizardry\:glide.desc=Használója lassan siklik lefelé, míg a levegőben van és lenyomva tartja a használat gombját. +spell.ebwizardry\:greater_fireball.desc=Egy nagy tűzgolyót lő ki, mely becsapódáskor felrobban. +spell.ebwizardry\:greater_heal.desc=Használóját 4 szívvel gyógyítja. +spell.ebwizardry\:greater_telekinesis.desc=Segítségével használója felemelhet egy blokkot vagy entitást, és a levegőben tarthatja mindaddig, míg a használat gombját lenyomva tartja. Ha guggol miközben egy entitást vagy blokkot tart, elhajíthatja azt egy rövid távolságra. +spell.ebwizardry\:greater_ward.desc=Erős védelmező hatást nyújt használójának a beérkező mágikus sebzések ellen, 30 másodpercig tart. +spell.ebwizardry\:group_heal.desc=Használóját és minden közeli megidézett lényét és szövetségesét 3 szívvel gyógyítja. +spell.ebwizardry\:growth_aura.desc=Megérlel minden közeli termést, valamint magas füvet és virágokat sarjaszt füvön. +spell.ebwizardry\:hailstorm.desc=A harmadik kor nagy tele alatt történt, hogy a jégmágusok felfedezték valódi erejüket. +spell.ebwizardry\:heal.desc=A használót 2 szívvel gyógyítja. +spell.ebwizardry\:heal_ally.desc=2 és fél szívvel gyógyítja a célpontot. +spell.ebwizardry\:healing_aura.desc=Gyógyító energiából álló zónát alkot, mely minden benne álló szövetségest regenerál. Minden a zónán belül álló élőholt lassan sebződik. +spell.ebwizardry\:homing_spark.desc=Egy lebegő szikrát idéz meg, mely ellenfeleid felé mozog. +spell.ebwizardry\:iceball.desc=Egy jéggömböt lő ki arra, amerre mutatsz. +spell.ebwizardry\:ice_age.desc="Légy a jég foglya az örrökkévalóságig!" +spell.ebwizardry\:ice_charge.desc=Egy jégtömböt lövell ki, mely becsapódáskor szétrobban, szilánkokat szórva minden irányba és megfagyasztva a közeli lényeket. +spell.ebwizardry\:ice_lance.desc=Egy nagy jégcsapot lő ki, mely több célpontot is átdöf, miközben súlyosan megsebesíti és megfagyasztja őket. +spell.ebwizardry\:ice_shard.desc=Egy jégszilánkot lő ki, mely megsebzi és lelassítja a célpontot, ha eltalálja. +spell.ebwizardry\:ice_shroud.desc=Hideg lepelbe burkolja használójátCreates a shroud of cold around the caster for 30 seconds, causing anything that attacks them to be frozen. +spell.ebwizardry\:ice_spikes.desc=Borotvaéles jégtüskék emelkednek ki a földből, felnyársalva minden lényt, mely köztük reked. +spell.ebwizardry\:ice_statue.desc=Keményre fagyasztja a célpontot 20 másodpercig, vagy amíg valami ki nem töri fogságából. A célpont nem tud semmit se eközben, de védve is van minden sebzéstől. +spell.ebwizardry\:ignite.desc=Felgyújtja a célpontot 10 másodpercre. Kovakő és acél helyettesítőjeként is használható. +spell.ebwizardry\:imbue_weapon.desc=Ideiglenesen átitatja az első fegyvert a használó gyorstárában mágiával, megnövelve annak hatékonyságát. A varázslat 45 másodperc után szertefoszlik. +spell.ebwizardry\:intimidate.desc=Rettenetes morgást bocsát ki, mely hallatára a közeli lények félelemmel eltelve menekülni kezdenek. A megfélemlített lények 30 másodperc után magukhoz térnek. +spell.ebwizardry\:invigorating_presence.desc=Használójának és közeli szövetségeseinek megnövelt erőt nyújt 45 másodpercre. +spell.ebwizardry\:invisibility.desc=30 másodpercre láthatatlanná teszi használóját. +spell.ebwizardry\:invoke_weather.desc=Megváltoztatja a világ időjárását. +spell.ebwizardry\:ironflesh.desc=Nagyban megnöveli használója ellenállóképességét 30 másodpercre. +spell.ebwizardry\:leap.desc=Felfelé és enyhén előre veti használóját több blokkal. +spell.ebwizardry\:levitation.desc=Amíg a használat gombja le van nyomva, felfelé emel. Az esési sebzést is képes elnyelni, ha földet érés előtt használják. +spell.ebwizardry\:life_drain.desc=Sorvasztó energia áradatát alkotja az irányban, merre mutatsz, mely elszívja a célpont életerejét, és fokozatosan téged gyógyít vele. +spell.ebwizardry\:light.desc=Egy mágikus fényforrást idéz meg, mely 30 másodpercig bevilágítja a környező területet. +spell.ebwizardry\:lightning_arrow.desc=Egy villámnyilat lő ki az irányba, merre mutatsz. +spell.ebwizardry\:lightning_bolt.desc=Villámcsapást idéz a pontra, ahova mutatsz. +spell.ebwizardry\:lightning_disc.desc=Egy szikrákból álló korongot lő ki az irányba, merre nézel, mely célpontokat keres. +spell.ebwizardry\:lightning_hammer.desc="A mennyek haragjával sújtok le rád!" +spell.ebwizardry\:lightning_pulse.desc=Elektromossággal tölti fel a földet az idézője körül, megsebezve és hátravetve a közeli ellenfeleket. +spell.ebwizardry\:lightning_ray.desc=Villámlás áradatát idézi meg, mely folyamatosan sebzi a célpontokat. +spell.ebwizardry\:lightning_sigil.desc=Egy mágikus villámcsapda, mely megsebez minden azt aktiváló lényt, és átpattanó szikrákat küld a többi közeli ellenfél felé. +spell.ebwizardry\:lightning_web.desc="Koncentrálj. Vezesd át a gondolataidban dúló vihart pálcádon, és szabadítsd el haragját." +spell.ebwizardry\:magic_missile.desc=Egy mágikus energiából álló lövedéket lő ki az irányba, merre mutatsz. +spell.ebwizardry\:metamorphosis.desc=Átváltoztatja a célpontot egy másik formává. Csak bizonyos lényeken működik. +spell.ebwizardry\:meteor.desc=Egyes varázslók csak látni szeretnék a világot égni... +spell.ebwizardry\:mind_control.desc=Átveszi az irányítást a célpont elméje felett 30 másodpercre, mely így oldalt vált és a használót védve kezd harcolni. Nem működik az erős akarattal bíró lényeken. +spell.ebwizardry\:mind_trick.desc=Összezavarja a célpontot 15 másodpercre, mely így képtelenné válik az effektív ellentámadások véghezvitelére. A hatás eltűnik, ha a célpont megsebződik. +spell.ebwizardry\:mine.desc=Kitöri a blokkot melyre használója éppen néz. A potenciál növelésével keményebb blokkok kitörése is lehetővé válik. +spell.ebwizardry\:muffle.desc=Elfojt minden neszt, melyet használója kelt, 30 másodpercre. Elnémított állapotban a mobok csak akkor vesznek észre, ha egyenesen feléd néznek. +spell.ebwizardry\:none.desc=Ahhoz, hogy varázskönyvet szerezz a /give paranccsal, használd ezt az id-t\: /give [játékos] ebwizardry\:spell_book 1 [varázsige id] (Ha ezt a könyvet egy ládában találtad, egy másik mod valamit összekavart). +spell.ebwizardry\:oakflesh.desc=Megnöveli használója ellenállóképességét 30 másodpercre. +spell.ebwizardry\:paralysis.desc=Egy erős villámcsapással megbénítja az ellenfelet 5 másodpercre. A megbénított lények nem tudnak mozogni, viszont továbbra is sebződhetnek - de ha túl sokat sebződnek, magukhoz térnek a bénulásból. +spell.ebwizardry\:petrify.desc=Kővé változtatja a célpontot, míg ki nem törik, adott eséllyel az önálló kiszabadulásra ha sötétben van. A célpont képtelen bármit is tenni, viszont sebezhetetlenné is válik. +spell.ebwizardry\:phase_step.desc=Rövid távon előreteleportálja használóját, akár falakon keresztül. A hatókör fejlesztésével megnő az átléphető falak vastagsága. +spell.ebwizardry\:plague_of_darkness.desc=A sötétség elnyeli mindüket... +spell.ebwizardry\:pocket_furnace.desc=Kisüt legfeljebb 5 kisüthető tárgyat a használója eszkörtárából. A gyorstár tárgyait választja ki elsőnek. +spell.ebwizardry\:pocket_workbench.desc=Segítségével használója ugyanúgy barkácsolhat, mintha csak egy barkácsasztalnál dolgozna. +spell.ebwizardry\:poison.desc=Mérget köp az irányba, melyre nézel. +spell.ebwizardry\:poison_bomb.desc=Egy méregbombát lövell ki, mely becsapódáskor felrobban, megmérgezve minden közeli lényt. +spell.ebwizardry\:possession.desc="Válj ellenségeddé." +spell.ebwizardry\:ray_of_purification.desc=Vakító fénysugarat bocsát ki, mely megsebzi és elvakítja a célpontokat. Az élőholt lények kétszer annyi sebzést szenvednek el, és felgyulladnak. +spell.ebwizardry\:remove_curse.desc=Eltávolít bármely átkot, mely használóját jelenleg érinti. +spell.ebwizardry\:replenish_hunger.desc=Újratölti használója éhségét 3 ponttal. +spell.ebwizardry\:resurrection.desc=Egy gyógyítómesterrel az oldaladon, a halál... csupán egy opció. +spell.ebwizardry\:reversal.desc=Eltávolít egy random negatív effektet, és átrakja a célpontra, mely így a hátramaradó időben helyetted fogja elszenvedni. A potenciál növelésével nő az átirányított effektek száma. +spell.ebwizardry\:ring_of_fire.desc=Egy tűzgyűrűt alkot használója körül, megsebezve és felgyújtva minden környező ellenfelet. +spell.ebwizardry\:satiety.desc=Újratölti használója éhségét 8 ponttal. +spell.ebwizardry\:shadow_ward.desc=Sötétség falát képezve idézője előtt minden beérkező sebzés felét visszairányítja a támadóra. +spell.ebwizardry\:shield.desc=Egy védelmező erőpajzsot alkot, mely kivédi a beérkező mágikus támadásokat és lövedékeket. Mindeközben egy gyenge ellenállás effektet is nyújt idézőjének. +spell.ebwizardry\:shockwave.desc=Bumm. +spell.ebwizardry\:shulker_bullet.desc=Egy shulkergolyót lő ki, mely célkövető és találatkor levitációt ad. +spell.ebwizardry\:silverfish_swarm.desc="Áhhhh! Ezek SOKSZOROZÓDNAK!" +spell.ebwizardry\:sixth_sense.desc=Kiélesíti használója érzékeit és lehetővé teszi a közeli lények helyzetének érzékelését, akár falakon keresztül is, 20 másodperc erejéig. +spell.ebwizardry\:slime.desc=Nyálkával önti el a célpontot, mely lelassítja és folyamatosan sebzi azt. A nyálka 10 másodperc után szétdurran. +spell.ebwizardry\:slow_time.desc=A Chronomágia az idő manipulációjának és személyes igényekhez szabásának műveszete. Széles körben úgy hitték, hogy eltűnt a régmúltban... egészen mostanáig. +spell.ebwizardry\:smoke_bomb.desc=Egy füstbombát lövell ki, mely becsapódáskor felrobban, füstöt eresztve ki és megvakítva a közeli lényeket egy rövid időre. +spell.ebwizardry\:snare.desc=Egy csapdát helyez a földre, mely megsebzi és kis időre lelassítja a belelépőt. +spell.ebwizardry\:snowball.desc=Egy hógolyót lő ki az irányba, melybe nézel. +spell.ebwizardry\:spark_bomb.desc=Egy sokkoló töltetet lő ki, mely becsapódáskor szikrákat vet a közeli ellenfelek felé. +spell.ebwizardry\:spectral_pathway.desc=Egy elpusztíthatatlan mágikus hidat alkot előtted, mely 15 blokkra nyúlik ki, és 60 másodperc után eltűnik. +spell.ebwizardry\:speed_time.desc=...nappal, éjszaka, hajnal, alkonyat, napkelte és naplemente\: így megy az idő menete, mely minket foglyul ejt végtelen ciklusában a... +spell.ebwizardry\:spider_swarm.desc=Mérges pókok raját idézi meg, melyek érted küzdenek. A pókok eltűnnek 30 másodperc után, vagy ha megölik őket. +spell.ebwizardry\:static_aura.desc=Villámokba burkolja használóját 30 másodpercre, szikrákat lövellve bármi felé, mely megüti őt. +spell.ebwizardry\:summon_blaze.desc=Egy őrlángot idéz meg, hogy harcoljon érted. 30 másodperc múlva eltűnik, hacsak nem ölik meg hamarabb. +spell.ebwizardry\:summon_ice_giant.desc="Zúzd össze csontjaikat!" +spell.ebwizardry\:summon_ice_wraith.desc=Egy jéglidércet idéz meg, hogy harcoljon érted. 30 másodperc múlva eltűnik, hacsak nem ölik meg hamarabb. +spell.ebwizardry\:summon_iron_golem.desc=Automatikus automatizált autonóm automaton. +spell.ebwizardry\:summon_lightning_wraith.desc=Egy villámlidércet idéz meg, hogy harcoljon érted. 30 másodperc múlva eltűnik, hacsak nem ölik meg hamarabb. +spell.ebwizardry\:summon_phoenix.desc=A hamvakból... +spell.ebwizardry\:summon_shadow_wraith.desc=Egy árnylidércet idéz meg, hogy harcoljon érted. +spell.ebwizardry\:summon_skeleton.desc=Egy csontvázat idéz meg, hogy harcoljon érted. 30 másodperc múlva eltűnik, hacsak nem ölik meg hamarabb. +spell.ebwizardry\:summon_skeleton_legion.desc="Emelkedj, élőholt sereg!" +spell.ebwizardry\:summon_snow_golem.desc=Egy hógólemet idéz meg, hogy harcoljon érted. A gólem haláláig megmarad. +spell.ebwizardry\:summon_spirit_horse.desc=Egy lélekparipát idéz meg, melyet megülhetsz. Ha nem ülnek rajta, rövid idő után eltűnik, illetve elküldhető ha guggolva kattintanak rá bármilyen pálcával. +spell.ebwizardry\:summon_spirit_wolf.desc=Egy lélekfarkast idéz meg, mely harcol érted. Csak akkor fog eltűnni, ha megölik, de elküldheted, ha guggolva kattintasz rá bármilyen pálcával. +spell.ebwizardry\:summon_storm_elemental.desc="Vihar Elementál\: Az elemek ősi manifesztációja, alig képes a benne kavargó nyers erőt benntartani." - A Varázsló Útmutatója Arkán Lényekhez, II. Kötet +spell.ebwizardry\:summon_wither_skeleton.desc=Egy sorvasztócsontvázat idéz meg, hogy harcoljon érted. 30 másodperc múlva eltűnik, hacsak nem ölik meg hamarabb. +spell.ebwizardry\:summon_zombie.desc=Egy zombit idéz meg, hogy harcoljon érted. 30 másodperc múlva eltűnik, hacsak nem ölik meg hamarabb. +spell.ebwizardry\:telekinesis.desc=Egy tárgyat vagy kis objektumot feléd mozgat, vagy rákattint a blokkra, amire nézel. Játékosok lefegyverzésére is használható. +spell.ebwizardry\:thunderbolt.desc=Egy villámcsapást lő ki, mely hátralöki a célpontokat. +spell.ebwizardry\:thunderstorm.desc="Muhahahahahahaha!" +spell.ebwizardry\:tornado.desc=Egy tornádót szabadít el az irányba, melyre mutatsz, felkapva és az ég felé hajítva mindent mi útjába kerül. +spell.ebwizardry\:transience.desc=Alaktalanná teszi használóját 20 másodpercre, ki így immunis lesz minden sebzésre, de nem tud kitörni vagy letenni blokkokat, sem bármit megsebezni. +spell.ebwizardry\:transportation.desc=Elteleportálja használóját a megjegyzett kőköréhez. Ezen varázsige használatához először építs egy kört transzportációs kövekből, majd kattints rájuk pálcáddal. +spell.ebwizardry\:vanishing_box.desc=Hozzáférést nyújt használójának végládája tartalmához. +spell.ebwizardry\:vex_swarm.desc=Bosszancsok raját idézi meg, hogy harcoljanak érted. 30 másodperc múlva eltűnnek, hacsak nem ölik meg őket hamarabb. +spell.ebwizardry\:wall_of_frost.desc=Tél az ujjaid hegyén. +spell.ebwizardry\:ward.desc=Védelmező hatást nyújt idézőjének, mely 30 másodperc erejéig lecsökkenti a beérkező mágikus sebzést. +spell.ebwizardry\:water_breathing.desc=Segítségével használója képessé válik a víz alatt légzésre 60 másodpercig. +spell.ebwizardry\:whirlwind.desc=Felfele és elfele fújja a célpontot, nagy sebességgel. +spell.ebwizardry\:wither.desc=Sötét sugarat bocsát ki, mely elsorvaszt bármit mit megérint. +spell.ebwizardry\:wither_skull.desc=Egy sorvasztókoponyát lő ki az irányba, merre mutatsz. + +spell.ebwizardry\:invoke_weather.sun=Az eső lassan eláll... +spell.ebwizardry\:invoke_weather.rain=Az egek megnyílnak... +spell.ebwizardry\:transportation.missing=A kőkör hiányzik vagy el van torlaszolva... +spell.ebwizardry\:transportation.undefined=Előbb meg kell jegyezned egy kőkör helyét! +spell.ebwizardry\:transportation.wrongdimension=Nincs megjegyzett kőkör ebben a dimenzióban... +spell.ebwizardry\:clairvoyance.searching=Keresés... +spell.ebwizardry\:clairvoyance.confirm=Az út melyet az %1$s használata jelenít meg, mostantól erre a pontra vezet vissza +spell.ebwizardry\:clairvoyance.outofrange=A megjegyzett helyed túl messze van, vagy hozzáférhetetlen... +spell.ebwizardry\:clairvoyance.undefined=Előbb meg kell jegyezned egy helyet! +spell.ebwizardry\:clairvoyance.wrongdimension=A megjegyzett hely egy másik dimenzióban fekszik... +spell.ebwizardry\:possession.insufficienthealth=Nincs elég életerőd hogy megszálld %1$s-t! +spell.ebwizardry\:possession.success=Nyomd meg a %s gombot, hogy megszüntesd a megszállást +spell.ebwizardry\:divination.nothing=Semmi sem történt. +spell.ebwizardry\:divination.weak=Érzed ahogy a pálcád enyhén %s rándul. +spell.ebwizardry\:divination.moderate=Érzed ahogy a pálcád %s ránt. +spell.ebwizardry\:divination.strong=Pálcád %s húz erősen. +spell.ebwizardry\:divination.very_strong=Pálcád élesen %s lódul. +spell.ebwizardry\:divination.down=lefele +spell.ebwizardry\:divination.up=felfele +spell.ebwizardry\:divination.front=előre +spell.ebwizardry\:divination.back=hátra +spell.ebwizardry\:divination.left=balra +spell.ebwizardry\:divination.right=jobbra +spell.ebwizardry\:resurrection.resurrect_ally=%s fel lett támasztva %s által +spell.ebwizardry\:resurrection.resurrect_self=%s visszatért az életbe +spell.ebwizardry\:resurrection.button_wait=Feltámasztás (%ss) +spell.ebwizardry\:resurrection.button_ready=Feltámasztás + +forfeit.ebwizardry\:do_nothing=Semmi sem történt. + +forfeit.ebwizardry\:burn_self=A %s lángokba borul a kezedben! +forfeit.ebwizardry\:fireball=Egy tűzgolyó materializálódik előtted! +forfeit.ebwizardry\:firebomb=Egy tűzbomba jelenik meg pont feletted! +forfeit.ebwizardry\:explode=Egy hirtelen robbanás ledönt a lábadról! +forfeit.ebwizardry\:blazes=Hirtelen ellenséges őrlángok jelennek meg körülötted! +forfeit.ebwizardry\:burn_surroundings=A körülötted fekvő terület hirtelen lángokban áll! +forfeit.ebwizardry\:meteors=Tüzes armageddon hull az égből! + +forfeit.ebwizardry\:freeze_self=Jeges borzongást érzel végigfutni magadon! +forfeit.ebwizardry\:freeze_self_2=A %s oly hideget áraszt, hogy helyben megfagysz! +forfeit.ebwizardry\:ice_spikes=Jégtüskék emelkednek ki a földből talpaid alatt! +forfeit.ebwizardry\:blizzard=Hirtelen egy üvöltő hóvihar kellős közepén találod magad! +forfeit.ebwizardry\:ice_wraiths=Hirtelen ellenséges jéglidércek materializálódnak körülötted! +forfeit.ebwizardry\:hailstorm=Borotvaéles jégszilánkok kezdenek hullani fejedre! +forfeit.ebwizardry\:ice_giant=Egy ellenséges jégóriás jelenik meg előtted! + +forfeit.ebwizardry\:thunder=A tekercsből fülsüketítő dübörgés hallatszik, mely ledönt a lábadról! +forfeit.ebwizardry\:storm=Úgy tűnik vihar készülődik! +forfeit.ebwizardry\:lightning_sigils=Figyelj a lábad elé! +forfeit.ebwizardry\:lightning=Villám csap beléd! +forfeit.ebwizardry\:paralyse_self=Megbénultál! +forfeit.ebwizardry\:lightning_wraiths=Hirtelen ellenséges villámlidércek jelennek meg körülötted! +forfeit.ebwizardry\:storm_elementals=Ellenséges vihar elementálok materializálódnak minden oldaladon! + +forfeit.ebwizardry\:nausea=...huh? Mi történt? Hol vagyok? +forfeit.ebwizardry\:zombie_horde=Zombik hordája emelkedik ki a földből körülötted! +forfeit.ebwizardry\:wither_self=Sötétség sorvasztja lelkedet! +forfeit.ebwizardry\:cripple_self=A %s halálos üvöltést hallat, s hajszál híján halálosan megnyomorodtál! +forfeit.ebwizardry\:shadow_wraiths=Ellenséges árnylidércek materializálódnak minden oldaladon! + +forfeit.ebwizardry\:snares=Ez egy csapda! +forfeit.ebwizardry\:squid=Tintahal! +forfeit.ebwizardry\:uproot_plants=Minden közeli növény hirtelen gyökerestül kifordul! +forfeit.ebwizardry\:poison_self=Megmérgeződtél! +forfeit.ebwizardry\:flood=Víz jelenik meg körülötted! +forfeit.ebwizardry\:bury_self=A föld összeomlik alattad! + +forfeit.ebwizardry\:spill_inventory=Tárgyaid kiszóródnak mindenhová! +forfeit.ebwizardry\:teleport_self=Hirtelen elteleportálódsz valahová! +forfeit.ebwizardry\:levitate_self=Tehetetlenül lebegni kezdesz! +forfeit.ebwizardry\:vex_horde=Bosszancsok hordája materializálódik körülötted! +forfeit.ebwizardry\:black_hole=Kavargó örvény jelenik meg előtted! +forfeit.ebwizardry\:arrow_rain=Nyilak zápora kezd hullani fejedre! + +forfeit.ebwizardry\:damage_self=Áu! +forfeit.ebwizardry\:spill_armour=Minden páncélod leesik rólad! +forfeit.ebwizardry\:hunger=Hirtelen nagyon éhesnek érzed magad! +forfeit.ebwizardry\:blind_self=Megvakultál! +forfeit.ebwizardry\:weaken_self=Súlyosan elgyengültél! +forfeit.ebwizardry\:jam_self=Mágiád haszontalanná vált! +forfeit.ebwizardry\:curse_self=Élő halállal átkozódtál meg! + +potion.ebwizardry\:frost=Fagycsípés +potion.ebwizardry\:fireskin=Tűzbőr +potion.ebwizardry\:ice_shroud=Jéglepel +potion.ebwizardry\:static_aura=Statikus Aura +potion.ebwizardry\:transience=Alaktalanság +potion.ebwizardry\:decay=Rothadás +potion.ebwizardry\:sixth_sense=Hatodik Érzék +potion.ebwizardry\:arcane_jammer=Arkán Zavarás +potion.ebwizardry\:mind_trick=Elmetrükk +potion.ebwizardry\:mind_control=Agymosott +potion.ebwizardry\:font_of_mana=Mana Forrása +potion.ebwizardry\:fear=Félelem +potion.ebwizardry\:curse_of_soulbinding=Lélekkötés Átka +potion.ebwizardry\:paralysis=Bénulás +potion.ebwizardry\:muffle=Némítás +potion.ebwizardry\:ward=Hárítás +potion.ebwizardry\:slow_time=Lassított Idő +potion.ebwizardry\:empowerment=Megerősítés +potion.ebwizardry\:curse_of_enfeeblement=Elgyengülés Átka +potion.ebwizardry\:curse_of_undeath=Élőhalál Átka +potion.ebwizardry\:containment=Bezártság +potion.ebwizardry\:frost_step=Fagylépés + +enchantment.ebwizardry\:magic_sword=Átitatottság +enchantment.ebwizardry\:magic_bow=Átitatottság +enchantment.ebwizardry\:flaming_weapon=Lánggal Átitatott +enchantment.ebwizardry\:freezing_weapon=Faggyal Átitatott +enchantment.ebwizardry\:shocking_weapon=Villámmal Átitatott + +enchantment.ebwizardry\:magic_protection=Mágikus Védelem +enchantment.ebwizardry\:frost_protection=Fagyvédelem +enchantment.ebwizardry\:shock_protection=Sokkvédelem + +enchantment.ebwizardry.magic_sword.desc=Az Átitatott Fegyver varázslat része; elérhetetlen túlélő módban (a könyv nem fog működni). +enchantment.ebwizardry.magic_bow.desc=Az Átitatott Fegyver varázslat része; elérhetetlen túlélő módban (a könyv nem fog működni). +enchantment.ebwizardry.flaming_weapon.desc=A Lángoló Fegyver varázslat része; elérhetetlen túlélő módban (a könyv nem fog működni). +enchantment.ebwizardry.freezing_weapon.desc=A Fagyos Fegyver varázslat része; elérhetetlen túlélő módban (a könyv nem fog működni). +enchantment.ebwizardry.shocking_weapon.desc=A Sokkoló Fegyver varázslat része; elérhetetlen túlélő módban (a könyv nem fog működni). + +enchantment.ebwizardry.magic_protection.desc=Minden mágikus eredetű sebzést csökkent. +enchantment.ebwizardry.frost_protection.desc=Csökkenti a fagy okozta sebesülést. +enchantment.ebwizardry.shock_protection.desc=Csökkenti a sokkolás okozta sebesülést. + +key.categories.ebwizardry=Wizardry + +key.ebwizardry.next_spell=Következő Varázslat +key.ebwizardry.previous_spell=Előző Varázslat + +death.attack.wizardry_magic=%1$s meg lett ölve %2$s mágiája által +death.attack.indirect_wizardry_magic=%1$s meg lett ölve %2$s mágiája által + +soundCategory.ebwizardry_spells=Varázslatok + +commands.ebwizardry\:cast.usage=/%1$s [player | x y z direction] [duration] [modifiers] +commands.ebwizardry\:cast.success=Sikeresen végrehajtva: %s +commands.ebwizardry\:cast.success_continuous=Sikeresen végrehajtva: %s ennyi időre: %s másodperc +commands.ebwizardry\:cast.success_remote=Sikeresen végrehajtva: %s %s által +commands.ebwizardry\:cast.success_remote_continuous=Sikeresen végrehajtva: %s %s által ennyi időre: %s másodperc +commands.ebwizardry\:cast.success_position=Sikeresen végrehajtva: %s itt: %s, %s, %s +commands.ebwizardry\:cast.success_position_continuous=Sikeresen végrehajtva: %s itt: %s, %s, %s ennyi időre: %s másodperc +commands.ebwizardry\:cast.fail=Sikertelen végrehajtás: %1$s +commands.ebwizardry\:cast.not_found=Nincs varázslat ezzel az ID-vel: %1$s +commands.ebwizardry\:cast.invalid_direction=Invalid direkció: %1$s (valid direkciók: up, down, north, south, east, west) +commands.ebwizardry\:cast.tag_error=Data tag párosítás sikertelen\: %s +commands.ebwizardry\:cast.origin_not_specified=Egy helyet vagy játékost kell meghatároznod ezen varázslat végrehajtására +commands.ebwizardry\:cast.duration_not_specified=Egy időtartamot kell meghatároznod ezen varázslat végrehajtására + +commands.ebwizardry\:ally.usage=/%1$s [player] +commands.ebwizardry\:ally.addally=%1$s hozzá lett adva %2$s szövetségeseinek listájához +commands.ebwizardry\:ally.removeally=%1$s el lett távolítva %2$s szövetségeseinek listájából +commands.ebwizardry\:ally.self=Játékosok nem lehetnek önmaguk szövetségesei! +commands.ebwizardry\:ally.permission=Nincs engedélyed más játékosok szövetségeseinek módosítására + +commands.ebwizardry\:allies.usage=/%1$s [player] +commands.ebwizardry\:allies.list=Veled szövetséges játékosok\: %1$s +commands.ebwizardry\:allies.list_other=%1$s-el szövetséges játékosok\: %2$s +commands.ebwizardry\:allies.permission=Nincs engedélyed más játékosok szövetségeseinek kilistázására +commands.ebwizardry\:allies.none=Nincs + +commands.ebwizardry\:discoverspell.usage=/%1$s [player] +commands.ebwizardry\:discoverspell.not_found=Nincs varázsige ezzel az ID-vel: %1$s +commands.ebwizardry\:discoverspell.clear=Minden varázsige felfedezési adat törölve %1$s számára +commands.ebwizardry\:discoverspell.all=Minden varázslat hozzáadva %1$s varázsige felfedezési adataihoz +commands.ebwizardry\:discoverspell.addspell=%1$s hozzáadva %2$s varázsige felfedezési adataihoz +commands.ebwizardry\:discoverspell.removespell=%1$s eltávolítva %2$s varázsige felfedezési adataiból + +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.bonemeal_grows_crystal_flowers=Bonemeal Grows Crystal Flowers +config.ebwizardry.bonemeal_grows_crystal_flowers.tooltip=Whether using bonemeal on grass blocks has a chance to grow crystal flowers. +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.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. Remove all dimensions to disable wizard towers completely. +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. This list should not be empty; to disable wizard towers, use the tower dimensions setting. +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. This list should not be empty; to disable wizard towers, use the tower dimensions setting. +config.ebwizardry.obelisk_dimensions=Obelisk Dimensions +config.ebwizardry.obelisk_dimensions.tooltip=List of ids of dimensions in which obelisks will generate. Remove all dimensions to disable obelisks completely. +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. This list should not be empty; to disable obelisks, use the obelisk dimensions setting. +config.ebwizardry.shrine_dimensions=Shrine Dimensions +config.ebwizardry.shrine_dimensions.tooltip=List of ids of dimensions in which shrines will generate. Remove all dimensions to disable shrines completely. +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. This list should not be empty; to disable shrines, use the shrine dimensions setting. +config.ebwizardry.tree_blocks=Tree Blocks +config.ebwizardry.tree_blocks.tooltip=List of registry names of blocks which can be overwritten by wizardry's structure generators, affecting both fast and fancy structure generation. Most tree blocks and other foliage should work automatically, but those that don't can be added manually here. Block names are not case sensitive. For mod blocks, prefix with the mod ID (e.g. dynamictrees:oakbranch). +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.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.screen_shake=Screen Shake +config.ebwizardry.screen_shake.tooltip=Whether to use the screen shake effect for certain spells. +config.ebwizardry.blink_effect=Blink Effect +config.ebwizardry.blink_effect.tooltip=Whether to use the screen blink effect for teleportation spells. + +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.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.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.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=Arkán Munkaasztal + +integration.antiqueatlas.marker.ebwizardry.wizard_tower=Varázslótorony +integration.antiqueatlas.marker.ebwizardry.obelisk=Obeliszk +integration.antiqueatlas.marker.ebwizardry.shrine=Szentély + +spell.ebwizardry\:magic_missile_festive=Mágikus Fagyöngy +spell.ebwizardry\:summon_snow_golem_festive=Fagyoska a Hóember Idézése +spell.ebwizardry\:invigorating_presence_festive=Élénkítő Ajándékok +spell.ebwizardry\:empowering_presence_festive=Megerősítő Ajándékok + +wizard.debug=%1$s, %2$s, %3$s diff --git a/src/main/resources/assets/ebwizardry/lang/zh_tw.lang b/src/main/resources/assets/ebwizardry/lang/zh_tw.lang index a8f2b5e9..ab0b14e9 100644 --- a/src/main/resources/assets/ebwizardry/lang/zh_tw.lang +++ b/src/main/resources/assets/ebwizardry/lang/zh_tw.lang @@ -44,8 +44,7 @@ item.ebwizardry\:spell_book.name=法術書 item.ebwizardry\:spell_book.apply_to_wizard=將%1$s的法術 %2$s換成%3$s item.ebwizardry\:arcane_tome.name=法術寶典 -item.ebwizardry\:arcane_tome.desc1=升級%1$s法杖 -item.ebwizardry\:arcane_tome.desc2=成為%1$s法杖 +item.ebwizardry\:arcane_tome.desc=將成長夠的%1$s法杖升級成%2$s法杖 item.ebwizardry\:wizard_handbook.name=法師手冊 item.ebwizardry\:wizard_handbook.desc=作者: %1$s @@ -499,6 +498,13 @@ advancement.ebwizardry\:enchant_scroll.desc=製作一個空白卷軸,並在法 handbook.toast.title=已解鎖新的手冊章節! +gui.ebwizardry\:spell_book.tier=等級: %s +gui.ebwizardry\:spell_book.element=元素: %s +gui.ebwizardry\:spell_book.element_undiscovered=元素: 未知 +gui.ebwizardry\:spell_book.mana_cost=法力消耗: %s +gui.ebwizardry\:spell_book.mana_cost_continuous=法力消耗: %s/秒 +gui.ebwizardry\:spell_book.mana_cost_undiscovered=法力消耗: 未知 + container.ebwizardry\:arcane_workbench=法術工作台 container.ebwizardry\:arcane_workbench.apply=存入 container.ebwizardry\:arcane_workbench.mana=法力\: @@ -837,7 +843,7 @@ spell.ebwizardry\:poison_bomb.desc=朝你所指的方向發射一顆毒液炸彈 spell.ebwizardry\:possession.desc=『變成敵人!』 spell.ebwizardry\:ray_of_purification.desc=射出一道刺眼的陽光,傷害目標並使其暫時失明。不死生物會受到兩倍的傷害且著火。 spell.ebwizardry\:remove_curse.desc=驅除施法者身上所有的詛咒。 -spell.ebwizardry\:replenish_hunger.desc=為施法者補充6點(3支雞腿)的飽食度。 +spell.ebwizardry\:replenish_hunger.desc=為施法者補充3點的飽食度。 spell.ebwizardry\:resurrection.desc=身邊有個治療大師,就算死了...也沒關係。 spell.ebwizardry\:reversal.desc=隨機移除一種負面的藥水效果,將其加諸在目標身上,持續原本剩餘的效果時間。潛能可增加逆轉的效果數量。 spell.ebwizardry\:ring_of_fire.desc=在施法者四周製造一個火焰圈,傷害所有靠近的敵人並使它們著火。 @@ -1117,6 +1123,8 @@ config.ebwizardry.creative_bypasses_arcane_lock.true=任何在創造模式的人 config.ebwizardry.creative_bypasses_arcane_lock.false=只有在創造模式的管理者 config.ebwizardry.slow_time_affects_players=緩時術影響玩家 config.ebwizardry.slow_time_affects_players.tooltip=玩家是否會受到附近其他玩家施展的緩時術影響。如果這個選項關閉的話,只會影響生物及投擲物,而玩家能以正常的速度移動。 +config.ebwizardry.bonemeal_grows_crystal_flowers=骨粉種植水晶花 +config.ebwizardry.bonemeal_grows_crystal_flowers.tooltip=在草地上使用骨粉是否有機會長出水晶花。 config.ebwizardry.mob_loot_table_whitelist=怪物戰利品檔白名單 config.ebwizardry.mob_loot_table_whitelist.tooltip=白名單中列的是用來匯入以增加怪物掉落的戰利品檔(格式請參考 loot_tables/entities/mob_additions.json) 。 法術模組會盡量判斷哪些戰利品檔是屬於敵對生物的,但並不保證一定正確或合適。請加入戰利品檔的位置(非實體ID)到這個清單以手動將它們包含進來。 config.ebwizardry.mob_loot_table_blacklist=怪物戰利品檔黑名單 @@ -1226,6 +1234,10 @@ config.ebwizardry.use_shaders=使用自訂著色器 config.ebwizardry.use_shaders.tooltip=是否對某些法術使用自訂的著色器。這些使用一般的著色系統(像是模組旁觀著色器),在大部分的情況下應該不會對效能產生太大影響,但可能會與其它的著色器發生衝突。 config.ebwizardry.use_shaders.true=是 - 我要這些很讚的著色器! config.ebwizardry.use_shaders.false=不 - 我在龜速的電腦上執行! +config.ebwizardry.screen_shake=畫面震動 +config.ebwizardry.screen_shake.tooltip=是否對某些法術使用畫面震動效果。 +config.ebwizardry.blink_effect=閃爍效果 +config.ebwizardry.blink_effect.tooltip=是否對傳送法術使用畫面閃爍效果。 config.ebwizardry.category.commands=指令設定 config.ebwizardry.category.commands.tooltip=法術模組的指令設定 @@ -1283,8 +1295,13 @@ config.ebwizardry.auto_place_shrine_markers.tooltip=設定法術模組是否在 integration.jei.category.ebwizardry\:arcane_workbench=法術工作台 -integration.antiqueatlas.marker.ebwizardry\:wizard_tower=法師塔 -integration.antiqueatlas.marker.ebwizardry\:obelisk=方尖碑 -integration.antiqueatlas.marker.ebwizardry\:shrine=神殿 +integration.antiqueatlas.marker.ebwizardry.wizard_tower=法師塔 +integration.antiqueatlas.marker.ebwizardry.obelisk=方尖碑 +integration.antiqueatlas.marker.ebwizardry.shrine=神殿 + +spell.ebwizardry\:magic_missile_festive=魔法槲寄生 +spell.ebwizardry\:summon_snow_golem_festive=召喚冰霜雪人 +spell.ebwizardry\:invigorating_presence_festive=力量的贈禮 +spell.ebwizardry\:empowering_presence_festive=魔力的贈禮 wizard.debug=%1$s, %2$s, %3$s diff --git a/src/main/resources/assets/ebwizardry/loot_tables/chests/library_ruins_bookshelf.json b/src/main/resources/assets/ebwizardry/loot_tables/chests/library_ruins_bookshelf.json new file mode 100644 index 00000000..6b95dd04 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/loot_tables/chests/library_ruins_bookshelf.json @@ -0,0 +1,68 @@ +{ + "pools": [ + { + "name": "wizardry", + "conditions": [ + { + "condition": "random_chance", + "chance": 0.6 + } + ], + "rolls": { + "min": 0, + "max": 2 + }, + "entries": [ + { + "type": "loot_table", + "name": "ebwizardry:subsets/arcane_tomes", + "weight": 2 + }, + { + "type": "loot_table", + "name": "ebwizardry:subsets/wand_upgrades", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:spell_book", + "weight": 15, + "functions": [ + { + "function": "ebwizardry:random_spell", + "undiscovered_bias": 0.3 + } + ] + }, + { + "type": "item", + "name": "ebwizardry:ruined_spell_book", + "weight": 5 + }, + { + "type": "item", + "name": "ebwizardry:scroll", + "weight": 4, + "functions": [ + { + "function": "ebwizardry:random_spell", + "undiscovered_bias": 0.3 + }, + { + "function": "set_count", + "count": { + "min": 1, + "max": 5 + } + } + ] + }, + { + "type": "item", + "name": "ebwizardry:identification_scroll", + "weight": 2 + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/loot_tables/entities/evil_wizard.json b/src/main/resources/assets/ebwizardry/loot_tables/entities/evil_wizard.json index e769c78f..70d02ff4 100644 --- a/src/main/resources/assets/ebwizardry/loot_tables/entities/evil_wizard.json +++ b/src/main/resources/assets/ebwizardry/loot_tables/entities/evil_wizard.json @@ -9,12 +9,14 @@ "name": "ebwizardry:magic_crystal", "weight": 1, "functions": [ - { - "function": "set_count", - "count": { - "min": 3, - "max": 5 - }, + { + "function": "set_count", + "count": { + "min": 3, + "max": 5 + } + }, + { "function": "looting_enchant", "count": { "min": 0, 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 fa25f7e6..e70c979b 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 @@ -28,6 +28,11 @@ ] } ] + }, + { + "type": "item", + "name": "ebwizardry:ruined_spell_book", + "weight": 1 } ] } diff --git a/src/main/resources/assets/ebwizardry/loot_tables/entities/remnant/earth.json b/src/main/resources/assets/ebwizardry/loot_tables/entities/remnant/earth.json new file mode 100644 index 00000000..ceee3c72 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/loot_tables/entities/remnant/earth.json @@ -0,0 +1,28 @@ +{ + "pools": [ + { + "name": "spectral_dust", + "rolls": 1, + "entries": [ + { + "type": "item", + "name": "ebwizardry:spectral_dust", + "weight": 1, + "functions": [ + { + "function": "set_count", + "count": { + "min": 0, + "max": 1 + } + }, + { + "function": "minecraft:set_data", + "data": 5 + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/loot_tables/entities/remnant/fire.json b/src/main/resources/assets/ebwizardry/loot_tables/entities/remnant/fire.json new file mode 100644 index 00000000..30f1446d --- /dev/null +++ b/src/main/resources/assets/ebwizardry/loot_tables/entities/remnant/fire.json @@ -0,0 +1,28 @@ +{ + "pools": [ + { + "name": "spectral_dust", + "rolls": 1, + "entries": [ + { + "type": "item", + "name": "ebwizardry:spectral_dust", + "weight": 1, + "functions": [ + { + "function": "set_count", + "count": { + "min": 0, + "max": 1 + } + }, + { + "function": "minecraft:set_data", + "data": 1 + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/loot_tables/entities/remnant/healing.json b/src/main/resources/assets/ebwizardry/loot_tables/entities/remnant/healing.json new file mode 100644 index 00000000..876cb55b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/loot_tables/entities/remnant/healing.json @@ -0,0 +1,28 @@ +{ + "pools": [ + { + "name": "spectral_dust", + "rolls": 1, + "entries": [ + { + "type": "item", + "name": "ebwizardry:spectral_dust", + "weight": 1, + "functions": [ + { + "function": "set_count", + "count": { + "min": 0, + "max": 1 + } + }, + { + "function": "minecraft:set_data", + "data": 7 + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/loot_tables/entities/remnant/ice.json b/src/main/resources/assets/ebwizardry/loot_tables/entities/remnant/ice.json new file mode 100644 index 00000000..61a1d59f --- /dev/null +++ b/src/main/resources/assets/ebwizardry/loot_tables/entities/remnant/ice.json @@ -0,0 +1,28 @@ +{ + "pools": [ + { + "name": "spectral_dust", + "rolls": 1, + "entries": [ + { + "type": "item", + "name": "ebwizardry:spectral_dust", + "weight": 1, + "functions": [ + { + "function": "set_count", + "count": { + "min": 0, + "max": 1 + } + }, + { + "function": "minecraft:set_data", + "data": 2 + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/loot_tables/entities/remnant/lightning.json b/src/main/resources/assets/ebwizardry/loot_tables/entities/remnant/lightning.json new file mode 100644 index 00000000..6a4d5dec --- /dev/null +++ b/src/main/resources/assets/ebwizardry/loot_tables/entities/remnant/lightning.json @@ -0,0 +1,28 @@ +{ + "pools": [ + { + "name": "spectral_dust", + "rolls": 1, + "entries": [ + { + "type": "item", + "name": "ebwizardry:spectral_dust", + "weight": 1, + "functions": [ + { + "function": "set_count", + "count": { + "min": 0, + "max": 1 + } + }, + { + "function": "minecraft:set_data", + "data": 3 + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/loot_tables/entities/remnant/necromancy.json b/src/main/resources/assets/ebwizardry/loot_tables/entities/remnant/necromancy.json new file mode 100644 index 00000000..0064048f --- /dev/null +++ b/src/main/resources/assets/ebwizardry/loot_tables/entities/remnant/necromancy.json @@ -0,0 +1,28 @@ +{ + "pools": [ + { + "name": "spectral_dust", + "rolls": 1, + "entries": [ + { + "type": "item", + "name": "ebwizardry:spectral_dust", + "weight": 1, + "functions": [ + { + "function": "set_count", + "count": { + "min": 0, + "max": 1 + } + }, + { + "function": "minecraft:set_data", + "data": 4 + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/loot_tables/entities/remnant/sorcery.json b/src/main/resources/assets/ebwizardry/loot_tables/entities/remnant/sorcery.json new file mode 100644 index 00000000..8c9f9e8d --- /dev/null +++ b/src/main/resources/assets/ebwizardry/loot_tables/entities/remnant/sorcery.json @@ -0,0 +1,28 @@ +{ + "pools": [ + { + "name": "spectral_dust", + "rolls": 1, + "entries": [ + { + "type": "item", + "name": "ebwizardry:spectral_dust", + "weight": 1, + "functions": [ + { + "function": "set_count", + "count": { + "min": 0, + "max": 1 + } + }, + { + "function": "minecraft:set_data", + "data": 6 + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/loot_tables/gameplay/fishing/junk_additions.json b/src/main/resources/assets/ebwizardry/loot_tables/gameplay/fishing/junk_additions.json new file mode 100644 index 00000000..540d3f77 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/loot_tables/gameplay/fishing/junk_additions.json @@ -0,0 +1,15 @@ +{ + "pools": [ + { + "name": "wizardry", + "rolls": 1, + "entries": [ + { + "type": "item", + "name": "ebwizardry:ruined_spell_book", + "weight": 1 + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/loot_tables/gameplay/fishing/treasure_additions.json b/src/main/resources/assets/ebwizardry/loot_tables/gameplay/fishing/treasure_additions.json new file mode 100644 index 00000000..69ec2346 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/loot_tables/gameplay/fishing/treasure_additions.json @@ -0,0 +1,25 @@ +{ + "pools": [ + { + "name": "wizardry", + "rolls": 1, + "entries": [ + { + "type": "item", + "name": "ebwizardry:spell_book", + "weight": 1, + "functions": [ + { + "function": "ebwizardry:random_spell", + "tiers": [ + "novice", + "apprentice", + "advanced" + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/loot_tables/gameplay/imbuement_altar/ruined_spell_book_earth.json b/src/main/resources/assets/ebwizardry/loot_tables/gameplay/imbuement_altar/ruined_spell_book_earth.json new file mode 100644 index 00000000..8695fd7c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/loot_tables/gameplay/imbuement_altar/ruined_spell_book_earth.json @@ -0,0 +1,42 @@ +{ + "pools": [ + { + "name": "spell_book", + "rolls": 1, + "entries": [ + { + "type": "item", + "name": "ebwizardry:spell_book", + "entryName": "matching_book", + "weight": 3, + "functions": [ + { + "function": "ebwizardry:random_spell", + "elements": [ + "earth" + ], + "undiscovered_bias": 0.3 + } + ] + }, + { + "type": "item", + "name": "ebwizardry:spell_book", + "entryName": "random_book", + "weight": 2, + "functions": [ + { + "function": "ebwizardry:random_spell", + "tiers": [ + "novice", + "apprentice", + "advanced" + ], + "undiscovered_bias": 0.3 + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/loot_tables/gameplay/imbuement_altar/ruined_spell_book_fire.json b/src/main/resources/assets/ebwizardry/loot_tables/gameplay/imbuement_altar/ruined_spell_book_fire.json new file mode 100644 index 00000000..e6903f46 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/loot_tables/gameplay/imbuement_altar/ruined_spell_book_fire.json @@ -0,0 +1,42 @@ +{ + "pools": [ + { + "name": "spell_book", + "rolls": 1, + "entries": [ + { + "type": "item", + "name": "ebwizardry:spell_book", + "entryName": "matching_book", + "weight": 3, + "functions": [ + { + "function": "ebwizardry:random_spell", + "elements": [ + "fire" + ], + "undiscovered_bias": 0.3 + } + ] + }, + { + "type": "item", + "name": "ebwizardry:spell_book", + "entryName": "random_book", + "weight": 2, + "functions": [ + { + "function": "ebwizardry:random_spell", + "tiers": [ + "novice", + "apprentice", + "advanced" + ], + "undiscovered_bias": 0.3 + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/loot_tables/gameplay/imbuement_altar/ruined_spell_book_healing.json b/src/main/resources/assets/ebwizardry/loot_tables/gameplay/imbuement_altar/ruined_spell_book_healing.json new file mode 100644 index 00000000..98ede6e3 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/loot_tables/gameplay/imbuement_altar/ruined_spell_book_healing.json @@ -0,0 +1,42 @@ +{ + "pools": [ + { + "name": "spell_book", + "rolls": 1, + "entries": [ + { + "type": "item", + "name": "ebwizardry:spell_book", + "entryName": "matching_book", + "weight": 3, + "functions": [ + { + "function": "ebwizardry:random_spell", + "elements": [ + "healing" + ], + "undiscovered_bias": 0.3 + } + ] + }, + { + "type": "item", + "name": "ebwizardry:spell_book", + "entryName": "random_book", + "weight": 2, + "functions": [ + { + "function": "ebwizardry:random_spell", + "tiers": [ + "novice", + "apprentice", + "advanced" + ], + "undiscovered_bias": 0.3 + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/loot_tables/gameplay/imbuement_altar/ruined_spell_book_ice.json b/src/main/resources/assets/ebwizardry/loot_tables/gameplay/imbuement_altar/ruined_spell_book_ice.json new file mode 100644 index 00000000..cf12f027 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/loot_tables/gameplay/imbuement_altar/ruined_spell_book_ice.json @@ -0,0 +1,42 @@ +{ + "pools": [ + { + "name": "spell_book", + "rolls": 1, + "entries": [ + { + "type": "item", + "name": "ebwizardry:spell_book", + "entryName": "matching_book", + "weight": 3, + "functions": [ + { + "function": "ebwizardry:random_spell", + "elements": [ + "ice" + ], + "undiscovered_bias": 0.3 + } + ] + }, + { + "type": "item", + "name": "ebwizardry:spell_book", + "entryName": "random_book", + "weight": 2, + "functions": [ + { + "function": "ebwizardry:random_spell", + "tiers": [ + "novice", + "apprentice", + "advanced" + ], + "undiscovered_bias": 0.3 + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/loot_tables/gameplay/imbuement_altar/ruined_spell_book_lightning.json b/src/main/resources/assets/ebwizardry/loot_tables/gameplay/imbuement_altar/ruined_spell_book_lightning.json new file mode 100644 index 00000000..5005c4c3 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/loot_tables/gameplay/imbuement_altar/ruined_spell_book_lightning.json @@ -0,0 +1,42 @@ +{ + "pools": [ + { + "name": "spell_book", + "rolls": 1, + "entries": [ + { + "type": "item", + "name": "ebwizardry:spell_book", + "entryName": "matching_book", + "weight": 3, + "functions": [ + { + "function": "ebwizardry:random_spell", + "elements": [ + "lightning" + ], + "undiscovered_bias": 0.3 + } + ] + }, + { + "type": "item", + "name": "ebwizardry:spell_book", + "entryName": "random_book", + "weight": 2, + "functions": [ + { + "function": "ebwizardry:random_spell", + "tiers": [ + "novice", + "apprentice", + "advanced" + ], + "undiscovered_bias": 0.3 + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/loot_tables/gameplay/imbuement_altar/ruined_spell_book_necromancy.json b/src/main/resources/assets/ebwizardry/loot_tables/gameplay/imbuement_altar/ruined_spell_book_necromancy.json new file mode 100644 index 00000000..74a42510 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/loot_tables/gameplay/imbuement_altar/ruined_spell_book_necromancy.json @@ -0,0 +1,42 @@ +{ + "pools": [ + { + "name": "spell_book", + "rolls": 1, + "entries": [ + { + "type": "item", + "name": "ebwizardry:spell_book", + "entryName": "matching_book", + "weight": 3, + "functions": [ + { + "function": "ebwizardry:random_spell", + "elements": [ + "necromancy" + ], + "undiscovered_bias": 0.3 + } + ] + }, + { + "type": "item", + "name": "ebwizardry:spell_book", + "entryName": "random_book", + "weight": 2, + "functions": [ + { + "function": "ebwizardry:random_spell", + "tiers": [ + "novice", + "apprentice", + "advanced" + ], + "undiscovered_bias": 0.3 + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/loot_tables/gameplay/imbuement_altar/ruined_spell_book_sorcery.json b/src/main/resources/assets/ebwizardry/loot_tables/gameplay/imbuement_altar/ruined_spell_book_sorcery.json new file mode 100644 index 00000000..a96b22e5 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/loot_tables/gameplay/imbuement_altar/ruined_spell_book_sorcery.json @@ -0,0 +1,42 @@ +{ + "pools": [ + { + "name": "spell_book", + "rolls": 1, + "entries": [ + { + "type": "item", + "name": "ebwizardry:spell_book", + "entryName": "matching_book", + "weight": 3, + "functions": [ + { + "function": "ebwizardry:random_spell", + "elements": [ + "sorcery" + ], + "undiscovered_bias": 0.3 + } + ] + }, + { + "type": "item", + "name": "ebwizardry:spell_book", + "entryName": "random_book", + "weight": 2, + "functions": [ + { + "function": "ebwizardry:random_spell", + "tiers": [ + "novice", + "apprentice", + "advanced" + ], + "undiscovered_bias": 0.3 + } + ] + } + ] + } + ] +} \ 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 index dd8cd8cb..d08cde67 100644 --- a/src/main/resources/assets/ebwizardry/loot_tables/subsets/epic_artefacts.json +++ b/src/main/resources/assets/ebwizardry/loot_tables/subsets/epic_artefacts.json @@ -25,6 +25,11 @@ "weight": 1 }, { + "type": "item", + "name": "ebwizardry:ring_soulbinding", + "weight": 1 + }, + { "type": "item", "name": "ebwizardry:ring_mana_return", "weight": 1 @@ -55,11 +60,21 @@ "weight": 1 }, { + "type": "item", + "name": "ebwizardry:amulet_absorption", + "weight": 1 + }, + { "type": "item", "name": "ebwizardry:charm_experience_tome", "weight": 1 }, { + "type": "item", + "name": "ebwizardry:charm_lava_walking", + "weight": 1 + }, + { "type": "item", "name": "ebwizardry:charm_silk_touch", "weight": 1 @@ -68,7 +83,12 @@ "type": "item", "name": "ebwizardry:charm_stop_time", "weight": 1 - } + }, + { + "type": "item", + "name": "ebwizardry:charm_black_hole", + "weight": 1 + } ] } ] 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 index 24a74e24..92a91297 100644 --- a/src/main/resources/assets/ebwizardry/loot_tables/subsets/rare_artefacts.json +++ b/src/main/resources/assets/ebwizardry/loot_tables/subsets/rare_artefacts.json @@ -8,11 +8,6 @@ "type": "item", "name": "ebwizardry:ring_condensing", "weight": 1 - }, - { - "type": "item", - "name": "ebwizardry:ring_battlemage", - "weight": 1 }, { "type": "item", @@ -28,11 +23,6 @@ "type": "item", "name": "ebwizardry:ring_storm", "weight": 1 - }, - { - "type": "item", - "name": "ebwizardry:ring_soulbinding", - "weight": 1 }, { "type": "item", @@ -55,6 +45,11 @@ "weight": 1 }, { + "type": "item", + "name": "ebwizardry:ring_evoker", + "weight": 1 + }, + { "type": "item", "name": "ebwizardry:ring_blockwrangler", "weight": 1 @@ -110,6 +105,11 @@ "weight": 1 }, { + "type": "item", + "name": "ebwizardry:charm_move_speed", + "weight": 1 + }, + { "type": "item", "name": "ebwizardry:charm_auto_smelt", "weight": 1 @@ -121,7 +121,12 @@ }, { "type": "item", - "name": "ebwizardry:charm_minion_variants", + "name": "ebwizardry:charm_undead_helmets", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:charm_hunger_casting", "weight": 1 }, { @@ -138,7 +143,17 @@ "type": "item", "name": "ebwizardry:charm_light", "weight": 1 - } + }, + { + "type": "item", + "name": "ebwizardry:charm_transportation", + "weight": 1 + }, + { + "type": "item", + "name": "ebwizardry:charm_mount_teleporting", + "weight": 1 + } ] } ] 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 index c52abb3d..7715635a 100644 --- a/src/main/resources/assets/ebwizardry/loot_tables/subsets/uncommon_artefacts.json +++ b/src/main/resources/assets/ebwizardry/loot_tables/subsets/uncommon_artefacts.json @@ -10,6 +10,11 @@ "weight": 1 }, { + "type": "item", + "name": "ebwizardry:ring_battlemage", + "weight": 1 + }, + { "type": "item", "name": "ebwizardry:ring_fire_melee", "weight": 1 @@ -95,11 +100,21 @@ "weight": 1 }, { + "type": "item", + "name": "ebwizardry:charm_spell_discovery", + "weight": 1 + }, + { "type": "item", "name": "ebwizardry:charm_minion_health", "weight": 1 }, { + "type": "item", + "name": "ebwizardry:charm_minion_variants", + "weight": 1 + }, + { "type": "item", "name": "ebwizardry:charm_growth", "weight": 1 diff --git a/src/main/resources/assets/ebwizardry/models/block/acacia_bookshelf.json b/src/main/resources/assets/ebwizardry/models/block/acacia_bookshelf.json new file mode 100644 index 00000000..1908bdc0 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/acacia_bookshelf.json @@ -0,0 +1,9 @@ +{ + "parent": "ebwizardry:block/bookshelf", + "textures": { + "particle": "ebwizardry:blocks/acacia_bookshelf_side", + "side": "ebwizardry:blocks/acacia_bookshelf_side", + "inside": "ebwizardry:blocks/acacia_bookshelf_inside", + "top": "ebwizardry:blocks/acacia_bookshelf_top" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/acacia_lectern.json b/src/main/resources/assets/ebwizardry/models/block/acacia_lectern.json new file mode 100644 index 00000000..1f7315c4 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/acacia_lectern.json @@ -0,0 +1,10 @@ +{ + "parent": "ebwizardry:block/lectern", + "textures": { + "particle": "ebwizardry:blocks/acacia_lectern_top", + "base": "ebwizardry:blocks/acacia_lectern_base", + "side": "ebwizardry:blocks/acacia_lectern_side", + "top": "ebwizardry:blocks/acacia_lectern_top", + "underside": "ebwizardry:blocks/acacia_lectern_underside" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/birch_bookshelf.json b/src/main/resources/assets/ebwizardry/models/block/birch_bookshelf.json new file mode 100644 index 00000000..988c9506 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/birch_bookshelf.json @@ -0,0 +1,9 @@ +{ + "parent": "ebwizardry:block/bookshelf", + "textures": { + "particle": "ebwizardry:blocks/birch_bookshelf_side", + "side": "ebwizardry:blocks/birch_bookshelf_side", + "inside": "ebwizardry:blocks/birch_bookshelf_inside", + "top": "ebwizardry:blocks/birch_bookshelf_top" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/birch_lectern.json b/src/main/resources/assets/ebwizardry/models/block/birch_lectern.json new file mode 100644 index 00000000..5706382b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/birch_lectern.json @@ -0,0 +1,10 @@ +{ + "parent": "ebwizardry:block/lectern", + "textures": { + "particle": "ebwizardry:blocks/birch_lectern_top", + "base": "ebwizardry:blocks/birch_lectern_base", + "side": "ebwizardry:blocks/birch_lectern_side", + "top": "ebwizardry:blocks/birch_lectern_top", + "underside": "ebwizardry:blocks/birch_lectern_underside" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/books0.json b/src/main/resources/assets/ebwizardry/models/block/books0.json new file mode 100644 index 00000000..e8a196f5 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/books0.json @@ -0,0 +1,33 @@ +{ + "credit": "Made with Blockbench", + "parent": "block/block", + "textures": { + "books": "ebwizardry:blocks/books" + }, + "elements": [ + { + "name": "book0", + "from": [13, 9, 2], + "to": [15, 14, 6], + "faces": { + "north": {"uv": [0, 0, 2, 5], "texture": "#books"}, + "east": {"uv": [6, 0, 2, 5], "texture": "#books"}, + "south": {"uv": [6, 0, 8, 5], "texture": "#books"}, + "west": {"uv": [2, 0, 6, 5], "texture": "#books"}, + "up": {"uv": [8, 0, 10, 4], "texture": "#books"} + } + }, + { + "name": "book0", + "from": [1, 9, 10], + "to": [3, 14, 14], + "faces": { + "north": {"uv": [6, 0, 8, 5], "texture": "#books"}, + "east": {"uv": [2, 0, 6, 5], "texture": "#books"}, + "south": {"uv": [0, 0, 2, 5], "texture": "#books"}, + "west": {"uv": [6, 0, 2, 5], "texture": "#books"}, + "up": {"uv": [8, 0, 10, 4], "rotation": 180, "texture": "#books"} + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/block/books1.json b/src/main/resources/assets/ebwizardry/models/block/books1.json new file mode 100644 index 00000000..bd5dedf6 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/books1.json @@ -0,0 +1,33 @@ +{ + "credit": "Made with Blockbench", + "parent": "block/block", + "textures": { + "books": "ebwizardry:blocks/books" + }, + "elements": [ + { + "name": "book1", + "from": [11, 9, 2], + "to": [13, 14, 6], + "faces": { + "north": {"uv": [0, 5, 2, 10], "texture": "#books"}, + "east": {"uv": [6, 5, 2, 10], "texture": "#books"}, + "south": {"uv": [6, 5, 8, 10], "texture": "#books"}, + "west": {"uv": [2, 5, 6, 10], "texture": "#books"}, + "up": {"uv": [8, 5, 10, 9], "texture": "#books"} + } + }, + { + "name": "book1", + "from": [3, 9, 10], + "to": [5, 14, 14], + "faces": { + "north": {"uv": [6, 5, 8, 10], "texture": "#books"}, + "east": {"uv": [2, 5, 6, 10], "texture": "#books"}, + "south": {"uv": [0, 5, 2, 10], "texture": "#books"}, + "west": {"uv": [6, 5, 2, 10], "texture": "#books"}, + "up": {"uv": [8, 5, 10, 9], "rotation": 180, "texture": "#books"} + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/block/books10.json b/src/main/resources/assets/ebwizardry/models/block/books10.json new file mode 100644 index 00000000..dc7871f7 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/books10.json @@ -0,0 +1,33 @@ +{ + "credit": "Made with Blockbench", + "parent": "block/block", + "textures": { + "books": "ebwizardry:blocks/books" + }, + "elements": [ + { + "name": "book10", + "from": [4, 1, 2], + "to": [6, 6, 6], + "faces": { + "north": {"uv": [0, 10, 2, 15], "texture": "#books"}, + "east": {"uv": [6, 10, 2, 15], "texture": "#books"}, + "south": {"uv": [6, 10, 8, 15], "texture": "#books"}, + "west": {"uv": [2, 10, 6, 15], "texture": "#books"}, + "up": {"uv": [8, 10, 10, 14], "texture": "#books"} + } + }, + { + "name": "book10", + "from": [10, 1, 10], + "to": [12, 6, 14], + "faces": { + "north": {"uv": [6, 10, 8, 15], "texture": "#books"}, + "east": {"uv": [2, 10, 6, 15], "texture": "#books"}, + "south": {"uv": [0, 10, 2, 15], "texture": "#books"}, + "west": {"uv": [6, 10, 2, 15], "texture": "#books"}, + "up": {"uv": [8, 10, 10, 14], "rotation": 180, "texture": "#books"} + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/block/books11.json b/src/main/resources/assets/ebwizardry/models/block/books11.json new file mode 100644 index 00000000..1597ac01 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/books11.json @@ -0,0 +1,33 @@ +{ + "credit": "Made with Blockbench", + "parent": "block/block", + "textures": { + "books": "ebwizardry:blocks/books" + }, + "elements": [ + { + "name": "book11", + "from": [1, 1, 2], + "to": [3, 6, 6], + "faces": { + "north": {"uv": [0, 0, 2, 5], "texture": "#books"}, + "east": {"uv": [6, 0, 2, 5], "texture": "#books"}, + "south": {"uv": [6, 0, 8, 5], "texture": "#books"}, + "west": {"uv": [2, 0, 6, 5], "texture": "#books"}, + "up": {"uv": [8, 0, 10, 4], "texture": "#books"} + } + }, + { + "name": "book11", + "from": [13, 1, 10], + "to": [15, 6, 14], + "faces": { + "north": {"uv": [6, 0, 8, 5], "texture": "#books"}, + "east": {"uv": [2, 0, 6, 5], "texture": "#books"}, + "south": {"uv": [0, 0, 2, 5], "texture": "#books"}, + "west": {"uv": [6, 0, 2, 5], "texture": "#books"}, + "up": {"uv": [8, 0, 10, 4], "rotation": 180, "texture": "#books"} + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/block/books2.json b/src/main/resources/assets/ebwizardry/models/block/books2.json new file mode 100644 index 00000000..39171d25 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/books2.json @@ -0,0 +1,33 @@ +{ + "credit": "Made with Blockbench", + "parent": "block/block", + "textures": { + "books": "ebwizardry:blocks/books" + }, + "elements": [ + { + "name": "book2", + "from": [8, 9, 2], + "to": [10, 14, 6], + "faces": { + "north": {"uv": [0, 10, 2, 15], "texture": "#books"}, + "east": {"uv": [6, 10, 2, 15], "texture": "#books"}, + "south": {"uv": [6, 10, 8, 15], "texture": "#books"}, + "west": {"uv": [2, 10, 6, 15], "texture": "#books"}, + "up": {"uv": [8, 10, 10, 14], "texture": "#books"} + } + }, + { + "name": "book2", + "from": [6, 9, 10], + "to": [8, 14, 14], + "faces": { + "north": {"uv": [6, 10, 8, 15], "texture": "#books"}, + "east": {"uv": [2, 10, 6, 15], "texture": "#books"}, + "south": {"uv": [0, 10, 2, 15], "texture": "#books"}, + "west": {"uv": [6, 10, 2, 15], "texture": "#books"}, + "up": {"uv": [8, 10, 10, 14], "rotation": 180, "texture": "#books"} + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/block/books3.json b/src/main/resources/assets/ebwizardry/models/block/books3.json new file mode 100644 index 00000000..e9b1be97 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/books3.json @@ -0,0 +1,33 @@ +{ + "credit": "Made with Blockbench", + "parent": "block/block", + "textures": { + "books": "ebwizardry:blocks/books" + }, + "elements": [ + { + "name": "book3", + "from": [5, 9, 2], + "to": [7, 14, 6], + "faces": { + "north": {"uv": [0, 0, 2, 5], "texture": "#books"}, + "east": {"uv": [6, 0, 2, 5], "texture": "#books"}, + "south": {"uv": [6, 0, 8, 5], "texture": "#books"}, + "west": {"uv": [2, 0, 6, 5], "texture": "#books"}, + "up": {"uv": [8, 0, 10, 4], "texture": "#books"} + } + }, + { + "name": "book3", + "from": [9, 9, 10], + "to": [11, 14, 14], + "faces": { + "north": {"uv": [6, 0, 8, 5], "texture": "#books"}, + "east": {"uv": [2, 0, 6, 5], "texture": "#books"}, + "south": {"uv": [0, 0, 2, 5], "texture": "#books"}, + "west": {"uv": [6, 0, 2, 5], "texture": "#books"}, + "up": {"uv": [8, 0, 10, 4], "rotation": 180, "texture": "#books"} + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/block/books4.json b/src/main/resources/assets/ebwizardry/models/block/books4.json new file mode 100644 index 00000000..41187df9 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/books4.json @@ -0,0 +1,33 @@ +{ + "credit": "Made with Blockbench", + "parent": "block/block", + "textures": { + "books": "ebwizardry:blocks/books" + }, + "elements": [ + { + "name": "book4", + "from": [3, 9, 2], + "to": [5, 14, 6], + "faces": { + "north": {"uv": [0, 5, 2, 10], "texture": "#books"}, + "east": {"uv": [6, 5, 2, 10], "texture": "#books"}, + "south": {"uv": [6, 5, 8, 10], "texture": "#books"}, + "west": {"uv": [2, 5, 6, 10], "texture": "#books"}, + "up": {"uv": [8, 5, 10, 9], "texture": "#books"} + } + }, + { + "name": "book4", + "from": [11, 9, 10], + "to": [13, 14, 14], + "faces": { + "north": {"uv": [6, 5, 8, 10], "texture": "#books"}, + "east": {"uv": [2, 5, 6, 10], "texture": "#books"}, + "south": {"uv": [0, 5, 2, 10], "texture": "#books"}, + "west": {"uv": [6, 5, 2, 10], "texture": "#books"}, + "up": {"uv": [8, 5, 10, 9], "rotation": 180, "texture": "#books"} + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/block/books5.json b/src/main/resources/assets/ebwizardry/models/block/books5.json new file mode 100644 index 00000000..62ea95d2 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/books5.json @@ -0,0 +1,33 @@ +{ + "credit": "Made with Blockbench", + "parent": "block/block", + "textures": { + "books": "ebwizardry:blocks/books" + }, + "elements": [ + { + "name": "book5", + "from": [1, 9, 2], + "to": [3, 14, 6], + "faces": { + "north": {"uv": [0, 5, 2, 10], "texture": "#books"}, + "east": {"uv": [6, 5, 2, 10], "texture": "#books"}, + "south": {"uv": [6, 5, 8, 10], "texture": "#books"}, + "west": {"uv": [2, 5, 6, 10], "texture": "#books"}, + "up": {"uv": [8, 5, 10, 9], "texture": "#books"} + } + }, + { + "name": "book5", + "from": [13, 9, 10], + "to": [15, 14, 14], + "faces": { + "north": {"uv": [6, 5, 8, 10], "texture": "#books"}, + "east": {"uv": [2, 5, 6, 10], "texture": "#books"}, + "south": {"uv": [0, 5, 2, 10], "texture": "#books"}, + "west": {"uv": [6, 5, 2, 10], "texture": "#books"}, + "up": {"uv": [8, 5, 10, 9], "rotation": 180, "texture": "#books"} + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/block/books6.json b/src/main/resources/assets/ebwizardry/models/block/books6.json new file mode 100644 index 00000000..255f5174 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/books6.json @@ -0,0 +1,33 @@ +{ + "credit": "Made with Blockbench", + "parent": "block/block", + "textures": { + "books": "ebwizardry:blocks/books" + }, + "elements": [ + { + "name": "book6", + "from": [13, 1, 2], + "to": [15, 6, 6], + "faces": { + "north": {"uv": [0, 10, 2, 15], "texture": "#books"}, + "east": {"uv": [6, 10, 2, 15], "texture": "#books"}, + "south": {"uv": [6, 10, 8, 15], "texture": "#books"}, + "west": {"uv": [2, 10, 6, 15], "texture": "#books"}, + "up": {"uv": [8, 10, 10, 14], "texture": "#books"} + } + }, + { + "name": "book6", + "from": [1, 1, 10], + "to": [3, 6, 14], + "faces": { + "north": {"uv": [6, 10, 8, 15], "texture": "#books"}, + "east": {"uv": [2, 10, 6, 15], "texture": "#books"}, + "south": {"uv": [0, 10, 2, 15], "texture": "#books"}, + "west": {"uv": [6, 10, 2, 15], "texture": "#books"}, + "up": {"uv": [8, 10, 10, 14], "rotation": 180, "texture": "#books"} + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/block/books7.json b/src/main/resources/assets/ebwizardry/models/block/books7.json new file mode 100644 index 00000000..6a3fb74f --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/books7.json @@ -0,0 +1,33 @@ +{ + "credit": "Made with Blockbench", + "parent": "block/block", + "textures": { + "books": "ebwizardry:blocks/books" + }, + "elements": [ + { + "name": "book7", + "from": [11, 1, 2], + "to": [13, 6, 6], + "faces": { + "north": {"uv": [0, 5, 2, 10], "texture": "#books"}, + "east": {"uv": [6, 5, 2, 10], "texture": "#books"}, + "south": {"uv": [6, 5, 8, 10], "texture": "#books"}, + "west": {"uv": [2, 5, 6, 10], "texture": "#books"}, + "up": {"uv": [8, 5, 10, 9], "texture": "#books"} + } + }, + { + "name": "book7", + "from": [3, 1, 10], + "to": [5, 6, 14], + "faces": { + "north": {"uv": [6, 5, 8, 10], "texture": "#books"}, + "east": {"uv": [2, 5, 6, 10], "texture": "#books"}, + "south": {"uv": [0, 5, 2, 10], "texture": "#books"}, + "west": {"uv": [6, 5, 2, 10], "texture": "#books"}, + "up": {"uv": [8, 5, 10, 9], "rotation": 180, "texture": "#books"} + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/block/books8.json b/src/main/resources/assets/ebwizardry/models/block/books8.json new file mode 100644 index 00000000..0e3c5797 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/books8.json @@ -0,0 +1,33 @@ +{ + "credit": "Made with Blockbench", + "parent": "block/block", + "textures": { + "books": "ebwizardry:blocks/books" + }, + "elements": [ + { + "name": "book8", + "from": [9, 1, 2], + "to": [11, 6, 6], + "faces": { + "north": {"uv": [0, 5, 2, 10], "texture": "#books"}, + "east": {"uv": [6, 5, 2, 10], "texture": "#books"}, + "south": {"uv": [6, 5, 8, 10], "texture": "#books"}, + "west": {"uv": [2, 5, 6, 10], "texture": "#books"}, + "up": {"uv": [8, 5, 10, 9], "texture": "#books"} + } + }, + { + "name": "book8", + "from": [5, 1, 10], + "to": [7, 6, 14], + "faces": { + "north": {"uv": [6, 5, 8, 10], "texture": "#books"}, + "east": {"uv": [2, 5, 6, 10], "texture": "#books"}, + "south": {"uv": [0, 5, 2, 10], "texture": "#books"}, + "west": {"uv": [6, 5, 2, 10], "texture": "#books"}, + "up": {"uv": [8, 5, 10, 9], "rotation": 180, "texture": "#books"} + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/block/books9.json b/src/main/resources/assets/ebwizardry/models/block/books9.json new file mode 100644 index 00000000..2b098b7a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/books9.json @@ -0,0 +1,33 @@ +{ + "credit": "Made with Blockbench", + "parent": "block/block", + "textures": { + "books": "ebwizardry:blocks/books" + }, + "elements": [ + { + "name": "book9", + "from": [6, 1, 2], + "to": [8, 6, 6], + "faces": { + "north": {"uv": [0, 0, 2, 5], "texture": "#books"}, + "east": {"uv": [6, 0, 2, 5], "texture": "#books"}, + "south": {"uv": [6, 0, 8, 5], "texture": "#books"}, + "west": {"uv": [2, 0, 6, 5], "texture": "#books"}, + "up": {"uv": [8, 0, 10, 4], "texture": "#books"} + } + }, + { + "name": "book9", + "from": [8, 1, 10], + "to": [10, 6, 14], + "faces": { + "north": {"uv": [6, 0, 8, 5], "texture": "#books"}, + "east": {"uv": [2, 0, 6, 5], "texture": "#books"}, + "south": {"uv": [0, 0, 2, 5], "texture": "#books"}, + "west": {"uv": [6, 0, 2, 5], "texture": "#books"}, + "up": {"uv": [8, 0, 10, 4], "rotation": 180, "texture": "#books"} + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/block/bookshelf.json b/src/main/resources/assets/ebwizardry/models/block/bookshelf.json new file mode 100644 index 00000000..cccd9182 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/bookshelf.json @@ -0,0 +1,285 @@ +{ + "credit": "Made with Blockbench", + "parent": "block/block", + "elements": [ + { + "name": "backboard", + "from": [1, 1, 7], + "to": [15, 15, 9], + "faces": { + "north": {"uv": [1, 1, 15, 15], "texture": "#inside"}, + "south": {"uv": [1, 1, 15, 15], "texture": "#inside"} + } + }, + { + "name": "base", + "from": [0, 0, 0], + "to": [16, 1, 16], + "faces": { + "north": {"uv": [0, 15, 16, 16], "texture": "#side", "cullface": "north"}, + "east": {"uv": [0, 15, 16, 16], "texture": "#side", "cullface": "east"}, + "south": {"uv": [0, 15, 16, 16], "texture": "#side", "cullface": "south"}, + "west": {"uv": [0, 15, 16, 16], "texture": "#side", "cullface": "west"}, + "up": {"uv": [0, 0, 16, 16], "texture": "#inside"}, + "down": {"uv": [0, 0, 16, 16], "texture": "#top", "cullface": "down"} + } + }, + { + "name": "side_west", + "from": [0, 1, 0], + "to": [1, 15, 16], + "faces": { + "north": {"uv": [15, 1, 16, 15], "texture": "#side", "cullface": "north"}, + "east": {"uv": [0, 1, 16, 15], "texture": "#inside"}, + "south": {"uv": [0, 1, 1, 15], "texture": "#side", "cullface": "south"}, + "west": {"uv": [0, 1, 16, 15], "texture": "#side", "cullface": "west"} + } + }, + { + "name": "side_east", + "from": [15, 1, 0], + "to": [16, 15, 16], + "faces": { + "north": {"uv": [0, 1, 1, 15], "texture": "#side", "cullface": "north"}, + "east": {"uv": [0, 1, 16, 15], "texture": "#side", "cullface": "east"}, + "south": {"uv": [15, 1, 16, 15], "texture": "#side", "cullface": "south"}, + "west": {"uv": [0, 1, 16, 15], "texture": "#inside"} + } + }, + { + "name": "top", + "from": [0, 15, 0], + "to": [16, 16, 16], + "faces": { + "north": {"uv": [0, 0, 16, 1], "texture": "#side", "cullface": "north"}, + "east": {"uv": [0, 0, 16, 1], "texture": "#side", "cullface": "east"}, + "south": {"uv": [0, 0, 16, 1], "texture": "#side", "cullface": "south"}, + "west": {"uv": [0, 0, 16, 1], "texture": "#side", "cullface": "west"}, + "up": {"uv": [0, 0, 16, 16], "texture": "#top", "cullface": "up"}, + "down": {"uv": [0, 0, 16, 16], "texture": "#inside"} + } + }, + { + "name": "shelf", + "from": [1, 7, 0], + "to": [15, 9, 16], + "faces": { + "north": {"uv": [1, 7, 15, 9], "texture": "#side", "cullface": "north"}, + "south": {"uv": [1, 7, 15, 9], "texture": "#side", "cullface": "south"}, + "up": {"uv": [1, 0, 15, 16], "texture": "#inside"}, + "down": {"uv": [1, 0, 15, 16], "texture": "#inside"} + } + }, + { + "name": "cornerUNET", + "from": [14, 14, 0], + "to": [15, 15, 1], + "faces": { + "north": {"uv": [1, 1, 2, 2], "texture": "#side", "cullface": "north"}, + "south": {"uv": [1, 1, 2, 2], "texture": "#side"}, + "west": {"uv": [1, 1, 2, 2], "texture": "#side"}, + "down": {"uv": [1, 1, 2, 2], "texture": "#side"} + } + }, + { + "name": "cornerUSWT", + "from": [1, 14, 15], + "to": [2, 15, 16], + "faces": { + "north": {"uv": [1, 1, 2, 2], "texture": "#side"}, + "east": {"uv": [1, 1, 2, 2], "texture": "#side"}, + "south": {"uv": [1, 1, 2, 2], "texture": "#side", "cullface": "south"}, + "down": {"uv": [1, 1, 2, 2], "rotation": 180, "texture": "#side"} + } + }, + { + "name": "cornerLNET", + "from": [14, 6, 0], + "to": [15, 7, 1], + "rotation": {"angle": 0, "axis": "y", "origin": [8, 0, 8]}, + "faces": { + "north": {"uv": [1, 9, 2, 10], "texture": "#side", "cullface": "north"}, + "south": {"uv": [1, 9, 2, 10], "texture": "#side"}, + "west": {"uv": [1, 9, 2, 10], "texture": "#side"}, + "down": {"uv": [1, 9, 2, 10], "texture": "#side"} + } + }, + { + "name": "cornerLSWT", + "from": [1, 6, 15], + "to": [2, 7, 16], + "faces": { + "north": {"uv": [1, 9, 2, 10], "texture": "#side"}, + "east": {"uv": [1, 9, 2, 10], "texture": "#side"}, + "south": {"uv": [1, 9, 2, 10], "texture": "#side", "cullface": "south"}, + "down": {"uv": [1, 9, 2, 10], "rotation": 180, "texture": "#side"} + } + }, + { + "name": "cornerUNWT", + "from": [1, 14, 0], + "to": [2, 15, 1], + "faces": { + "north": {"uv": [14, 1, 15, 2], "texture": "#side", "cullface": "north"}, + "east": {"uv": [14, 1, 15, 2], "texture": "#side"}, + "south": {"uv": [14, 1, 15, 2], "texture": "#side"}, + "down": {"uv": [14, 1, 15, 2], "texture": "#side"} + } + }, + { + "name": "cornerUSET", + "from": [14, 14, 15], + "to": [15, 15, 16], + "faces": { + "north": {"uv": [14, 1, 15, 2], "texture": "#side"}, + "south": {"uv": [14, 1, 15, 2], "texture": "#side", "cullface": "south"}, + "west": {"uv": [14, 1, 15, 2], "texture": "#side"}, + "down": {"uv": [14, 1, 15, 2], "rotation": 180, "texture": "#side"} + } + }, + { + "name": "cornerLNWT", + "from": [1, 6, 0], + "to": [2, 7, 1], + "rotation": {"angle": 0, "axis": "y", "origin": [8, 0, 8]}, + "faces": { + "north": {"uv": [14, 9, 15, 10], "texture": "#side", "cullface": "north"}, + "east": {"uv": [14, 9, 15, 10], "texture": "#side"}, + "south": {"uv": [14, 9, 15, 10], "texture": "#side"}, + "down": {"uv": [14, 9, 15, 10], "texture": "#side"} + } + }, + { + "name": "cornerLSET", + "from": [14, 6, 15], + "to": [15, 7, 16], + "faces": { + "north": {"uv": [14, 9, 15, 10], "texture": "#side"}, + "south": {"uv": [14, 9, 15, 10], "texture": "#side", "cullface": "south"}, + "west": {"uv": [14, 9, 15, 10], "texture": "#side"}, + "down": {"uv": [14, 9, 15, 10], "rotation": 180, "texture": "#side"} + } + }, + { + "name": "cornerUNEB", + "from": [14, 9, 0], + "to": [15, 10, 1], + "faces": { + "north": {"uv": [1, 6, 2, 7], "texture": "#side", "cullface": "north"}, + "south": {"uv": [1, 6, 2, 7], "texture": "#side"}, + "west": {"uv": [1, 6, 2, 7], "texture": "#side"}, + "up": {"uv": [1, 6, 2, 7], "texture": "#side"} + } + }, + { + "name": "cornerUSWB", + "from": [1, 9, 15], + "to": [2, 10, 16], + "faces": { + "north": {"uv": [1, 6, 2, 7], "texture": "#side"}, + "east": {"uv": [1, 6, 2, 7], "texture": "#side"}, + "south": {"uv": [1, 6, 2, 7], "texture": "#side", "cullface": "south"}, + "up": {"uv": [1, 6, 2, 7], "rotation": 180, "texture": "#side"} + } + }, + { + "name": "cornerLNEB", + "from": [14, 1, 0], + "to": [15, 2, 1], + "rotation": {"angle": 0, "axis": "y", "origin": [8, 0, 8]}, + "faces": { + "north": {"uv": [1, 14, 2, 15], "texture": "#side", "cullface": "north"}, + "south": {"uv": [1, 14, 2, 15], "texture": "#side"}, + "west": {"uv": [1, 14, 2, 15], "texture": "#side"}, + "up": {"uv": [1, 14, 2, 15], "texture": "#side"} + } + }, + { + "name": "cornerLSWB", + "from": [1, 1, 15], + "to": [2, 2, 16], + "faces": { + "north": {"uv": [1, 14, 2, 15], "texture": "#side"}, + "east": {"uv": [1, 14, 2, 15], "texture": "#side"}, + "south": {"uv": [1, 14, 2, 15], "texture": "#side", "cullface": "south"}, + "up": {"uv": [1, 14, 2, 15], "rotation": 180, "texture": "#side"} + } + }, + { + "name": "cornerUNWB", + "from": [1, 9, 0], + "to": [2, 10, 1], + "faces": { + "north": {"uv": [14, 6, 15, 7], "texture": "#side", "cullface": "north"}, + "east": {"uv": [14, 6, 15, 7], "texture": "#side"}, + "south": {"uv": [14, 6, 15, 7], "texture": "#side"}, + "up": {"uv": [14, 6, 15, 7], "texture": "#side"} + } + }, + { + "name": "cornerUSEB", + "from": [14, 9, 15], + "to": [15, 10, 16], + "faces": { + "north": {"uv": [14, 6, 15, 7], "texture": "#side"}, + "south": {"uv": [14, 6, 15, 7], "texture": "#side", "cullface": "south"}, + "west": {"uv": [14, 6, 15, 7], "texture": "#side"}, + "up": {"uv": [14, 6, 15, 7], "rotation": 180, "texture": "#side"} + } + }, + { + "name": "cornerLNWB", + "from": [1, 1, 0], + "to": [2, 2, 1], + "rotation": {"angle": 0, "axis": "y", "origin": [8, 0, 8]}, + "faces": { + "north": {"uv": [14, 14, 15, 15], "texture": "#side", "cullface": "north"}, + "east": {"uv": [14, 14, 15, 15], "texture": "#side"}, + "south": {"uv": [14, 14, 15, 15], "texture": "#side"}, + "up": {"uv": [14, 14, 15, 15], "texture": "#side"} + } + }, + { + "name": "cornerLSEB", + "from": [14, 1, 15], + "to": [15, 2, 16], + "faces": { + "north": {"uv": [14, 14, 15, 15], "texture": "#side"}, + "south": {"uv": [14, 14, 15, 15], "texture": "#side", "cullface": "south"}, + "west": {"uv": [14, 14, 15, 15], "texture": "#side"}, + "up": {"uv": [14, 14, 15, 15], "rotation": 180, "texture": "#side"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 225, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, 0], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "scale": [0.625, 0.625, 0.625] + }, + "fixed": { + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/block/crystal_flower_pot.json b/src/main/resources/assets/ebwizardry/models/block/crystal_flower_pot.json new file mode 100644 index 00000000..cde19ace --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/crystal_flower_pot.json @@ -0,0 +1,6 @@ +{ + "parent": "block/flower_pot_cross", + "textures": { + "plant": "ebwizardry:blocks/crystal_flower" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/block/dark_oak_bookshelf.json b/src/main/resources/assets/ebwizardry/models/block/dark_oak_bookshelf.json new file mode 100644 index 00000000..eccb39de --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/dark_oak_bookshelf.json @@ -0,0 +1,9 @@ +{ + "parent": "ebwizardry:block/bookshelf", + "textures": { + "particle": "ebwizardry:blocks/dark_oak_bookshelf_side", + "side": "ebwizardry:blocks/dark_oak_bookshelf_side", + "inside": "ebwizardry:blocks/dark_oak_bookshelf_inside", + "top": "ebwizardry:blocks/dark_oak_bookshelf_top" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/dark_oak_lectern.json b/src/main/resources/assets/ebwizardry/models/block/dark_oak_lectern.json new file mode 100644 index 00000000..fa7eb635 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/dark_oak_lectern.json @@ -0,0 +1,10 @@ +{ + "parent": "ebwizardry:block/lectern", + "textures": { + "particle": "ebwizardry:blocks/dark_oak_lectern_top", + "base": "ebwizardry:blocks/dark_oak_lectern_base", + "side": "ebwizardry:blocks/dark_oak_lectern_side", + "top": "ebwizardry:blocks/dark_oak_lectern_top", + "underside": "ebwizardry:blocks/dark_oak_lectern_underside" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/gilded_acacia_wood.json b/src/main/resources/assets/ebwizardry/models/block/gilded_acacia_wood.json new file mode 100644 index 00000000..4574dea5 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/gilded_acacia_wood.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "ebwizardry:blocks/gilded_acacia_wood" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/gilded_birch_wood.json b/src/main/resources/assets/ebwizardry/models/block/gilded_birch_wood.json new file mode 100644 index 00000000..f3369511 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/gilded_birch_wood.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "ebwizardry:blocks/gilded_birch_wood" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/gilded_dark_oak_wood.json b/src/main/resources/assets/ebwizardry/models/block/gilded_dark_oak_wood.json new file mode 100644 index 00000000..08934ce4 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/gilded_dark_oak_wood.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "ebwizardry:blocks/gilded_dark_oak_wood" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/gilded_jungle_wood.json b/src/main/resources/assets/ebwizardry/models/block/gilded_jungle_wood.json new file mode 100644 index 00000000..6b7e319c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/gilded_jungle_wood.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "ebwizardry:blocks/gilded_jungle_wood" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/gilded_oak_wood.json b/src/main/resources/assets/ebwizardry/models/block/gilded_oak_wood.json new file mode 100644 index 00000000..59b4e00f --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/gilded_oak_wood.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "ebwizardry:blocks/gilded_oak_wood" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/gilded_spruce_wood.json b/src/main/resources/assets/ebwizardry/models/block/gilded_spruce_wood.json new file mode 100644 index 00000000..5d3d72ec --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/gilded_spruce_wood.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "ebwizardry:blocks/gilded_spruce_wood" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/imbuement_altar.json b/src/main/resources/assets/ebwizardry/models/block/imbuement_altar.json new file mode 100644 index 00000000..e5bb2841 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/imbuement_altar.json @@ -0,0 +1,36 @@ +{ + "parent": "block/block", + "textures": { + "particle": "ebwizardry:blocks/imbuement_altar_bottom", + "bottom": "ebwizardry:blocks/imbuement_altar_bottom", + "top": "ebwizardry:blocks/imbuement_altar_top", + "side": "ebwizardry:blocks/imbuement_altar_side", + "top_overlay": "ebwizardry:blocks/imbuement_altar_top_overlay", + "side_overlay": "ebwizardry:blocks/imbuement_altar_side_overlay" + }, + "elements": [ + { + "from": [ 0, 0, 0 ], + "to": [ 16, 12, 16 ], + "faces": { + "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#bottom", "cullface": "down" }, + "up": { "uv": [ 0, 0, 16, 16 ], "texture": "#top" }, + "north": { "uv": [ 0, 4, 16, 16 ], "texture": "#side", "cullface": "north" }, + "south": { "uv": [ 0, 4, 16, 16 ], "texture": "#side", "cullface": "south" }, + "west": { "uv": [ 0, 4, 16, 16 ], "texture": "#side", "cullface": "west" }, + "east": { "uv": [ 0, 4, 16, 16 ], "texture": "#side", "cullface": "east" } + } + }, + { + "from": [ 0, 0, 0 ], + "to": [ 16, 12, 16 ], + "faces": { + "up": { "uv": [ 0, 0, 16, 16 ], "texture": "#top_overlay" }, + "north": { "uv": [ 0, 4, 16, 16 ], "texture": "#side_overlay", "cullface": "north" }, + "south": { "uv": [ 0, 4, 16, 16 ], "texture": "#side_overlay", "cullface": "south" }, + "west": { "uv": [ 0, 4, 16, 16 ], "texture": "#side_overlay", "cullface": "west" }, + "east": { "uv": [ 0, 4, 16, 16 ], "texture": "#side_overlay", "cullface": "east" } + } + } + ] +} diff --git a/src/main/resources/assets/ebwizardry/models/block/imbuement_altar_inactive.json b/src/main/resources/assets/ebwizardry/models/block/imbuement_altar_inactive.json new file mode 100644 index 00000000..85009bfa --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/imbuement_altar_inactive.json @@ -0,0 +1,22 @@ +{ + "parent": "block/block", + "textures": { + "particle": "ebwizardry:blocks/imbuement_altar_bottom", + "bottom": "ebwizardry:blocks/imbuement_altar_bottom", + "top": "ebwizardry:blocks/imbuement_altar_top", + "side": "ebwizardry:blocks/imbuement_altar_side" + }, + "elements": [ + { "from": [ 0, 0, 0 ], + "to": [ 16, 12, 16 ], + "faces": { + "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#bottom", "cullface": "down" }, + "up": { "uv": [ 0, 0, 16, 16 ], "texture": "#top" }, + "north": { "uv": [ 0, 4, 16, 16 ], "texture": "#side", "cullface": "north" }, + "south": { "uv": [ 0, 4, 16, 16 ], "texture": "#side", "cullface": "south" }, + "west": { "uv": [ 0, 4, 16, 16 ], "texture": "#side", "cullface": "west" }, + "east": { "uv": [ 0, 4, 16, 16 ], "texture": "#side", "cullface": "east" } + } + } + ] +} diff --git a/src/main/resources/assets/ebwizardry/models/block/jungle_bookshelf.json b/src/main/resources/assets/ebwizardry/models/block/jungle_bookshelf.json new file mode 100644 index 00000000..53f5efa7 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/jungle_bookshelf.json @@ -0,0 +1,9 @@ +{ + "parent": "ebwizardry:block/bookshelf", + "textures": { + "particle": "ebwizardry:blocks/jungle_bookshelf_side", + "side": "ebwizardry:blocks/jungle_bookshelf_side", + "inside": "ebwizardry:blocks/jungle_bookshelf_inside", + "top": "ebwizardry:blocks/jungle_bookshelf_top" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/jungle_lectern.json b/src/main/resources/assets/ebwizardry/models/block/jungle_lectern.json new file mode 100644 index 00000000..0b560139 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/jungle_lectern.json @@ -0,0 +1,10 @@ +{ + "parent": "ebwizardry:block/lectern", + "textures": { + "particle": "ebwizardry:blocks/jungle_lectern_top", + "base": "ebwizardry:blocks/jungle_lectern_base", + "side": "ebwizardry:blocks/jungle_lectern_side", + "top": "ebwizardry:blocks/jungle_lectern_top", + "underside": "ebwizardry:blocks/jungle_lectern_underside" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/lectern.json b/src/main/resources/assets/ebwizardry/models/block/lectern.json new file mode 100644 index 00000000..a8084ad5 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/lectern.json @@ -0,0 +1,116 @@ +{ + "credit": "Made with Blockbench", + "parent": "block/block", + "elements": [ + { + "from": [2, 0, 2], + "to": [14, 1, 14], + "faces": { + "north": {"uv": [2, 15, 14, 16], "texture": "#side"}, + "east": {"uv": [2, 15, 14, 16], "texture": "#side"}, + "south": {"uv": [2, 15, 14, 16], "texture": "#side"}, + "west": {"uv": [2, 15, 14, 16], "texture": "#side"}, + "up": {"uv": [2, 2, 14, 14], "texture": "#base"}, + "down": {"uv": [2, 1, 14, 13], "texture": "#underside", "cullface": "down"} + } + }, + { + "from": [3, 1, 3], + "to": [13, 2, 13], + "faces": { + "north": {"uv": [3, 14, 13, 15], "texture": "#side"}, + "east": {"uv": [3, 14, 13, 15], "texture": "#side"}, + "south": {"uv": [3, 14, 13, 15], "texture": "#side"}, + "west": {"uv": [3, 14, 13, 15], "texture": "#side"}, + "up": {"uv": [3, 3, 13, 13], "texture": "#base"} + } + }, + { + "from": [4, 13, 4], + "to": [12, 15, 12], + "faces": { + "north": {"uv": [4, 1, 12, 3], "texture": "#side"}, + "east": {"uv": [4, 1, 12, 3], "texture": "#side"}, + "south": {"uv": [4, 1, 12, 3], "texture": "#side"}, + "west": {"uv": [4, 1, 12, 3], "texture": "#side"}, + "down": {"uv": [4, 3, 12, 11], "texture": "#underside"} + } + }, + { + "from": [5, 2, 5], + "to": [11, 13, 11], + "faces": { + "north": {"uv": [5, 3, 11, 14], "texture": "#side"}, + "east": {"uv": [5, 3, 11, 14], "texture": "#side"}, + "south": {"uv": [5, 3, 11, 14], "texture": "#side"}, + "west": {"uv": [5, 3, 11, 14], "texture": "#side"} + } + }, + { + "from": [0, 14.07612, 3.61732], + "to": [16, 16.07612, 17.61732], + "rotation": {"angle": -22.5, "axis": "x", "origin": [8, 8, 8]}, + "faces": { + "north": {"uv": [0, 14, 16, 16], "texture": "#top"}, + "east": {"uv": [1, 14, 15, 16], "texture": "#underside"}, + "south": {"uv": [0, 14, 16, 16], "texture": "#top"}, + "west": {"uv": [1, 14, 15, 16], "texture": "#underside"}, + "up": {"uv": [0, 0, 16, 14], "rotation": 180, "texture": "#top"}, + "down": {"uv": [0, 0, 16, 14], "texture": "#underside"} + } + }, + { + "from": [1, 16.07612, 3.61732], + "to": [15, 17.07612, 4.61732], + "rotation": {"angle": -22.5, "axis": "x", "origin": [8, 8, 8]}, + "faces": { + "north": {"uv": [1, 13, 15, 14], "texture": "#top"}, + "east": {"uv": [1, 13, 2, 14], "texture": "#top"}, + "south": {"uv": [1, 13, 15, 14], "rotation": 180, "texture": "#top"}, + "west": {"uv": [14, 13, 15, 14], "texture": "#top"}, + "up": {"uv": [1, 13, 15, 14], "rotation": 180, "texture": "#top"} + } + }, + { + "from": [4, 15, 9], + "to": [12, 17, 12], + "faces": { + "east": {"uv": [9, 0, 12, 2], "rotation": 180, "texture": "#side"}, + "south": {"uv": [4, 0, 12, 2], "rotation": 180, "texture": "#side"}, + "west": {"uv": [4, 0, 7, 2], "rotation": 180, "texture": "#side"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 135, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 135, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, 0], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "translation": [0, -1, 0], + "scale": [0.625, 0.625, 0.625] + }, + "fixed": { + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/block/oak_bookshelf.json b/src/main/resources/assets/ebwizardry/models/block/oak_bookshelf.json new file mode 100644 index 00000000..36cc798d --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/oak_bookshelf.json @@ -0,0 +1,9 @@ +{ + "parent": "ebwizardry:block/bookshelf", + "textures": { + "particle": "ebwizardry:blocks/oak_bookshelf_side", + "side": "ebwizardry:blocks/oak_bookshelf_side", + "inside": "ebwizardry:blocks/oak_bookshelf_inside", + "top": "ebwizardry:blocks/oak_bookshelf_top" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/oak_lectern.json b/src/main/resources/assets/ebwizardry/models/block/oak_lectern.json new file mode 100644 index 00000000..bbd75a37 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/oak_lectern.json @@ -0,0 +1,10 @@ +{ + "parent": "ebwizardry:block/lectern", + "textures": { + "particle": "ebwizardry:blocks/oak_lectern_top", + "base": "ebwizardry:blocks/oak_lectern_base", + "side": "ebwizardry:blocks/oak_lectern_side", + "top": "ebwizardry:blocks/oak_lectern_top", + "underside": "ebwizardry:blocks/oak_lectern_underside" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/permafrost_0.json b/src/main/resources/assets/ebwizardry/models/block/permafrost_0.json new file mode 100644 index 00000000..c400d5d1 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/permafrost_0.json @@ -0,0 +1,20 @@ +{ + "parent": "block/thin_block", + "textures": { + "particle": "blocks/frosted_ice_0", + "texture": "blocks/frosted_ice_0" + }, + "elements": [ + { "from": [ 0, 0, 0 ], + "to": [ 16, 1, 16 ], + "faces": { + "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#texture", "cullface": "down" }, + "up": { "uv": [ 0, 0, 16, 16 ], "texture": "#texture" }, + "north": { "uv": [ 0, 15, 16, 16 ], "texture": "#texture", "cullface": "north" }, + "south": { "uv": [ 0, 15, 16, 16 ], "texture": "#texture", "cullface": "south" }, + "west": { "uv": [ 0, 15, 16, 16 ], "texture": "#texture", "cullface": "west" }, + "east": { "uv": [ 0, 15, 16, 16 ], "texture": "#texture", "cullface": "east" } + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/block/permafrost_1.json b/src/main/resources/assets/ebwizardry/models/block/permafrost_1.json new file mode 100644 index 00000000..9f15c160 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/permafrost_1.json @@ -0,0 +1,7 @@ +{ + "parent": "ebwizardry:block/permafrost_0", + "textures": { + "particle": "blocks/frosted_ice_1", + "texture": "blocks/frosted_ice_1" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/block/permafrost_2.json b/src/main/resources/assets/ebwizardry/models/block/permafrost_2.json new file mode 100644 index 00000000..06ddcdb0 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/permafrost_2.json @@ -0,0 +1,7 @@ +{ + "parent": "ebwizardry:block/permafrost_0", + "textures": { + "particle": "blocks/frosted_ice_2", + "texture": "blocks/frosted_ice_2" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/block/permafrost_3.json b/src/main/resources/assets/ebwizardry/models/block/permafrost_3.json new file mode 100644 index 00000000..c57a8c4c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/permafrost_3.json @@ -0,0 +1,7 @@ +{ + "parent": "ebwizardry:block/permafrost_0", + "textures": { + "particle": "blocks/frosted_ice_3", + "texture": "blocks/frosted_ice_3" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/block/receptacle.json b/src/main/resources/assets/ebwizardry/models/block/receptacle.json new file mode 100644 index 00000000..407c7cc1 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/receptacle.json @@ -0,0 +1,268 @@ +{ + "credit": "Made with Blockbench", + "parent": "block/block", + "textures": { + "main": "ebwizardry:blocks/receptacle", + "particle": "ebwizardry:blocks/receptacle" + }, + "elements": [ + { + "from": [6, 6.5, 10], + "to": [10, 7.5, 11], + "rotation": {"angle": 0, "axis": "y", "origin": [10, 6.5, 12]}, + "faces": { + "north": {"uv": [1, 5, 5, 6], "texture": "#main"}, + "east": {"uv": [4, 5, 5, 6], "texture": "#main"}, + "south": {"uv": [1, 5, 5, 6], "texture": "#main"}, + "west": {"uv": [1, 5, 2, 6], "texture": "#main"}, + "up": {"uv": [1, 5, 5, 6], "texture": "#main"}, + "down": {"uv": [1, 5, 5, 6], "texture": "#main"} + } + }, + { + "from": [6, 5.5, 6], + "to": [10, 6.5, 10], + "rotation": {"angle": 0, "axis": "y", "origin": [8, 6.5, 8]}, + "faces": { + "north": {"uv": [1, 7, 5, 8], "texture": "#main"}, + "east": {"uv": [1, 7, 5, 8], "texture": "#main"}, + "south": {"uv": [1, 7, 5, 8], "texture": "#main"}, + "west": {"uv": [1, 7, 5, 8], "texture": "#main"}, + "up": {"uv": [1, 1, 5, 5], "texture": "#main"}, + "down": {"uv": [1, 1, 5, 5], "texture": "#main"} + } + }, + { + "from": [5, 6.5, 6], + "to": [6, 7.5, 10], + "rotation": {"angle": 0, "axis": "y", "origin": [8, 6.5, 8]}, + "faces": { + "north": {"uv": [0, 1, 1, 2], "texture": "#main"}, + "east": {"uv": [0, 1, 1, 5], "rotation": 90, "texture": "#main"}, + "south": {"uv": [0, 4, 1, 5], "texture": "#main"}, + "west": {"uv": [0, 1, 1, 5], "rotation": 90, "texture": "#main"}, + "up": {"uv": [0, 1, 1, 5], "texture": "#main"}, + "down": {"uv": [0, 1, 1, 5], "texture": "#main"} + } + }, + { + "from": [6, 6.5, 5], + "to": [10, 7.5, 6], + "rotation": {"angle": 0, "axis": "y", "origin": [10, 6.5, 7]}, + "faces": { + "north": {"uv": [1, 0, 5, 1], "texture": "#main"}, + "east": {"uv": [4, 0, 5, 1], "texture": "#main"}, + "south": {"uv": [1, 0, 5, 1], "texture": "#main"}, + "west": {"uv": [1, 0, 2, 1], "texture": "#main"}, + "up": {"uv": [1, 0, 5, 1], "texture": "#main"}, + "down": {"uv": [1, 0, 5, 1], "texture": "#main"} + } + }, + { + "from": [10, 6.5, 6], + "to": [11, 7.5, 10], + "rotation": {"angle": 0, "axis": "y", "origin": [13, 6.5, 8]}, + "faces": { + "north": {"uv": [5, 1, 6, 2], "texture": "#main"}, + "east": {"uv": [5, 1, 6, 5], "rotation": 90, "texture": "#main"}, + "south": {"uv": [5, 4, 6, 5], "texture": "#main"}, + "west": {"uv": [5, 1, 6, 5], "rotation": 90, "texture": "#main"}, + "up": {"uv": [5, 1, 6, 5], "texture": "#main"}, + "down": {"uv": [5, 1, 6, 5], "texture": "#main"} + } + }, + { + "from": [10, 2.5, 7.5], + "to": [11, 6.5, 8.5], + "rotation": {"angle": -45, "axis": "z", "origin": [11, 5, 8]}, + "faces": { + "north": {"uv": [15, 1, 16, 5], "texture": "#main"}, + "east": {"uv": [14, 1, 15, 5], "texture": "#main"}, + "south": {"uv": [13, 1, 14, 5], "texture": "#main"}, + "west": {"uv": [12, 1, 13, 5], "texture": "#main"}, + "down": {"uv": [13, 4, 14, 5], "texture": "#main"} + } + }, + { + "from": [11.06066, 6.06066, 7.5], + "to": [12.06066, 8.06066, 8.5], + "rotation": {"angle": 0, "axis": "y", "origin": [19.06066, 15.06066, 16]}, + "faces": { + "north": {"uv": [15, 0, 16, 2], "texture": "#main"}, + "east": {"uv": [14, 0, 15, 2], "texture": "#main"}, + "south": {"uv": [13, 0, 14, 2], "texture": "#main"}, + "west": {"uv": [12, 0, 13, 2], "texture": "#main"}, + "up": {"uv": [13, 0, 14, 1], "texture": "#main"} + } + }, + { + "from": [7.5, 2.5, 5], + "to": [8.5, 6.5, 6], + "rotation": {"angle": -45, "axis": "x", "origin": [8, 5, 5]}, + "faces": { + "north": {"uv": [14, 1, 15, 5], "texture": "#main"}, + "east": {"uv": [13, 1, 14, 5], "texture": "#main"}, + "south": {"uv": [12, 1, 13, 5], "texture": "#main"}, + "west": {"uv": [15, 1, 16, 5], "texture": "#main"}, + "down": {"uv": [13, 4, 14, 5], "rotation": 90, "texture": "#main"} + } + }, + { + "from": [7.5, 6.06066, 3.93934], + "to": [8.5, 8.06066, 4.93934], + "rotation": {"angle": 0, "axis": "y", "origin": [16, 15.06066, -3.06066]}, + "faces": { + "north": {"uv": [14, 0, 15, 2], "texture": "#main"}, + "east": {"uv": [13, 0, 14, 2], "texture": "#main"}, + "south": {"uv": [12, 0, 13, 2], "texture": "#main"}, + "west": {"uv": [15, 0, 16, 2], "texture": "#main"}, + "up": {"uv": [13, 0, 14, 1], "rotation": 270, "texture": "#main"} + } + }, + { + "from": [5, 2.5, 7.5], + "to": [6, 6.5, 8.5], + "rotation": {"angle": 45, "axis": "z", "origin": [5, 5, 8]}, + "faces": { + "north": {"uv": [13, 1, 14, 5], "texture": "#main"}, + "east": {"uv": [12, 1, 13, 5], "texture": "#main"}, + "south": {"uv": [15, 1, 16, 5], "texture": "#main"}, + "west": {"uv": [14, 1, 15, 5], "texture": "#main"}, + "down": {"uv": [13, 4, 14, 5], "rotation": 180, "texture": "#main"} + } + }, + { + "from": [3.93934, 6.06066, 7.5], + "to": [4.93934, 8.06066, 8.5], + "rotation": {"angle": 0, "axis": "y", "origin": [-3.06066, 15.06066, 0]}, + "faces": { + "north": {"uv": [13, 0, 14, 2], "texture": "#main"}, + "east": {"uv": [12, 0, 13, 2], "texture": "#main"}, + "south": {"uv": [15, 0, 16, 2], "texture": "#main"}, + "west": {"uv": [14, 0, 15, 2], "texture": "#main"}, + "up": {"uv": [13, 0, 14, 1], "rotation": 180, "texture": "#main"} + } + }, + { + "from": [7.5, 2.5, 10], + "to": [8.5, 6.5, 11], + "rotation": {"angle": 45, "axis": "x", "origin": [8, 5, 11]}, + "faces": { + "north": {"uv": [12, 1, 13, 5], "texture": "#main"}, + "east": {"uv": [15, 1, 16, 5], "texture": "#main"}, + "south": {"uv": [14, 1, 15, 5], "texture": "#main"}, + "west": {"uv": [13, 1, 14, 5], "texture": "#main"}, + "down": {"uv": [13, 4, 14, 5], "rotation": 270, "texture": "#main"} + } + }, + { + "from": [7.5, 6.06066, 11.06066], + "to": [8.5, 8.06066, 12.06066], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 15.06066, 19.06066]}, + "faces": { + "north": {"uv": [12, 0, 13, 2], "texture": "#main"}, + "east": {"uv": [15, 0, 16, 2], "texture": "#main"}, + "south": {"uv": [14, 0, 15, 2], "texture": "#main"}, + "west": {"uv": [13, 0, 14, 2], "texture": "#main"}, + "up": {"uv": [13, 0, 14, 1], "rotation": 90, "texture": "#main"} + } + }, + { + "name": "connector", + "from": [6.5, 3, 6.5], + "to": [9.5, 4, 9.5], + "rotation": {"angle": 0, "axis": "y", "origin": [14, 11, 17]}, + "faces": { + "north": {"uv": [7, 3, 10, 4], "texture": "#main"}, + "east": {"uv": [7, 3, 10, 4], "texture": "#main"}, + "south": {"uv": [7, 3, 10, 4], "texture": "#main"}, + "west": {"uv": [7, 3, 10, 4], "texture": "#main"}, + "up": {"uv": [7, 0, 10, 3], "texture": "#main"}, + "down": {"uv": [7, 0, 10, 3], "texture": "#main"} + } + }, + { + "name": "pillar", + "from": [7, 1, 7], + "to": [9, 3, 9], + "rotation": {"angle": 0, "axis": "y", "origin": [15, 9.5, 15]}, + "faces": { + "north": {"uv": [5, 9, 7, 11], "texture": "#main"}, + "east": {"uv": [5, 9, 7, 11], "texture": "#main"}, + "south": {"uv": [5, 9, 7, 11], "texture": "#main"}, + "west": {"uv": [5, 9, 7, 11], "texture": "#main"} + } + }, + { + "name": "base", + "from": [6, 0, 6], + "to": [10, 1, 10], + "rotation": {"angle": 0, "axis": "y", "origin": [14, 8, 17]}, + "faces": { + "north": {"uv": [4, 11, 8, 12], "texture": "#main"}, + "east": {"uv": [4, 11, 8, 12], "texture": "#main"}, + "south": {"uv": [4, 11, 8, 12], "texture": "#main"}, + "west": {"uv": [4, 11, 8, 12], "texture": "#main"}, + "up": {"uv": [0, 8, 4, 12], "texture": "#main"}, + "down": {"uv": [0, 12, 4, 16], "texture": "#main", "cullface": "down"} + } + } + ], + "display": { + "thirdperson_righthand": { + "translation": [-0.25, 3.5, 0], + "scale": [0.75, 0.75, 0.75] + }, + "thirdperson_lefthand": { + "translation": [-0.25, 3.5, 0], + "scale": [0.75, 0.75, 0.75] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "translation": [1, 3, 0], + "scale": [0.5, 0.5, 0.5] + }, + "firstperson_lefthand": { + "rotation": [0, 45, 0], + "translation": [1, 3, 0], + "scale": [0.5, 0.5, 0.5] + }, + "ground": { + "translation": [0, 3, 0], + "scale": [0.75, 0.75, 0.75] + }, + "gui": { + "rotation": [30, 225, 0], + "translation": [0, 3, 0] + }, + "fixed": { + "translation": [0.25, 3.5, 0] + } + }, + "groups": [ + { + "name": "dish", + "origin": [13, 8, 8], + "children": [0, 1, 2, 3, 4] + }, + { + "name": "arm", + "origin": [19.06066, 16.56066, 16], + "children": [5, 6] + }, + { + "name": "arm", + "origin": [19.06066, 16.56066, 16], + "children": [7, 8] + }, + { + "name": "arm", + "origin": [19.06066, 16.56066, 16], + "children": [9, 10] + }, + { + "name": "arm", + "origin": [19.06066, 16.56066, 16], + "children": [11, 12] + }, 13, 14, 15] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/block/spruce_bookshelf.json b/src/main/resources/assets/ebwizardry/models/block/spruce_bookshelf.json new file mode 100644 index 00000000..d75b4d4e --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/spruce_bookshelf.json @@ -0,0 +1,9 @@ +{ + "parent": "ebwizardry:block/bookshelf", + "textures": { + "particle": "ebwizardry:blocks/spruce_bookshelf_side", + "side": "ebwizardry:blocks/spruce_bookshelf_side", + "inside": "ebwizardry:blocks/spruce_bookshelf_inside", + "top": "ebwizardry:blocks/spruce_bookshelf_top" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/spruce_lectern.json b/src/main/resources/assets/ebwizardry/models/block/spruce_lectern.json new file mode 100644 index 00000000..396cdfed --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/spruce_lectern.json @@ -0,0 +1,10 @@ +{ + "parent": "ebwizardry:block/lectern", + "textures": { + "particle": "ebwizardry:blocks/spruce_lectern_top", + "base": "ebwizardry:blocks/spruce_lectern_base", + "side": "ebwizardry:blocks/spruce_lectern_side", + "top": "ebwizardry:blocks/spruce_lectern_top", + "underside": "ebwizardry:blocks/spruce_lectern_underside" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/block/wall_receptacle.json b/src/main/resources/assets/ebwizardry/models/block/wall_receptacle.json new file mode 100644 index 00000000..d378d412 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/block/wall_receptacle.json @@ -0,0 +1,238 @@ +{ + "credit": "Made with Blockbench", + "parent": "block/block", + "textures": { + "particle": "ebwizardry:blocks/wall_receptacle", + "main": "ebwizardry:blocks/wall_receptacle" + }, + "elements": [ + { + "from": [6, 8.5, 7], + "to": [10, 9.5, 8], + "rotation": {"angle": 0, "axis": "y", "origin": [10, 8.5, 9]}, + "faces": { + "north": {"uv": [1, 5, 5, 6], "texture": "#main"}, + "east": {"uv": [4, 5, 5, 6], "texture": "#main"}, + "south": {"uv": [1, 5, 5, 6], "texture": "#main"}, + "west": {"uv": [1, 5, 2, 6], "texture": "#main"}, + "up": {"uv": [1, 5, 5, 6], "texture": "#main"}, + "down": {"uv": [1, 5, 5, 6], "texture": "#main"} + } + }, + { + "from": [6, 7.5, 3], + "to": [10, 8.5, 7], + "rotation": {"angle": 0, "axis": "y", "origin": [8, 8.5, 5]}, + "faces": { + "north": {"uv": [1, 7, 5, 8], "texture": "#main"}, + "east": {"uv": [1, 7, 5, 8], "texture": "#main"}, + "south": {"uv": [1, 7, 5, 8], "texture": "#main"}, + "west": {"uv": [1, 7, 5, 8], "texture": "#main"}, + "up": {"uv": [1, 1, 5, 5], "texture": "#main"}, + "down": {"uv": [1, 1, 5, 5], "texture": "#main"} + } + }, + { + "from": [5, 8.5, 3], + "to": [6, 9.5, 7], + "rotation": {"angle": 0, "axis": "y", "origin": [8, 8.5, 5]}, + "faces": { + "north": {"uv": [0, 1, 1, 2], "texture": "#main"}, + "east": {"uv": [0, 1, 1, 5], "rotation": 90, "texture": "#main"}, + "south": {"uv": [0, 4, 1, 5], "texture": "#main"}, + "west": {"uv": [0, 1, 1, 5], "rotation": 90, "texture": "#main"}, + "up": {"uv": [0, 1, 1, 5], "texture": "#main"}, + "down": {"uv": [0, 1, 1, 5], "texture": "#main"} + } + }, + { + "from": [6, 8.5, 2], + "to": [10, 9.5, 3], + "rotation": {"angle": 0, "axis": "y", "origin": [10, 8.5, 4]}, + "faces": { + "north": {"uv": [1, 0, 5, 1], "texture": "#main"}, + "east": {"uv": [4, 0, 5, 1], "texture": "#main"}, + "south": {"uv": [1, 0, 5, 1], "texture": "#main"}, + "west": {"uv": [1, 0, 2, 1], "texture": "#main"}, + "up": {"uv": [1, 0, 5, 1], "texture": "#main"}, + "down": {"uv": [1, 0, 5, 1], "texture": "#main"} + } + }, + { + "from": [10, 8.5, 3], + "to": [11, 9.5, 7], + "rotation": {"angle": 0, "axis": "y", "origin": [13, 8.5, 5]}, + "faces": { + "north": {"uv": [5, 1, 6, 2], "texture": "#main"}, + "east": {"uv": [5, 1, 6, 5], "rotation": 90, "texture": "#main"}, + "south": {"uv": [5, 4, 6, 5], "texture": "#main"}, + "west": {"uv": [5, 1, 6, 5], "rotation": 90, "texture": "#main"}, + "up": {"uv": [5, 1, 6, 5], "texture": "#main"}, + "down": {"uv": [5, 1, 6, 5], "texture": "#main"} + } + }, + { + "from": [10, 4.5, 4.5], + "to": [11, 8.5, 5.5], + "rotation": {"angle": -45, "axis": "z", "origin": [11, 7, 5]}, + "faces": { + "north": {"uv": [15, 1, 16, 5], "texture": "#main"}, + "east": {"uv": [14, 1, 15, 5], "texture": "#main"}, + "south": {"uv": [13, 1, 14, 5], "texture": "#main"}, + "west": {"uv": [12, 1, 13, 5], "texture": "#main"}, + "down": {"uv": [13, 4, 14, 5], "texture": "#main"} + } + }, + { + "from": [11.06066, 8.06066, 4.5], + "to": [12.06066, 10.06066, 5.5], + "rotation": {"angle": 0, "axis": "y", "origin": [19.06066, 17.06066, 13]}, + "faces": { + "north": {"uv": [15, 0, 16, 2], "texture": "#main"}, + "east": {"uv": [14, 0, 15, 2], "texture": "#main"}, + "south": {"uv": [13, 0, 14, 2], "texture": "#main"}, + "west": {"uv": [12, 0, 13, 2], "texture": "#main"}, + "up": {"uv": [13, 0, 14, 1], "texture": "#main"} + } + }, + { + "from": [7.5, 4.5, 2], + "to": [8.5, 8.5, 3], + "rotation": {"angle": -45, "axis": "x", "origin": [8, 7, 2]}, + "faces": { + "north": {"uv": [14, 1, 15, 5], "texture": "#main"}, + "east": {"uv": [13, 1, 14, 5], "texture": "#main"}, + "south": {"uv": [12, 1, 13, 5], "texture": "#main"}, + "west": {"uv": [15, 1, 16, 5], "texture": "#main"}, + "down": {"uv": [13, 4, 14, 5], "rotation": 90, "texture": "#main"} + } + }, + { + "from": [7.5, 8.06066, 0.93934], + "to": [8.5, 10.06066, 1.93934], + "rotation": {"angle": 0, "axis": "y", "origin": [16, 17.06066, -6.06066]}, + "faces": { + "north": {"uv": [14, 0, 15, 2], "texture": "#main"}, + "east": {"uv": [13, 0, 14, 2], "texture": "#main"}, + "south": {"uv": [12, 0, 13, 2], "texture": "#main"}, + "west": {"uv": [15, 0, 16, 2], "texture": "#main"}, + "up": {"uv": [13, 0, 14, 1], "rotation": 270, "texture": "#main"} + } + }, + { + "from": [5, 4.5, 4.5], + "to": [6, 8.5, 5.5], + "rotation": {"angle": 45, "axis": "z", "origin": [5, 7, 5]}, + "faces": { + "north": {"uv": [13, 1, 14, 5], "texture": "#main"}, + "east": {"uv": [12, 1, 13, 5], "texture": "#main"}, + "south": {"uv": [15, 1, 16, 5], "texture": "#main"}, + "west": {"uv": [14, 1, 15, 5], "texture": "#main"}, + "down": {"uv": [13, 4, 14, 5], "rotation": 180, "texture": "#main"} + } + }, + { + "from": [3.93934, 8.06066, 4.5], + "to": [4.93934, 10.06066, 5.5], + "rotation": {"angle": 0, "axis": "y", "origin": [-3.06066, 17.06066, -3]}, + "faces": { + "north": {"uv": [13, 0, 14, 2], "texture": "#main"}, + "east": {"uv": [12, 0, 13, 2], "texture": "#main"}, + "south": {"uv": [15, 0, 16, 2], "texture": "#main"}, + "west": {"uv": [14, 0, 15, 2], "texture": "#main"}, + "up": {"uv": [13, 0, 14, 1], "rotation": 180, "texture": "#main"} + } + }, + { + "from": [7.5, 4.5, 7], + "to": [8.5, 8.5, 8], + "rotation": {"angle": 45, "axis": "x", "origin": [8, 7, 8]}, + "faces": { + "north": {"uv": [12, 1, 13, 5], "texture": "#main"}, + "east": {"uv": [15, 1, 16, 5], "texture": "#main"}, + "south": {"uv": [14, 1, 15, 5], "texture": "#main"}, + "west": {"uv": [13, 1, 14, 5], "texture": "#main"}, + "down": {"uv": [13, 4, 14, 5], "rotation": 270, "texture": "#main"} + } + }, + { + "from": [7.5, 8.06066, 8.06066], + "to": [8.5, 10.06066, 9.06066], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 17.06066, 16.06066]}, + "faces": { + "north": {"uv": [12, 0, 13, 2], "texture": "#main"}, + "east": {"uv": [15, 0, 16, 2], "texture": "#main"}, + "south": {"uv": [14, 0, 15, 2], "texture": "#main"}, + "west": {"uv": [13, 0, 14, 2], "texture": "#main"}, + "up": {"uv": [13, 0, 14, 1], "rotation": 90, "texture": "#main"} + } + }, + { + "name": "connector", + "from": [6.5, 5, 3.5], + "to": [9.5, 6, 6.5], + "rotation": {"angle": 0, "axis": "y", "origin": [14, 13, 14]}, + "faces": { + "north": {"uv": [7, 3, 10, 4], "texture": "#main"}, + "east": {"uv": [7, 3, 10, 4], "texture": "#main"}, + "south": {"uv": [7, 3, 10, 4], "texture": "#main"}, + "west": {"uv": [7, 3, 10, 4], "texture": "#main"}, + "up": {"uv": [7, 0, 10, 3], "texture": "#main"}, + "down": {"uv": [7, 0, 10, 3], "texture": "#main"} + } + }, + { + "name": "pillar", + "from": [7, 3, 1], + "to": [9, 5, 6], + "rotation": {"angle": 0, "axis": "y", "origin": [15, 11.5, 15]}, + "faces": { + "east": {"uv": [10, 10, 5, 12], "texture": "#main"}, + "south": {"uv": [10, 10, 12, 12], "texture": "#main"}, + "west": {"uv": [5, 10, 10, 12], "texture": "#main"}, + "up": {"uv": [10, 10, 5, 12], "rotation": 270, "texture": "#main"}, + "down": {"uv": [5, 10, 10, 12], "rotation": 270, "texture": "#main"} + } + }, + { + "name": "base", + "from": [6, 2, 0], + "to": [10, 6, 1], + "rotation": {"angle": 0, "axis": "y", "origin": [8, 4.5, 1]}, + "faces": { + "north": {"uv": [0, 9, 4, 13], "texture": "#main", "cullface": "north"}, + "east": {"uv": [4, 9, 5, 13], "texture": "#main"}, + "south": {"uv": [0, 9, 4, 13], "texture": "#main"}, + "west": {"uv": [4, 9, 5, 13], "texture": "#main"}, + "up": {"uv": [0, 8, 4, 9], "texture": "#main"}, + "down": {"uv": [0, 13, 4, 14], "texture": "#main"} + } + } + ], + "groups": [ + { + "name": "dish", + "origin": [13, 8, 8], + "children": [0, 1, 2, 3, 4] + }, + { + "name": "arm", + "origin": [19.06066, 16.56066, 16], + "children": [5, 6] + }, + { + "name": "arm", + "origin": [19.06066, 16.56066, 16], + "children": [7, 8] + }, + { + "name": "arm", + "origin": [19.06066, 16.56066, 16], + "children": [9, 10] + }, + { + "name": "arm", + "origin": [19.06066, 16.56066, 16], + "children": [11, 12] + }, 13, 14, 15] +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/acacia_bookshelf.json b/src/main/resources/assets/ebwizardry/models/item/acacia_bookshelf.json new file mode 100644 index 00000000..0adc3e0c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/acacia_bookshelf.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/acacia_bookshelf" +} diff --git a/src/main/resources/assets/ebwizardry/models/item/acacia_gilded_wood.json b/src/main/resources/assets/ebwizardry/models/item/acacia_gilded_wood.json new file mode 100644 index 00000000..dde12e2e --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/acacia_gilded_wood.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/gilded_acacia_wood" +} diff --git a/src/main/resources/assets/ebwizardry/models/item/acacia_lectern.json b/src/main/resources/assets/ebwizardry/models/item/acacia_lectern.json new file mode 100644 index 00000000..59f077e4 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/acacia_lectern.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/acacia_lectern" +} diff --git a/src/main/resources/assets/ebwizardry/models/item/advanced_earth_wand.json b/src/main/resources/assets/ebwizardry/models/item/advanced_earth_wand.json index cf039f84..c096a218 100644 --- a/src/main/resources/assets/ebwizardry/models/item/advanced_earth_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/advanced_earth_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_advanced_earth" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/advanced_fire_wand.json b/src/main/resources/assets/ebwizardry/models/item/advanced_fire_wand.json index a71872f0..e3ee972b 100644 --- a/src/main/resources/assets/ebwizardry/models/item/advanced_fire_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/advanced_fire_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_advanced_fire" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/advanced_healing_wand.json b/src/main/resources/assets/ebwizardry/models/item/advanced_healing_wand.json index bf0096f6..0854d2a9 100644 --- a/src/main/resources/assets/ebwizardry/models/item/advanced_healing_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/advanced_healing_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_advanced_healing" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/advanced_ice_wand.json b/src/main/resources/assets/ebwizardry/models/item/advanced_ice_wand.json index 0c4eccd5..a3c64272 100644 --- a/src/main/resources/assets/ebwizardry/models/item/advanced_ice_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/advanced_ice_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_advanced_ice" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/advanced_lightning_wand.json b/src/main/resources/assets/ebwizardry/models/item/advanced_lightning_wand.json index 7be6ebe9..bfd46bfd 100644 --- a/src/main/resources/assets/ebwizardry/models/item/advanced_lightning_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/advanced_lightning_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_advanced_lightning" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/advanced_necromancy_wand.json b/src/main/resources/assets/ebwizardry/models/item/advanced_necromancy_wand.json index 6f0856fe..8604ea87 100644 --- a/src/main/resources/assets/ebwizardry/models/item/advanced_necromancy_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/advanced_necromancy_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_advanced_necromancy" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/advanced_sorcery_wand.json b/src/main/resources/assets/ebwizardry/models/item/advanced_sorcery_wand.json index 73de0258..32b308f6 100644 --- a/src/main/resources/assets/ebwizardry/models/item/advanced_sorcery_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/advanced_sorcery_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_advanced_sorcery" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/advanced_wand.json b/src/main/resources/assets/ebwizardry/models/item/advanced_wand.json index ceb0b2e0..2852b87f 100644 --- a/src/main/resources/assets/ebwizardry/models/item/advanced_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/advanced_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_advanced" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/amulet_absorption.json b/src/main/resources/assets/ebwizardry/models/item/amulet_absorption.json new file mode 100644 index 00000000..e9d56323 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/amulet_absorption.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/amulet_absorption" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/amulet_frost_warding.json b/src/main/resources/assets/ebwizardry/models/item/amulet_frost_warding.json new file mode 100644 index 00000000..c6d9cc2e --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/amulet_frost_warding.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/amulet_frost_warding" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/apprentice_earth_wand.json b/src/main/resources/assets/ebwizardry/models/item/apprentice_earth_wand.json index dc0c9748..448931cc 100644 --- a/src/main/resources/assets/ebwizardry/models/item/apprentice_earth_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/apprentice_earth_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_apprentice_earth" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/apprentice_fire_wand.json b/src/main/resources/assets/ebwizardry/models/item/apprentice_fire_wand.json index 49568c5b..12f60681 100644 --- a/src/main/resources/assets/ebwizardry/models/item/apprentice_fire_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/apprentice_fire_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_apprentice_fire" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/apprentice_healing_wand.json b/src/main/resources/assets/ebwizardry/models/item/apprentice_healing_wand.json index bdfd6ceb..3478e831 100644 --- a/src/main/resources/assets/ebwizardry/models/item/apprentice_healing_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/apprentice_healing_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_apprentice_healing" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/apprentice_ice_wand.json b/src/main/resources/assets/ebwizardry/models/item/apprentice_ice_wand.json index 9f83e3e1..a3fb8819 100644 --- a/src/main/resources/assets/ebwizardry/models/item/apprentice_ice_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/apprentice_ice_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_apprentice_ice" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/apprentice_lightning_wand.json b/src/main/resources/assets/ebwizardry/models/item/apprentice_lightning_wand.json index ad2565e1..8c28070c 100644 --- a/src/main/resources/assets/ebwizardry/models/item/apprentice_lightning_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/apprentice_lightning_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_apprentice_lightning" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/apprentice_necromancy_wand.json b/src/main/resources/assets/ebwizardry/models/item/apprentice_necromancy_wand.json index 3750104a..d5f28f6f 100644 --- a/src/main/resources/assets/ebwizardry/models/item/apprentice_necromancy_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/apprentice_necromancy_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_apprentice_necromancy" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/apprentice_sorcery_wand.json b/src/main/resources/assets/ebwizardry/models/item/apprentice_sorcery_wand.json index debce2e4..dd4169be 100644 --- a/src/main/resources/assets/ebwizardry/models/item/apprentice_sorcery_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/apprentice_sorcery_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_apprentice_sorcery" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/apprentice_wand.json b/src/main/resources/assets/ebwizardry/models/item/apprentice_wand.json index d3baa0fe..c35d3d43 100644 --- a/src/main/resources/assets/ebwizardry/models/item/apprentice_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/apprentice_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_apprentice" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/birch_bookshelf.json b/src/main/resources/assets/ebwizardry/models/item/birch_bookshelf.json new file mode 100644 index 00000000..a41498fd --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/birch_bookshelf.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/birch_bookshelf" +} diff --git a/src/main/resources/assets/ebwizardry/models/item/birch_gilded_wood.json b/src/main/resources/assets/ebwizardry/models/item/birch_gilded_wood.json new file mode 100644 index 00000000..845dcc60 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/birch_gilded_wood.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/gilded_birch_wood" +} diff --git a/src/main/resources/assets/ebwizardry/models/item/birch_lectern.json b/src/main/resources/assets/ebwizardry/models/item/birch_lectern.json new file mode 100644 index 00000000..aaa107eb --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/birch_lectern.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/birch_lectern" +} diff --git a/src/main/resources/assets/ebwizardry/models/item/charm_black_hole.json b/src/main/resources/assets/ebwizardry/models/item/charm_black_hole.json new file mode 100644 index 00000000..f416b151 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/charm_black_hole.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/charm_black_hole" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/charm_hunger_casting.json b/src/main/resources/assets/ebwizardry/models/item/charm_hunger_casting.json new file mode 100644 index 00000000..95f9c1c7 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/charm_hunger_casting.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/charm_hunger_casting" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/charm_mount_teleporting.json b/src/main/resources/assets/ebwizardry/models/item/charm_mount_teleporting.json new file mode 100644 index 00000000..b0d8b187 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/charm_mount_teleporting.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/charm_mount_teleporting" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/charm_move_speed.json b/src/main/resources/assets/ebwizardry/models/item/charm_move_speed.json new file mode 100644 index 00000000..71f461b1 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/charm_move_speed.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/charm_move_speed" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/charm_sixth_sense.json b/src/main/resources/assets/ebwizardry/models/item/charm_sixth_sense.json new file mode 100644 index 00000000..0c08bcc2 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/charm_sixth_sense.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/charm_sixth_sense" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/charm_spell_discovery.json b/src/main/resources/assets/ebwizardry/models/item/charm_spell_discovery.json new file mode 100644 index 00000000..061c845f --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/charm_spell_discovery.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/charm_spell_discovery" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/charm_undead_helmets.json b/src/main/resources/assets/ebwizardry/models/item/charm_undead_helmets.json new file mode 100644 index 00000000..af4fea86 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/charm_undead_helmets.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/charm_undead_helmets" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/dark_oak_bookshelf.json b/src/main/resources/assets/ebwizardry/models/item/dark_oak_bookshelf.json new file mode 100644 index 00000000..9818d2d5 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/dark_oak_bookshelf.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/dark_oak_bookshelf" +} diff --git a/src/main/resources/assets/ebwizardry/models/item/dark_oak_gilded_wood.json b/src/main/resources/assets/ebwizardry/models/item/dark_oak_gilded_wood.json new file mode 100644 index 00000000..4b8fdfe6 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/dark_oak_gilded_wood.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/gilded_dark_oak_wood" +} diff --git a/src/main/resources/assets/ebwizardry/models/item/dark_oak_lectern.json b/src/main/resources/assets/ebwizardry/models/item/dark_oak_lectern.json new file mode 100644 index 00000000..324650ba --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/dark_oak_lectern.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/dark_oak_lectern" +} diff --git a/src/main/resources/assets/ebwizardry/models/item/festive_wand.json b/src/main/resources/assets/ebwizardry/models/item/festive_wand.json index b31af7dd..5ce21ed9 100644 --- a/src/main/resources/assets/ebwizardry/models/item/festive_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/festive_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_festive" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/flamecatcher.json b/src/main/resources/assets/ebwizardry/models/item/flamecatcher.json new file mode 100644 index 00000000..a2337094 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/flamecatcher.json @@ -0,0 +1,105 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/flamecatcher_standby" + }, + "display": { + "thirdperson_righthand": { + "rotation": [ -80, 260, -40 ], + "translation": [ -1, -2, 2.5 ], + "scale": [ 0.9, 0.9, 0.9 ] + }, + "thirdperson_lefthand": { + "rotation": [ -80, -280, 40 ], + "translation": [ -1, -2, 2.5 ], + "scale": [ 0.9, 0.9, 0.9 ] + }, + "firstperson_righthand": { + "rotation": [ 0, -90, 25 ], + "translation": [ 1.13, 3.2, 1.13], + "scale": [ 0.68, 0.68, 0.68 ] + }, + "firstperson_lefthand": { + "rotation": [ 0, 90, -25 ], + "translation": [ 1.13, 3.2, 1.13], + "scale": [ 0.68, 0.68, 0.68 ] + } + }, + "overrides": [ + { + "predicate": { + "pulling": 1 + }, + "model": "ebwizardry:item/flamecatcher_pulling_0" + }, + { + "predicate": { + "pulling": 1, + "pull": 0.65 + }, + "model": "ebwizardry:item/flamecatcher_pulling_1" + }, + { + "predicate": { + "pulling": 1, + "pull": 0.9 + }, + "model": "ebwizardry:item/flamecatcher_pulling_2" + }, + { + "predicate": { + "conjuring": 1 + }, + "model": "ebwizardry:item/flamecatcher_conjuring_0" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.125 + }, + "model": "ebwizardry:item/flamecatcher_conjuring_1" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.25 + }, + "model": "ebwizardry:item/flamecatcher_conjuring_2" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.375 + }, + "model": "ebwizardry:item/flamecatcher_conjuring_3" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.5 + }, + "model": "ebwizardry:item/flamecatcher_conjuring_4" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.625 + }, + "model": "ebwizardry:item/flamecatcher_conjuring_5" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.75 + }, + "model": "ebwizardry:item/flamecatcher_conjuring_6" + }, + { + "predicate": { + "conjuring": 1, + "conjure": 0.875 + }, + "model": "ebwizardry:item/flamecatcher_conjuring_7" + } + ] +} diff --git a/src/main/resources/assets/ebwizardry/models/item/flamecatcher_conjuring_0.json b/src/main/resources/assets/ebwizardry/models/item/flamecatcher_conjuring_0.json new file mode 100644 index 00000000..bb2a7ef2 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/flamecatcher_conjuring_0.json @@ -0,0 +1,6 @@ +{ + "parent": "item/bow", + "textures": { + "layer0": "ebwizardry:items/flamecatcher_conjuring_0" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/flamecatcher_conjuring_1.json b/src/main/resources/assets/ebwizardry/models/item/flamecatcher_conjuring_1.json new file mode 100644 index 00000000..26ab48e9 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/flamecatcher_conjuring_1.json @@ -0,0 +1,6 @@ +{ + "parent": "item/bow", + "textures": { + "layer0": "ebwizardry:items/flamecatcher_conjuring_1" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/flamecatcher_conjuring_2.json b/src/main/resources/assets/ebwizardry/models/item/flamecatcher_conjuring_2.json new file mode 100644 index 00000000..6ab25cd2 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/flamecatcher_conjuring_2.json @@ -0,0 +1,6 @@ +{ + "parent": "item/bow", + "textures": { + "layer0": "ebwizardry:items/flamecatcher_conjuring_2" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/flamecatcher_conjuring_3.json b/src/main/resources/assets/ebwizardry/models/item/flamecatcher_conjuring_3.json new file mode 100644 index 00000000..dd041724 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/flamecatcher_conjuring_3.json @@ -0,0 +1,6 @@ +{ + "parent": "item/bow", + "textures": { + "layer0": "ebwizardry:items/flamecatcher_conjuring_3" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/flamecatcher_conjuring_4.json b/src/main/resources/assets/ebwizardry/models/item/flamecatcher_conjuring_4.json new file mode 100644 index 00000000..5e980ed4 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/flamecatcher_conjuring_4.json @@ -0,0 +1,6 @@ +{ + "parent": "item/bow", + "textures": { + "layer0": "ebwizardry:items/flamecatcher_conjuring_4" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/flamecatcher_conjuring_5.json b/src/main/resources/assets/ebwizardry/models/item/flamecatcher_conjuring_5.json new file mode 100644 index 00000000..bd32f65c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/flamecatcher_conjuring_5.json @@ -0,0 +1,6 @@ +{ + "parent": "item/bow", + "textures": { + "layer0": "ebwizardry:items/flamecatcher_conjuring_5" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/flamecatcher_conjuring_6.json b/src/main/resources/assets/ebwizardry/models/item/flamecatcher_conjuring_6.json new file mode 100644 index 00000000..6669564e --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/flamecatcher_conjuring_6.json @@ -0,0 +1,6 @@ +{ + "parent": "item/bow", + "textures": { + "layer0": "ebwizardry:items/flamecatcher_conjuring_6" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/flamecatcher_conjuring_7.json b/src/main/resources/assets/ebwizardry/models/item/flamecatcher_conjuring_7.json new file mode 100644 index 00000000..80bbd6d5 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/flamecatcher_conjuring_7.json @@ -0,0 +1,6 @@ +{ + "parent": "item/bow", + "textures": { + "layer0": "ebwizardry:items/flamecatcher_conjuring_7" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/flamecatcher_pulling_0.json b/src/main/resources/assets/ebwizardry/models/item/flamecatcher_pulling_0.json new file mode 100644 index 00000000..528b5b65 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/flamecatcher_pulling_0.json @@ -0,0 +1,6 @@ +{ + "parent": "item/bow", + "textures": { + "layer0": "ebwizardry:items/flamecatcher_pulling_0" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/flamecatcher_pulling_1.json b/src/main/resources/assets/ebwizardry/models/item/flamecatcher_pulling_1.json new file mode 100644 index 00000000..ae4d8fa2 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/flamecatcher_pulling_1.json @@ -0,0 +1,6 @@ +{ + "parent": "item/bow", + "textures": { + "layer0": "ebwizardry:items/flamecatcher_pulling_1" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/flamecatcher_pulling_2.json b/src/main/resources/assets/ebwizardry/models/item/flamecatcher_pulling_2.json new file mode 100644 index 00000000..ce385d5b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/flamecatcher_pulling_2.json @@ -0,0 +1,6 @@ +{ + "parent": "item/bow", + "textures": { + "layer0": "ebwizardry:items/flamecatcher_pulling_2" + } +} diff --git a/src/main/resources/assets/ebwizardry/models/item/imbuement_altar.json b/src/main/resources/assets/ebwizardry/models/item/imbuement_altar.json new file mode 100644 index 00000000..d82d1f0b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/imbuement_altar.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/imbuement_altar_inactive" +} diff --git a/src/main/resources/assets/ebwizardry/models/item/jungle_bookshelf.json b/src/main/resources/assets/ebwizardry/models/item/jungle_bookshelf.json new file mode 100644 index 00000000..e65f05c7 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/jungle_bookshelf.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/jungle_bookshelf" +} diff --git a/src/main/resources/assets/ebwizardry/models/item/jungle_gilded_wood.json b/src/main/resources/assets/ebwizardry/models/item/jungle_gilded_wood.json new file mode 100644 index 00000000..54d2fbc0 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/jungle_gilded_wood.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/gilded_jungle_wood" +} diff --git a/src/main/resources/assets/ebwizardry/models/item/jungle_lectern.json b/src/main/resources/assets/ebwizardry/models/item/jungle_lectern.json new file mode 100644 index 00000000..59a3dffd --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/jungle_lectern.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/jungle_lectern" +} 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 59387cf7..04e194f2 100644 --- a/src/main/resources/assets/ebwizardry/models/item/magic_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/magic_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_novice" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/master_earth_wand.json b/src/main/resources/assets/ebwizardry/models/item/master_earth_wand.json index 65b2b9a5..d8c92322 100644 --- a/src/main/resources/assets/ebwizardry/models/item/master_earth_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/master_earth_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_master_earth" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/master_fire_wand.json b/src/main/resources/assets/ebwizardry/models/item/master_fire_wand.json index a66856d3..0055168f 100644 --- a/src/main/resources/assets/ebwizardry/models/item/master_fire_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/master_fire_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_master_fire" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/master_healing_wand.json b/src/main/resources/assets/ebwizardry/models/item/master_healing_wand.json index 3f4c37cb..e7882b9a 100644 --- a/src/main/resources/assets/ebwizardry/models/item/master_healing_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/master_healing_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_master_healing" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/master_ice_wand.json b/src/main/resources/assets/ebwizardry/models/item/master_ice_wand.json index 5f98c5b5..803b8bb0 100644 --- a/src/main/resources/assets/ebwizardry/models/item/master_ice_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/master_ice_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_master_ice" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/master_lightning_wand.json b/src/main/resources/assets/ebwizardry/models/item/master_lightning_wand.json index 14659d01..5c480232 100644 --- a/src/main/resources/assets/ebwizardry/models/item/master_lightning_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/master_lightning_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_master_lightning" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/master_necromancy_wand.json b/src/main/resources/assets/ebwizardry/models/item/master_necromancy_wand.json index ebf1ae67..57165e31 100644 --- a/src/main/resources/assets/ebwizardry/models/item/master_necromancy_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/master_necromancy_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_master_necromancy" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/master_sorcery_wand.json b/src/main/resources/assets/ebwizardry/models/item/master_sorcery_wand.json index eabb2af3..47d27356 100644 --- a/src/main/resources/assets/ebwizardry/models/item/master_sorcery_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/master_sorcery_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_master_sorcery" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/master_wand.json b/src/main/resources/assets/ebwizardry/models/item/master_wand.json index c0c29b76..b298e38b 100644 --- a/src/main/resources/assets/ebwizardry/models/item/master_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/master_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_master" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/novice_earth_wand.json b/src/main/resources/assets/ebwizardry/models/item/novice_earth_wand.json index 7431443b..f893f16a 100644 --- a/src/main/resources/assets/ebwizardry/models/item/novice_earth_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/novice_earth_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_novice_earth" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/novice_fire_wand.json b/src/main/resources/assets/ebwizardry/models/item/novice_fire_wand.json index 27055741..15bab228 100644 --- a/src/main/resources/assets/ebwizardry/models/item/novice_fire_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/novice_fire_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_novice_fire" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/novice_healing_wand.json b/src/main/resources/assets/ebwizardry/models/item/novice_healing_wand.json index e9095bf3..4f9d16c5 100644 --- a/src/main/resources/assets/ebwizardry/models/item/novice_healing_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/novice_healing_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_novice_healing" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/novice_ice_wand.json b/src/main/resources/assets/ebwizardry/models/item/novice_ice_wand.json index feb24c4c..a60b71b2 100644 --- a/src/main/resources/assets/ebwizardry/models/item/novice_ice_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/novice_ice_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_novice_ice" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } 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 index ce044666..925a23a4 100644 --- a/src/main/resources/assets/ebwizardry/models/item/novice_lightning_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/novice_lightning_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_novice_lightning" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } 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 index ab64fb68..5f1a07d0 100644 --- a/src/main/resources/assets/ebwizardry/models/item/novice_necromancy_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/novice_necromancy_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_novice_necromancy" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } 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 index 968ff38e..7db5be49 100644 --- a/src/main/resources/assets/ebwizardry/models/item/novice_sorcery_wand.json +++ b/src/main/resources/assets/ebwizardry/models/item/novice_sorcery_wand.json @@ -2,5 +2,13 @@ "parent": "item/handheld", "textures": { "layer0": "ebwizardry:items/wand_novice_sorcery" - } + }, + "overrides": [ + { + "predicate": { + "this_gets_ignored": 1 + }, + "model": "ebwizardry:item/override_generators/wand" + } + ] } diff --git a/src/main/resources/assets/ebwizardry/models/item/oak_bookshelf.json b/src/main/resources/assets/ebwizardry/models/item/oak_bookshelf.json new file mode 100644 index 00000000..cbeffa68 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/oak_bookshelf.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/oak_bookshelf" +} diff --git a/src/main/resources/assets/ebwizardry/models/item/oak_gilded_wood.json b/src/main/resources/assets/ebwizardry/models/item/oak_gilded_wood.json new file mode 100644 index 00000000..34674ded --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/oak_gilded_wood.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/gilded_oak_wood" +} diff --git a/src/main/resources/assets/ebwizardry/models/item/oak_lectern.json b/src/main/resources/assets/ebwizardry/models/item/oak_lectern.json new file mode 100644 index 00000000..2ec35f3e --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/oak_lectern.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/oak_lectern" +} diff --git a/src/main/resources/assets/ebwizardry/models/item/override_generators/wand.json b/src/main/resources/assets/ebwizardry/models/item/override_generators/wand.json new file mode 100644 index 00000000..bcdf7d94 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/override_generators/wand.json @@ -0,0 +1,11 @@ +{ + "parent": "item/handheld", + "overrides": [ + { + "predicate": { + "pointing": 1 + }, + "model": "ebwizardry:item/wand_point" + } + ] +} diff --git a/src/main/resources/assets/ebwizardry/models/item/receptacle.json b/src/main/resources/assets/ebwizardry/models/item/receptacle.json new file mode 100644 index 00000000..6d9b51f0 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/receptacle.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/receptacle" +} diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_evoker.json b/src/main/resources/assets/ebwizardry/models/item/ring_evoker.json new file mode 100644 index 00000000..f2f4ac53 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_evoker.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_evoker" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_meteor.json b/src/main/resources/assets/ebwizardry/models/item/ring_meteor.json new file mode 100644 index 00000000..2d73ca47 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_meteor.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_meteor" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ring_stormcloud.json b/src/main/resources/assets/ebwizardry/models/item/ring_stormcloud.json new file mode 100644 index 00000000..781dd2ae --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ring_stormcloud.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ring_stormcloud" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/ruined_spell_book.json b/src/main/resources/assets/ebwizardry/models/item/ruined_spell_book.json new file mode 100644 index 00000000..f8eb65a0 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/ruined_spell_book.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/ruined_spell_book" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_dust_earth.json b/src/main/resources/assets/ebwizardry/models/item/spectral_dust_earth.json new file mode 100644 index 00000000..d22acbb9 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_dust_earth.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/spectral_dust_earth" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_dust_fire.json b/src/main/resources/assets/ebwizardry/models/item/spectral_dust_fire.json new file mode 100644 index 00000000..7dbf7125 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_dust_fire.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/spectral_dust_fire" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_dust_healing.json b/src/main/resources/assets/ebwizardry/models/item/spectral_dust_healing.json new file mode 100644 index 00000000..31fb5b3a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_dust_healing.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/spectral_dust_healing" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_dust_ice.json b/src/main/resources/assets/ebwizardry/models/item/spectral_dust_ice.json new file mode 100644 index 00000000..4a1bdfd8 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_dust_ice.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/spectral_dust_ice" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_dust_lightning.json b/src/main/resources/assets/ebwizardry/models/item/spectral_dust_lightning.json new file mode 100644 index 00000000..26b8b830 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_dust_lightning.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/spectral_dust_lightning" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_dust_necromancy.json b/src/main/resources/assets/ebwizardry/models/item/spectral_dust_necromancy.json new file mode 100644 index 00000000..d79c130e --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_dust_necromancy.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/spectral_dust_necromancy" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/spectral_dust_sorcery.json b/src/main/resources/assets/ebwizardry/models/item/spectral_dust_sorcery.json new file mode 100644 index 00000000..bf7f068b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spectral_dust_sorcery.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "ebwizardry:items/spectral_dust_sorcery" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/models/item/spruce_bookshelf.json b/src/main/resources/assets/ebwizardry/models/item/spruce_bookshelf.json new file mode 100644 index 00000000..d29cc438 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spruce_bookshelf.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/spruce_bookshelf" +} diff --git a/src/main/resources/assets/ebwizardry/models/item/spruce_gilded_wood.json b/src/main/resources/assets/ebwizardry/models/item/spruce_gilded_wood.json new file mode 100644 index 00000000..0a68aba2 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spruce_gilded_wood.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/gilded_spruce_wood" +} diff --git a/src/main/resources/assets/ebwizardry/models/item/spruce_lectern.json b/src/main/resources/assets/ebwizardry/models/item/spruce_lectern.json new file mode 100644 index 00000000..4aeb91ca --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/spruce_lectern.json @@ -0,0 +1,3 @@ +{ + "parent": "ebwizardry:block/spruce_lectern" +} diff --git a/src/main/resources/assets/ebwizardry/models/item/wand_point.json b/src/main/resources/assets/ebwizardry/models/item/wand_point.json new file mode 100644 index 00000000..67d781b2 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/models/item/wand_point.json @@ -0,0 +1,25 @@ +{ + "parent": "item/handheld", + "display": { + "thirdperson_righthand": { + "rotation": [0, -90, 105], + "translation": [0, 0, -3.25], + "scale": [0.85, 0.85, 0.85] + }, + "thirdperson_lefthand": { + "rotation": [0, 90, -105], + "translation": [0, 0, -3.25], + "scale": [0.85, 0.85, 0.85] + }, + "firstperson_righthand": { + "rotation": [0, -90, 105], + "translation": [1.13, 3.2, 0.5], + "scale": [0.68, 0.68, 0.68] + }, + "firstperson_lefthand": { + "rotation": [0, 90, -105], + "translation": [1.13, 3.2, 0.5], + "scale": [0.68, 0.68, 0.68] + } + } +} diff --git a/src/main/resources/assets/ebwizardry/recipes/acacia_bookshelf.json b/src/main/resources/assets/ebwizardry/recipes/acacia_bookshelf.json new file mode 100644 index 00000000..510f7286 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/acacia_bookshelf.json @@ -0,0 +1,20 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "www", + " c ", + "www" + ], + "key": { + "c": { + "item": "ebwizardry:grand_crystal" + }, + "w": { + "item": "ebwizardry:gilded_wood", + "data": 4 + } + }, + "result": { + "item": "ebwizardry:acacia_bookshelf" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/acacia_lectern.json b/src/main/resources/assets/ebwizardry/recipes/acacia_lectern.json new file mode 100644 index 00000000..b785dbdf --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/acacia_lectern.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "wbw", + "cwc", + "www" + ], + "key": { + "b": { + "item": "minecraft:book" + }, + "c": { + "item": "ebwizardry:grand_crystal" + }, + "w": { + "item": "ebwizardry:gilded_wood", + "data": 4 + } + }, + "result": { + "item": "ebwizardry:acacia_lectern" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/birch_bookshelf.json b/src/main/resources/assets/ebwizardry/recipes/birch_bookshelf.json new file mode 100644 index 00000000..5a0ed340 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/birch_bookshelf.json @@ -0,0 +1,20 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "www", + " c ", + "www" + ], + "key": { + "c": { + "item": "ebwizardry:grand_crystal" + }, + "w": { + "item": "ebwizardry:gilded_wood", + "data": 2 + } + }, + "result": { + "item": "ebwizardry:birch_bookshelf" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/birch_lectern.json b/src/main/resources/assets/ebwizardry/recipes/birch_lectern.json new file mode 100644 index 00000000..cb4f6322 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/birch_lectern.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "wbw", + "cwc", + "www" + ], + "key": { + "b": { + "item": "minecraft:book" + }, + "c": { + "item": "ebwizardry:grand_crystal" + }, + "w": { + "item": "ebwizardry:gilded_wood", + "data": 2 + } + }, + "result": { + "item": "ebwizardry:birch_lectern" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/dark_oak_bookshelf.json b/src/main/resources/assets/ebwizardry/recipes/dark_oak_bookshelf.json new file mode 100644 index 00000000..ffad88f7 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/dark_oak_bookshelf.json @@ -0,0 +1,20 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "www", + " c ", + "www" + ], + "key": { + "c": { + "item": "ebwizardry:grand_crystal" + }, + "w": { + "item": "ebwizardry:gilded_wood", + "data": 5 + } + }, + "result": { + "item": "ebwizardry:dark_oak_bookshelf" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/dark_oak_lectern.json b/src/main/resources/assets/ebwizardry/recipes/dark_oak_lectern.json new file mode 100644 index 00000000..4f8354d8 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/dark_oak_lectern.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "wbw", + "cwc", + "www" + ], + "key": { + "b": { + "item": "minecraft:book" + }, + "c": { + "item": "ebwizardry:grand_crystal" + }, + "w": { + "item": "ebwizardry:gilded_wood", + "data": 5 + } + }, + "result": { + "item": "ebwizardry:dark_oak_lectern" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/gilded_acacia_wood.json b/src/main/resources/assets/ebwizardry/recipes/gilded_acacia_wood.json new file mode 100644 index 00000000..fda2add0 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/gilded_acacia_wood.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "www", + "wgw", + "www" + ], + "key": { + "g": { + "type": "forge:ore_dict", + "ore": "nuggetGold" + }, + "w": { + "item": "minecraft:planks", + "data": 4 + } + }, + "result": { + "item": "ebwizardry:gilded_wood", + "data": 4, + "count": 8 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/gilded_birch_wood.json b/src/main/resources/assets/ebwizardry/recipes/gilded_birch_wood.json new file mode 100644 index 00000000..0679b0eb --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/gilded_birch_wood.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "www", + "wgw", + "www" + ], + "key": { + "g": { + "type": "forge:ore_dict", + "ore": "nuggetGold" + }, + "w": { + "item": "minecraft:planks", + "data": 2 + } + }, + "result": { + "item": "ebwizardry:gilded_wood", + "data": 2, + "count": 8 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/gilded_dark_oak_wood.json b/src/main/resources/assets/ebwizardry/recipes/gilded_dark_oak_wood.json new file mode 100644 index 00000000..6ea664e4 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/gilded_dark_oak_wood.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "www", + "wgw", + "www" + ], + "key": { + "g": { + "type": "forge:ore_dict", + "ore": "nuggetGold" + }, + "w": { + "item": "minecraft:planks", + "data": 5 + } + }, + "result": { + "item": "ebwizardry:gilded_wood", + "data": 5, + "count": 8 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/gilded_jungle_wood.json b/src/main/resources/assets/ebwizardry/recipes/gilded_jungle_wood.json new file mode 100644 index 00000000..17497047 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/gilded_jungle_wood.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "www", + "wgw", + "www" + ], + "key": { + "g": { + "type": "forge:ore_dict", + "ore": "nuggetGold" + }, + "w": { + "item": "minecraft:planks", + "data": 3 + } + }, + "result": { + "item": "ebwizardry:gilded_wood", + "data": 3, + "count": 8 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/gilded_oak_wood.json b/src/main/resources/assets/ebwizardry/recipes/gilded_oak_wood.json new file mode 100644 index 00000000..18da37be --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/gilded_oak_wood.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "www", + "wgw", + "www" + ], + "key": { + "g": { + "type": "forge:ore_dict", + "ore": "nuggetGold" + }, + "w": { + "item": "minecraft:planks", + "data": 0 + } + }, + "result": { + "item": "ebwizardry:gilded_wood", + "data": 0, + "count": 8 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/gilded_spruce_wood.json b/src/main/resources/assets/ebwizardry/recipes/gilded_spruce_wood.json new file mode 100644 index 00000000..ec5c03e1 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/gilded_spruce_wood.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "www", + "wgw", + "www" + ], + "key": { + "g": { + "type": "forge:ore_dict", + "ore": "nuggetGold" + }, + "w": { + "item": "minecraft:planks", + "data": 1 + } + }, + "result": { + "item": "ebwizardry:gilded_wood", + "data": 1, + "count": 8 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/jungle_bookshelf.json b/src/main/resources/assets/ebwizardry/recipes/jungle_bookshelf.json new file mode 100644 index 00000000..20676b56 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/jungle_bookshelf.json @@ -0,0 +1,20 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "www", + " c ", + "www" + ], + "key": { + "c": { + "item": "ebwizardry:grand_crystal" + }, + "w": { + "item": "ebwizardry:gilded_wood", + "data": 3 + } + }, + "result": { + "item": "ebwizardry:jungle_bookshelf" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/jungle_lectern.json b/src/main/resources/assets/ebwizardry/recipes/jungle_lectern.json new file mode 100644 index 00000000..f1e85736 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/jungle_lectern.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "wbw", + "cwc", + "www" + ], + "key": { + "b": { + "item": "minecraft:book" + }, + "c": { + "item": "ebwizardry:grand_crystal" + }, + "w": { + "item": "ebwizardry:gilded_wood", + "data": 3 + } + }, + "result": { + "item": "ebwizardry:jungle_lectern" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/oak_bookshelf.json b/src/main/resources/assets/ebwizardry/recipes/oak_bookshelf.json new file mode 100644 index 00000000..be8a2963 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/oak_bookshelf.json @@ -0,0 +1,20 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "www", + " c ", + "www" + ], + "key": { + "c": { + "item": "ebwizardry:grand_crystal" + }, + "w": { + "item": "ebwizardry:gilded_wood", + "data": 0 + } + }, + "result": { + "item": "ebwizardry:oak_bookshelf" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/oak_lectern.json b/src/main/resources/assets/ebwizardry/recipes/oak_lectern.json new file mode 100644 index 00000000..e791bc85 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/oak_lectern.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "wbw", + "cwc", + "www" + ], + "key": { + "b": { + "item": "minecraft:book" + }, + "c": { + "item": "ebwizardry:grand_crystal" + }, + "w": { + "item": "ebwizardry:gilded_wood", + "data": 0 + } + }, + "result": { + "item": "ebwizardry:oak_lectern" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/receptacle.json b/src/main/resources/assets/ebwizardry/recipes/receptacle.json new file mode 100644 index 00000000..21743a82 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/receptacle.json @@ -0,0 +1,19 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "gpg", + " g " + ], + "key": { + "g": { + "type": "forge:ore_dict", + "ore": "ingotGold" + }, + "p": { + "item": "minecraft:flower_pot" + } + }, + "result": { + "item": "ebwizardry:receptacle" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/spruce_bookshelf.json b/src/main/resources/assets/ebwizardry/recipes/spruce_bookshelf.json new file mode 100644 index 00000000..291fb0c3 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/spruce_bookshelf.json @@ -0,0 +1,20 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "www", + " c ", + "www" + ], + "key": { + "c": { + "item": "ebwizardry:grand_crystal" + }, + "w": { + "item": "ebwizardry:gilded_wood", + "data": 1 + } + }, + "result": { + "item": "ebwizardry:spruce_bookshelf" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/recipes/spruce_lectern.json b/src/main/resources/assets/ebwizardry/recipes/spruce_lectern.json new file mode 100644 index 00000000..459a8c38 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/recipes/spruce_lectern.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "wbw", + "cwc", + "www" + ], + "key": { + "b": { + "item": "minecraft:book" + }, + "c": { + "item": "ebwizardry:grand_crystal" + }, + "w": { + "item": "ebwizardry:gilded_wood", + "data": 1 + } + }, + "result": { + "item": "ebwizardry:spruce_lectern" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/sounds.json b/src/main/resources/assets/ebwizardry/sounds.json index 75efd3a7..da8d9c34 100644 --- a/src/main/resources/assets/ebwizardry/sounds.json +++ b/src/main/resources/assets/ebwizardry/sounds.json @@ -2,22 +2,36 @@ "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"]}, + "block.lectern.locate_spell": {"category": "blocks", "sounds": ["ebwizardry:conjure"]}, + "block.receptacle.ignite": {"category": "blocks", "sounds": ["mob/evocation_illager/cast1", "mob/evocation_illager/cast2"]}, + "block.imbuement_altar.imbue": {"category": "blocks", "sounds": ["ebwizardry:imbue"]}, "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.wand.chargeup": {"category": "player", "sounds": ["ebwizardry:chargeup"]}, "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:buff_large"]}, - + "item.mana_flask.use": {"category": "player", "sounds": ["item/bottle/fill1", "item/bottle/fill2", "item/bottle/fill3", "item/bottle/fill4"]}, + "item.mana_flask.recharge": {"category": "player", "sounds": ["ebwizardry:conjure"]}, + "item.flamecatcher.shoot": {"category": "player", "sounds": ["fireworks/launch1"]}, + "item.flamecatcher.flame": {"category": "player", "sounds": ["ebwizardry:combust"]}, + "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.black_hole.break_block": {"category": "spells", "sounds": ["mob/zombie/woodbreak"]}, "entity.blizzard.ambient": {"category": "spells", "sounds": ["ebwizardry:wind"]}, + "entity.boulder.roll": {"category": "spells", "sounds": ["ebwizardry:roll"]}, + "entity.boulder.land": {"category": "spells", "sounds": ["ebwizardry:rumble"]}, + "entity.boulder.hit": {"category": "spells", "sounds": ["damage/hit1", "damage/hit2", "damage/hit3"]}, + "entity.boulder.break_block": {"category": "spells", "sounds": ["mob/zombie/woodbreak"]}, + "entity.bubble.pop": {"category": "spells", "sounds": ["random/pop"]}, "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.fire_sigil.trigger": {"category": "spells", "sounds": ["ebwizardry:combust"]}, + "entity.forcefield.ambient": {"category": "spells", "sounds": ["ebwizardry:forcefield"]}, "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"]}, @@ -25,11 +39,20 @@ "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.ice_barrier.deflect": {"category": "spells", "sounds": ["random/anvil_land"]}, + "entity.ice_barrier.extend": {"category": "spells", "sounds": ["ebwizardry:slice"]}, + "entity.ice_spike.extend": {"category": "spells", "sounds": ["ebwizardry:slice"]}, "entity.lightning_sigil.trigger": {"category": "spells", "sounds": ["ebwizardry:arc1", "ebwizardry:arc2", "ebwizardry:arc3"]}, "entity.meteor.falling": {"category": "spells", "sounds": ["ebwizardry:flames_loop"]}, + "entity.radiant_totem.ambient": {"category": "spells", "sounds": ["ebwizardry:beam_loop"]}, + "entity.radiant_totem.vanish": {"category": "spells", "sounds": ["ebwizardry:beam_end"]}, "entity.shield.deflect": {"category": "spells", "sounds": ["ebwizardry:effect1", "ebwizardry:effect2"]}, + "entity.stormcloud.thunder": {"category": "spells", "sounds": ["ambient/weather/thunder1", "ambient/weather/thunder2", "ambient/weather/thunder3"]}, + "entity.stormcloud.attack": {"category": "spells", "sounds": ["ebwizardry:arc1", "ebwizardry:arc2", "ebwizardry:arc3"]}, "entity.tornado.ambient": {"category": "spells", "sounds": ["ebwizardry:wind"]}, + "entity.withering_totem.ambient": {"category": "spells", "sounds": ["ebwizardry:dark_aura_loop"]}, + "entity.withering_totem.explode": {"category": "spells", "sounds": ["mob/wither/death"]}, + "entity.zombie_spawner.spawn": {"category": "spells", "sounds": ["mob/guardian/elder_death"]}, "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"]}, @@ -45,6 +68,9 @@ "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.remnant.ambient": {"category": "hostile", "sounds": ["ebwizardry:remnant_hiss"]}, + "entity.remnant.hurt": {"category": "hostile", "sounds": ["ebwizardry:remnant_hurt"]}, + "entity.remnant.death": {"category": "hostile", "sounds": ["ebwizardry:remnant_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"]}, @@ -70,7 +96,8 @@ "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.firebomb.fire": {"category": "spells", "sounds": ["ebwizardry:combust"]}, + "entity.flamecatcher_arrow.hit": {"category": "spells", "sounds": ["damage/hit1", "damage/hit2", "damage/hit3"]}, "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"]}, @@ -102,7 +129,8 @@ "misc.book_open": {"category": "player", "sounds": ["ebwizardry:book"]}, "misc.freeze": {"category": "spells", "sounds": ["ebwizardry:freeze"]}, - + "misc.spell_fail": {"category": "spells", "sounds": ["random/fizz"]}, + "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"]}, @@ -110,15 +138,18 @@ "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.blinding_flash": {"category": "spells", "sounds": ["ebwizardry:flash"]}, "spell.blink": {"category": "spells", "sounds": ["mob/endermen/portal", "mob/endermen/portal2"]}, - "spell.blizzard": {"category": "spells", "sounds": ["ebwizardry:ice"]}, + "spell.blizzard": {"category": "spells", "sounds": ["ebwizardry:ice_age"]}, + "spell.boulder": {"category": "spells", "sounds": ["mob/evocation_illager/prepare_attack1", "mob/evocation_illager/prepare_attack2"]}, "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.celestial_smite": {"category": "spells", "sounds": ["mob/illusion_illager/prepare_blind"]}, "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.combustion_rune": {"category": "spells", "sounds": ["ebwizardry:combust"]}, "spell.containment": {"category": "spells", "sounds": ["mob/guardian/curse"]}, "spell.conjure_armour": {"category": "spells", "sounds": ["ebwizardry:conjure"]}, "spell.conjure_block": {"category": "spells", "sounds": ["ebwizardry:conjure"]}, @@ -142,17 +173,21 @@ "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.enrage": {"category": "spells", "sounds": ["mob/wither/idle1", "mob/wither/idle2", "mob/wither/idle3", "mob/wither/idle4"]}, "spell.entrapment": {"category": "spells", "sounds": ["mob/evocation_illager/prepare_attack1", "mob/evocation_illager/prepare_attack2"]}, "spell.evade": {"category": "spells", "sounds": ["random/bow"]}, + "spell.fangs": {"category": "spells", "sounds": ["mob/evocation_illager/prepare_attack1", "mob/evocation_illager/prepare_attack2"]}, "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.firestorm": {"category": "spells", "sounds": ["ebwizardry:firestorm"]}, "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.flamecatcher": {"category": "spells", "sounds": ["ebwizardry:buff"]}, "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"]}, @@ -169,9 +204,10 @@ "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_barrier": {"category": "spells", "sounds": []}, + "spell.frost_ray.start": {"category": "spells", "sounds": ["ebwizardry:frost_start"]}, + "spell.frost_ray.loop": {"category": "spells", "sounds": ["ebwizardry:frost_loop"]}, + "spell.frost_ray.end": {"category": "spells", "sounds": ["ebwizardry:frost_end"]}, "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}]}, @@ -187,18 +223,19 @@ "spell.greater_ward": {"category": "spells", "sounds": ["ebwizardry:buff_large"]}, "spell.group_heal": {"category": "spells", "sounds": ["ebwizardry:heal"]}, "spell.growth_aura": {"category": "spells", "sounds": ["ebwizardry:conjure_large"]}, + "spell.guardian_beam": {"category": "spells", "sounds": ["mob/guardian/attack_loop"]}, "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.iceball": {"category": "spells", "sounds": ["ebwizardry:whoosh1", "ebwizardry:whoosh2"]}, + "spell.ice_age": {"category": "spells", "sounds": ["ebwizardry:ice_age"]}, + "spell.ice_charge": {"category": "spells", "sounds": ["ebwizardry:whoosh1", "ebwizardry:whoosh2"]}, "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_spikes": {"category": "spells", "sounds": ["ebwizardry:whoosh1", "ebwizardry:whoosh2"]}, "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"]}, @@ -229,7 +266,8 @@ "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.magic_missile": {"category": "spells", "sounds": ["ebwizardry:magic"]}, + "spell.mark_sacrifice": {"category": "spells", "sounds": ["mob/guardian/curse"]}, "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"]}, @@ -239,23 +277,27 @@ "spell.none": {"category": "spells", "sounds": []}, "spell.oakflesh": {"category": "spells", "sounds": ["ebwizardry:buff"]}, "spell.paralysis": {"category": "spells", "sounds": ["ebwizardry:shockwave"]}, + "spell.permafrost.start": {"category": "spells", "sounds": ["ebwizardry:frost_start"]}, + "spell.permafrost.loop": {"category": "spells", "sounds": ["ebwizardry:frost_loop"]}, + "spell.permafrost.end": {"category": "spells", "sounds": ["ebwizardry:frost_end"]}, "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": {"category": "spells", "sounds": ["ebwizardry:whoosh1", "ebwizardry:whoosh2"]}, "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.ray_of_purification.start": {"category": "spells", "sounds": ["ebwizardry:beam_start"]}, + "spell.ray_of_purification.loop": {"category": "spells", "sounds": ["ebwizardry:beam_loop"]}, + "spell.ray_of_purification.end": {"category": "spells", "sounds": ["ebwizardry:beam_end"]}, + "spell.radiant_totem": {"category": "spells", "sounds": ["ebwizardry:beam_start"]}, "spell.remove_curse": {"category": "spells", "sounds": ["ebwizardry:buff_large"]}, "spell.replenish_hunger": {"category": "spells", "sounds": ["ebwizardry:heal"]}, "spell.resurrection": {"category": "spells", "sounds": ["ebwizardry:buff_large"]}, "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.ring_of_fire": {"category": "spells", "sounds": ["ebwizardry:combust"]}, "spell.satiety": {"category": "spells", "sounds": ["ebwizardry:buff_large"]}, "spell.shadow_ward.start": {"category": "spells", "sounds": ["ebwizardry:dark_aura_start"]}, "spell.shadow_ward.loop": {"category": "spells", "sounds": ["ebwizardry:dark_aura_loop"]}, @@ -267,7 +309,7 @@ "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.shoot": {"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"]}, @@ -281,6 +323,7 @@ "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.stormcloud": {"category": "spells", "sounds": ["ambient/weather/thunder1", "ambient/weather/thunder2", "ambient/weather/thunder3"]}, "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"]}, @@ -299,20 +342,24 @@ "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.tornado": {"category": "spells", "sounds": ["ebwizardry:zoom1", "ebwizardry:zoom2", "ebwizardry:zoom3"]}, "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.wall_of_frost.start": {"category": "spells", "sounds": ["ebwizardry:frost_start"]}, + "spell.wall_of_frost.loop": {"category": "spells", "sounds": ["ebwizardry:frost_loop"]}, + "spell.wall_of_frost.end": {"category": "spells", "sounds": ["ebwizardry:frost_end"]}, "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"]}, + "spell.withering_totem": {"category": "spells", "sounds": ["ebwizardry:beam_start"]}, + "spell.zombie_apocalypse.start": {"category": "spells", "sounds": ["ebwizardry:dark_aura_start"]}, + "spell.zombie_apocalypse.loop": {"category": "spells", "sounds": ["ebwizardry:dark_aura_loop"]}, + "spell.zombie_apocalypse.end": {"category": "spells", "sounds": ["ebwizardry:dark_aura_end"]}, "forfeit.burn_self": {"category": "spells", "sounds": ["fire/ignite"]}, "forfeit.fireball": {"category": "spells", "sounds": ["mob/ghast/fireball4"]}, @@ -324,7 +371,7 @@ "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.blizzard": {"category": "spells", "sounds": ["ebwizardry:ice_age"]}, "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"]}, @@ -343,7 +390,7 @@ "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.poison_self": {"category": "spells", "sounds": ["random/fizz"]}, "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"]}, diff --git a/src/main/resources/assets/ebwizardry/sounds/beam_end.ogg b/src/main/resources/assets/ebwizardry/sounds/beam_end.ogg new file mode 100644 index 00000000..9e64fd4c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/beam_end.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/beam_loop.ogg b/src/main/resources/assets/ebwizardry/sounds/beam_loop.ogg new file mode 100644 index 00000000..230d616e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/beam_loop.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/beam_start.ogg b/src/main/resources/assets/ebwizardry/sounds/beam_start.ogg new file mode 100644 index 00000000..4eb74dff Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/beam_start.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/chargeup.ogg b/src/main/resources/assets/ebwizardry/sounds/chargeup.ogg new file mode 100644 index 00000000..2ecb1a9a Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/chargeup.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/combust.ogg b/src/main/resources/assets/ebwizardry/sounds/combust.ogg new file mode 100644 index 00000000..3ff943b7 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/combust.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/crackle.ogg b/src/main/resources/assets/ebwizardry/sounds/crackle.ogg deleted file mode 100644 index 44a499bf..00000000 Binary files a/src/main/resources/assets/ebwizardry/sounds/crackle.ogg and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/sounds/firestorm.ogg b/src/main/resources/assets/ebwizardry/sounds/firestorm.ogg new file mode 100644 index 00000000..5694f58b Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/firestorm.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/flash.ogg b/src/main/resources/assets/ebwizardry/sounds/flash.ogg new file mode 100644 index 00000000..3323b004 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/flash.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/forcefield.ogg b/src/main/resources/assets/ebwizardry/sounds/forcefield.ogg new file mode 100644 index 00000000..eb00e697 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/forcefield.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/frost_end.ogg b/src/main/resources/assets/ebwizardry/sounds/frost_end.ogg new file mode 100644 index 00000000..eb3ac5a8 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/frost_end.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/frost_loop.ogg b/src/main/resources/assets/ebwizardry/sounds/frost_loop.ogg new file mode 100644 index 00000000..5388f726 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/frost_loop.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/frost_start.ogg b/src/main/resources/assets/ebwizardry/sounds/frost_start.ogg new file mode 100644 index 00000000..2bc364fe Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/frost_start.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/frostray.ogg b/src/main/resources/assets/ebwizardry/sounds/frostray.ogg deleted file mode 100644 index 4117e4e6..00000000 Binary files a/src/main/resources/assets/ebwizardry/sounds/frostray.ogg and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/sounds/grow.ogg b/src/main/resources/assets/ebwizardry/sounds/grow.ogg index 5fd63112..3776cef8 100644 Binary files a/src/main/resources/assets/ebwizardry/sounds/grow.ogg and b/src/main/resources/assets/ebwizardry/sounds/grow.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/ice_age.ogg b/src/main/resources/assets/ebwizardry/sounds/ice_age.ogg new file mode 100644 index 00000000..533eb5c5 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/ice_age.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/imbue.ogg b/src/main/resources/assets/ebwizardry/sounds/imbue.ogg new file mode 100644 index 00000000..fd2183cb Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/imbue.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/magic1.ogg b/src/main/resources/assets/ebwizardry/sounds/magic.ogg similarity index 100% rename from src/main/resources/assets/ebwizardry/sounds/magic1.ogg rename to src/main/resources/assets/ebwizardry/sounds/magic.ogg diff --git a/src/main/resources/assets/ebwizardry/sounds/magic2.ogg b/src/main/resources/assets/ebwizardry/sounds/magic2.ogg deleted file mode 100644 index 8f51fb1b..00000000 Binary files a/src/main/resources/assets/ebwizardry/sounds/magic2.ogg and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/sounds/magic3.ogg b/src/main/resources/assets/ebwizardry/sounds/magic3.ogg deleted file mode 100644 index a8048bae..00000000 Binary files a/src/main/resources/assets/ebwizardry/sounds/magic3.ogg and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/sounds/magic4.ogg b/src/main/resources/assets/ebwizardry/sounds/magic4.ogg deleted file mode 100644 index ff88573a..00000000 Binary files a/src/main/resources/assets/ebwizardry/sounds/magic4.ogg and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/sounds/remnant_death.ogg b/src/main/resources/assets/ebwizardry/sounds/remnant_death.ogg new file mode 100644 index 00000000..4607b82b Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/remnant_death.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/remnant_hiss.ogg b/src/main/resources/assets/ebwizardry/sounds/remnant_hiss.ogg new file mode 100644 index 00000000..8d81d0d7 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/remnant_hiss.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/remnant_hurt.ogg b/src/main/resources/assets/ebwizardry/sounds/remnant_hurt.ogg new file mode 100644 index 00000000..da59842f Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/remnant_hurt.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/roll.ogg b/src/main/resources/assets/ebwizardry/sounds/roll.ogg new file mode 100644 index 00000000..62a91264 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/roll.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/slice.ogg b/src/main/resources/assets/ebwizardry/sounds/slice.ogg new file mode 100644 index 00000000..2ecd384c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/slice.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/whoosh1.ogg b/src/main/resources/assets/ebwizardry/sounds/whoosh1.ogg new file mode 100644 index 00000000..84f2fa76 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/whoosh1.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/whoosh2.ogg b/src/main/resources/assets/ebwizardry/sounds/whoosh2.ogg new file mode 100644 index 00000000..18545456 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/whoosh2.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/zoom1.ogg b/src/main/resources/assets/ebwizardry/sounds/zoom1.ogg new file mode 100644 index 00000000..e19cb4f2 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/zoom1.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/zoom2.ogg b/src/main/resources/assets/ebwizardry/sounds/zoom2.ogg new file mode 100644 index 00000000..492a9f5c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/zoom2.ogg differ diff --git a/src/main/resources/assets/ebwizardry/sounds/zoom3.ogg b/src/main/resources/assets/ebwizardry/sounds/zoom3.ogg new file mode 100644 index 00000000..fd407cc4 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/sounds/zoom3.ogg differ diff --git a/src/main/resources/assets/ebwizardry/spells/arcane_jammer.json b/src/main/resources/assets/ebwizardry/spells/arcane_jammer.json index bdd61d41..336ca013 100644 --- a/src/main/resources/assets/ebwizardry/spells/arcane_jammer.json +++ b/src/main/resources/assets/ebwizardry/spells/arcane_jammer.json @@ -12,12 +12,13 @@ }, "tier": "advanced", "element": "healing", - "type": "attack", + "type": "alteration", "cost": 30, - "chargeup": 0, + "chargeup": 5, "cooldown": 50, "base_properties": { "range": 10, - "effect_duration": 300 + "effect_duration": 300, + "effect_strength": 0 } } \ 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 index e0c5602b..903112d6 100644 --- a/src/main/resources/assets/ebwizardry/spells/arrow_rain.json +++ b/src/main/resources/assets/ebwizardry/spells/arrow_rain.json @@ -14,10 +14,11 @@ "element": "sorcery", "type": "attack", "cost": 75, - "chargeup": 0, + "chargeup": 20, "cooldown": 300, "base_properties": { "range": 20, - "duration": 120 + "duration": 120, + "effect_radius": 2 } } \ 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 index 290ad352..eeb5f3d9 100644 --- a/src/main/resources/assets/ebwizardry/spells/black_hole.json +++ b/src/main/resources/assets/ebwizardry/spells/black_hole.json @@ -14,10 +14,11 @@ "element": "sorcery", "type": "construct", "cost": 150, - "chargeup": 0, + "chargeup": 25, "cooldown": 400, "base_properties": { "duration": 400, - "range": 10 + "range": 10, + "effect_radius": 3 } } \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/blinding_flash.json b/src/main/resources/assets/ebwizardry/spells/blinding_flash.json new file mode 100644 index 00000000..b716c742 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/blinding_flash.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": "alteration", + "cost": 20, + "chargeup": 0, + "cooldown": 60, + "base_properties": { + "effect_radius": 5, + "effect_duration": 300 + } +} \ 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 index cda68e4c..b467d1c2 100644 --- a/src/main/resources/assets/ebwizardry/spells/blizzard.json +++ b/src/main/resources/assets/ebwizardry/spells/blizzard.json @@ -14,7 +14,7 @@ "element": "ice", "type": "construct", "cost": 40, - "chargeup": 0, + "chargeup": 10, "cooldown": 100, "base_properties": { "duration": 600, diff --git a/src/main/resources/assets/ebwizardry/spells/boulder.json b/src/main/resources/assets/ebwizardry/spells/boulder.json new file mode 100644 index 00000000..6c1b1252 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/boulder.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": 125, + "chargeup": 25, + "cooldown": 350, + "base_properties": { + "duration": 200, + "speed": 0.33, + "damage": 10, + "knockback_strength": 1 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/celestial_smite.json b/src/main/resources/assets/ebwizardry/spells/celestial_smite.json new file mode 100644 index 00000000..00c571b2 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/celestial_smite.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": "attack", + "cost": 100, + "chargeup": 25, + "cooldown": 200, + "base_properties": { + "range": 40, + "effect_radius": 3, + "damage": 8, + "burn_duration": 10 + } +} \ 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 index 0f7fcc3c..3eec3ace 100644 --- a/src/main/resources/assets/ebwizardry/spells/chain_lightning.json +++ b/src/main/resources/assets/ebwizardry/spells/chain_lightning.json @@ -14,7 +14,7 @@ "element": "lightning", "type": "attack", "cost": 25, - "chargeup": 0, + "chargeup": 5, "cooldown": 50, "base_properties": { "primary_damage": 10, diff --git a/src/main/resources/assets/ebwizardry/spells/clairvoyance.json b/src/main/resources/assets/ebwizardry/spells/clairvoyance.json index e8b43887..3ec742cc 100644 --- a/src/main/resources/assets/ebwizardry/spells/clairvoyance.json +++ b/src/main/resources/assets/ebwizardry/spells/clairvoyance.json @@ -14,7 +14,7 @@ "element": "sorcery", "type": "utility", "cost": 20, - "chargeup": 0, + "chargeup": 10, "cooldown": 100, "base_properties": { "range": 256, diff --git a/src/main/resources/assets/ebwizardry/spells/conjure_armour.json b/src/main/resources/assets/ebwizardry/spells/conjure_armour.json index 2cdb95f9..078b543b 100644 --- a/src/main/resources/assets/ebwizardry/spells/conjure_armour.json +++ b/src/main/resources/assets/ebwizardry/spells/conjure_armour.json @@ -11,10 +11,10 @@ "looting": true }, "tier": "advanced", - "element": "healing", + "element": "sorcery", "type": "defence", "cost": 45, - "chargeup": 0, + "chargeup": 10, "cooldown": 50, "base_properties": { "item_lifetime": 1800 diff --git a/src/main/resources/assets/ebwizardry/spells/containment.json b/src/main/resources/assets/ebwizardry/spells/containment.json index 4c5d86c5..0626f068 100644 --- a/src/main/resources/assets/ebwizardry/spells/containment.json +++ b/src/main/resources/assets/ebwizardry/spells/containment.json @@ -14,7 +14,7 @@ "element": "sorcery", "type": "alteration", "cost": 40, - "chargeup": 0, + "chargeup": 5, "cooldown": 60, "base_properties": { "range": 10, diff --git a/src/main/resources/assets/ebwizardry/spells/cure_effects.json b/src/main/resources/assets/ebwizardry/spells/cure_effects.json index 5d9c7b3e..8121a2df 100644 --- a/src/main/resources/assets/ebwizardry/spells/cure_effects.json +++ b/src/main/resources/assets/ebwizardry/spells/cure_effects.json @@ -14,7 +14,7 @@ "element": "healing", "type": "defence", "cost": 25, - "chargeup": 0, + "chargeup": 10, "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 index 8fbb4684..0f39a656 100644 --- a/src/main/resources/assets/ebwizardry/spells/curse_of_enfeeblement.json +++ b/src/main/resources/assets/ebwizardry/spells/curse_of_enfeeblement.json @@ -14,7 +14,7 @@ "element": "necromancy", "type": "alteration", "cost": 60, - "chargeup": 0, + "chargeup": 20, "cooldown": 150, "base_properties": { "range": 10, diff --git a/src/main/resources/assets/ebwizardry/spells/curse_of_soulbinding.json b/src/main/resources/assets/ebwizardry/spells/curse_of_soulbinding.json index 12adc02a..bfac23df 100644 --- a/src/main/resources/assets/ebwizardry/spells/curse_of_soulbinding.json +++ b/src/main/resources/assets/ebwizardry/spells/curse_of_soulbinding.json @@ -14,7 +14,7 @@ "element": "necromancy", "type": "alteration", "cost": 35, - "chargeup": 0, + "chargeup": 10, "cooldown": 100, "base_properties": { "range": 10 diff --git a/src/main/resources/assets/ebwizardry/spells/curse_of_undeath.json b/src/main/resources/assets/ebwizardry/spells/curse_of_undeath.json index f77ecc30..5e490e3f 100644 --- a/src/main/resources/assets/ebwizardry/spells/curse_of_undeath.json +++ b/src/main/resources/assets/ebwizardry/spells/curse_of_undeath.json @@ -14,7 +14,7 @@ "element": "necromancy", "type": "alteration", "cost": 40, - "chargeup": 0, + "chargeup": 10, "cooldown": 100, "base_properties": { "range": 10, diff --git a/src/main/resources/assets/ebwizardry/spells/decay.json b/src/main/resources/assets/ebwizardry/spells/decay.json index 1c3673ef..b2ca5024 100644 --- a/src/main/resources/assets/ebwizardry/spells/decay.json +++ b/src/main/resources/assets/ebwizardry/spells/decay.json @@ -14,7 +14,7 @@ "element": "necromancy", "type": "attack", "cost": 50, - "chargeup": 0, + "chargeup": 10, "cooldown": 200, "base_properties": { "range": 12, diff --git a/src/main/resources/assets/ebwizardry/spells/decoy.json b/src/main/resources/assets/ebwizardry/spells/decoy.json index 6d684c52..0a53ef69 100644 --- a/src/main/resources/assets/ebwizardry/spells/decoy.json +++ b/src/main/resources/assets/ebwizardry/spells/decoy.json @@ -14,7 +14,7 @@ "element": "sorcery", "type": "utility", "cost": 40, - "chargeup": 0, + "chargeup": 10, "cooldown": 200, "base_properties": { "decoy_lifetime": 600, diff --git a/src/main/resources/assets/ebwizardry/spells/diamondflesh.json b/src/main/resources/assets/ebwizardry/spells/diamondflesh.json index 9a969105..2867ca12 100644 --- a/src/main/resources/assets/ebwizardry/spells/diamondflesh.json +++ b/src/main/resources/assets/ebwizardry/spells/diamondflesh.json @@ -14,7 +14,7 @@ "element": "healing", "type": "defence", "cost": 100, - "chargeup": 0, + "chargeup": 25, "cooldown": 300, "base_properties": { "resistance_duration": 600, diff --git a/src/main/resources/assets/ebwizardry/spells/earthquake.json b/src/main/resources/assets/ebwizardry/spells/earthquake.json index 18401a07..90616d27 100644 --- a/src/main/resources/assets/ebwizardry/spells/earthquake.json +++ b/src/main/resources/assets/ebwizardry/spells/earthquake.json @@ -14,7 +14,7 @@ "element": "earth", "type": "attack", "cost": 75, - "chargeup": 0, + "chargeup": 25, "cooldown": 250, "base_properties": { "effect_radius": 8, diff --git a/src/main/resources/assets/ebwizardry/spells/enrage.json b/src/main/resources/assets/ebwizardry/spells/enrage.json new file mode 100644 index 00000000..af7ce24a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/enrage.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": "alteration", + "cost": 20, + "chargeup": 0, + "cooldown": 100, + "base_properties": { + "effect_radius": 8 + } +} \ 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 index c6dc3d24..2e973aa7 100644 --- a/src/main/resources/assets/ebwizardry/spells/entrapment.json +++ b/src/main/resources/assets/ebwizardry/spells/entrapment.json @@ -14,7 +14,7 @@ "element": "necromancy", "type": "attack", "cost": 35, - "chargeup": 0, + "chargeup": 10, "cooldown": 75, "base_properties": { "range": 10, diff --git a/src/main/resources/assets/ebwizardry/spells/fangs.json b/src/main/resources/assets/ebwizardry/spells/fangs.json new file mode 100644 index 00000000..928db480 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/fangs.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": "attack", + "cost": 40, + "chargeup": 5, + "cooldown": 60, + "base_properties": { + "range": 10 + } +} \ 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 index aaf9401b..b4d9e135 100644 --- a/src/main/resources/assets/ebwizardry/spells/fire_breath.json +++ b/src/main/resources/assets/ebwizardry/spells/fire_breath.json @@ -14,8 +14,8 @@ "element": "fire", "type": "attack", "cost": 15, - "chargeup": 0, - "cooldown": 0, + "chargeup": 15, + "cooldown": 70, "base_properties": { "range": 10, "damage": 6, diff --git a/src/main/resources/assets/ebwizardry/spells/fire_resistance.json b/src/main/resources/assets/ebwizardry/spells/fire_resistance.json index e143b964..2ac239a6 100644 --- a/src/main/resources/assets/ebwizardry/spells/fire_resistance.json +++ b/src/main/resources/assets/ebwizardry/spells/fire_resistance.json @@ -14,7 +14,7 @@ "element": "fire", "type": "defence", "cost": 20, - "chargeup": 0, + "chargeup": 15, "cooldown": 80, "base_properties": { "fire_resistance_duration": 600, diff --git a/src/main/resources/assets/ebwizardry/spells/fire_sigil.json b/src/main/resources/assets/ebwizardry/spells/fire_sigil.json index 62816eca..a5b2d64e 100644 --- a/src/main/resources/assets/ebwizardry/spells/fire_sigil.json +++ b/src/main/resources/assets/ebwizardry/spells/fire_sigil.json @@ -18,6 +18,7 @@ "cooldown": 20, "base_properties": { "range": 10, + "effect_radius": 1, "damage": 6, "burn_duration": 10 } diff --git a/src/main/resources/assets/ebwizardry/spells/firestorm.json b/src/main/resources/assets/ebwizardry/spells/firestorm.json new file mode 100644 index 00000000..9461f620 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/firestorm.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": 80, + "chargeup": 20, + "cooldown": 250, + "base_properties": { + "effect_radius": 6, + "burn_duration": 15 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/flamecatcher.json b/src/main/resources/assets/ebwizardry/spells/flamecatcher.json new file mode 100644 index 00000000..4b03cd44 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/flamecatcher.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": "master", + "element": "fire", + "type": "utility", + "cost": 100, + "chargeup": 20, + "cooldown": 150, + "base_properties": { + "item_lifetime": 900, + "range": 40, + "shot_count": 5, + "damage": 16, + "burn_duration": 15 + } +} \ 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 index cbfab3ec..3014de16 100644 --- a/src/main/resources/assets/ebwizardry/spells/flaming_axe.json +++ b/src/main/resources/assets/ebwizardry/spells/flaming_axe.json @@ -14,7 +14,7 @@ "element": "fire", "type": "utility", "cost": 45, - "chargeup": 0, + "chargeup": 10, "cooldown": 50, "base_properties": { "item_lifetime": 1200, diff --git a/src/main/resources/assets/ebwizardry/spells/font_of_mana.json b/src/main/resources/assets/ebwizardry/spells/font_of_mana.json index c4bac0d8..66da2de7 100644 --- a/src/main/resources/assets/ebwizardry/spells/font_of_mana.json +++ b/src/main/resources/assets/ebwizardry/spells/font_of_mana.json @@ -14,7 +14,7 @@ "element": "healing", "type": "utility", "cost": 100, - "chargeup": 0, + "chargeup": 15, "cooldown": 250, "base_properties": { "effect_radius": 5, diff --git a/src/main/resources/assets/ebwizardry/spells/font_of_vitality.json b/src/main/resources/assets/ebwizardry/spells/font_of_vitality.json index f858b6ed..4d486150 100644 --- a/src/main/resources/assets/ebwizardry/spells/font_of_vitality.json +++ b/src/main/resources/assets/ebwizardry/spells/font_of_vitality.json @@ -14,7 +14,7 @@ "element": "healing", "type": "defence", "cost": 75, - "chargeup": 0, + "chargeup": 20, "cooldown": 300, "base_properties": { "absorption_duration": 1200, diff --git a/src/main/resources/assets/ebwizardry/spells/forcefield.json b/src/main/resources/assets/ebwizardry/spells/forcefield.json index bb6cbf3a..9e34fe86 100644 --- a/src/main/resources/assets/ebwizardry/spells/forcefield.json +++ b/src/main/resources/assets/ebwizardry/spells/forcefield.json @@ -14,7 +14,7 @@ "element": "healing", "type": "defence", "cost": 45, - "chargeup": 0, + "chargeup": 15, "cooldown": 200, "base_properties": { "duration": 600, diff --git a/src/main/resources/assets/ebwizardry/spells/forest_of_thorns.json b/src/main/resources/assets/ebwizardry/spells/forest_of_thorns.json index 4d5e2414..a8d44cad 100644 --- a/src/main/resources/assets/ebwizardry/spells/forest_of_thorns.json +++ b/src/main/resources/assets/ebwizardry/spells/forest_of_thorns.json @@ -14,7 +14,7 @@ "element": "earth", "type": "construct", "cost": 100, - "chargeup": 0, + "chargeup": 20, "cooldown": 250, "base_properties": { "effect_radius": 3, diff --git a/src/main/resources/assets/ebwizardry/spells/forests_curse.json b/src/main/resources/assets/ebwizardry/spells/forests_curse.json index 1e12f461..fb84dc46 100644 --- a/src/main/resources/assets/ebwizardry/spells/forests_curse.json +++ b/src/main/resources/assets/ebwizardry/spells/forests_curse.json @@ -14,7 +14,7 @@ "element": "earth", "type": "attack", "cost": 75, - "chargeup": 0, + "chargeup": 15, "cooldown": 200, "base_properties": { "effect_radius": 5, diff --git a/src/main/resources/assets/ebwizardry/spells/frost_axe.json b/src/main/resources/assets/ebwizardry/spells/frost_axe.json index e0d8912f..594de077 100644 --- a/src/main/resources/assets/ebwizardry/spells/frost_axe.json +++ b/src/main/resources/assets/ebwizardry/spells/frost_axe.json @@ -14,7 +14,7 @@ "element": "ice", "type": "utility", "cost": 45, - "chargeup": 0, + "chargeup": 10, "cooldown": 50, "base_properties": { "item_lifetime": 1200, diff --git a/src/main/resources/assets/ebwizardry/spells/frost_barrier.json b/src/main/resources/assets/ebwizardry/spells/frost_barrier.json new file mode 100644 index 00000000..6b7fc5b9 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/frost_barrier.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": "defence", + "cost": 20, + "chargeup": 0, + "cooldown": 50, + "base_properties": { + "duration": 400 + } +} \ 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 index 02770806..d80cd3fa 100644 --- a/src/main/resources/assets/ebwizardry/spells/frost_sigil.json +++ b/src/main/resources/assets/ebwizardry/spells/frost_sigil.json @@ -18,6 +18,7 @@ "cooldown": 20, "base_properties": { "range": 10, + "effect_radius": 1, "damage": 8, "effect_duration": 200, "effect_strength": 1 diff --git a/src/main/resources/assets/ebwizardry/spells/greater_fireball.json b/src/main/resources/assets/ebwizardry/spells/greater_fireball.json index d94fc93c..8c2f6c44 100644 --- a/src/main/resources/assets/ebwizardry/spells/greater_fireball.json +++ b/src/main/resources/assets/ebwizardry/spells/greater_fireball.json @@ -14,7 +14,7 @@ "element": "fire", "type": "projectile", "cost": 20, - "chargeup": 0, + "chargeup": 5, "cooldown": 30, "base_properties": { "range": 20, diff --git a/src/main/resources/assets/ebwizardry/spells/greater_heal.json b/src/main/resources/assets/ebwizardry/spells/greater_heal.json index 5797115c..614285a5 100644 --- a/src/main/resources/assets/ebwizardry/spells/greater_heal.json +++ b/src/main/resources/assets/ebwizardry/spells/greater_heal.json @@ -14,7 +14,7 @@ "element": "healing", "type": "defence", "cost": 15, - "chargeup": 0, + "chargeup": 10, "cooldown": 40, "base_properties": { "health": 8 diff --git a/src/main/resources/assets/ebwizardry/spells/greater_ward.json b/src/main/resources/assets/ebwizardry/spells/greater_ward.json index 9fde216e..e3aecb09 100644 --- a/src/main/resources/assets/ebwizardry/spells/greater_ward.json +++ b/src/main/resources/assets/ebwizardry/spells/greater_ward.json @@ -14,7 +14,7 @@ "element": "healing", "type": "buff", "cost": 20, - "chargeup": 0, + "chargeup": 10, "cooldown": 65, "base_properties": { "ward_duration": 600, diff --git a/src/main/resources/assets/ebwizardry/spells/group_heal.json b/src/main/resources/assets/ebwizardry/spells/group_heal.json index c1449e69..5ecaa49b 100644 --- a/src/main/resources/assets/ebwizardry/spells/group_heal.json +++ b/src/main/resources/assets/ebwizardry/spells/group_heal.json @@ -14,7 +14,7 @@ "element": "healing", "type": "defence", "cost": 35, - "chargeup": 0, + "chargeup": 10, "cooldown": 150, "base_properties": { "effect_radius": 5, diff --git a/src/main/resources/assets/ebwizardry/spells/guardian_beam.json b/src/main/resources/assets/ebwizardry/spells/guardian_beam.json new file mode 100644 index 00000000..b3a465e1 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/guardian_beam.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": "attack", + "cost": 10, + "chargeup": 0, + "cooldown": 50, + "base_properties": { + "range": 12, + "damage": 5, + "air_depletion": 70 + } +} \ 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 index b7dd6e29..ea352cd1 100644 --- a/src/main/resources/assets/ebwizardry/spells/hailstorm.json +++ b/src/main/resources/assets/ebwizardry/spells/hailstorm.json @@ -14,10 +14,11 @@ "element": "ice", "type": "attack", "cost": 75, - "chargeup": 0, + "chargeup": 20, "cooldown": 300, "base_properties": { "range": 20, - "duration": 120 + "duration": 120, + "effect_radius": 2 } } \ 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 index 18d81c27..541f9418 100644 --- a/src/main/resources/assets/ebwizardry/spells/healing_aura.json +++ b/src/main/resources/assets/ebwizardry/spells/healing_aura.json @@ -14,10 +14,11 @@ "element": "healing", "type": "construct", "cost": 35, - "chargeup": 0, + "chargeup": 15, "cooldown": 150, "base_properties": { "duration": 600, + "effect_radius": 2.5, "damage": 1, "health": 1 } diff --git a/src/main/resources/assets/ebwizardry/spells/ice_age.json b/src/main/resources/assets/ebwizardry/spells/ice_age.json index f7befce5..cc5f113e 100644 --- a/src/main/resources/assets/ebwizardry/spells/ice_age.json +++ b/src/main/resources/assets/ebwizardry/spells/ice_age.json @@ -14,10 +14,12 @@ "element": "ice", "type": "attack", "cost": 70, - "chargeup": 0, + "chargeup": 20, "cooldown": 250, "base_properties": { "effect_radius": 7, - "effect_duration": 1200 + "freeze_duration": 1200, + "effect_duration": 400, + "effect_strength": 1 } } \ 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 index a259d9b1..1e99c6e7 100644 --- a/src/main/resources/assets/ebwizardry/spells/ice_lance.json +++ b/src/main/resources/assets/ebwizardry/spells/ice_lance.json @@ -14,7 +14,7 @@ "element": "ice", "type": "projectile", "cost": 20, - "chargeup": 0, + "chargeup": 5, "cooldown": 20, "base_properties": { "range": 15, diff --git a/src/main/resources/assets/ebwizardry/spells/intimidate.json b/src/main/resources/assets/ebwizardry/spells/intimidate.json index b503c3ec..a145de19 100644 --- a/src/main/resources/assets/ebwizardry/spells/intimidate.json +++ b/src/main/resources/assets/ebwizardry/spells/intimidate.json @@ -12,7 +12,7 @@ }, "tier": "apprentice", "element": "necromancy", - "type": "attack", + "type": "alteration", "cost": 20, "chargeup": 0, "cooldown": 100, diff --git a/src/main/resources/assets/ebwizardry/spells/invisibility.json b/src/main/resources/assets/ebwizardry/spells/invisibility.json index 04fe24b1..18a95b75 100644 --- a/src/main/resources/assets/ebwizardry/spells/invisibility.json +++ b/src/main/resources/assets/ebwizardry/spells/invisibility.json @@ -14,7 +14,7 @@ "element": "sorcery", "type": "buff", "cost": 35, - "chargeup": 0, + "chargeup": 15, "cooldown": 200, "base_properties": { "invisibility_duration": 600, diff --git a/src/main/resources/assets/ebwizardry/spells/invoke_weather.json b/src/main/resources/assets/ebwizardry/spells/invoke_weather.json index 9c4ba918..2064207e 100644 --- a/src/main/resources/assets/ebwizardry/spells/invoke_weather.json +++ b/src/main/resources/assets/ebwizardry/spells/invoke_weather.json @@ -14,7 +14,7 @@ "element": "lightning", "type": "utility", "cost": 30, - "chargeup": 0, + "chargeup": 15, "cooldown": 100, "base_properties": { "thunderstorm_chance": 0.2 diff --git a/src/main/resources/assets/ebwizardry/spells/ironflesh.json b/src/main/resources/assets/ebwizardry/spells/ironflesh.json index 31a017a7..089f63bd 100644 --- a/src/main/resources/assets/ebwizardry/spells/ironflesh.json +++ b/src/main/resources/assets/ebwizardry/spells/ironflesh.json @@ -14,7 +14,7 @@ "element": "healing", "type": "defence", "cost": 30, - "chargeup": 0, + "chargeup": 15, "cooldown": 100, "base_properties": { "resistance_duration": 600, diff --git a/src/main/resources/assets/ebwizardry/spells/lightning_bolt.json b/src/main/resources/assets/ebwizardry/spells/lightning_bolt.json index 9be2d4a5..692c059c 100644 --- a/src/main/resources/assets/ebwizardry/spells/lightning_bolt.json +++ b/src/main/resources/assets/ebwizardry/spells/lightning_bolt.json @@ -14,9 +14,10 @@ "element": "lightning", "type": "attack", "cost": 40, - "chargeup": 0, + "chargeup": 10, "cooldown": 80, "base_properties": { - "range": 80 + "range": 40, + "damage": 5 } } \ 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 index fd2f9ff9..6d9f49f7 100644 --- a/src/main/resources/assets/ebwizardry/spells/lightning_disc.json +++ b/src/main/resources/assets/ebwizardry/spells/lightning_disc.json @@ -14,7 +14,7 @@ "element": "lightning", "type": "projectile", "cost": 25, - "chargeup": 0, + "chargeup": 5, "cooldown": 60, "base_properties": { "range": 30, diff --git a/src/main/resources/assets/ebwizardry/spells/lightning_hammer.json b/src/main/resources/assets/ebwizardry/spells/lightning_hammer.json index 3d93cbb4..3ebeb686 100644 --- a/src/main/resources/assets/ebwizardry/spells/lightning_hammer.json +++ b/src/main/resources/assets/ebwizardry/spells/lightning_hammer.json @@ -14,7 +14,7 @@ "element": "lightning", "type": "attack", "cost": 100, - "chargeup": 0, + "chargeup": 25, "cooldown": 300, "base_properties": { "range": 40, diff --git a/src/main/resources/assets/ebwizardry/spells/lightning_sigil.json b/src/main/resources/assets/ebwizardry/spells/lightning_sigil.json index 77ce013f..66c44455 100644 --- a/src/main/resources/assets/ebwizardry/spells/lightning_sigil.json +++ b/src/main/resources/assets/ebwizardry/spells/lightning_sigil.json @@ -18,8 +18,9 @@ "cooldown": 20, "base_properties": { "range": 10, + "effect_radius": 1, "direct_damage": 6, - "effect_radius": 5, + "secondary_range": 1, "secondary_max_targets": 3, "splash_damage": 4 } diff --git a/src/main/resources/assets/ebwizardry/spells/lightning_web.json b/src/main/resources/assets/ebwizardry/spells/lightning_web.json index cc16ee60..846ad70f 100644 --- a/src/main/resources/assets/ebwizardry/spells/lightning_web.json +++ b/src/main/resources/assets/ebwizardry/spells/lightning_web.json @@ -14,8 +14,8 @@ "element": "lightning", "type": "attack", "cost": 15, - "chargeup": 0, - "cooldown": 0, + "chargeup": 15, + "cooldown": 70, "base_properties": { "primary_damage": 5, "secondary_damage": 4, diff --git a/src/main/resources/assets/ebwizardry/spells/mark_sacrifice.json b/src/main/resources/assets/ebwizardry/spells/mark_sacrifice.json new file mode 100644 index 00000000..c62d8982 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/mark_sacrifice.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": "alteration", + "cost": 25, + "chargeup": 0, + "cooldown": 60, + "base_properties": { + "range": 8, + "effect_duration": 200, + "effect_strength": 0 + } +} \ 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 index d56985aa..aa27a909 100644 --- a/src/main/resources/assets/ebwizardry/spells/meteor.json +++ b/src/main/resources/assets/ebwizardry/spells/meteor.json @@ -14,7 +14,7 @@ "element": "fire", "type": "attack", "cost": 100, - "chargeup": 0, + "chargeup": 20, "cooldown": 200, "base_properties": { "range": 40, diff --git a/src/main/resources/assets/ebwizardry/spells/mind_control.json b/src/main/resources/assets/ebwizardry/spells/mind_control.json index e019a77f..bb3402c8 100644 --- a/src/main/resources/assets/ebwizardry/spells/mind_control.json +++ b/src/main/resources/assets/ebwizardry/spells/mind_control.json @@ -14,7 +14,7 @@ "element": "necromancy", "type": "attack", "cost": 40, - "chargeup": 0, + "chargeup": 10, "cooldown": 150, "base_properties": { "range": 8, diff --git a/src/main/resources/assets/ebwizardry/spells/mirage.json b/src/main/resources/assets/ebwizardry/spells/mirage.json new file mode 100644 index 00000000..50aedefa --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/mirage.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": "alteration", + "cost": 40, + "chargeup": 10, + "cooldown": 200, + "base_properties": { + "mirage_duration": 400, + "mirage_strength": 0 + } +} \ 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 index 8691b64b..226df1e6 100644 --- a/src/main/resources/assets/ebwizardry/spells/paralysis.json +++ b/src/main/resources/assets/ebwizardry/spells/paralysis.json @@ -14,7 +14,7 @@ "element": "lightning", "type": "alteration", "cost": 20, - "chargeup": 0, + "chargeup": 10, "cooldown": 60, "base_properties": { "range": 10, diff --git a/src/main/resources/assets/ebwizardry/spells/permafrost.json b/src/main/resources/assets/ebwizardry/spells/permafrost.json new file mode 100644 index 00000000..0fc7bb15 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/permafrost.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": "ice", + "type": "alteration", + "cost": 10, + "chargeup": 10, + "cooldown": 40, + "base_properties": { + "range": 10, + "duration": 600, + "damage": 3, + "effect_duration": 100, + "effect_strength": 0 + } +} \ 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 index 9add942a..53ff72d4 100644 --- a/src/main/resources/assets/ebwizardry/spells/petrify.json +++ b/src/main/resources/assets/ebwizardry/spells/petrify.json @@ -14,7 +14,7 @@ "element": "sorcery", "type": "attack", "cost": 40, - "chargeup": 0, + "chargeup": 20, "cooldown": 100, "base_properties": { "range": 10, diff --git a/src/main/resources/assets/ebwizardry/spells/plague_of_darkness.json b/src/main/resources/assets/ebwizardry/spells/plague_of_darkness.json index 3da3cc81..d3d4d1ff 100644 --- a/src/main/resources/assets/ebwizardry/spells/plague_of_darkness.json +++ b/src/main/resources/assets/ebwizardry/spells/plague_of_darkness.json @@ -14,7 +14,7 @@ "element": "necromancy", "type": "attack", "cost": 75, - "chargeup": 0, + "chargeup": 15, "cooldown": 200, "base_properties": { "effect_radius": 5, diff --git a/src/main/resources/assets/ebwizardry/spells/possession.json b/src/main/resources/assets/ebwizardry/spells/possession.json index ade443bb..2ef90fee 100644 --- a/src/main/resources/assets/ebwizardry/spells/possession.json +++ b/src/main/resources/assets/ebwizardry/spells/possession.json @@ -14,7 +14,7 @@ "element": "necromancy", "type": "alteration", "cost": 100, - "chargeup": 0, + "chargeup": 15, "cooldown": 300, "base_properties": { "range": 8, diff --git a/src/main/resources/assets/ebwizardry/spells/radiant_totem.json b/src/main/resources/assets/ebwizardry/spells/radiant_totem.json new file mode 100644 index 00000000..6608736a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/radiant_totem.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": "healing", + "type": "construct", + "cost": 40, + "chargeup": 15, + "cooldown": 175, + "base_properties": { + "range": 8, + "duration": 600, + "effect_radius": 6, + "max_targets": 1, + "health": 1, + "damage": 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 index 940eb3e5..e02c997f 100644 --- a/src/main/resources/assets/ebwizardry/spells/ray_of_purification.json +++ b/src/main/resources/assets/ebwizardry/spells/ray_of_purification.json @@ -14,13 +14,12 @@ "element": "healing", "type": "attack", "cost": 10, - "chargeup": 0, - "cooldown": 0, + "chargeup": 10, + "cooldown": 40, "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 index ca69c0bf..dce3fb10 100644 --- a/src/main/resources/assets/ebwizardry/spells/remove_curse.json +++ b/src/main/resources/assets/ebwizardry/spells/remove_curse.json @@ -14,7 +14,7 @@ "element": "healing", "type": "defence", "cost": 50, - "chargeup": 0, + "chargeup": 20, "cooldown": 80, "base_properties": {} } \ 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 index 402e2f2d..d9124f5f 100644 --- a/src/main/resources/assets/ebwizardry/spells/resurrection.json +++ b/src/main/resources/assets/ebwizardry/spells/resurrection.json @@ -14,7 +14,7 @@ "element": "healing", "type": "alteration", "cost": 150, - "chargeup": 0, + "chargeup": 25, "cooldown": 400, "base_properties": { "effect_radius": 8, diff --git a/src/main/resources/assets/ebwizardry/spells/ring_of_fire.json b/src/main/resources/assets/ebwizardry/spells/ring_of_fire.json index 79f7510e..5c2d8671 100644 --- a/src/main/resources/assets/ebwizardry/spells/ring_of_fire.json +++ b/src/main/resources/assets/ebwizardry/spells/ring_of_fire.json @@ -14,10 +14,11 @@ "element": "fire", "type": "construct", "cost": 30, - "chargeup": 0, + "chargeup": 10, "cooldown": 100, "base_properties": { "duration": 600, + "effect_radius": 2.5, "damage": 1, "burn_duration": 10 } diff --git a/src/main/resources/assets/ebwizardry/spells/satiety.json b/src/main/resources/assets/ebwizardry/spells/satiety.json index 06dc1140..a28a120e 100644 --- a/src/main/resources/assets/ebwizardry/spells/satiety.json +++ b/src/main/resources/assets/ebwizardry/spells/satiety.json @@ -14,7 +14,7 @@ "element": "healing", "type": "buff", "cost": 40, - "chargeup": 0, + "chargeup": 15, "cooldown": 50, "base_properties": { "hunger_points": 16, diff --git a/src/main/resources/assets/ebwizardry/spells/shockwave.json b/src/main/resources/assets/ebwizardry/spells/shockwave.json index c2fbf6dd..85fdcbf5 100644 --- a/src/main/resources/assets/ebwizardry/spells/shockwave.json +++ b/src/main/resources/assets/ebwizardry/spells/shockwave.json @@ -14,10 +14,10 @@ "element": "sorcery", "type": "attack", "cost": 65, - "chargeup": 0, + "chargeup": 20, "cooldown": 150, "base_properties": { - "blast_radius": 5, + "effect_radius": 5, "damage": 8, "max_repulsion_velocity": 3 } diff --git a/src/main/resources/assets/ebwizardry/spells/silverfish_swarm.json b/src/main/resources/assets/ebwizardry/spells/silverfish_swarm.json index 9390042c..8bf95392 100644 --- a/src/main/resources/assets/ebwizardry/spells/silverfish_swarm.json +++ b/src/main/resources/assets/ebwizardry/spells/silverfish_swarm.json @@ -14,7 +14,7 @@ "element": "earth", "type": "minion", "cost": 80, - "chargeup": 0, + "chargeup": 20, "cooldown": 300, "base_properties": { "minion_lifetime": 600, diff --git a/src/main/resources/assets/ebwizardry/spells/slow_time.json b/src/main/resources/assets/ebwizardry/spells/slow_time.json index ec7eee50..0f4b666d 100644 --- a/src/main/resources/assets/ebwizardry/spells/slow_time.json +++ b/src/main/resources/assets/ebwizardry/spells/slow_time.json @@ -14,7 +14,7 @@ "element": "sorcery", "type": "alteration", "cost": 100, - "chargeup": 0, + "chargeup": 15, "cooldown": 200, "base_properties": { "slow_time_duration": 300, diff --git a/src/main/resources/assets/ebwizardry/spells/snowball.json b/src/main/resources/assets/ebwizardry/spells/snowball.json index 2191aa3a..7b56a7fa 100644 --- a/src/main/resources/assets/ebwizardry/spells/snowball.json +++ b/src/main/resources/assets/ebwizardry/spells/snowball.json @@ -12,7 +12,7 @@ }, "tier": "novice", "element": "ice", - "type": "attack", + "type": "projectile", "cost": 1, "chargeup": 0, "cooldown": 1, diff --git a/src/main/resources/assets/ebwizardry/spells/spectral_pathway.json b/src/main/resources/assets/ebwizardry/spells/spectral_pathway.json index 0e775f40..a7a91e1e 100644 --- a/src/main/resources/assets/ebwizardry/spells/spectral_pathway.json +++ b/src/main/resources/assets/ebwizardry/spells/spectral_pathway.json @@ -14,7 +14,7 @@ "element": "sorcery", "type": "utility", "cost": 40, - "chargeup": 0, + "chargeup": 15, "cooldown": 300, "base_properties": { "length": 25, diff --git a/src/main/resources/assets/ebwizardry/spells/spider_swarm.json b/src/main/resources/assets/ebwizardry/spells/spider_swarm.json index ffdc4197..c6358c95 100644 --- a/src/main/resources/assets/ebwizardry/spells/spider_swarm.json +++ b/src/main/resources/assets/ebwizardry/spells/spider_swarm.json @@ -14,7 +14,7 @@ "element": "earth", "type": "minion", "cost": 45, - "chargeup": 0, + "chargeup": 10, "cooldown": 200, "base_properties": { "minion_lifetime": 600, diff --git a/src/main/resources/assets/ebwizardry/spells/stormcloud.json b/src/main/resources/assets/ebwizardry/spells/stormcloud.json new file mode 100644 index 00000000..96711f1a --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/stormcloud.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": "construct", + "cost": 60, + "chargeup": 10, + "cooldown": 150, + "base_properties": { + "duration": 600, + "range": 10, + "damage": 8, + "effect_radius": 3 + } +} \ 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 index 7621260f..cd3707e6 100644 --- a/src/main/resources/assets/ebwizardry/spells/summon_blaze.json +++ b/src/main/resources/assets/ebwizardry/spells/summon_blaze.json @@ -14,7 +14,7 @@ "element": "fire", "type": "minion", "cost": 40, - "chargeup": 0, + "chargeup": 10, "cooldown": 200, "base_properties": { "minion_lifetime": 600, diff --git a/src/main/resources/assets/ebwizardry/spells/summon_ice_giant.json b/src/main/resources/assets/ebwizardry/spells/summon_ice_giant.json index 810f0c67..92738b1a 100644 --- a/src/main/resources/assets/ebwizardry/spells/summon_ice_giant.json +++ b/src/main/resources/assets/ebwizardry/spells/summon_ice_giant.json @@ -14,7 +14,7 @@ "element": "ice", "type": "minion", "cost": 100, - "chargeup": 0, + "chargeup": 20, "cooldown": 400, "base_properties": { "minion_lifetime": 600, diff --git a/src/main/resources/assets/ebwizardry/spells/summon_ice_wraith.json b/src/main/resources/assets/ebwizardry/spells/summon_ice_wraith.json index 934b94b6..77a51efe 100644 --- a/src/main/resources/assets/ebwizardry/spells/summon_ice_wraith.json +++ b/src/main/resources/assets/ebwizardry/spells/summon_ice_wraith.json @@ -14,7 +14,7 @@ "element": "ice", "type": "minion", "cost": 40, - "chargeup": 0, + "chargeup": 10, "cooldown": 200, "base_properties": { "minion_lifetime": 600, diff --git a/src/main/resources/assets/ebwizardry/spells/summon_iron_golem.json b/src/main/resources/assets/ebwizardry/spells/summon_iron_golem.json index 01991e04..fca50afc 100644 --- a/src/main/resources/assets/ebwizardry/spells/summon_iron_golem.json +++ b/src/main/resources/assets/ebwizardry/spells/summon_iron_golem.json @@ -14,7 +14,7 @@ "element": "sorcery", "type": "minion", "cost": 175, - "chargeup": 0, + "chargeup": 20, "cooldown": 400, "base_properties": { "summon_radius": 2 diff --git a/src/main/resources/assets/ebwizardry/spells/summon_lightning_wraith.json b/src/main/resources/assets/ebwizardry/spells/summon_lightning_wraith.json index e04aa12b..b2baf94e 100644 --- a/src/main/resources/assets/ebwizardry/spells/summon_lightning_wraith.json +++ b/src/main/resources/assets/ebwizardry/spells/summon_lightning_wraith.json @@ -14,7 +14,7 @@ "element": "lightning", "type": "minion", "cost": 40, - "chargeup": 0, + "chargeup": 10, "cooldown": 200, "base_properties": { "minion_lifetime": 600, diff --git a/src/main/resources/assets/ebwizardry/spells/summon_phoenix.json b/src/main/resources/assets/ebwizardry/spells/summon_phoenix.json index 13664fcf..827beb31 100644 --- a/src/main/resources/assets/ebwizardry/spells/summon_phoenix.json +++ b/src/main/resources/assets/ebwizardry/spells/summon_phoenix.json @@ -14,7 +14,7 @@ "element": "fire", "type": "minion", "cost": 150, - "chargeup": 0, + "chargeup": 20, "cooldown": 400, "base_properties": { "minion_lifetime": 600, diff --git a/src/main/resources/assets/ebwizardry/spells/summon_shadow_wraith.json b/src/main/resources/assets/ebwizardry/spells/summon_shadow_wraith.json index e699bf4b..2c30faaa 100644 --- a/src/main/resources/assets/ebwizardry/spells/summon_shadow_wraith.json +++ b/src/main/resources/assets/ebwizardry/spells/summon_shadow_wraith.json @@ -14,7 +14,7 @@ "element": "necromancy", "type": "minion", "cost": 100, - "chargeup": 0, + "chargeup": 20, "cooldown": 400, "base_properties": { "minion_lifetime": 600, diff --git a/src/main/resources/assets/ebwizardry/spells/summon_skeleton_legion.json b/src/main/resources/assets/ebwizardry/spells/summon_skeleton_legion.json index 3bc7f2a8..524a1c3f 100644 --- a/src/main/resources/assets/ebwizardry/spells/summon_skeleton_legion.json +++ b/src/main/resources/assets/ebwizardry/spells/summon_skeleton_legion.json @@ -14,7 +14,7 @@ "element": "necromancy", "type": "minion", "cost": 100, - "chargeup": 0, + "chargeup": 20, "cooldown": 400, "base_properties": { "minion_lifetime": 1200, diff --git a/src/main/resources/assets/ebwizardry/spells/summon_spirit_horse.json b/src/main/resources/assets/ebwizardry/spells/summon_spirit_horse.json index 73b29590..55e5a9ff 100644 --- a/src/main/resources/assets/ebwizardry/spells/summon_spirit_horse.json +++ b/src/main/resources/assets/ebwizardry/spells/summon_spirit_horse.json @@ -14,7 +14,7 @@ "element": "earth", "type": "minion", "cost": 50, - "chargeup": 0, + "chargeup": 10, "cooldown": 150, "base_properties": { "summon_radius": 2 diff --git a/src/main/resources/assets/ebwizardry/spells/summon_storm_elemental.json b/src/main/resources/assets/ebwizardry/spells/summon_storm_elemental.json index 02c1f50d..51d117d6 100644 --- a/src/main/resources/assets/ebwizardry/spells/summon_storm_elemental.json +++ b/src/main/resources/assets/ebwizardry/spells/summon_storm_elemental.json @@ -14,7 +14,7 @@ "element": "lightning", "type": "minion", "cost": 100, - "chargeup": 0, + "chargeup": 20, "cooldown": 400, "base_properties": { "minion_lifetime": 600, diff --git a/src/main/resources/assets/ebwizardry/spells/summon_wither_skeleton.json b/src/main/resources/assets/ebwizardry/spells/summon_wither_skeleton.json index f16431f2..c1e77a0d 100644 --- a/src/main/resources/assets/ebwizardry/spells/summon_wither_skeleton.json +++ b/src/main/resources/assets/ebwizardry/spells/summon_wither_skeleton.json @@ -14,7 +14,7 @@ "element": "necromancy", "type": "minion", "cost": 35, - "chargeup": 0, + "chargeup": 10, "cooldown": 150, "base_properties": { "minion_lifetime": 600, diff --git a/src/main/resources/assets/ebwizardry/spells/thunderstorm.json b/src/main/resources/assets/ebwizardry/spells/thunderstorm.json index c414078e..5df426ea 100644 --- a/src/main/resources/assets/ebwizardry/spells/thunderstorm.json +++ b/src/main/resources/assets/ebwizardry/spells/thunderstorm.json @@ -14,7 +14,7 @@ "element": "lightning", "type": "attack", "cost": 100, - "chargeup": 0, + "chargeup": 20, "cooldown": 250, "base_properties": { "effect_radius": 10, diff --git a/src/main/resources/assets/ebwizardry/spells/tornado.json b/src/main/resources/assets/ebwizardry/spells/tornado.json index 87d8cfbd..e62b097f 100644 --- a/src/main/resources/assets/ebwizardry/spells/tornado.json +++ b/src/main/resources/assets/ebwizardry/spells/tornado.json @@ -14,7 +14,7 @@ "element": "earth", "type": "attack", "cost": 35, - "chargeup": 0, + "chargeup": 10, "cooldown": 80, "base_properties": { "duration": 200, diff --git a/src/main/resources/assets/ebwizardry/spells/transience.json b/src/main/resources/assets/ebwizardry/spells/transience.json index ff5886c2..651eae30 100644 --- a/src/main/resources/assets/ebwizardry/spells/transience.json +++ b/src/main/resources/assets/ebwizardry/spells/transience.json @@ -14,7 +14,7 @@ "element": "healing", "type": "buff", "cost": 50, - "chargeup": 0, + "chargeup": 15, "cooldown": 100, "base_properties": { "effect_duration": 400 diff --git a/src/main/resources/assets/ebwizardry/spells/transportation.json b/src/main/resources/assets/ebwizardry/spells/transportation.json index 0dbfcbfb..8ba20ce8 100644 --- a/src/main/resources/assets/ebwizardry/spells/transportation.json +++ b/src/main/resources/assets/ebwizardry/spells/transportation.json @@ -14,7 +14,7 @@ "element": "sorcery", "type": "utility", "cost": 100, - "chargeup": 0, + "chargeup": 20, "cooldown": 100, "base_properties": { "teleport_countdown": 75 diff --git a/src/main/resources/assets/ebwizardry/spells/vanishing_box.json b/src/main/resources/assets/ebwizardry/spells/vanishing_box.json index c6e1eab5..aff160d6 100644 --- a/src/main/resources/assets/ebwizardry/spells/vanishing_box.json +++ b/src/main/resources/assets/ebwizardry/spells/vanishing_box.json @@ -14,7 +14,7 @@ "element": "sorcery", "type": "utility", "cost": 45, - "chargeup": 0, + "chargeup": 10, "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 index e54c7cc3..7c7c5f09 100644 --- a/src/main/resources/assets/ebwizardry/spells/vex_swarm.json +++ b/src/main/resources/assets/ebwizardry/spells/vex_swarm.json @@ -14,7 +14,7 @@ "element": "sorcery", "type": "minion", "cost": 50, - "chargeup": 0, + "chargeup": 10, "cooldown": 200, "base_properties": { "minion_lifetime": 600, diff --git a/src/main/resources/assets/ebwizardry/spells/wall_of_frost.json b/src/main/resources/assets/ebwizardry/spells/wall_of_frost.json index 87e3daf6..56fc4bb2 100644 --- a/src/main/resources/assets/ebwizardry/spells/wall_of_frost.json +++ b/src/main/resources/assets/ebwizardry/spells/wall_of_frost.json @@ -14,8 +14,8 @@ "element": "ice", "type": "utility", "cost": 15, - "chargeup": 0, - "cooldown": 0, + "chargeup": 15, + "cooldown": 70, "base_properties": { "duration": 600, "range": 10 diff --git a/src/main/resources/assets/ebwizardry/spells/water_breathing.json b/src/main/resources/assets/ebwizardry/spells/water_breathing.json index 9cee0e43..cb03821e 100644 --- a/src/main/resources/assets/ebwizardry/spells/water_breathing.json +++ b/src/main/resources/assets/ebwizardry/spells/water_breathing.json @@ -14,7 +14,7 @@ "element": "earth", "type": "buff", "cost": 30, - "chargeup": 0, + "chargeup": 15, "cooldown": 250, "base_properties": { "water_breathing_duration": 1200, diff --git a/src/main/resources/assets/ebwizardry/spells/wither_skull.json b/src/main/resources/assets/ebwizardry/spells/wither_skull.json index e1bc8083..a8fca3bb 100644 --- a/src/main/resources/assets/ebwizardry/spells/wither_skull.json +++ b/src/main/resources/assets/ebwizardry/spells/wither_skull.json @@ -14,7 +14,7 @@ "element": "necromancy", "type": "attack", "cost": 20, - "chargeup": 0, + "chargeup": 5, "cooldown": 30, "base_properties": { "acceleration": 0.1 diff --git a/src/main/resources/assets/ebwizardry/spells/withering_totem.json b/src/main/resources/assets/ebwizardry/spells/withering_totem.json new file mode 100644 index 00000000..d5a7bae4 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/withering_totem.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": "necromancy", + "type": "construct", + "cost": 50, + "chargeup": 20, + "cooldown": 200, + "base_properties": { + "range": 8, + "duration": 600, + "effect_radius": 6, + "max_targets": 1, + "damage": 1, + "max_explosion_damage": 10, + "effect_duration": 120, + "effect_strength": 0 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/spells/zombie_apocalypse.json b/src/main/resources/assets/ebwizardry/spells/zombie_apocalypse.json new file mode 100644 index 00000000..d6686e9e --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/zombie_apocalypse.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": "construct", + "cost": 150, + "chargeup": 25, + "cooldown": 300, + "base_properties": { + "duration": 500, + "minion_lifetime": 600, + "minion_spawn_interval": 20 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/structures/library_ruins_0.nbt b/src/main/resources/assets/ebwizardry/structures/library_ruins_0.nbt new file mode 100644 index 00000000..5f5d5468 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/library_ruins_0.nbt differ diff --git a/src/main/resources/assets/ebwizardry/structures/library_ruins_1.nbt b/src/main/resources/assets/ebwizardry/structures/library_ruins_1.nbt new file mode 100644 index 00000000..0f5f3c75 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/library_ruins_1.nbt differ diff --git a/src/main/resources/assets/ebwizardry/structures/library_ruins_2.nbt b/src/main/resources/assets/ebwizardry/structures/library_ruins_2.nbt new file mode 100644 index 00000000..cc2a16ea Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/library_ruins_2.nbt differ diff --git a/src/main/resources/assets/ebwizardry/structures/library_ruins_3.nbt b/src/main/resources/assets/ebwizardry/structures/library_ruins_3.nbt new file mode 100644 index 00000000..540487aa Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/library_ruins_3.nbt differ diff --git a/src/main/resources/assets/ebwizardry/structures/underground_library_ruins_0.nbt b/src/main/resources/assets/ebwizardry/structures/underground_library_ruins_0.nbt new file mode 100644 index 00000000..68f8658f Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/underground_library_ruins_0.nbt differ diff --git a/src/main/resources/assets/ebwizardry/structures/underground_library_ruins_1.nbt b/src/main/resources/assets/ebwizardry/structures/underground_library_ruins_1.nbt new file mode 100644 index 00000000..41a076fa Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/underground_library_ruins_1.nbt differ diff --git a/src/main/resources/assets/ebwizardry/structures/underground_library_ruins_2.nbt b/src/main/resources/assets/ebwizardry/structures/underground_library_ruins_2.nbt new file mode 100644 index 00000000..31599f1b Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/underground_library_ruins_2.nbt differ diff --git a/src/main/resources/assets/ebwizardry/structures/underground_library_ruins_3.nbt b/src/main/resources/assets/ebwizardry/structures/underground_library_ruins_3.nbt new file mode 100644 index 00000000..404be220 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/structures/underground_library_ruins_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 index c619a80c..e435d541 100644 --- a/src/main/resources/assets/ebwizardry/texts/handbook_en_gb.json +++ b/src/main/resources/assets/ebwizardry/texts/handbook_en_gb.json @@ -11,7 +11,7 @@ "images": { "workbench": { - "location": "ebwizardry:textures/gui/arcane_workbench_picture.png", + "location": "ebwizardry:textures/gui/handbook/pictures/arcane_workbench.png", "caption": "The Arcane Workbench", "u": 0, "v": 0, @@ -19,7 +19,7 @@ "height": 110 }, "crystal_ore": { - "location": "ebwizardry:textures/gui/ore_picture.png", + "location": "ebwizardry:textures/gui/handbook/pictures/crystal_ore.png", "caption": "Crystal Ore", "u": 0, "v": 0, @@ -47,7 +47,7 @@ "border": false }, "crystal_flower": { - "location": "ebwizardry:textures/gui/flower_picture.png", + "location": "ebwizardry:textures/gui/handbook/pictures/crystal_flower.png", "caption": "A Crystal Flower", "u": 0, "v": 0, @@ -55,7 +55,7 @@ "height": 48 }, "wizard_tower": { - "location": "ebwizardry:textures/gui/tower_picture.png", + "location": "ebwizardry:textures/gui/handbook/pictures/wizard_tower.png", "caption": "A Wizard's Tower", "u": 0, "v": 0, @@ -63,7 +63,7 @@ "height": 110 }, "wizard_armour": { - "location": "ebwizardry:textures/gui/armour_picture.png", + "location": "ebwizardry:textures/gui/handbook/pictures/wizard_armour.png", "caption": "A Set Of Wizard Armour", "u": 0, "v": 0, @@ -71,7 +71,7 @@ "height": 90 }, "obelisk": { - "location": "ebwizardry:textures/gui/obelisk_picture.png", + "location": "ebwizardry:textures/gui/handbook/pictures/obelisk.png", "caption": "An Obelisk", "u": 0, "v": 0, @@ -79,15 +79,31 @@ "height": 67 }, "shrine": { - "location": "ebwizardry:textures/gui/shrine_picture.png", + "location": "ebwizardry:textures/gui/handbook/pictures/shrine.png", "caption": "An Earth Shrine", "u": 0, "v": 0, "width": 110, "height": 67 }, + "library_ruins": { + "location": "ebwizardry:textures/gui/handbook/pictures/library_ruins.png", + "caption": "Library Ruins", + "u": 0, + "v": 0, + "width": 110, + "height": 57 + }, + "remnant": { + "location": "ebwizardry:textures/gui/handbook/pictures/remnant.png", + "caption": "A Fire Remnant", + "u": 0, + "v": 0, + "width": 110, + "height": 73 + }, "tiers": { - "location": "ebwizardry:textures/gui/tiers_picture.png", + "location": "ebwizardry:textures/gui/handbook/pictures/tiers.png", "caption": "The 4 Tiers Of Wand", "u": 0, "v": 0, @@ -96,7 +112,7 @@ "border": false }, "elements": { - "location": "ebwizardry:textures/gui/elements_picture.png", + "location": "ebwizardry:textures/gui/handbook/pictures/elements.png", "caption": "The 7 Arcane Elements", "u": 0, "v": 0, @@ -224,6 +240,39 @@ }, "spark_bomb": { "locations": [ "ebwizardry:spark_bomb" ] + }, + "gilded_wood": { + "locations": [ + "ebwizardry:gilded_oak_wood", + "ebwizardry:gilded_spruce_wood", + "ebwizardry:gilded_birch_wood", + "ebwizardry:gilded_jungle_wood", + "ebwizardry:gilded_acacia_wood", + "ebwizardry:gilded_dark_oak_wood" + ] + }, + "bookshelves": { + "locations": [ + "ebwizardry:oak_bookshelf", + "ebwizardry:spruce_bookshelf", + "ebwizardry:birch_bookshelf", + "ebwizardry:jungle_bookshelf", + "ebwizardry:acacia_bookshelf", + "ebwizardry:dark_oak_bookshelf" + ] + }, + "lecterns": { + "locations": [ + "ebwizardry:oak_lectern", + "ebwizardry:spruce_lectern", + "ebwizardry:birch_lectern", + "ebwizardry:jungle_lectern", + "ebwizardry:acacia_lectern", + "ebwizardry:dark_oak_lectern" + ] + }, + "receptacle": { + "locations": [ "ebwizardry:receptacle" ] } }, @@ -341,7 +390,7 @@ "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.", + "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@. They also have the advantage of being able to instantly cast spells that would normally require a charge-up period. 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." ] @@ -535,6 +584,33 @@ "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..." ] }, + "library_ruins": { + "title": "Library Ruins", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:find_library_ruins" + ], + "text": [ + + "Wizards tend to accumulate rather a lot of spell books during their lifetime, and it is therefore usual for a wizard to build a library to store their collection. Some particularly secretive wizards have even been known to store their arcane knowledge underground, away from prying eyes. However, a library requires upkeep, and if its owner dies without an apprentice to inherit the books, the structure will quickly fall into disrepair. Thousands of years of magic use have seen more and more libraries left abandoned in this way, and you may perhaps stumble across one when exploring. They are often worth searching for any spell books or other items that might have survived the ravages of time.", + + "#image library_ruins", + + "Libraries are not just used for storing books, though. Wizards of the past harnessed the power of the imbuement altar, used to perform elemental imbuement rituals on magical materials. The positioning of the altar is a precise and difficult art as it must be aligned according to particular celestial movements and local mana fluctuations. The practice of building them has become much rarer in recent history, mainly because once built, a solid rock altar lasts for centuries, so there is little need for new ones. Instead, most wizards find and repair an existing altar for when they need to perform imbuement rituals - and library ruins are the perfect place to look.", + + "You, too, may at some point find it useful to construct your own library, which will of course require some bookshelves:", + + "#recipe bookshelves", + + "#recipe gilded_wood", + + "These bookshelves are not mere furniture, however. Infusing the most potent magic crystals into your bookshelves allows effortless transfer of knowledge between them and a nearby @arcane_workbench arcane workbench@ - very handy as one's collection expands. Equally useful is a lectern:", + + "#recipe lecterns", + + "The tome that floats above a lectern gathers the knowledge from nearby spell books, allowing each to be perused at leisure without remembering its precise location." + ] + }, "creatures": { "title": "Magical Creatures", "include_in_contents": "magical_world_subsections", @@ -543,7 +619,17 @@ ], "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." + "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.", + + "A notable example is the remnant, a spirit-like being that forms naturally from the traces left behind by powerful magic and typically takes on one of the seven arcane @elements@. On their own, they are little more than a nuisance, but a swarm can quickly overwhelm the unprepared adventurer, and their innate resistance to magic makes it advisable to carry a bow or other ranged weapon.", + + "#image remnant", + + "Once slain, their spectral dust can be collected, which, besides emitting a colourful glow, has a variety of useful properties. A receptacle, crafted as shown, provides a convenient means of containing it.", + + "#recipe receptacle", + + "Receptacles may be placed atop a surface or affixed to a wall and right-clicked with spectral dust to fill them, acting as a soft light source. Right-clicking a filled receptacle empties it and retreives the dust." ] }, "artefacts": { @@ -711,7 +797,11 @@ "#recipe firebomb", "#recipe poison_bomb", "#recipe smoke_bomb", - "#recipe spark_bomb" + "#recipe spark_bomb", + "#recipe gilded_wood", + "#recipe bookshelves", + "#recipe lecterns", + "#recipe receptacle" ] }, @@ -736,13 +826,15 @@ "Translations:", - "- Spanish: MadWrist, Alsentar \n- Mexican Spanish: MadWrist \n- Russian: VilagVil, kellixon, bigenergy \n- French: Hahdrim \n- Brazilian Portuguese: lorrampi \n- Chinese: ZHENGLOC, dragon-evol, Hokorizero, TUsama, Determancer \n- Korean: shejery, rewi_wire \n- Polish: Trozuu \n-German: BirdyDragon \n-Taiwanese: chesterccj305", + "- Spanish: MadWrist, Alsentar \n- Mexican Spanish: MadWrist \n- Russian: VilagVil, kellixon, bigenergy \n- French: Hahdrim \n- Brazilian Portuguese: lorrampi \n- Chinese (Simplified): ZHENGLOC, dragon-evol, Hokorizero, TUsama, Determancer \n- Korean: shejery, rewi_wire \n- Polish: Trozuu \n- German: BirdyDragon \n- Chinese (Traditional): chesterccj305 \n- Hungarian: Bombadil", - "Lightning ray sound effect from OhhWowProductions", + "Sound Effects:", + + "- OhhWowProductions\n- fredzed\n- deleted_user_3277771\n- DiscoveryME\n- OGSoundFX\n- leosalom\n- juskiddink", "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!" + "Can't get enough wizardry? Join the @https://discord.gg/hs8yJP2 Discord server@ for the latest news, discussions and extras!" ] } diff --git a/src/main/resources/assets/ebwizardry/texts/handbook_en_us.json b/src/main/resources/assets/ebwizardry/texts/handbook_en_us.json index a72d1af7..289d54d0 100644 --- a/src/main/resources/assets/ebwizardry/texts/handbook_en_us.json +++ b/src/main/resources/assets/ebwizardry/texts/handbook_en_us.json @@ -11,7 +11,7 @@ "images": { "workbench": { - "location": "ebwizardry:textures/gui/arcane_workbench_picture.png", + "location": "ebwizardry:textures/gui/handbook/pictures/arcane_workbench.png", "caption": "The Arcane Workbench", "u": 0, "v": 0, @@ -19,7 +19,7 @@ "height": 110 }, "crystal_ore": { - "location": "ebwizardry:textures/gui/ore_picture.png", + "location": "ebwizardry:textures/gui/handbook/pictures/crystal_ore.png", "caption": "Crystal Ore", "u": 0, "v": 0, @@ -47,7 +47,7 @@ "border": false }, "crystal_flower": { - "location": "ebwizardry:textures/gui/flower_picture.png", + "location": "ebwizardry:textures/gui/handbook/pictures/crystal_flower.png", "caption": "A Crystal Flower", "u": 0, "v": 0, @@ -55,7 +55,7 @@ "height": 48 }, "wizard_tower": { - "location": "ebwizardry:textures/gui/tower_picture.png", + "location": "ebwizardry:textures/gui/handbook/pictures/wizard_tower.png", "caption": "A Wizard's Tower", "u": 0, "v": 0, @@ -63,7 +63,7 @@ "height": 110 }, "wizard_armour": { - "location": "ebwizardry:textures/gui/armour_picture.png", + "location": "ebwizardry:textures/gui/handbook/pictures/wizard_armour.png", "caption": "A Set Of Wizard Armour", "u": 0, "v": 0, @@ -71,7 +71,7 @@ "height": 90 }, "obelisk": { - "location": "ebwizardry:textures/gui/obelisk_picture.png", + "location": "ebwizardry:textures/gui/handbook/pictures/obelisk.png", "caption": "An Obelisk", "u": 0, "v": 0, @@ -79,15 +79,31 @@ "height": 67 }, "shrine": { - "location": "ebwizardry:textures/gui/shrine_picture.png", + "location": "ebwizardry:textures/gui/handbook/pictures/shrine.png", "caption": "An Earth Shrine", "u": 0, "v": 0, "width": 110, "height": 67 }, + "library_ruins": { + "location": "ebwizardry:textures/gui/handbook/pictures/library_ruins.png", + "caption": "Library Ruins", + "u": 0, + "v": 0, + "width": 110, + "height": 57 + }, + "remnant": { + "location": "ebwizardry:textures/gui/handbook/pictures/remnant.png", + "caption": "A Fire Remnant", + "u": 0, + "v": 0, + "width": 110, + "height": 73 + }, "tiers": { - "location": "ebwizardry:textures/gui/tiers_picture.png", + "location": "ebwizardry:textures/gui/handbook/pictures/tiers.png", "caption": "The 4 Tiers Of Wand", "u": 0, "v": 0, @@ -96,7 +112,7 @@ "border": false }, "elements": { - "location": "ebwizardry:textures/gui/elements_picture.png", + "location": "ebwizardry:textures/gui/handbook/pictures/elements.png", "caption": "The 7 Arcane Elements", "u": 0, "v": 0, @@ -224,6 +240,39 @@ }, "spark_bomb": { "locations": [ "ebwizardry:spark_bomb" ] + }, + "gilded_wood": { + "locations": [ + "ebwizardry:gilded_oak_wood", + "ebwizardry:gilded_spruce_wood", + "ebwizardry:gilded_birch_wood", + "ebwizardry:gilded_jungle_wood", + "ebwizardry:gilded_acacia_wood", + "ebwizardry:gilded_dark_oak_wood" + ] + }, + "bookshelves": { + "locations": [ + "ebwizardry:oak_bookshelf", + "ebwizardry:spruce_bookshelf", + "ebwizardry:birch_bookshelf", + "ebwizardry:jungle_bookshelf", + "ebwizardry:acacia_bookshelf", + "ebwizardry:dark_oak_bookshelf" + ] + }, + "lecterns": { + "locations": [ + "ebwizardry:oak_lectern", + "ebwizardry:spruce_lectern", + "ebwizardry:birch_lectern", + "ebwizardry:jungle_lectern", + "ebwizardry:acacia_lectern", + "ebwizardry:dark_oak_lectern" + ] + }, + "receptacle": { + "locations": [ "ebwizardry:receptacle" ] } }, @@ -341,7 +390,7 @@ "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.", + "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@. They also have the advantage of being able to instantly cast spells that would normally require a charge-up period. 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." ] @@ -535,6 +584,33 @@ "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..." ] }, + "library_ruins": { + "title": "Library Ruins", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:find_library_ruins" + ], + "text": [ + + "Wizards tend to accumulate rather a lot of spell books during their lifetime, and it is therefore usual for a wizard to build a library to store their collection. Some particularly secretive wizards have even been known to store their arcane knowledge underground, away from prying eyes. However, a library requires upkeep, and if its owner dies without an apprentice to inherit the books, the structure will quickly fall into disrepair. Thousands of years of magic use have seen more and more libraries left abandoned in this way, and you may perhaps stumble across one when exploring. They are often worth searching for any spell books or other items that might have survived the ravages of time.", + + "#image library_ruins", + + "Libraries are not just used for storing books, though. Wizards of the past harnessed the power of the imbuement altar, used to perform elemental imbuement rituals on magical materials. The positioning of the altar is a precise and difficult art as it must be aligned according to particular celestial movements and local mana fluctuations. The practice of building them has become much rarer in recent history, mainly because once built, a solid rock altar lasts for centuries, so there is little need for new ones. Instead, most wizards find and repair an existing altar for when they need to perform imbuement rituals - and library ruins are the perfect place to look.", + + "You, too, may at some point find it useful to construct your own library, which will of course require some bookshelves:", + + "#recipe bookshelves", + + "#recipe gilded_wood", + + "These bookshelves are not mere furniture, however. Infusing the most potent magic crystals into your bookshelves allows effortless transfer of knowledge between them and a nearby @arcane_workbench arcane workbench@ - very handy as one's collection expands. Equally useful is a lectern:", + + "#recipe lecterns", + + "The tome that floats above a lectern gathers the knowledge from nearby spell books, allowing each to be perused at leisure without remembering its precise location." + ] + }, "creatures": { "title": "Magical Creatures", "include_in_contents": "magical_world_subsections", @@ -543,7 +619,17 @@ ], "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." + "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.", + + "A notable example is the remnant, a spirit-like being that forms naturally from the traces left behind by powerful magic and typically takes on one of the seven arcane @elements@. On their own, they are little more than a nuisance, but a swarm can quickly overwhelm the unprepared adventurer, and their innate resistance to magic makes it advisable to carry a bow or other ranged weapon.", + + "#image remnant", + + "Once slain, their spectral dust can be collected, which, besides emitting a colorful glow, has a variety of useful properties. A receptacle, crafted as shown, provides a convenient means of containing it.", + + "#recipe receptacle", + + "Receptacles may be placed atop a surface or affixed to a wall and right-clicked with spectral dust to fill them, acting as a soft light source. Right-clicking a filled receptacle empties it and retreives the dust." ] }, "artefacts": { @@ -711,7 +797,11 @@ "#recipe firebomb", "#recipe poison_bomb", "#recipe smoke_bomb", - "#recipe spark_bomb" + "#recipe spark_bomb", + "#recipe gilded_wood", + "#recipe bookshelves", + "#recipe lecterns", + "#recipe receptacle" ] }, @@ -736,13 +826,15 @@ "Translations:", - "- Spanish: MadWrist, Alsentar \n- Mexican Spanish: MadWrist \n- Russian: VilagVil, kellixon, bigenergy \n- French: Hahdrim \n- Brazilian Portuguese: lorrampi \n- Chinese: ZHENGLOC, dragon-evol, Hokorizero, TUsama, Determancer \n- Korean: shejery, rewi_wire \n- Polish: Trozuu \n-German: BirdyDragon \n-Taiwanese: chesterccj305", + "- Spanish: MadWrist, Alsentar \n- Mexican Spanish: MadWrist \n- Russian: VilagVil, kellixon, bigenergy \n- French: Hahdrim \n- Brazilian Portuguese: lorrampi \n- Chinese (Simplified): ZHENGLOC, dragon-evol, Hokorizero, TUsama, Determancer \n- Korean: shejery, rewi_wire \n- Polish: Trozuu \n- German: BirdyDragon \n- Chinese (Traditional): chesterccj305 \n- Hungarian: Bombadil", - "Lightning ray sound effect from OhhWowProductions", + "Sound Effects:", + + "- OhhWowProductions\n- fredzed\n- deleted_user_3277771\n- DiscoveryME\n- OGSoundFX\n- leosalom\n- juskiddink", "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!" + "Can't get enough wizardry? Join the @https://discord.gg/hs8yJP2 Discord server@ for the latest news, discussions and extras!" ] } diff --git a/src/main/resources/assets/ebwizardry/texts/handbook_es_es.json b/src/main/resources/assets/ebwizardry/texts/handbook_es_es.json index 90d94eff..d9096703 100644 --- a/src/main/resources/assets/ebwizardry/texts/handbook_es_es.json +++ b/src/main/resources/assets/ebwizardry/texts/handbook_es_es.json @@ -1,6 +1,6 @@ { "bookmark_start_section": "introduction", - + "colours": { "text": "#000000", "caption": "#666666", @@ -8,10 +8,10 @@ "highlight": "#dd4c1d", "new_section": "#ee88f7" }, - + "images": { "workbench": { - "location": "ebwizardry:textures/gui/arcane_workbench_picture.png", + "location": "ebwizardry:textures/gui/arcane_workbench.png", "caption": "Mesa Arcana", "u": 0, "v": 0, @@ -19,8 +19,8 @@ "height": 110 }, "crystal_ore": { - "location": "ebwizardry:textures/gui/ore_picture.png", - "caption": "Mena de Cristal Mágico", + "location": "ebwizardry:textures/gui/crystal_ore.png", + "caption": "Mena de Cristal Magico", "u": 0, "v": 0, "width": 64, @@ -28,7 +28,7 @@ }, "magic_crystal": { "location": "ebwizardry:textures/items/crystal_magic.png", - "caption": "Cristal Mágico", + "caption": "Cristal Magico", "u": 6, "v": 6, "width": 36, @@ -39,7 +39,7 @@ }, "magic_wand": { "location": "ebwizardry:textures/items/wand_novice.png", - "caption": "Varita Mágica", + "caption": "Varita Magica", "u": 0, "v": 0, "width": 64, @@ -47,7 +47,7 @@ "border": false }, "crystal_flower": { - "location": "ebwizardry:textures/gui/flower_picture.png", + "location": "ebwizardry:textures/gui/crystal_flower.png", "caption": "Flor de Cristal", "u": 0, "v": 0, @@ -55,7 +55,7 @@ "height": 48 }, "wizard_tower": { - "location": "ebwizardry:textures/gui/tower_picture.png", + "location": "ebwizardry:textures/gui/wizard_tower.png", "caption": "Torre de mago", "u": 0, "v": 0, @@ -63,7 +63,7 @@ "height": 110 }, "wizard_armour": { - "location": "ebwizardry:textures/gui/armour_picture.png", + "location": "ebwizardry:textures/gui/wizard_armour.png", "caption": "Conjunto de Armadura de mago", "u": 0, "v": 0, @@ -71,7 +71,7 @@ "height": 90 }, "obelisk": { - "location": "ebwizardry:textures/gui/obelisk_picture.png", + "location": "ebwizardry:textures/gui/obelisk.png", "caption": "Obelisco", "u": 0, "v": 0, @@ -79,7 +79,7 @@ "height": 67 }, "shrine": { - "location": "ebwizardry:textures/gui/shrine_picture.png", + "location": "ebwizardry:textures/gui/shrine.png", "caption": "Santuario de la tierra", "u": 0, "v": 0, @@ -87,7 +87,7 @@ "height": 67 }, "tiers": { - "location": "ebwizardry:textures/gui/tiers_picture.png", + "location": "ebwizardry:textures/gui/tiers.png", "caption": "Los 4 niveles en las Varitas", "u": 0, "v": 0, @@ -96,7 +96,7 @@ "border": false }, "elements": { - "location": "ebwizardry:textures/gui/elements_picture.png", + "location": "ebwizardry:textures/gui/elements.png", "caption": "Los 7 elementos Arcanos", "u": 0, "v": 0, @@ -226,36 +226,36 @@ "locations": [ "ebwizardry:spark_bomb" ] } }, - + "sections": { - + "inside_cover": { "centre": { "x": true, "y": true }, "text": [ - + "El Manual del Mago", - + "Por Electroblob" ] }, - + "introduction": { "title": "Introduccion", "text": [ + + "Saludos Mago! Este gran libro te mostrara los secretos de las artes arcanas, y de como usarlas. Este no es un libro comun y corriente, ya que su conocimiento y sus paginas aumentaran a medida que descubres mas y mas sobre mundo magico.", - "¡Saludos Mago! Este libro te mostrara los secretos de las artes arcanas, y de cómo usarlas. Este no es un libro común y corriente, ya que su conocimiento y sus páginas aumentarán a medida que descubres más y más sobre mundo mágico.", + "Utiliza los botones en forma de flecha para pasar la pagina, y los botones de doble flecha para pasar de una seccion a otra. Usa el boton de menu central para regresar al indice.", - "Utiliza los botones en forma de flecha para pasar la página, y los botones de doble flecha para pasar de una sección a otra. Usa el botón de menú central para regresar al índice.", - - "Haz clic en cualquier palabra en color purpura para ir directamente al tema en cuestión. Haz clic derecho al marcador rojo para moverlo a la página actual, y clic izquierdo para saltar a la página marcada." + "Haz clic en cualquier palabra en color purpura para ir directamente al tema en cuestion. Haz clic derecho al marcador rojo para moverlo a la pagina actual, y clic izquierdo para saltar a la pagina marcada." ] }, "main_contents": { - "title": "Índice", + "title": "Indice", "contents": { "id": "main_contents", "hyperlinks": true, @@ -265,13 +265,13 @@ }, "setting_up": { - "title": "Iniciación", + "title": "Iniciacion", "include_in_contents": "main_contents", "text": [ - "Para comenzar como mago, necesitarás conseguir lo siguiente:", + "Para comenzar como mago, necesitaras conseguir lo siguiente:", - "- Una Varita mágica, hecha de la siguiente forma:", + "- Una Varita magica, hecha de la siguiente forma:", "#recipe magic_wand", @@ -279,29 +279,29 @@ "#recipe arcane_workbench", - "- Un @spells Libro de hechizos@. Estos se pueden encontrar en cofres, recompensas por matar monstruos, y comprado a otros magos. Puedes crear el hechizo Misil Mágico, un hechizo de bajo nivel, de la siguiente forma:", + "- Un @spells Libro de hechizo@. Estos se pueden encontrar en cofres, recompensas por matar monstruos, y comprado a otros magos. Puedes crear el hechizo Misil Magico, un hechizo de bajo nivel, de la siguiente forma:", "#recipe magic_missile_spell_book", - "También necesitaras bastantes Cristales Mágicos para suplir tu varita de @mana@." + "Tambien necesitaras bastantes Cristales Mágicos para suplir tu varita de @mana@." ] }, "mana": { - "title": "Maná", + "title": "Mana", "include_in_contents": "main_contents", "triggers": [ "ebwizardry:crystal" ], "text": [ - "El Maná es la energía mística que concede a los magos sus habilidades. Mucho más que una sustancia física, el Maná es un aura que impregna la realidad de forma omnipresente. En nuestro mundo, suele acumularse hasta formar cristales sólidos, los cuales pueden ser hallados incrustados en piedra en el subsuelo. Las @wands Varitas@ almacenan Maná dentro de ellas, y este Maná es canalizado hacia fuera al conjurar un @spells Hechizo@. Diferentes Hechizos requieren diferentes cantidades de Maná, dependiendo de que tan poderosos sean y de cuanto duren sus efectos.", + "El Mana es la energia mistica que concede a los magos sus habilidades. Mucho mas que una sustancia fisica, el Mana es un aura que impregna la realidad de forma omnipresente. En nuestro mundo, suele acumularse hasta formar cristales solidos, los cuales pueden ser hallados incrustados en piedra en el subsuelo. Las @wands Varitas@ almacenan Mana dentro de ellas, y este Mana es canalizado hacia fuera al conjurar un @spells Hechizo@. Diferentes Hechizos requieren diferentes cantidades de Mana, dependiendo de que tan poderosos sean y de cuanto duren sus efectos.", "#image crystal_ore", "#image magic_crystal", - "Al ser energía pura, el Maná no puede ser creado ni destruido. Cuando se conjura un hechizo, El Maná canalizado simplemente se disipa en el ambiente. Ciertamente, la mayoría de Maná se encuentra esparcido en pequeñas cantidades a través de todo el Cosmos. Sin embargo, para poder ser usado, el Maná debe de estar concentrado. Esto se puede lograr de forma natural a través de miles de años, como pasa con los cristales mágicos, o de forma artificial, un método avanzado no abordado en este libro. También existen diferentes formas de que el Maná sea mejor administrado al conjurar hechizos, y de recuperar una parte del Maná disipado en el ambiente." + "Al ser energia pura, el Mana no puede ser creado ni destruido. Cuando se conjura un hechizo, El Mana canalizado simplemente se disipa en el ambiente. Ciertamente, la mayoria de Mana se encuentra esparcido en peque\u00f1as cantidades a traves de todo el Cosmos. Sin embargo, para poder ser usado, el Mana debe de estar concentrado. Esto se puede lograr de forma natural a traves de miles de a\u00f1os, como pasa con los cristales magicos, o de forma artificial, un metodo avanzado no abordado en este libro. Tambien existen diferentes formas de que el Mana sea mejor administrado al conjurar hechizos, y de recuperar una parte del Mana disipado en el ambiente." ] }, @@ -313,17 +313,17 @@ ], "text": [ - "La varita es el arma y herramienta principal de un mago. Con ella, podrás conjurar cualquier @spells hechizo@ que la varita pueda canalizar (véase @tiers Niveles@). Las varitas vienen en diferentes variedades, pero es muy probable que empieces con una varita básica.", + "La varita es el arma y herramienta principal de un mago. Con ella, podras conjurar cualquier @spells hechizo@ que la varita pueda canalizar (vease @tiers Niveles@). Las varitas vienen en diferentes variedades, pero es muy probable que empieces con una varita basica.", "#image magic_wand", - "La mayoria de varitas comienzan como una estructura simple de un cristal mágico unido a un palo de madera, con una pepita de oro unido en el otro extremo. El cristal es, claro está, la fuente de poder de la varita, ya que provee el foco necesario para canalizar @spells hechizos@. El resto de la varita es importante también, ya que no solo provee un medio para sostener la varita, sino también un circuito interno por el cual el @mana maná@ puede ser canalizado y dirigido.", + "La mayoria de varitas comienzan como una estructura simple de un cristal magico unido a un palo de madera, con una pepita de oro unido en el otro extremo. El cristal es, claro esta, la fuente de poder de la varita, ya que provee el foco necesario para canalizar @spells hechizos@. El resto de la varita es importante tambien, ya que no solo provee un medio para sostener la varita, sino tambien un circuito interno por el cual el @mana mana@ puede ser canalizado y dirigido.", - "A medida que se conjuren hechizos con la varita, esta se volverá más efectiva en canalizar dichos @spells hechizos@, y eventualmente, en conjurar hechizos mas poderosos. Esto se puede notar debido a ciertos cambios en la varita misma: Mientras mas poderosa se vuelva una varita, su cristal mágico se volverá más brillante, su color puede llegar a cambiar dependiendo de su @elements elemento@, y después de un tiempo, la madera de la que esta hecha comenzara a crecer por si sola y a retorcerse en figuras mas complejas.", + "A medida que se conjuren hechizos con la varita, esta se volvera mas efectiva en canalizar dichos @spells hechizos@, y eventualmente, en conjurar hechizos mas poderosos. Esto se puede notar debido a ciertos cambios en la varita misma: Mientras mas poderosa se vuelva una varita, su cristal magico se volvera mas brillante, su color puede llegar a cambiar dependiendo de su @elements elemento@, y despues de un tiempo, la madera de la que esta hecha comenzara a crecer por si sola y a retorcerse en figuras mas complejas.", - "Mientras sostienes una varita, un pequeño recuadro aparecerá en la esquina de tu pantalla. Este recuadro muestra el hechizo seleccionado actualmente, además de una imagen para representar tal hechizo, si el hechizo ya fue conjurado, y una barra de cooldown indicando el tiempo restante para poder conjurar el hechizo otra vez. Tambien muestra los nombres del hechizo anterior y el hechizo siguiente vinculados a la varita. Para cambiar de hechizo, utiliza las teclas #next_spell_key and #previous_spell_key (Estas pueden ser modificadas en Opciones > Controles). Tambien puedes cambiar de hechizo utilizando la rueda del mouse mientras estas agachado.", + "Mientras sostienes una varita, un peque\u00f1o recuadro aparecera en la esquina de tu pantalla. Este recuadro muestra el hechizo seleccionado actualmente, ademas de una imagen para representar tal hechizo, si el hechizo ya fue conjurado, y una barra de cooldown indicando el tiempo restante para poder conjurar el hechizo otra vez. Tambien muestra los nombres del hechizo anterior y el hechizo siguiente vinculados a la varita. Para cambiar de hechizo, utiliza las teclas #next_spell_key y #previous_spell_key (Estas pueden ser modificadas en Opciones > Controles). Tambien puedes cambiar de hechizo utilizando la rueda del mouse mientras estas agachado.", - "Cuando veas tu inventario, si pones el mouse sobre la varita, veras cuanto @mana mana@ le queda, junto con el @spells hechizo@ seleccionado actualmente y otras habilidades que tenga. Puedes visualizar más información sobre la varita colocándola en una @arcane_workbench mesa arcana@." + "Cuando veas tu inventario, si pones el mouse sobre la varita, veras cuanto @mana mana@ le queda, junto con el @spells hechizo@ seleccionado actualmente y otras habilidades que tenga. Puedes visualizar mas informacion sobre la varita colocandola en una @arcane_workbench mesa arcana@." ] }, @@ -335,15 +335,15 @@ ], "text": [ - "Sin hechizos para conjurar y lanzar, una @wands varita@ es prácticamente inútil. Los hechizos son grabados en dos formas: en libros, los cual los hace permanentes, y en pergaminos, los cuales son temporales.", + "Sin hechizos para conjurar y lanzar, una @wands varita@ es practicamente inutil. Los hechizos son grabados en dos formas: en libros, los cual los hace permanentes, y en pergaminos, los cuales son temporales.", - "Los libros de hechizos son inútiles por si mismos, por lo que son usados para vincular el hechizo que contienen a las @wands varitas@. Los libros de hechizos se encuentran pueden ser hallados a través de todo el planeta, y no tomara mucho tiempo encontrarte con uno. Cuando lo encuentres, puedes colocar el mouse sobre el libro de hechizo para ver información básica sobre el hechizo que contiene. También puedes darle clic derecho mientras lo sostienes para leer mas acerca de él.", + "Los libros de hechizos son inutiles por si mismos, por lo que son usados para vincular el hechizo que contienen a las @wands varitas@. Los libros de hechizos se encuentran pueden ser hallados a traves de todo el planeta, y no tomara mucho tiempo encontrarte con uno. Cuando lo encuentres, puedes colocar el mouse sobre el libro de hechizo para ver informacion basica sobre el hechizo que contiene. Tambien puedes darle clic derecho mientras lo sostienes para leer mas acerca de el.", - "Por otro lado, los pergaminos sirven otro propósito: Haciendo click derecho mientras sostienes uno conjurará el hechizo escrito en el, consumiendo el pergamino. Al igual que los libros de hechizos, los pergaminos pueden ser hallados en muchas partes del mundo, pero también puedes crearlos tu mismo(a) (véase @enchanting_scrolls Encantando Pergaminos@).", + "Por otro lado, los pergaminos sirven otro proposito: Haciendo click derecho mientras sostienes uno conjurara el hechizo escrito en el, consumiendo el pergamino. Al igual que los libros de hechizos, los pergaminos pueden ser hallados en muchas partes del mundo, pero tambien puedes crearlos tu mismo(a) (vease @enchanting_scrolls Encantando Pergaminos@).", - "Los pergaminos son bastante utiles cuando necesitas utilizar un hechizo una o dos veces en una misión específica, y no quieres que tome espacio innecesario en tu @wands varita@. De todas formas, cabe destacar que no son tan efectivas para conjurar hechizos en comparación con las varitas.", + "Los pergaminos son bastante utiles cuando necesitas utilizar un hechizo una o dos veces en una mision especifica, y no quieres que tome espacio innecesario en tu @wands varita@. De todas formas, cabe destacar que no son tan efectivas para conjurar hechizos en comparacion con las varitas.", - "Desde la perspectiva de alguien común y corriente, los símbolos y runas en los cuales están escritos los hechizos son solo garabatos ilegibles. Pero tú no eres alguien común. ¡Eres un mago, joder! Y como tal, poco a poco iras aprendiendo a descifrar este lenguaje. Para identificar un hechizo podrías, claro está, simplemente conjurarlo y ver que pasa. Pero ten en cuenta que al hacerlo puede haber efectos no deseados, además de que muchos hechizos necesitan ciertas condiciones para funcionar. Además de eso, para novatos es probable leer el hechizo de forma errónea y pueden llegar a haber consecuencias peligrosas dependiendo de que tan poderoso sea el hechizo. Una opción más segura es utilizar un pergamino de identificación, los cuales pueden ser encontrados en cofres o comprados a otros magos. Al hacer click derecho mientras se sostiene uno, se identificará el primer hechizo desconocido en tu inventario, consumiendo el pergamino de identificación." + "Desde la perspectiva de alguien comun y corriente, los simbolos y runas en los cuales estan escritos los hechizos son solo garabatos ilegibles. Pero tu no eres alguien comun. Eres un mago, joder! Y como tal, poco a poco iras aprendiendo a descifrar este lenguaje. Para identificar un hechizo podrias, claro esta, simplemente conjurarlo y ver que pasa. Pero ten en cuenta que al hacerlo puede haber efectos no deseados, ademas de que muchos hechizos necesitan ciertas condiciones para funcionar. Ademas de eso, para novatos es probable leer el hechizo de forma erronea y pueden llegar a haber consecuencias peligrosas dependiendo de que tan poderoso sea el hechizo. Una opcion mas segura es utilizar un pergamino de identificacion, los cuales pueden ser encontrados en cofres o comprados a otros magos. Al hacer click derecho mientras se sostiene uno, se identificara el primer hechizo desconocido en tu inventario, consumiendo el pergamino de identificacion." ] }, @@ -361,7 +361,7 @@ ], "text": [ - "La mesa arcana es un bloque similar a la mesa de encantamientos, donde podrás recargar, mejorar, y vincular @spells hechizos@ a tu @wands varita@. Puedes crearla de la siguiente forma:", + "La mesa arcana es un bloque similar a la mesa de encantamientos, donde podras recargar, mejorar, y vincular @spells hechizos@ a tu @wands varita@. Puedes crearla de la siguiente forma:", "#recipe arcane_workbench", @@ -378,7 +378,7 @@ ], "text": [ - "Para vincular un @spells hechizo@ a tu @wands varita@, solo tienes que colocar la varita en la ranura central de la mesa arcana, luego colocar el libro de hechizo que desees en las ranuras alrededor y presionar “Aplicar”. Como habrás de notar, el acto de vincular un hechizo no afecta al libro, esto es porque al vincular un hechizo, la mesa arcana no “toma” el hechizo del libro, sino que de cierta forma “copia” de forma resumida el hechizo en la varita. De esta forma, puedes cambiar los hechizos vinculados a tu varita tantas veces como quieras con solo repetir el proceso. Puedes vincular hasta cinco hechizos diferentes a una sola varita cada vez, aunque este numero puede aumentar con mejoras de vinculación." + "Para vincular un @spells hechizo@ a tu @wands varita@, solo tienes que colocar la varita en la ranura central de la mesa arcana, luego colocar el libro de hechizo que desees en las ranuras alrededor y presionar \u0022Aplicar\u0022. Como habras de notar, el acto de vincular un hechizo no afecta al libro, esto es porque al vincular un hechizo, la mesa arcana no \u0022toma\u0022 el hechizo del libro, sino que de cierta forma \u0022copia\u0022 de forma resumida el hechizo en la varita. De esta forma, puedes cambiar los hechizos vinculados a tu varita tantas veces como quieras con solo repetir el proceso. Puedes vincular hasta cinco hechizos diferentes a una sola varita cada vez, aunque este numero puede aumentar con mejoras de vinculacion." ] }, "charging_wands": { @@ -389,7 +389,7 @@ ], "text": [ - "Para recargar tu @wands varita@, coloca la varita en la ranura central de la mesa arcana, coloca algunos cristales mágicos en la ranura inferior izquierda y luego presiona “Aplicar”. Cada cristal vale #mana_per_crystal @mana maná@, y la varita solo tomara lo que necesita, por lo que puedes dejar una provision de cristales en la mesa arcana para cuando la necesites. Sin embargo, ten en cuenta que la varita solo puede tomar números de cristales enteros, así que si por ejemplo, la varita necesita 30 mas de maná, #example_charging_loss de maná se perderán al cargarla." + "Para recargar tu @wands varita@, coloca la varita en la ranura central de la mesa arcana, coloca algunos cristales magicos en la ranura inferior izquierda y luego presiona \u0022Aplicar\u0022. Cada cristal vale #mana_per_crystal @mana mana@, y la varita solo tomara lo que necesita, por lo que puedes dejar una provision de cristales en la mesa arcana para cuando la necesites. Sin embargo, ten en cuenta que la varita solo puede tomar numeros de cristales enteros, asi que si por ejemplo, la varita necesita 30 mas de mana, #example_charging_loss de mana se perderan al cargarla." ] }, "upgrading_wands": { @@ -400,7 +400,7 @@ ], "text": [ - "Para mejorar tu @wands varita@, necesitaras un Tomo de Arcana del @tiers nivel@ adecuado, o también, una mejora especial para tu varita. Los Tomos de Arcana y las mejoras especiales pueden ser hallados en cofres o comprados a otros magos. Para mejorar tu varita, colocala en la ranura central, y coloca el objeto de mejora en la ranura superior derecha, luego presiona “Aplicar”.", + "Para mejorar tu @wands varita@, necesitaras un Tomo de Arcana del @tiers nivel@ adecuado, o tambien, una mejora especial para tu varita. Los Tomos de Arcana y las mejoras especiales pueden ser hallados en cofres o comprados a otros magos. Para mejorar tu varita, colocala en la ranura central, y coloca el objeto de mejora en la ranura superior derecha, luego presiona \u0022Aplicar\u0022.", "Mejoras especiales para la varita cambian el aspecto de la varita. Cada tipo de mejora se puede acumular tres veces, y el total de mejoras que una varita puede tomar depende de su nivel. Solo recuerda que las mejoras no pueden ser removidas." ] @@ -413,7 +413,7 @@ ], "text": [ - "La mesa Arcana tambien puede ser usada para encantar @spells hechizos@ en pergaminos. Para hacerlo, necesitaras un pergamino en blanco, creado con papel y un poco de cuerda, un libro de hechizo de tu preferencia, y suficientes cristales mágicos para proveer @mana maná@ para conjurar.", + "La mesa Arcana tambien puede ser usada para encantar @spells hechizos@ en pergaminos. Para hacerlo, necesitaras un pergamino en blanco, creado con papel y un poco de cuerda, un libro de hechizo de tu preferencia, y suficientes cristales magicos para proveer @mana mana@ para conjurar.", "#recipe blank_scroll", @@ -431,11 +431,11 @@ ], "text": [ - "Como el legendario y poderoso mago que eres, necesitaras algo para protegerte de las criaturas y monstruos que obviamente vencerás. Las armaduras normales son buenas, sí… pero checa eso: Sombrero, túnica, pantalones y botas. A diferencia de las armaduras normales, la armadura de mago no se rompe, ya que utiliza maná para endurecer su tela al punto de simular el metal, y proteger al usuario. Estas armaduras deben de ser recargadas con cristales mágicos, como las varitas. Afortunadamente, estas ropas no consumen mucho @mana maná@. Recárgalas como lo harías con una varita en la @arcane_workbench mesa arcana@ o con @mana_flasks frascos de mana@. Pero ten cuidado, si se le acaba el maná, lo único que te protegerá de aquellos monstruos será simple tela.", + "Como el legendario y poderoso mago que eres, necesitaras algo para protegerte de las criaturas y monstruos que obviamente venceras. Las armaduras normales son buenas, si... pero checa eso: Sombrero, tunica, pantalones y botas. A diferencia de las armaduras normales, la armadura de mago no se rompe, ya que utiliza mana para endurecer su tela al punto de simular el metal, y proteger al usuario. Estas armaduras deben de ser recargadas con cristales magicos, como las varitas. Afortunadamente, estas ropas no consumen mucho @mana mana@. Recargalas como lo harias con una varita en la @arcane_workbench mesa arcana@ o con @mana_flasks frascos de mana@. Pero ten cuidado, si se le acaba el mana, lo único que te protegera de aquellos monstruos sera simple tela.", "#image wizard_armour", - "Puedes crear una armadura de mago a partir de tela mágica:", + "Puedes crear una armadura de mago a partir de tela magica:", "#recipe magic_silk", "#recipe wizard_hat", @@ -443,11 +443,11 @@ "#recipe wizard_leggings", "#recipe wizard_boots", - "Puedes encantar una armadura de mago usando una mesa de encantamientos común, y debido a las propiedades de la tela mágica, es bastante efectiva en retener encantamientos. Los encantamientos de Protección contra Magia, Hielo y Electricidad son de particular utilidad para magos como tú, especialmente en defensa contra otros magos.", + "Puedes encantar una armadura de mago usando una mesa de encantamientos comun, y debido a las propiedades de la tela magica, es bastante efectiva en retener encantamientos. Los encantamientos de Proteccion contra Magia, Hielo y Electricidad son de particular utilidad para magos como tu, especialmente en defensa contra otros magos.", - "Aquellos magos que se dedican a la práctica de un elemento particular suelen llevar ropas especiales que les proporcionan efectos adicionales para dominar tal elemento. Conjuntos enteros de aquellas ropas son muy buscadas por los magos y no son fáciles de encontrar.", + "Aquellos magos que se dedican a la practica de un elemento particular suelen llevar ropas especiales que les proporcionan efectos adicionales para dominar tal elemento. Conjuntos enteros de aquellas ropas son muy buscadas por los magos y no son faciles de encontrar.", - "Leyendas hablan sobre un sello arcano usado por los magos y brujas mas poderosos. Dicen que aquel sello volverá la armadura de cualquier mago tan resistente como su contraparte mundana…" + "Leyendas hablan sobre un sello arcano usado por los magos y brujas mas poderosos. Dicen que aquel sello volvera la armadura de cualquier mago tan resistente como su contraparte mundana..." ] }, @@ -465,9 +465,9 @@ ], "text": [ - "No puedes dominar las artes arcanas quedándote encerrado en tu cuarto ¿o sí? ¡Hay un mundo entero de magia allá afuera para explorar! Ruinas Antiguas, reliquias y seres misteriosos benévolos y malévolos esperan ser descubiertos – si sabes donde buscar. Los más grandes magos también son los más curiosos, y mucho de su conocimiento y poder lo encontraron explorando el mundo que los rodea, y experimentando con lo que encuentran.", + "No puedes dominar las artes arcanas quedandote encerrado en tu cuarto, o si? Hay un mundo entero de magia alla afuera para explorar! Ruinas Antiguas, reliquias y seres misteriosos benevolos y malevolos esperan ser descubiertos, si sabes donde buscar. Los mas grandes magos tambien son los mas curiosos, y mucho de su conocimiento y poder lo encontraron explorando el mundo que los rodea, y experimentando con lo que encuentran.", - "Aunque un mago pasa la mayoría de su tiempo solo, compartir conocimiento es vital para aprender las artes arcanas. Busca a otros practicantes de la magia y aprende todo lo que puedas de ellos. La comunicación es uno de los poderes mas grandes que puedas tener." + "Aunque un mago pasa la mayoria de su tiempo solo, compartir conocimiento es vital para aprender las artes arcanas. Busca a otros practicantes de la magia y aprende todo lo que puedas de ellos. La comunicacion es uno de los poderes mas grandes que puedas tener." ], "sections": { "crystal_flowers": { @@ -478,7 +478,7 @@ ], "text": [ - "Durante tu travesía, es probable que te encuentre una curiosa y colorida flor que emite luz. Estas interesantes flores se les conoce como flores de cristal, y son altamente concentradas en @mana maná@. Nade sabe el porque de esto, lo que si sabemos es que sus pétalos están hechos de cristales de mana, haciendo de estas flores una fuente útil de mana en la superficie. La cantidad de maná obtenida de esta forma es limitada debido al pequeño tamaño de las flores, y que suelen crecer en grupos pequeños.", + "Durante tu travesia, es probable que te encuentre una curiosa y colorida flor que emite luz. Estas interesantes flores se les conoce como flores de cristal, y son altamente concentradas en @mana mana@. Nade sabe el porque de esto, lo que si sabemos es que sus petalos están hechos de cristales de mana, haciendo de estas flores una fuente util de mana en la superficie. La cantidad de mana obtenida de esta forma es limitada debido al peque\u00f1o tama\u00f1o de las flores, y que suelen crecer en grupos peque\u00f1os.", "#image crystal_flower", "#recipe crystal_flower_to_crystals" @@ -492,15 +492,15 @@ ], "text": [ - "No temas, no eres el único practicante de magia en este mundo. En tus viajes, puede que te encuentres con una o dos altas torres con techos puntiagudos. Esta es la residencia de un mago. A veces una vida de soledad puede volver a los magos un poco gruñones, pero usualmente son personas amigables que están dispuestos a compartir su conocimiento contigo, por un precio. No te sorprendas si debes pagar raros y preciosos metales y gemas, ya que a cambio recibirás grandes maravillas arcanas.", + "No temas, no eres el unico practicante de magia en este mundo. En tus viajes, puede que te encuentres con una o dos altas torres con techos puntiagudos. Esta es la residencia de un mago. A veces una vida de soledad puede volver a los magos un poco gru\u00f1ones, pero usualmente son personas amigables que estan dispuestos a compartir su conocimiento contigo, por un precio. No te sorprendas si debes pagar raros y preciosos metales y gemas, ya que a cambio recibiras grandes maravillas arcanas.", "En el caso de que fuese un hechizo de nivel @tiers maestro@ lo que buscas, necesitaras hablar con un especialista.", "#image wizard_tower", - "Desafortunadamente, existen algunos magos en cuyas puertas los visitantes no son bienvenidos. Estos magos suelen ser marginados, y son hostiles hacia todo aquel que se cruce en su camino. Ten cuidado con estos individuos. Si los encuentras, prepárate para defenderte contra magia poderosa. Véncelos, y su conocimiento será tuyo.", + "Desafortunadamente, existen algunos magos en cuyas puertas los visitantes no son bienvenidos. Estos magos suelen ser marginados, y son hostiles hacia todo aquel que se cruce en su camino. Ten cuidado con estos individuos. Si los encuentras, preparate para defenderte contra magia poderosa. Vencelos, y su conocimiento sera tuyo.", - "Cabe destacar que los magos no toman de buena forma el ser atacados o robados, y siendo ese el caso, no dudarán en tomar la justicia por sus propias manos." + "Cabe destacar que los magos no toman de buena forma el ser atacados o robados, y siendo ese el caso, no dudaran en tomar la justicia por sus propias manos." ] }, "obelisks": { @@ -511,13 +511,13 @@ ], "text": [ - "Vestigios de antiguas civilizaciones yacen esparcidas por todo el mundo. Los mas notables son estructuras de piedra tallada con símbolos y runas. Estas ruinas son todo lo que queda de lo que parece ser alguna clase de civilización antigua. Quien sea, o lo que sea que las haya construido, claramente tenían amplios conocimientos sobre magia, conocimientos que uso para conjurar encantamientos protectores que aun hoy día permanecen en aquellos lugares.", + "Vestigios de antiguas civilizaciones yacen esparcidas por todo el mundo. Los mas notables son estructuras de piedra tallada con simbolos y runas. Estas ruinas son todo lo que queda de lo que parece ser alguna clase de civilizacion antigua. Quien sea, o lo que sea que las haya construido, claramente tenian amplios conocimientos sobre magia, conocimientos que uso para conjurar encantamientos protectores que aun hoy dia permanecen en aquellos lugares.", - "Estas estructuras suelen ser de dos tipos. El primero, y el mas común, son los Obeliscos: altas construcciones puntiagudas hechas de piedras rúnicas, con una estructura inferior abierta que contiene reliquias arcanas menores, ya olvidadas en el pasado.", + "Estas estructuras suelen ser de dos tipos. El primero, y el mas comun, son los Obeliscos: altas construcciones puntiagudas hechas de piedras runicas, con una estructura inferior abierta que contiene reliquias arcanas menores, ya olvidadas en el pasado.", "#image obelisk", - "Estas estructuras suelen estar protegidas por un encantamiento que invoca @creatures criaturas@ del Caos Elemental cuando un humano se acerca demasiado. Vence a estas criaturas, destruye su invocador, y las reliquias serán tuyas." + "Estas estructuras suelen estar protegidas por un encantamiento que invoca @creatures criaturas@ del Caos Elemental cuando un humano se acerca demasiado. Vence a estas criaturas, destruye su invocador, y las reliquias seran tuyas." ] }, "shrines": { @@ -528,11 +528,11 @@ ], "text": [ - "El Segundo tipo es mucho mas raro, y son conocidos como santuarios. Estas estructuras consisten en un circulo de pilares de piedras rúnicas rodeando un pedestal central, en el cualse encuentra un cofre que contiene @artefacts artefactos@. Este cofre suele estar protegido por un encantamiento de Cerradura Arcana, impidiendo que cualquiera excepto el dueño lo abra. La estructura completa también esta protegida por un campo de contención, el cual impide el escape de cualquier cosa que se acerque demasiado.", + "El Segundo tipo es mucho mas raro, y son conocidos como santuarios. Estas estructuras consisten en un circulo de pilares de piedras runicas rodeando un pedestal central, en el cual se encuentra un cofre que contiene @artefacts artefactos@. Este cofre suele estar protegido por un encantamiento de Cerradura Arcana, impidiendo que cualquiera excepto el due\u00f1o lo abra. La estructura completa tambien esta protegida por un campo de contencion, el cual impide el escape de cualquier cosa que se acerque demasiado.", "#image shrine", - "Debido al gran poder e importancia arcana, sin mencionar las riquezas que contiene, estas estructuras son particularmente atractivas para cualquier mago aspirante. Pero ten cuidado, ha habido reportes de magos quedando atrapados en en el campo de contención y lentamente perdiendo la cordura a medida que pasa el tiempo. Algunos otros, creen que aquellas ocurrencias es un deliberado esfuerzo de la magia protectora del santuario, y que aquellos magos atrapados son controlados para proteger la estructura. Una perspectiva escalofriante sin duda para todo aquel que se atreva a aventurarse cerca…." + "Debido al gran poder e importancia arcana, sin mencionar las riquezas que contiene, estas estructuras son particularmente atractivas para cualquier mago aspirante. Pero ten cuidado, ha habido reportes de magos quedando atrapados en en el campo de contencion y lentamente perdiendo la cordura a medida que pasa el tiempo. Algunos otros, creen que aquellas ocurrencias es un deliberado esfuerzo de la magia protectora del santuario, y que aquellos magos atrapados son controlados para proteger la estructura. Una perspectiva escalofriante sin duda para todo aquel que se atreva a aventurarse cerca...." ] }, "creatures": { @@ -543,7 +543,7 @@ ], "text": [ - "Además de magos, una gran cantidad de criaturas utilizan magia. Muchas de estas criaturas son de origen arcano, y la mayoría pueden ser invocadas desde el Caos Elemental usando @spells hechizos@. Es posible encontrarse con estas criaturas protegiendo un obelisco o a un poderoso mago invocador. Seres arcanos menores pueden ser encontrados casualmente en la naturaleza." + "Ademas de magos, una gran cantidad de criaturas utilizan magia. Muchas de estas criaturas son de origen arcano, y la mayoria pueden ser invocadas desde el Caos Elemental usando @spells hechizos@. Es posible encontrarse con estas criaturas protegiendo un obelisco o a un poderoso mago invocador. Seres arcanos menores pueden ser encontrados casualmente en la naturaleza." ] }, "artefacts": { @@ -554,7 +554,7 @@ ], "text": [ - "Al explorar un @shrines santuario@, puedes ser lo bastante afortunado(a) como para encontrar alguna clase de artefacto mágico antiguo, un objeto capaz de concederle a su portador poderes y habilidades únicas y poderosas. Tres tipos de artefactos se han encontrado son: anillos, lo cuales conceden efectos especiales a hechizos, amuletos, los cuales mejoran habilidades defensivas y talismanes, que conceden efectos de utilidad. Estos artefactos parecen funcionar solo si se llevan de forma apropiada, el simple hecho de tenerla en su persona es insuficiente." + "Al explorar un @shrines santuario@, puedes ser lo bastante afortunado(a) como para encontrar alguna clase de artefacto magico antiguo, un objeto capaz de concederle a su portador poderes y habilidades unicas y poderosas. Tres tipos de artefactos se han encontrado son: anillos, lo cuales conceden efectos especiales a hechizos, amuletos, los cuales mejoran habilidades defensivas y talismanes, que conceden efectos de utilidad. Estos artefactos parecen funcionar solo si se llevan de forma apropiada, el simple hecho de tenerla en su persona es insuficiente." ] } } @@ -568,21 +568,21 @@ ], "text":[ - "Las @wands varitas@ y @spells hechizos@ vienen en cuatro niveles: #colour_novicenovice#colour_reset, #colour_apprenticeapprentice#colour_reset, #colour_advancedadvanced #colour_resetand #colour_mastermaster#colour_reset. Cada nivel es mas poderoso que el anterior.", + "Las @wands varitas@ y @spells hechizos@ vienen en cuatro niveles: #colour_novicenovato#colour_reset, #colour_apprenticeapprendiz#colour_reset, #colour_advancedavanzado #colour_reset y #colour_mastermaestro#colour_reset. Cada nivel es mas poderoso que el anterior.", "#image tiers", - "#colour_noviceNovice #colour_resetwands son las que pueden ser creadas. Pueden almacenar hasta #novice_max_charge maná, y solo pueden conjurar hechizos de nivel novato. Hechizos de nivel novato son lo bastante simples para ser conjurados por cualquiera y no suelen usar mucho maná. Esto no significa que son inútiles en niveles mas altos, ya que su bajo costo los hace bastante útiles incluso al tener acceso a hechizos de nivel maestro.", + "#colour_noviceNovato #colour_reset,es el nivel de varita en las que pueden ser creadas. Pueden almacenar hasta #novice_max_charge mana, y solo pueden conjurar hechizos de nivel novato. Hechizos de nivel novato son lo bastante simples para ser conjurados por cualquiera y no suelen usar mucho mana. Esto no significa que son inutiles en niveles mas altos, ya que su bajo costo los hace bastante utiles incluso al tener acceso a hechizos de nivel maestro.", - "#colour_apprenticeApprentice #colour_resetwands son el siguiente nivel de varitas después del nivel novato. Pueden almacenar hasta #apprentice_max_charge maná y puede conjurar hechizos de novato y hechizos de aprendiz. Los hechizos de aprendiz son mas impresionantes que los hechizos de novato, pero suelen costar más.", + "#colour_apprenticeApprendiz #colour_reset, es el siguiente nivel de varitas después del nivel novato. Pueden almacenar hasta #apprentice_max_charge mana y puede conjurar hechizos de novato y hechizos de aprendiz. Los hechizos de aprendiz son mas impresionantes que los hechizos de novato, pero suelen costar mas.", - "#colour_advancedAdvanced #colour_resetwands son mas raras y ponderosas. Pueden conjurar todos los hechizos excepto hechizos de nivel maestro, y almacenan hasta #advanced_max_charge maná. Hechizos de nivel avanzado pueden conceder poderes y habilidades super humanas como inivisibilidad y causar estragos en enemigos.", + "#colour_advancedAvanzado #colour_resetwands,las varitas de este nivel son mas raras y poderosas. Pueden conjurar todos los hechizos excepto hechizos de nivel maestro, y almacenan hasta #advanced_max_charge mana. Hechizos de nivel avanzado pueden conceder poderes y habilidades super humanas como invisibilidad y causar estragos en enemigos.", - "#colour_masterMaster #colour_resetwands son las varitas más poderosas que existen. Almacenan hasta #master_max_charge maná y pueden conjurar cualquier hechizo. Libros de hechizo de nivel maestro son muy raros, y ciertos hechizos pueden causar una terrible devastación no solo a enemigos, sino al mundo mismo. Usar con precaución.", + "#colour_masterMaestro #colour_reset,las varitas mas poderosas que existen. Almacenan hasta #master_max_charge mana y pueden conjurar cualquier hechizo. Libros de hechizo de nivel maestro son muy raros, y ciertos hechizos pueden causar una terrible devastacion no solo a enemigos, sino al mundo que los rodea. Usar con precaucion.", - "Para poder ser mejorada a un nivel mayor, la varita debe de volverse lo suficientemente poderosa como para canalizar hechizos de ese nivel. Las varitas ganan poder a medida de que hechizos son conjurados con ellas. Variedad, poder, efectos elementales y otros fatores externos pueden tener efecto en que tan rápido una varita gana este poder.", + "Para poder ser mejorada a un nivel mayor, la varita debe de volverse lo suficientemente poderosa como para canalizar hechizos de ese nivel. Las varitas ganan poder a medida de que hechizos son conjurados con ellas. Variedad, poder, efectos elementales y otros fatores externos pueden tener efecto en que tan rapido una varita gana este poder.", - "Una vez que una varita es lo suficientemente poderosa, un tomo de arcana proveerá el catalizador final para elevar a la varita al siguiente nivel (véase @upgrading_wands Mejorando varitas@), una vez que esto pasa, el proceso de maduración para la varita comienza de nuevo." + "Una vez que una varita es lo suficientemente poderosa, un tomo de arcana proveera el catalizador final para elevar a la varita al siguiente nivel (vease @upgrading_wands Mejorando varitas@), una vez que esto pasa, el proceso de maduracion para la varita comienza de nuevo." ] }, @@ -598,23 +598,23 @@ "#image elements", - "#colour_fireFire#colour_reset \n Talvez el elemento mas destructivo. El fuego se trata sobre quemar, consumir, lava y explosiones. Un Pirómano poderoso desatará el infierno ante sus enemigos y probablemente incendie el mundo en el proceso. La mayoría de hechizos de fuego envuelven a los enemigos en llamas, causando Daño adicional con el tiempo. Sin embargo, ten en cuenta que los hechizos de fuego no tienen efecto sobre las criaturas provenientes del Nether.", + "#colour_fireFuego#colour_reset \n Talvez el elemento mas destructivo. El fuego se trata sobre quemar, consumir, lava y explosiones. Un Piromano poderoso desatara el infierno ante sus enemigos y probablemente incendie el mundo en el proceso. La mayoria de hechizos de fuego envuelven a los enemigos en llamas, causando Da\u00f1o adicional con el tiempo. Sin embargo, ten en cuenta que los hechizos de fuego no tienen efecto sobre las criaturas provenientes del Nether.", - "#colour_iceIce#colour_reset \n El elemento de hielo contiene todo lo que es frio. Los hechizos gélidos suelen ralentizar a los enemigos o congelarlos completamente, y son especialmente efectivos en criaturas de fuego. Los hechizos de hielo suelen también ser útiles fuera de combate, como por ejemplo, congelar el agua para cruzar un rio.", + "#colour_iceHielo#colour_reset \n El elemento de hielo contiene todo lo que es frio. Los hechizos gelidos suelen ralentizar a los enemigos o congelarlos completamente, y son especialmente efectivos en criaturas de fuego. Los hechizos de hielo suelen tambien ser utiles fuera de combate, como por ejemplo, congelar el agua para cruzar un rio.", - "#colour_lightningLightning#colour_reset \n Este es el element que concierne los relámpagos, truenos y rayos, tormentas, lluvias y el clima. Un poderoso mago de la tormenta es una fuerza a temer, y algunos poseen la habilidad de comandar a los rayos a voluntad. Los hechizos de tormenta suelen dañar múltiples enemigos al mismo tiempo e incluso siguen a sus objetivos, haciendo de estos un ataque efectivo contra cualquier enemigo…..pero ten cuidado con los creepers.", + "#colour_lightningElectricidad#colour_reset \n Este es el elemento que concierne los relampagos, truenos y rayos, tormentas, lluvias y el clima. Un poderoso mago de la tormenta es una fuerza a temer, y algunos poseen la habilidad de comandar a los rayos a voluntad. Los hechizos de tormenta suelen da\u00f1ar multiples enemigos al mismo tiempo e incluso siguen a sus objetivos, haciendo de estos un ataque efectivo contra cualquier enemigo....pero ten cuidado con los creepers.", - "#colour_necromancyNecromancy#colour_reset \n La necromancia es el elemento de la oscuridad, caos y de la no-muerte. Los necromagos son misteriosos, y suelen considerarse malvados, aunque este no suele ser el caso. Los hechizos de necromancia suelen ser usados para invocar @creatures criaturas@ para pelear por ti o incluso doblegar la voluntad de tus enemigos.", + "#colour_necromancyNecromancia#colour_reset \n La necromancia es el elemento de la oscuridad, caos y de la no-muerte. Los necromagos son misteriosos, y suelen considerarse malvados, aunque este no suele ser el caso. Los hechizos de necromancia suelen ser usados para invocar @creatures criaturas@ para pelear por ti o incluso doblegar la voluntad de tus enemigos.", - "#colour_earthEarth#colour_reset \n El elemento de la tierra se enfoca en el mundo natural: animales, plantas, el viento, y cosas por el estilo. Los hechizos de tierra son diversos y toman una gran variedad de formas, desde envenenar enemigos hasta desencadenar la furia del clima terrenal. Los hechizos de tierra suelen ser una mezcla de ataque, defensa y utilidad.", + "#colour_earthTierra#colour_reset \n El elemento de la tierra se enfoca en el mundo natural: animales, plantas, el viento, y cosas por el estilo. Los hechizos de tierra son diversos y toman una gran variedad de formas, desde envenenar enemigos hasta desencadenar la furia del clima terrenal. Los hechizos de tierra suelen ser una mezcla de ataque, defensa y utilidad.", - "#colour_sorcerySorcery#colour_reset \n La hechicería es el elemento del cambio y la fuerza. Los hechiceros manipulan la luz, la gravedad e incluso la realidad misma para que se ajuste a sus necesidades. Los hechizos de hechicería pueden ser usados, entre muchas otras cosas, para darle al mago poderes especiales o mover objetos a voluntad.", + "#colour_sorceryHechiceria#colour_reset \n La hechiceria es el elemento del cambio y la fuerza. Los hechiceros manipulan la luz, la gravedad e incluso la realidad misma para que se ajuste a sus necesidades. Los hechizos de hechiceria pueden ser usados, entre muchas otras cosas, para darle al mago poderes especiales o mover objetos a voluntad.", - "#colour_healingHealing#colour_reset \n El elemento de curación es un elemento perfectamente válido, ¿verdad? ¡¿Verdad?! Este elemento se trata sobre defensa y regeneración. Los curanderos buscan protegerse a ellos mismos y a sus aliados en los mas posible y por eso suelen ser oponentes formidables. Aunque no suele ser usado de forma ofensiva, la luz purificadora de algunos hechizos de curación hará un Daño considerable a los no-muertos. Los hechizos de curación son indispensables en combate", + "#colour_healingCuracion#colour_reset \n El elemento de curacion es un elemento perfectamente valido, verdad? Verdad?! Este elemento se trata sobre defensa y regeneracion. Los curanderos buscan protegerse a ellos mismos y a sus aliados en los mas posible y por eso suelen ser oponentes formidables. Aunque no suele ser usado de forma ofensiva, la luz purificadora de algunos hechizos de curacion hara un Da\u00f1o considerable a los no-muertos. Los hechizos de curacion son indispensables en combate", - "Los límites entre diferentes elementos no son siempre diferentes, y algunos hechizos suelen tener semejanza a otros elementos diferentes al suyo.", + "Los limites entre diferentes elementos no son siempre diferentes, y algunos hechizos suelen tener semejanza a otros elementos diferentes al suyo.", - "Si tienes suerte, podrás encontrar un cristal elemental. Los cristales elementales son cristales mágicos que absorbieron propiedades elementales mientras se formaban. Estos cristales pueden ser usados para crear varitas elementales, las cuales potencian los hechizos de tal elemento." + "Si tienes suerte, podras encontrar un cristal elemental. Los cristales elementales son cristales magicos que absorbieron propiedades elementales mientras se formaban. Estos cristales pueden ser usados para crear varitas elementales, las cuales potencian los hechizos de tal elemento." ] }, @@ -639,18 +639,18 @@ ], "text": [ - "Provisiones Arcanas de Arkendur inc. – ¡Objetos mágicos para todas tus necesidades!", + "Provisiones Arcanas de Arkendur inc. Objetos magicos para todas tus necesidades!", - "¿Necesitas recargar tu @wands varita@ de forma rápida? ¡No hay problema! El @mana mana@ ya puede ser embotellado. Simplemente creas un frasco de mana con una botella y ocho cristales mágicos. Cuando quieras usarlo, solo colócalo en la mesa de crafteo con tu varita y cargara cierta parte. *", + "Necesitas recargar tu @wands varita@ de forma rapida? No hay problema! El @mana mana@ ya puede ser embotellado. Simplemente creas un frasco de mana con una botella y ocho cristales magicos. Cuando quieras usarlo, solo colocalo en la mesa de crafteo con tu varita y cargara cierta parte. *", "#recipe medium_mana_flask", - "¡NUEVO! Aquí llegaron los nuevos frascos de mana de tamaños pequeños y grandes – ¡ahora puedes seleccionar un frasco que satisface tus necesidades!", + "NUEVO! Aqui llegaron los nuevos frascos de mana de tama\u00f1os peque\u00f1os y grandes, ahora puedes seleccionar un frasco que satisface tus necesidades!", "#recipe small_mana_flask", "#recipe large_mana_flask", - "* Este proceso destruirá la botella. Cierto nivel de mana se pierde durante el embotellamiento." + "* Este proceso destruira la botella. Cierto nivel de mana se pierde durante el embotellamiento." ] }, "throwable_items": { @@ -661,7 +661,7 @@ ], "text": [ - "Algunos @spells hechizos@ conjuran objetos físicos, algunos de estos pueden ser creados directamente. Bombas de fuego, Bombas de veneno, Bombas de chispas y Bombas de humo pueden todas ser creadas:", + "Algunos @spells hechizos@ conjuran objetos fisicos, algunos de estos pueden ser creados directamente. Bombas de fuego, Bombas de veneno, Bombas de chispas y Bombas de humo pueden todas ser creadas:", "#recipe firebomb", "#recipe poison_bomb", @@ -676,13 +676,13 @@ "ebwizardry:enchant_scroll" ], "text": [ - - "Experimentación reciente ha revelado que es posible automatizar la conjuración de @spells hechizos@, hasta cierto punto, usando nada mas que un dispensador simple. Nadie sabe porque, pero parece que las extrañas propiedades de la piedra roja se extienden hasta la conjuración de un pergamino de hechizo. Poniendo un par de estos en un dispensador y dándole poder serviría como una buena trampa…." + + "Experimentacion reciente ha revelado que es posible automatizar la conjuracion de @spells hechizos@, hasta cierto punto, usando nada mas que un dispensador simple. Nadie sabe porque, pero parece que las extra\u00f1as propiedades de la piedra roja se extienden hasta la conjuracion de un pergamino de hechizo. Poniendo un par de estos en un dispensador y dandole poder serviria como una buena trampa." ] } } }, - + "crafting_recipes": { "title": "Recetas para crafteos", "include_in_contents": "main_contents", @@ -714,37 +714,37 @@ "#recipe spark_bomb" ] }, - + "credits": { "title": "Creditos", "include_in_contents": "main_contents", "text": [ - + "Electroblob's Wizardry \nVersion #version \nPara Minecraft #mcversion", - "Diseñado, programado y texturizado por Electroblob", + "Dise\u00f1ado, programado y texturizado por Electroblob", "Gracias a Minecraft Forge y MCP, cuya presencia hizo posible este mod.", - "¡Gracias también a la Comunidad de Mods de Minecraft, que siempre tiene una respuesta para mis preguntas!", + "Gracias tambien a la Comunidad de Mods de Minecraft, que siempre tiene una respuesta para mis preguntas!", - "Adicionalmente, me gustaría agradecer a los siguientes individuos por sus contribuciones al mod:", + "Adicionalmente, me gustaria agradecer a los siguientes individuos por sus contribuciones al mod:", "Codigo:", - "- Corail31 \n- 12foo \n- Shadows-of-Fire \n- HellFirePvP \n- Tora-B \n- Avatair \n- Aeronica \n- UltraHex \n- Azim-Palmer \n- raoulvdberge \n- rafasoares", + "- Corail31 \n- 12foo \n- Shadows-of-Fire \n- HellFirePvP \n- Tora-B \n- Avatair \n- Aeronica", "Traducciones:", - "- Español: MadWrist, Alsentar \n- Español de Mexico: MadWrist \n- Ruso: VilagVil, kellixon, bigenergy \n- Frances: Hahdrim \n- Portuges Brasileño: lorrampi \n- Chino: ZHENGLOC, dragon-evol, Hokorizero, TUsama, Determancer \n- Koreano: shejery, rewi_wire \n- Polaco: Trozuu, \nAlemán: BirdyDragon", + "- Espa\u00f1ol de Espa\u00f1a y Mexico: MadWrist, Alsentar \n- Ruso: VilagVil, kellixon \n- Frances: Hahdrim \n- Portuges Brasile\u00f1o: lorrampi \n- Chino: ZHENGLOC, dragon-evol \n- Koreano: shejery, rewi_wire \n- Polaco: Trozuu", - "Efecto de sonido para los relámpagos por OhhWowProductions", + "Efecto de sonido para los relampagos por OhhWowProductions", "Para mas informacion, revisa nuestra @https://github.com/Electroblob77/Wizardry/wiki wiki@.", - "¿Te encanta este mod? ¡unete al @https://discord.gg/MTmMzMv servidor de Discord@ para las ultimas noticias,discusiones y extras!" + "Te encanta este mod? unete al @https://discord.gg/hs8yJP2 servidor de Discord@ para las ultimas noticias,discusiones y extras!" ] } - + } } \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/texts/handbook_hu_hu.json b/src/main/resources/assets/ebwizardry/texts/handbook_hu_hu.json new file mode 100644 index 00000000..aa171ac6 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/texts/handbook_hu_hu.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.png", + "caption": "Az Arkán Munkaasztal", + "u": 0, + "v": 0, + "width": 110, + "height": 110 + }, + "crystal_ore": { + "location": "ebwizardry:textures/gui/crystal_ore.png", + "caption": "Kristályérc", + "u": 0, + "v": 0, + "width": 64, + "height": 64 + }, + "magic_crystal": { + "location": "ebwizardry:textures/items/crystal_magic.png", + "caption": "Egy Varázskristály", + "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": "Egy Varázspálca", + "u": 0, + "v": 0, + "width": 64, + "height": 64, + "border": false + }, + "crystal_flower": { + "location": "ebwizardry:textures/gui/crystal_flower.png", + "caption": "Egy Kristályvirág", + "u": 0, + "v": 0, + "width": 48, + "height": 48 + }, + "wizard_tower": { + "location": "ebwizardry:textures/gui/wizard_tower.png", + "caption": "Egy Varázslótorony", + "u": 0, + "v": 0, + "width": 110, + "height": 110 + }, + "wizard_armour": { + "location": "ebwizardry:textures/gui/wizard_armour.png", + "caption": "Egy Szett Varázslóruha", + "u": 0, + "v": 0, + "width": 90, + "height": 90 + }, + "obelisk": { + "location": "ebwizardry:textures/gui/obelisk.png", + "caption": "Egy Obeliszk", + "u": 0, + "v": 0, + "width": 110, + "height": 67 + }, + "shrine": { + "location": "ebwizardry:textures/gui/shrine.png", + "caption": "Egy Föld Szentély", + "u": 0, + "v": 0, + "width": 110, + "height": 67 + }, + "tiers": { + "location": "ebwizardry:textures/gui/tiers.png", + "caption": "A Pálcák 4 Szintje", + "u": 0, + "v": 0, + "width": 96, + "height": 24, + "border": false + }, + "elements": { + "location": "ebwizardry:textures/gui/elements.png", + "caption": "A 7 Arkán Elem", + "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": [ + + "A Varázsló Kézikönyve", + + "Írta Electroblob" + ] + }, + + "introduction": { + "title": "Bevezetés", + "text": [ + + "Üdvözlégy, varázsló! Ez a könyv elmagyarázza az arkánum csínját-bínját, és használatának módjait. De ez nem egy közönséges könyv - lapja úgy fognak megjelenni előtted, ahogy többet fedezel fel magad körül a mágikus világból.", + + "Használd a nyilakat a lapozáshoz, és a dupla nyilakat a fejezetek közötti gyors váltáshoz. A középső menü gombbal visszatérhetsz a tartalomjegyzékhez.", + + "Kattints bármely lila szövegrészletre, hogy rögtön a releváns lapra ugorj. A könyvjelzőt jobb kattintással a jelenlegi oldalra állíthatod be, és bal kattintással rá visszatérhetsz a megjelölt oldalhoz." + ] + }, + + "main_contents": { + "title": "Tartalom", + "contents": { + "id": "main_contents", + "hyperlinks": true, + "page_numbers": true, + "separator": "." + } + }, + + "setting_up": { + "title": "Felkészülés", + "include_in_contents": "main_contents", + "text": [ + + "Hogy elmélyülj a mágiában, szükséged lesz:", + + "- Egy varázspálcára, mely a következőképp készíthető:", + + "#recipe magic_wand", + + "- Egy @arcane_workbench arkán munkaasztalra@, így barkácsolva:", + + "#recipe arcane_workbench", + + "- Egy @spells varázsigés@ könyvre. Ezeket ládákban lehet találni, vagy mobok dobhatják, esetleg varázslóktól is vehetők, de a kezdők varázslatát, a mágikus lövedéket, a következőképp is megszerezheted:", + + "#recipe magic_missile_spell_book", + + "Szükséged lesz továbbá egy szép nagy rakás varázskristályra is még, hogy pálcáid elláthasd energiával, melyet úgy is neveznek, hogy @mana@." + ] + }, + + "mana": { + "title": "Mana", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:crystal" + ], + "text": [ + + "A Mana az arkán energia, mely a varázslók hatalmának alapját nyújtja. Önmagában nem kézzel fogható, fizikai anyag; sokkal inkább egy mindenhol jelenlévő aura, mely átitat mindent a világban. Különösen erősen manifesztálódik természetes kristályokban, melyeket sziklákba beágyazódva lehet a föld mélyén meglelni. @wands A pálcák@ képesek önmagukban manát tárolni, és ez a mana vezetődik át a @spells varázslatokba@ melyeket használsz. A különböző varázslatok különböző mennyiségű manát igényelnek, attól függően, hogy milyen erősek, és milyen hosszan tartanak.", + + "#image crystal_ore", + + "#image magic_crystal", + + "Széles körben elfogadott tény, hogy a manát nem lehet létrehozni, sem elpusztítani, és a varázslat során felhasznált mana egyszerűen szertefoszlik a környezetbe. És valóban, a mana legnagyobb része így van jelen - ritkásan szétszórva a világban. Viszont ahhoz, hogy fel lehessen használni, először koncentrálni kell, akár természetesen, évezredek alatt, ahogy történik az a kristályokkal mélyen a föld alatt, vagy mesterségesen - egy összetett és fejlett folyamattal, melyet ebben a könyvben nem fejtünk ki. Vannak továbbá különböző módszerek a varázslatok mana-hatékonyságának növelésére, és a varázslatok során szétszóródott mana egy részének visszacsenésére." + ] + }, + + "wands": { + "title": "Pálcák", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:arcane_initiate" + ], + "text": [ + + "A pálca a varázslók választott eszköze. Segítségével képes vagy bármilyen @spells varázslat@ megidézésére, feltéve hogy a pálca képes megtartani annak erejét (lásd @tiers Szintek@). A pálcák többféle méretben és alakban léteznek, de szinte biztosan egy egyszerű varázspálcával fogsz kezdeni.", + + "#image magic_wand", + + "A legtöbb pálca kezdetben nem más, mint egy varázskristály egy bot egyik végére, és egy aranyrög a másik végére rögzítve. A kristály, természetesen, a pálca erejének forrása, mivel ez adja a fókuszpontot a @spells varázslatoknak@. A pálca többi része is fontos ugyanakkor, hiszen nem csak fogást nyújtanak, hanem az utat is alkotják, melyen keresztül a @mana@ keresztülhalad és irányítódik.", + + "Ahogy több varázslatot hajtanak végre egy pálcával, egyre hatékonyabb lesz a @spells varázslatok@ használatában, s így képessé válik nagyobb erejű varázsigék használatára is. Ezt a hatást apró változásokból lehet észrevenni a pálca kinézetében: ahogy a pálca egyre erősebb lesz, a kristály egyre fényesebb lesz, színe elváltozhat @elements eleme@ alapján, és egy idő után fája elkezd nőni és komplex alakzatokba csavarodni.", + + "Mikor egy pálcát tartasz, egy kis kijelző jelenik meg a képernyő egyik sarkában. Ez mutatja meg az éppen kiválasztott varázslatot egy képikon segítségével, és, ha egy varázslatot végrehajtottak, az újra használhatóságig eltelő időt. Megmutatja továbbá az előző és a következő varázsigék neveit is, melyekre váltáshoz használd a #next_spell_key és a #previous_spell_key gombokat (ezek megváltoztathatók a Beállítások -> Irányítás menüpont alatt). Guggolás közben az egérgörgő segítségével is válthatsz varázslatot.", + + "Mikor az eszköztáradat nézed, a pálcára mutatva meglesheted, mennyi @mana@ van benne, a pillanatnyilag kiválasztott @spells varázslattal@ és egyéb különleges képességeivel együtt. Még mélyebb információt egy pálcáról annak egy @arcane_workbench arkán munkaasztalba@ helyezésével lehet szerezni." + ] + }, + + "spells": { + "title": "Varázslatok", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:handbook/spells" + ], + "text": [ + + "Egy @wands pálca@ hasznavehetetlen varázslatok nélkül. A varázslatok kétféle formában lelhetők meg: könyvekben, melyek állandóak, és tekercsekben, melyek ideiglenesek.", + + "A varázskönyveknek nem sok haszna van önmagukban; viszont segítségükkel hozzá lehet kötni a bennük rejlő varázslatot egy @wands pálcához@. A varázskönyvek szétszórva mindenfelé meglelhetők a világban, így nem telik majd sok időbe, mire találsz egyet. Ha meglelsz egyet, az egérrel rámutatva egy pillantás alatt megtudhatsz róla alapabb információkat. Jobb-kattintással ki is lehet nyitni őket, hogy ezáltal még többet olvashass róluk.", + + "A tekercsek viszont másfelől egy egészen eltérő célt szolgálnak: ha jobb-kattintanak velük, megidézi a bennük rejlő varázslatot, de a tekercs elpusztulásának árán. Akárcsak a varázskönyvek, a tekercsek is több helyen meglelhetőek, vagy készíthetsz is magadnak egyet (lásd a @enchanting_scrolls Tekercsek Bűvölése@ fejezetet).", + + "A tekercsek különösen hasznosak, ha kalandod során egy varázslatra csak egyszer vagy kétszer lesz szükséged, és nem akarsz helyet pazarolni eszköztáradban egy @wands pálcával@. Meg kell viszont jegyezni ugyanakkor, hogy meglehetősen ineffektív módjai a varázslatok végrehajtásának a pálcákhoz viszonyítva.", + + "A hozzá nem értő szemeknek a mágikus rúnák, melyekkel a varázsigék íródtak, olvashatatlanok. Hogy beazonosíts egy varázslatot, elvileg természetesen megtehetnéd, hogy egyszerűen megidézed, és nézed mi történik - bár ha így teszel, az könnyen nem várt mellékhatásokhoz vezethet, és sok varázslatnak specifikus kondíciók kellenek, hogy működjenek. Továbbá mindig fennáll a félreolvasás veszélye, mikor egy ismeretlen varázsigét használsz, melynek potenciálisan veszedelmes következményei lehetnek a varázsige erejétől függően. Egy biztonságosabb és megbízhatóbb módszer talán az azonosítás tekercsének használata, melyeket ládákban lehet megtalálni, vagy varázslóktól lehet venni. A jobb-kattintás egy ilyen tekerccsel beazonosítja az első ismeretlen könyvet vagy tekercset a gyorstáradban, elpusztítva az azonosítás tekercsét eközben." + ] + }, + + "arcane_workbench": { + "title": "Arkán Munkaasztal", + "include_in_contents": "main_contents", + "contents": { + "id": "arcane_workbench_subsections", + "hyperlinks": true, + "page_numbers": true, + "separator": "." + }, + "triggers": [ + "ebwizardry:handbook/arcane_workbench" + ], + "text": [ + + "Az arkán munkaasztal egy varázslóasztalhoz hasonlít kinézetében, s itt lehet feltölteni, fejleszteni, és @spells varázslatokkal@ felszerelni @wands pálcáid@. A következőképp lehet készíteni:", + + "#recipe arcane_workbench", + + "A következő oldal a különböző lehetőségeket részletezi, melyeket az arkán munkaasztalban lehet megcsinálni.", + + "#image workbench" + ], + "sections": { + "binding_spells": { + "title": "Varázsigék Hozzákötése", + "include_in_contents": "arcane_workbench_subsections", + "triggers": [ + "ebwizardry:handbook/arcane_workbench" + ], + "text": [ + + "Ahhoz, hogy @spells varázslatokat@ köthess @wands pálcádhoz@, egyszerűen helyezd pálcád a középső négyzetbe, és tedd a varázskönyvet az egyik környező négyzetbe, majd nyomd meg az alkalmazás gombot. Észreveheted, hogy a varázskönyv sértetlen marad eközben - ez amiatt van, hogy a varázsige hozzákötése nem 'szedi ki' a varázslatot a könyvből; sokkal inkább áthangolja a pálcát a varázslatra. Imígyen a pálcádhoz kötött varázslatokat annyiszor változtatod meg, ahányszor csak kívánod, a hozzákötési processzus megismétlésével. Egyszerre legfeljebb öt varázslatot köthetsz egy pálcához, de ezt a számot növelheted ráhangolódási fejlesztésekkel." + ] + }, + "charging_wands": { + "title": "Pálcák töltése", + "include_in_contents": "arcane_workbench_subsections", + "triggers": [ + "ebwizardry:handbook/arcane_workbench" + ], + "text": [ + + "Hogy feltöltsd @wands pálcád@, helyezd azt a középső négyzetbe, majd tégy pár varázskristályt a bal alsó négyzetbe, s nyomd meg az alkalmazás gombot. Minden egyes kristály #mana_per_crystal @mana manát@ tölt bele, s a pálca csupán annyit vesz fel, amennyire szüksége van, tehát állandóan tarthatsz némi kristályutánpótlást a munkaasztalban, várva mikor lesz szükséged rájuk. De tartsd észben, hogy a pálca csupán egész számú kristályt vehet fel, s így ha a pálcának, példának okáért, még 30 mana kell, #example_charging_loss mana el fog veszni, miközben megtöltöd." + ] + }, + "upgrading_wands": { + "title": "Pálcák Fejlesztése", + "include_in_contents": "arcane_workbench_subsections", + "triggers": [ + "ebwizardry:handbook/tome_of_arcana" + ], + "text": [ + + "Hogy fejleszthesd @wands pálcád@, szükséged lesz egy megfelelő @tiers szintű@ arkánum kötetre, vagy egy speciális pálcafejlesztésre. Mindkettő lelhető zsákmányként vagy megvehető varázslóktól. A fejlesztés feltevéséhez helyezd pálcád a középső négyzetbe, majd a fejlesztést a jobb felső négyzetbe, és nyomd meg az alkalmazás gombot.", + + "A különleges pálcafejlesztések egyedi aspektusait javítják a pálcának. Minden típus maximum háromszor tehető fel egyazon pálcára, és az egy pálcára tehető fejlesztések száma függ a pálca szintjétől. A fejlesztéseket nem lehet eltávolítani." + ] + }, + "enchanting_scrolls": { + "title": "Tekercsek Bűvölése", + "include_in_contents": "arcane_workbench_subsections", + "triggers": [ + "ebwizardry:handbook/scrolls" + ], + "text": [ + + "Az arkán munkaasztal tekercsek @spells varázsigékkel@ megbűvölésére is alkalmasak. Ehhez szükség lesz egy üres tekercsre, mely papírból és némi fonálból készül, a választott varázslatot tartalmazó könyvre, és elég varázskristályra, hogy a varázslat használatához szükséges @mana@ meglegyen.", + + "#recipe blank_scroll", + + "Helyezd a kristályokat a bal alsó négyzetbe, az üres tekercset a középső négyzetbe, és a varázskönyvet az alatta lévő négyzetbe, majd nyomd meg az alkalmazás gombot a tekercs megbájolásához." + ] + } + } + }, + + "wizard_armour": { + "title": "Varázsló Öltözködés", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:arcane_initiate" + ], + "text": [ + + "Varázslóként szükséged lesz valamire, ami megvéd téged a lényektől, melyek ellen küzdesz. De semmilyen közönséges páncélzat nem elég jó ehhez. Hogy a legtöbbet hozd ki varázslataidból, varázslóruhákra lesz szükséged, kalap, köntös, nadrág, csizmák. A közönséges páncélokkal ellentétben ezek nem fognak eltörni. Ehelyett arkán energiát használnak viselőjük védelmezésére. Ebből természetesen az következik, hogy varázskristályokkal kell őket feltölteni, akár csak a pálcákat, ám szerencsére ezen öltözékek nem nyelnek el olyan sok @mana manát@. Töltsd fel őket, ahogy egy pálcát töltenél fel az @arcane_workbench arkán munkaasztalban@, vagy @mana_flasks manapalackokkal@, de légy elővigyázatos - ha kifutnak a manából, védtelenül leled magad.", + + "#image wizard_armour", + + "A varázslóöltözékek szabásához mágikus selyemre lesz szükséged:", + + "#recipe magic_silk", + "#recipe wizard_hat", + "#recipe wizard_robe", + "#recipe wizard_leggings", + "#recipe wizard_boots", + + "Közönséges varázslóasztal segítségével meg lehet bájolni ezen ruhadarabokat, s a mágikus selyem veleszületett tulajdonságainak köszönhetően igen effektív általánosságban a bűbájok megtartásában. Különösen hasznosak a varázslók számára a Varázs-, Fagy- és Sokkvédelmet nyújtó bűbájok, melyek hasznosnak bizonyulhatnak más mágiahasználók ellen.", + + "Azok a varázslók, melyek egyetlen specifikus elem gyakorlásának szentelik magukat, gyakran speciális öltözéket viselnek, melyek bónuszokat nyújtanak adott elemhez. Teljes szettjei az ilyen öltözeteknek igen keresettek, és nem könnyű rájuk lelni.", + + "Legendák mesélnek egy arkán pecsétről, melyet a leghatalmasabb varázslók használnak öltözetük nagymértékű megerősítésére." + ] + }, + + "magical_world": { + "title": "Mágia a Világban", + "include_in_contents": "main_contents", + "contents": { + "id": "magical_world_subsections", + "hyperlinks": true, + "page_numbers": true, + "separator": "." + }, + "triggers": [ + "ebwizardry:handbook/on_subsection_unlock" + ], + "text": [ + + "Nem lehet az arkán útjainak mesterévé válni, ha valaki csak otthon ül - a világ tele odakinn varázzsal, mely felfedezésre vár! Ősi romok, relikviák és rejtélyes lények, melyek jót vagy rosszat hoznak, mind felfedezésre várnak - ha tudod, hol keresd őket. A legnagyobb varázslók egyben a legkíváncsibbak is, és tudásuk és hatalmuk javát környezetük felfedezésével és kísérletezéssel szerzik.", + + "Míg egy varázsló idejének legnagyobb részét elkerülhetetlenül egyedül tölti, a tudás megosztása is létfontosságú az arkán művészetek kitanulásában. Keresd fel a mágia többi gyakorlóját, s tanulj olyan sokat tőlük, amennyit csak lehet: a kommunikáció vitathatatlanul a legnagyobb hatalom mind közül." + ], + "sections": { + "crystal_flowers": { + "title": "Kristályvirágok", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:handbook/crystal_flowers" + ], + "text": [ + + "Utazásaid során valószínűleg találkozni fogsz majd érdekes, világító virágokkal, melyek időről időre a vadonban nőnek. Ezek a jellegzetes virágok kristályvirágokként is ismeretesek, és furcsán hatékonyak a @mana@ koncentrálásában - mai napig senki sem tudja biztosra, miért. Amit viszont tudunk, az az, hogy gyűjthetők és a mana kristályok formájában kinyerhető belőlük, mely által igencsak hasznos felszín feletti manaforrásokká válnak. Az ily módon gyűjthető mana mennyisége ugyanakkor erősen limitált, főként a virágok kis mérete és amiatt, hogy csak elszórt foltokban nőnek.", + + "#image crystal_flower", + "#recipe crystal_flower_to_crystals" + ] + }, + "wizard_towers": { + "title": "Varázslótornyok", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:wizard_tower" + ], + "text": [ + + "Ne félj - nem te vagy a mágia egyetlen gyakorlója ezen a világon. Utazásaid során tán már találkoztál is egy vagy két magas toronnyal, jellegzetes csúcsos tetővel. Az egy varázslótársad lakhelye. A magányos élet folytán kissé morgóssá válhatnak olykor, de általában barátságosak népség, mely hajlandó megosztani tudását veled - bár nem ingyen. Készülj fel, hogy értékes fémekkel és ékkövekkel fizess, s cserébe fogadd az arkán nagy csodáit.", + + "Ugyanakkor, ha @tiers mester@ szintű varázslatot keresel, egy specialistával kell majd beszélned.", + + "#image wizard_tower", + + "Sajnálatos módon néhány varázsló nem látja szívesen a látogatókat. Ezek a varázslók általában számkivetettek, és ellenségesek mindenki ellen, ki keresztezi útjukat. Igen nagy elővigyázatossággal közelítsd meg ezeket a személyeket, s légy felkészülve életed megvédésére erős mágia ellen. Ám győzd le őket, s tudásukat megszerezheted magadnak.", + + "Azt is meg kell jegyeznünk, hogy egy varázsló sem veszi örömmel, ha megtámadják vagy kirabolják őket - s készen állnak kezelésbe venni a dolgokat." + ] + }, + "obelisks": { + "title": "Obeliszkek", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:handbook/obelisks" + ], + "text": [ + + "Ősi mágia nyomai vannak szétszóródva a világban, melyek közül az egyik legfeltűnőbbek azon faragott kőépítmények, melyek szimbólumok és rúnák tömkelegével vannak tele. Ezek a romok minden, mi megmaradt valamiféle ősi civilizációból. Akárki, vagy akármi építette őket, tisztán láthatóan jelentős tudással bírtak a mágia területén, és védelmező bűbájokat tettek ezen helyekre, melyek mind a mai napig fennmaradtak.", + + "Ezek a struktúrák tágan véve két alapvető típusba sorolhatóak. Az első, és gyakoribb, az obeliszkek: magas faragott kövek, melyek tüskeként merednek az ég felé, s rúnakövekből állnak. Talapzatában az elfeledett régmúlt kisebb arkán relikviái nyugszanak.", + + "#image obelisk", + + "Ezek a struktúrák tipikusan egy bűbájjal védettek, mely ellenséges @creatures mágikus lényeket@ idéz meg, amennyiben bárki emberfia közel merészkedne. Győzd le ezeket a lényeket, pusztítsd el forrásukat, és a relikviák a tiéid." + ] + }, + "shrines": { + "title": "Szentélyek", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:visit_shrine" + ], + "text": [ + + "A második, és ritkább fajta struktúra a szentélyek. Ezek egy rúnakő pillérek körével körbevett központi pedesztálból állnak, melyen egy láda nyugszik, tele ősi @artefacts műalkotásokkal@. Ez a láda általában egy arkán zárbűbájjal van védve, megakadályozva tulajdonosán kívül mindenkit kinyitásában. A teljes struktúra is védve van továbbá egy elzárt mezővel, mely megakadályoz bármit, mi túl közel merészkedik, a megszökésben.", + + "#image shrine", + + "Nagy arkán erejük és jelentőségük miatt, a bennük rejlő kincsekről nem is beszélve, ezek az építmények különösen vonzóak minden feltörekvő varázsló számára - de légy óvatos. Számos hír jár varázslókról, kiket rabul ejtett a bezáró mező, s az őrületbe menekültek, talán klausztrofóbia okán. Egyre többen vélik azonban úgy, hogy a rabul ejtés szándékos része a szentélyt védelmező mágiának, s a foglyul ejtett varázslókat irányítva védelmezi magát. Hátborzongató ötlet, az bizonyos, mindazoknak, kik közel merészkednek..." + ] + }, + "creatures": { + "title": "Mágikus Lények", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:handbook/magical_creatures" + ], + "text": [ + + "A varázslókon kívül rengeteg más lény is használ mágiát. A legtöbb ilyen lény arkán eredetű, és megidézhetők bizonyos @spells varázslatok@ segítségével. Ezen lényekkel összefuthat a vándor esetleg az általuk őrzött @obelisks obeliszkek@ környékén, vagy egy hatalmas idéző által. A kisebb arkán lények időnként akár a vadonban is fellelhetők." + ] + }, + "artefacts": { + "title": "Relikviák", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:artefact" + ], + "text": [ + + "Miközben @shrines szentélyeket@ fedezel fel, tán rád mosolyog a szerencse, s valamilyen ősi mágikus relikviára lelhetsz - egy tárgyra, mely képes viselőjének egyedi és hatalmas erejű hatásokat és különleges erőket ruházni. Három ismert típust találtak eddig: gyűrűket, melyek bónuszokat és effekteket nyújtanak varázslatokhoz, amuletteket, melyek a védekező képességeket javítják, és ékeket, melyek hasznos effekteket nyújtanak. Ezek a relikviák csak akkor látszódnak megfelelően működni, ha rendesen viselik őket; egyszerűen magadnál tudni őket nem elég." + ] + } + } + }, + + "tiers": { + "title": "Szintek", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:handbook/tome_of_arcana" + ], + "text":[ + + "A @wands pálcák@ és @spells varázslatok@ négy szintben fordulnak elő: #colour_novicekezdő#colour_reset, #colour_apprenticetanítvány#colour_reset, #colour_advancedhaladó #colour_resetand és #colour_mastermester#colour_reset. Minden egyes szint erősebb az előzőnél.", + + "#image tiers", + + "A #colour_novicekedző #colour_resetpálcák azok, melyek barkácsolhatók. Maximum #novice_max_charge manát képesek tárolni, és csak kezdő varázslatokat tudnak használni. Ezek általában elég egyszerűek, hogy bárki végre tudja hajtani őket, és nem kerülnek sok manába. Ez azonban nem feltétlenül teszi őket hasznavehetetlenné, ha már magasabb szinteket is elérsz; alacsony áruk miatt egyes kezdő varázslatok még akkor is hasznosak maradnak, mikor már mester szintű varázslatokhoz is hozzáférsz.", + + "A #colour_apprenticetanítvány #colour_resetszintű pálcák a következő szint a kedző után. Képesek maximum #apprentice_max_charge mana tárolására, és kezdő és tanítványi varázslatokra is képesek. A tanítványi varázslatok jellemzően kicsivel lenyűgözőbbek a kezdő varázslatoknál, de általában többe is kerülnek.", + + "A #colour_advancedhaladó #colour_resetszintű pálcák meglehetősen ritkák, és sokkal erősebbek. Mester szintű varázslatok kivételével mindenre képesek, és maximum #advanced_max_charge mana megtartására képesek. A haladó varázslatok emberfeletti erőkkel ruházhatnak fel, mint például a láthatatlanság, s pusztítást rendeznek az ellenség soraiban.", + + "A #colour_mastermester #colour_resetszintű pálcák a leghatalmasabb pálcák a létezésben. Legfeljebb #master_max_charge manát tárolnak, s bármilyen varázslatot képesek használni. A mesteri szintű varázskönyvek igen ritkák, és a bennük rejlő varázslatok teljes pusztulást hozhatnak nemcsak az ellenfélre, de magára a világra is. Használd elővigyázatossággal.", + + "Hogy a következő szintre fejlődhessen, egy pálcának elég erősnek kell lennie ahhoz, hogy kibírja az adott szint varázslatainak használatát. A pálcák úgy nyernek erőt, ahogy varázslatokat idéznek meg velük. A varázslatok sokfélesége, ereje, elementális hatásai és egyéb külső hatások mind együtt befolyásolják, milyen gyorsan növekszik egy pálca.", + + "Ha egyszer egy pálca elegendően erős, egy arkánum kötet adja a végső löketet, mely szükséges a következő szintre fejlődéshez (lásd @upgrading_wands Pálcák Fejlesztése@), s ha egyszer ez megtörtént, az erősödési folyamat elölről kezdődhet a következő szinthez." + ] + }, + + "elements": { + "title": "Elemek", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:handbook/elements" + ], + "text": [ + + "A @spells varázslatok@ különböző elemekhez tartoznak, melyek meghatározzák a varázslat természetét, és előnyökhöz juttatnak, ha bizonyos @wands pálcákkal@ használják őket együtt.", + + "#image elements", + + "#colour_fireTűz#colour_reset \nTán a legpusztítóbb elem, a tűz szakterülete az égés, láva, és robbanások. Egy erős pyromágus képes a poklot rászabadítani ellenfeleire, és valószínűleg lángokba állítja a világot eközben. A legtöbb tűzalapú támadás felgyújtja a célpontot, folyamatos sebzést okozva. Ugyanakkor légy figyelemmel arra, hogy a tűzvarázslatoknak nincs hatása az alvilági lényeken.", + + "#colour_iceJég#colour_reset \nA jég eleme mindabból áll, mi hideg. A fagyvarázslatok gyakran lelassítják az ellenfelet, vagy teljesen megfagyasztják, és különösen hatékonyak a lángok lényei ellen. A fagymágia legalább ilyen hasznosnak bizonyulhat harcon kívül is, mint például egy folyó befagyasztásánál az átkeléshez.", + + "#colour_lightningVillám#colour_reset \nEz az elem magába foglalja a villámokat, viharokat és az időjárást. Egy hatalmas viharmágus olyan erő, melyet nem lehet figyelmen kívül hagyni, és bír azon képességgel, hogy villámot hívjon le akaratával. A villámvarázslatok gyakran több ellenfelet is megsebeznek egyszerre, és gyakran követik célpontjukat, hatékonnyá téve őket minden mob ellen... de vigyázz a creeperekkel.", + + "#colour_necromancyNekromágia#colour_reset \nA nekromágia a sötétség eleme, a káoszé és élőhalálé. A nekromágusok rejtélyesek, és gyakran gonosznak tartottak, még ha gyakran nem is ez az eset. A nekromágiai varázslatok gyakran használtak arra, hogy @creatures lényeket@ idézzen meg, melyek helyetted harcolnak, vagy akár ellenfeled akaratának megtörésére.", + + "#colour_earthFöld#colour_reset \nA föld eleme a természetes világon fókuszál: állatok, növények, a szél, és társaik. A földmágia változatos és sokféle formát ölthet fel, ellenfelek megmérgezésétől az időjárás haragjának elszabadításáig, s ezáltal támadás, védekezés és hasznosságok keverékét nyújtja.", + + "#colour_sorceryMisztika#colour_reset \nA misztika az erő és változás eleme. A misztikus mágusok a fényt, gravitációt vagy akár a valóságot magát manipulálják saját szükségleteiknek megfelelően. Így a misztikus varázslatok használhatók például mágikus erők adására használójuknak, vagy objektumok mozgatására akaratukkal.", + + "#colour_healingGyógyítás#colour_reset \nA gyógyítás eleme a védelemmel és regenerációval foglalkozik. A gyógyítók célja, hogy megvédjék magukat és társaikat amennyire csak lehet, és így igen nehéz ellenfeleknek bizonyulhatnak. Bár a gyógyítás általában nem használt támadásra, némely gyógyító varázslat tisztító fénye figyelemre méltó sebzést okozhat az élőholtaknak. A gyógyító varázslatok elengedhetetlenek harcban.", + + "A határvonalak a különböző elemek között nem mindig határozottak, és némely varázslatok sajátjukon kívül más elemek jellegzetességeit is viselik.", + + "Ha szerencsés vagy, találhatsz egy elementáris kristályt. Ezek olyan varázskristályok, melyek elementáris tulajdonságokat szívtak magukba, míg nőttek - bár a pontos feltételek és a mechanizmus még rejtélyes. Azt viszont tudjuk, hogy ezen kristályok segítségével elementáris pálcák készíthetők, melyek segítségével a megfelelő elem varázslatai a szokásosnál nagyobb potenciállal idézhetőek meg." + ] + }, + + "miscellaneous": { + "title": "Egyebek", + "include_in_contents": "main_contents", + "contents": { + "id": "miscellaneous_subsections", + "hyperlinks": true, + "page_numbers": true, + "separator": "." + }, + "triggers": [ + "ebwizardry:on_subsection_unlock" + ], + "sections": { + "mana_flasks": { + "title": "Manapalackok", + "include_in_contents": "miscellaneous_subsections", + "triggers": [ + "ebwizardry:handbook/mana_flasks" + ], + "text": [ + + "Arkendur's Arcane Supplies Co. - mágikus tárgyak minden célra!", + + "Újra kell töltened @wands pálcád@ út közben? Nem probléma! A @mana mana@ mostantól palackba zárható. Egyszerűen barkácsolj össze egy manapalackok egy üres üvegpalackból és 8 varázskristályból, s vidd magaddal. Mikor szükséged válik rá, egyszerűen barkácsold össze pálcáddal és némi manát vissza fog tölteni. *", + + "#recipe medium_mana_flask", + + "ÚJ! Bemutatjuk a vadonatúj kis és nagy manapalackokat - Mostantól magad választhatod, melyik a legalkalmasabb számodra!", + + "#recipe small_mana_flask", + "#recipe large_mana_flask", + + "* Ez az eljárás el fogja törni a palackot. Palackozás közben elvész némi mana." + ] + }, + "throwable_items": { + "title": "Eldobható Tárgyak", + "include_in_contents": "miscellaneous_subsections", + "triggers": [ + "ebwizardry:handbook/throwables" + ], + "text": [ + + "Változatos @spells varázsigék@ idéznek meg fizikai tárgyakat, melyek közül némely közvetlenül is barkácsolható. Tűzbombák, méregbombák, szikrabombák és füstbombák mind készíthetőek:", + + "#recipe firebomb", + "#recipe poison_bomb", + "#recipe smoke_bomb", + "#recipe spark_bomb" + ] + }, + "automated_casting": { + "title": "Automatizált Varázslás", + "include_in_contents": "miscellaneous_subsections", + "triggers": [ + "ebwizardry:enchant_scroll" + ], + "text": [ + + "Újkeletű kísérletezések felfedték, hogy lehetséges a @spells varázslatok@ használatának automatizálása, bizonyos fokig, nem mást használva, mind egy egyszerű adagolót. Senki sem biztos benne, miért, de úgy néz ki, hogy a vöröskő érdekes tulajdonságai még a varázstekercsek aktiválására is kiterjednek. Párat az adagolóba helyezve és jelet adva neki elvileg működnie kéne..." + ] + } + } + }, + + "crafting_recipes": { + "title": "Barkácsreceptek", + "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": "Köszönetnyílvánítások", + "include_in_contents": "main_contents", + "text": [ + + "Electroblob's Wizardry \nVersion #version \nFor Minecraft #mcversion", + + "Design, kódolás és textúrázás: Electroblob", + + "Köszönet a Minecraft Forge-nak és MCP-nek, melyek nélkül ez a mod nem lett volna lehetséges.", + + "Szintén köszönet a Minecraft modding közösségnek, mely mindig válaszol moddolási gondjaimra!", + + "Továbbá szeretnék köszönetet mondani a következő egyéneknek a modhoz való hozzájárulásukért:", + + "Kód:", + + "- Corail31 \n- 12foo \n- Shadows-of-Fire \n- HellFirePvP \n- Tora-B \n- Avatair \n- Aeronica \n- UltraHex \n- Azim-Palmer \n- raoulvdberge \n- rafasoares", + + "Fordítások:", + + "- Spanyol: MadWrist, Alsentar \n- Mexikói Spanyol: MadWrist \n- Orosz: VilagVil, kellixon, bigenergy \n- Francia: Hahdrim \n- Braziliai Portugál: lorrampi \n- Kínai: ZHENGLOC, dragon-evol, Hokorizero, TUsama, Determancer \n- Koreai: shejery, rewi_wire \n- Lengyel: Trozuu \n-Német: BirdyDragon \n-Taiwán: chesterccj305 \n-Magyar: Bombadil", + + "Villámsugár hangeffekt OhhWowProductions által", + + "Még több információért keresd fel a @https://github.com/Electroblob77/Wizardry/wiki wikit@.", + + "Sosem elég a varázslásból? Csatlakozz a @https://discord.gg/hs8yJP2 Discord szerverhez@ a legfrissebb hírekért, beszélgetésekért és extrákért! (Angol nyelvű.)" + ] + } + + } +} diff --git a/src/main/resources/assets/ebwizardry/texts/handbook_ru_ru.json b/src/main/resources/assets/ebwizardry/texts/handbook_ru_ru.json new file mode 100644 index 00000000..2e7da3d8 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/texts/handbook_ru_ru.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.png", + "caption": "\u0427\u0430\u0440\u043e\u0434\u0435\u0439\u0441\u043a\u0438\u0439 \u0421\u0442\u043e\u043b", + "u": 0, + "v": 0, + "width": 110, + "height": 110 + }, + "crystal_ore": { + "location": "ebwizardry:textures/gui/crystal_ore.png", + "caption": "\u041a\u0440\u0438\u0441\u0442\u0430\u043b\u044c\u043d\u0430\u044f \u0420\u0443\u0434\u0430", + "u": 0, + "v": 0, + "width": 64, + "height": 64 + }, + "magic_crystal": { + "location": "ebwizardry:textures/items/crystal_magic.png", + "caption": "\u041c\u0430\u0433\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u041a\u0440\u0438\u0441\u0442\u0430\u043b\u043b\u044b\n", + "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": "Ma\u0433\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0416\u0435\u0437\u043b\n", + "u": 0, + "v": 0, + "width": 64, + "height": 64, + "border": false + }, + "crystal_flower": { + "location": "ebwizardry:textures/gui/crystal_flower.png", + "caption": "\u041a\u0440\u0438\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0439 \u0426\u0432\u0435\u0442\u043e\u043a\n", + "u": 0, + "v": 0, + "width": 48, + "height": 48 + }, + "wizard_tower": { + "location": "ebwizardry:textures/gui/wizard_tower.png", + "caption": "\u0411\u0430\u0448\u043d\u044f Ma\u0433\u043e\u0432\n", + "u": 0, + "v": 0, + "width": 110, + "height": 110 + }, + "wizard_armour": { + "location": "ebwizardry:textures/gui/wizard_armour.png", + "caption": "\u041e\u0434\u0435\u044f\u043d\u0438\u044f \u0412\u043e\u043b\u0448\u0435\u0431\u043d\u0438\u043a\u043e\u0432\n", + "u": 0, + "v": 0, + "width": 90, + "height": 90 + }, + "obelisk": { + "location": "ebwizardry:textures/gui/obelisk.png", + "caption": "\u041e\u0431\u0435\u043b\u0438\u0441\u043a\n", + "u": 0, + "v": 0, + "width": 110, + "height": 67 + }, + "shrine": { + "location": "ebwizardry:textures/gui/shrine.png", + "caption": "\u0417\u0435\u043c\u043d\u043e\u0435 \u0421\u0432\u044f\u0442\u0438\u043b\u0438\u0449\u0435\n", + "u": 0, + "v": 0, + "width": 110, + "height": 67 + }, + "tiers": { + "location": "ebwizardry:textures/gui/tiers.png", + "caption": "4 \u0423\u0440\u043e\u0432\u043d\u044f \u0416\u0435\u0437\u043b\u0430\n", + "u": 0, + "v": 0, + "width": 96, + "height": 24, + "border": false + }, + "elements": { + "location": "ebwizardry:textures/gui/elements.png", + "caption": "7 \u0422\u0430\u0439\u043d\u044b\u0445 \u042d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432\n", + "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": [ + + "\u041a\u043d\u0438\u0433\u0430 \u0412\u043e\u043b\u0448\u0435\u0431\u043d\u0438\u043a\u0430", + + "\u043e\u0442 Electroblob" + ] + }, + + "introduction": { + "title": "\u0412\u0432\u0435\u0434\u0435\u043d\u0438\u0435", + "text": [ + + "\u041f\u0440\u0438\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e, \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u0438\u043a! \u042d\u0442\u0430 \u043a\u043d\u0438\u0433\u0430 \u043e\u0431\u044a\u044f\u0441\u043d\u044f\u0435\u0442 \u043c\u043d\u043e\u0433\u0438\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u044b \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043c\u0430\u0433\u0438\u0438 \u0438 \u0441\u043f\u043e\u0441\u043e\u0431\u044b \u0435\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f. \u041e\u0434\u043d\u0430\u043a\u043e \u044d\u0442\u043e \u043d\u0435 \u043e\u0431\u044b\u0447\u043d\u0430\u044f \u043a\u043d\u0438\u0433\u0430 - \u0435\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0443\u044e\u0442\u0441\u044f \u043f\u0435\u0440\u0435\u0434 \u0432\u0430\u043c\u0438, \u043a\u043e\u0433\u0434\u0430 \u0432\u044b \u0443\u0437\u043d\u0430\u0435\u0442\u0435 \u0431\u043e\u043b\u044c\u0448\u0435 \u043e \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u043e\u043c \u043c\u0438\u0440\u0435.", + + "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0438 \u0441\u043e \u0441\u0442\u0440\u0435\u043b\u043a\u0430\u043c\u0438, \u0447\u0442\u043e\u0431\u044b \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0442\u044c\u0441\u044f \u043c\u0435\u0436\u0434\u0443 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u043c\u0438, \u0438 \u043a\u043d\u043e\u043f\u043a\u0438 \u0441 \u0434\u0432\u043e\u0439\u043d\u044b\u043c\u0438 \u0441\u0442\u0440\u0435\u043b\u043a\u0430\u043c\u0438, \u0447\u0442\u043e\u0431\u044b \u0431\u044b\u0441\u0442\u0440\u043e \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0442\u044c\u0441\u044f \u043c\u0435\u0436\u0434\u0443 \u0440\u0430\u0437\u0434\u0435\u043b\u0430\u043c\u0438. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0446\u0435\u043d\u0442\u0440\u0430\u043b\u044c\u043d\u0443\u044e \u043a\u043d\u043e\u043f\u043a\u0443 \u043c\u0435\u043d\u044e, \u0447\u0442\u043e\u0431\u044b \u0432\u0435\u0440\u043d\u0443\u0442\u044c\u0441\u044f \u043d\u0430 \u0433\u043b\u0430\u0432\u043d\u0443\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0433\u043e", + + "\u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u043d\u0430 \u043b\u044e\u0431\u043e\u0439 \u0444\u0438\u043e\u043b\u0435\u0442\u043e\u0432\u044b\u0439 \u0442\u0435\u043a\u0441\u0442-\u0441\u0441\u044b\u043b\u043a\u0443, \u0447\u0442\u043e\u0431\u044b \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043f\u0440\u044f\u043c\u043e \u043d\u0430 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443. \u0429\u0435\u043b\u043a\u043d\u0438\u0442\u0435 \u0437\u0430\u043a\u043b\u0430\u0434\u043a\u0443 \u041f\u0440\u0430\u0432\u043e\u0439 \u041a\u043d\u043e\u043f\u043a\u043e\u0439 \u041c\u044b\u0448\u0438, \u0447\u0442\u043e\u0431\u044b \u043f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0435\u0435 \u043d\u0430 \u0442\u0435\u043a\u0443\u0449\u0443\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443, \u0438 \u0449\u0435\u043b\u043a\u043d\u0438\u0442\u0435 \u043b\u0435\u0432\u043e\u0439 \u043a\u043d\u043e\u043f\u043a\u043e\u0439 \u043c\u044b\u0448\u0438, \u0447\u0442\u043e\u0431\u044b \u0432\u0435\u0440\u043d\u0443\u0442\u044c\u0441\u044f \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u0441 \u0437\u0430\u043a\u043b\u0430\u0434\u043a\u0430\u043c\u0438." + ] + }, + + "main_contents": { + "title": "\u0421\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435", + "contents": { + "id": "main_contents", + "hyperlinks": true, + "page_numbers": true, + "separator": "." + } + }, + + "setting_up": { + "title": "\u041d\u0430\u0447\u0430\u043b\u043e", + "include_in_contents": "main_contents", + "text": [ + + "\u0427\u0442\u043e\u0431\u044b \u043d\u0430\u0447\u0430\u0442\u044c \u0440\u0430\u0431\u043e\u0442\u0443 \u0441 \u043c\u0430\u0433\u0438\u0435\u0439, \u0432\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u044f\u0442\u0441\u044f \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0432\u0435\u0449\u0435\u0439:", + + "- \u0412\u043e\u043b\u0448\u0435\u0431\u043d\u044b\u0439 \u0436\u0435\u0437\u043b, \u0441\u0434\u0435\u043b\u0430\u043d\u043d\u044b\u0439 \u0438\u0437 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u043e\u0432:", + + "#recipe magic_wand", + + "- @arcane_workbench \u0427\u0430\u0440\u043e\u0434\u0435\u0439\u0441\u043a\u0438\u0439 \u0441\u0442\u043e\u043b@, \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u0439 \u0438\u0437:", + + "#recipe arcane_workbench", + + "- \u041a\u043d\u0438\u0433\u0430 @spells \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439@ . \u041e\u043d\u0438 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0432 \u0441\u0443\u043d\u0434\u0443\u043a\u0430\u0445, \u0432\u044b\u0440\u0432\u0430\u043d\u044b \u0438\u0437 \u0440\u0443\u043a \u043c\u0435\u0440\u0442\u0432\u0435\u0446\u043e\u0432, \u043a\u0443\u043f\u043b\u0435\u043d\u044b \u0443 \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u0438\u043a\u043e\u0432, \u0438\u043b\u0438 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0435 \u0434\u043b\u044f \u043d\u043e\u0432\u0438\u0447\u043a\u0430, \u043c\u0430\u0433\u0438\u0447\u0435\u0441\u043a\u0443\u044e \u0440\u0430\u043a\u0435\u0442\u0443 \u043a\u0430\u043a \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u043e \u0442\u0443\u0442:", + + "#recipe magic_missile_spell_book", + + "\u0412\u0430\u043c \u0442\u0430\u043a\u0436\u0435 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u043a\u0443\u0447\u0430 \u043c\u0430\u0433\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u043a\u0440\u0438\u0441\u0442\u0430\u043b\u043b\u043e\u0432, \u0447\u0442\u043e\u0431\u044b \u0441\u043d\u0430\u0431\u0436\u0430\u0442\u044c \u0432\u0430\u0448 \u0436\u0435\u0437\u043b @mana \u043c\u0430\u043d\u043e\u0439@." + ] + }, + + "mana": { + "title": "\u041c\u0430\u043d\u0430", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:crystal" + ], + "text": [ + + "\u041c\u0430\u043d\u0430 - \u044d\u0442\u043e \u0442\u0430\u0439\u043d\u0430\u044f \u044d\u043d\u0435\u0440\u0433\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0434\u0430\u0435\u0442 \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u0438\u043a\u0430\u043c \u0438\u0445 \u0441\u0438\u043b\u044b. \u041e\u043d\u0430 \u043f\u0440\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432 \u0432\u0441\u0442\u0440\u0435\u0447\u0430\u044e\u0449\u0438\u0445\u0441\u044f \u0432 \u043f\u0440\u0438\u0440\u043e\u0434\u0435 \u043a\u0440\u0438\u0441\u0442\u0430\u043b\u043b\u0430\u0445, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0436\u043d\u043e \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0442\u044c \u0432 \u043f\u043e\u0434\u0437\u0435\u043c\u043d\u044b\u0445 \u043f\u043e\u0440\u043e\u0434\u0430\u0445, \u0438\u043b\u0438 \u0432 \u0432\u0438\u0434\u0435 \u043a\u0440\u0438\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0445 \u0446\u0432\u0435\u0442\u043a\u043e\u0432 \u0440\u0430\u0441\u0442\u0443\u0449\u0438\u0445 \u043f\u043e \u0432\u0441\u0435\u043c\u0443 \u043c\u0438\u0440\u0443. @wands \u0416\u0435\u0437\u043b\u044b@ \u043c\u043e\u0433\u0443\u0442 \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043c\u0430\u043d\u0443, \u0438 \u044d\u0442\u0430 \u043c\u0430\u043d\u0430 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432 @spells \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f@ \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0435. \u0420\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f \u0442\u0440\u0435\u0431\u0443\u044e\u0442 \u0440\u0430\u0437\u043d\u043e\u0433\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u043c\u0430\u043d\u044b, \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0442\u043e\u0433\u043e, \u043d\u0430\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043e\u043d\u0438 \u0441\u0438\u043b\u044c\u043d\u044b \u0438 \u0434\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u044b.", + + "#image crystal_ore", + + "#image magic_crystal", + + "\u0428\u0438\u0440\u043e\u043a\u043e \u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0435\u043d\u043e \u043c\u043d\u0435\u043d\u0438\u0435, \u0447\u0442\u043e \u043c\u0430\u043d\u0430 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0441\u043e\u0437\u0434\u0430\u043d\u0430 \u0438\u043b\u0438 \u0443\u043d\u0438\u0447\u0442\u043e\u0436\u0435\u043d\u0430, \u0438 \u0447\u0442\u043e \u043a\u043e\u0433\u0434\u0430 \u043f\u0440\u043e\u0438\u0437\u043d\u043e\u0441\u0438\u0442\u0441\u044f \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0435, \u0442\u043e \u043c\u0430\u043d\u0430, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043e\u043d\u043e \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442, \u043f\u0440\u043e\u0441\u0442\u043e \u0440\u0430\u0441\u0441\u0435\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0432 \u043e\u043a\u0440\u0443\u0436\u0430\u044e\u0449\u0435\u0439 \u0441\u0440\u0435\u0434\u0435. \u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e, \u0438\u043c\u0435\u043d\u043d\u043e \u0442\u0430\u043a \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u043f\u043e\u0434\u0430\u0432\u043b\u044f\u044e\u0449\u0435\u0435 \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u043e \u043c\u0430\u043d\u044b - \u0442\u043e\u043d\u043a\u043e \u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0439 \u043f\u043e \u0432\u0441\u0435\u043c\u0443 \u043c\u0438\u0440\u0443. \u041e\u0434\u043d\u0430\u043a\u043e, \u0447\u0442\u043e\u0431\u044b \u0431\u044b\u0442\u044c \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u043c, \u043e\u043d\u0430 \u0434\u043e\u043b\u0436\u043d\u0430 \u0431\u044b\u0442\u044c \u0441\u043a\u043e\u043d\u0446\u0435\u043d\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0430, \u043b\u0438\u0431\u043e \u0435\u0441\u0442\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u0432 \u0442\u0435\u0447\u0435\u043d\u0438\u0435 \u0442\u044b\u0441\u044f\u0447 \u043b\u0435\u0442, \u043a\u0430\u043a \u044d\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0441 \u043a\u0440\u0438\u0441\u0442\u0430\u043b\u043b\u0430\u043c\u0438 \u043f\u043e\u0434 \u0437\u0435\u043c\u043b\u0435\u0439, \u043b\u0438\u0431\u043e \u0438\u0441\u043a\u0443\u0441\u0441\u0442\u0432\u0435\u043d\u043d\u043e - \u043f\u0440\u043e\u0434\u0432\u0438\u043d\u0443\u0442\u0430\u044f \u0442\u0435\u043c\u0430, \u043d\u0435 \u0440\u0430\u0441\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u0435\u043c\u0430\u044f \u0432 \u044d\u0442\u043e\u0439 \u043a\u043d\u0438\u0433\u0435. \u0421\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0442 \u0442\u0430\u043a\u0436\u0435 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u044b \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f \u0431\u043e\u043b\u0435\u0435 \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u044b\u043c\u0438 \u0441 \u0442\u043e\u0447\u043a\u0438 \u0437\u0440\u0435\u043d\u0438\u044f \u043c\u0430\u043d\u044b \u0438 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0447\u0430\u0441\u0442\u044c \u043c\u0430\u043d\u044b, \u0440\u0430\u0441\u0441\u0435\u044f\u043d\u043d\u043e\u0439 \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f." + ] + }, + + "wands": { + "title": "\u0416\u0435\u0437\u043b\u044b", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:arcane_initiate" + ], + "text": [ + + "\u0416\u0435\u0437\u043b - \u044d\u0442\u043e \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u0434\u043b\u044f \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u0438\u043a\u0430. \u0421 \u0435\u0433\u043e \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043b\u044e\u0431\u043e\u0435 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0435, \u0435\u0441\u043b\u0438 \u0436\u0435\u0437\u043b \u043c\u043e\u0436\u0435\u0442 \u0441\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0435\u0433\u043e \u0441\u0438\u043b\u0443 (\u0441\u043c. \u0420\u0430\u0437\u0434\u0435\u043b\n @tiers \n\"\u0423\u0440\u043e\u0432\u043d\u0438\"\n@ \u0434\u043b\u044f \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0433\u043e \u043e\u0431\u044a\u044f\u0441\u043d\u0435\u043d\u0438\u044f). \u0416\u0435\u0437\u043b\u043e\u0432 \n\u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u044b, \u043d\u043e \u0432\u044b \u043f\u043e\u0447\u0442\u0438 \u043d\u0430\u0432\u0435\u0440\u043d\u044f\u043a\u0430 \u043d\u0430\u0447\u043d\u0435\u0442\u0435 \u0441 \u0431\u0430\u0437\u043e\u0432\u043e\u0433\u043e \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u043e\u0433\u043e \u0436\u0435\u0437\u043b\u0430.\n", + + "#image magic_wand", + + "\u0411\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u043e \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u044b\u0445 \u0436\u0435\u0437\u043b\u043e\u0432 \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0442 \u0441\u0432\u043e\u044e \u0436\u0438\u0437\u043d\u044c \u043a\u0430\u043a \u043f\u0440\u043e\u0441\u0442\u043e\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0438\u0437 \u043c\u0430\u0433\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u043a\u0440\u0438\u0441\u0442\u0430\u043b\u043b\u0430 \u043f\u0440\u0438\u043a\u0440\u0435\u043f\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u043a \u0434\u0435\u0440\u0435\u0432\u044f\u043d\u043d\u043e\u0439 \u043f\u0430\u043b\u043a\u0435, \u0441 \u0437\u043e\u043b\u043e\u0442\u044b\u043c \u0441\u0430\u043c\u043e\u0440\u043e\u0434\u043a\u043e\u043c \u043f\u0440\u0438\u043a\u0440\u0435\u043f\u043b\u0435\u043d\u043d\u044b\u043c \u043a \u0434\u0440\u0443\u0433\u043e\u043c\u0443 \u043a\u043e\u043d\u0446\u0443. \u041a\u0440\u0438\u0441\u0442\u0430\u043b\u043b, \u043a\u043e\u043d\u0435\u0447\u043d\u043e, \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u043c \u0441\u0438\u043b\u044b \u0436\u0435\u0437\u043b\u0430, \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u043e\u043d \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u0435\u0442 \u0444\u043e\u043a\u0443\u0441\u0438\u0440\u043e\u0432\u043a\u0443, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0443\u044e \u0434\u043b\u044f \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f @spells \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439@. \u041e\u0441\u0442\u0430\u043b\u044c\u043d\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u0436\u0435\u0437\u043b\u0430 \u0442\u043e\u0436\u0435 \u0432\u0430\u0436\u043d\u0430, \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u043e\u043d\u0430 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u0435\u0442 \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u043e, \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043c\u043e\u0436\u043d\u043e \u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0436\u0435\u0437\u043b, \u043d\u043e \u0438 \u043f\u0443\u0442\u044c, \u043f\u043e \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 @mana \u043c\u0430\u043d\u0430@ \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0430.\n", + + "\u0427\u0435\u043c \u0431\u043e\u043b\u044c\u0448\u0435 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0436\u0435\u0437\u043b\u0430, \u0442\u0435\u043c \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u0435\u0435 \u043e\u043d \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 @spells \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f@ \u0438, \u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e, \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u043e\u0438\u0437\u043d\u043e\u0441\u0438\u0442\u044c \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f \u0431\u043e\u043b\u044c\u0448\u0435\u0439 \u0441\u0438\u043b\u044b. \u042d\u0442\u043e\u0442 \u044d\u0444\u0444\u0435\u043a\u0442 \u043c\u043e\u0436\u043d\u043e \u0440\u0430\u0437\u043b\u0438\u0447\u0438\u0442\u044c \u043f\u043e \u0442\u043e\u043d\u043a\u0438\u043c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f\u043c, \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u043c \u0432 \u0444\u043e\u0440\u043c\u0435 \u0438 \u0432\u043d\u0435\u0448\u043d\u0435\u043c \u0432\u0438\u0434\u0435 \u0441\u0430\u043c\u043e\u0433\u043e \u0436\u0435\u0437\u043b\u0430: \u043f\u043e \u043c\u0435\u0440\u0435 \u0442\u043e\u0433\u043e, \u043a\u0430\u043a \u0436\u0435\u0437\u043b \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u0431\u043e\u043b\u0435\u0435 \u043c\u043e\u0449\u043d\u044b\u043c, \u0435\u0433\u043e \u041a\u0440\u0438\u0441\u0442\u0430\u043b\u043b \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u0431\u043e\u043b\u0435\u0435 \u044f\u0440\u043a\u0438\u043c, \u0435\u0433\u043e \u0446\u0432\u0435\u0442 \u043c\u043e\u0436\u0435\u0442 \u043c\u0435\u043d\u044f\u0442\u044c\u0441\u044f \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0435\u0433\u043e @elements \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430@, \u0438 \u0447\u0435\u0440\u0435\u0437 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u0434\u0435\u0440\u0435\u0432\u043e, \u0438\u0437 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043e\u043d \u0441\u0434\u0435\u043b\u0430\u043d, \u043d\u0430\u0447\u043d\u0435\u0442 \u0440\u0430\u0441\u0442\u0438 \u0438 \u0441\u043a\u0440\u0443\u0447\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u0432 \u0431\u043e\u043b\u0435\u0435 \u0441\u043b\u043e\u0436\u043d\u044b\u0435 \u0444\u043e\u0440\u043c\u044b.\n", + + "\u041a\u043e\u0433\u0434\u0430 \u0432\u044b \u0434\u0435\u0440\u0436\u0438\u0442\u0435 \u0436\u0435\u0437\u043b, \u0432 \u0443\u0433\u043b\u0443 \u044d\u043a\u0440\u0430\u043d\u0430 \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0430\u044f \u043e\u0431\u043b\u0430\u0441\u0442\u044c \u0441 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u043c. \u042d\u0442\u043e \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043d\u0430 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0435 \u0432 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0438\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0435 \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u0435\u0433\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u043c, \u0438, \u0435\u0441\u043b\u0438 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0435 \u0431\u044b\u043b\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u043d\u043e, \u0442\u043e \u043f\u043e\u044f\u0432\u0438\u0442\u0441\u044f \u043f\u043e\u043b\u043e\u0441\u0430 \u043f\u0435\u0440\u0435\u0437\u0430\u0440\u044f\u0434\u043a\u0438, \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u044e\u0449\u0430\u044f \u0432\u0440\u0435\u043c\u044f \u043f\u043e\u043a\u0430 \u044d\u0442\u043e \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0435 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u043e \u0441\u043d\u043e\u0432\u0430. \u041e\u043d\u0430 \u0442\u0430\u043a\u0436\u0435 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0438 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0445 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441 \u0436\u0435\u0437\u043b\u043e\u043c. \u0414\u043b\u044f \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043c\u0435\u0436\u0434\u0443 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f\u043c\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043a\u043b\u0430\u0432\u0438\u0448\u0438 #next_spell_key \u0438 #previous_spell_key (\u0438\u0445 \u043c\u043e\u0436\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 options -> controls). \u0412\u044b \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f, \u043f\u0440\u043e\u043a\u0440\u0443\u0447\u0438\u0432\u0430\u044f \u043a\u043e\u043b\u0435\u0441\u0438\u043a\u043e \u043c\u044b\u0448\u0438 \u043a\u043e\u0433\u0434\u0430 \u043a\u0440\u0430\u0434\u0435\u0442\u0435\u0441\u044c.\n", + + "\u041f\u0440\u0438 \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435 \u0438\u043d\u0432\u0435\u043d\u0442\u0430\u0440\u044f \u043d\u0430\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u043a\u0443\u0440\u0441\u043e\u0440\u0430 \u043d\u0430 \u0436\u0435\u0437\u043b \u043f\u043e\u043a\u0430\u0436\u0435\u0442, \u0441\u043a\u043e\u043b\u044c\u043a\u043e @mana \u043c\u0430\u043d\u044b@ \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u0432 \u043d\u0435\u043c, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0435\u0433\u043e \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0435 @spells \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0435@ \u0438 \u043b\u044e\u0431\u044b\u0435 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u043e\u043d \u043c\u043e\u0436\u0435\u0442 \u043e\u0431\u043b\u0430\u0434\u0430\u0442\u044c. \u0411\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u0436\u0435\u0437\u043b\u0435 \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c, \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u0432 \u0435\u0433\u043e \u0432 @arcane_workbench \u0447\u0430\u0440\u043e\u0434\u0435\u0439\u0441\u043a\u0438\u0439 \u0441\u0442\u043e\u043b@.\n" + ] + }, + + "spells": { + "title": "\u0417\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:handbook/spells" + ], + "text": [ + + "@wands \u0416\u0435\u0437\u043b@ \u0431\u0435\u0441\u043f\u043e\u043b\u0435\u0437\u0435\u043d \u0431\u0435\u0437 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439. \u0417\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0432 \u0434\u0432\u0443\u0445 \u0444\u043e\u0440\u043c\u0430\u0445: \u0432 \u043a\u043d\u0438\u0433\u0430\u0445, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u043c\u043d\u043e\u0433\u043e\u0440\u0430\u0437\u043e\u0432\u044b\u043c\u0438, \u0438 \u0432 \u0441\u0432\u0438\u0442\u043a\u0430\u0445, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u043e\u0434\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u044b\u043c\u0438.\n", + + "\u041a\u043d\u0438\u0433\u0438 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439 \u043c\u0430\u043b\u043e \u043d\u0443\u0436\u043d\u044b \u0441\u0430\u043c\u0438 \u043f\u043e \u0441\u0435\u0431\u0435; \u0412\u043c\u0435\u0441\u0442\u043e \u044d\u0442\u043e\u0433\u043e \u043e\u043d\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u0434\u043b\u044f \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0438 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043e\u043d\u0438 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442 \u043a @wands \u0436\u0435\u0437\u043b\u0430\u043c@. \u041a\u043d\u0438\u0433\u0438 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439 \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 \u043f\u043e \u0432\u0441\u0435\u043c\u0443 \u043c\u0438\u0440\u0443, \u0438 \u0432\u0430\u043c \u043f\u0440\u0438\u0434\u0435\u0442\u0441\u044f \u0434\u043e\u043b\u0433\u043e \u0438\u0441\u043a\u0430\u0442\u044c \u0438\u0445. \u041a\u043e\u0433\u0434\u0430 \u0432\u044b \u0435\u0451 \u043d\u0430\u0439\u0434\u0435\u0442\u0435, \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043d\u0430\u0432\u0435\u0441\u0442\u0438 \u043a\u0443\u0440\u0441\u043e\u0440 \u043c\u044b\u0448\u0438 \u043d\u0430 \u043a\u043d\u0438\u0433\u0443 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439, \u0447\u0442\u043e\u0431\u044b \u0441\u0440\u0430\u0437\u0443 \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u043e\u0441\u043d\u043e\u0432\u043d\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0438. \u0412\u044b \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0449\u0435\u043b\u043a\u043d\u0443\u0442\u044c \u043f\u0440\u0430\u0432\u043e\u0439 \u043a\u043d\u043e\u043f\u043a\u043e\u0439 \u043c\u044b\u0448\u0438, \u0443\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044f \u043a\u043d\u0438\u0433\u0443 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0438.\n", + + "\u0421 \u0434\u0440\u0443\u0433\u043e\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u044b, \u0441\u0432\u0438\u0442\u043a\u0438 \u0441\u043b\u0443\u0436\u0430\u0442 \u0434\u043b\u044f \u0434\u0440\u0443\u0433\u043e\u0439 \u0446\u0435\u043b\u0438: \u0449\u0435\u043b\u0447\u043e\u043a \u043f\u0440\u0430\u0432\u043e\u0439 \u043a\u043d\u043e\u043f\u043a\u043e\u0439 \u043c\u044b\u0448\u0438 \u043f\u0440\u0438 \u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0438 \u0435\u0433\u043e \u0432 \u0440\u0443\u043a\u0430\u0445 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0435, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u043e\u0435 \u0441 \u043d\u0438\u043c, \u0443\u043d\u0438\u0447\u0442\u043e\u0436\u0430\u0435\u0442 \u0441\u0432\u0438\u0442\u043e\u043a \u0432 \u044d\u0442\u043e\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435. \u041a\u0430\u043a \u0438 \u043a\u043d\u0438\u0433\u0438 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439, \u0441\u0432\u0438\u0442\u043a\u0438 \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 \u043f\u043e \u0432\u0441\u0435\u043c\u0443 \u043c\u0438\u0440\u0443, \u043d\u043e \u0432\u044b \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0438\u0445 \u0441\u0430\u043c\u0438, \u0441\u043e\u0437\u0434\u0430\u0432 \u043f\u0443\u0441\u0442\u043e\u0439 \u0441\u0432\u0438\u0442\u043e\u043a \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043b\u0438\u0441\u0442\u0430 \u0431\u0443\u043c\u0430\u0433\u0438 \u0438 \u043d\u0438\u0442\u0438 (\u0441\u043c. @enchanting_scrolls \u0417\u0430\u0447\u0430\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0421\u0432\u0438\u0442\u043a\u0438@. \u0417\u0430\u0442\u0435\u043c \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u0442\u044c \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0435 \u043a \u0441\u0432\u0438\u0442\u043a\u0443 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0447\u0430\u0440\u043e\u0434\u0435\u0439\u0441\u043a\u043e\u0433\u043e \u0441\u0442\u043e\u043b\u0430 \u043e\u0431\u044b\u0447\u043d\u044b\u043c \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u043c. \u042d\u0442\u043e \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u0442 \u043a \u0442\u043e\u043c\u0443, \u0447\u0442\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043c\u0430\u0433\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u043a\u0440\u0438\u0441\u0442\u0430\u043b\u043b\u043e\u0432 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0432\u043d\u043e \u043c\u0430\u043d\u0430-\u0441\u0442\u043e\u0438\u043c\u043e\u0441\u0442\u0438 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f.", + + "\u0421\u0432\u0438\u0442\u043a\u0438 \u043e\u0447\u0435\u043d\u044c \u043f\u043e\u043b\u0435\u0437\u043d\u044b, \u043a\u043e\u0433\u0434\u0430 \u0432\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0435 \u043e\u0434\u0438\u043d \u0438\u043b\u0438 \u0434\u0432\u0430 \u0440\u0430\u0437\u0430 \u0432 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u0434\u0430\u0447\u0435, \u043d\u043e \u0432\u044b \u043d\u0435 \u0445\u043e\u0442\u0438\u0442\u0435, \u0447\u0442\u043e\u0431\u044b \u043e\u043d\u043e \u0437\u0430\u043d\u0438\u043c\u0430\u043b\u043e \u0441\u043b\u043e\u0442 \u043d\u0430 \u0432\u0430\u0448\u0435\u043c @wands \u0436\u0435\u0437\u043b\u0435@. \u041e\u0434\u043d\u0430\u043a\u043e \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e \u043e\u043d\u0438 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u043d\u0435\u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u044b\u043c \u043c\u0435\u0442\u043e\u0434\u043e\u043c \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439 \u043f\u043e \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u044e \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043f\u0430\u043b\u043e\u0447\u043a\u0438.", + + "\u0414\u043b\u044f \u043d\u0435\u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u0433\u043b\u0430\u0437\u0430 \u043c\u0430\u0433\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0440\u0443\u043d\u044b, \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043f\u0438\u0448\u0443\u0442\u0441\u044f \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f - \u043d\u0435\u0447\u0438\u0442\u0430\u0435\u043c\u044b. \u0427\u0442\u043e\u0431\u044b \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u0446\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0435 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435, \u043a\u043e\u043d\u0435\u0447\u043d\u043e, \u0431\u0440\u043e\u0441\u0438\u0442\u044c \u0435\u0433\u043e \u0438 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0447\u0442\u043e \u043f\u0440\u043e\u0438\u0437\u043e\u0439\u0434\u0435\u0442 - \u0445\u043e\u0442\u044f \u044d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u0437\u0432\u0430\u0442\u044c \u043d\u0435\u0436\u0435\u043b\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043f\u043e\u0431\u043e\u0447\u043d\u044b\u0435 \u044d\u0444\u0444\u0435\u043a\u0442\u044b, \u0438 \u043c\u043d\u043e\u0433\u0438\u0435 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f \u043d\u0443\u0436\u0434\u0430\u044e\u0442\u0441\u044f \u0432 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0445 \u0443\u0441\u043b\u043e\u0432\u0438\u044f\u0445, \u0447\u0442\u043e\u0431\u044b \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c. \u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u0432\u0441\u0435\u0433\u0434\u0430 \u0435\u0441\u0442\u044c \u0448\u0430\u043d\u0441 \u043d\u0435\u0432\u0435\u0440\u043d\u043e \u0438\u0441\u0442\u043e\u043b\u043a\u043e\u0432\u0430\u0442\u044c \u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u043e\u0435 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0435 \u043f\u0440\u0438 \u043f\u0440\u043e\u0438\u0437\u043d\u0435\u0441\u0435\u043d\u0438\u0438, \u0441 \u043f\u043e\u0442\u0435\u043d\u0446\u0438\u0430\u043b\u044c\u043d\u043e \u043e\u043f\u0430\u0441\u043d\u044b\u043c\u0438 \u043f\u043e\u0441\u043b\u0435\u0434\u0441\u0442\u0432\u0438\u044f\u043c\u0438 \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0442\u043e\u0433\u043e, \u043d\u0430\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u043e\u0449\u043d\u044b\u043c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0435. \u0411\u043e\u043b\u0435\u0435 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u043c \u0438 \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u043c \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u043e\u043c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0441\u0432\u0438\u0442\u043a\u0430 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 \u0432 \u0441\u0443\u043d\u0434\u0443\u043a\u0430\u0445 \u0438\u043b\u0438 \u043f\u0440\u0438\u043e\u0431\u0440\u0435\u0441\u0442\u0438 \u0443 \u043c\u0430\u0441\u0442\u0435\u0440\u0430. \u0429\u0435\u043b\u0447\u043e\u043a \u043f\u0440\u0430\u0432\u043e\u0439 \u043a\u043d\u043e\u043f\u043a\u043e\u0439 \u043c\u044b\u0448\u0438, \u0443\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044f \u043e\u0434\u043d\u0443 \u0438\u0437 \u043d\u0438\u0445 \u0432 \u0440\u0443\u043a\u0430\u0445, \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u0446\u0438\u0440\u0443\u0435\u0442 \u043f\u0435\u0440\u0432\u0443\u044e \u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0443\u044e \u043a\u043d\u0438\u0433\u0443 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439 \u0438\u043b\u0438 \u0441\u0432\u0438\u0442\u043e\u043a \u043d\u0430 \u0432\u0430\u0448\u0435\u0439 \u043f\u0430\u043d\u0435\u043b\u0438 \u0431\u044b\u0441\u0442\u0440\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0443\u043f\u0430, \u043f\u043e\u0442\u0440\u0435\u0431\u043b\u044f\u044f \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u0441\u0432\u0438\u0442\u043e\u043a \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438." + ] + }, + + "arcane_workbench": { + "title": "\u0427\u0430\u0440\u043e\u0434\u0435\u0439\u0441\u043a\u0438\u0439 \u0421\u0442\u043e\u043b", + "include_in_contents": "main_contents", + "contents": { + "id": "arcane_workbench_subsections", + "hyperlinks": true, + "page_numbers": true, + "separator": "." + }, + "triggers": [ + "ebwizardry:handbook/arcane_workbench" + ], + "text": [ + + "\u0427\u0430\u0440\u043e\u0434\u0435\u0439\u0441\u043a\u0438\u0439 \u0421\u0442\u043e\u043b - \u044d\u0442\u043e \u0431\u043b\u043e\u043a, \u043f\u043e\u0445\u043e\u0436\u0438\u0439 \u043f\u043e \u0432\u043d\u0435\u0448\u043d\u0435\u043c\u0443 \u0432\u0438\u0434\u0443 \u043d\u0430 \u0441\u0442\u043e\u043b \u0437\u0430\u0447\u0430\u0440\u043e\u0432\u0430\u043d\u0438\u044f, \u0433\u0434\u0435 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0437\u0430\u0440\u044f\u0436\u0430\u0442\u044c, \u043c\u043e\u0434\u0435\u0440\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u0441\u0432\u044f\u0437\u044b\u0432\u0430\u0442\u044c @spells \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f@ \u0441 \u0432\u0430\u0448\u0438\u043c @wands \u0436\u0435\u0437\u043b\u043e\u043c@. \u0421\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u043e\u043d \u0442\u0430\u043a:", + + "#recipe arcane_workbench", + + "\u041d\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445 \u043e\u0431\u044a\u044f\u0441\u043d\u044f\u044e\u0442\u0441\u044f \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u044b \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0427\u0430\u0440\u043e\u0434\u0435\u0439\u0441\u043a\u043e\u0433\u043e \u0421\u0442\u043e\u043b\u0430", + + "#image workbench" + ], + "sections": { + "binding_spells": { + "title": "\u041f\u0440\u0438\u0432\u044f\u0437\u043a\u0430 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439", + "include_in_contents": "arcane_workbench_subsections", + "triggers": [ + "ebwizardry:handbook/arcane_workbench" + ], + "text": [ + + "\u0427\u0442\u043e\u0431\u044b \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u0442\u044c @spells \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0435@ \u043a \u0432\u0430\u0448\u0435\u043c\u0443 @wands \u0436\u0435\u0437\u043b\u0443@, \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u0442\u0435 \u043a\u043d\u0438\u0433\u0443 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439 \u0432 \u043e\u0434\u0438\u043d \u0438\u0437 \u043f\u044f\u0442\u0438 \u0441\u043b\u043e\u0442\u043e\u0432 \u0432\u043e\u043a\u0440\u0443\u0433 \u043a\u0440\u0430\u044f \u0440\u0430\u0431\u043e\u0447\u0435\u0433\u043e \u0441\u0442\u043e\u043b\u0430 \u0438 \u0436\u0435\u0437\u043b\u0430 \u0432 \u0446\u0435\u043d\u0442\u0440\u0430\u043b\u044c\u043d\u043e\u043c \u0441\u043b\u043e\u0442\u0435 \u0438 \u043d\u0430\u0436\u043c\u0438\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 \u041f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c. \u0412\u044b \u0437\u0430\u043c\u0435\u0442\u0438\u0442\u0435, \u0447\u0442\u043e \u043a\u043d\u0438\u0433\u0430 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439 \u043e\u0441\u0442\u0430\u0435\u0442\u0441\u044f \u043d\u0435\u0442\u0440\u043e\u043d\u0443\u0442\u043e\u0439 \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u044d\u0442\u043e\u0433\u043e \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 - \u044d\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043f\u043e\u0442\u043e\u043c\u0443, \u0447\u0442\u043e \u0430\u043a\u0442 \u0441\u0432\u044f\u0437\u044b\u0432\u0430\u043d\u0438\u044f \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439 \u043d\u0435 \"\u0431\u0435\u0440\u0435\u0442\" \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0435 \u0438\u0437 \u043a\u043d\u0438\u0433\u0438 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439; \u0441\u043a\u043e\u0440\u0435\u0435, \u043e\u043d \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u0442 \u0436\u0435\u0437\u043b \u043d\u0430 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0435. \u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0437\u043c\u0435\u043d\u044f\u0442\u044c \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f, \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0435 \u043a \u0432\u0430\u0448\u0435\u043c\u0443 \u0436\u0435\u0437\u043b\u0443, \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0437\u0430\u0445\u043e\u0442\u0438\u0442\u0435, \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u044f \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0441\u0432\u044f\u0437\u044b\u0432\u0430\u043d\u0438\u044f \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439. \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u0442\u044c \u0434\u043e \u043f\u044f\u0442\u0438 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439 \u043a \u0436\u0435\u0437\u043b\u0443 \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e,\u0445\u043e\u0442\u044f \u044d\u0442\u043e \u0447\u0438\u0441\u043b\u043e \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0443\u0432\u0435\u043b\u0438\u0447\u0435\u043d\u043e \u0441 \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u0435\u043c \u0436\u0435\u0437\u043b\u0430." + ] + }, + "charging_wands": { + "title": "\u0417\u0430\u0440\u044f\u0434\u043a\u0430 \u0436\u0435\u0437\u043b\u043e\u0432", + "include_in_contents": "arcane_workbench_subsections", + "triggers": [ + "ebwizardry:handbook/arcane_workbench" + ], + "text": [ + + "\u0427\u0442\u043e\u0431\u044b \u0437\u0430\u0440\u044f\u0434\u0438\u0442\u044c \u0441\u0432\u043e\u0439 @wands \u0436\u0435\u0437\u043b@, \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u0442\u0435 \u0436\u0435\u0437\u043b \u0432 \u0446\u0435\u043d\u0442\u0440\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u043b\u043e\u0442 \u0427\u0430\u0440\u043e\u0434\u0435\u0439\u0441\u043a\u043e\u0433\u043e \u0421\u0442\u043e\u043b\u0430 \u0438 \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u0442\u0435 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0430\u0433\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u043a\u0440\u0438\u0441\u0442\u0430\u043b\u043b\u043e\u0432 \u0432 \u043d\u0438\u0436\u043d\u0438\u0439 \u043b\u0435\u0432\u044b\u0439 \u0441\u043b\u043e\u0442, \u0430 \u0437\u0430\u0442\u0435\u043c \u043d\u0430\u0436\u043c\u0438\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 \u041f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c. \u041a\u0430\u0436\u0434\u044b\u0439 \u043a\u0440\u0438\u0441\u0442\u0430\u043b\u043b \u0441\u0442\u043e\u0438\u0442 #mana_per_crystal @mana \u043c\u0430\u043d\u044b@, \u0438 \u0436\u0435\u0437\u043b \u0431\u0443\u0434\u0435\u0442 \u0431\u0440\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u043e, \u0447\u0442\u043e \u0435\u043c\u0443 \u043d\u0443\u0436\u043d\u043e, \u0442\u0430\u043a \u0447\u0442\u043e \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0437\u0430\u043f\u0430\u0441 \u043a\u0440\u0438\u0441\u0442\u0430\u043b\u043b\u043e\u0432 \u043d\u0430 \u0441\u0442\u043e\u043b\u0435, \u043a\u043e\u0433\u0434\u0430 \u043e\u043d\u0438 \u0432\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u044f\u0442\u0441\u044f. \u041e\u0434\u043d\u0430\u043a\u043e \u0438\u043c\u0435\u0439\u0442\u0435 \u0432 \u0432\u0438\u0434\u0443, \u0447\u0442\u043e \u0436\u0435\u0437\u043b \u043c\u043e\u0436\u0435\u0442 \u0432\u0437\u044f\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0446\u0435\u043b\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043a\u0440\u0438\u0441\u0442\u0430\u043b\u043b\u043e\u0432, \u043f\u043e\u044d\u0442\u043e\u043c\u0443, \u0435\u0441\u043b\u0438 \u0436\u0435\u0437\u043b\u0443 \u043d\u0443\u0436\u043d\u043e, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0435\u0449\u0435 30 \u043c\u0430\u043d\u044b, #example_charging_loss \u041c\u0430\u043d\u0430 \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0442\u0435\u0440\u044f\u043d\u0430 \u043f\u0440\u0438 \u0435\u0433\u043e \u0437\u0430\u0440\u044f\u0434\u043a\u0435." + ] + }, + "upgrading_wands": { + "title": "\u0423\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u0435 \u0416\u0435\u0437\u043b\u0430", + "include_in_contents": "arcane_workbench_subsections", + "triggers": [ + "ebwizardry:handbook/tome_of_arcana" + ], + "text": [ + + "\u0427\u0442\u043e\u0431\u044b \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u0432\u043e\u0439 @wands \u0436\u0435\u0437\u043b@, \u0432\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0444\u043e\u043b\u0438\u0430\u043d\u0442 \u0410\u0440\u043a\u0430\u043d\u044b \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0433\u043e @tiers \u0443\u0440\u043e\u0432\u043d\u044f@ \u0438\u043b\u0438 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0435 \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u0435 \u0436\u0435\u0437\u043b\u0430. \u041e\u0431\u0430 \u043e\u043d\u0438 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u043a\u0430\u043a \u0434\u043e\u0431\u044b\u0447\u0430 \u0438\u043b\u0438 \u043f\u0440\u0438\u043e\u0431\u0440\u0435\u0442\u0435\u043d\u044b \u0443 \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u0438\u043a\u0430. \u0427\u0442\u043e\u0431\u044b \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0436\u0435\u0437\u043b, \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u0442\u0435 \u0435\u0433\u043e \u0432 \u0446\u0435\u043d\u0442\u0440\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u043b\u043e\u0442, \u0430 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 - \u0432 \u0432\u0435\u0440\u0445\u043d\u0438\u0439 \u043f\u0440\u0430\u0432\u044b\u0439 \u0441\u043b\u043e\u0442, \u0437\u0430\u0442\u0435\u043c \u043d\u0430\u0436\u043c\u0438\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 \"\u041f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c\".", + + "\u0421\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0435 \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u044f \u0436\u0435\u0437\u043b\u0430 \u0443\u043b\u0443\u0447\u0448\u0430\u044e\u0442 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0439 \u0430\u0441\u043f\u0435\u043a\u0442 \u0436\u0435\u0437\u043b\u0430. \u041a\u0430\u0436\u0434\u044b\u0439 \u0442\u0438\u043f \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u044f \u043c\u043e\u0436\u0435\u0442 \u0441\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u0434\u043e \u0442\u0440\u0435\u0445 \u0440\u0430\u0437, \u0438 \u043e\u0431\u0449\u0435\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0436\u0435\u0442 \u0432\u0437\u044f\u0442\u044c \u0436\u0435\u0437\u043b, \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0435\u0433\u043e \u0443\u0440\u043e\u0432\u043d\u044f. \u041d\u0435 \u0443\u0434\u0430\u0435\u0442\u0441\u044f \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u044f." + ] + }, + "enchanting_scrolls": { + "title": "\u0417\u0430\u043a\u043b\u0438\u043d\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0421\u0432\u0438\u0442\u043a\u0438", + "include_in_contents": "arcane_workbench_subsections", + "triggers": [ + "ebwizardry:handbook/scrolls" + ], + "text": [ + + "\u0427\u0430\u0440\u043e\u0434\u0435\u0439\u0441\u043a\u0438\u0439 \u0421\u0442\u043e\u043b \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d, \u0447\u0442\u043e\u0431\u044b \u0437\u0430\u0447\u0430\u0440\u043e\u0432\u0430\u0442\u044c @spells \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f@ \u0432 \u0441\u0432\u0438\u0442\u043a\u0438. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432\u0430\u043c \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0447\u0438\u0441\u0442\u044b\u0439 \u0441\u0432\u0438\u0442\u043e\u043a, \u0441\u0434\u0435\u043b\u0430\u043d\u043d\u044b\u0439 \u0438\u0437 \u043b\u0438\u0441\u0442\u0430 \u0431\u0443\u043c\u0430\u0433\u0438 \u0438 \u043d\u0438\u0442\u0438, \u043a\u043d\u0438\u0433\u0430 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439 \u0434\u043b\u044f \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0433\u043e \u0432\u0430\u043c\u0438 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f \u0438 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043c\u0430\u0433\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u043a\u0440\u0438\u0441\u0442\u0430\u043b\u043b\u043e\u0432, \u0447\u0442\u043e\u0431\u044b \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0442\u044c @mana \u043c\u0430\u043d\u0443@ \u0434\u043b\u044f \u0435\u0433\u043e \u043f\u0440\u043e\u0438\u0437\u043d\u0435\u0441\u0435\u043d\u0438\u044f.", + + "#recipe blank_scroll", + + "\u041f\u043e\u043c\u0435\u0441\u0442\u0438\u0442\u0435 \u043a\u0440\u0438\u0441\u0442\u0430\u043b\u043b\u044b \u0432 \u043d\u0438\u0436\u043d\u0438\u0439 \u043b\u0435\u0432\u044b\u0439 \u0441\u043b\u043e\u0442, \u043f\u0443\u0441\u0442\u043e\u0439 \u0441\u0432\u0438\u0442\u043e\u043a \u0432 \u0446\u0435\u043d\u0442\u0440\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u043b\u043e\u0442 \u0438 \u043a\u043d\u0438\u0433\u0443 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439 \u0432 \u043e\u0434\u0438\u043d \u0441\u043b\u043e\u0442 \u043d\u0430\u0434 \u043d\u0438\u043c, \u0437\u0430\u0442\u0435\u043c \u043d\u0430\u0436\u043c\u0438\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 \"\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c\", \u0447\u0442\u043e\u0431\u044b \u0437\u0430\u0447\u0430\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0432\u0438\u0442\u043e\u043a." + ] + } + } + }, + + "wizard_armour": { + "title": "\u041e\u0434\u0435\u044f\u043d\u0438\u0435 \u0412\u043e\u043b\u0448\u0435\u0431\u043d\u0438\u043a\u0430", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:arcane_initiate" + ], + "text": [ + + "\u041a\u0430\u043a \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u0438\u043a\u0443, \u0432\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0447\u0442\u043e-\u0442\u043e, \u0447\u0442\u043e\u0431\u044b \u0437\u0430\u0449\u0438\u0442\u0438\u0442\u044c \u0432\u0430\u0441 \u043e\u0442 \u0441\u0443\u0449\u0435\u0441\u0442\u0432, \u0441 \u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u0432\u044b \u0441\u0440\u0430\u0436\u0430\u0435\u0442\u0435\u0441\u044c. \u041e\u0434\u043d\u0430\u043a\u043e \u043e\u0431\u044b\u0447\u043d\u044b\u0435 \u0434\u043e\u0441\u043f\u0435\u0445\u0438 \u043d\u0435 \u0441\u0434\u0435\u043b\u0430\u044e\u0442 \u044d\u0442\u043e\u0433\u043e. \u0427\u0442\u043e\u0431\u044b \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u0430\u0448\u0438 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f, \u0432\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u044f\u0442\u0441\u044f \u043c\u0430\u0433\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0434\u043e\u0441\u043f\u0435\u0445\u0438: \u0448\u043b\u044f\u043f\u0430, \u0445\u0430\u043b\u0430\u0442\u044b, \u0448\u0442\u0430\u043d\u044b \u0438 \u0441\u0430\u043f\u043e\u0433\u0438. \u0412 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043e\u0442 \u043e\u0431\u044b\u0447\u043d\u043e\u0439 \u0431\u0440\u043e\u043d\u0438, \u043e\u043d\u0438 \u043d\u0435 \u0441\u043b\u043e\u043c\u0430\u044e\u0442\u0441\u044f. \u0412\u043c\u0435\u0441\u0442\u043e \u044d\u0442\u043e\u0433\u043e \u043e\u043d\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442 \u0442\u0430\u0439\u043d\u0443\u044e \u044d\u043d\u0435\u0440\u0433\u0438\u044e \u0434\u043b\u044f \u0437\u0430\u0449\u0438\u0442\u044b \u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044f. \u042d\u0442\u043e, \u043a\u043e\u043d\u0435\u0447\u043d\u043e, \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e \u043e\u043d\u0438 \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u0437\u0430\u0440\u044f\u0436\u0435\u043d\u044b \u043c\u0430\u0433\u0438\u0447\u0435\u0441\u043a\u0438\u043c\u0438 \u043a\u0440\u0438\u0441\u0442\u0430\u043b\u043b\u0430\u043c\u0438, \u043a\u0430\u043a \u0438 \u0436\u0435\u0437\u043b\u044b. \u041a \u0441\u0447\u0430\u0441\u0442\u044c\u044e, \u044d\u0442\u0438 \u043f\u0440\u0435\u0434\u043c\u0435\u0442\u044b \u043e\u0434\u0435\u0436\u0434\u044b \u043d\u0435 \u043f\u043e\u0442\u0440\u0435\u0431\u043b\u044f\u044e\u0442 \u043c\u043d\u043e\u0433\u043e @mana \u043c\u0430\u043d\u044b@. \u0417\u0430\u0440\u044f\u0434\u0438\u0442\u0435 \u0438\u0445, \u043a\u0430\u043a \u0436\u0435\u0437\u043b \u0432 @arcane_workbench \u0447\u0430\u0440\u043e\u0434\u0435\u0439\u0441\u043a\u043e\u043c \u0441\u0442\u043e\u043b\u0435@ \u0438\u043b\u0438 \u0432 @mana_flasks \u043a\u043e\u043b\u0431\u0435 \u043c\u0430\u043d\u044b@, \u043d\u043e \u0431\u0443\u0434\u044c\u0442\u0435 \u043e\u0441\u0442\u043e\u0440\u043e\u0436\u043d\u044b - \u0435\u0441\u043b\u0438 \u0443 \u043d\u0438\u0445 \u0437\u0430\u043a\u043e\u043d\u0447\u0438\u0442\u0441\u044f \u043c\u0430\u043d\u0430, \u0432\u044b \u043e\u043a\u0430\u0436\u0435\u0442\u0435\u0441\u044c \u0431\u0435\u0437\u0437\u0430\u0449\u0438\u0442\u043d\u044b\u043c\u0438.", + + "#image wizard_armour", + + "\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u0443\u044e \u0431\u0440\u043e\u043d\u044e, \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u044f \u0435\u0435 \u0438\u0437 \u043c\u0430\u0433\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0448\u0435\u043b\u043a\u0430, \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c\u043e\u0433\u043e \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u043d\u0438\u0442\u043a\u0438 \u0441 \u043c\u0430\u0433\u0438\u0447\u0435\u0441\u043a\u0438\u043c \u043a\u0440\u0438\u0441\u0442\u0430\u043b\u043b\u043e\u043c:", + + "#recipe magic_silk", + "#recipe wizard_hat", + "#recipe wizard_robe", + "#recipe wizard_leggings", + "#recipe wizard_boots", + + "\u041c\u043e\u0436\u043d\u043e \u0437\u0430\u043a\u043e\u043b\u0434\u043e\u0432\u0430\u0442\u044c \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u0443\u044e \u0431\u0440\u043e\u043d\u044e, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u043e\u0431\u044b\u0447\u043d\u044b\u0439 \u0441\u0442\u043e\u043b \u0437\u0430\u0447\u0430\u0440\u043e\u0432\u0430\u043d\u0438\u0439, \u0438 \u0438\u0437-\u0437\u0430 \u043f\u0440\u0438\u0441\u0443\u0449\u0438\u0445 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430\u043c \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u043e\u0433\u043e \u0448\u0435\u043b\u043a\u0430 \u043e\u043d\u0430, \u043a\u0430\u043a \u043f\u0440\u0430\u0432\u0438\u043b\u043e, \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u0430 \u0434\u043b\u044f \u0443\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f \u0447\u0430\u0440. \u041e\u0441\u043e\u0431\u0443\u044e \u043f\u043e\u043b\u044c\u0437\u0443 \u0434\u043b\u044f \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u0438\u043a\u0430 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f \u0437\u0430\u0449\u0438\u0442\u044b \u043e\u0442 \u041c\u0430\u0433\u0438\u0438, \u041c\u043e\u0440\u043e\u0437\u0430, \u0438 \u0423\u0434\u0430\u0440\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0433\u0443\u0442 \u043e\u043a\u0430\u0437\u0430\u0442\u044c\u0441\u044f \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u043c\u0438 \u0434\u043b\u044f \u0437\u0430\u0449\u0438\u0442\u044b \u043e\u0442 \u0434\u0440\u0443\u0433\u0438\u0445 \u043e\u0431\u043b\u0430\u0434\u0430\u0442\u0435\u043b\u0435\u0439 \u043c\u0430\u0433\u0438\u0438.", + + "\u0422\u0435 \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u0438\u043a\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u043e\u0441\u0432\u044f\u0449\u0430\u044e\u0442 \u0441\u0435\u0431\u044f \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0435 \u043e\u0434\u043d\u043e\u0433\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430, \u0438\u043d\u043e\u0433\u0434\u0430 \u043d\u043e\u0441\u044f\u0442 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0435 \u043e\u0434\u0435\u0436\u0434\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0434\u0430\u044e\u0442 \u0431\u043e\u043d\u0443\u0441\u044b \u0437\u0430 \u044d\u0442\u043e\u0442 \u044d\u043b\u0435\u043c\u0435\u043d\u0442. \u041f\u043e\u043b\u043d\u044b\u0435 \u043a\u043e\u043c\u043f\u043b\u0435\u043a\u0442\u044b \u0442\u0430\u043a\u043e\u0439 \u043e\u0434\u0435\u0436\u0434\u044b \u043e\u0447\u0435\u043d\u044c \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u044b \u0441\u0440\u0435\u0434\u0438 \u043c\u0430\u0441\u0442\u0435\u0440\u043e\u0432, \u0438 \u0438\u0445 \u043d\u0435 \u0442\u0430\u043a \u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0430\u0439\u0442\u0438.", + + "\u041b\u0435\u0433\u0435\u043d\u0434\u0430 \u0433\u043e\u0432\u043e\u0440\u0438\u0442 \u043e \u0442\u0430\u0439\u043d\u043e\u0439 \u043f\u0435\u0447\u0430\u0442\u0438, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u043e\u0439 \u0441\u0430\u043c\u044b\u043c\u0438 \u043c\u043e\u0433\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u043c\u0438 \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u0438\u043a\u0430\u043c\u0438, \u0447\u0442\u043e\u0431\u044b \u0437\u043d\u0430\u0447\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0443\u043b\u0443\u0447\u0448\u0438\u0442\u044c \u0438\u0445 \u0431\u0440\u043e\u043d\u044e." + ] + }, + + "magical_world": { + "title": "\u041c\u0430\u0433\u0438\u044f \u0432 \u043c\u0438\u0440\u0435", + "include_in_contents": "main_contents", + "contents": { + "id": "magical_world_subsections", + "hyperlinks": true, + "page_numbers": true, + "separator": "." + }, + "triggers": [ + "ebwizardry:handbook/on_subsection_unlock" + ], + "text": [ + + "\u041d\u0438\u043a\u0442\u043e \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0441\u0442\u0430\u0442\u044c \u0432\u0435\u043b\u0438\u043a\u0438\u043c \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u0438\u043a\u043e\u043c, \u043f\u0440\u043e\u0441\u0442\u043e \u043e\u0441\u0442\u0430\u0432\u0430\u044f\u0441\u044c \u0434\u043e\u043c\u0430 - \u0435\u0441\u0442\u044c \u043c\u0438\u0440, \u043f\u043e\u043b\u043d\u044b\u0439 \u043c\u0430\u0433\u0438\u0438, \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u0439 \u0434\u043b\u044f \u0438\u0441\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u044f! \u0414\u0440\u0435\u0432\u043d\u0438\u0435 \u0440\u0443\u0438\u043d\u044b, \u0440\u0435\u043b\u0438\u043a\u0432\u0438\u0438 \u0438 \u0442\u0430\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0430, \u0445\u043e\u0440\u043e\u0448\u0438\u0435 \u0438 \u043f\u043b\u043e\u0445\u0438\u0435, \u0436\u0434\u0443\u0442 \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u044f - \u0435\u0441\u043b\u0438 \u0432\u044b \u0437\u043d\u0430\u0435\u0442\u0435, \u0433\u0434\u0435 \u0438\u0441\u043a\u0430\u0442\u044c. \u0412\u0435\u043b\u0438\u0447\u0430\u0439\u0448\u0438\u0435 \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u0438\u043a\u0438 \u0442\u0430\u043a\u0436\u0435 \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u043b\u044e\u0431\u043e\u043f\u044b\u0442\u043d\u044b \u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u044e\u0442 \u0431\u043e\u043b\u044c\u0448\u0443\u044e \u0447\u0430\u0441\u0442\u044c \u0441\u0432\u043e\u0438\u0445 \u0437\u043d\u0430\u043d\u0438\u0439 \u0438 \u0441\u0438\u043b\u044b, \u0438\u0441\u0441\u043b\u0435\u0434\u0443\u044f \u043e\u043a\u0440\u0443\u0436\u0430\u044e\u0449\u0443\u044e \u0441\u0440\u0435\u0434\u0443 \u0438 \u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0438\u0440\u0443\u044f \u0441 \u0442\u0435\u043c, \u0447\u0442\u043e \u043e\u043d\u0438 \u043d\u0430\u0445\u043e\u0434\u044f\u0442.", + + "\u0425\u043e\u0442\u044f \u0431\u043e\u043b\u044c\u0448\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u0438\u043a\u0430 \u043d\u0435\u0438\u0437\u0431\u0435\u0436\u043d\u043e \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 \u0432 \u043e\u0434\u0438\u043d\u043e\u0447\u0435\u0441\u0442\u0432\u0435, \u043e\u0431\u043c\u0435\u043d \u0437\u043d\u0430\u043d\u0438\u044f\u043c\u0438 \u0442\u0430\u043a\u0436\u0435 \u0436\u0438\u0437\u043d\u0435\u043d\u043d\u043e \u0432\u0430\u0436\u0435\u043d \u0434\u043b\u044f \u0438\u0437\u0443\u0447\u0435\u043d\u0438\u044f \u0442\u0430\u0439\u043d\u044b\u0445 \u0438\u0441\u043a\u0443\u0441\u0441\u0442\u0432. \u0418\u0449\u0438\u0442\u0435 \u043a\u043e\u043b\u043b\u0435\u0433-\u043f\u0440\u0430\u043a\u0442\u0438\u043a\u043e\u0432 \u043c\u0430\u0433\u0438\u0438 \u0438 \u0443\u0447\u0438\u0442\u0435\u0441\u044c \u0443 \u043d\u0438\u0445 \u043a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u0431\u043e\u043b\u044c\u0448\u0435\u043c\u0443: \u0434\u0440\u0443\u0436\u0431\u0430, \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e, \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432\u0435\u043b\u0438\u0447\u0430\u0439\u0448\u0435\u0439 \u0441\u0438\u043b\u043e\u0439 \u0438\u0437 \u0432\u0441\u0435\u0445." + ], + "sections": { + "crystal_flowers": { + "title": "\u041a\u0440\u0438\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0426\u0432\u0435\u0442\u044b", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:handbook/crystal_flowers" + ], + "text": [ + + "\u0412\u043e \u0432\u0440\u0435\u043c\u044f \u0432\u0430\u0448\u0438\u0445 \u043f\u0443\u0442\u0435\u0448\u0435\u0441\u0442\u0432\u0438\u0439 \u0432\u044b, \u0432\u0435\u0440\u043e\u044f\u0442\u043d\u043e, \u0441\u0442\u043e\u043b\u043a\u043d\u0435\u0442\u0435\u0441\u044c \u0441 \u043b\u044e\u0431\u043e\u043f\u044b\u0442\u043d\u044b\u043c\u0438 \u0441\u0432\u0435\u0442\u044f\u0449\u0438\u043c\u0438\u0441\u044f \u0446\u0432\u0435\u0442\u0430\u043c\u0438, \u0440\u0430\u0441\u0442\u0443\u0449\u0438\u043c\u0438 \u0432 \u0434\u0438\u043a\u043e\u0439 \u043f\u0440\u0438\u0440\u043e\u0434\u0435 \u0432\u0440\u0435\u043c\u044f \u043e\u0442 \u0432\u0440\u0435\u043c\u0435\u043d\u0438. \u042d\u0442\u043e \u043a\u0440\u0438\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0446\u0432\u0435\u0442\u044b, \u0438 \u043e\u043d\u0438 \u0441\u0442\u0440\u0430\u043d\u043d\u043e \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u044b \u043f\u0440\u0438 \u043a\u043e\u043d\u0446\u0435\u043d\u0442\u0440\u0430\u0446\u0438\u0438 @mana \u043c\u0430\u043d\u044b@ - \u0434\u043e \u0441\u0438\u0445 \u043f\u043e\u0440 \u043d\u0438\u043a\u0442\u043e \u0442\u043e\u0447\u043d\u043e \u043d\u0435 \u0437\u043d\u0430\u0435\u0442, \u043f\u043e\u0447\u0435\u043c\u0443. \u041e\u0434\u043d\u0430\u043a\u043e \u043c\u044b \u0437\u043d\u0430\u0435\u043c, \u0447\u0442\u043e \u043e\u043d\u0438 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0441\u043e\u0431\u0440\u0430\u043d\u044b \u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u044b \u0434\u043b\u044f \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u044f \u043c\u0430\u043d\u044b \u0432 \u0432\u0438\u0434\u0435 \u043a\u0440\u0438\u0441\u0442\u0430\u043b\u043b\u043e\u0432, \u0447\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442 \u0438\u0445 \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u043c \u043d\u0430\u0437\u0435\u043c\u043d\u044b\u043c \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u043c \u043c\u0430\u043d\u044b. \u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043c\u0430\u043d\u044b, \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c\u043e\u0435 \u0442\u0430\u043a\u0438\u043c \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u043c, \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u043e \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0438\u043c \u0440\u0430\u0437\u043c\u0435\u0440\u043e\u043c \u0446\u0432\u0435\u0442\u043a\u043e\u0432 \u0438 \u0438\u0445 \u043c\u0430\u043b\u043e\u0447\u0438\u0441\u043b\u0435\u043d\u043d\u043e\u0441\u0442\u044c\u044e.", + + "#image crystal_flower", + "#recipe crystal_flower_to_crystals" + ] + }, + "wizard_towers": { + "title": "\u0411\u0430\u0448\u043d\u0438 \u0412\u043e\u043b\u0448\u0435\u0431\u043d\u0438\u043a\u043e\u0432", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:wizard_tower" + ], + "text": [ + + "\u041d\u0435 \u0431\u043e\u0439\u0442\u0435\u0441\u044c - \u0432\u044b \u043d\u0435 \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0443\u044e\u0449\u0438\u0439 \u0432 \u044d\u0442\u043e\u043c \u043c\u0438\u0440\u0435. \u0412 \u0432\u0430\u0448\u0438\u0445 \u043f\u0443\u0442\u0435\u0448\u0435\u0441\u0442\u0432\u0438\u044f\u0445 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u0441\u0442\u0440\u0435\u0442\u0438\u0442\u044c \u0432\u044b\u0441\u043e\u043a\u0438\u0435 \u0431\u0430\u0448\u043d\u0438 \u0441 \u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u043d\u044b\u043c\u0438 \u043e\u0441\u0442\u0440\u043e\u043a\u043e\u043d\u0435\u0447\u043d\u044b\u043c\u0438 \u043a\u0440\u044b\u0448\u0430\u043c\u0438. \u042d\u0442\u043e \u0440\u0435\u0437\u0438\u0434\u0435\u043d\u0446\u0438\u0438 \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u0438\u043a\u043e\u0432. \u0416\u0438\u0437\u043d\u044c \u043e\u0434\u0438\u043d\u043e\u0447\u0435\u0441\u0442\u0432\u0430 \u043c\u043e\u0436\u0435\u0442 \u0438\u043d\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u0432 \u0437\u0430\u043c\u0435\u0448\u0430\u0442\u0435\u043b\u044c\u0441\u0442\u0432\u043e \u043c\u0430\u0433\u043e\u0432, \u043d\u043e \u043e\u043d\u0438, \u043a\u0430\u043a \u043f\u0440\u0430\u0432\u0438\u043b\u043e, \u0434\u0440\u0443\u0436\u0435\u043b\u044e\u0431\u043d\u044b\u0435 \u043b\u044e\u0434\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0433\u043e\u0442\u043e\u0432\u044b \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u0441 \u0432\u0430\u043c\u0438 \u0441\u0432\u043e\u0438\u043c\u0438 \u0437\u043d\u0430\u043d\u0438\u044f\u043c\u0438, \u0445\u043e\u0442\u044f \u0438 \u0437\u0430 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u0443\u044e \u043f\u043b\u0430\u0442\u0443. \u041e\u0436\u0438\u0434\u0430\u0439\u0442\u0435 \u0437\u0430\u043f\u043b\u0430\u0442\u0438\u0442\u044c \u0434\u0440\u0430\u0433\u043e\u0446\u0435\u043d\u043d\u044b\u043c\u0438 \u043c\u0435\u0442\u0430\u043b\u043b\u0430\u043c\u0438 \u0438 \u0434\u0440\u0430\u0433\u043e\u0446\u0435\u043d\u043d\u044b\u043c\u0438 \u043a\u0430\u043c\u043d\u044f\u043c\u0438, \u0438 \u0432\u0437\u0430\u043c\u0435\u043d \u0432\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0435 \u043c\u043d\u043e\u0433\u043e \u043e\u0442\u043b\u0438\u0447\u043d\u044b\u0445 \u0447\u0430\u0440\u043e\u0434\u0435\u0439\u0441\u043a\u0438\u0445 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e\u0441\u0442\u0435\u0439.", + + "\u041e\u0434\u043d\u0430\u043a\u043e \u0435\u0441\u043b\u0438 \u0432\u044b \u0438\u0449\u0435\u0442\u0435 @tiers \u043c\u0430\u0441\u0442\u0435\u0440\u0441\u043a\u043e\u0435@ \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0435, \u0442\u043e \u0432\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0433\u043e\u0432\u043e\u0440\u0438\u0442\u044c \u0441\u043e \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u0438\u0441\u0442\u043e\u043c.", + + "#image wizard_tower", + + "\u041a \u0441\u043e\u0436\u0430\u043b\u0435\u043d\u0438\u044e, \u0435\u0441\u0442\u044c \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u0438\u043a\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0435 \u0440\u0430\u0434\u044b \u043f\u043e\u0441\u0435\u0442\u0438\u0442\u0435\u043b\u044f\u043c. \u042d\u0442\u0438 \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u0438\u043a\u0438, \u043a\u0430\u043a \u043f\u0440\u0430\u0432\u0438\u043b\u043e, \u041e\u0442\u0432\u0435\u0440\u0436\u0435\u043d\u043d\u044b\u0435 \u0438 \u0432\u0440\u0430\u0436\u0434\u0435\u0431\u043d\u043e \u043e\u0442\u043d\u043e\u0441\u044f\u0442\u0441\u044f \u043a\u043e \u0432\u0441\u0435\u043c, \u043a\u0442\u043e \u043f\u0435\u0440\u0435\u0441\u0435\u043a\u0430\u0435\u0442 \u0438\u0445 \u043f\u0443\u0442\u044c. \u041f\u043e\u0434\u0445\u043e\u0434\u0438\u0442\u0435 \u043a \u044d\u0442\u0438\u043c \u043b\u044e\u0434\u044f\u043c \u0441 \u043e\u0441\u0442\u043e\u0440\u043e\u0436\u043d\u043e\u0441\u0442\u044c\u044e \u0438 \u0431\u0443\u0434\u044c\u0442\u0435 \u0433\u043e\u0442\u043e\u0432\u044b \u0437\u0430\u0449\u0438\u0442\u0438\u0442\u044c \u0441\u0435\u0431\u044f \u043e\u0442 \u043c\u043e\u0433\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0439 \u043c\u0430\u0433\u0438\u0438. \u041e\u0434\u043d\u0430\u043a\u043e \u0435\u0441\u043b\u0438 \u043f\u043e\u0431\u0435\u0434\u0438\u0442\u0435 \u0438\u0445, \u0442\u043e \u0438\u0445 \u0437\u043d\u0430\u043d\u0438\u044f \u0441\u0442\u0430\u043d\u0443\u0442 \u0432\u0430\u0448\u0438\u043c\u0438.", + + "\u0421\u043b\u0435\u0434\u0443\u0435\u0442 \u0442\u0430\u043a\u0436\u0435 \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e \u043d\u0438 \u043e\u0434\u0438\u043d \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u0438\u043a \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 \u0431\u043b\u0430\u0433\u043e\u0441\u043a\u043b\u043e\u043d\u043d\u043e \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u044c\u0441\u044f \u043a \u043d\u0430\u043f\u0430\u0434\u0435\u043d\u0438\u044e \u0438\u043b\u0438 \u043a\u0440\u0430\u0436\u0435 - \u0438 \u043e\u043d\u0438 \u0441 \u0433\u043e\u0442\u043e\u0432\u043d\u043e\u0441\u0442\u044c\u044e \u0432\u043e\u0437\u044c\u043c\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0437\u0430\u043a\u043e\u043d\u043e\u0432 \u0432 \u0441\u0432\u043e\u0438 \u0440\u0443\u043a\u0438." + ] + }, + "obelisks": { + "title": "\u041e\u0431\u0435\u043b\u0438\u0441\u043a\u0438", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:handbook/obelisks" + ], + "text": [ + + "\u041e\u0441\u0442\u0430\u0442\u043a\u0438 \u0434\u0440\u0435\u0432\u043d\u0435\u0439 \u043c\u0430\u0433\u0438\u0438 \u0440\u0430\u0437\u0431\u0440\u043e\u0441\u0430\u043d\u044b \u043f\u043e \u0432\u0441\u0435\u043c\u0443 \u043c\u0438\u0440\u0443, \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u0437\u0430\u043c\u0435\u0442\u043d\u044b\u043c\u0438 \u0438\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0440\u0435\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u043a\u0430\u043c\u0435\u043d\u043d\u044b\u0435 \u0441\u043e\u043e\u0440\u0443\u0436\u0435\u043d\u0438\u044f, \u0441 \u043d\u0430\u043d\u0435\u0441\u0435\u043d\u043d\u044b\u043c\u0438 \u043d\u0430 \u043d\u0438\u0445 \u0441\u0438\u043c\u0432\u043e\u043b\u0430\u043c\u0438 \u0438 \u0440\u0443\u043d\u0430\u043c\u0438. \u042d\u0442\u0438 \u0440\u0443\u0438\u043d\u044b - \u0432\u0441\u0435, \u0447\u0442\u043e \u043e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u043e\u0442 \u0442\u043e\u0433\u043e, \u0447\u0442\u043e, \u043f\u043e-\u0432\u0438\u0434\u0438\u043c\u043e\u043c\u0443, \u0431\u044b\u043b\u043e \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u0434\u0440\u0435\u0432\u043d\u0435\u0439 \u0446\u0438\u0432\u0438\u043b\u0438\u0437\u0430\u0446\u0438\u0435\u0439. \u041a\u0442\u043e \u0431\u044b \u0438\u043b\u0438 \u0447\u0442\u043e \u0431\u044b \u043d\u0438 \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u043b\u043e \u0438\u0445, \u043e\u043d\u0438 \u044f\u0432\u043d\u043e \u043e\u0431\u043b\u0430\u0434\u0430\u043b\u0438 \u0437\u043d\u0430\u0447\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u043c \u0437\u043d\u0430\u043d\u0438\u0435\u043c \u043c\u0430\u0433\u0438\u0438 \u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b\u0438 \u0435\u0433\u043e \u0434\u043b\u044f \u0440\u0430\u0437\u043c\u0435\u0449\u0435\u043d\u0438\u044f \u0437\u0430\u0449\u0438\u0442\u043d\u044b\u0445 \u0447\u0430\u0440 \u043d\u0430\u0434 \u0442\u0430\u043a\u0438\u043c\u0438 \u043c\u0435\u0441\u0442\u0430\u043c\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0442\u0441\u044f \u0438 \u043f\u043e \u0441\u0435\u0439 \u0434\u0435\u043d\u044c.", + + "\u042d\u0442\u0438 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b \u043c\u043e\u0436\u043d\u043e \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u044c \u043d\u0430 \u0434\u0432\u0430 \u0442\u0438\u043f\u0430. \u041f\u0435\u0440\u0432\u044b\u0439 \u0438 \u0431\u043e\u043b\u0435\u0435 \u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u0439 \u0438\u0437 \u043d\u0438\u0445 - \u043e\u0431\u0435\u043b\u0438\u0441\u043a\u0438: \u0432\u044b\u0441\u043e\u043a\u0438\u0435 \u0448\u0438\u043f\u044b \u0438\u0437 \u0440\u0435\u0437\u043d\u043e\u0433\u043e \u043a\u0430\u043c\u043d\u044f, \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u043e\u0433\u043e \u043a\u0430\u043a \u0440\u0443\u043d\u043d\u044b\u0439 \u043a\u0430\u043c\u0435\u043d\u044c, \u0441 \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u0439 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0439\u043a\u043e\u0439 \u0432\u043d\u0438\u0437\u0443, \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0435\u0439 \u043d\u0435\u0437\u043d\u0430\u0447\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0442\u0430\u0439\u043d\u044b\u0435 \u0440\u0435\u043b\u0438\u043a\u0432\u0438\u0438 \u0438\u0437 \u0437\u0430\u0431\u044b\u0442\u043e\u0433\u043e \u043f\u0440\u043e\u0448\u043b\u043e\u0433\u043e.", + + "#image obelisk", + + "\u042d\u0442\u0438 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b \u043e\u0431\u044b\u0447\u043d\u043e \u0437\u0430\u0449\u0438\u0449\u0435\u043d\u044b \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0435\u043c, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u0432\u0440\u0430\u0436\u0434\u0435\u0431\u043d\u044b\u0445 @creatures \u043c\u0430\u0433\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0441\u0443\u0449\u0435\u0441\u0442\u0432@ \u0435\u0441\u043b\u0438 \u043a\u0442\u043e-\u043b\u0438\u0431\u043e \u043f\u043e\u0434\u043e\u0439\u0434\u0435\u0442 \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u0431\u043b\u0438\u0437\u043a\u043e. \u041e\u0442\u0431\u0438\u0432\u0430\u0439\u0441\u044f \u043e\u0442 \u044d\u0442\u0438\u0445 \u0442\u0432\u0430\u0440\u0435\u0439 \u0438 \u0443\u043d\u0438\u0447\u0442\u043e\u0436\u044c \u0438\u0445 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a, \u0438 \u0440\u0435\u043b\u0438\u043a\u0432\u0438\u0438 \u0431\u0443\u0434\u0443\u0442 \u0442\u0432\u043e\u0438\u043c\u0438." + ] + }, + "shrines": { + "title": "\u0421\u0432\u044f\u0442\u0438\u043b\u0438\u0449\u0430", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:visit_shrine" + ], + "text": [ + + "\u0412\u0442\u043e\u0440\u043e\u0439, \u0431\u043e\u043b\u0435\u0435 \u0440\u0435\u0434\u043a\u0438\u0439 \u0432\u0438\u0434 \u0441\u043e\u043e\u0440\u0443\u0436\u0435\u043d\u0438\u044f - \u0421\u0432\u044f\u0442\u0438\u043b\u0438\u0449\u0435. \u042d\u0442\u0438 \u0441\u043e\u043e\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u0442 \u0438\u0437 \u043a\u0440\u0443\u0433\u0430 \u0440\u0443\u043d\u043d\u044b\u0445 \u043a\u043e\u043b\u043e\u043d\u043d, \u043e\u043a\u0440\u0443\u0436\u0430\u044e\u0449\u0438\u0445 \u0446\u0435\u043d\u0442\u0440\u0430\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u0441\u0442\u0430\u043c\u0435\u043d\u0442, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0441\u0442\u043e\u0438\u0442 \u0441\u0443\u043d\u0434\u0443\u043a, \u043d\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u0439 \u0434\u0440\u0435\u0432\u043d\u0438\u043c\u0438 @artefacts \u0430\u0440\u0442\u0435\u0444\u0430\u043a\u0442\u0430\u043c\u0438@. \u042d\u0442\u043e\u0442 \u0441\u0443\u043d\u0434\u0443\u043a \u043e\u0431\u044b\u0447\u043d\u043e \u0437\u0430\u0449\u0438\u0449\u0435\u043d \u0442\u0430\u0439\u043d\u044b\u043c \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0435\u043c \u0437\u0430\u043c\u043a\u0430, \u043d\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0449\u0438\u043c \u043d\u0438\u043a\u043e\u043c\u0443, \u043a\u0440\u043e\u043c\u0435 \u0432\u043b\u0430\u0434\u0435\u043b\u044c\u0446\u0430, \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u0435\u0433\u043e. \u0412\u0441\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0442\u0430\u043a\u0436\u0435 \u0437\u0430\u0449\u0438\u0449\u0435\u043d\u0430 \u0441\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0449\u0438\u043c \u043f\u043e\u043b\u0435\u043c, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043d\u0435 \u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u0432\u0441\u0451, \u0447\u0442\u043e \u0431\u043b\u0443\u0436\u0434\u0430\u0435\u0442 \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u0431\u043b\u0438\u0437\u043a\u043e.\n", + + "#image shrine", + + "\u0418\u0437-\u0437\u0430 \u0438\u0445 \u0432\u0435\u043b\u0438\u043a\u043e\u0439 \u0442\u0430\u0439\u043d\u043e\u0439 \u0441\u0438\u043b\u044b \u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f, \u043d\u0435 \u0433\u043e\u0432\u043e\u0440\u044f \u0443\u0436\u0435 \u043e \u0431\u043e\u0433\u0430\u0442\u0441\u0442\u0432\u0435 \u0432\u043d\u0443\u0442\u0440\u0438, \u044d\u0442\u0438 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e \u043f\u0440\u0438\u0432\u043b\u0435\u043a\u0430\u0442\u0435\u043b\u044c\u043d\u044b \u0434\u043b\u044f \u043b\u044e\u0431\u043e\u0433\u043e \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0435\u0433\u043e \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u0438\u043a\u0430 - \u043d\u043e \u0431\u0443\u0434\u044c\u0442\u0435 \u043e\u0441\u0442\u043e\u0440\u043e\u0436\u043d\u044b. \u0415\u0441\u0442\u044c \u043c\u043d\u043e\u0433\u043e\u0447\u0438\u0441\u043b\u0435\u043d\u043d\u044b\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043e \u0442\u043e\u043c, \u0447\u0442\u043e \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u0438\u043a\u0438 \u043f\u043e\u043f\u0430\u0434\u0430\u044e\u0442 \u0432 \u043b\u043e\u0432\u0443\u0448\u043a\u0443 \u0441\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0449\u0438\u0445 \u043f\u043e\u043b\u0435\u0439 \u0438 \u043f\u043e\u0441\u0442\u0435\u043f\u0435\u043d\u043d\u043e \u0441\u0445\u043e\u0434\u044f\u0442 \u0441 \u0443\u043c\u0430, \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e, \u0438\u0437-\u0437\u0430 \u043a\u043b\u0430\u0443\u0441\u0442\u0440\u043e\u0444\u043e\u0431\u0438\u0438. \u041e\u0434\u043d\u0430\u043a\u043e \u0432\u0441\u0435 \u0431\u043e\u043b\u044c\u0448\u0435\u0435 \u0447\u0438\u0441\u043b\u043e \u043b\u044e\u0434\u0435\u0439 \u0441\u0447\u0438\u0442\u0430\u044e\u0442, \u0447\u0442\u043e \u043f\u043e\u0434\u043e\u0431\u043d\u044b\u0435 \u0441\u043b\u0443\u0447\u0430\u0438 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u043f\u0440\u0435\u0434\u043d\u0430\u043c\u0435\u0440\u0435\u043d\u043d\u043e\u0439 \u0447\u0430\u0441\u0442\u044c\u044e \u0437\u0430\u0449\u0438\u0442\u043d\u043e\u0439 \u043c\u0430\u0433\u0438\u0438 \u0441\u0432\u044f\u0442\u0438\u043b\u0438\u0449, \u0438 \u0447\u0442\u043e \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u0438\u043a\u0438, \u0437\u0430\u043f\u0435\u0440\u0442\u044b\u0435 \u0432\u043d\u0443\u0442\u0440\u0438, \u043d\u0430 \u0441\u0430\u043c\u043e\u043c \u0434\u0435\u043b\u0435 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0438\u0440\u0443\u044e\u0442\u0441\u044f, \u0447\u0442\u043e\u0431\u044b \u0437\u0430\u0449\u0438\u0442\u0438\u0442\u044c \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443. \u041f\u0443\u0433\u0430\u044e\u0449\u0430\u044f \u043f\u0435\u0440\u0441\u043f\u0435\u043a\u0442\u0438\u0432\u0430, \u043a\u043e\u043d\u0435\u0447\u043d\u043e, \u0434\u043b\u044f \u043b\u044e\u0431\u043e\u0433\u043e, \u043a\u0442\u043e \u043e\u0441\u043c\u0435\u043b\u0438\u0442\u0441\u044f \u043f\u0440\u0438\u0431\u043b\u0438\u0437\u0438\u0442\u044c\u0441\u044f...\n" + ] + }, + "creatures": { + "title": "\u041c\u0430\u0433\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0421\u0443\u0449\u0435\u0441\u0442\u0432\u0430", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:handbook/magical_creatures" + ], + "text": [ + + "\u041f\u043e\u043c\u0438\u043c\u043e \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u0438\u043a\u043e\u0432, \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u0434\u0440\u0443\u0433\u0438\u0445 \u0441\u0443\u0449\u0435\u0441\u0442\u0432 \u0442\u0430\u043a\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442 \u043c\u0430\u0433\u0438\u044e. \u041c\u043d\u043e\u0433\u0438\u0435 \u0438\u0437 \u044d\u0442\u0438\u0445 \u0441\u0443\u0449\u0435\u0441\u0442\u0432 \u0438\u043c\u0435\u044e\u0442 \u0422\u0430\u0439\u043d\u043e\u0435 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0435, \u0438 \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u043e \u0438\u0437 \u043d\u0438\u0445 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0432\u044b\u0437\u0432\u0430\u043d\u044b \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0445 \u043c\u0430\u0433\u0438\u0447\u0435\u0441\u043a\u0438\u0445 @spells \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439@. \u041c\u043e\u0436\u043d\u043e \u0432\u0441\u0442\u0440\u0435\u0442\u0438\u0442\u044c \u044d\u0442\u0438\u0445 \u0441\u0443\u0449\u0435\u0441\u0442\u0432, \u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0449\u0438\u0445 @obelisks \u043e\u0431\u0435\u043b\u0438\u0441\u043a\u0438@, \u0438\u043b\u0438, \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e, \u043c\u043e\u0433\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u0437\u044b\u0432\u0430\u0442\u0435\u043b\u044f. \u041c\u0435\u043d\u044c\u0448\u0438\u0435 \u0442\u0430\u0439\u043d\u044b\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0430 \u0434\u0430\u0436\u0435 \u0438\u043d\u043e\u0433\u0434\u0430 \u0432\u0441\u0442\u0440\u0435\u0447\u0430\u044e\u0442\u0441\u044f \u0432 \u043f\u0443\u0441\u0442\u044b\u043d\u0435." + ] + }, + "artefacts": { + "title": "\u0410\u0440\u0442\u0435\u0444\u0430\u043a\u0442\u044b", + "include_in_contents": "magical_world_subsections", + "triggers": [ + "ebwizardry:artefact" + ], + "text": [ + + "\u0418\u0441\u0441\u043b\u0435\u0434\u0443\u044f @shrines \u0441\u0432\u044f\u0442\u0438\u043b\u0438\u0449\u0430@, \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0443\u0434\u0430\u0447\u043b\u0438\u0432\u044b, \u0447\u0442\u043e\u0431\u044b \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0442\u044c \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u0434\u0440\u0435\u0432\u043d\u0438\u0439 \u043c\u0430\u0433\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0430\u0440\u0442\u0435\u0444\u0430\u043a\u0442-\u043e\u0431\u044a\u0435\u043a\u0442, \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u044b\u0439 \u043d\u0430\u0434\u0435\u043b\u0438\u0442\u044c \u0441\u0432\u043e\u0435\u0433\u043e \u0432\u043b\u0430\u0434\u0435\u043b\u044c\u0446\u0430 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u043c\u0438 \u0438 \u043c\u043e\u0449\u043d\u044b\u043c\u0438 \u0443\u0441\u0438\u043b\u0435\u043d\u0438\u044f\u043c\u0438 \u0438 \u043e\u0441\u043e\u0431\u044b\u043c\u0438 \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u044f\u043c\u0438. \u0418\u0437\u0432\u0435\u0441\u0442\u043d\u043e, \u0447\u0442\u043e \u0431\u044b\u043b\u0438 \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0442\u0440\u0438 \u0442\u0438\u043f\u0430: \u043a\u043e\u043b\u044c\u0446\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0434\u0430\u044e\u0442 \u0431\u043e\u043d\u0443\u0441\u044b \u0438 \u044d\u0444\u0444\u0435\u043a\u0442\u044b \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f\u043c, \u0430\u043c\u0443\u043b\u0435\u0442\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0443\u043b\u0443\u0447\u0448\u0430\u044e\u0442 \u0437\u0430\u0449\u0438\u0442\u043d\u044b\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u0438, \u0438 \u0430\u043c\u0443\u043b\u0435\u0442\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0434\u0430\u044e\u0442 \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u0435 \u044d\u0444\u0444\u0435\u043a\u0442\u044b. \u042d\u0442\u0438 \u0430\u0440\u0442\u0435\u0444\u0430\u043a\u0442\u044b, \u043f\u043e-\u0432\u0438\u0434\u0438\u043c\u043e\u043c\u0443, \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0438\u0440\u0443\u044e\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u0438 \u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0435\u043c \u043d\u043e\u0448\u0435\u043d\u0438\u0438; \u043f\u0440\u043e\u0441\u0442\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0438\u0445 \u043f\u0440\u0438 \u0441\u0435\u0431\u0435 \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e." + ] + } + } + }, + + "tiers": { + "title": "\u0423\u0440\u043e\u0432\u043d\u0438", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:handbook/tome_of_arcana" + ], + "text":[ + + "@wands \u0416\u0435\u0437\u043b\u044b@ and @spells \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f@ \u0431\u044b\u0432\u0430\u044e\u0442 \u0447\u0435\u0442\u044b\u0440\u0435\u0445 \u0443\u0440\u043e\u0432\u043d\u0435\u0439: #colour_novice\u043d\u043e\u0432\u0438\u0447\u043e\u043a#colour_reset, #colour_apprentice\u0443\u0447\u0435\u043d\u0438\u043a#colour_reset, #colour_advanced\u043f\u0440\u043e\u0434\u0432\u0438\u043d\u0443\u0442\u044b\u0439 #colour_resetand #colour_master\u043c\u0430\u0441\u0442\u0435\u0440#colour_reset. \u041a\u0430\u0436\u0434\u044b\u0439 \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u043c\u043e\u0449\u043d\u0435\u0435 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0433\u043e.", + + "#image tiers", + + "\u0416\u0435\u0437\u043b\u044b #colour_novice\u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0438\u0445 #colour_reset\u044d\u0442\u043e \u0442\u0435, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0436\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c. \u041e\u043d\u0438 \u0432\u044b\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0442 \u0434\u043e #novice_max_charge \u043c\u0430\u043d\u044b, \u0438 \u043c\u043e\u0433\u0443\u0442 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0435\u0433\u043e \u0443\u0440\u043e\u0432\u043d\u044f. \u041d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0438\u0435 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u044b, \u0447\u0442\u043e\u0431\u044b \u0431\u044b\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u043c\u0438 \u043a\u0435\u043c \u0443\u0433\u043e\u0434\u043d\u043e, \u0438 \u043e\u043d\u0438 \u043e\u0431\u044b\u0447\u043d\u043e \u043d\u0435 \u0442\u0440\u0430\u0442\u044f\u0442 \u043c\u043d\u043e\u0433\u043e \u043c\u0430\u043d\u044b. \u041e\u0434\u043d\u0430\u043a\u043e \u044d\u0442\u043e \u043d\u0435 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e \u043e\u043d\u0438 \u0431\u0435\u0441\u043f\u043e\u043b\u0435\u0437\u043d\u044b \u043d\u0430 \u0431\u043e\u043b\u0435\u0435 \u0432\u044b\u0441\u043e\u043a\u0438\u0445 \u0443\u0440\u043e\u0432\u043d\u044f\u0445; \u0438\u0445 \u043d\u0438\u0437\u043a\u0430\u044f \u0441\u0442\u043e\u0438\u043c\u043e\u0441\u0442\u044c \u0434\u0435\u043b\u0430\u0435\u0442 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0438\u0435 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u043c\u0438 \u0434\u0430\u0436\u0435 \u0442\u043e\u0433\u0434\u0430, \u043a\u043e\u0433\u0434\u0430 \u0443 \u0432\u0430\u0441 \u0435\u0441\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043c\u0430\u0441\u0442\u0435\u0440\u0441\u043a\u0438\u043c \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f\u043c.", + + "#colour_apprentice\u0423\u0447\u0435\u043d\u0438\u0447\u0435\u0441\u043a\u0438\u0435 #colour_reset\u0436\u0435\u0437\u043b\u044b \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u043f\u043e\u0441\u043b\u0435 \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0438\u0445. \u041e\u043d\u0438 \u0432\u044b\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0442 #apprentice_max_charge \u043c\u0430\u043d\u044b \u0438 \u043c\u043e\u0433\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f \u0434\u043b\u044f \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0438\u0445 \u0438 \u0443\u0447\u0435\u043d\u0438\u043a\u043e\u0432. \u0423\u0447\u0435\u043d\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0431\u043e\u043b\u0435\u0435 \u0432\u043f\u0435\u0447\u0430\u0442\u043b\u044f\u044e\u0449\u0438\u0435, \u0447\u0435\u043c \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0438\u0445, \u043d\u043e \u043e\u0431\u044b\u0447\u043d\u043e \u0441\u0442\u043e\u044f\u0442 \u0434\u043e\u0440\u043e\u0436\u0435.", + + "#colour_advanced\u041f\u0440\u043e\u0434\u0432\u0438\u043d\u0443\u0442\u044b\u0435 #colour_reset\u0436\u0435\u0437\u043b\u044b \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0440\u0435\u0434\u043a\u0438 \u0438 \u0433\u043e\u0440\u0430\u0437\u0434\u043e \u043c\u043e\u0449\u043d\u0435\u0435. \u041e\u043d\u0438 \u043c\u043e\u0433\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u0441\u0435 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f, \u043a\u0440\u043e\u043c\u0435 \u043c\u0430\u0441\u0442\u0435\u0440-\u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439, \u0438 \u0443\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c #advanced_max_charge \u043c\u0430\u043d\u044b. \u041f\u0440\u043e\u0434\u0432\u0438\u043d\u0443\u0442\u044b\u0435 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f \u043c\u043e\u0433\u0443\u0442 \u043d\u0430\u0434\u0435\u043b\u044f\u0442\u044c \u0441\u0432\u0435\u0440\u0445\u0447\u0435\u043b\u043e\u0432\u0435\u0447\u0435\u0441\u043a\u0438\u043c\u0438 \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u044f\u043c\u0438, \u0442\u0430\u043a\u0438\u043c\u0438 \u043a\u0430\u043a \u043d\u0435\u0432\u0438\u0434\u0438\u043c\u043e\u0441\u0442\u044c, \u0438 \u0441\u0435\u044f\u0442\u044c \u0445\u0430\u043e\u0441 \u043d\u0430 \u0432\u0440\u0430\u0433\u043e\u0432.", + + "#colour_master\u041c\u0430\u0441\u0442\u0435\u0440\u0441\u043a\u0438\u0435 #colour_reset\u0436\u0435\u0437\u043b\u044b \u0441\u0430\u043c\u044b\u0435 \u043c\u043e\u0433\u0443\u0447\u0438\u0435 \u0432 \u043c\u0438\u0440\u0435. \u041e\u043d\u0438 \u0432\u044b\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0442 \u0434\u043e #master_max_charge \u043c\u0430\u043d\u044b, \u0438 \u043c\u043e\u0433\u0443\u0442 \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c \u043b\u044e\u0431\u044b\u0435 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f. \u041c\u0430\u0441\u0442\u0435\u0440\u0441\u043a\u0438\u0435 \u043a\u043d\u0438\u0433\u0438 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439 \u043e\u0447\u0435\u043d\u044c \u0440\u0435\u0434\u043a\u0438, \u0438 \u043c\u043e\u0433\u0443\u0442 \u043d\u0430\u043d\u0435\u0441\u0442\u0438 \u0432\u0440\u0435\u0434 \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u0440\u0430\u0433\u0430\u043c, \u043d\u043e \u0434\u0430\u0436\u0435 \u0441\u0430\u043c\u043e\u043c\u0443 \u043c\u0438\u0440\u0443. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441 \u043e\u0441\u0442\u043e\u0440\u043e\u0436\u043d\u043e\u0441\u0442\u044c\u044e.", + + "\u0427\u0442\u043e\u0431\u044b \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043d\u0430 \u0431\u043e\u043b\u0435\u0435 \u0432\u044b\u0441\u043e\u043a\u0438\u0439 \u0443\u0440\u043e\u0432\u0435\u043d\u044c, \u0436\u0435\u0437\u043b \u0434\u043e\u043b\u0436\u0435\u043d \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u0441\u0442\u0430\u0442\u044c \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043c\u043e\u0449\u043d\u044b\u043c, \u0447\u0442\u043e\u0431\u044b \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f \u044d\u0442\u043e\u0433\u043e \u0443\u0440\u043e\u0432\u043d\u044f. \u041f\u0430\u043b\u043e\u0447\u043a\u0438 \u043f\u0440\u0438\u043e\u0431\u0440\u0435\u0442\u0430\u044e\u0442 \u0441\u0438\u043b\u0443, \u043a\u043e\u0433\u0434\u0430 \u0438\u043c\u0438 \u0432\u044b\u0437\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f. \u0420\u0430\u0437\u043d\u043e\u043e\u0431\u0440\u0430\u0437\u0438\u0435 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439, \u0441\u0438\u043b\u0430 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439, \u0441\u0442\u0438\u0445\u0438\u0439\u043d\u044b\u0435 \u044d\u0444\u0444\u0435\u043a\u0442\u044b \u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u0432\u043d\u0435\u0448\u043d\u0438\u0435 \u0444\u0430\u043a\u0442\u043e\u0440\u044b \u043c\u043e\u0433\u0443\u0442 \u0432\u043b\u0438\u044f\u0442\u044c \u043d\u0430 \u0442\u043e, \u043a\u0430\u043a \u0431\u044b\u0441\u0442\u0440\u043e \u0441\u043e\u0437\u0440\u0435\u0432\u0430\u0435\u0442 \u043f\u0430\u043b\u043e\u0447\u043a\u0430.", + + "\u041a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u0436\u0435\u0437\u043b \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043c\u043e\u0449\u043d\u044b\u043c, \u0442\u043e\u043c \u0410\u0440\u043a\u0430\u043d\u043e\u0432 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u0435\u0442 \u043e\u043a\u043e\u043d\u0447\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043a\u0430\u0442\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0439 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430 \u0436\u0435\u0437\u043b\u0430 \u043d\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0443\u0440\u043e\u0432\u0435\u043d\u044c (\u0441\u043c. @upgrading_wands \u0423\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u0435 \u0416\u0435\u0437\u043b\u043e\u0432@), \u0438 \u043a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u044d\u0442\u043e \u043f\u0440\u043e\u0438\u0437\u043e\u0439\u0434\u0435\u0442, \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0441\u043e\u0437\u0440\u0435\u0432\u0430\u043d\u0438\u044f \u043c\u043e\u0436\u0435\u0442 \u043d\u0430\u0447\u0430\u0442\u044c\u0441\u044f \u0441\u043d\u043e\u0432\u0430 \u0443\u0436\u0435 \u0434\u043b\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u0443\u0440\u043e\u0432\u043d\u044f." + ] + }, + + "elements": { + "title": "\u0421\u0442\u0438\u0445\u0438\u0438", + "include_in_contents": "main_contents", + "triggers": [ + "ebwizardry:handbook/elements" + ], + "text": [ + + "@spells \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f@ \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0442 \u043a \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u043c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u044e\u0442 \u043f\u0440\u0438\u0440\u043e\u0434\u0443 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442 \u043f\u0435\u0440\u043a\u0438 \u043f\u0440\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 \u0441 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u043c\u0438 @wands \u0436\u0435\u0437\u043b\u043e\u043c@.", + + "#image elements", + + "#colour_fire\u041e\u0433\u043e\u043d\u044c#colour_reset \\n\u041f\u043e\u0436\u0430\u043b\u0443\u0439, \u0441\u0430\u043c\u044b\u0439 \u0440\u0430\u0437\u0440\u0443\u0448\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442, \u043e\u0433\u043e\u043d\u044c \u043a\u0430\u0441\u0430\u0435\u0442\u0441\u044f \u0433\u043e\u0440\u0435\u043d\u0438\u044f, \u043b\u0430\u0432\u044b \u0438 \u0432\u0437\u0440\u044b\u0432\u043e\u0432. \u041c\u043e\u0449\u043d\u044b\u0439 \u043f\u0438\u0440\u043e\u043c\u0430\u043d\u0442 \u0443\u0441\u0442\u0440\u043e\u0438\u0442 \u0430\u0434 \u0441\u0432\u043e\u0438\u043c \u0432\u0440\u0430\u0433\u0430\u043c \u0438, \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e, \u0438 \u043c\u0438\u0440. \u0411\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u043e \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439 \u0441 \u043e\u0433\u043d\u0435\u043d\u043d\u043e\u0439 \u0430\u0442\u0430\u043a\u043e\u0439 \u043f\u043e\u0434\u0436\u0438\u0433\u0430\u0435\u0442 \u0446\u0435\u043b\u044c, \u043d\u0430\u043d\u043e\u0441\u044f \u0435\u0439 \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u044b\u0439 \u0443\u0440\u043e\u043d \u0441 \u0442\u0435\u0447\u0435\u043d\u0438\u0435\u043c \u0432\u0440\u0435\u043c\u0435\u043d\u0438. \u041e\u0434\u043d\u0430\u043a\u043e \u0431\u0443\u0434\u044c\u0442\u0435 \u043e\u0441\u0442\u043e\u0440\u043e\u0436\u043d\u044b, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f \u043e\u0433\u043d\u044f \u043c\u043e\u0433\u0443\u0442 \u043d\u0435 \u0432\u043b\u0438\u044f\u0442\u044c \u043d\u0430 \u0441\u0443\u0449\u0435\u0441\u0442\u0432 \u041d\u0438\u0436\u043d\u0435\u0433\u043e \u041c\u0438\u0440\u0430.", + + "#colour_ice\u041b\u0435\u0434#colour_reset \\n\u042d\u043b\u0435\u043c\u0435\u043d\u0442 \u043b\u044c\u0434\u0430 \u0441\u0434\u0435\u043b\u0430\u043d \u0438\u0437 \u0432\u0441\u0435\u0433\u043e, \u0447\u0442\u043e \u0445\u043e\u043b\u043e\u0434\u043d\u043e\u0435. \u041c\u043e\u0440\u043e\u0437\u043d\u044b\u0435 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f \u0447\u0430\u0441\u0442\u043e \u0437\u0430\u043c\u0435\u0434\u043b\u044f\u044e\u0442 \u0432\u0440\u0430\u0433\u043e\u0432 \u0438\u043b\u0438 \u0437\u0430\u043c\u043e\u0440\u0430\u0436\u0438\u0432\u0430\u044e\u0442 \u0438\u0445 \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e, \u0438 \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u044b \u043f\u0440\u043e\u0442\u0438\u0432 \u0441\u0443\u0449\u0435\u0441\u0442\u0432 \u043e\u0433\u043d\u044f. \u042d\u043b\u0435\u043c\u0435\u043d\u0442 \u043c\u0430\u0433\u0438\u0438 \u043b\u044c\u0434\u0430 \u043c\u043e\u0436\u0435\u0442 \u043e\u043a\u0430\u0437\u0430\u0442\u044c\u0441\u044f \u0441\u0442\u043e\u043b\u044c \u0436\u0435 \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u043c \u0438 \u0432\u043d\u0435 \u0431\u043e\u044f, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0434\u043b\u044f \u0437\u0430\u043c\u043e\u0440\u0430\u0436\u0438\u0432\u0430\u043d\u0438\u044f \u0432\u043e\u0434\u044b \u0438\u043b\u0438 \u043f\u0435\u0440\u0435\u0441\u0435\u0447\u0435\u043d\u0438\u044f \u0440\u0435\u043a\u0438.", + + "#colour_lightning\u041c\u043e\u043b\u043d\u0438\u044f#colour_reset \\n\u042d\u0442\u043e\u0442 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u043a\u0430\u0441\u0430\u0435\u0442\u0441\u044f \u043c\u043e\u043b\u043d\u0438\u0439, \u0448\u0442\u043e\u0440\u043c\u043e\u0432 \u0438 \u043f\u043e\u0433\u043e\u0434\u044b. \u0421\u0438\u043b\u044c\u043d\u044b\u0439 \u0448\u0442\u043e\u0440\u043c\u043e\u0432\u0440\u0439 \u043c\u0430\u0433 - \u044d\u0442\u043e \u043c\u043e\u0449\u044c, \u0441 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043d\u0443\u0436\u043d\u043e \u0441\u0447\u0438\u0442\u0430\u0442\u044c\u0441\u044f, \u0430 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043e\u0431\u043b\u0430\u0434\u0430\u044e\u0442 \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u044c\u044e \u043f\u0440\u0438\u0437\u044b\u0432\u0430\u0442\u044c \u043c\u043e\u043b\u043d\u0438\u044e \u043f\u043e \u0436\u0435\u043b\u0430\u043d\u0438\u044e. \u0417\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f \u043c\u043e\u043b\u043d\u0438\u0439 \u0447\u0430\u0441\u0442\u043e \u043d\u0430\u043d\u043e\u0441\u044f\u0442 \u0443\u0440\u043e\u043d \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u0443 \u0432\u0440\u0430\u0433\u043e\u0432 \u0441\u0440\u0430\u0437\u0443 \u0438 \u043e\u0431\u044b\u0447\u043d\u043e \u0438\u0449\u0443\u0442 \u0441\u0432\u043e\u044e \u0446\u0435\u043b\u044c, \u0434\u0435\u043b\u0430\u044f \u0438\u0445 \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0439 \u0430\u0442\u0430\u043a\u043e\u0439 \u043f\u0440\u043e\u0442\u0438\u0432 \u043b\u044e\u0431\u044b\u0445 \u043c\u043e\u0431\u043e\u0432... \u043d\u043e \u043e\u0441\u0442\u0435\u0440\u0435\u0433\u0430\u0439\u0442\u0435\u0441\u044c \u043a\u0440\u0438\u043f\u0435\u0440\u043e\u0432.\n", + + "#colour_necromancy\u041d\u0435\u043a\u0440\u043e\u043c\u0430\u043d\u0442\u0438\u044f#colour_reset \\n\u041d\u0435\u043a\u0440\u043e\u043c\u0430\u043d\u0442\u0438\u044f - \u044d\u0442\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u0442\u044c\u043c\u044b, \u0445\u0430\u043e\u0441\u0430 \u0438 \u043d\u0435\u0436\u0438\u0442\u0438. \u041d\u0435\u043a\u0440\u043e\u043c\u0430\u043d\u0442\u044b \u0442\u0430\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u044b \u0438 \u0447\u0430\u0441\u0442\u043e \u0440\u0430\u0441\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u043a\u0430\u043a \u0437\u043b\u044b\u0435, \u0445\u043e\u0442\u044f \u043e\u0431\u044b\u0447\u043d\u043e \u044d\u0442\u043e \u043d\u0435 \u0442\u0430\u043a. N\u0417\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f \u043d\u0435\u043a\u0440\u043e\u043c\u0430\u043d\u0442\u0438\u0438 \u043e\u0431\u044b\u0447\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438\u0437\u0432\u0430\u0442\u044c @creatures \u0441\u0443\u0449\u0435\u0441\u0442\u0432@ \u0447\u0442\u043e\u0431\u044b \u0441\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u0437\u0430 \u0432\u0430\u0441 \u0438\u043b\u0438 \u0434\u0430\u0436\u0435 \u0441\u0433\u0438\u0431\u0430\u0442\u044c \u0432\u043e\u043b\u044e \u0432\u0430\u0448\u0438\u0445 \u0432\u0440\u0430\u0433\u043e\u0432.\n", + + "#colour_earth\u0417\u0435\u043c\u043b\u044f#colour_reset \\n\u042d\u043b\u0435\u043c\u0435\u043d\u0442 \u0437\u0435\u043c\u043b\u0438 \u0444\u043e\u043a\u0443\u0441\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u043d\u0430 \u043f\u0440\u0438\u0440\u043e\u0434\u043d\u043e\u043c \u043c\u0438\u0440\u0435: \u0436\u0438\u0432\u043e\u0442\u043d\u044b\u0445, \u0440\u0430\u0441\u0442\u0435\u043d\u0438\u044f\u0445, \u0432\u0435\u0442\u0440\u0435 \u0438 \u0442.\u0434. \u041c\u0430\u0433\u0438\u044f \u0417\u0435\u043c\u043b\u0438 \u0440\u0430\u0437\u043d\u043e\u043e\u0431\u0440\u0430\u0437\u043d\u0430 \u0438 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0441\u0430\u043c\u044b\u0435 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0444\u043e\u0440\u043c\u044b: \u043e\u0442 \u043e\u0442\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0432\u0440\u0430\u0433\u043e\u0432 \u0434\u043e \u0440\u0430\u0437\u0432\u044f\u0437\u044b\u0432\u0430\u043d\u0438\u044f \u044f\u0440\u043e\u0441\u0442\u0438 \u043f\u043e\u0433\u043e\u0434\u044b. \u0417\u0435\u043c\u043d\u044b\u0435 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442 \u0441\u043e\u0431\u043e\u0439 \u0441\u043c\u0435\u0441\u044c \u0430\u0442\u0430\u043a\u0438, \u0437\u0430\u0449\u0438\u0442\u044b, \u0438 \u043f\u043e\u043b\u0435\u0437\u043d\u043e\u0441\u0442\u0438.\n", + + "#colour_sorcery\u041a\u043e\u043b\u0434\u043e\u0432\u0441\u0442\u0432\u043e#colour_reset \\n\u041a\u043e\u043b\u0434\u043e\u0432\u0441\u0442\u0432\u043e - \u044d\u0442\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u0441\u0438\u043b\u044b \u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f. \u041a\u043e\u043b\u0434\u0443\u043d\u044b \u043c\u0430\u043d\u0438\u043f\u0443\u043b\u0438\u0440\u0443\u044e\u0442 \u0441\u0432\u0435\u0442\u043e\u043c, \u0433\u0440\u0430\u0432\u0438\u0442\u0430\u0446\u0438\u0435\u0439 \u0438 \u0434\u0430\u0436\u0435 \u0441\u0430\u043c\u043e\u0439 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c\u044e, \u0447\u0442\u043e\u0431\u044b \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0438\u0445 \u043f\u043e\u0442\u0440\u0435\u0431\u043d\u043e\u0441\u0442\u044f\u043c. \u041a\u043e\u043b\u0434\u043e\u0432\u0441\u043a\u0438\u0435 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f \u043c\u043e\u0433\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f, \u043f\u043e\u043c\u0438\u043c\u043e \u0432\u0441\u0435\u0433\u043e \u043f\u0440\u043e\u0447\u0435\u0433\u043e, \u0434\u043b\u044f \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043c\u0430\u0433\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0441\u0438\u043b \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u0442\u0435\u043b\u044e \u0438\u043b\u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u044f \u043f\u0440\u0435\u0434\u043c\u0435\u0442\u043e\u0432 \u043f\u043e \u0438\u0445 \u0436\u0435\u043b\u0430\u043d\u0438\u044e.\n", + + "#colour_healing\u0418\u0441\u0446\u0435\u043b\u0435\u043d\u0438\u0435#colour_reset \\n\u042d\u043b\u0435\u043c\u0435\u043d\u0442 \u0438\u0441\u0446\u0435\u043b\u0435\u043d\u0438\u044f \u0441\u0432\u044f\u0437\u0430\u043d \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u0438 \u0440\u0435\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0435\u0439. \u0426\u0435\u043b\u0438\u0442\u0435\u043b\u0438 \u0441\u0442\u0430\u0440\u0430\u044e\u0442\u0441\u044f \u043a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u0431\u043e\u043b\u044c\u0448\u0435 \u0437\u0430\u0449\u0438\u0442\u0438\u0442\u044c \u0441\u0435\u0431\u044f \u0438 \u0441\u0432\u043e\u0438\u0445 \u0441\u043e\u044e\u0437\u043d\u0438\u043a\u043e\u0432 \u0438 \u043a\u0430\u043a \u0442\u0430\u043a\u043e\u0432\u044b\u0435 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u043e\u0447\u0435\u043d\u044c \u0442\u0440\u0443\u0434\u043d\u044b\u043c\u0438 \u043f\u0440\u043e\u0442\u0438\u0432\u043d\u0438\u043a\u0430\u043c\u0438. \u0425\u043e\u0442\u044f \u043e\u0431\u044b\u0447\u043d\u043e \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0430\u0442\u0430\u043a\u0438, \u043e\u0447\u0438\u0449\u0430\u044e\u0449\u0438\u0439 \u0441\u0432\u0435\u0442 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0438\u0441\u0446\u0435\u043b\u044f\u044e\u0449\u0438\u0445 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439 \u043d\u0430\u043d\u0435\u0441\u0435\u0442 \u0437\u043d\u0430\u0447\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0443\u0440\u043e\u043d \u043d\u0435\u0436\u0438\u0442\u0438. \u0418\u0441\u0446\u0435\u043b\u044f\u044e\u0449\u0438\u0435 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f \u043d\u0435\u0437\u0430\u043c\u0435\u043d\u0438\u043c\u044b \u0432 \u0431\u043e\u044e.\n", + + "\u0413\u0440\u0430\u043d\u0438\u0446\u044b \u043c\u0435\u0436\u0434\u0443 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u043c\u0438 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u043c\u0438 \u043d\u0435 \u0432\u0441\u0435\u0433\u0434\u0430 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b, \u0438 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f \u043d\u0435\u0441\u0443\u0442 \u0432 \u0441\u0435\u0431\u0435 \u0447\u0435\u0440\u0442\u044b \u0434\u0440\u0443\u0433\u0438\u0445 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432, \u043e\u0442\u043b\u0438\u0447\u043d\u044b\u0445 \u043e\u0442 \u0438\u0445 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0445.\n", + + "\u0415\u0441\u043b\u0438 \u0412\u0430\u043c \u043f\u043e\u0432\u0435\u0437\u0435\u0442, \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u043e \u043d\u0430\u0442\u043a\u043d\u0443\u0442\u044c\u0441\u044f \u043d\u0430 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u0439 \u041a\u0440\u0438\u0441\u0442\u0430\u043b\u043b. \u042d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u043a\u0440\u0438\u0441\u0442\u0430\u043b\u043b\u044b - \u044d\u0442\u043e \u043c\u0430\u0433\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043a\u0440\u0438\u0441\u0442\u0430\u043b\u043b\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u043e\u0433\u043b\u043e\u0449\u0430\u044e\u0442 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u043f\u043e \u043c\u0435\u0440\u0435 \u0438\u0445 \u0440\u043e\u0441\u0442\u0430, \u0445\u043e\u0442\u044f \u0442\u043e\u0447\u043d\u044b\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0443\u0441\u043b\u043e\u0432\u0438\u044f \u0438 \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c, \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u044d\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442, \u043e\u0441\u0442\u0430\u044e\u0442\u0441\u044f \u0437\u0430\u0433\u0430\u0434\u043a\u043e\u0439. \u041e\u0434\u043d\u0430\u043a\u043e \u043c\u044b \u0437\u043d\u0430\u0435\u043c, \u0447\u0442\u043e \u0442\u0430\u043a\u0438\u0435 \u043a\u0440\u0438\u0441\u0442\u0430\u043b\u043b\u044b \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u044b \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u0445 \u0436\u0435\u0437\u043b\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0442 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f\u043c \u043d\u0443\u0436\u043d\u043e\u0433\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u0431\u044b\u0442\u044c \u0431\u0440\u043e\u0448\u0435\u043d\u043d\u044b\u043c\u0438 \u0441 \u0431\u043e\u043b\u044c\u0448\u0435\u0439 \u0441\u0438\u043b\u043e\u0439, \u0447\u0435\u043c \u043e\u0431\u044b\u0447\u043d\u043e.\n" + ] + }, + + "miscellaneous": { + "title": "\u041f\u0440\u043e\u0447\u0435\u0435\n", + "include_in_contents": "main_contents", + "contents": { + "id": "miscellaneous_subsections", + "hyperlinks": true, + "page_numbers": true, + "separator": "." + }, + "triggers": [ + "ebwizardry:on_subsection_unlock" + ], + "sections": { + "mana_flasks": { + "title": "\u041a\u043e\u043b\u0431\u044b \u041c\u0430\u043d\u044b\n", + "include_in_contents": "miscellaneous_subsections", + "triggers": [ + "ebwizardry:handbook/mana_flasks" + ], + "text": [ + + "Arkendur'S Arcane Supplies Co. - \u0432\u043e\u043b\u0448\u0435\u0431\u043d\u044b\u0435 \u043f\u0440\u0435\u0434\u043c\u0435\u0442\u044b \u0434\u043b\u044f \u043b\u044e\u0431\u043e\u0439 \u0432\u0430\u0448\u0435\u0439 \u043d\u0430\u0434\u043e\u0431\u043d\u043e\u0441\u0442\u0438!\n", + + "\u041d\u0443\u0436\u043d\u043e \u043f\u043e\u0434\u0437\u0430\u0440\u044f\u0434\u0438\u0442\u044c \u0441\u0432\u043e\u0439 @wands \u0436\u0435\u0437\u043b@ \u043d\u0430 \u0445\u043e\u0434\u0443? \u041d\u0438\u043a\u0430\u043a\u0438\u0445 \u043f\u0440\u043e\u0431\u043b\u0435\u043c! @mana \u041c\u0430\u043d\u0443@ \u0442\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u043d\u043e \u0440\u0430\u0437\u043b\u0438\u0432\u0430\u0442\u044c \u043f\u043e \u0431\u0443\u0442\u044b\u043b\u043a\u0430\u043c. \u041f\u0440\u043e\u0441\u0442\u043e \u0441\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u043a\u043e\u043b\u0431\u0443 \u043c\u0430\u043d\u044b \u0441\u043e \u0441\u0442\u0435\u043a\u043b\u044f\u043d\u043d\u043e\u0439 \u0431\u0443\u0442\u044b\u043b\u043a\u043e\u0439 \u0438 \u0432\u043e\u0441\u0435\u043c\u044c\u044e \u043c\u0430\u0433\u0438\u0447\u0435\u0441\u043a\u0438\u043c\u0438 \u043a\u0440\u0438\u0441\u0442\u0430\u043b\u043b\u0430\u043c\u0438 \u0438 \u0432\u043e\u0437\u044c\u043c\u0438\u0442\u0435 \u0435\u0435 \u0441 \u0441\u043e\u0431\u043e\u0439. \u041a\u043e\u0433\u0434\u0430 \u0432\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0435\u0433\u043e, \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0435\u0451 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0441\u0432\u043e\u0435\u0433\u043e \u0436\u0435\u0437\u043b\u0430, \u0438 \u043e\u043d \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442 \u0447\u0430\u0441\u0442\u044c \u0437\u0430\u0440\u044f\u0434\u0430. *\n", + + "#recipe medium_mana_flask", + + "\u041d\u043e\u0432\u0438\u043d\u043a\u0430! \u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u043e \u043d\u043e\u0432\u044b\u0435 \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u0435 \u0438 \u0431\u043e\u043b\u044c\u0448\u0438\u0435 \u043a\u043e\u043b\u0431\u044b \u043c\u0430\u043d\u044b - \u0442\u0435\u043f\u0435\u0440\u044c \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u044b\u0431\u0440\u0430\u0442\u044c \u0440\u0430\u0437\u043c\u0435\u0440 \u043a\u043e\u043b\u0431\u044b \u043c\u0430\u043d\u044b \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0438 \u0441 \u0432\u0430\u0448\u0438\u043c\u0438 \u043f\u043e\u0442\u0440\u0435\u0431\u043d\u043e\u0441\u0442\u044f\u043c\u0438!\n", + + "#recipe small_mana_flask", + "#recipe large_mana_flask", + + "* \u042d\u0442\u043e\u0442 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0440\u0430\u0437\u0440\u0443\u0448\u0438\u0442 \u0431\u0443\u0442\u044b\u043b\u0435\u043a. \u0427\u0430\u0441\u0442\u044c \u043c\u0430\u043d\u044b \u0442\u0435\u0440\u044f\u0435\u0442\u0441\u044f \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0440\u043e\u0437\u043b\u0438\u0432\u0430.\n" + ] + }, + "throwable_items": { + "title": "\u0411\u0440\u043e\u0441\u0430\u0435\u043c\u044b\u0435 \u043f\u0440\u0435\u0434\u043c\u0435\u0442\u044b\n", + "include_in_contents": "miscellaneous_subsections", + "triggers": [ + "ebwizardry:handbook/throwables" + ], + "text": [ + + "\u0420\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 @spells \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u044f@ \u0432\u044b\u0437\u044b\u0432\u0430\u044e\u0442 \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043f\u0440\u0435\u0434\u043c\u0435\u0442\u044b, \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0438\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0441\u043e\u0437\u0434\u0430\u043d\u044b \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e. \u041e\u0433\u043d\u0435\u043d\u043d\u044b\u0435 \u0431\u043e\u043c\u0431\u044b, \u043e\u0442\u0440\u0430\u0432\u043b\u044f\u044e\u0449\u0438\u0435 \u0431\u043e\u043c\u0431\u044b, \u0438\u0441\u043a\u0440\u043e\u0432\u044b\u0435 \u0431\u043e\u043c\u0431\u044b \u0438 \u0434\u044b\u043c\u043e\u0432\u044b\u0435 \u0431\u043e\u043c\u0431\u044b - \u0432\u0441\u0435 \u044d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0438\u0437\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c:\n", + + "#recipe firebomb", + "#recipe poison_bomb", + "#recipe smoke_bomb", + "#recipe spark_bomb" + ] + }, + "automated_casting": { + "title": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0435 \u041a\u0430\u0441\u0442\u043e\u0432\u0430\u043d\u0438\u0435\n", + "include_in_contents": "miscellaneous_subsections", + "triggers": [ + "ebwizardry:enchant_scroll" + ], + "text": [ + + "\u041d\u0435\u0434\u0430\u0432\u043d\u0438\u0435 \u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u044b \u043f\u043e\u043a\u0430\u0437\u0430\u043b\u0438, \u0447\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u0440\u0438\u0437\u044b\u0432 @spells \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439@, \u0432 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0439 \u0441\u0442\u0435\u043f\u0435\u043d\u0438, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u043d\u0435 \u0431\u043e\u043b\u0435\u0435 \u0447\u0435\u043c \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0434\u0438\u0441\u043f\u0435\u043d\u0441\u0435\u0440. \u041d\u0438\u043a\u0442\u043e \u0442\u043e\u0447\u043d\u043e \u043d\u0435 \u0437\u043d\u0430\u0435\u0442, \u043f\u043e\u0447\u0435\u043c\u0443, \u043d\u043e, \u043f\u043e\u0445\u043e\u0436\u0435, \u0441\u0442\u0440\u0430\u043d\u043d\u044b\u0435 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0440\u0435\u0434\u0441\u0442\u043e\u0443\u043d\u0430 \u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u044f\u044e\u0442\u0441\u044f \u0434\u0430\u0436\u0435 \u043d\u0430 \u0430\u043a\u0442\u0438\u0432\u0430\u0446\u0438\u044e \u0441\u0432\u0438\u0442\u043a\u043e\u0432 \u0437\u0430\u043a\u043b\u0438\u043d\u0430\u043d\u0438\u0439. \u041f\u043e\u043c\u0435\u0449\u0430\u044f \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0448\u0442\u0443\u043a \u0432 \u0434\u043e\u0437\u0430\u0442\u043e\u0440 \u0438 \u0437\u0430\u043f\u0438\u0442\u044b\u0432\u0430\u044f \u0435\u0433\u043e, \u0443 \u0432\u0430\u0441 \u0434\u043e\u043b\u0436\u0435\u043d \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0441\u044f \u0442\u0440\u044e\u043a...\n" + ] + } + } + }, + + "crafting_recipes": { + "title": "\u0420\u0435\u0446\u0435\u043f\u0442\u044b \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u044f\n", + "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": "\u0410\u0432\u0442\u043e\u0440\u044b\n", + "include_in_contents": "main_contents", + "text": [ + + "Electroblob's Wizardry \n\u0412\u0435\u0440\u0441\u0438\u044f\n #version \n\u0414\u043b\u044f Minecraft\n #mcversion", + + "\u0420\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u043d\u043e, \u0437\u0430\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e, \u0438 \u0437\u0430\u0442\u0435\u043a\u0441\u0442\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e Electroblob'\u043e\u043c\n", + + "\u0421\u043f\u0430\u0441\u0438\u0431\u043e Minecraft Forge \u0438 MCP, \u0431\u0435\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u044d\u0442\u043e\u0442 \u043c\u043e\u0434 \u0431\u044b\u043b \u0431\u044b \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u0435\u043d.\n", + + "\u0421\u043f\u0430\u0441\u0438\u0431\u043e \u0442\u0430\u043a\u0436\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u0443 Minecraft modding, \u0443 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0432\u0441\u0435\u0433\u0434\u0430 \u0435\u0441\u0442\u044c \u043e\u0442\u0432\u0435\u0442 \u043d\u0430 \u043c\u043e\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441 \u043c\u043e\u0434\u0434\u0438\u043d\u0433\u043e\u043c!\n", + + "\u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u044f \u0445\u043e\u0442\u0435\u043b \u0431\u044b \u043f\u043e\u0431\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u0438\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u043b\u044e\u0434\u0435\u0439 \u0437\u0430 \u0438\u0445 \u0432\u043a\u043b\u0430\u0434 \u0432 \u043c\u043e\u0434:\n", + + "\u041a\u043e\u0434:\n", + + "- Corail31 \n- 12foo \n- Shadows-of-Fire \n- HellFirePvP \n- Tora-B \n- Avatair \n- Aeronica", + + "\u041f\u0435\u0440\u0435\u0432\u043e\u0434:\n", + + "- Spanish: MadWrist, Alsentar \n- Mexican Spanish: MadWrist \n- \u0420\u0443\u0441\u0441\u043a\u0438\u0439: MugGod\n \n- French: Hahdrim \n- Brazilian Portuguese: lorrampi \n- Chinese: ZHENGLOC, dragon-evol, Hokorizero, TUsama \n- Korean: shejery, rewi_wire \n- Polish: Trozuu \n-German: BirdyDragon", + + "\u0417\u0432\u0443\u043a\u043e\u0432\u043e\u0439 \u044d\u0444\u0444\u0435\u043a\u0442 \u0421\u0432\u0435\u0442\u043e\u0432\u043e\u0433\u043e \u043b\u0443\u0447\u0430 \u043e\u0442 Ohh Wow Productions", + + "\u0414\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u044c\u0448\u0435\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0435\u0439 \u043f\u043e\u0441\u0435\u0442\u0438\u0442\u0435\n @https://github.com/Electroblob77/Wizardry/wiki wiki@.", + + "\u041d\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442 \u0432\u043e\u043b\u0448\u0435\u0431\u0441\u0442\u0432\u0430? \u041f\u0440\u0438\u0441\u043e\u0435\u0434\u0438\u043d\u044f\u0439\u0442\u0435\u0441\u044c \u043a \u043d\u0430\u043c \u0432 @https://discord.gg/MTmMzMv \u0414\u0438\u0441\u043a\u043e\u0440\u0434 \u0441\u0435\u0440\u0432\u0435\u0440@ \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0445 \u043d\u043e\u0432\u043e\u0441\u0442\u0435\u0439, \u043e\u0431\u0441\u0443\u0436\u0434\u0435\u043d\u0438\u0439 \u0438 \u043f\u0440\u043e\u0447\u0435\u0433\u043e!\n" + ] + } + + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/texts/handbook_zh_tw.json b/src/main/resources/assets/ebwizardry/texts/handbook_zh_tw.json index ae708a34..482271d2 100644 --- a/src/main/resources/assets/ebwizardry/texts/handbook_zh_tw.json +++ b/src/main/resources/assets/ebwizardry/texts/handbook_zh_tw.json @@ -11,16 +11,16 @@ "images": { "workbench": { - "location": "ebwizardry:textures/gui/arcane_workbench_picture.png", - "caption": "法術工作台", + "location": "ebwizardry:textures/gui/arcane_workbench.png", + "caption": "�k�N�u�@�x", "u": 0, "v": 0, "width": 110, "height": 110 }, "crystal_ore": { - "location": "ebwizardry:textures/gui/ore_picture.png", - "caption": "水晶礦", + "location": "ebwizardry:textures/gui/crystal_ore.png", + "caption": "�����q", "u": 0, "v": 0, "width": 64, @@ -28,7 +28,7 @@ }, "magic_crystal": { "location": "ebwizardry:textures/items/crystal_magic.png", - "caption": "魔法水晶", + "caption": "�]�k����", "u": 6, "v": 6, "width": 36, @@ -39,7 +39,7 @@ }, "magic_wand": { "location": "ebwizardry:textures/items/wand_novice.png", - "caption": "法杖", + "caption": "�k��", "u": 0, "v": 0, "width": 64, @@ -47,48 +47,48 @@ "border": false }, "crystal_flower": { - "location": "ebwizardry:textures/gui/flower_picture.png", - "caption": "水晶花", + "location": "ebwizardry:textures/gui/crystal_flower.png", + "caption": "������", "u": 0, "v": 0, "width": 48, "height": 48 }, "wizard_tower": { - "location": "ebwizardry:textures/gui/tower_picture.png", - "caption": "法師塔", + "location": "ebwizardry:textures/gui/wizard_tower.png", + "caption": "�k�v��", "u": 0, "v": 0, "width": 110, "height": 110 }, "wizard_armour": { - "location": "ebwizardry:textures/gui/armour_picture.png", - "caption": "法師服", + "location": "ebwizardry:textures/gui/wizard_armour.png", + "caption": "�k�v�A", "u": 0, "v": 0, "width": 90, "height": 90 }, "obelisk": { - "location": "ebwizardry:textures/gui/obelisk_picture.png", - "caption": "方尖碑", + "location": "ebwizardry:textures/gui/obelisk.png", + "caption": "��y�O", "u": 0, "v": 0, "width": 110, "height": 67 }, "shrine": { - "location": "ebwizardry:textures/gui/shrine_picture.png", - "caption": "神殿", + "location": "ebwizardry:textures/gui/shrine.png", + "caption": "����", "u": 0, "v": 0, "width": 110, "height": 67 }, "tiers": { - "location": "ebwizardry:textures/gui/tiers_picture.png", - "caption": "四種等級的法杖", + "location": "ebwizardry:textures/gui/tiers.png", + "caption": "�|�ص��Ū��k��", "u": 0, "v": 0, "width": 96, @@ -96,8 +96,8 @@ "border": false }, "elements": { - "location": "ebwizardry:textures/gui/elements_picture.png", - "caption": "七種法術元素", + "location": "ebwizardry:textures/gui/elements.png", + "caption": "�C�تk�N����", "u": 0, "v": 0, "width": 106, @@ -236,26 +236,26 @@ }, "text": [ - "法師手冊", + "�k�v��U", - "作者: Electroblob\n\n翻譯: Grant" + "�@��: Electroblob" ] }, "introduction": { - "title": "前言", + "title": "�e��", "text": [ - "你好啊,法師!\n\n本書介紹許多法術相關事物及使用方式。 這不是一本普通的書,其內容會隨著你在魔法世界的探索而逐漸呈現。", + "�A�n�ڡA�k�v�I\n\n���Ѥ��г\�h�k�N�����ƪ��ΨϥΤ覡�C �o���O�@�����q���ѡA�䤺�e�|�H�ۧA�b�]�k�@�ɪ������ӳv���e�{�C", - "使用箭頭按鈕來翻頁,按下雙箭頭按鈕則可快速的切換章節,而中間的選單按鈕則是翻回主目錄頁面。", + "�ϥνb�Y���s��½���A���U���b�Y���s�h�i�ֳt���������`�A�Ӥ����������s�h�O½�^�D�ؿ������C", - "點擊任何紫色連結文字直接跳至相關頁面。對書籤點擊滑鼠右鍵可將書籤移到目前這頁,對書籤點擊滑鼠左鍵則可翻回書籤那頁。" + "�I�����󵵦�s����r�������ܬ��������C������I���ƹ��k��i�N���Ҳ���ثe�o���A������I���ƹ�����h�i½�^���Ҩ����C" ] }, "main_contents": { - "title": "目錄", + "title": "�ؿ�", "contents": { "id": "main_contents", "hyperlinks": true, @@ -265,90 +265,90 @@ }, "setting_up": { - "title": "準備", + "title": "�dz�", "include_in_contents": "main_contents", "text": [ - "要開始使用法術,你需要準備:", + "�n�}�l�ϥΪk�N�A�A�ݭn�dz�:", - "》法杖, 請依圖示合成:", + "�n�k���A �Ш̹ϥܦX��:", "#recipe magic_wand", - "\n\n》@arcane_workbench 法術工作台@, 合成方式如圖:", + "\n\n�n@arcane_workbench �k�N�u�@�x@�A �X���覡�p��:", "#recipe arcane_workbench", - "》@spells 法術書@\n這個可以在箱子裡找到,或從怪物身上掉落,或向法師購買。\n\n\n\n不然你也可以自己合成一個\n初學者法術【魔法彈】,\n如圖所示:", + "�n@spells �k�N��@\n�o�ӥi�H�b�c�l�̧��A�αq�Ǫ����W�����A�ΦV�k�v�ʶR�C\n\n\n\n���M�A�]�i�H�ۤv�X���@��\n��Ǫ̪k�N�i�]�k�u�j�A\n�p�ϩҥ�:", "#recipe magic_missile_spell_book", - "你還需要準備一大堆魔法水晶\n來為法杖供應@mana 法力@。" + "�A�ٻݭn�dzƤ@�j���]�k����\n�Ӭ��k������@mana �k�O@�C" ] }, "mana": { - "title": "法力", + "title": "�k�O", "include_in_contents": "main_contents", "triggers": [ "ebwizardry:crystal" ], "text": [ - "法力是賦予法師力量的魔法能量,其本身並非實體的物質,反而是可滲入於萬物之中的永恆能量。特別的是,法力蘊藏在於地下岩石中所找到的天然水晶內。\n@wands 法杖@可儲存法力,並在施法時\n將法力引導到@spells 法術@上。\n不同的法術會依其力量強度及持續時間消耗不同的法力值。", + "�k�O�O�ᤩ�k�v�O�q���]�k��q�A�䥻���ëD���骺����A�ϦӬO�i���J��U���������ë��q�C�S�O���O�A�k�Oĭ�æb��a�U���ۤ��ҧ�쪺�ѵM�������C\n@wands �k��@�i�x�s�k�O�A�æb�I�k��\n�N�k�O�޾ɨ�@spells �k�N@�W�C\n���P���k�N�|�̨�O�q�j�פΫ���ɶ����Ӥ��P���k�O�ȡC", "#image crystal_ore", "#image magic_crystal", - "人們普遍認為法力無法被創造或消滅,每當施展法術時,其引導的法力就會散失到周圍環境中。事實上這就是大量的法力散布於世界各處的原因。總之無論要如何使用法力,必須從地底下的天然千年水晶中,或本書未涵蓋的高級人工方式將其收集。還有各種方法使法術更有效的使用法力,以及回收在施法過程中逸散的部分法力。" + "�H�̴��M�{���k�O�L�k�Q�гy�ή����A�C��I�i�k�N�ɡA��޾ɪ��k�O�N�|������P�����Ҥ��C�ƹ�W�o�N�O�j�q���k�O������@�ɦU�B����]�C�`���L�׭n�p��ϥΪk�O�A�����q�a���U���ѵM�d�~�������A�Υ��ѥ��[�\�����ŤH�u�覡�N�䦬���C�٦��U�ؤ�k�Ϫk�N�󦳮Ī��ϥΪk�O�A�H�Φ^���b�I�k�L�{���h���������k�O�C" ] }, "wands": { - "title": "法杖", + "title": "�k��", "include_in_contents": "main_contents", "triggers": [ "ebwizardry:arcane_initiate" ], "text": [ - "法杖是法師首選的器具。\n你能用它來施展任何法杖能力\n所及的@spells 法術@(請參閱@tiers 等級@章節)\n。\n法杖的種類很多,不過當然都得從基礎法杖開始。", + "�k���O�k�v���諸����C\n�A��Υ��ӬI�i����k����O\n�ҤΪ�@spells �k�N@(�аѾ\@tiers ����@���`)\n�C\n�k���������ܦh�A���L��M���o�q��¦�k���}�l�C", "#image magic_wand", - "大多數的法杖都誕生於一個簡單的製作方式。\n準備一顆某種類型的魔法水晶,\n將水晶裝在木棒的一端,\n再於另一端黏上一顆金粒。\n水晶自然是法杖力量的源頭,\n因為它提供了引導@spells 法術@所需的\n焦點。而其餘的部分也很重要,不只是因為提供了握住法杖的地方,更是指引@mana 法力@前進的路徑\n。", + "�j�h�ƪ��k�����ϥͩ�@��²�檺�s�@�覡�C\n�dzƤ@���Y���������]�k�����A\n�N�����˦b��Ϊ��@�ݡA\n�A��t�@���H�W�@�����ɡC\n�����۵M�O�k���O�q�����Y�A\n�]�������ѤF�޾�@spells �k�N@�һݪ�\n�J�I�C�Ө�l�������]�ܭ��n�A���u�O�]�����ѤF����k�����a��A��O����@mana �k�O@�e�i�����|\n�C", - "隨著法杖施展了愈多法術,\n其變得愈能駕馭@spells 法術@,\n因此也能施展更強大的法術。\n這樣的成長可以從法杖外觀上的細微變化中發現,隨著法杖愈來愈強,水晶會變得更加明亮,\n顏色也可能會根據其@elements 元素@而\n產生變化。過了一段時間之後,法杖本身的木材也會開始扭曲生長成各種複雜的形狀。", + "�H�۪k���I�i�F�U�h�k�N�A\n���ܱo�U��r��@spells �k�N@�A\n�]���]��I�i��j�j���k�N�C\n�o�˪������i�H�q�k���~�[�W���ӷL�ܤƤ��o�{�A�H�۪k���U�ӷU�j�A�����|�ܱo��[���G�A\n�C��]�i��|�ھڨ�@elements ����@��\n�����ܤơC�L�F�@�q�ɶ�����A�k������������]�|�}�l�ᦱ�ͪ����U�ؽ������Ϊ��C", - "當你拿著法杖時,畫面的角落會顯示出目前法杖所選的法術及其圖示,而在施放法術之後,還會顯示冷卻條來表示直到下次施展所需等待的時間,它還會顯示法杖中的前一個和下一個法術名稱。按下《#next_spell_key 鍵》及《#previous_spell_key 鍵》可切換法術(這些都可在【選項】->【按鍵設定】中變更),你也可以在潛行時使用滑鼠滾輪來切換法術。", + "��A���۪k���ɡA�e���������|��ܥX�ثe�k���ҿ諸�k�N�Ψ�ϥܡA�Ӧb�I��k�N����A�ٷ|��ܧN�o���Ӫ�ܪ���U���I�i�һݵ��ݪ��ɶ��A���ٷ|��ܪk�������e�@�өM�U�@�Ӫk�N�W�١C���U�m#next_spell_key ��n�Ρm#previous_spell_key ��n�i�����k�N(�o�dz��i�b�i�ﶵ�j->�i����]�w�j���ܧ�)�A�A�]�i�H�b���ɨϥηƹ��u���Ӥ����k�N�C", - "當你將游標停留在物品欄的法杖上時,會顯示法杖中儲存的\n@mana 法力@,伴隨目前所選的@spells 法術@及其\n特殊能力。\n將法杖放入@arcane_workbench 法術工作台@則可\n看見更詳細的資訊。" + "��A�N��а��d�b���~�檺�k���W�ɡA�|��ܪk�����x�s��\n@mana �k�O@�A���H�ثe�ҿ諸@spells �k�N@�Ψ�\n�S���O�C\n�N�k����J@arcane_workbench �k�N�u�@�x@�h�i\n�ݨ���ԲӪ���T�C" ] }, "spells": { - "title": "法術", + "title": "�k�N", "include_in_contents": "main_contents", "triggers": [ "ebwizardry:handbook/spells" ], "text": [ - "沒有法術的@wands 法杖@沒有作用。\n法術的記錄形式有兩種:\n一種是永久性的書本,\n一種是暫時性的卷軸。", + "�S���k�N��@wands �k��@�S���@�ΡC\n�k�N���O���Φ������:\n�@�جO�ä[�ʪ��ѥ��A\n�@�جO�Ȯɩʪ����b�C", - "法術書本身無法使用,而是用於將書中記載的法術存入@wands 法杖@。\n在世界各地都能發現法術書,應該用不了多少時間你就能找到一本。當你找到後,可將游標停留在書上,看一下這本法術書的基本資訊,也可以拿著書點擊滑鼠右鍵以閱讀更詳細的資料。", + "�k�N�ѥ����L�k�ϥΡA�ӬO�Ω�N�Ѥ��O�����k�N�s�J@wands �k��@�C\n�b�@�ɦU�a����o�{�k�N�ѡA���ӥΤ��F�h�֮ɶ��A�N����@���C��A����A�i�N��а��d�b�ѤW�A�ݤ@�U�o���k�N�Ѫ��򥻸�T�A�]�i�H���ۮ��I���ƹ��k��H�\Ū��ԲӪ���ơC", - "相對而言卷軸則有不同的用途: 拿著卷軸點擊滑鼠右鍵便會施展上面的法術並用掉卷軸。跟法術書一樣,卷軸在世界各地都能找到,但你也可以自己@enchanting_scrolls 製作@。", + "�۹�Ө����b�h�����P���γ~: ���ۨ��b�I���ƹ��k��K�|�I�i�W�����k�N�åα����b�C��k�N�Ѥ@�ˡA���b�b�@�ɦU�a������A���A�]�i�H�ۤv@enchanting_scrolls �s�@@�C", - "在你準備進行某個任務時,有些法術可能只會用到一兩次,\n所以你不想讓那些法術占用\n你的@wands 法杖@空間,此時卷軸就非常\n有用。不過得注意,比起法杖,卷軸還是效率較差的施法方式。", + "�b�A�dzƶi��Y�ӥ��ȮɡA���Ǫk�N�i��u�|�Ψ�@�⦸�A\n�ҥH�A���Q�����Ǫk�N�e��\n�A��@wands �k��@�Ŷ��A���ɨ��b�N�D�`\n���ΡC���L�o�`�N�A��_�k���A���b�٬O�IJv���t���I�k�覡�C", - "未經訓練的眼睛無法閱讀法術中的魔法符文。然而若要鑑定一個法術,你當然可以冒著發生意外的風險來施展看看結果,但即便如此許多法術是要在特定的條件下才會發生作用。不過即便如此,還是有可能在施法時誤用了未鑑定的法術,其潛在的危險性取決於這個法術有多強大。因此更安全可靠的選擇是使用鑑定卷軸,可以在箱子裡找到或是向法師購買。拿著鑑定卷軸點擊滑鼠右鍵便會用掉卷軸並鑑定你快捷欄中第一個未知的法術書或卷軸。" + "���g�V�m�������L�k�\Ū�k�N�����]�k�Ť�C�M�ӭY�nŲ�w�@�Ӫk�N�A�A��M�i�H�_�۵o�ͷN�~�����I�ӬI�i�ݬݵ��G�A���Y�K�p���\�h�k�N�O�n�b�S�w������U�~�|�o�ͧ@�ΡC���L�Y�K�p���A�٬O���i��b�I�k�ɻ~�ΤF��Ų�w���k�N�A���b���M�I�ʨ��M��o�Ӫk�N���h�j�j�C�]����w���i�a����ܬO�ϥ�Ų�w���b�A�i�H�b�c�l�̧��άO�V�k�v�ʶR�C����Ų�w���b�I���ƹ��k��K�|�α����b��Ų�w�A�ֱ��椤�Ĥ@�ӥ������k�N�ѩΨ��b�C" ] }, "arcane_workbench": { - "title": "法術工作台", + "title": "�k�N�u�@�x", "include_in_contents": "main_contents", "contents": { "id": "arcane_workbench_subsections", @@ -361,81 +361,81 @@ ], "text": [ - "法術工作台看起來有點像附魔台, 用來升級你的@wands 法杖@、\n為其補充法力和存入@spells 法術@。\n\n其合成方式如下:", + "�k�N�u�@�x�ݰ_�Ӧ��I�����]�x�A �ΨӤɯŧA��@wands �k��@�B\n����ɥR�k�O�M�s�J@spells �k�N@�C\n\n��X���覡�p�U:", "#recipe arcane_workbench", - "接下來的頁面將說明法術工作台的各種功用。", + "���U�Ӫ������N�����k�N�u�@�x���U�إ\�ΡC", "#image workbench" ], "sections": { "binding_spells": { - "title": "抄寫法術", + "title": "�ۼg�k�N", "include_in_contents": "arcane_workbench_subsections", "triggers": [ "ebwizardry:handbook/arcane_workbench" ], "text": [ - "要將@spells 法術@存入你的@wands 法杖@,只要將\n法杖放入工作台中央的槽中,然後將法術書放入周圍其中一個槽並按下按鈕。此時你會注意到,在這個過程中,法術書並沒有受到任何影響,這是因為存入法術的動作,並不是將法術從法術書中取走,只是將法術抄寫至法杖中而已。因此你可以重複這個動作,盡量將想要的法術存入你的法杖。你一次最多只能存入五種法術到法杖中,不過可用協調升級來增加法術存量。" + "�n�N@spells �k�N@�s�J�A��@wands �k��@�A�u�n�N\n�k����J�u�@�x�������Ѥ��A�M��N�k�N�ѩ�J�P��䤤�@�ӼѨë��U���s�C���ɧA�|�`�N��A�b�o�ӹL�{���A�k�N�ѨèS���������v�T�A�o�O�]���s�J�k�N���ʧ@�A�ä��O�N�k�N�q�k�N�Ѥ������A�u�O�N�k�N�ۼg�ܪk�����Ӥw�C�]���A�i�H���Ƴo�Ӱʧ@�A�ɶq�N�Q�n���k�N�s�J�A���k���C�A�@���̦h�u��s�J���تk�N��k�����A���L�i�Ψ�դɯŨӼW�[�k�N�s�q�C" ] }, "charging_wands": { - "title": "法杖補充法力", + "title": "�k���ɥR�k�O", "include_in_contents": "arcane_workbench_subsections", "triggers": [ "ebwizardry:handbook/arcane_workbench" ], "text": [ - "要幫@wands 法杖@補充法力,先將法杖\n放入法術工作台中央的槽,並在左下方那個槽放入魔法水晶,然後按下按鈕。\n每個水晶相當於#mana_per_crystal點的@mana 法力@,\n而法杖只會在需要時用掉水晶,因此你可以將水晶留在工作台內,以便需要時供應。但是請記得,法杖只能取用整數單位的水晶,舉例來說,如果法杖只需要30點法力,那補充法力時會有#example_charging_loss點法力浪費掉。" + "�n��@wands �k��@�ɥR�k�O�A���N�k��\n��J�k�N�u�@�x�������ѡA�æb���U�診�Ӽѩ�J�]�k�����A�M����U���s�C\n�C�Ӥ����۷��#mana_per_crystal�I��@mana �k�O@�A\n�Ӫk���u�|�b�ݭn�ɥα������A�]���A�i�H�N�����d�b�u�@�x���A�H�K�ݭn�ɨ����C���O�аO�o�A�k���u����ξ�Ƴ�쪺�����A�|�Ҩӻ��A�p�G�k���u�ݭn30�I�k�O�A���ɥR�k�O�ɷ|��#example_charging_loss�I�k�O���O���C" ] }, "upgrading_wands": { - "title": "升級法杖", + "title": "�ɯŪk��", "include_in_contents": "arcane_workbench_subsections", "triggers": [ "ebwizardry:handbook/tome_of_arcana" ], "text": [ - "你需要對應@tiers 等級@的法術寶典來\n升級你的@wands 法杖@或進行特殊的法\n杖升級。這兩種東西都可從怪物的掉落物或向法師購買獲得。要升級法杖,先將法杖放入法術工作台中央的槽,並在右上方的槽放入升級物後按下按鈕。", + "�A�ݭn����@tiers ����@���k�N�_���\n�ɯŧA��@wands �k��@�ζi��S���k\n���ɯšC�o��تF�賣�i�q�Ǫ����������ΦV�k�v�ʶR��o�C�n�ɯŪk���A���N�k����J�k�N�u�@�x�������ѡA�æb�k�W�誺�ѩ�J�ɯŪ�����U���s�C", - "特殊的法杖升級可加強法杖某方面的能力,每種升級最高只能疊加三次,而法杖的總升級數取決於法杖的等級。升級後就無法移除。" + "�S���k���ɯťi�[�j�k���Y�譱����O�A�C�ؤɯų̰��u���|�[�T���A�Ӫk�����`�ɯżƨ��M��k�������šC�ɯū�N�L�k�����C" ] }, "enchanting_scrolls": { - "title": "製作卷軸", + "title": "�s�@���b", "include_in_contents": "arcane_workbench_subsections", "triggers": [ "ebwizardry:handbook/scrolls" ], "text": [ - "法術工作台也能用來將@spells 法術@\n存入卷軸。要製作卷軸,你需要先用一張紙及一些線來合成空白卷軸,然後選一本想要作成卷軸的法術書,最後準備足夠的魔法水晶以提供施展這個法術的@mana 法力@。", + "�k�N�u�@�x�]��ΨӱN@spells �k�N@\n�s�J���b�C�n�s�@���b�A�A�ݭn���Τ@�i�ȤΤ@�ǽu�ӦX���ťը��b�A�M���@���Q�n�@�����b���k�N�ѡA�̫�dzƨ������]�k�����H���ѬI�i�o�Ӫk�N��@mana �k�O@�C", "#recipe blank_scroll", - "先將水晶放入左下方的槽,再將空白卷軸放入中央的槽,然後將法術書放入上方單獨的槽,最後按下按鈕以製成法術卷軸。" + "���N������J���U�誺�ѡA�A�N�ťը��b��J�������ѡA�M��N�k�N�ѩ�J�W���W���ѡA�̫���U���s�H�s���k�N���b�C" ] } } }, "wizard_armour": { - "title": "法師服", + "title": "�k�v�A", "include_in_contents": "main_contents", "triggers": [ "ebwizardry:arcane_initiate" ], "text": [ - "身為一位法師,在與生物搏鬥時需要一些東西來保護自己。雖然也能使用一般的護甲,但是為了充分發揮你的法術,你還是需要一套法師服,包括帽子、長袍、褲子和鞋子。\n與一般護甲不同的是,法師服不會破損,其使用魔法能量來保護穿著的人。因此跟法杖一樣,需要魔法水晶來補充能量。不過幸好這些服裝不會消耗太多\n@mana 法力@。\n法師服補充能量的方式也跟法杖一樣,可以用@arcane_workbench 法術工作台@或\n@mana_flasks 法力瓶@來補充,但是必須當心,\n如果法師服的法力耗盡了,你會發現自己毫無防禦力。", + "�����@��k�v�A�b�P�ͪ��i���ɻݭn�@�ǪF��ӫO�@�ۤv�C���M�]��ϥΤ@�몺�@�ҡA���O���F�R���o���A���k�N�A�A�٬O�ݭn�@�M�k�v�A�A�]�A�U�l�B���T�B�Ǥl�M�c�l�C\n�P�@���@�Ҥ��P���O�A�k�v�A���|�}�l�A��ϥ��]�k��q�ӫO�@��۪��H�C�]����k���@�ˡA�ݭn�]�k�����ӸɥR��q�C���L���n�o�ǪA�ˤ��|���ӤӦh\n@mana �k�O@�C\n�k�v�A�ɥR��q���覡�]��k���@�ˡA�i�H��@arcane_workbench �k�N�u�@�x@��\n@mana_flasks �k�O�~@�ӸɥR�A���O������ߡA\n�p�G�k�v�A���k�O�ӺɤF�A�A�|�o�{�ۤv�@�L���m�O�C", "#image wizard_armour", - "你可以用魔法絲綢來製成法師服:", + "�A�i�H���]�k�����ӻs���k�v�A:", "#recipe magic_silk", "#recipe wizard_hat", @@ -443,16 +443,16 @@ "#recipe wizard_leggings", "#recipe wizard_boots", - "使用一般的附魔台就能為法師服附魔,而且由於魔法絲綢本身的特性,附魔後的效果特別好。對一個法師而言,最有用的是魔法、冰凍及衝擊三種防護附魔,有助於對抗敵人的魔法攻擊。", + "�ϥΤ@�몺���]�x�N�ର�k�v�A���]�A�ӥB�ѩ��]�k�����������S�ʡA���]�᪺�ĪG�S�O�n�C��@�Ӫk�v�Ө��A�̦��Ϊ��O�]�k�B�B��ν����T�ب��@���]�A���U���ܼĤH���]�k�����C", - "有些專注於特定元素的法師會穿著能加強該元素力量的特殊服裝,這種整套的服裝法師們都相當喜歡,但也不容易找到。", + "���DZM�`��S�w�������k�v�|��ۯ�[�j�Ӥ����O�q���S��A�ˡA�o�ؾ�M���A�˪k�v�̳��۷���w�A���]���e�����C", - "傳說中最強的法師曾經用一種法術印記來大大強化他們的服裝。" + "�ǻ����̱j���k�v���g�Τ@�تk�N�L�O�Ӥj�j�j�ƥL�̪��A�ˡC" ] }, "magical_world": { - "title": "魔法世界", + "title": "�]�k�@��", "include_in_contents": "main_contents", "contents": { "id": "magical_world_subsections", @@ -465,161 +465,161 @@ ], "text": [ - "只是待在家裡是無法成為法術大師的,到外頭探索充滿魔法的世界吧!\n若你勇於挑戰,古代遺址、遺物以及不管好的或壞的神秘生物,都等著你發現。\n最偉大的法師同時也最具好奇心,他們會不斷的探索周遭環境,並嘗試所發現的東西,來獲得豐富的知識及強大的力量。", + "�u�O�ݦb�a�̬O�L�k�����k�N�j�v���A��~�Y�����R���]�k���@�ɧa�I\n�Y�A�i��D�ԡA�j�N��}�B�򪫥H�Τ��ަn�����a�������ͪ��A�����ۧA�o�{�C\n�̰��j���k�v�P�ɤ]�̨�n�_�ߡA�L�̷|���_�������P�D���ҡA�ù��թҵo�{���F��A����o�״I�����Ѥαj�j���O�q�C", - "雖然法師不免大多的時間都是孤身一人,但知識的分享也是學習魔法藝術重要的一環。找尋其他法師,盡可能從他們身上學習更多知識,溝通才是一切最大的力量。" + "���M�k�v���K�j�h���ɶ����O�t���@�H�A�����Ѫ����ɤ]�O�Dz��]�k���N���n���@���C��M��L�k�v�A�ɥi��q�L�̨��W�Dzߧ�h���ѡA���q�~�O�@���̤j���O�q�C" ], "sections": { "crystal_flowers": { - "title": "水晶花", + "title": "������", "include_in_contents": "magical_world_subsections", "triggers": [ "ebwizardry:handbook/crystal_flowers" ], "text": [ - "在你的旅途當中,可能在野外會不時遇到奇怪的發光花朵,這些獨特的花稱為水晶花,它們具有聚集@mana 法力@的神奇能力,至今還不\n知道原因。不過我們卻知道這些水晶花,可經由採集製作以提取法力成為水晶,這使它們成為地面上相當有用的法力來源。然而由於這種花並不大,而且生長的土地都很小塊,因此這種方式所能獲得的法力其實很有限。", + "�b�A���ȳ~���A�i��b���~�|���ɹJ��_�Ǫ��o���ᦷ�A�o�ǿW�S����٬�������A���̨㦳�E��@mana �k�O@�����_��O�A�ܤ��٤�\n���D��]�C���L�ڭ̫o���D�o�Ǥ�����A�i�g�ѱĶ��s�@�H�����k�O���������A�o�ϥ��̦����a���W�۷��Ϊ��k�O�ӷ��C�M�ӥѩ�o�ت�ä��j�A�ӥB�ͪ����g�a���ܤp���A�]���o�ؤ覡�ү���o���k�O���ܦ����C", "#image crystal_flower", "#recipe crystal_flower_to_crystals" ] }, "wizard_towers": { - "title": "法師塔", + "title": "�k�v��", "include_in_contents": "magical_world_subsections", "triggers": [ "ebwizardry:wizard_tower" ], "text": [ - "害怕嗎?你並不是這個世界上唯一會使用魔法的人。\n在你的旅途中,你可能會遇到具有獨特尖頂或雙尖頂的高塔,這些是法師同伴居住的地方。\n隱居的生活使法師們有時候會有點古怪,但是他們通常還是友善的,而且只要你願意付出代價,還是會樂意與你分享他們的知識。\n在支付了貴重金屬及寶石後,你會收到許多驚奇的法術作為回報。", + "�`�ȶܡH�A�ä��O�o�ӥ@�ɤW�ߤ@�|�ϥ��]�k���H�C\n�b�A���ȳ~���A�A�i��|�J��㦳�W�S�y�������y��������A�o�ǬO�k�v�P��~���a��C\n���~���ͬ��Ϫk�v�̦��ɭԷ|���I�j�ǡA���O�L�̳q�`�٬O�͵����A�ӥB�u�n�A�@�N�I�X�N���A�٬O�|�ַN�P�A���ɥL�̪����ѡC\n�b��I�F�Q�����ݤ��_�۫�A�A�|����\�h��_���k�N�@���^���C", - "如果你想尋找的是@tiers 大師級@法術,\n那你得跟專門的人交談。", + "�p�G�A�Q�M�䪺�O@tiers �j�v��@�k�N�A\n���A�o��M�����H��͡C", "#image wizard_tower", - "不幸的是,有少數法師並不歡迎訪客。這些法師通常已被驅逐,所以對於任何闖入地盤的人都充滿敵意。\n接近這些傢伙要特別小心,並做好抵擋強大魔法的準備。只要擊敗他們,他們的知識就會成為你的了。", + "�������O�A���ּƪk�v�ä��w��X�ȡC�o�Ǫk�v�q�`�w�Q�X�v�A�ҥH���������J�a�L���H���R���ķN�C\n����o�dzå�n�S�O�p�ߡA�ð��n��ױj�j�]�k���dzơC�u�n���ѥL�̡A�L�̪����ѴN�|�����A���F�C", - "有一點必須知道,沒有法師會接受對他們攻擊或盜取他們東西的人,他們肯定不會善罷甘休。" + "���@�I�������D�A�S���k�v�|������L�̧����εs���L�̪F�誺�H�A�L�̪֩w���|���}�̥�C" ] }, "obelisks": { - "title": "方尖碑", + "title": "��y�O", "include_in_contents": "magical_world_subsections", "triggers": [ "ebwizardry:handbook/obelisks" ], "text": [ - "古老魔法的遺跡散落在世界各處,其中最著名的是刻有許多符號及符文的石頭結構。這些廢墟看起來像是某種古代文明的遺跡,無論建造它們的是誰或是什麼,顯然具有相當豐富的魔法知識,且用來在這些地方設置持續至今的防衛魔法。", + "�j���]�k����񴲸��b�@�ɦU�B�A�䤤�̵ۦW���O�観�\�h�Ÿ��βŤ媺���Y���c�C�o�Ǽo�V�ݰ_�ӹ��O�Y�إj�N��������A�L�׫سy���̪��O�֩άO����A��M�㦳�۷��״I���]�k���ѡA�B�ΨӦb�o�Ǧa��]�m����ܤ��������]�k�C", - "這些結構大致上分成兩種類型,第一種是較常見的方尖碑:\n以雕刻符文石堆成高高尖頂的結構,底部開著一塊空間,存放著過去所遺留的次等魔法物品。", + "�o�ǵ��c�j�P�W������������A�Ĥ@�جO���`������y�O:\n�H�J��Ť�۰令�����y�������c�A�����}�ۤ@���Ŷ��A�s��۹L�h�ҿ�d�������]�k���~�C", "#image obelisk", - "這些結構通常受到魔法保護,一旦有人靠近就會召喚出充滿敵意的@creatures 魔法生物@來攻擊人類。\n對抗這些生物並摧毀其魔法源頭,那些遺物就是你的了。" + "�o�ǵ��c�q�`�����]�k�O�@�A�@�����H�a��N�|�l��X�R���ķN��@creatures �]�k�ͪ�@�ӧ����H���C\n��ܳo�ǥͪ��úR�����]�k���Y�A���ǿ򪫴N�O�A���F�C" ] }, "shrines": { - "title": "神殿", + "title": "����", "include_in_contents": "magical_world_subsections", "triggers": [ "ebwizardry:visit_shrine" ], "text": [ - "第二種較為罕見的結構稱之為神殿,由一個中央基座及圍繞在四周的符文石柱所組成。基座上方擺著一個裝有古代@artefacts 神器@的\n寶箱,並通常施加了防護魔法鎖,以防他人竊取。整個結構還受到封鎖力場保護,使擅闖者無法逃離。", + "�ĤG�ظ����u�������c�٤��������A�Ѥ@�Ӥ�����y�γ�¶�b�|�P���Ť�۬W�Ҳզ��C��y�W���\�ۤ@�Ӹ˦��j�N@artefacts ����@��\n�_�c�A�óq�`�I�[�F���@�]�k��A�H���L�H�Ѩ��C��ӵ��c�٨������O���O�@�A�Ͼ����̵L�k�k���C", "#image shrine", - "由於這些神殿的重要意義及巨大的神秘力量,更別說裡面的寶物,對於志氣高昂的法師特別有吸引力。\n\n但是務必謹慎,有許多關於法師被關在封鎖區內,可能因為幽閉恐懼症導致慢慢發瘋的報導。且愈來愈多人認為,這就是神殿防護魔法的一個部分,而被關在其中的法師,其實是被控制用來守護神殿。這對於膽敢前往冒險的人來說,無疑是令人不寒而慄的結局..." + "�ѩ�o�ǯ��������n�N�q�Υ��j�������O�q�A��O���̭����_���A���Ӯ𰪩����k�v�S�O���l�ޤO�C\n\n���O�ȥ��ԷV�A���\�h����k�v�Q���b����Ϥ��A�i��]���ճ����߯g�ɭP�C�C�o�ƪ����ɡC�B�U�ӷU�h�H�{���A�o�N�O�������@�]�k���@�ӳ����A�ӳQ���b�䤤���k�v�A���O�Q����ΨӦu�@�����C�o����x���e���_�I���H�ӻ��A�L�ìO�O�H���H�ӷX������..." ] }, "creatures": { - "title": "魔法生物", + "title": "�]�k�ͪ�", "include_in_contents": "magical_world_subsections", "triggers": [ "ebwizardry:handbook/magical_creatures" ], "text": [ - "除了法師,還有許多其它生物也會使用魔法。其中有許多是源自於魔法,且大部分都能用特定的@spells 法術@召喚。會看到這些生物,\n大多是在守護@obelisks 方尖碑@,但也可能\n遇到了強大的召喚法師。只有非常少數的神秘生物才會偶爾在野外發現。" + "���F�k�v�A�٦��\�h�䥦�ͪ��]�|�ϥ��]�k�C�䤤���\�h�O���۩��]�k�A�B�j��������ίS�w��@spells �k�N@�l��C�|�ݨ�o�ǥͪ��A\n�j�h�O�b�u�@@obelisks ��y�O@�A���]�i��\n�J��F�j�j���l��k�v�C�u���D�`�ּƪ������ͪ��~�|�����b���~�o�{�C" ] }, "artefacts": { - "title": "神器", + "title": "����", "include_in_contents": "magical_world_subsections", "triggers": [ "ebwizardry:artefact" ], "text": [ - "在探索@shrines 神殿@時,你可能會好運的\n發現某種古代魔法神器,一種能夠賦予配戴者獨特而強大能力的物品。\n目前所知的類型有三種:\n為法術附加額外能力及效果的戒指,增強防禦力的項鍊,以及各種應用魔法的寶器。\n這些神器只是放入背包是沒有用的,要擺在身上的正確位置才會發生作用。" + "�b����@shrines ����@�ɡA�A�i��|�n�B��\n�o�{�Y�إj�N�]�k�����A�@�د���ᤩ�t���̿W�S�ӱj�j��O�����~�C\n�ثe�Ҫ����������T��:\n���k�N���[�B�~��O�ήĪG���٫��A�W�j���m�O������A�H�ΦU�������]�k���_���C\n�o�ǯ����u�O��J�I�]�O�S���Ϊ��A�n�\�b���W�����T��m�~�|�o�ͧ@�ΡC" ] } } }, "tiers": { - "title": "等級", + "title": "����", "include_in_contents": "main_contents", "triggers": [ "ebwizardry:handbook/tome_of_arcana" ], "text":[ - "@wands 法杖@和@spells 法術@都分成四個等級: #colour_novice基礎#colour_reset、#colour_apprentice學徒#colour_reset、#colour_advanced進階#colour_reset以及#colour_master大師#colour_reset。每個等級都比前一級還強。", + "@wands �k��@�M@spells �k�N@�������|�ӵ���: #colour_novice��¦#colour_reset�B#colour_apprentice�Ǯ{#colour_reset�B#colour_advanced�i��#colour_reset�H��#colour_master�j�v#colour_reset�C�C�ӵ��ų���e�@���ٱj�C", "#image tiers", - "只有#colour_novice基礎#colour_reset法杖能以合成的方式製作,其最多可儲存#novice_max_charge點法力,且只能施展基礎法術。基礎法術通常不會消耗太多法力,任何人都足以施展。但這並不表示它們相較於高等法術就毫無用處。即便你已經能施展大師級法術,某些基礎法術由於消耗法力低而仍然實用。", + "�u��#colour_novice��¦#colour_reset�k����H�X�����覡�s�@�A��̦h�i�x�s#novice_max_charge�I�k�O�A�B�u��I�i��¦�k�N�C��¦�k�N�q�`���|���ӤӦh�k�O�A����H�����H�I�i�C���o�ä���ܥ��̬۸��󰪵��k�N�N�@�L�γB�C�Y�K�A�w�g��I�i�j�v�Ūk�N�A�Y�ǰ�¦�k�N�ѩ���Ӫk�O�C�Ӥ��M��ΡC", - "#colour_apprentice學徒#colour_reset法杖是基礎法杖的下個等級,其最多可儲存#apprentice_max_charge點法力,並可施展基礎及學徒法術。學徒法術比基礎法術更強一些,但通常也消耗更多法力。", + "#colour_apprentice�Ǯ{#colour_reset�k���O��¦�k�����U�ӵ��šA��̦h�i�x�s#apprentice_max_charge�I�k�O�A�åi�I�i��¦�ξǮ{�k�N�C�Ǯ{�k�N���¦�k�N��j�@�ǡA���q�`�]���ӧ�h�k�O�C", - "#colour_advanced進階#colour_reset法杖非常稀有也更加強大,可施展大師級以外的所有法術,並可儲存#advanced_max_charge點法力。進階法術能夠重創敵人,並擁有超人般的能力,例如隱身術。", + "#colour_advanced�i��#colour_reset�k���D�`�}���]��[�j�j�A�i�I�i�j�v�ťH�~���Ҧ��k�N�A�åi�x�s#advanced_max_charge�I�k�O�C�i���k�N������мĤH�A�þ֦��W�H�몺��O�A�Ҧp�����N�C", - "#colour_master大師#colour_reset法杖是現存最強大的法杖,其最多可儲存#master_max_charge點法力,並可施展所有法術。大師級法術書相當罕見,且法術的威力能徹底毀滅的不僅是敵人,甚至包括整個世界。務必謹慎使用!", + "#colour_master�j�v#colour_reset�k���O�{�s�̱j�j���k���A��̦h�i�x�s#master_max_charge�I�k�O�A�åi�I�i�Ҧ��k�N�C�j�v�Ūk�N�Ѭ۷�u���A�B�k�N���¤O��������������ȬO�ĤH�A�Ʀܥ]�A��ӥ@�ɡC�ȥ��ԷV�ϥΡI", - "要提升至更高等級,首先法杖必須變得夠強大到足以引導該等級的法術。法杖會隨著施展法術而獲得成長,法術類型、法術力量、元素效果以及其它外部因素都可能會影響法杖的成長速度。", + "�n���ɦܧ󰪵��šA�����k�������ܱo���j�j�쨬�H�޾ɸӵ��Ū��k�N�C�k���|�H�۬I�i�k�N����o�����A�k�N�����B�k�N�O�q�B�����ĪG�H�Ψ䥦�~���]�����i��|�v�T�k���������t�סC", - "當法杖足夠強大,法術寶典為最終將法杖提升至下個等級的關鍵物品(請參閱@upgrading_wands 升級法杖@),\n一旦完成升級,法杖將再次朝下個等級開始成長。" + "��k�������j�j�A�k�N�_�嬰�̲ױN�k�����ɦܤU�ӵ��Ū����䪫�~(�аѾ\@upgrading_wands �ɯŪk��@)�A\n�@�������ɯšA�k���N�A���¤U�ӵ��Ŷ}�l�����C" ] }, "elements": { - "title": "元素", + "title": "����", "include_in_contents": "main_contents", "triggers": [ "ebwizardry:handbook/elements" ], "text": [ - "@spells 法術@各屬於不同的元素,代表\n法術的本質,且搭配使用對應的@wands 法杖@還會加強效果。", + "@spells �k�N@�U�ݩ󤣦P�������A�N��\n�k�N������A�B�f�t�ϥι�����@wands �k��@�ٷ|�[�j�ĪG�C", "#image elements", - "#colour_fire火#colour_reset \n火元素涉及燃燒、岩漿以及爆炸,大概是最具破壞性的元素。一個強大的烈焰法師可使敵人面臨煉獄般的火海,並可能在過程中毀掉這個世界。\n大部分的火系法術會讓目標著火,使他們持續受到傷害,但是對地獄的生物則沒什麼效果。", + "#colour_fire��#colour_reset \n�������A�οU�N�B���ߥH���z���A�j���O�̨�}�a�ʪ������C�@�ӱj�j���P�K�k�v�i�ϼĤH���{�Һ��몺�����A�åi��b�L�{�������o�ӥ@�ɡC\n�j���������t�k�N�|���ؼеۤ��A�ϥL�̫������ˮ`�A���O��a�����ͪ��h�S����ĪG�C", - "#colour_ice冰#colour_reset \n冰元素來自於所有冰冷的物質。冰系法術通常會讓敵人減速,甚至使他們完全凍結,對於火系生物特別有效。\n冰凍魔法在戰鬥之外也很有用,例如使水面結冰以過河。", + "#colour_ice�B#colour_reset \n�B�����Ӧ۩�Ҧ��B�N������C�B�t�k�N�q�`�|���ĤH��t�A�ƦܨϥL�̧����ᵲ�A�����t�ͪ��S�O���ġC\n�B���]�k�b�԰����~�]�ܦ��ΡA�Ҧp�Ϥ������B�H�L�e�C", - "\n#colour_lightning電#colour_reset \n這個元素涉及雷電、風暴以及天氣。決不可小看一個強大雷電法師的力量,有些甚至能隨意召喚閃電。\n雷電法術經常能一次傷害多個敵人,還會自動搜尋目標,對任何生物都能造成有效的攻擊...但是得當心苦力怕。", + "\n#colour_lightning�q#colour_reset \n�o�Ӥ����A�ιp�q�B���ɥH�ΤѮ�C�M���i�p�ݤ@�ӱj�j�p�q�k�v���O�q�A���ǬƦܯ��H�N�l��{�q�C\n�p�q�k�N�g�`��@���ˮ`�h�ӼĤH�A�ٷ|�۰ʷj�M�ؼСA�����ͪ�����y�����Ī�����...���O�o��߭W�O�ȡC", - "#colour_necromancy死靈#colour_reset \n死靈是黑暗、渾沌以及不死生物的元素。神秘的死靈法師往往被視為邪惡,儘管通常不是這樣的。\n死靈法術常召喚@creatures 生物@來為他\n戰鬥,或進而扭曲敵人的意志。", + "#colour_necromancy���F#colour_reset \n���F�O�·t�B���P�H�Τ����ͪ��������C���������F�k�v�����Q�������c�A���޳q�`���O�o�˪��C\n���F�k�N�`�l��@creatures �ͪ�@�Ӭ��L\n�԰��A�ζi�ӧᦱ�ĤH���N�ӡC", - "#colour_earth大地#colour_reset \n大地元素來自於動植物、風等自然界的的力量。大地魔法千變萬化,從毒害敵人到控制天氣,有各式各樣的形式。\n大地法術是綜合攻擊、防禦以及實用的法術。", + "#colour_earth�j�a#colour_reset \n�j�a�����Ӧ۩�ʴӪ��B�����۵M�ɪ����O�q�C�j�a�]�k�d�ܸU�ơA�q�r�`�ĤH�챱��Ѯ�A���U���U�˪��Φ��C\n�j�a�k�N�O��X�����B���m�H�ι�Ϊ��k�N�C", - "#colour_sorcery秘術#colour_reset \n秘術是力量與變化的元素。\n秘術法師能操縱光線、重力,甚至現實世界本身來符合他們的需求。\n且不提其它用途,秘術法術可用來提供施法者魔法力量,或是任意移動物體。", + "#colour_sorcery���N#colour_reset \n���N�O�O�q�P�ܤƪ������C\n���N�k�v����a���u�B���O�A�Ʀܲ{��@�ɥ����ӲŦX�L�̪��ݨD�C\n�B�����䥦�γ~�A���N�k�N�i�ΨӴ��ѬI�k���]�k�O�q�A�άO���N���ʪ���C", - "#colour_healing治療#colour_reset \n治療元素包括防禦和再生。\n治療法師會盡可能的保護自己及他們的同伴,因此可能會是非常難以對付的對手。\n治療法術在戰鬥中是不可或缺的,即便其通常不會用來進行攻擊,但是一些治療法術的淨化光芒會對不死生物造成相當大的傷害。", + "#colour_healing�v��#colour_reset \n�v�������]�A���m�M�A�͡C\n�v���k�v�|�ɥi�઺�O�@�ۤv�ΥL�̪��P��A�]���i��|�O�D�`���H��I�����C\n�v���k�N�b�԰����O���i�ίʪ��A�Y�K��q�`���|�ΨӶi������A���O�@�Ǫv���k�N���b�ƥ��~�|�藍���ͪ��y���۷�j���ˮ`�C", - "不同元素之間的分界並不總是那麼明確,有些法術除了所屬元素的性質外,也會具有其它元素的特性。", + "���P�������������ɨä��`�O������T�A���Ǫk�N���F���ݤ������ʽ�~�A�]�|�㦳�䥦�������S�ʡC", - "幸運的話,或許你能發現一種元素水晶,這是種在生長過程中吸收了元素的魔法水晶,不過實際上發生的方式及所需的條件仍然是個謎。但我們知道這種水晶能用來製作元素法杖,用來施展對應的法術,可比平常發揮更大的效果。" + "���B���ܡA�γ\�A��o�{�@�ؤ��������A�o�O�ئb�ͪ��L�{���l���F�������]�k�����A���L��ڤW�o�ͪ��覡�Ωһݪ����󤴵M�O�����C���ڭ̪��D�o�ؤ�����Ψӻs�@�����k���A�ΨӬI�i�������k�N�A�i�񥭱`�o����j���ĪG�C" ] }, "miscellaneous": { - "title": "其它", + "title": "�䥦", "include_in_contents": "main_contents", "contents": { "id": "miscellaneous_subsections", @@ -632,36 +632,36 @@ ], "sections": { "mana_flasks": { - "title": "法力瓶", + "title": "�k�O�~", "include_in_contents": "miscellaneous_subsections", "triggers": [ "ebwizardry:handbook/mana_flasks" ], "text": [ - "亞肯多魔法用品公司 - 為您所需的魔法物品!", + "�Ȫ֦h�]�k�Ϋ~���q - ���z�һݪ��]�k���~�I", - "需要在旅行中為你的@wands 法杖@補充\n法力嗎?\n沒問題! 現在@mana 法力@可以裝瓶。\n只要用一個玻璃瓶及八個魔法水晶合成出法力瓶,就可以隨身攜帶。要用的時候,只要將你的法杖與法力瓶合成,就能將法力存入法杖。(註)", + "�ݭn�b�Ȧ椤���A��@wands �k��@�ɥR\n�k�O�ܡH\n�S���D�I �{�b@mana �k�O@�i�H�˲~�C\n�u�n�Τ@�Ӭ����~�ΤK���]�k�����X���X�k�O�~�A�N�i�H�H����a�C�n�Ϊ��ɭԡA�u�n�N�A���k���P�k�O�~�X���A�N��N�k�O�s�J�k���C(��)", "#recipe medium_mana_flask", - "好消息! 即日起推出全新的小瓶裝及大瓶裝法力,可依您的需求選擇!", + "�n�����I �Y��_���X���s���p�~�ˤΤj�~�˪k�O�A�i�̱z���ݨD��ܡI", "#recipe small_mana_flask", "#recipe large_mana_flask", - "(註)過程中瓶子會用掉,且裝瓶時也會耗損一些法力。" + "(��)�L�{���~�l�|�α��A�B�˲~�ɤ]�|�ӷl�@�Ǫk�O�C" ] }, "throwable_items": { - "title": "投擲物", + "title": "���Y��", "include_in_contents": "miscellaneous_subsections", "triggers": [ "ebwizardry:handbook/throwables" ], "text": [ - "各種@spells 法術@的實體物,像是\n火焰彈、毒液彈、煙霧彈,還有電擊彈都能直接合成:", + "�U��@spells �k�N@�����骫�A���O\n���K�u�B�r�G�u�B�����u�A�٦��q���u���ઽ���X��:", "#recipe firebomb", "#recipe poison_bomb", @@ -670,21 +670,21 @@ ] }, "automated_casting": { - "title": "自動施法", + "title": "�۰ʬI�k", "include_in_contents": "miscellaneous_subsections", "triggers": [ "ebwizardry:enchant_scroll" ], "text": [ - "近期的實驗顯示,在某種程度上只需要簡單的發射器就能自動施展@spells 法術@。沒有人知道原因,\n但看起來似乎是紅石的神奇特性竟然能觸發法術卷軸。只要將一些卷軸放入發射器,然後提供能量,就完成了這個機關..." + "�����������ܡA�b�Y�ص{�פW�u�ݭn²�檺�o�g���N��۰ʬI�i@spells �k�N@�C�S���H���D��]�A\n���ݰ_�Ӧ��G�O���۪����_�S�ʳ��M��IJ�o�k�N���b�C�u�n�N�@�Ǩ��b��J�o�g���A�M�ᴣ�ѯ�q�A�N�����F�o�Ӿ���..." ] } } }, "crafting_recipes": { - "title": "合成表", + "title": "�X����", "include_in_contents": "main_contents", "text": [ @@ -716,33 +716,33 @@ }, "credits": { - "title": "製作群", + "title": "�s�@�s", "include_in_contents": "main_contents", "text": [ - "法術模組\n版本 #version\n支援 Minecraft #mcversion", + "�k�N�Ҳ�\n���� #version\n�䴩 Minecraft #mcversion", - "設計、程式及材質: Electroblob", + "�]�p�B�{���Χ���: Electroblob", - "感謝 Minecraft Forge 及 MCP,\n沒有它們就沒有這個模組。", + "�P�� Minecraft Forge �� MCP�A\n�S�����̴N�S���o�ӼҲաC", - "也感謝Minecraft的模組討論區,\n總能回答我的模組問題!", + "�]�P��Minecraft���ҲհQ�װϡA\n�`��^���ڪ��Ҳհ��D�I", - "\n另外,我要感謝以下各位對本模組的貢獻:", + "\n�t�~�A�ڭn�P�¥H�U�U��糧�Ҳժ��^�m:", - "程式碼:", + "�{���X:", - "》Corail31 \n》12foo \n》Shadows-of-Fire \n》HellFirePvP \n》Tora-B \n》Avatair \n》Aeronica \n》UltraHex \n》Azim-Palmer \n》raoulvdberge \n》rafasoares", + "�nCorail31 \n�n12foo \n�nShadows-of-Fire \n�nHellFirePvP \n�nTora-B \n�nAvatair \n�nAeronica \n�nUltraHex \n�nAzim-Palmer \n�nraoulvdberge \n�nrafasoares", - "\n\n翻譯:", + "½Ķ:", - "》繁體中文: Grant\n》(墨西哥)西班牙文: MadWrist \n》俄文: VilagVil, kellixon\n》法文: Hahdrim\n》巴西葡萄牙文: lorrampi\n》簡體中文: ZHENGLOC, dragon-evol\n》韓文: shejery, rewi_wire\n》波蘭文: Trozuu", + "�n�c�餤��: meow_meow_ying\n�n��Z����: MadWrist, Alsentar\n�n�������Z����: MadWrist\n�n�X��: VilagVil, kellixon, bigenergy\n�n�k��: Hahdrim\n�n�ڦ踲�����: lorrampi\n�n²�餤��: ZHENGLOC, dragon-evol, Hokorizero, TUsama, Determancer\n�n����: shejery, rewi_wire\n�n�i����: Trozuu\n�n�w��: BirdyDragon", - "雷電音效: OhhWowProductions", + "�p�q����: OhhWowProductions", - "更多資訊請前往 @https://github.com/Electroblob77/Wizardry/wiki wiki@", + "��h��T�Ыe�� @https://github.com/Electroblob77/Wizardry/wiki wiki@", - "覺得法術模組還有不足之處? 請加入 @https://discord.gg/MTmMzMv Discord 伺服器@以獲得\n最新消息、討論及額外事務!" + "ı�o�k�N�Ҳ��٦��������B? �Х[�J @https://discord.gg/MTmMzMv Discord ���A��@�H��o\n�̷s�����B�Q�פ��B�~�ưȡI" ] } diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/acacia_bookshelf_inside.png b/src/main/resources/assets/ebwizardry/textures/blocks/acacia_bookshelf_inside.png new file mode 100644 index 00000000..fa0d343d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/acacia_bookshelf_inside.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/acacia_bookshelf_side.png b/src/main/resources/assets/ebwizardry/textures/blocks/acacia_bookshelf_side.png new file mode 100644 index 00000000..e1033364 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/acacia_bookshelf_side.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/acacia_bookshelf_top.png b/src/main/resources/assets/ebwizardry/textures/blocks/acacia_bookshelf_top.png new file mode 100644 index 00000000..1021d20e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/acacia_bookshelf_top.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/acacia_lectern_base.png b/src/main/resources/assets/ebwizardry/textures/blocks/acacia_lectern_base.png new file mode 100644 index 00000000..77697875 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/acacia_lectern_base.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/acacia_lectern_side.png b/src/main/resources/assets/ebwizardry/textures/blocks/acacia_lectern_side.png new file mode 100644 index 00000000..5dfc524b Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/acacia_lectern_side.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/acacia_lectern_top.png b/src/main/resources/assets/ebwizardry/textures/blocks/acacia_lectern_top.png new file mode 100644 index 00000000..4f588ada Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/acacia_lectern_top.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/acacia_lectern_underside.png b/src/main/resources/assets/ebwizardry/textures/blocks/acacia_lectern_underside.png new file mode 100644 index 00000000..8305f170 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/acacia_lectern_underside.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_0.png b/src/main/resources/assets/ebwizardry/textures/blocks/arcane_lock_0.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_0.png rename to src/main/resources/assets/ebwizardry/textures/blocks/arcane_lock_0.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_1.png b/src/main/resources/assets/ebwizardry/textures/blocks/arcane_lock_1.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_1.png rename to src/main/resources/assets/ebwizardry/textures/blocks/arcane_lock_1.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_2.png b/src/main/resources/assets/ebwizardry/textures/blocks/arcane_lock_2.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_2.png rename to src/main/resources/assets/ebwizardry/textures/blocks/arcane_lock_2.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_3.png b/src/main/resources/assets/ebwizardry/textures/blocks/arcane_lock_3.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_3.png rename to src/main/resources/assets/ebwizardry/textures/blocks/arcane_lock_3.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_4.png b/src/main/resources/assets/ebwizardry/textures/blocks/arcane_lock_4.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_4.png rename to src/main/resources/assets/ebwizardry/textures/blocks/arcane_lock_4.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_5.png b/src/main/resources/assets/ebwizardry/textures/blocks/arcane_lock_5.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_5.png rename to src/main/resources/assets/ebwizardry/textures/blocks/arcane_lock_5.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_6.png b/src/main/resources/assets/ebwizardry/textures/blocks/arcane_lock_6.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_6.png rename to src/main/resources/assets/ebwizardry/textures/blocks/arcane_lock_6.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_7.png b/src/main/resources/assets/ebwizardry/textures/blocks/arcane_lock_7.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/arcane_lock_7.png rename to src/main/resources/assets/ebwizardry/textures/blocks/arcane_lock_7.png diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/birch_bookshelf_inside.png b/src/main/resources/assets/ebwizardry/textures/blocks/birch_bookshelf_inside.png new file mode 100644 index 00000000..bd98104a Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/birch_bookshelf_inside.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/birch_bookshelf_side.png b/src/main/resources/assets/ebwizardry/textures/blocks/birch_bookshelf_side.png new file mode 100644 index 00000000..393ec084 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/birch_bookshelf_side.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/birch_bookshelf_top.png b/src/main/resources/assets/ebwizardry/textures/blocks/birch_bookshelf_top.png new file mode 100644 index 00000000..1045a755 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/birch_bookshelf_top.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/birch_lectern_base.png b/src/main/resources/assets/ebwizardry/textures/blocks/birch_lectern_base.png new file mode 100644 index 00000000..19b1e482 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/birch_lectern_base.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/birch_lectern_side.png b/src/main/resources/assets/ebwizardry/textures/blocks/birch_lectern_side.png new file mode 100644 index 00000000..e71307dd Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/birch_lectern_side.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/birch_lectern_top.png b/src/main/resources/assets/ebwizardry/textures/blocks/birch_lectern_top.png new file mode 100644 index 00000000..41146dff Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/birch_lectern_top.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/birch_lectern_underside.png b/src/main/resources/assets/ebwizardry/textures/blocks/birch_lectern_underside.png new file mode 100644 index 00000000..e8ae2c11 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/birch_lectern_underside.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/books_blue.png b/src/main/resources/assets/ebwizardry/textures/blocks/books_blue.png new file mode 100644 index 00000000..07235139 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/books_blue.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/books_brown.png b/src/main/resources/assets/ebwizardry/textures/blocks/books_brown.png new file mode 100644 index 00000000..bfc03dc3 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/books_brown.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/books_enchanted.png b/src/main/resources/assets/ebwizardry/textures/blocks/books_enchanted.png new file mode 100644 index 00000000..fe1be1ab Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/books_enchanted.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/books_purple.png b/src/main/resources/assets/ebwizardry/textures/blocks/books_purple.png new file mode 100644 index 00000000..af1439fa Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/books_purple.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/books_red.png b/src/main/resources/assets/ebwizardry/textures/blocks/books_red.png new file mode 100644 index 00000000..c8b3ab2a Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/books_red.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/dark_oak_bookshelf_inside.png b/src/main/resources/assets/ebwizardry/textures/blocks/dark_oak_bookshelf_inside.png new file mode 100644 index 00000000..1002e81d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/dark_oak_bookshelf_inside.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/dark_oak_bookshelf_side.png b/src/main/resources/assets/ebwizardry/textures/blocks/dark_oak_bookshelf_side.png new file mode 100644 index 00000000..e7bc8f32 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/dark_oak_bookshelf_side.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/dark_oak_bookshelf_top.png b/src/main/resources/assets/ebwizardry/textures/blocks/dark_oak_bookshelf_top.png new file mode 100644 index 00000000..6fc6358c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/dark_oak_bookshelf_top.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/dark_oak_lectern_base.png b/src/main/resources/assets/ebwizardry/textures/blocks/dark_oak_lectern_base.png new file mode 100644 index 00000000..f0e125ed Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/dark_oak_lectern_base.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/dark_oak_lectern_side.png b/src/main/resources/assets/ebwizardry/textures/blocks/dark_oak_lectern_side.png new file mode 100644 index 00000000..4c040558 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/dark_oak_lectern_side.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/dark_oak_lectern_top.png b/src/main/resources/assets/ebwizardry/textures/blocks/dark_oak_lectern_top.png new file mode 100644 index 00000000..7b5c5548 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/dark_oak_lectern_top.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/dark_oak_lectern_underside.png b/src/main/resources/assets/ebwizardry/textures/blocks/dark_oak_lectern_underside.png new file mode 100644 index 00000000..63d023d3 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/dark_oak_lectern_underside.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/gilded_acacia_wood.png b/src/main/resources/assets/ebwizardry/textures/blocks/gilded_acacia_wood.png new file mode 100644 index 00000000..a056d2b8 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/gilded_acacia_wood.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/gilded_birch_wood.png b/src/main/resources/assets/ebwizardry/textures/blocks/gilded_birch_wood.png new file mode 100644 index 00000000..1c226083 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/gilded_birch_wood.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/gilded_dark_oak_wood.png b/src/main/resources/assets/ebwizardry/textures/blocks/gilded_dark_oak_wood.png new file mode 100644 index 00000000..6c016fc7 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/gilded_dark_oak_wood.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/gilded_jungle_wood.png b/src/main/resources/assets/ebwizardry/textures/blocks/gilded_jungle_wood.png new file mode 100644 index 00000000..b928ff61 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/gilded_jungle_wood.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/gilded_oak_wood.png b/src/main/resources/assets/ebwizardry/textures/blocks/gilded_oak_wood.png new file mode 100644 index 00000000..29b2f19a Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/gilded_oak_wood.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/gilded_spruce_wood.png b/src/main/resources/assets/ebwizardry/textures/blocks/gilded_spruce_wood.png new file mode 100644 index 00000000..b87a39e1 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/gilded_spruce_wood.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/imbuement_altar_bottom.png b/src/main/resources/assets/ebwizardry/textures/blocks/imbuement_altar_bottom.png new file mode 100644 index 00000000..49884613 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/imbuement_altar_bottom.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/imbuement_altar_side.png b/src/main/resources/assets/ebwizardry/textures/blocks/imbuement_altar_side.png new file mode 100644 index 00000000..9982b86d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/imbuement_altar_side.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/imbuement_altar_side_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/imbuement_altar_side_overlay.png new file mode 100644 index 00000000..8eaeba9a Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/imbuement_altar_side_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/imbuement_altar_side_overlay.png.mcmeta b/src/main/resources/assets/ebwizardry/textures/blocks/imbuement_altar_side_overlay.png.mcmeta new file mode 100644 index 00000000..0719b1bb --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/blocks/imbuement_altar_side_overlay.png.mcmeta @@ -0,0 +1,12 @@ +{ + "animation": { + + "frametime": 40, + + "interpolate": true, + + "frames": [0, 1, 2, 3] + + } + +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/imbuement_altar_top.png b/src/main/resources/assets/ebwizardry/textures/blocks/imbuement_altar_top.png new file mode 100644 index 00000000..1d75ed0c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/imbuement_altar_top.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/imbuement_altar_top_overlay.png b/src/main/resources/assets/ebwizardry/textures/blocks/imbuement_altar_top_overlay.png new file mode 100644 index 00000000..3adc9e9c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/imbuement_altar_top_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/imbuement_altar_top_overlay.png.mcmeta b/src/main/resources/assets/ebwizardry/textures/blocks/imbuement_altar_top_overlay.png.mcmeta new file mode 100644 index 00000000..0719b1bb --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/blocks/imbuement_altar_top_overlay.png.mcmeta @@ -0,0 +1,12 @@ +{ + "animation": { + + "frametime": 40, + + "interpolate": true, + + "frames": [0, 1, 2, 3] + + } + +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/jungle_bookshelf_inside.png b/src/main/resources/assets/ebwizardry/textures/blocks/jungle_bookshelf_inside.png new file mode 100644 index 00000000..871a563a Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/jungle_bookshelf_inside.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/jungle_bookshelf_side.png b/src/main/resources/assets/ebwizardry/textures/blocks/jungle_bookshelf_side.png new file mode 100644 index 00000000..0acc2705 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/jungle_bookshelf_side.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/jungle_bookshelf_top.png b/src/main/resources/assets/ebwizardry/textures/blocks/jungle_bookshelf_top.png new file mode 100644 index 00000000..9d2cb687 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/jungle_bookshelf_top.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/jungle_lectern_base.png b/src/main/resources/assets/ebwizardry/textures/blocks/jungle_lectern_base.png new file mode 100644 index 00000000..a4f7fde5 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/jungle_lectern_base.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/jungle_lectern_side.png b/src/main/resources/assets/ebwizardry/textures/blocks/jungle_lectern_side.png new file mode 100644 index 00000000..44fb9521 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/jungle_lectern_side.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/jungle_lectern_top.png b/src/main/resources/assets/ebwizardry/textures/blocks/jungle_lectern_top.png new file mode 100644 index 00000000..0683e9c7 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/jungle_lectern_top.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/jungle_lectern_underside.png b/src/main/resources/assets/ebwizardry/textures/blocks/jungle_lectern_underside.png new file mode 100644 index 00000000..1e98ffa2 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/jungle_lectern_underside.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/oak_bookshelf_inside.png b/src/main/resources/assets/ebwizardry/textures/blocks/oak_bookshelf_inside.png new file mode 100644 index 00000000..d8626b0a Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/oak_bookshelf_inside.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/oak_bookshelf_side.png b/src/main/resources/assets/ebwizardry/textures/blocks/oak_bookshelf_side.png new file mode 100644 index 00000000..45866763 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/oak_bookshelf_side.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/oak_bookshelf_top.png b/src/main/resources/assets/ebwizardry/textures/blocks/oak_bookshelf_top.png new file mode 100644 index 00000000..fad076fe Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/oak_bookshelf_top.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/oak_lectern_base.png b/src/main/resources/assets/ebwizardry/textures/blocks/oak_lectern_base.png new file mode 100644 index 00000000..a7b2109e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/oak_lectern_base.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/oak_lectern_side.png b/src/main/resources/assets/ebwizardry/textures/blocks/oak_lectern_side.png new file mode 100644 index 00000000..414690ab Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/oak_lectern_side.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/oak_lectern_top.png b/src/main/resources/assets/ebwizardry/textures/blocks/oak_lectern_top.png new file mode 100644 index 00000000..ad128f79 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/oak_lectern_top.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/oak_lectern_underside.png b/src/main/resources/assets/ebwizardry/textures/blocks/oak_lectern_underside.png new file mode 100644 index 00000000..10fae59c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/oak_lectern_underside.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/receptacle.png b/src/main/resources/assets/ebwizardry/textures/blocks/receptacle.png new file mode 100644 index 00000000..300eadb5 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/receptacle.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/scrolls_blue.png b/src/main/resources/assets/ebwizardry/textures/blocks/scrolls_blue.png new file mode 100644 index 00000000..15bacb25 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/scrolls_blue.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/scrolls_purple.png b/src/main/resources/assets/ebwizardry/textures/blocks/scrolls_purple.png new file mode 100644 index 00000000..f7602d2d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/scrolls_purple.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/scrolls_red.png b/src/main/resources/assets/ebwizardry/textures/blocks/scrolls_red.png new file mode 100644 index 00000000..56fb53d9 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/scrolls_red.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/scrolls_wooden.png b/src/main/resources/assets/ebwizardry/textures/blocks/scrolls_wooden.png new file mode 100644 index 00000000..840dbe5f Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/scrolls_wooden.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/spruce_bookshelf_inside.png b/src/main/resources/assets/ebwizardry/textures/blocks/spruce_bookshelf_inside.png new file mode 100644 index 00000000..2a066929 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/spruce_bookshelf_inside.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/spruce_bookshelf_side.png b/src/main/resources/assets/ebwizardry/textures/blocks/spruce_bookshelf_side.png new file mode 100644 index 00000000..821d3a6f Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/spruce_bookshelf_side.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/spruce_bookshelf_top.png b/src/main/resources/assets/ebwizardry/textures/blocks/spruce_bookshelf_top.png new file mode 100644 index 00000000..d3da5800 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/spruce_bookshelf_top.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/spruce_lectern_base.png b/src/main/resources/assets/ebwizardry/textures/blocks/spruce_lectern_base.png new file mode 100644 index 00000000..2ab6af8e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/spruce_lectern_base.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/spruce_lectern_side.png b/src/main/resources/assets/ebwizardry/textures/blocks/spruce_lectern_side.png new file mode 100644 index 00000000..bff762de Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/spruce_lectern_side.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/spruce_lectern_top.png b/src/main/resources/assets/ebwizardry/textures/blocks/spruce_lectern_top.png new file mode 100644 index 00000000..6e3c7f1c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/spruce_lectern_top.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/spruce_lectern_underside.png b/src/main/resources/assets/ebwizardry/textures/blocks/spruce_lectern_underside.png new file mode 100644 index 00000000..cc7e92bc Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/spruce_lectern_underside.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/blocks/wall_receptacle.png b/src/main/resources/assets/ebwizardry/textures/blocks/wall_receptacle.png new file mode 100644 index 00000000..bbae2f2d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/blocks/wall_receptacle.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/rune.png b/src/main/resources/assets/ebwizardry/textures/entity/arcane_workbench_rune.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/rune.png rename to src/main/resources/assets/ebwizardry/textures/entity/arcane_workbench_rune.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/aura.png b/src/main/resources/assets/ebwizardry/textures/entity/aura.png deleted file mode 100644 index 89e2234b..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/entity/aura.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/black_hole.png b/src/main/resources/assets/ebwizardry/textures/entity/black_hole/centre.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/black_hole.png rename to src/main/resources/assets/ebwizardry/textures/entity/black_hole/centre.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/dark_ray.png b/src/main/resources/assets/ebwizardry/textures/entity/black_hole/ray.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/dark_ray.png rename to src/main/resources/assets/ebwizardry/textures/entity/black_hole/ray.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/boulder.png b/src/main/resources/assets/ebwizardry/textures/entity/boulder.png new file mode 100644 index 00000000..72d1e647 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/boulder.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/decay_0.png b/src/main/resources/assets/ebwizardry/textures/entity/decay/decay_0.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/decay_0.png rename to src/main/resources/assets/ebwizardry/textures/entity/decay/decay_0.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/decay_1.png b/src/main/resources/assets/ebwizardry/textures/entity/decay/decay_1.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/decay_1.png rename to src/main/resources/assets/ebwizardry/textures/entity/decay/decay_1.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/decay_2.png b/src/main/resources/assets/ebwizardry/textures/entity/decay/decay_2.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/decay_2.png rename to src/main/resources/assets/ebwizardry/textures/entity/decay/decay_2.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/decay_3.png b/src/main/resources/assets/ebwizardry/textures/entity/decay/decay_3.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/decay_3.png rename to src/main/resources/assets/ebwizardry/textures/entity/decay/decay_3.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/decay_4.png b/src/main/resources/assets/ebwizardry/textures/entity/decay/decay_4.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/decay_4.png rename to src/main/resources/assets/ebwizardry/textures/entity/decay/decay_4.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/decay_5.png b/src/main/resources/assets/ebwizardry/textures/entity/decay/decay_5.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/decay_5.png rename to src/main/resources/assets/ebwizardry/textures/entity/decay/decay_5.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/decay_6.png b/src/main/resources/assets/ebwizardry/textures/entity/decay/decay_6.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/decay_6.png rename to src/main/resources/assets/ebwizardry/textures/entity/decay/decay_6.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/decay_7.png b/src/main/resources/assets/ebwizardry/textures/entity/decay/decay_7.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/decay_7.png rename to src/main/resources/assets/ebwizardry/textures/entity/decay/decay_7.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/decay_8.png b/src/main/resources/assets/ebwizardry/textures/entity/decay/decay_8.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/decay_8.png rename to src/main/resources/assets/ebwizardry/textures/entity/decay/decay_8.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/decay_9.png b/src/main/resources/assets/ebwizardry/textures/entity/decay/decay_9.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/decay_9.png rename to src/main/resources/assets/ebwizardry/textures/entity/decay/decay_9.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_0.png b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_0.png new file mode 100644 index 00000000..7832affd Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_1.png b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_1.png new file mode 100644 index 00000000..709e27f7 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_10.png b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_10.png new file mode 100644 index 00000000..dbd9134e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_10.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_11.png b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_11.png new file mode 100644 index 00000000..62884354 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_11.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_12.png b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_12.png new file mode 100644 index 00000000..41155b91 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_12.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_13.png b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_13.png new file mode 100644 index 00000000..febedf26 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_13.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_14.png b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_14.png new file mode 100644 index 00000000..b9d54978 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_14.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_15.png b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_15.png new file mode 100644 index 00000000..db713be0 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_15.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_16.png b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_16.png new file mode 100644 index 00000000..5195c32e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_16.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_17.png b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_17.png new file mode 100644 index 00000000..ff52291b Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_17.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_18.png b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_18.png new file mode 100644 index 00000000..a78cac36 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_18.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_2.png b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_2.png new file mode 100644 index 00000000..7c8f0914 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_3.png b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_3.png new file mode 100644 index 00000000..58e101a7 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_4.png b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_4.png new file mode 100644 index 00000000..3c045a72 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_5.png b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_5.png new file mode 100644 index 00000000..c6f5a706 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_5.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_6.png b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_6.png new file mode 100644 index 00000000..8ec60551 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_6.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_7.png b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_7.png new file mode 100644 index 00000000..77de8a27 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_7.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_8.png b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_8.png new file mode 100644 index 00000000..34b63168 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_8.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_9.png b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_9.png new file mode 100644 index 00000000..6404c89f Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/disintegrate_overlay/disintegrate_overlay_9.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/dark_orb.png b/src/main/resources/assets/ebwizardry/textures/entity/entrapment.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/dark_orb.png rename to src/main/resources/assets/ebwizardry/textures/entity/entrapment.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/evil_wizard_0.png b/src/main/resources/assets/ebwizardry/textures/entity/evil_wizard/evil_wizard_0.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/evil_wizard_0.png rename to src/main/resources/assets/ebwizardry/textures/entity/evil_wizard/evil_wizard_0.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/evil_wizard_1.png b/src/main/resources/assets/ebwizardry/textures/entity/evil_wizard/evil_wizard_1.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/evil_wizard_1.png rename to src/main/resources/assets/ebwizardry/textures/entity/evil_wizard/evil_wizard_1.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/evil_wizard_2.png b/src/main/resources/assets/ebwizardry/textures/entity/evil_wizard/evil_wizard_2.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/evil_wizard_2.png rename to src/main/resources/assets/ebwizardry/textures/entity/evil_wizard/evil_wizard_2.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/evil_wizard_3.png b/src/main/resources/assets/ebwizardry/textures/entity/evil_wizard/evil_wizard_3.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/evil_wizard_3.png rename to src/main/resources/assets/ebwizardry/textures/entity/evil_wizard/evil_wizard_3.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/evil_wizard_4.png b/src/main/resources/assets/ebwizardry/textures/entity/evil_wizard/evil_wizard_4.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/evil_wizard_4.png rename to src/main/resources/assets/ebwizardry/textures/entity/evil_wizard/evil_wizard_4.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/evil_wizard_5.png b/src/main/resources/assets/ebwizardry/textures/entity/evil_wizard/evil_wizard_5.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/evil_wizard_5.png rename to src/main/resources/assets/ebwizardry/textures/entity/evil_wizard/evil_wizard_5.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/flamecatcher_arrow.png b/src/main/resources/assets/ebwizardry/textures/entity/flamecatcher_arrow.png new file mode 100644 index 00000000..db64d4b8 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/flamecatcher_arrow.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/ice_barrier.png b/src/main/resources/assets/ebwizardry/textures/entity/ice_barrier.png new file mode 100644 index 00000000..9b6e1229 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/ice_barrier.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/ice_giant.png b/src/main/resources/assets/ebwizardry/textures/entity/ice_giant.png index bd6ef77a..63667344 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/entity/ice_giant.png and b/src/main/resources/assets/ebwizardry/textures/entity/ice_giant.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/lectern_book.png b/src/main/resources/assets/ebwizardry/textures/entity/lectern_book.png new file mode 100644 index 00000000..4b9b746d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/lectern_book.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/light_aura.png b/src/main/resources/assets/ebwizardry/textures/entity/light/flare.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/light_aura.png rename to src/main/resources/assets/ebwizardry/textures/entity/light/flare.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/light_ray.png b/src/main/resources/assets/ebwizardry/textures/entity/light/ray.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/light_ray.png rename to src/main/resources/assets/ebwizardry/textures/entity/light/ray.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/mind_control_overlay.png b/src/main/resources/assets/ebwizardry/textures/entity/mind_control_overlay.png new file mode 100644 index 00000000..ca3be54c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/mind_control_overlay.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/remnant.png b/src/main/resources/assets/ebwizardry/textures/entity/remnant.png new file mode 100644 index 00000000..f4429150 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/remnant.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/shield.png b/src/main/resources/assets/ebwizardry/textures/entity/shield.png index db964c49..0fb3e564 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/entity/shield.png and b/src/main/resources/assets/ebwizardry/textures/entity/shield.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/sixth_sense.png b/src/main/resources/assets/ebwizardry/textures/entity/sixth_sense.png deleted file mode 100644 index 5187f19e..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/entity/sixth_sense.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_0.png b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_0.png new file mode 100644 index 00000000..c1d348c0 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_1.png b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_1.png new file mode 100644 index 00000000..7e94bcec Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_10.png b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_10.png new file mode 100644 index 00000000..af60cfbf Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_10.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_11.png b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_11.png new file mode 100644 index 00000000..9ebf86ae Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_11.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_12.png b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_12.png new file mode 100644 index 00000000..18a10e3e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_12.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_13.png b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_13.png new file mode 100644 index 00000000..98f57c22 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_13.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_14.png b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_14.png new file mode 100644 index 00000000..3ef94ac8 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_14.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_15.png b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_15.png new file mode 100644 index 00000000..b6db5d2d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_15.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_16.png b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_16.png new file mode 100644 index 00000000..5949439e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_16.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_17.png b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_17.png new file mode 100644 index 00000000..333aed6c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_17.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_18.png b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_18.png new file mode 100644 index 00000000..c19f0c94 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_18.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_2.png b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_2.png new file mode 100644 index 00000000..4e9d8098 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_3.png b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_3.png new file mode 100644 index 00000000..ec674b2c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_4.png b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_4.png new file mode 100644 index 00000000..236d1871 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_5.png b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_5.png new file mode 100644 index 00000000..a8a876ed Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_5.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_6.png b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_6.png new file mode 100644 index 00000000..b9b527bf Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_6.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_7.png b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_7.png new file mode 100644 index 00000000..4175d583 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_7.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_8.png b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_8.png new file mode 100644 index 00000000..cfe465c5 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_8.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_9.png b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_9.png new file mode 100644 index 00000000..d4397aed Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/summon_overlay/summon_overlay_9.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_0.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_0.png new file mode 100644 index 00000000..fa1e0012 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_1.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_1.png new file mode 100644 index 00000000..aafb2355 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_10.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_10.png new file mode 100644 index 00000000..fcfc9dd8 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_10.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_11.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_11.png new file mode 100644 index 00000000..e1383851 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_11.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_12.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_12.png new file mode 100644 index 00000000..5948821d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_12.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_13.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_13.png new file mode 100644 index 00000000..94f283cc Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_13.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_2.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_2.png new file mode 100644 index 00000000..6338ada4 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_3.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_3.png new file mode 100644 index 00000000..9b4dde73 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_4.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_4.png new file mode 100644 index 00000000..8ea24c85 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_5.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_5.png new file mode 100644 index 00000000..ffb44497 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_5.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_6.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_6.png new file mode 100644 index 00000000..5686ae5e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_6.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_7.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_7.png new file mode 100644 index 00000000..dde02871 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_7.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_8.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_8.png new file mode 100644 index 00000000..4c6e3a89 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_8.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_9.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_9.png new file mode 100644 index 00000000..6ef6c65e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_9.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/totem/flare.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/flare.png new file mode 100644 index 00000000..991dc12c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/totem/flare.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/wizard_0.png b/src/main/resources/assets/ebwizardry/textures/entity/wizard/wizard_0.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/wizard_0.png rename to src/main/resources/assets/ebwizardry/textures/entity/wizard/wizard_0.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/wizard_1.png b/src/main/resources/assets/ebwizardry/textures/entity/wizard/wizard_1.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/wizard_1.png rename to src/main/resources/assets/ebwizardry/textures/entity/wizard/wizard_1.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/wizard_2.png b/src/main/resources/assets/ebwizardry/textures/entity/wizard/wizard_2.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/wizard_2.png rename to src/main/resources/assets/ebwizardry/textures/entity/wizard/wizard_2.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/wizard_3.png b/src/main/resources/assets/ebwizardry/textures/entity/wizard/wizard_3.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/wizard_3.png rename to src/main/resources/assets/ebwizardry/textures/entity/wizard/wizard_3.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/wizard_4.png b/src/main/resources/assets/ebwizardry/textures/entity/wizard/wizard_4.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/wizard_4.png rename to src/main/resources/assets/ebwizardry/textures/entity/wizard/wizard_4.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/wizard_5.png b/src/main/resources/assets/ebwizardry/textures/entity/wizard/wizard_5.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/wizard_5.png rename to src/main/resources/assets/ebwizardry/textures/entity/wizard/wizard_5.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/wizard_7.png b/src/main/resources/assets/ebwizardry/textures/entity/wizard/wizard_7.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/wizard_7.png rename to src/main/resources/assets/ebwizardry/textures/entity/wizard/wizard_7.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/wizard_8.png b/src/main/resources/assets/ebwizardry/textures/entity/wizard/wizard_8.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/wizard_8.png rename to src/main/resources/assets/ebwizardry/textures/entity/wizard/wizard_8.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/wizard_female_0.png b/src/main/resources/assets/ebwizardry/textures/entity/wizard/wizard_female_0.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/wizard_female_0.png rename to src/main/resources/assets/ebwizardry/textures/entity/wizard/wizard_female_0.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/wizard_female_1.png b/src/main/resources/assets/ebwizardry/textures/entity/wizard/wizard_female_1.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/wizard_female_1.png rename to src/main/resources/assets/ebwizardry/textures/entity/wizard/wizard_female_1.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/wizard_female_2.png b/src/main/resources/assets/ebwizardry/textures/entity/wizard/wizard_female_2.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/wizard_female_2.png rename to src/main/resources/assets/ebwizardry/textures/entity/wizard/wizard_female_2.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/zombie_spawner.png b/src/main/resources/assets/ebwizardry/textures/entity/zombie_spawner.png new file mode 100644 index 00000000..33a4f4b4 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/entity/zombie_spawner.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/containment_field_0.png b/src/main/resources/assets/ebwizardry/textures/environment/containment_field_0.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/containment_field_0.png rename to src/main/resources/assets/ebwizardry/textures/environment/containment_field_0.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/containment_field_1.png b/src/main/resources/assets/ebwizardry/textures/environment/containment_field_1.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/containment_field_1.png rename to src/main/resources/assets/ebwizardry/textures/environment/containment_field_1.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/containment_field_2.png b/src/main/resources/assets/ebwizardry/textures/environment/containment_field_2.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/containment_field_2.png rename to src/main/resources/assets/ebwizardry/textures/environment/containment_field_2.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/containment_field_3.png b/src/main/resources/assets/ebwizardry/textures/environment/containment_field_3.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/containment_field_3.png rename to src/main/resources/assets/ebwizardry/textures/environment/containment_field_3.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/containment_field_4.png b/src/main/resources/assets/ebwizardry/textures/environment/containment_field_4.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/containment_field_4.png rename to src/main/resources/assets/ebwizardry/textures/environment/containment_field_4.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/containment_field_5.png b/src/main/resources/assets/ebwizardry/textures/environment/containment_field_5.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/containment_field_5.png rename to src/main/resources/assets/ebwizardry/textures/environment/containment_field_5.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/containment_field_6.png b/src/main/resources/assets/ebwizardry/textures/environment/containment_field_6.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/containment_field_6.png rename to src/main/resources/assets/ebwizardry/textures/environment/containment_field_6.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/containment_field_7.png b/src/main/resources/assets/ebwizardry/textures/environment/containment_field_7.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/containment_field_7.png rename to src/main/resources/assets/ebwizardry/textures/environment/containment_field_7.png 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 c5de2e88..fc6cc3fe 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 deleted file mode 100644 index 814ea32d..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/gui/arcane_workbench.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/container/arcane_workbench.png b/src/main/resources/assets/ebwizardry/textures/gui/container/arcane_workbench.png new file mode 100644 index 00000000..2261cfa3 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/container/arcane_workbench.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/container/bookshelf.png b/src/main/resources/assets/ebwizardry/textures/gui/container/bookshelf.png new file mode 100644 index 00000000..55c33044 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/container/bookshelf.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/element_icon_earth.png b/src/main/resources/assets/ebwizardry/textures/gui/container/element_icon_earth.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/element_icon_earth.png rename to src/main/resources/assets/ebwizardry/textures/gui/container/element_icon_earth.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/element_icon_fire.png b/src/main/resources/assets/ebwizardry/textures/gui/container/element_icon_fire.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/element_icon_fire.png rename to src/main/resources/assets/ebwizardry/textures/gui/container/element_icon_fire.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/element_icon_healing.png b/src/main/resources/assets/ebwizardry/textures/gui/container/element_icon_healing.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/element_icon_healing.png rename to src/main/resources/assets/ebwizardry/textures/gui/container/element_icon_healing.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/element_icon_ice.png b/src/main/resources/assets/ebwizardry/textures/gui/container/element_icon_ice.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/element_icon_ice.png rename to src/main/resources/assets/ebwizardry/textures/gui/container/element_icon_ice.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/element_icon_lightning.png b/src/main/resources/assets/ebwizardry/textures/gui/container/element_icon_lightning.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/element_icon_lightning.png rename to src/main/resources/assets/ebwizardry/textures/gui/container/element_icon_lightning.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/element_icon_magic.png b/src/main/resources/assets/ebwizardry/textures/gui/container/element_icon_magic.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/element_icon_magic.png rename to src/main/resources/assets/ebwizardry/textures/gui/container/element_icon_magic.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/element_icon_necromancy.png b/src/main/resources/assets/ebwizardry/textures/gui/container/element_icon_necromancy.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/element_icon_necromancy.png rename to src/main/resources/assets/ebwizardry/textures/gui/container/element_icon_necromancy.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/element_icon_sorcery.png b/src/main/resources/assets/ebwizardry/textures/gui/container/element_icon_sorcery.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/element_icon_sorcery.png rename to src/main/resources/assets/ebwizardry/textures/gui/container/element_icon_sorcery.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/empty_slot_crystal.png b/src/main/resources/assets/ebwizardry/textures/gui/container/empty_slot_crystal.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/empty_slot_crystal.png rename to src/main/resources/assets/ebwizardry/textures/gui/container/empty_slot_crystal.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/empty_slot_upgrade.png b/src/main/resources/assets/ebwizardry/textures/gui/container/empty_slot_upgrade.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/empty_slot_upgrade.png rename to src/main/resources/assets/ebwizardry/textures/gui/container/empty_slot_upgrade.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/container/lectern.png b/src/main/resources/assets/ebwizardry/textures/gui/container/lectern.png new file mode 100644 index 00000000..a6b253c2 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/container/lectern.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/container/new_spell_indicator.png b/src/main/resources/assets/ebwizardry/textures/gui/container/new_spell_indicator.png new file mode 100644 index 00000000..62381a81 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/container/new_spell_indicator.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/container/spell_sort_buttons.png b/src/main/resources/assets/ebwizardry/textures/gui/container/spell_sort_buttons.png new file mode 100644 index 00000000..9ed4d337 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/container/spell_sort_buttons.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/handbook.png b/src/main/resources/assets/ebwizardry/textures/gui/handbook/handbook.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/handbook.png rename to src/main/resources/assets/ebwizardry/textures/gui/handbook/handbook.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/arcane_workbench_picture.png b/src/main/resources/assets/ebwizardry/textures/gui/handbook/pictures/arcane_workbench.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/arcane_workbench_picture.png rename to src/main/resources/assets/ebwizardry/textures/gui/handbook/pictures/arcane_workbench.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/flower_picture.png b/src/main/resources/assets/ebwizardry/textures/gui/handbook/pictures/crystal_flower.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/flower_picture.png rename to src/main/resources/assets/ebwizardry/textures/gui/handbook/pictures/crystal_flower.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/ore_picture.png b/src/main/resources/assets/ebwizardry/textures/gui/handbook/pictures/crystal_ore.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/ore_picture.png rename to src/main/resources/assets/ebwizardry/textures/gui/handbook/pictures/crystal_ore.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/elements_picture.png b/src/main/resources/assets/ebwizardry/textures/gui/handbook/pictures/elements.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/elements_picture.png rename to src/main/resources/assets/ebwizardry/textures/gui/handbook/pictures/elements.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/handbook/pictures/library_ruins.png b/src/main/resources/assets/ebwizardry/textures/gui/handbook/pictures/library_ruins.png new file mode 100644 index 00000000..c4c4e54f Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/handbook/pictures/library_ruins.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/obelisk_picture.png b/src/main/resources/assets/ebwizardry/textures/gui/handbook/pictures/obelisk.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/obelisk_picture.png rename to src/main/resources/assets/ebwizardry/textures/gui/handbook/pictures/obelisk.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/handbook/pictures/remnant.png b/src/main/resources/assets/ebwizardry/textures/gui/handbook/pictures/remnant.png new file mode 100644 index 00000000..8cdf95d5 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/handbook/pictures/remnant.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/gui/shrine_picture.png b/src/main/resources/assets/ebwizardry/textures/gui/handbook/pictures/shrine.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/shrine_picture.png rename to src/main/resources/assets/ebwizardry/textures/gui/handbook/pictures/shrine.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/tiers_picture.png b/src/main/resources/assets/ebwizardry/textures/gui/handbook/pictures/tiers.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/tiers_picture.png rename to src/main/resources/assets/ebwizardry/textures/gui/handbook/pictures/tiers.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/armour_picture.png b/src/main/resources/assets/ebwizardry/textures/gui/handbook/pictures/wizard_armour.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/armour_picture.png rename to src/main/resources/assets/ebwizardry/textures/gui/handbook/pictures/wizard_armour.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/tower_picture.png b/src/main/resources/assets/ebwizardry/textures/gui/handbook/pictures/wizard_tower.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/tower_picture.png rename to src/main/resources/assets/ebwizardry/textures/gui/handbook/pictures/wizard_tower.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/pointer.png b/src/main/resources/assets/ebwizardry/textures/gui/pointer.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/pointer.png rename to src/main/resources/assets/ebwizardry/textures/gui/pointer.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_arcane_jammer.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icons/arcane_jammer.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/potion_icon_arcane_jammer.png rename to src/main/resources/assets/ebwizardry/textures/gui/potion_icons/arcane_jammer.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_containment.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icons/containment.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/potion_icon_containment.png rename to src/main/resources/assets/ebwizardry/textures/gui/potion_icons/containment.png 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_icons/curse_of_enfeeblement.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/potion_icon_curse_of_enfeeblement.png rename to src/main/resources/assets/ebwizardry/textures/gui/potion_icons/curse_of_enfeeblement.png 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_icons/curse_of_soulbinding.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/potion_icon_curse_of_soulbinding.png rename to src/main/resources/assets/ebwizardry/textures/gui/potion_icons/curse_of_soulbinding.png 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_icons/curse_of_undeath.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/potion_icon_curse_of_undeath.png rename to src/main/resources/assets/ebwizardry/textures/gui/potion_icons/curse_of_undeath.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_decay.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icons/decay.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/potion_icon_decay.png rename to src/main/resources/assets/ebwizardry/textures/gui/potion_icons/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_icons/empowerment.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/potion_icon_empowerment.png rename to src/main/resources/assets/ebwizardry/textures/gui/potion_icons/empowerment.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_fear.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icons/fear.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/potion_icon_fear.png rename to src/main/resources/assets/ebwizardry/textures/gui/potion_icons/fear.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_fireskin.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icons/fireskin.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/potion_icon_fireskin.png rename to src/main/resources/assets/ebwizardry/textures/gui/potion_icons/fireskin.png 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_icons/font_of_mana.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/potion_icon_font_of_mana.png rename to src/main/resources/assets/ebwizardry/textures/gui/potion_icons/font_of_mana.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_frost.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icons/frost.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/potion_icon_frost.png rename to src/main/resources/assets/ebwizardry/textures/gui/potion_icons/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_icons/frost_step.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/potion_icon_frost_step.png rename to src/main/resources/assets/ebwizardry/textures/gui/potion_icons/frost_step.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_ice_shroud.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icons/ice_shroud.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/potion_icon_ice_shroud.png rename to src/main/resources/assets/ebwizardry/textures/gui/potion_icons/ice_shroud.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icons/mark_of_sacrifice.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icons/mark_of_sacrifice.png new file mode 100644 index 00000000..1aa8ac87 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/potion_icons/mark_of_sacrifice.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_icons/mind_control.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/potion_icon_mind_control.png rename to src/main/resources/assets/ebwizardry/textures/gui/potion_icons/mind_control.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_mind_trick.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icons/mind_trick.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/potion_icon_mind_trick.png rename to src/main/resources/assets/ebwizardry/textures/gui/potion_icons/mind_trick.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icons/mirage.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icons/mirage.png new file mode 100644 index 00000000..edebc087 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/potion_icons/mirage.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_icons/muffle.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/potion_icon_muffle.png rename to src/main/resources/assets/ebwizardry/textures/gui/potion_icons/muffle.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_paralysis.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icons/paralysis.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/potion_icon_paralysis.png rename to src/main/resources/assets/ebwizardry/textures/gui/potion_icons/paralysis.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_sixth_sense.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icons/sixth_sense.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/potion_icon_sixth_sense.png rename to src/main/resources/assets/ebwizardry/textures/gui/potion_icons/sixth_sense.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_slow_time.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icons/slow_time.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/potion_icon_slow_time.png rename to src/main/resources/assets/ebwizardry/textures/gui/potion_icons/slow_time.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_static_aura.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icons/static_aura.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/potion_icon_static_aura.png rename to src/main/resources/assets/ebwizardry/textures/gui/potion_icons/static_aura.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_transience.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icons/transience.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/potion_icon_transience.png rename to src/main/resources/assets/ebwizardry/textures/gui/potion_icons/transience.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/potion_icon_ward.png b/src/main/resources/assets/ebwizardry/textures/gui/potion_icons/ward.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/potion_icon_ward.png rename to src/main/resources/assets/ebwizardry/textures/gui/potion_icons/ward.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/sixth_sense_marker.png b/src/main/resources/assets/ebwizardry/textures/gui/sixth_sense_marker.png new file mode 100644 index 00000000..8b83f5ab Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/sixth_sense_marker.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 index 72b51efd..1bc9af05 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/gui/spell_book_advanced.png 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 index 7c1cab34..e816021a 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/gui/spell_book_apprentice.png 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 index 6a6396e1..7266a64e 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/gui/spell_book_master.png 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 index 19a78f8f..20699914 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/gui/spell_book_novice.png 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_charge_meter.png b/src/main/resources/assets/ebwizardry/textures/gui/spell_charge_meter.png new file mode 100644 index 00000000..dd8b6019 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/spell_charge_meter.png 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 index c61e8b52..66c34b77 100644 --- a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/_index.json +++ b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/_index.json @@ -39,9 +39,9 @@ "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" + "compact": { + "texture": "ebwizardry:gui/spell_hud/compact", + "metadata": "ebwizardry:gui/spell_hud/compact" }, "skyrim_style": { "texture": "ebwizardry:gui/spell_hud/skyrim_style", @@ -78,5 +78,13 @@ "festive": { "texture": "ebwizardry:gui/spell_hud/festive", "metadata": "ebwizardry:gui/spell_hud/festive" + }, + "library": { + "texture": "ebwizardry:gui/spell_hud/library", + "metadata": "ebwizardry:gui/spell_hud/library" + }, + "altar": { + "texture": "ebwizardry:gui/spell_hud/altar", + "metadata": "ebwizardry:gui/spell_hud/altar" } } \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/altar.json b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/altar.json new file mode 100644 index 00000000..232be8df --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/altar.json @@ -0,0 +1,33 @@ +{ + "name": "Altar", + "description": "Imbuement altars are carved from a rare type of stone called blue malthrock, prized for both its beauty and its imperishability.", + "width": 128, + "height": 47, + "mirror": { + "x": true, + "y": true + }, + "spell_icon_inset": { + "x": 2, + "y": 2 + }, + "text_inset": { + "x": 42, + "y": 20 + }, + "spell_cascade_offset": { + "x": 2, + "y": 8 + }, + "cooldown_bar": { + "x": 37, + "y": 0, + "length": 77, + "height": 7, + "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/altar.png b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/altar.png new file mode 100644 index 00000000..fdd822a0 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/altar.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/compact.json similarity index 95% rename from src/main/resources/assets/ebwizardry/textures/gui/spell_hud/no_icon.json rename to src/main/resources/assets/ebwizardry/textures/gui/spell_hud/compact.json index cbee2a6f..c8b24990 100644 --- a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/no_icon.json +++ b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/compact.json @@ -1,5 +1,5 @@ { - "name": "No Icon", + "name": "Compact", "description": "A variation on the default skin that doesn't show the spell icon.", "width": 96, "height": 37, 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/compact.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/gui/spell_hud/no_icon.png rename to src/main/resources/assets/ebwizardry/textures/gui/spell_hud/compact.png diff --git a/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/library.json b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/library.json new file mode 100644 index 00000000..33a78c3b --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/library.json @@ -0,0 +1,33 @@ +{ + "name": "Library", + "description": "I'm sure I put that book somewhere up here...", + "width": 128, + "height": 44, + "mirror": { + "x": true, + "y": true + }, + "spell_icon_inset": { + "x": 2, + "y": 5 + }, + "text_inset": { + "x": 45, + "y": 21 + }, + "spell_cascade_offset": { + "x": 2, + "y": 8 + }, + "cooldown_bar": { + "x": 40, + "y": 2, + "length": 82, + "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/library.png b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/library.png new file mode 100644 index 00000000..5c889b93 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/gui/spell_hud/library.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/entity/target_pointer.png b/src/main/resources/assets/ebwizardry/textures/gui/target_pointer.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/target_pointer.png rename to src/main/resources/assets/ebwizardry/textures/gui/target_pointer.png diff --git a/src/main/resources/assets/ebwizardry/textures/integration/antiqueatlas/library_ruins.png b/src/main/resources/assets/ebwizardry/textures/integration/antiqueatlas/library_ruins.png new file mode 100644 index 00000000..8697a363 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/integration/antiqueatlas/library_ruins.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/integration/jei/arcane_workbench_background.png b/src/main/resources/assets/ebwizardry/textures/integration/jei/arcane_workbench_background.png new file mode 100644 index 00000000..3546a60b Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/integration/jei/arcane_workbench_background.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/integration/jei/imbuement_altar_background.png b/src/main/resources/assets/ebwizardry/textures/integration/jei/imbuement_altar_background.png new file mode 100644 index 00000000..135f37cd Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/integration/jei/imbuement_altar_background.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/amulet_absorption.png b/src/main/resources/assets/ebwizardry/textures/items/amulet_absorption.png new file mode 100644 index 00000000..1a81b45c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/amulet_absorption.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/amulet_frost_warding.png b/src/main/resources/assets/ebwizardry/textures/items/amulet_frost_warding.png new file mode 100644 index 00000000..c370f09e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/amulet_frost_warding.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/charm_black_hole.png b/src/main/resources/assets/ebwizardry/textures/items/charm_black_hole.png new file mode 100644 index 00000000..ee7193cc Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/charm_black_hole.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/charm_hunger_casting.png b/src/main/resources/assets/ebwizardry/textures/items/charm_hunger_casting.png new file mode 100644 index 00000000..dfdbece7 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/charm_hunger_casting.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/charm_mount_teleporting.png b/src/main/resources/assets/ebwizardry/textures/items/charm_mount_teleporting.png new file mode 100644 index 00000000..4ca520fc Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/charm_mount_teleporting.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/charm_move_speed.png b/src/main/resources/assets/ebwizardry/textures/items/charm_move_speed.png new file mode 100644 index 00000000..2c746930 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/charm_move_speed.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/charm_sixth_sense.png b/src/main/resources/assets/ebwizardry/textures/items/charm_sixth_sense.png new file mode 100644 index 00000000..a196532f Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/charm_sixth_sense.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/charm_spell_discovery.png b/src/main/resources/assets/ebwizardry/textures/items/charm_spell_discovery.png new file mode 100644 index 00000000..ea0c3575 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/charm_spell_discovery.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 index 32bb5b8c..6fc35882 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/charm_storm.png and b/src/main/resources/assets/ebwizardry/textures/items/charm_storm.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/charm_storm.png.mcmeta b/src/main/resources/assets/ebwizardry/textures/items/charm_storm.png.mcmeta new file mode 100644 index 00000000..b88d9507 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/items/charm_storm.png.mcmeta @@ -0,0 +1,55 @@ +{ + "animation": { + + "frametime": 4, + + "interpolate": true, + + "frames": [ + { + "index": 0, + "time": 2 + }, + { + "index": 1, + "time": 1 + }, + { + "index": 0, + "time": 1 + }, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + { + "index": 9, + "time": 1 + }, + { + "index": 10, + "time": 2 + }, + { + "index": 10, + "time": 1 + }, + 11, + 12, + 13, + 14, + 15, + { + "index": 1, + "time": 1 + } + ] + + } + +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/items/charm_undead_helmets.png b/src/main/resources/assets/ebwizardry/textures/items/charm_undead_helmets.png new file mode 100644 index 00000000..bf951c99 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/charm_undead_helmets.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 index 8b38d0cc..29b43f8b 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/items/crystal_earth.png and b/src/main/resources/assets/ebwizardry/textures/items/crystal_earth.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_conjuring_0.png b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_conjuring_0.png new file mode 100644 index 00000000..d2765163 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_conjuring_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_conjuring_1.png b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_conjuring_1.png new file mode 100644 index 00000000..5dc4013a Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_conjuring_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_conjuring_2.png b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_conjuring_2.png new file mode 100644 index 00000000..8df96747 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_conjuring_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_conjuring_3.png b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_conjuring_3.png new file mode 100644 index 00000000..9d0eff4d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_conjuring_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_conjuring_4.png b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_conjuring_4.png new file mode 100644 index 00000000..49b2b8c5 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_conjuring_4.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_conjuring_5.png b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_conjuring_5.png new file mode 100644 index 00000000..5d308b65 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_conjuring_5.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_conjuring_6.png b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_conjuring_6.png new file mode 100644 index 00000000..0660ad33 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_conjuring_6.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_conjuring_7.png b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_conjuring_7.png new file mode 100644 index 00000000..9374f0a5 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_conjuring_7.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_pulling_0.png b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_pulling_0.png new file mode 100644 index 00000000..40221234 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_pulling_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_pulling_0.png.mcmeta b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_pulling_0.png.mcmeta new file mode 100644 index 00000000..eb5c833c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_pulling_0.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/flamecatcher_pulling_1.png b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_pulling_1.png new file mode 100644 index 00000000..811fec21 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_pulling_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_pulling_1.png.mcmeta b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_pulling_1.png.mcmeta new file mode 100644 index 00000000..eb5c833c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_pulling_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/flamecatcher_pulling_2.png b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_pulling_2.png new file mode 100644 index 00000000..5a68646e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_pulling_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_pulling_2.png.mcmeta b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_pulling_2.png.mcmeta new file mode 100644 index 00000000..eb5c833c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_pulling_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/flamecatcher_standby.png b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_standby.png new file mode 100644 index 00000000..2c680f9d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_standby.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_standby.png.mcmeta b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_standby.png.mcmeta new file mode 100644 index 00000000..eb5c833c --- /dev/null +++ b/src/main/resources/assets/ebwizardry/textures/items/flamecatcher_standby.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_evoker.png b/src/main/resources/assets/ebwizardry/textures/items/ring_evoker.png new file mode 100644 index 00000000..2ec9d27f Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_evoker.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_meteor.png b/src/main/resources/assets/ebwizardry/textures/items/ring_meteor.png new file mode 100644 index 00000000..b32fab84 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_meteor.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ring_stormcloud.png b/src/main/resources/assets/ebwizardry/textures/items/ring_stormcloud.png new file mode 100644 index 00000000..a4d8de05 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ring_stormcloud.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/ruined_spell_book.png b/src/main/resources/assets/ebwizardry/textures/items/ruined_spell_book.png new file mode 100644 index 00000000..4352c38f Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/ruined_spell_book.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_dust_earth.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_dust_earth.png new file mode 100644 index 00000000..7b8fa46e Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_dust_earth.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_dust_fire.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_dust_fire.png new file mode 100644 index 00000000..9e64160c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_dust_fire.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_dust_healing.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_dust_healing.png new file mode 100644 index 00000000..218e92df Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_dust_healing.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_dust_ice.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_dust_ice.png new file mode 100644 index 00000000..73122cf1 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_dust_ice.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_dust_lightning.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_dust_lightning.png new file mode 100644 index 00000000..b8634fe6 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_dust_lightning.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_dust_necromancy.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_dust_necromancy.png new file mode 100644 index 00000000..afcbaf87 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_dust_necromancy.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/items/spectral_dust_sorcery.png b/src/main/resources/assets/ebwizardry/textures/items/spectral_dust_sorcery.png new file mode 100644 index 00000000..666caa7c Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/items/spectral_dust_sorcery.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/block_highlight.png b/src/main/resources/assets/ebwizardry/textures/particle/block_highlight.png new file mode 100644 index 00000000..8e6c4e01 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/block_highlight.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/cloud_0.png b/src/main/resources/assets/ebwizardry/textures/particle/cloud_0.png new file mode 100644 index 00000000..a2f8dd5f Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/cloud_0.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/cloud_1.png b/src/main/resources/assets/ebwizardry/textures/particle/cloud_1.png new file mode 100644 index 00000000..c424e60f Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/cloud_1.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/cloud_2.png b/src/main/resources/assets/ebwizardry/textures/particle/cloud_2.png new file mode 100644 index 00000000..7e49bfcc Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/cloud_2.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/cloud_3.png b/src/main/resources/assets/ebwizardry/textures/particle/cloud_3.png new file mode 100644 index 00000000..a53b59f4 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/particle/cloud_3.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/particle/summon.png b/src/main/resources/assets/ebwizardry/textures/particle/summon.png deleted file mode 100644 index 58c7ceca..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/particle/summon.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/blinding_flash.png b/src/main/resources/assets/ebwizardry/textures/spells/blinding_flash.png new file mode 100644 index 00000000..09298467 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/blinding_flash.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/boulder.png b/src/main/resources/assets/ebwizardry/textures/spells/boulder.png new file mode 100644 index 00000000..f0507e85 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/boulder.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/celestial_smite.png b/src/main/resources/assets/ebwizardry/textures/spells/celestial_smite.png new file mode 100644 index 00000000..a2858b74 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/celestial_smite.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/enrage.png b/src/main/resources/assets/ebwizardry/textures/spells/enrage.png new file mode 100644 index 00000000..6fcf177a Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/enrage.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/fangs.png b/src/main/resources/assets/ebwizardry/textures/spells/fangs.png new file mode 100644 index 00000000..80bdea4a Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/fangs.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/flamecatcher.png b/src/main/resources/assets/ebwizardry/textures/spells/flamecatcher.png new file mode 100644 index 00000000..aaebf85d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/flamecatcher.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 index 16fbe282..8891a5a1 100644 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/forest_of_thorns.png 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_barrier.png b/src/main/resources/assets/ebwizardry/textures/spells/frost_barrier.png new file mode 100644 index 00000000..b53ac71d Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/frost_barrier.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/frost_shield.png b/src/main/resources/assets/ebwizardry/textures/spells/frost_shield.png new file mode 100644 index 00000000..3268d351 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/frost_shield.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/mark_sacrifice.png b/src/main/resources/assets/ebwizardry/textures/spells/mark_sacrifice.png new file mode 100644 index 00000000..c52f7d24 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/mark_sacrifice.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/mirage.png b/src/main/resources/assets/ebwizardry/textures/spells/mirage.png new file mode 100644 index 00000000..e4743e91 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/mirage.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/healing_totem.png b/src/main/resources/assets/ebwizardry/textures/spells/radiant_totem.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/spells/healing_totem.png rename to src/main/resources/assets/ebwizardry/textures/spells/radiant_totem.png diff --git a/src/main/resources/assets/ebwizardry/textures/spells/shapeshift.png b/src/main/resources/assets/ebwizardry/textures/spells/shapeshift.png deleted file mode 100644 index 8d151467..00000000 Binary files a/src/main/resources/assets/ebwizardry/textures/spells/shapeshift.png and /dev/null differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/stormcloud.png b/src/main/resources/assets/ebwizardry/textures/spells/stormcloud.png new file mode 100644 index 00000000..27340d0b Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/stormcloud.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/withering_totem.png b/src/main/resources/assets/ebwizardry/textures/spells/withering_totem.png new file mode 100644 index 00000000..75ea6800 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/withering_totem.png differ diff --git a/src/main/resources/assets/ebwizardry/textures/spells/zombie_apocalypse.png b/src/main/resources/assets/ebwizardry/textures/spells/zombie_apocalypse.png new file mode 100644 index 00000000..5d4bc1d3 Binary files /dev/null and b/src/main/resources/assets/ebwizardry/textures/spells/zombie_apocalypse.png differ diff --git a/src/main/resources/assets/minecraft/textures/entity/banner/wizardry_earth.png b/src/main/resources/assets/minecraft/textures/entity/banner/wizardry_earth.png new file mode 100644 index 00000000..7a45eb1d Binary files /dev/null and b/src/main/resources/assets/minecraft/textures/entity/banner/wizardry_earth.png differ diff --git a/src/main/resources/assets/minecraft/textures/entity/banner/wizardry_fire.png b/src/main/resources/assets/minecraft/textures/entity/banner/wizardry_fire.png new file mode 100644 index 00000000..70b91640 Binary files /dev/null and b/src/main/resources/assets/minecraft/textures/entity/banner/wizardry_fire.png differ diff --git a/src/main/resources/assets/minecraft/textures/entity/banner/wizardry_healing.png b/src/main/resources/assets/minecraft/textures/entity/banner/wizardry_healing.png new file mode 100644 index 00000000..4d535e16 Binary files /dev/null and b/src/main/resources/assets/minecraft/textures/entity/banner/wizardry_healing.png differ diff --git a/src/main/resources/assets/minecraft/textures/entity/banner/wizardry_ice.png b/src/main/resources/assets/minecraft/textures/entity/banner/wizardry_ice.png new file mode 100644 index 00000000..e048b964 Binary files /dev/null and b/src/main/resources/assets/minecraft/textures/entity/banner/wizardry_ice.png differ diff --git a/src/main/resources/assets/minecraft/textures/entity/banner/wizardry_lightning.png b/src/main/resources/assets/minecraft/textures/entity/banner/wizardry_lightning.png new file mode 100644 index 00000000..b1306595 Binary files /dev/null and b/src/main/resources/assets/minecraft/textures/entity/banner/wizardry_lightning.png differ diff --git a/src/main/resources/assets/minecraft/textures/entity/banner/wizardry_necromancy.png b/src/main/resources/assets/minecraft/textures/entity/banner/wizardry_necromancy.png new file mode 100644 index 00000000..6bfb8cf2 Binary files /dev/null and b/src/main/resources/assets/minecraft/textures/entity/banner/wizardry_necromancy.png differ diff --git a/src/main/resources/assets/minecraft/textures/entity/banner/wizardry_sorcery.png b/src/main/resources/assets/minecraft/textures/entity/banner/wizardry_sorcery.png new file mode 100644 index 00000000..601e6c05 Binary files /dev/null and b/src/main/resources/assets/minecraft/textures/entity/banner/wizardry_sorcery.png differ diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info index 2f2f961d..75ac2eea 100644 --- a/src/main/resources/mcmod.info +++ b/src/main/resources/mcmod.info @@ -2,10 +2,10 @@ { "modid" : "ebwizardry", "name" : "Electroblob's Wizardry", - "version" : "4.2.11", + "version" : "4.3", "mcversion" : "1.12.2", "url" : "https://minecraft.curseforge.com/projects/electroblobs-wizardry", - "credits" : "\nDesigned, coded and textured by Electroblob.\nCode contributed by: Corail31, 12foo, Shadows-of-Fire, Tora-B, Avatair, Aeronica, UltraHex, Azim-Palmer, raoulvdberge.\nTranslators: MadWrist (Spanish, Mexican Spanish), VilagVil, kellixon & bigenergy (Russian), Hahdrim (French), lorrampi (Brazilian Portuguese), ZHENGLOC & dragon-evol (Chinese), shejery & rewi_wire (Korean), Trozuu (Polish), chesterccj305 (Taiwanese).", + "credits" : "\nDesigned, coded and textured by Electroblob.\nCode contributed by: Corail31, 12foo, Shadows-of-Fire, Tora-B, Avatair, Aeronica, UltraHex, Azim-Palmer, raoulvdberge.\nTranslators: Alsentar (Spanish), MadWrist (Mexican Spanish), VilagVil, kellixon, bigenergy & MugGod2 (Russian), Hahdrim (French), lorrampi (Brazilian Portuguese), ZHENGLOC, dragon-evol, Hokorizero, TUsama & Determancer (Chinese - Simplified), shejery & rewi_wire (Korean), Trozuu (Polish), chesterccj305 (Chinese - Traditional), Bombadil (Hungarian).\nSound Effects: OhhWowProductions, fredzed, deleted_user_3277771, DiscoveryME, OGSoundFX, leosalom, juskiddink", "authorList" : [ "Electroblob" ],