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 extends EntityLivingBase> 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 extends IBakedModel, Matrix4f> 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 extends Entity> 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 extends Entity> 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 extends T>> layerFactory){
+
+ for(Class extends Entity> 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 extends EntityLivingBase>> 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 extends Item> 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 super ItemStack> 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