That's one heck of a commit you've got there...

I may have got a bit behind with version control. A lot behind, in fact. Maybe I'll go back and split this sometime - then again, I probably won't. But hey, at least it's here!
This commit is contained in:
Electroblob77
2019-08-18 00:04:18 +01:00
parent 2680d02304
commit f37812be3e
1564 changed files with 47652 additions and 12984 deletions
+5 -2
View File
@@ -20,6 +20,9 @@ build
# other # other
eclipse eclipse
run run
misc logs
libs /misc
/libs
libsfordeobf
generated
Thumbs.db Thumbs.db
+36 -3
View File
@@ -11,17 +11,39 @@ apply plugin: 'net.minecraftforge.gradle.forge'
//Only edit below this line, the above code adds and enables the necessary things for Forge to be setup. //Only edit below this line, the above code adds and enables the necessary things for Forge to be setup.
version = " 4.1.4 - MC 1.12.2" version = "4.2.0" // There, it matches semver, happy now? (I am just gonna rename the file afterwards, you know that right?)
group= "electroblob.wizardry"// http://maven.apache.org/guides/mini/guide-naming-conventions.html group= "electroblob.wizardry"// http://maven.apache.org/guides/mini/guide-naming-conventions.html
archivesBaseName = "Electroblob's Wizardry " archivesBaseName = "ElectroblobsWizardry"
sourceCompatibility = targetCompatibility = "1.8" // Need this here so eclipse task generates correctly. sourceCompatibility = targetCompatibility = "1.8" // Need this here so eclipse task generates correctly.
compileJava { compileJava {
sourceCompatibility = targetCompatibility = "1.8" sourceCompatibility = targetCompatibility = "1.8"
} }
repositories {
maven {
// location of the maven that hosts JEI files
name = "Progwml6 maven"
url = "http://dvs1.progwml6.com/files/maven"
}
maven {
// location of a maven mirror for JEI files, as a fallback
name = "ModMaven"
url = "modmaven.k-4u.nl"
}
flatDir {
// evErYthINg hAS tO Be a mAVeN rEPoSiTOrY... (to use deobfProvided, for some reason)
// The lack of documentation is actually quite impressive, see the following threads:
// http://www.minecraftforge.net/forum/topic/44262-solved-how-to-build-mod-with-dependencies/
// https://www.minecraftforum.net/forums/mapping-and-modding-java-edition/mapping-and-modding-tutorials/2866817-setting-up-dependencies-in-minecraft-forge-with
// http://www.minecraftforge.net/forum/topic/44534-adding-dependencies-to-a-forge-project/
dirs "libsfordeobf" // Don't use libs or forge will think you have duplicate mods
}
}
minecraft { minecraft {
version = "1.12.2-14.23.2.2611" version = "1.12.2-14.23.5.2814"
runDir = "run" runDir = "run"
// the mappings can be changed at any time, and must be in the following format. // the mappings can be changed at any time, and must be in the following format.
@@ -38,6 +60,17 @@ dependencies {
// or you may define them like so.. // or you may define them like so..
//compile "some.group:artifact:version:classifier" //compile "some.group:artifact:version:classifier"
//compile "some.group:artifact:version" //compile "some.group:artifact:version"
// compile against the JEI API but do not include it at runtime
deobfProvided "mezz.jei:jei_${mc_version}:${jei_version}:api"
// at runtime, use the full JEI jar
runtime "mezz.jei:jei_${mc_version}:${jei_version}"
//deobfProvided "baubles:baubles-1.12:1.5.2:api" // #ihavenoideawhatimdoing #itjustworks
//runtime "baubles:baubles-1.12:1.5.2" // If in doubt, copy what already works
//deobfProvided "antiqueatlas:antiqueatlas-1.12.2:4.5.1:src"
//runtime "antiqueatlas:antiqueatlas-1.12.2:4.5.1"
// real examples // real examples
//compile 'com.mod-buildcraft:buildcraft:6.0.8:dev' // adds buildcraft to the dev env //compile 'com.mod-buildcraft:buildcraft:6.0.8:dev' // adds buildcraft to the dev env
+2
View File
@@ -1,3 +1,5 @@
# Sets default memory used for gradle commands. Can be overridden by user or command line properties. # Sets default memory used for gradle commands. Can be overridden by user or command line properties.
# This is required to provide enough memory for the Minecraft decompilation process. # This is required to provide enough memory for the Minecraft decompilation process.
org.gradle.jvmargs=-Xmx3G org.gradle.jvmargs=-Xmx3G
mc_version=1.12.2
jei_version=4.13.1.225
Vendored
+2 -2
View File
@@ -79,14 +79,14 @@ if [ -n "$JAVA_HOME" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." locations of your Java installation."
fi fi
else else
JAVACMD="java" JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." locations of your Java installation."
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
Vendored
+2 -2
View File
@@ -27,7 +27,7 @@ echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo. echo.
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation. echo locations of your Java installation.
goto fail goto fail
@@ -41,7 +41,7 @@ echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo. echo.
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation. echo locations of your Java installation.
goto fail goto fail
@@ -1,33 +1,32 @@
package electroblob.wizardry; package electroblob.wizardry;
import electroblob.wizardry.client.particle.ParticleWizardry;
import electroblob.wizardry.item.ItemSpectralBow; import electroblob.wizardry.item.ItemSpectralBow;
import electroblob.wizardry.packet.PacketCastContinuousSpell; import electroblob.wizardry.packet.*;
import electroblob.wizardry.packet.PacketCastSpell;
import electroblob.wizardry.packet.PacketClairvoyance;
import electroblob.wizardry.packet.PacketGlyphData;
import electroblob.wizardry.packet.PacketNPCCastSpell.Message;
import electroblob.wizardry.packet.PacketPlayerSync;
import electroblob.wizardry.packet.PacketTransportation;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.spell.Spell; import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.ParticleBuilder.Type;
import net.minecraft.block.state.IBlockState; import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvent; import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.Style;
import net.minecraft.util.text.TextFormatting;
import net.minecraft.util.text.translation.I18n; import net.minecraft.util.text.translation.I18n;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.common.config.Property; import net.minecraftforge.common.config.Property;
import java.util.List;
import java.util.Set;
/** /**
* The common proxy for wizardry, serving the usual purpose of dealing with all things that need to be handled * The common proxy for wizardry, serving the usual purpose of dealing with all things that need to be handled
* differently on the client and the server. A lot of the methods here appear to do absolutely nothing; this is because * differently on the client and the server. A lot of the methods here appear to do absolutely nothing; this is because
* they do client-only things which are only handled in the client proxy. * they do client-only things which are only handled in the client proxy.
* *
* @see {@link electroblob.wizardry.client.ClientProxy} * @see electroblob.wizardry.client.ClientProxy ClientProxy
* @author Electroblob * @author Electroblob
* @since Wizardry 1.0 * @since Wizardry 1.0
*/ */
@@ -51,15 +50,19 @@ public class CommonProxy {
public void registerResourceReloadListeners(){} public void registerResourceReloadListeners(){}
public void registerSoundEventListener(){}
public void registerAtlasMarkers(){}
// SECTION Particles // SECTION Particles
// =============================================================================================================== // ===============================================================================================================
/** Called from init() in the main mod class to initialise the particle factories. */ /** Called from init() in the main mod class to initialise the particle factories. */
public void initParticleFactories(){} // Does nothing since particles are client-side only public void registerParticles(){} // Does nothing since particles are client-side only
/** Creates a new particle of the specified type from the appropriate particle factory. <i>Does not actually spawn the /** Creates a new particle of the specified type from the appropriate particle factory. <i>Does not actually spawn the
* particle; use {@link electroblob.wizardry.util.ParticleBuilder ParticleBuilder} to spawn particles.</i> */ * particle; use {@link electroblob.wizardry.util.ParticleBuilder ParticleBuilder} to spawn particles.</i> */
public ParticleWizardry createParticle(Type type, World world, double x, double y, double z){ public electroblob.wizardry.client.particle.ParticleWizardry createParticle(ResourceLocation type, World world, double x, double y, double z){
return null; return null;
} }
@@ -70,19 +73,23 @@ public class CommonProxy {
// SECTION Items // SECTION Items
// =============================================================================================================== // ===============================================================================================================
public boolean shouldDisplayDiscovered(Spell spell, ItemStack stack){
return false;
}
public net.minecraft.client.gui.FontRenderer getFontRenderer(ItemStack stack){ public net.minecraft.client.gui.FontRenderer getFontRenderer(ItemStack stack){
return null; return null;
} }
/** /**
* Returns the translated name of the scroll, taking spell discovery into account. If, for some reason, this gets * Returns the translated name of the scroll, taking spell discovery into account. If, for some reason, this gets
* called server-side, it uses the deprecated server version of I18n, with a warning. Since any likely server-side * called server-side, it uses the deprecated server version of I18n. Since any likely server-side
* use of this method will be for text-based logic purposes (like Bibliocraft's book checking system), and since no * use of this method will be for text-based logic purposes (like Bibliocraft's book checking system), and since
* particular player instance can be accessed, spell discovery is ignored. * no particular player instance can be accessed, spell discovery is ignored.
*/ */
public String getScrollDisplayName(ItemStack scroll){ public String getScrollDisplayName(ItemStack scroll){
Spell spell = Spell.get(scroll.getItemDamage()); Spell spell = Spell.byMetadata(scroll.getItemDamage());
return I18n.translateToLocalFormatted("item." + Wizardry.MODID + ":scroll.name", return I18n.translateToLocalFormatted("item." + Wizardry.MODID + ":scroll.name",
I18n.translateToLocal("spell." + spell.getUnlocalisedName())).trim(); I18n.translateToLocal("spell." + spell.getUnlocalisedName())).trim();
@@ -92,29 +99,53 @@ public class CommonProxy {
return ((ItemSpectralBow)WizardryItems.spectral_bow).getDefaultDurabilityForDisplay(stack); return ((ItemSpectralBow)WizardryItems.spectral_bow).getDefaultDurabilityForDisplay(stack);
} }
/** Like {@link CommonProxy#addMultiLineDescription(List, String, Style)}, but style defaults to light grey. */
public void addMultiLineDescription(List<String> tooltip, String key){
this.addMultiLineDescription(tooltip, key, new Style().setColor(TextFormatting.GRAY));
}
/**
* Adds a multi-line description to the given tooltip list. The description is first translated using the given
* translation key, then the formatting code for the given style is appended, and finally the string is word-wrapped
* to the standard width (100).
* @param tooltip The tooltip list to add to
* @param key The translation key for the description
* @param style A style to apply
*/
public void addMultiLineDescription(List<String> tooltip, String key, Style style){}
// SECTION Packet Handlers // SECTION Packet Handlers
// =============================================================================================================== // ===============================================================================================================
public void handlePlayerSyncPacket(PacketPlayerSync.Message message){ public void handlePlayerSyncPacket(PacketPlayerSync.Message message){}
}
public void handleGlyphDataPacket(PacketGlyphData.Message message){ public void handleGlyphDataPacket(PacketGlyphData.Message message){}
}
public void handleCastSpellPacket(PacketCastSpell.Message message){ public void handleEmitterDataPacket(PacketEmitterData.Message message){}
}
public void handleCastContinuousSpellPacket(PacketCastContinuousSpell.Message message){ public void handleCastSpellPacket(PacketCastSpell.Message message){}
}
public void handleNPCCastSpellPacket(Message message){ public void handleCastContinuousSpellPacket(PacketCastContinuousSpell.Message message){}
}
public void handleTransportationPacket(PacketTransportation.Message message){ public void handleNPCCastSpellPacket(PacketNPCCastSpell.Message message){}
}
public void handleClairvoyancePacket(PacketClairvoyance.Message message){ public void handleDispenserCastSpellPacket(PacketDispenserCastSpell.Message message){}
}
public void handleCastSpellAtPosPacket(PacketCastSpellAtPos.Message message){}
public void handleTransportationPacket(PacketTransportation.Message message){}
public void handleClairvoyancePacket(PacketClairvoyance.Message message){}
public void handleAdvancementSyncPacket(PacketSyncAdvancements.Message message){}
public void handleEndSlowTimePacket(PacketEndSlowTime.Message message){}
public void handleResurrectionPacket(PacketResurrection.Message message){}
public void handlePossessionPacket(PacketPossession.Message message){}
public void handleConquerShrinePacket(PacketConquerShrine.Message message){}
// SECTION Misc // SECTION Misc
// =============================================================================================================== // ===============================================================================================================
@@ -122,6 +153,8 @@ public class CommonProxy {
public void setToNumberSliderEntry(Property property){} public void setToNumberSliderEntry(Property property){}
public void setToHUDChooserEntry(Property property){} public void setToHUDChooserEntry(Property property){}
public void setToNamedBooleanEntry(Property property){}
// public void setToEntityNameEntry(Property property){} // public void setToEntityNameEntry(Property property){}
@@ -137,6 +170,75 @@ public class CommonProxy {
*/ */
public void playMovingSound(Entity entity, SoundEvent sound, SoundCategory category, float volume, float pitch, boolean repeat){} public void playMovingSound(Entity entity, SoundEvent sound, SoundCategory category, float volume, float pitch, boolean repeat){}
/**
* Plays a continuous spell sound which moves with the given entity.
*
* @param entity The source of the sound
* @param spell The spell this sound is associated with
* @param start The starting SoundEvent to play
* @param loop The main looped SoundEvent to play
* @param end The ending SoundEvent to play
* @param category The SoundCategory to use
* @param volume Volume relative to 1
* @param pitch Pitch relative to 1
*/
public void playSpellSoundLoop(EntityLivingBase entity, Spell spell, SoundEvent start, SoundEvent loop, SoundEvent end, SoundCategory category, float volume, float pitch){}
/**
* Plays a continuous spell sound which moves with the given entity.
*
* @param entity The source of the sound
* @param spell The spell this sound is associated with
* @param sounds An array of the sound events to play, which must have three elements in the order: start, loop, end
* @param category The SoundCategory to use
* @param volume Volume relative to 1
* @param pitch Pitch relative to 1
* @throws IllegalArgumentException if the given array contains less than 3 sound events
*/
public void playSpellSoundLoop(EntityLivingBase entity, Spell spell, SoundEvent[] sounds, SoundCategory category, float volume, float pitch){
if(sounds.length < 3) throw new IllegalArgumentException("Tried to play a continuous spell sound using an array "
+ "of sound events, but the given array contained less than 3 sound events!");
playSpellSoundLoop(entity, spell, sounds[0], sounds[1], sounds[2], category, volume, pitch);
}
/**
* Plays a continuous spell sound at the given position.
* @param world The world in which to play the sound
* @param x The x coordinate to play the sound at
* @param y The y coordinate to play the sound at
* @param z The z coordinate to play the sound at
* @param spell The spell this sound is associated with
* @param start The starting SoundEvent to play
* @param loop The main looped SoundEvent to play
* @param end The ending SoundEvent to play
* @param category The SoundCategory to use
* @param volume Volume relative to 1
* @param pitch Pitch relative to 1
* @param duration The duration of the sound, or -1 to link it to a dispenser at the given coordinates
*/
public void playSpellSoundLoop(World world, double x, double y, double z, Spell spell, SoundEvent start, SoundEvent loop, SoundEvent end, SoundCategory category, float volume, float pitch, int duration){}
/**
* Plays a continuous spell sound at the given position.
*
* @param world The world in which to play the sound
* @param x The x coordinate to play the sound at
* @param y The y coordinate to play the sound at
* @param z The z coordinate to play the sound at
* @param spell The spell this sound is associated with
* @param sounds An array of the sound events to play, which must have three elements in the order: start, loop, end
* @param category The SoundCategory to use
* @param volume Volume relative to 1
* @param pitch Pitch relative to 1
* @param duration The duration of the sound, or -1 to link it to a dispenser at the given coordinates
* @throws IllegalArgumentException if the given array contains less than 3 sound events
*/
public void playSpellSoundLoop(World world, double x, double y, double z, Spell spell, SoundEvent[] sounds, SoundCategory category, float volume, float pitch, int duration){
if(sounds.length < 3) throw new IllegalArgumentException("Tried to play a continuous spell sound using an array "
+ "of sound events, but the given array contained less than 3 sound events!");
playSpellSoundLoop(world, x, y, z, spell, sounds[0], sounds[1], sounds[2], category, volume, pitch, duration);
}
/** /**
* Gets the client side world using Minecraft.getMinecraft().world. <b>Only to be called client side!</b> Returns * Gets the client side world using Minecraft.getMinecraft().world. <b>Only to be called client side!</b> Returns
* null on the server side. * null on the server side.
File diff suppressed because it is too large Load Diff
@@ -1,23 +1,22 @@
package electroblob.wizardry; package electroblob.wizardry;
import org.apache.logging.log4j.Logger;
import electroblob.wizardry.command.CommandCastSpell; import electroblob.wizardry.command.CommandCastSpell;
import electroblob.wizardry.command.CommandDiscoverSpell; import electroblob.wizardry.command.CommandDiscoverSpell;
import electroblob.wizardry.command.CommandSetAlly; import electroblob.wizardry.command.CommandSetAlly;
import electroblob.wizardry.command.CommandViewAllies; import electroblob.wizardry.command.CommandViewAllies;
import electroblob.wizardry.data.DispenserCastingData;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.integration.antiqueatlas.WizardryAntiqueAtlasIntegration;
import electroblob.wizardry.integration.baubles.WizardryBaublesIntegration;
import electroblob.wizardry.misc.Forfeit;
import electroblob.wizardry.packet.WizardryPacketHandler; import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.WizardryAdvancementTriggers; import electroblob.wizardry.registry.*;
import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.registry.WizardryRegistry; import electroblob.wizardry.util.CustomSoundCategory;
import electroblob.wizardry.registry.WizardryTabs; import electroblob.wizardry.util.SpellProperties;
import electroblob.wizardry.worldgen.*;
import net.minecraft.item.Item; import net.minecraft.item.Item;
import net.minecraft.nbt.NBTBase;
import net.minecraft.util.EnumFacing;
import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.Capability.IStorage;
import net.minecraftforge.common.capabilities.CapabilityManager;
import net.minecraftforge.event.RegistryEvent; import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.Mod.EventHandler; import net.minecraftforge.fml.common.Mod.EventHandler;
@@ -30,12 +29,13 @@ import net.minecraftforge.fml.common.event.FMLServerStartingEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.network.NetworkRegistry; import net.minecraftforge.fml.common.network.NetworkRegistry;
import net.minecraftforge.fml.common.registry.GameRegistry; import net.minecraftforge.fml.common.registry.GameRegistry;
import org.apache.logging.log4j.Logger;
/** /**
* <i>"Electroblob's Wizardry adds an RPG-like system of spells to Minecraft, with the aim of being as playable as * <i>"Electroblob's Wizardry adds an RPG-like system of spells to Minecraft, with the aim of being as playable as
* possible. No crazy constructs, no perk trees, no complex recipes - simply find spell books, cast spells, and master * possible. No crazy constructs, no perk trees, no complex recipes - simply find spell books, cast spells, and master
* the arcane! - But you knew that, right?"</i> * the arcane! - But you knew that, right?"</i>
* <p> * <p></p>
* Main mod class for Wizardry. Contains the logger and settings instances, along with all the other stuff that's normally * Main mod class for Wizardry. Contains the logger and settings instances, along with all the other stuff that's normally
* in a main mod class. * in a main mod class.
* @author Electroblob * @author Electroblob
@@ -50,9 +50,9 @@ public class Wizardry {
public static final String NAME = "Electroblob's Wizardry"; public static final String NAME = "Electroblob's Wizardry";
/** /**
* The version number for this version of wizardry. The following system is used for version numbers: * The version number for this version of wizardry. The following system is used for version numbers:
* <p> * <p></p>
* <center><b>[major Minecraft version].[major mod version].[minor mod version/patch] * <center><b>[major Minecraft version].[major mod version].[minor mod version/patch]
* <p> * <p></p>
* </center></b> The major mod version is consistent across Minecraft versions, i.e. Wizardry 1.1 has the same * </center></b> The major mod version is consistent across Minecraft versions, i.e. Wizardry 1.1 has the same
* features as Wizardry 2.1, but they are for different versions of Minecraft and have separate minor versioning. * features as Wizardry 2.1, but they are for different versions of Minecraft and have separate minor versioning.
* 1.x.x represents Minecraft 1.7.x versions, 2.x.x represents Minecraft 1.10.x versions, 3.x.x represents Minecraft * 1.x.x represents Minecraft 1.7.x versions, 2.x.x represents Minecraft 1.10.x versions, 3.x.x represents Minecraft
@@ -60,56 +60,28 @@ public class Wizardry {
*/ */
public static final String VERSION = "4.2.0"; public static final String VERSION = "4.2.0";
// IDEA: Improve the algorithm that finds a place to summon creatures to take walls into account.
// IDEA: Replace all uses of Math.cos and Math.sin with MathHelper versions
// IDEA: Triggering of inbuilt Forge events in relevant places? // IDEA: Triggering of inbuilt Forge events in relevant places?
// IDEA: Abstract the vanilla particles behind the particle builder // IDEA: Abstract the vanilla particles behind the particle builder
/* Minor bugs that need fixing at some point:
* - Player skin hat layer shows through wizard hats - Wizard armour breaks rather than just running out of mana (I
* can't seem to replicate this bug, but I have had it happen to me before...)
* - Shift-clicking a stack of special upgrades when in the arcane workbench causes the whole stack to be
* transferred when it should be just one (this is a bug with vanilla as well - try putting a stack of bottles into
* a brewing stand). I have at least made it so only one gets used now, so it has no impact on the game.
* - When a spell is on cooldown, you can't break blocks when holding a wand. */
// TODO: So somehow I have managed to overlook the fact that health is actually a float. What this means is that
// I can make the healing spells use damage multipliers - hurrah!
// TODO: Switch from IInventory to IItemHandler (Or don't. It's only useful for automation really.)
// TODO: Have particles obey Minecraft's particle setting where appropriate // TODO: Have particles obey Minecraft's particle setting where appropriate
// (see https://github.com/RootsTeam/Embers/blob/master/src/main/java/teamroots/embers/particle/ParticleUtil.java) // (see https://github.com/RootsTeam/Embers/blob/master/src/main/java/teamroots/embers/particle/ParticleUtil.java)
// TODO: Go over all the worldgen code, use IWorldGenerator
// TODO: Implement a continuous sound system using MovingSoundEntity, allowing continuous spells to have a long sound
// loop as well as a start and end sound
// TODO: EntityMagicArrow needs attention
// TODO: Go over particle spawning on projectile impact and make sure hitvec is used wherever appropriate
// TODO: Replace spell IDs in packets with ResourceLocation strings
// TODO: Forcefield needs looking at, esp. with regards to projectiles and explosions
// TODO: TileEntityArcaneWorkbench needs looking at, esp. regarding inventory and markDirty // TODO: TileEntityArcaneWorkbench needs looking at, esp. regarding inventory and markDirty
// TODO: Fireskin somehow lost its particles // TODO: See what can be done with the illager spell sounds (and go over sounds in general)
// TODO: Convert settings over to the @Config system // TODO: Fix possession
// TODO: Go through listeners of LivingHurtEvent and decide whether they should change to LivingDamageEvent // TODO: Fix charge
// NOTE: Add melee upgrades to loot tables when they are added.
/** Static instance of the {@link Settings} object for Wizardry. */ /** Static instance of the {@link Settings} object for Wizardry. */
public static final Settings settings = new Settings(); public static final Settings settings = new Settings();
/** Static instance of the {@link Logger} object for Wizardry. /** Static instance of the {@link Logger} object for Wizardry.
* <p> * <p></p>
* Logging conventions for wizardry (only these levels are used currently): * Logging conventions for wizardry (only these levels are used currently):
* <p> * <p></p>
* - <b>ERROR</b>: Anything that threw an exception; may or may not crash the game.<br> * - <b>ERROR</b>: Anything that threw an exception; may or may not crash the game.<br>
* - <b>WARN</b>: Anything that isn't supposed to happen during normal operation, but didn't throw an exception.<br> * - <b>WARN</b>: Anything that isn't supposed to happen during normal operation, but didn't throw an exception.<br>
* - <b>INFO</b>: Anything that might happen during normal mod operation that the user needs to know about. */ * - <b>INFO</b>: Anything that might happen during normal mod operation that the user needs to know about. */
public static Logger logger; public static Logger logger;
// private static Pattern entityNamePattern;
// EventManager
WizardryWorldGenerator generator = new WizardryWorldGenerator();
// The instance of wizardry that Forge uses. // The instance of wizardry that Forge uses.
@Instance(Wizardry.MODID) @Instance(Wizardry.MODID)
public static Wizardry instance; public static Wizardry instance;
@@ -123,40 +95,27 @@ public class Wizardry {
logger = event.getModLog(); logger = event.getModLog();
proxy.registerResourceReloadListener(); proxy.registerResourceReloadListeners();
settings.initConfig(event); settings.initConfig(event);
// Yes - by the looks of it, having an interface is completely unnecessary in this case. // Capabilities
CapabilityManager.INSTANCE.register(WizardData.class, new IStorage<WizardData>(){ WizardData.register();
// These methods are only called by Capability.writeNBT() or Capability.readNBT(), which in turn are DispenserCastingData.register();
// NEVER CALLED. Unless I'm missing some reflective invocation, that means this entire class serves only
// to allow capabilities to be saved and loaded manually. What that would be useful for I don't know.
// (If an API forces most users to write redundant code for no reason, it's not user friendly, is it?)
// ... well, that's my rant for today!
@Override
public NBTBase writeNBT(Capability<WizardData> capability, WizardData instance, EnumFacing side){
return null;
}
@Override
public void readNBT(Capability<WizardData> capability, WizardData instance, EnumFacing side, NBTBase nbt){
}
}, WizardData::new);
WizardryRegistry.registerTileEntities();
WizardryRegistry.registerEntities();
// The check for the generateLoot setting is now done within this method.
WizardryRegistry.registerLoot();
// Register things that don't have registries
WizardryBlocks.registerTileEntities();
WizardryLoot.register();
WizardryAdvancementTriggers.register(); WizardryAdvancementTriggers.register();
Forfeit.register();
// Moved to preInit, because apparently it has to be here now. // Client-side stuff (via proxies)
proxy.registerRenderers(); proxy.registerRenderers();
// It seems this also has to be here
proxy.registerKeyBindings(); proxy.registerKeyBindings();
WizardryBaublesIntegration.init();
WizardryAntiqueAtlasIntegration.init();
} }
@EventHandler @EventHandler
@@ -164,26 +123,41 @@ public class Wizardry {
settings.initConfigExtras(); settings.initConfigExtras();
// Event Handlers // World generators
GameRegistry.registerWorldGenerator(generator, 0); // Weight is a misnomer, it's actually the priority (where lower numbers get generated first)
MinecraftForge.EVENT_BUS.register(instance); // Literally nothing on typical 'weight' values here, there isn't even an upper limit
// Examples I've managed to find:
// - Tinker's construct slime islands use 25
GameRegistry.registerWorldGenerator(new WorldGenCrystalOre(), 0);
GameRegistry.registerWorldGenerator(new WorldGenCrystalFlower(), 50);
GameRegistry.registerWorldGenerator(new WorldGenWizardTower(), 20);
GameRegistry.registerWorldGenerator(new WorldGenObelisk(), 20);
GameRegistry.registerWorldGenerator(new WorldGenShrine(), 20);
// This is for the config change and missing mappings events
MinecraftForge.EVENT_BUS.register(instance); // Since there's already an instance we might as well use it
NetworkRegistry.INSTANCE.registerGuiHandler(this, new WizardryGuiHandler()); NetworkRegistry.INSTANCE.registerGuiHandler(this, new WizardryGuiHandler());
WizardryPacketHandler.initPackets(); WizardryPacketHandler.initPackets();
// Post-registry extras
WizardryItems.populateWandMap();
WizardryItems.populateArmourMap();
WizardryItems.registerDispenseBehaviours();
Spell.registry.forEach(Spell::init);
SpellProperties.init();
// Client-side stuff (via proxies)
proxy.initGuiBits(); proxy.initGuiBits();
proxy.initParticleFactories(); proxy.registerParticles();
proxy.registerSoundEventListener();
WizardrySounds.SPELLS = CustomSoundCategory.add(Wizardry.MODID + ":spells");
} }
@EventHandler @EventHandler
public void postInit(FMLPostInitializationEvent event){ public void postInit(FMLPostInitializationEvent event){
// This needs to be here or it won't necessarily include all the mods' entities.
// TODO: Re-implement this when the Forge bug is fixed.
/* Doesn't seem to be doing anything... String entityNames = ""; for(Object name :
* EntityList.classToStringMapping.values()){ if(name instanceof String){ entityNames = entityNames + name +
* '|'; } } // Cuts off the last '|' entityNames = entityNames.substring(0, entityNames.length()-1);
* entityNamePattern = Pattern.compile(entityNames); */
proxy.initialiseLayers(); proxy.initialiseLayers();
WizardryTabs.sort();
} }
@EventHandler @EventHandler
@@ -205,23 +179,30 @@ public class Wizardry {
// 2.1 changed some item ids, so this fixes them for existing worlds // 2.1 changed some item ids, so this fixes them for existing worlds
// Nobody updates minecraft versions when using mods, but I may as well leave this here just in case. // Nobody updates minecraft versions when using mods, but I may as well leave this here just in case.
@SubscribeEvent @SubscribeEvent
public static void onMissingMappingEvent(RegistryEvent.MissingMappings<Item> event){ public static void onMissingItemMappingEvent(RegistryEvent.MissingMappings<Item> event){
// Just get, not getAll, since the mod id didn't change! // Just get, not getAll, since the mod id didn't change!
for(RegistryEvent.MissingMappings.Mapping<Item> mapping : event.getAllMappings()){ for(RegistryEvent.MissingMappings.Mapping<Item> mapping : event.getAllMappings()){
if(mapping.key.getResourceDomain().equals(Wizardry.MODID)){ if(mapping.key.getNamespace().equals(Wizardry.MODID)){
Item replacement; Item replacement;
switch(mapping.key.getResourcePath()){ switch(mapping.key.getPath()){
case "wand_basic": replacement = WizardryItems.magic_wand; break; case "wand_basic": replacement = WizardryItems.magic_wand; break;
case "wand_basic_fire": replacement = WizardryItems.basic_fire_wand; break; case "wand_basic_fire": replacement = WizardryItems.novice_fire_wand; break;
case "wand_basic_ice": replacement = WizardryItems.basic_ice_wand; break; case "wand_basic_ice": replacement = WizardryItems.novice_ice_wand; break;
case "wand_basic_lightning": replacement = WizardryItems.basic_lightning_wand; break; case "wand_basic_lightning": replacement = WizardryItems.novice_lightning_wand; break;
case "wand_basic_necromancy": replacement = WizardryItems.basic_necromancy_wand; break; case "wand_basic_necromancy": replacement = WizardryItems.novice_necromancy_wand; break;
case "wand_basic_earth": replacement = WizardryItems.basic_earth_wand; break; case "wand_basic_earth": replacement = WizardryItems.novice_earth_wand; break;
case "wand_basic_sorcery": replacement = WizardryItems.basic_sorcery_wand; break; case "wand_basic_sorcery": replacement = WizardryItems.novice_sorcery_wand; break;
case "wand_basic_healing": replacement = WizardryItems.basic_healing_wand; break; case "wand_basic_healing": replacement = WizardryItems.novice_healing_wand; break;
case "basic_fire_wand": replacement = WizardryItems.novice_fire_wand; break;
case "basic_ice_wand": replacement = WizardryItems.novice_ice_wand; break;
case "basic_lightning_wand": replacement = WizardryItems.novice_lightning_wand; break;
case "basic_necromancy_wand": replacement = WizardryItems.novice_necromancy_wand; break;
case "basic_earth_wand": replacement = WizardryItems.novice_earth_wand; break;
case "basic_sorcery_wand": replacement = WizardryItems.novice_sorcery_wand; break;
case "basic_healing_wand": replacement = WizardryItems.novice_healing_wand; break;
case "wand_apprentice": replacement = WizardryItems.apprentice_wand; break; case "wand_apprentice": replacement = WizardryItems.apprentice_wand; break;
case "wand_apprentice_fire": replacement = WizardryItems.apprentice_fire_wand; break; case "wand_apprentice_fire": replacement = WizardryItems.apprentice_fire_wand; break;
case "wand_apprentice_ice": replacement = WizardryItems.apprentice_ice_wand; break; case "wand_apprentice_ice": replacement = WizardryItems.apprentice_ice_wand; break;
@@ -264,4 +245,13 @@ public class Wizardry {
} }
} }
@SubscribeEvent
public static void onMissingSpellMappingEvent(RegistryEvent.MissingMappings<Spell> event){
for(RegistryEvent.MissingMappings.Mapping<Spell> mapping : event.getAllMappings()){
if(mapping.key.getNamespace().equals(Wizardry.MODID)){
if(mapping.key.getPath().equals("firestorm")) mapping.remap(Spells.fire_breath);
}
}
}
} }
@@ -1,120 +1,137 @@
package electroblob.wizardry; package electroblob.wizardry;
import electroblob.wizardry.constants.Constants; import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.entity.EntityArc; import electroblob.wizardry.data.SpellEmitterData;
import electroblob.wizardry.entity.living.EntityEvilWizard; import electroblob.wizardry.data.SpellGlyphData;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.entity.living.ISpellCaster; import electroblob.wizardry.entity.living.ISpellCaster;
import electroblob.wizardry.entity.living.ISummonedCreature;
import electroblob.wizardry.event.DiscoverSpellEvent; import electroblob.wizardry.event.DiscoverSpellEvent;
import electroblob.wizardry.event.SpellCastEvent; import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.integration.DamageSafetyChecker; import electroblob.wizardry.integration.DamageSafetyChecker;
import electroblob.wizardry.item.ItemWand; import electroblob.wizardry.item.IManaStoringItem;
import electroblob.wizardry.item.ItemWizardArmour; import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.registry.Spells; import electroblob.wizardry.packet.PacketSyncAdvancements;
import electroblob.wizardry.registry.WizardryAdvancementTriggers; import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.WizardryEnchantments; import electroblob.wizardry.registry.*;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.spell.FreezingWeapon; import electroblob.wizardry.spell.FreezingWeapon;
import electroblob.wizardry.spell.ImbueWeapon;
import electroblob.wizardry.spell.Spell; import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.*;
import electroblob.wizardry.util.MagicDamage.DamageType; import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WandHelper;
import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.WizardryUtilities; import net.minecraft.advancements.Advancement;
import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.monster.IMob;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.entity.projectile.EntityArrow; import net.minecraft.entity.projectile.EntityArrow;
import net.minecraft.init.SoundEvents;
import net.minecraft.inventory.ContainerPlayer;
import net.minecraft.inventory.ContainerWorkbench;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemSword;
import net.minecraft.potion.PotionEffect; import net.minecraft.potion.PotionEffect;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EnumHand; import net.minecraft.util.EnumHand;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraft.world.storage.loot.LootEntry;
import net.minecraft.world.storage.loot.LootEntryTable;
import net.minecraft.world.storage.loot.LootPool;
import net.minecraft.world.storage.loot.RandomValueRange;
import net.minecraft.world.storage.loot.conditions.LootCondition;
import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.util.FakePlayer; import net.minecraftforge.common.util.FakePlayer;
import net.minecraftforge.event.LootTableLoadEvent; import net.minecraftforge.event.entity.PlaySoundAtEntityEvent;
import net.minecraftforge.event.entity.living.LivingAttackEvent; import net.minecraftforge.event.entity.living.*;
import net.minecraftforge.event.entity.living.LivingDeathEvent;
import net.minecraftforge.event.entity.living.LivingDropsEvent;
import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent; import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent;
import net.minecraftforge.event.entity.living.LivingHurtEvent; import net.minecraftforge.event.entity.player.AdvancementEvent;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.EventPriority;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerLoggedInEvent; import net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerLoggedInEvent;
import java.util.ArrayList;
/** /**
* As of Wizardry 2.1, most of the code in this class has been relocated somewhere sensible, leaving only a few * General-purpose event handler for things that don't fit anywhere else or groups of related behaviours that are better
* miscellaneous things that don't make much sense anywhere else, or that are better kept together. Previously, this was * kept together. As of Wizardry 2.1, most of the code in this class has been relocated somewhere sensible, leaving only
* a gigantic class with about half of the entire mod's logic in it! * a few miscellaneous things that don't make much sense anywhere else, or that are better kept together (previously,
* this was a gigantic class with about half of the entire mod's logic in it!)
* *
* @author Electroblob * @author Electroblob
* @since Wizardry 1.0 * @since Wizardry 1.0
*/ */
// The general rules for where event-based logic goes are:
// - If the logic relates only to vanilla things or is general (e.g. gameplay settings) it lives in here
// - If there is an obvious class for the thing relevant to the logic being performed, it goes in there for the sake
// of modularity/separation-of-concerns (e.g. armour cost reductions go in ItemWizardArmour)
// - If the thing has an instance but not a separate class (e.g. most custom potions), it goes in a related class
// where applicable (e.g. a spell class), or if not it goes in here
// - If several things share a significant amount of logic, to avoid duplicate code and potentially improve efficiency
// they should be kept together either in a class relevant to all of them (probably a common superclass) or in here
// - Client-side logic goes in WizardryClientEventHandler or another relevant client-side class
@Mod.EventBusSubscriber @Mod.EventBusSubscriber
public final class WizardryEventHandler { public final class WizardryEventHandler {
// IDEA: Config option allowing users to specify loot locations private WizardryEventHandler(){} // No instances!
private static final String[] LOOT_INJECTION_LOCATIONS = {"minecraft:chests/simple_dungeon",
"minecraft:chests/abandoned_mineshaft", "minecraft:chests/desert_pyramid", "minecraft:chests/jungle_temple",
"minecraft:chests/stronghold_corridor", "minecraft:chests/stronghold_crossing",
"minecraft:chests/stronghold_library", "minecraft:chests/igloo_chest", "minecraft:chests/woodland_mansion",
"minecraft:chests/end_city_treasure"};
@SubscribeEvent
public static void onLootTableLoadEvent(LootTableLoadEvent event){
if(Wizardry.settings.generateLoot){
for(String location : LOOT_INJECTION_LOCATIONS){
if(event.getName().toString().matches(location)){
event.getTable().addPool(getAdditive(Wizardry.MODID + ":chests/dungeon_additions"));
}
}
}
}
private static LootPool getAdditive(String entryName){
return new LootPool(new LootEntry[]{getAdditiveEntry(entryName, 1)}, new LootCondition[0],
new RandomValueRange(1), new RandomValueRange(0, 1), Wizardry.MODID + "_additive_pool");
}
private static LootEntryTable getAdditiveEntry(String name, int weight){
return new LootEntryTable(new ResourceLocation(name), weight, 0, new LootCondition[0],
Wizardry.MODID + "_additive_entry");
}
@SubscribeEvent @SubscribeEvent
public static void onPlayerLoggedInEvent(PlayerLoggedInEvent event){ public static void onPlayerLoggedInEvent(PlayerLoggedInEvent event){
// When a player logs in, they are sent the glyph data and the server's settings. // When a player logs in, they are sent the glyph data, server settings and spell properties.
if(event.player instanceof EntityPlayerMP){ if(event.player instanceof EntityPlayerMP){
SpellGlyphData.get(event.player.world).sync((EntityPlayerMP)event.player); SpellGlyphData.get(event.player.world).sync((EntityPlayerMP)event.player);
SpellEmitterData.get(event.player.world).sync((EntityPlayerMP)event.player);
Wizardry.settings.sync((EntityPlayerMP)event.player); Wizardry.settings.sync((EntityPlayerMP)event.player);
syncAdvancements((EntityPlayerMP)event.player, false);
}
Spell.syncProperties(event.player);
}
@SubscribeEvent(priority = EventPriority.HIGH)
public static void onPlaySoundAtEntityEvent(PlaySoundAtEntityEvent event){
// Muffle (there's no spell class for it so it's here instead)
if(event.getEntity() instanceof EntityLivingBase
&& ((EntityLivingBase)event.getEntity()).isPotionActive(WizardryPotions.muffle)){
event.setCanceled(true);
} }
} }
@SubscribeEvent @SubscribeEvent
public static void onAdvancementEvent(AdvancementEvent event){
// Forge has no hook for revoked advancements :(
// Guess we'll just have to make do
// Also, this seems to get fired on player login, so to prevent the toasts from appearing every login the
// only way I can see to do it is by testing the player has been around long enough.
if(event.getEntityPlayer() instanceof EntityPlayerMP && event.getEntityPlayer().ticksExisted > 0){
syncAdvancements((EntityPlayerMP)event.getEntityPlayer(), true);
}
}
private static void syncAdvancements(EntityPlayerMP player, boolean showToasts){
Wizardry.logger.info("Synchronising advancements for " + player.getName());
ArrayList<ResourceLocation> advancements = new ArrayList<>();
for(Advancement advancement : player.getServer().getAdvancementManager().getAdvancements()){
if(player.getAdvancements().getProgress(advancement).isDone()) advancements.add(advancement.getId());
}
WizardryPacketHandler.net.sendTo(new PacketSyncAdvancements.Message(showToasts, advancements.toArray(new ResourceLocation[0])), player);
}
@SubscribeEvent(priority = EventPriority.HIGH) // Disabling of specific spells comes after arcane jammer but before everything else
public static void onSpellCastPreEvent(SpellCastEvent.Pre event){ public static void onSpellCastPreEvent(SpellCastEvent.Pre event){
boolean enabled = true;
switch(event.getSource()){
case WAND: enabled = event.getSpell().isEnabled(SpellProperties.Context.WANDS); break;
case SCROLL: enabled = event.getSpell().isEnabled(SpellProperties.Context.SCROLL); break;
case COMMAND: enabled = event.getSpell().isEnabled(SpellProperties.Context.COMMANDS); break;
case NPC: enabled = event.getSpell().isEnabled(SpellProperties.Context.NPCS); break;
case DISPENSER: enabled = event.getSpell().isEnabled(SpellProperties.Context.DISPENSERS); break;
case OTHER: enabled = event.getSpell().isEnabled(); break; // Any enabled context will do for this one
}
// If a spell is disabled in the config, it will not work. // If a spell is disabled in the config, it will not work.
if(!event.getSpell().isEnabled()){ if(!enabled){
if(!event.getEntityLiving().world.isRemote) event.getEntity().sendMessage( if(event.getCaster() != null && !event.getCaster().world.isRemote) event.getCaster().sendMessage(
new TextComponentTranslation("spell.disabled", event.getSpell().getNameForTranslationFormatted())); new TextComponentTranslation("spell.disabled", event.getSpell().getNameForTranslationFormatted()));
event.setCanceled(true); event.setCanceled(true);
} }
@@ -123,32 +140,36 @@ public final class WizardryEventHandler {
@SubscribeEvent @SubscribeEvent
public static void onSpellCastPostEvent(SpellCastEvent.Post event){ public static void onSpellCastPostEvent(SpellCastEvent.Post event){
// Spell discovery (only players can discover spells, obviously) if(event.getCaster() instanceof EntityPlayer){
if(event.getEntity() instanceof EntityPlayer){
EntityPlayer player = (EntityPlayer)event.getEntity(); EntityPlayer player = (EntityPlayer)event.getCaster();
// Advancement triggers
if(player instanceof EntityPlayerMP){
WizardryAdvancementTriggers.cast_spell.trigger((EntityPlayerMP)player, event.getSpell(), player.getHeldItem(player.getActiveHand()));
}
// Spell discovery (only players can discover spells, obviously)
WizardData data = WizardData.get(player); WizardData data = WizardData.get(player);
if(data != null){ if(data != null){
// Data is updated on both sides (This line was added client-side to fix a bug back in 1.1.3, so now // Data is updated on both sides (This line was added client-side to fix a bug back in 1.1.3, so now
// it's in common code, which is nice!) // it's in common code, which is nice!)
// Short-circuiting AND means that discoverSpell is only called if the event isn't cancelled. // Short-circuiting AND means that discoverSpell is only called if the event isn't cancelled.
if(!MinecraftForge.EVENT_BUS if(!MinecraftForge.EVENT_BUS.post(new DiscoverSpellEvent(player, event.getSpell(), DiscoverSpellEvent.Source.CASTING))
.post(new DiscoverSpellEvent(player, event.getSpell(), DiscoverSpellEvent.Source.CASTING))
&& data.discoverSpell(event.getSpell())){ && data.discoverSpell(event.getSpell())){
// If the spell wasn't already discovered, other stuff happens: // If the spell wasn't already discovered, other stuff happens:
if(event.getSource() == SpellCastEvent.Source.COMMAND){ if(event.getSource() == SpellCastEvent.Source.COMMAND){
// If the spell didn't send a packet itself, the extended player needs to be synced so the // If the spell didn't send a packet itself, the extended player needs to be synced so the
// spell discovery updates on the client. // spell discovery updates on the client.
if(!event.getSpell().doesSpellRequirePacket()) data.sync(); if(!event.getSpell().requiresPacket()) data.sync();
}else if(!event.getEntity().world.isRemote && !player.capabilities.isCreativeMode }else if(!event.getCaster().world.isRemote && !player.isCreative()
&& Wizardry.settings.discoveryMode){ && Wizardry.settings.discoveryMode){
// Sound and text only happen server-side, in survival, with discovery mode on, and only when // Sound and text only happen server-side, in survival, with discovery mode on, and only when
// the spell wasn't cast using commands. // the spell wasn't cast using commands.
WizardryUtilities.playSoundAtPlayer(player, SoundEvents.ENTITY_PLAYER_LEVELUP, 1.25f, 1); WizardryUtilities.playSoundAtPlayer(player, WizardrySounds.MISC_DISCOVER_SPELL, 1.25f, 1);
player.sendMessage(new TextComponentTranslation("spell.discover", player.sendMessage(new TextComponentTranslation("spell.discover",
event.getSpell().getNameForTranslationFormatted())); event.getSpell().getNameForTranslationFormatted()));
} }
@@ -157,33 +178,59 @@ public final class WizardryEventHandler {
} }
} }
@SubscribeEvent(priority = EventPriority.LOW)
public static void onDiscoverSpellEvent(DiscoverSpellEvent event){
if(event.getEntityPlayer() instanceof EntityPlayerMP){
WizardryAdvancementTriggers.discover_spell.trigger((EntityPlayerMP)event.getEntityPlayer(), event.getSpell(), event.getSource());
}
}
@SubscribeEvent
public static void onLivingSetAttackTargetEvent(LivingSetAttackTargetEvent event){
if(event.getTarget() != null && event.getEntityLiving() instanceof EntityLiving
&& event.getTarget().isPotionActive(WizardryPotions.muffle)){
Vec3d vec = event.getTarget().getPositionEyes(1).subtract(event.getEntity().getPositionEyes(1));
// Find the angle between the direction the mob is looking and the direction the player is in
// Angle between a and b = acos((a.b) / (|a|*|b|))
double angle = Math.acos(vec.dotProduct(event.getEntity().getLookVec()) / vec.length());
System.out.println(angle);
// If the player is not within the 144-degree arc in front of the mob, it won't detect them
if(angle > 0.4 * Math.PI){
((EntityLiving)event.getEntityLiving()).setAttackTarget(null);
}
}
}
/* There is a subtle but important difference between LivingAttackEvent and LivingHurtEvent - LivingAttackEvent /* There is a subtle but important difference between LivingAttackEvent and LivingHurtEvent - LivingAttackEvent
* fires immediately when attackEntityFrom is called, whereas LivingHurtEvent only fires if the attack actually * fires immediately when attackEntityFrom is called, whereas LivingHurtEvent only fires if the attack actually
* succeeded, i.e. if the entity in question takes damage (though the event is fired before that so you can cancel * succeeded, i.e. if the entity in question takes damage (though the event is fired before that so you can cancel
* the damage). Things are processed in the following order: * LivingAttackEvent * - Invulnerability - * the damage). Things are processed in the following order:
* Already-dead-ness - Fire resistance - Helmets vs. falling things - Hurt resistant time - Invulnerability (again) *
* * LivingHurtEvent * - Armour - Potions - Health is finally changed Of course, there are no guarantees that other * * LivingAttackEvent *
* - Invulnerability
* - Already-dead-ness
* - Fire resistance
* - Helmets vs. falling things
* - Hurt resistant time
* - Invulnerability (again)
* * LivingHurtEvent *
* - Armour
* - Potions
* * LivingDamageEvent *
* - Health is finally changed
*
* Of course, there are no guarantees that other
* mods hooking into these two events will be called before or after yours, but you can have some degree of control * mods hooking into these two events will be called before or after yours, but you can have some degree of control
* by choosing which event to use. EDIT: Actually, there are. Firstly, you can set a priority in the @SubscribeEvent * by choosing which event to use. EDIT: Actually, there are. Firstly, you can set a priority in the @SubscribeEvent
* annotation which defines how early (higher priority) or late (lower priority) the method is called. Methods with * annotation which defines how early (higher priority) or late (lower priority) the method is called. Methods with
* the same priority are sorted alphabetically by mod id (so it's safe to assume wizardry would be fairly late on!). * the same priority are sorted alphabetically by mod id (so it's safe to assume wizardry would be fairly late on!).
* I wonder if there are any conventions for what sort of things take what priority...? */ * I wonder if there are any conventions for what sort of things take what priority...? */
@SubscribeEvent @SubscribeEvent(priority = EventPriority.LOW) // Low priority in case the event gets cancelled at default priority
public static void onLivingAttackEvent(LivingAttackEvent event){ public static void onLivingAttackEvent(LivingAttackEvent event){
// Prevents any damage to allies from magic if friendly fire is disabled
if(!Wizardry.settings.friendlyFire && event.getSource() != null
&& event.getSource().getTrueSource() instanceof EntityPlayer && event.getEntity() instanceof EntityPlayer
&& event.getSource() instanceof IElementalDamage){
if(WizardryUtilities.isPlayerAlly((EntityPlayer)event.getSource().getTrueSource(),
(EntityPlayer)event.getEntity())){
event.setCanceled(true);
// This needs to be here, since if the event is cancelled nothing else needs to happen.
return;
}
}
// Retaliatory effects // Retaliatory effects
// These are better off here because the revenge effects are pretty similar, and I'd rather keep the (lengthy) // These are better off here because the revenge effects are pretty similar, and I'd rather keep the (lengthy)
// if statement in one place. // if statement in one place.
@@ -194,38 +241,43 @@ public final class WizardryEventHandler {
EntityLivingBase attacker = (EntityLivingBase)event.getSource().getTrueSource(); EntityLivingBase attacker = (EntityLivingBase)event.getSource().getTrueSource();
World world = event.getEntityLiving().world; World world = event.getEntityLiving().world;
// Fireskin if(attacker.getDistance(event.getEntityLiving()) < 10){
if(event.getEntityLiving().isPotionActive(WizardryPotions.fireskin)
&& !MagicDamage.isEntityImmune(DamageType.FIRE, event.getEntityLiving()))
attacker.setFire(5);
// Ice Shroud // Fireskin
if(event.getEntityLiving().isPotionActive(WizardryPotions.ice_shroud) if(event.getEntityLiving().isPotionActive(WizardryPotions.fireskin)
&& !MagicDamage.isEntityImmune(DamageType.FROST, event.getEntityLiving()) && !MagicDamage.isEntityImmune(DamageType.FIRE, event.getEntityLiving()))
&& !(event.getEntityLiving() instanceof FakePlayer)) attacker.setFire(Spells.fire_breath.getProperty(Spell.BURN_DURATION).intValue());
attacker.addPotionEffect(new PotionEffect(WizardryPotions.frost, 100, 0));
// Static Aura // Ice Shroud
if(event.getEntityLiving().isPotionActive(WizardryPotions.static_aura)){ if(event.getEntityLiving().isPotionActive(WizardryPotions.ice_shroud)
&& !MagicDamage.isEntityImmune(DamageType.FROST, event.getEntityLiving())
&& !(attacker instanceof FakePlayer)) // Fake players cause problems
attacker.addPotionEffect(new PotionEffect(WizardryPotions.frost,
Spells.ice_shroud.getProperty(Spell.EFFECT_DURATION).intValue(),
Spells.ice_shroud.getProperty(Spell.EFFECT_STRENGTH).intValue()));
if(world.isRemote){ // Static Aura
if(event.getEntityLiving().isPotionActive(WizardryPotions.static_aura)){
ParticleBuilder.create(Type.LIGHTNING).entity(event.getEntity()).pos(0, event.getEntity().height/2, 0)
.target(attacker).spawn(world); if(world.isRemote){
ParticleBuilder.spawnShockParticles(world, attacker.posX, ParticleBuilder.create(Type.LIGHTNING).entity(event.getEntity()).pos(0, event.getEntity().height / 2, 0)
attacker.getEntityBoundingBox().minY + attacker.height, attacker.posZ); .target(attacker).spawn(world);
ParticleBuilder.spawnShockParticles(world, attacker.posX,
attacker.getEntityBoundingBox().minY + attacker.height / 2, attacker.posZ);
}
DamageSafetyChecker.attackEntitySafely(attacker, MagicDamage.causeDirectMagicDamage(event.getEntityLiving(),
DamageType.SHOCK, true), Spells.static_aura.getProperty(Spell.DAMAGE).floatValue(), event.getSource().getDamageType());
attacker.playSound(WizardrySounds.SPELL_STATIC_AURA_RETALIATE, 1.0F, world.rand.nextFloat() * 0.4F + 1.5F);
} }
DamageSafetyChecker.attackEntitySafely(attacker, MagicDamage.causeDirectMagicDamage(event.getEntityLiving(),
DamageType.SHOCK, true), 4.0f, event.getSource().getDamageType());
attacker.playSound(WizardrySounds.SPELL_STATIC_AURA_RETALIATE, 1.0F, world.rand.nextFloat() * 0.4F + 1.5F);
} }
} }
} }
@SubscribeEvent @SubscribeEvent(priority = EventPriority.LOW) // Again, we don't want these effects if the event is cancelled
public static void onLivingHurtEvent(LivingHurtEvent event){ public static void onLivingHurtEvent(LivingHurtEvent event){
// Flaming and freezing swords // Flaming and freezing swords
@@ -234,7 +286,7 @@ public final class WizardryEventHandler {
EntityLivingBase attacker = (EntityLivingBase)event.getSource().getTrueSource(); EntityLivingBase attacker = (EntityLivingBase)event.getSource().getTrueSource();
// Players can only ever attack with their main hand, so this is the right method to use here. // Players can only ever attack with their main hand, so this is the right method to use here.
if(!attacker.getHeldItemMainhand().isEmpty() && attacker.getHeldItemMainhand().getItem() instanceof ItemSword){ if(!attacker.getHeldItemMainhand().isEmpty() && ImbueWeapon.isSword(attacker.getHeldItemMainhand().getItem())){
int level = EnchantmentHelper.getEnchantmentLevel(WizardryEnchantments.flaming_weapon, int level = EnchantmentHelper.getEnchantmentLevel(WizardryEnchantments.flaming_weapon,
attacker.getHeldItemMainhand()); attacker.getHeldItemMainhand());
@@ -275,30 +327,13 @@ public final class WizardryEventHandler {
@SubscribeEvent @SubscribeEvent
public static void onLivingUpdateEvent(LivingUpdateEvent event){ public static void onLivingUpdateEvent(LivingUpdateEvent event){
if(event.getEntityLiving() instanceof EntityPlayer){ // Experimental animation feature
// if(event.getEntityLiving().isHandActive() && event.getEntityLiving().getActiveItemStack().getItemUseAction() == WizardryUtilities.POINT){
EntityPlayer player = (EntityPlayer)event.getEntityLiving(); // event.getEntityLiving().isSwingInProgress = true;
// event.getEntityLiving().swingProgress = 1f;
if(player.world.isRemote) hackilyFixContinuousSpellCasting(player); // event.getEntityLiving().prevSwingProgress = 1;
// event.getEntityLiving().swingingHand = event.getEntityLiving().getActiveHand();
// Mana flask crafting // }
if(player.openContainer instanceof ContainerWorkbench){
IInventory craftMatrix = ((ContainerWorkbench)player.openContainer).craftMatrix;
ItemStack output = ((ContainerWorkbench)player.openContainer).craftResult.getStackInSlot(0);
processManaFlaskCrafting(craftMatrix, output);
}else if(player.openContainer instanceof ContainerPlayer){
IInventory craftMatrix = ((ContainerPlayer)player.openContainer).craftMatrix;
ItemStack output = ((ContainerPlayer)player.openContainer).craftResult.getStackInSlot(0);
// Unfortunately I have no choice but to call this method every tick when the player isn't using another
// inventory, since the only thing tracking whether the player is looking at their inventory is the GUI
// itself, which is client-side only.
processManaFlaskCrafting(craftMatrix, output);
}
}
if(event.getEntityLiving().world.isRemote){ if(event.getEntityLiving().world.isRemote){
@@ -309,13 +344,14 @@ public final class WizardryEventHandler {
Spell spell = ((ISpellCaster)event.getEntity()).getContinuousSpell(); Spell spell = ((ISpellCaster)event.getEntity()).getContinuousSpell();
SpellModifiers modifiers = ((ISpellCaster)event.getEntity()).getModifiers(); SpellModifiers modifiers = ((ISpellCaster)event.getEntity()).getModifiers();
if(spell != null && spell != Spells.none){ if(spell != null && spell != Spells.none){ // IntelliJ is wrong, do NOT remove the null check!
if(!MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Tick(event.getEntityLiving(), spell, modifiers, if(!MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Tick(SpellCastEvent.Source.NPC, spell, event.getEntityLiving(),
SpellCastEvent.Source.NPC, 0))){ modifiers, 0))){
spell.cast(event.getEntity().world, (EntityLiving)event.getEntity(), EnumHand.MAIN_HAND, 0, spell.cast(event.getEntity().world, (EntityLiving)event.getEntity(), EnumHand.MAIN_HAND, 0,
// TODO: This implementation of modifiers relies on them being accessible client-side. // 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); ((EntityLiving)event.getEntity()).getAttackTarget(), modifiers);
} }
} }
@@ -323,7 +359,7 @@ public final class WizardryEventHandler {
} }
} }
@SubscribeEvent @SubscribeEvent(priority = EventPriority.LOWEST) // No siphoning if the event is cancelled, that could be exploited...
public static void onLivingDeathEvent(LivingDeathEvent event){ public static void onLivingDeathEvent(LivingDeathEvent event){
if(event.getSource().getTrueSource() instanceof EntityPlayer){ if(event.getSource().getTrueSource() instanceof EntityPlayer){
@@ -332,108 +368,83 @@ public final class WizardryEventHandler {
for(ItemStack stack : WizardryUtilities.getPrioritisedHotbarAndOffhand(player)){ for(ItemStack stack : WizardryUtilities.getPrioritisedHotbarAndOffhand(player)){
if(stack.getItem() instanceof ItemWand && stack.isItemDamaged() if(stack.getItem() instanceof IManaStoringItem && !((IManaStoringItem)stack.getItem()).isManaFull(stack)
&& WandHelper.getUpgradeLevel(stack, WizardryItems.siphon_upgrade) > 0){ && WandHelper.getUpgradeLevel(stack, WizardryItems.siphon_upgrade) > 0){
int damage = stack.getItemDamage()
- Constants.SIPHON_MANA_PER_LEVEL int mana = Constants.SIPHON_MANA_PER_LEVEL
* WandHelper.getUpgradeLevel(stack, WizardryItems.siphon_upgrade) * WandHelper.getUpgradeLevel(stack, WizardryItems.siphon_upgrade)
- player.world.rand.nextInt(Constants.SIPHON_MANA_PER_LEVEL); + player.world.rand.nextInt(Constants.SIPHON_MANA_PER_LEVEL);
if(damage < 0) damage = 0;
stack.setItemDamage(damage); if(ItemArtefact.isArtefactActive(player, WizardryItems.ring_siphoning)) mana *= 1.3f;
break;
((IManaStoringItem)stack.getItem()).rechargeMana(stack, mana);
break; // Only recharge one item per kill
}
}
}
}
// These two are lifted from EntityLivingBase#travel
private static final double LIVING_ENTITY_GRAVITY = 0.08;
private static final double LIVING_ENTITY_DRAG = 0.98;
private static final double LIVING_ENTITY_TERMINAL_VELOCITY = 3.92; // From Minecraft Wiki!
private static final double LOG_LIVING_ENTITY_DRAG = Math.log(LIVING_ENTITY_DRAG);
private static final double FALL_TICKS_ERROR_CORRECTION = 0.500841776608447;
@SubscribeEvent // Priority doesn't matter here, we're only setting event fields so if it's cancelled it won't matter
public static void onLivingFallEvent(LivingFallEvent event){
// Why is fall damage based on distance fallen? Why? Who on earth came up with that? It makes no sense whatsoever!
if(Wizardry.settings.replaceVanillaFallDamage && !Loader.isModLoaded("speedbasedfalldamage")){
// We want to keep the fall damage EXACTLY THE SAME for free, uninterrupted falls, but fix the weirdness
// caused when something else changes the entity's velocity
// All living entities have a gravity of 0.08b/t^2
// Therefore it would be simple to say v^2 = u^2 + 2gs gives the equivalent fall distance as motionY^2 / 0.16
// However, Minecraft also has a drag of 0.02 * the velocity, so we actually expect a slightly different value
// Much maths later...
double v = event.getEntity().motionY;
// Players are weird, their velocity somehow resets on the server just before this event fires so
// instead we're storing the y velocity from the previous tick in WizardData and retrieving it here
// Of course, if another mod screws things up and sets a player's velocity client-side only then this won't
// work ...but it's better than having clients calculate their own fall damage
if(event.getEntity() instanceof EntityPlayer){
WizardData data = WizardData.get((EntityPlayer)event.getEntity());
if(data != null){
v = data.prevMotionY;
} }
} }
if(event.getEntityLiving() == player && event.getSource() instanceof IElementalDamage){ // At terminal velocity, there's no way of finding fall distance from velocity, and the entity is probably dead anyway!
WizardryAdvancementTriggers.self_destruct.triggerFor(player); if(v > -3.9){
}
}
}
@SubscribeEvent // Just to make the code more readable, java will replace them all with numbers anyway
public static void onLivingDropsEvent(LivingDropsEvent event){ double g = LIVING_ENTITY_GRAVITY;
// TODO: Really, this should be in a loot table (mob_additions), however I can't seem to find a way of double f = LIVING_ENTITY_DRAG;
// automatically adding it to all subclasses of IMob. double lnf = LOG_LIVING_ENTITY_DRAG;
// Evil wizards drop spell books themselves double tv = LIVING_ENTITY_TERMINAL_VELOCITY;
if(event.getEntityLiving() instanceof IMob && !(event.getEntityLiving() instanceof EntityEvilWizard)
// TODO: Backport when you backport the new summoned creature system.
&& !(event.getEntityLiving() instanceof ISummonedCreature)
&& event.getSource().getTrueSource() instanceof EntityPlayer && Wizardry.settings.spellBookDropChance > 0){
// This does exactly what the entity drop method does, but with a different random number so that the // Work backwards from y velocity to get fall time
// spell book doesn't always drop with other rare drops. // logs are probably slow but it's not like this gets calculated every tick, and we only need one log
int rareDropNumber = event.getEntity().world.rand.nextInt(200) - event.getLootingLevel(); double t = Math.log(((-v - tv) * lnf) / g) / lnf; // Log the number over log the base
if(rareDropNumber < Wizardry.settings.spellBookDropChance){
// Drops a spell book
int id = WizardryUtilities.getStandardWeightedRandomSpellId(event.getEntity().world.rand);
event.getDrops() // Because time is in discrete ticks, the above equation for t results in a constant error of
.add(new EntityItem(event.getEntityLiving().world, event.getEntityLiving().posX, // +0.500841776608447, so I guess we can just subtract it... if it works it works I guess!
event.getEntityLiving().posY, event.getEntityLiving().posZ, t -= FALL_TICKS_ERROR_CORRECTION; // Don't cast to int or perform any rounding
new ItemStack(WizardryItems.spell_book, 1, id)));
}
}
}
// Private helper methods // Now work forwards from t to find the effective fall distance, i.e. the distance the entity would
// ================================================================================================================ // have to freefall to reach the same velocity
// Don't ask me where the 196 is from, again, it just works!
double y = (g * Math.pow(f, t)) / (lnf*lnf) + tv * (t) - 196;
/** // DEBUG
* Detects inconsistencies between player.getActiveItemStack and the actual itemstack and forces them to be equal. // if(event.getEntity() instanceof EntityPlayer){
* Fixes issue #25. // Wizardry.logger.info("Replaced fall distance {} with effective distance {} based on entity velocity", event.getDistance(), y);
* // }
* @param player
*/
private static void hackilyFixContinuousSpellCasting(EntityPlayer player){
if(player.isHandActive() && player.getHeldItem(player.getActiveHand()).getItem() instanceof ItemWand
&& WandHelper.getCurrentSpell(player.getHeldItem(player.getActiveHand())).isContinuous){
if(player.getActiveItemStack() != player.getHeldItem(player.getActiveHand())){
player.setHeldItem(player.getActiveHand(), player.getActiveItemStack());
}
}
}
private static void processManaFlaskCrafting(IInventory craftMatrix, ItemStack output){ event.setDistance((float)y);
// Charges wand using mana flask. It is here rather than in the crafting handler so the result displays
// the proper damage before it is actually crafted.
boolean flag = false;
ItemStack wand = ItemStack.EMPTY;
ItemStack armour = ItemStack.EMPTY;
for(int i = 0; i < craftMatrix.getSizeInventory(); i++){
ItemStack itemstack = craftMatrix.getStackInSlot(i);
if(itemstack.getItem() == WizardryItems.mana_flask){
flag = true;
}
if(itemstack.getItem() instanceof ItemWand){
wand = itemstack;
}
if(itemstack.getItem() instanceof ItemWizardArmour){
armour = itemstack;
}
}
if(output.getItem() instanceof ItemWand && flag && !wand.isEmpty()){
output.setTagCompound((wand.getTagCompound()));
if(wand.getItemDamage() - Constants.MANA_PER_FLASK < 0){
output.setItemDamage(0);
}else{
output.setItemDamage(wand.getItemDamage() - Constants.MANA_PER_FLASK);
}
}
if(output.getItem() instanceof ItemWizardArmour && flag && !armour.isEmpty()){
output.setTagCompound((armour.getTagCompound()));
if(armour.getItemDamage() - Constants.MANA_PER_FLASK < 0){
output.setItemDamage(0);
}else{
output.setItemDamage(wand.getItemDamage() - Constants.MANA_PER_FLASK);
} }
} }
} }
@@ -1,12 +1,12 @@
package electroblob.wizardry; package electroblob.wizardry;
import java.util.Set; import electroblob.wizardry.client.gui.config.GuiConfigWizardry;
import electroblob.wizardry.client.gui.GuiConfigWizardry;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.gui.GuiScreen;
import net.minecraftforge.fml.client.IModGuiFactory; import net.minecraftforge.fml.client.IModGuiFactory;
import java.util.Set;
public class WizardryGuiFactory implements IModGuiFactory { public class WizardryGuiFactory implements IModGuiFactory {
@Override @Override
@@ -1,6 +1,5 @@
package electroblob.wizardry; package electroblob.wizardry;
import electroblob.wizardry.client.gui.handbook.GuiWizardHandbook;
import electroblob.wizardry.item.ItemSpellBook; import electroblob.wizardry.item.ItemSpellBook;
import electroblob.wizardry.item.ItemWizardHandbook; import electroblob.wizardry.item.ItemWizardHandbook;
import electroblob.wizardry.spell.Spell; import electroblob.wizardry.spell.Spell;
@@ -46,12 +45,12 @@ public class WizardryGuiHandler implements IGuiHandler {
} }
}else if(id == WIZARD_HANDBOOK && (player.getHeldItemMainhand().getItem() instanceof ItemWizardHandbook }else if(id == WIZARD_HANDBOOK && (player.getHeldItemMainhand().getItem() instanceof ItemWizardHandbook
|| player.getHeldItemOffhand().getItem() instanceof ItemWizardHandbook)){ || player.getHeldItemOffhand().getItem() instanceof ItemWizardHandbook)){
return new GuiWizardHandbook(); return new electroblob.wizardry.client.gui.handbook.GuiWizardHandbook();
}else if(id == SPELL_BOOK){ }else if(id == SPELL_BOOK){
if(player.getHeldItemMainhand().getItem() instanceof ItemSpellBook){ if(player.getHeldItemMainhand().getItem() instanceof ItemSpellBook){
return new electroblob.wizardry.client.gui.GuiSpellBook(Spell.get(player.getHeldItemMainhand().getItemDamage())); return new electroblob.wizardry.client.gui.GuiSpellBook(Spell.byMetadata(player.getHeldItemMainhand().getItemDamage()));
}else if(player.getHeldItemOffhand().getItem() instanceof ItemSpellBook){ }else if(player.getHeldItemOffhand().getItem() instanceof ItemSpellBook){
return new electroblob.wizardry.client.gui.GuiSpellBook(Spell.get(player.getHeldItemOffhand().getItemDamage())); return new electroblob.wizardry.client.gui.GuiSpellBook(Spell.byMetadata(player.getHeldItemOffhand().getItemDamage()));
} }
}else if(id == PORTABLE_CRAFTING){ }else if(id == PORTABLE_CRAFTING){
return new electroblob.wizardry.client.gui.GuiPortableCrafting(player.inventory, world, new BlockPos(x, y, z)); return new electroblob.wizardry.client.gui.GuiPortableCrafting(player.inventory, world, new BlockPos(x, y, z));
@@ -1,949 +0,0 @@
package electroblob.wizardry;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import org.apache.commons.lang3.ArrayUtils;
import electroblob.wizardry.entity.living.EntityEvilWizard;
import electroblob.wizardry.entity.living.EntityWizard;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.block.BlockChest;
import net.minecraft.block.BlockColored;
import net.minecraft.block.BlockLeaves;
import net.minecraft.block.BlockLiquid;
import net.minecraft.block.BlockPlanks;
import net.minecraft.block.BlockSlab;
import net.minecraft.block.BlockSlab.EnumBlockHalf;
import net.minecraft.block.BlockTorch;
import net.minecraft.block.state.IBlockState;
import net.minecraft.init.Biomes;
import net.minecraft.init.Blocks;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.EnumDyeColor;
import net.minecraft.item.ItemDoor;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.chunk.IChunkProvider;
import net.minecraft.world.gen.IChunkGenerator;
import net.minecraft.world.gen.feature.WorldGenMinable;
import net.minecraft.world.storage.loot.LootContext;
import net.minecraft.world.storage.loot.LootTable;
import net.minecraftforge.common.BiomeDictionary;
import net.minecraftforge.common.IPlantable;
import net.minecraftforge.fml.common.IWorldGenerator;
public class WizardryWorldGenerator implements IWorldGenerator {
/** The string identifier for wizard tower chests, used in ChestGenHooks. */
public static final String WIZARD_TOWER = Wizardry.MODID + "wizardTower";
@Override
public void generate(Random random, int chunkX, int chunkZ, World world, IChunkGenerator chunkGenerator,
IChunkProvider chunkProvider){
for(int id : Wizardry.settings.oreDimensions){
if(id == world.provider.getDimension()) this.addOreSpawn(WizardryBlocks.crystal_ore.getDefaultState(),
world, random, chunkX * 16, chunkZ * 16, 16, 16, 5, 7, 5, 30);
}
for(int id : Wizardry.settings.flowerDimensions){
if(id == world.provider.getDimension()) this.generatePlant(WizardryBlocks.crystal_flower.getDefaultState(),
world, random, chunkX * 16, chunkZ * 16, 2, 20);
}
if(world.getWorldInfo().isMapFeaturesEnabled()){
for(int id : Wizardry.settings.towerDimensions){
if(id == world.provider.getDimension())
this.generateWizardTower(world, random, chunkX * 16, chunkZ * 16);
}
}
}
/**
* Adds an Ore Spawn to Minecraft. Simply register all Ores to spawn with this method in your Generation method in
* your IWorldGeneration extending Class
*
* @param The Block to spawn
* @param The World to spawn in
* @param A Random object for retrieving random positions within the world to spawn the Block
* @param An int for passing the X-Coordinate for the Generation method
* @param An int for passing the Z-Coordinate for the Generation method
* @param An int for setting the maximum X-Coordinate values for spawning on the X-Axis on a Per-Chunk basis
* @param An int for setting the maximum Z-Coordinate values for spawning on the Z-Axis on a Per-Chunk basis
* @param An int for setting the maximum size of a vein
* @param An int for the Number of chances available for the Block to spawn per-chunk
* @param An int for the minimum Y-Coordinate height at which this block may spawn
* @param An int for the maximum Y-Coordinate height at which this block may spawn
**/
public void addOreSpawn(IBlockState state, World world, Random random, int blockXPos, int blockZPos, int maxX,
int maxZ, int maxVeinSize, int chancesToSpawn, int minY, int maxY){
// int maxPossY = minY + (maxY - 1);
assert maxY > minY : "The maximum Y must be greater than the Minimum Y";
assert maxX > 0 && maxX <= 16 : "addOreSpawn: The Maximum X must be greater than 0 and less than 16";
assert minY > 0 : "addOreSpawn: The Minimum Y must be greater than 0";
assert maxY < 256 && maxY > 0 : "addOreSpawn: The Maximum Y must be less than 256 but greater than 0";
assert maxZ > 0 && maxZ <= 16 : "addOreSpawn: The Maximum Z must be greater than 0 and less than 16";
int diffBtwnMinMaxY = maxY - minY;
for(int x = 0; x < chancesToSpawn; x++){
int posX = blockXPos + random.nextInt(maxX);
int posY = minY + random.nextInt(diffBtwnMinMaxY);
int posZ = blockZPos + random.nextInt(maxZ);
(new WorldGenMinable(state, maxVeinSize)).generate(world, random, new BlockPos(posX, posY, posZ));
}
}
/**
* Generates the specified plant randomly throughout the world.
*
* @param block The plant block
* @param world The world
* @param random A random instance
* @param x The x coord of the first block in the chunk
* @param z The y coord of the first block in the chunk
* @param chancesToSpawn Number of chances to spawn a flower patch
* @param groupSize The number of times to try generating a flower per flower patch spawn
*/
public void generatePlant(IBlockState state, World world, Random random, int x, int z, int chancesToSpawn,
int groupSize){
for(int i = 0; i < chancesToSpawn; i++){
int randPosX = x + random.nextInt(16);
int randPosY = random.nextInt(256);
int randPosZ = z + random.nextInt(16);
for(int l = 0; l < groupSize; ++l){
int i1 = randPosX + random.nextInt(8) - random.nextInt(8);
int j1 = randPosY + random.nextInt(4) - random.nextInt(4);
int k1 = randPosZ + random.nextInt(8) - random.nextInt(8);
BlockPos pos = new BlockPos(i1, j1, k1);
if(world.isBlockLoaded(pos) && world.isAirBlock(pos) && (!world.provider.isNether() || j1 < 127)
&& state.getBlock().canPlaceBlockOnSide(world, pos, EnumFacing.UP)){
world.setBlockState(pos, state, 2);
}
}
}
}
/**
* Generates wizard towers randomly throughout the world.
*/
public void generateWizardTower(World world, Random random, int chunkX, int chunkZ){
// Allows the config file to set the rarity value to 0 to disable tower generation completely.
if(Wizardry.settings.towerRarity == 0) return;
// Compensates for the lack of space in forests. Math.max is required since treeless biomes have treesPerChunk =
// -999
// double treeFactor = 70 - Math.max((double)world.getBiomeGenForCoords(chunkX,
// chunkZ).theBiomeDecorator.treesPerChunk, 0) * 1.5d;
// Multiplied by 70 to (roughly) retain the old rarity scale
if(random.nextInt((int)(Wizardry.settings.towerRarity * 70)) == 0){
BlockPos origin = new BlockPos(chunkX + random.nextInt(16), 0, chunkZ + random.nextInt(16));
// Despite what its name suggests, this method does not return the position of a liquid. It is in fact
// exactly what is needed here since it is used for placing villages and stuff, and doesn't include leaves
// or other foliage.
origin = origin.up(world.getTopSolidOrLiquidBlock(origin).getY() - 1);
int[][][] towerBlueprint = towerBlueprintSmall;
switch(random.nextInt(4)){
case 0:
towerBlueprint = towerBlueprintSmall;
break;
case 1:
towerBlueprint = towerBlueprintMedium;
break;
case 2:
towerBlueprint = towerBlueprintTall;
break;
case 3:
towerBlueprint = towerBlueprintDouble;
break;
}
// 0 = West, 1 = North, 2 = East, 3 = South (The way you would face when walking out of the door)
EnumFacing orientation = EnumFacing.byHorizontalIndex(random.nextInt(4));
boolean flip = random.nextBoolean();
if(checkSpaceForTower(world, origin, towerBlueprint, orientation, flip)){
// == Setup ==
boolean evilWizard = random.nextInt(5) == 0;
IBlockState wallMaterial = Blocks.COBBLESTONE.getDefaultState();
BlockPlanks.EnumType woodType = BlockPlanks.EnumType.OAK;
Biome biome = world.getBiome(origin);
// The order of these is somewhat important in that biomes can be many types, so the last of these
// checks
// that is true gets priority. Generally speaking, the later the check, the more specific it is.
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.DENSE))
wallMaterial = Blocks.MOSSY_COBBLESTONE.getDefaultState();
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.SWAMP))
wallMaterial = Blocks.MOSSY_COBBLESTONE.getDefaultState();
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.SANDY))
wallMaterial = Blocks.SANDSTONE.getDefaultState();
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.NETHER))
wallMaterial = Blocks.NETHER_BRICK.getDefaultState();
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.MOUNTAIN))
wallMaterial = Blocks.STONEBRICK.getDefaultState();
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.MESA))
wallMaterial = Blocks.HARDENED_CLAY.getDefaultState();
// Unfortunately, I can't check all the wood types with the biome dictionary
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.CONIFEROUS))
woodType = BlockPlanks.EnumType.SPRUCE;
if(biome == Biomes.BIRCH_FOREST || biome == Biomes.BIRCH_FOREST_HILLS)
woodType = BlockPlanks.EnumType.BIRCH;
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.JUNGLE)) woodType = BlockPlanks.EnumType.JUNGLE;
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.SAVANNA)) woodType = BlockPlanks.EnumType.ACACIA;
// Not technically a tree type, but I think it fits quite well anyway
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.SPOOKY))
woodType = BlockPlanks.EnumType.DARK_OAK;
IBlockState[] blockStateList = new IBlockState[]{null, Blocks.AIR.getDefaultState(),
Blocks.PLANKS.getDefaultState().withProperty(BlockPlanks.VARIANT, woodType),
Blocks.BOOKSHELF.getDefaultState(),
Blocks.STAINED_HARDENED_CLAY.getDefaultState().withProperty(BlockColored.COLOR,
EnumDyeColor.values()[random.nextInt(EnumDyeColor.values().length)]),
Blocks.WOODEN_SLAB.getDefaultState().withProperty(BlockSlab.HALF, EnumBlockHalf.BOTTOM),
Blocks.WOODEN_SLAB.getDefaultState().withProperty(BlockSlab.HALF, EnumBlockHalf.TOP),
wallMaterial, Blocks.GLASS_PANE.getDefaultState(), Blocks.OAK_DOOR.getDefaultState(),
WizardryBlocks.arcane_workbench.getDefaultState(), Blocks.TORCH.getDefaultState(),
evilWizard ? Blocks.CHEST.getDefaultState() : Blocks.BOOKSHELF.getDefaultState()};
Set<BlockPos> blocksPlaced = new HashSet<BlockPos>();
// == Foundations ==
// Fills in foundations. This is done first so the door always has something to be placed on.
boolean flag = true;
// BlockPos is immutable, so I'm not sure if simply saying pos1 = pos will be sufficient.
BlockPos layerCentre = new BlockPos(origin);
// Stop when the bottom of the world is reached
while(flag && layerCentre.getY() > 0){
flag = false;
for(BlockPos offset : foundationLayer){
if(!world.isBlockNormalCube(layerCentre.add(offset), false)){
world.setBlockState(layerCentre.add(offset), wallMaterial);
blocksPlaced.add(layerCentre.add(offset));
// Keeps going as long as something was filled in.
flag = true;
}
}
layerCentre = layerCentre.down();
}
// == Main Structure ==
// It is assumed that the width of the blueprint is the same all the way up, and that the layers are
// square.
int width = towerBlueprint[0].length - 1;
// x, y and z are the position the block is being put in.
// x1, y and z1 are the position in the blueprint which determines which block is being placed.
int x1 = 0, z1 = 0;
for(int y = 0; y < towerBlueprint.length; y++){
for(int z = 0; z < towerBlueprint[y].length; z++){
for(int x = 0; x < towerBlueprint[y][z].length; x++){
BlockPos pos = origin.add(x - width / 2, y, z - width / 2);
switch(orientation){
case WEST:
x1 = flip ? width - x : x;
z1 = z;
break;
case NORTH:
x1 = z;
z1 = flip ? x : width - x;
break;
case EAST:
x1 = flip ? x : width - x;
z1 = width - z;
break;
case SOUTH:
x1 = width - z;
z1 = flip ? width - x : x;
break;
default:
break;
}
if(blockStateList[towerBlueprint[y][z1][x1]] != null
&& blockStateList[towerBlueprint[y][z1][x1]].getBlock() != Blocks.TORCH
&& blockStateList[towerBlueprint[y][z1][x1]].getBlock() != Blocks.CHEST){
if(blockStateList[towerBlueprint[y][z1][x1]].getBlock() == Blocks.OAK_DOOR){
// Rotates the door depending on whether flip is true.
ItemDoor.placeDoor(
world, pos, flip
? EnumFacing.byHorizontalIndex(3 - orientation.getHorizontalIndex())
.getOpposite()
: orientation.rotateYCCW(),
Blocks.OAK_DOOR, false);
}else{
world.setBlockState(pos, blockStateList[towerBlueprint[y][z1][x1]], 2);
}
blocksPlaced.add(pos);
}
}
}
}
// == Extras ==
// Torches are done afterwards so they don't fall off
// Chests are also done afterwards, because for some reason the chest decides which way round to face
// itself, after it has been placed, based on the surrounding blocks... but if it was placed during
// the rest of the tower generation, some of those blocks wouldn't exist, hence it must be done here.
for(int y = 0; y < towerBlueprint.length; y++){
for(int z = 0; z < towerBlueprint[y].length; z++){
for(int x = 0; x < towerBlueprint[y][z].length; x++){
BlockPos pos = origin.add(x - width / 2, y, z - width / 2);
switch(orientation){
case WEST:
x1 = flip ? width - x : x;
z1 = z;
break;
case NORTH:
x1 = z;
z1 = flip ? x : width - x;
break;
case EAST:
x1 = flip ? x : width - x;
z1 = width - z;
break;
case SOUTH:
x1 = width - z;
z1 = flip ? width - x : x;
break;
default:
break;
}
if(blockStateList[towerBlueprint[y][z1][x1]] != null){
if(blockStateList[towerBlueprint[y][z1][x1]].getBlock() == Blocks.TORCH){
if(placeTorch(world, pos, true)){
blocksPlaced.add(pos);
}else{
Wizardry.logger.info("Attempted to generate a torch at " + pos + " in " + world
+ ", but failed!");
}
}
// World should always be a WorldServer, but it's worth checking anyway.
if(blockStateList[towerBlueprint[y][z1][x1]].getBlock() == Blocks.CHEST
&& world instanceof WorldServer){
if(placeChest(world, pos)){
blocksPlaced.add(pos);
LootTable table = world.getLootTableManager().getLootTableFromLocation(
new ResourceLocation(Wizardry.MODID, "chests/wizard_tower"));
IInventory inventory = (IInventory)world.getTileEntity(pos);
LootContext context = new LootContext.Builder((WorldServer)world).build();
table.fillInventory(inventory, random, context);
}else{
Wizardry.logger.info("Attempted to generate a chest at " + pos + " in " + world
+ ", but failed!");
}
}
}
}
}
}
if(evilWizard){
EntityEvilWizard wizard = new EntityEvilWizard(world);
wizard.hasTower = true;
wizard.setLocationAndAngles(origin.getX() + 1.5, origin.getY() + towerBlueprint.length - 9.5,
origin.getZ() + 1.5, 0, 0);
wizard.onInitialSpawn(world.getDifficultyForLocation(origin), null);
world.spawnEntity(wizard);
}else{
EntityWizard wizard = new EntityWizard(world);
wizard.setLocationAndAngles(origin.getX() + 1.5, origin.getY() + towerBlueprint.length - 9.5,
origin.getZ() + 1.5, 0, 0);
wizard.onInitialSpawn(world.getDifficultyForLocation(origin), null);
wizard.setTowerBlocks(blocksPlaced);
world.spawnEntity(wizard);
}
}
}
}
/**
* Places a torch at the given position in the given world and automatically assigns an appropriate state. Order of
* priority is U-S-W-N-E, or S-W-N-E-U if wallPriority is true.
*
* @param world The world to place the torch in.
* @param pos The position to place the torch at.
* @param wallPriority True to prioritise wall torches, false to prioritise floor torches.
* @return True if the torch was placed, false if it is not possible.
*/
private static boolean placeTorch(World world, BlockPos pos, boolean wallPriority){
for(EnumFacing facing : ArrayUtils.add(EnumFacing.HORIZONTALS, wallPriority ? 4 : 0, EnumFacing.UP)){
if(world.isSideSolid(pos.offset(facing.getOpposite()), facing)){
world.setBlockState(pos, Blocks.TORCH.getDefaultState().withProperty(BlockTorch.FACING, facing));
return true;
}
}
return false;
}
/**
* Places a chest at the given position in the given world and automatically assigns an appropriate state. Order of
* priority is S-W-N-E.
*
* @param world The world to chest the torch in.
* @param pos The position to chest the torch at.
* @return True if the chest was placed, false if it is not possible.
*/
private static boolean placeChest(World world, BlockPos pos){
for(EnumFacing facing : EnumFacing.HORIZONTALS){
if(world.isAirBlock(pos.offset(facing))){
world.setBlockState(pos, Blocks.CHEST.getDefaultState().withProperty(BlockChest.FACING, facing));
return true;
}
}
return false;
}
/**
* Checks whether a tower generated at the given coordinates will intersect any solid or liquid blocks. Only tests
* for liquids (not solid blocks) for the first four layers to account for the floor and for sloping terrain.
*
* @return True if none of the blocks which the tower would replace are solid or liquid. Dirt, stone etc., water and
* lava count, as do logs, but leaves and plants don't.
*/
private static boolean checkSpaceForTower(World world, BlockPos pos, int[][][] towerBlueprint,
EnumFacing orientation, boolean flip){
// x, y and z are the position the block is being put in.
// x1, y and z1 are the position in the blueprint which determines which block is being placed.
int x1 = 0, z1 = 0;
// It is assumed that the width of the blueprint is the same all the way up, and that the layers are square.
int width = towerBlueprint[0].length - 1;
for(int y = 0; y < towerBlueprint.length; y++){
for(int z = 0; z < towerBlueprint[y].length; z++){
for(int x = 0; x < towerBlueprint[y][z].length; x++){
BlockPos pos1 = pos.add(x - width / 2, y, z - width / 2);
switch(orientation){
case WEST:
x1 = flip ? width - x : x;
z1 = z;
break;
case NORTH:
x1 = z;
z1 = flip ? x : width - x;
break;
case EAST:
x1 = flip ? x : width - x;
z1 = width - z;
break;
case SOUTH:
x1 = width - z;
z1 = flip ? width - x : x;
break;
default:
break;
}
if(towerBlueprint[y][z1][x1] != 0 && !WizardryUtilities.canBlockBeReplacedB(world, pos1)
// TODO: Is this a better replacement for the subsequent two lines?
// && !world.getBlockState(pos1).getBlock().isFoliage(world, pos1)
&& !(world.getBlockState(pos1).getBlock() instanceof IPlantable)
&& !(world.getBlockState(pos1).getBlock() instanceof BlockLeaves)
&& (y > 3 || world.getBlockState(pos1).getBlock() instanceof BlockLiquid)){
return false;
}
}
}
}
return true;
}
/** Array of relative positions of each block in a layer of foundations. */
private static final BlockPos[] foundationLayer = new BlockPos[]{new BlockPos(-2, 0, -1), new BlockPos(-2, 0, 0),
new BlockPos(-2, 0, 1), new BlockPos(2, 0, -1), new BlockPos(2, 0, 0), new BlockPos(2, 0, 1),
new BlockPos(-1, 0, -2), new BlockPos(0, 0, -2), new BlockPos(1, 0, -2), new BlockPos(-1, 0, 2),
new BlockPos(0, 0, 2), new BlockPos(1, 0, 2),};
/**
* 3D matrix of integers representing the different blocks which make up the wizard tower. The blocks corresponding
* to each integer are as follows (note that some blocks change depending on the biome):
* <p>
* 0 Nothing (keep existing block)<br>
* 1 Air (remove existing block)<br>
* 2 Floor (planks)<br>
* 3 Bookshelf<br>
* 4 Roof (stained clay)<br>
* 5 Floor slab (lower half)<br>
* 6 Floor slab (upper half)<br>
* 7 Wall (cobblestone by default)<br>
* 8 Glass pane<br>
* 9 Door (metadata is handled by the built-in vanilla method)<br>
* 10 Arcane workbench<br>
* 11 Torch (metadata is handled separately depending on adjacent blocks)<br>
* 12 Chest (only generates if the wizard is evil, otherwise places a bookshelf instead)
*/
private static final int[][][] towerBlueprintSmall = {
// x is horizontal, z is vertical, y is layers
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 2, 2, 2, 0, 0, 0}, {0, 0, 0, 2, 2, 2, 0, 0, 0}, {0, 0, 0, 2, 2, 2, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 6, 5, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 9, 7, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 5, 1, 1, 7, 0, 0}, {0, 0, 7, 6, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 0, 7, 0, 0, 0}, {0, 0, 0, 11, 1, 11, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 11, 7, 0, 0}, {0, 0, 7, 5, 6, 1, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 6, 7, 0, 0}, {0, 0, 7, 1, 1, 5, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 6, 5, 7, 0, 0}, {0, 0, 7, 11, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 5, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 7, 2, 2, 2, 7, 0, 0},
{0, 7, 1, 1, 1, 1, 2, 7, 0}, {0, 7, 5, 2, 2, 6, 2, 7, 0}, {0, 7, 2, 2, 2, 2, 2, 7, 0},
{0, 0, 7, 2, 2, 2, 7, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 7, 3, 3, 3, 7, 0, 0},
{0, 7, 1, 1, 1, 1, 3, 7, 0}, {0, 7, 1, 1, 1, 1, 3, 7, 0}, {0, 7, 1, 1, 10, 1, 3, 7, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 7, 11, 1, 11, 7, 0, 0},
{0, 7, 1, 1, 1, 1, 3, 7, 0}, {0, 8, 1, 1, 1, 1, 3, 7, 0}, {0, 7, 1, 1, 1, 1, 3, 7, 0},
{0, 0, 7, 11, 1, 11, 7, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 4, 7, 7, 7, 4, 0, 0}, {0, 4, 7, 1, 1, 1, 7, 4, 0},
{4, 7, 1, 1, 1, 1, 3, 7, 4}, {4, 7, 1, 1, 1, 1, 3, 7, 4}, {4, 7, 1, 1, 1, 1, 12, 7, 4},
{0, 4, 7, 1, 1, 1, 7, 4, 0}, {0, 0, 4, 7, 7, 7, 4, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0},
{0, 4, 1, 1, 1, 1, 1, 4, 0}, {0, 4, 1, 1, 1, 1, 1, 4, 0}, {0, 4, 1, 1, 1, 1, 1, 4, 0},
{0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0},
{0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0},
{0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0},
{0, 0, 0, 4, 1, 4, 0, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 0, 4, 1, 4, 0, 0, 0},
{0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 0, 4, 1, 4, 0, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 4, 1, 4, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}};
/**
* 3D matrix of integers representing the different blocks which make up the wizard tower. The blocks corresponding
* to each integer are as follows (note that some blocks change depending on the biome):
* <p>
* 0 Nothing (keep existing block)<br>
* 1 Air (remove existing block)<br>
* 2 Floor (planks)<br>
* 3 Bookshelf<br>
* 4 Roof (stained clay)<br>
* 5 Floor slab (lower half)<br>
* 6 Floor slab (upper half)<br>
* 7 Wall (cobblestone by default)<br>
* 8 Glass pane<br>
* 9 Door (metadata is handled by the built-in vanilla method)<br>
* 10 Arcane workbench<br>
* 11 Torch (metadata is handled separately depending on adjacent blocks)<br>
* 12 Chest (only generates if the wizard is evil, otherwise places a bookshelf instead)
*/
private static final int[][][] towerBlueprintMedium = {
// x is horizontal, z is vertical, y is layers
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 2, 2, 2, 0, 0, 0}, {0, 0, 0, 2, 2, 2, 0, 0, 0}, {0, 0, 0, 2, 2, 2, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 6, 5, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 9, 7, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 5, 1, 1, 7, 0, 0}, {0, 0, 7, 6, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 0, 7, 0, 0, 0}, {0, 0, 0, 11, 1, 11, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 11, 7, 0, 0}, {0, 0, 7, 5, 6, 1, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 6, 7, 0, 0}, {0, 0, 7, 1, 1, 5, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 6, 5, 7, 0, 0}, {0, 0, 7, 11, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 5, 1, 1, 7, 0, 0}, {0, 0, 7, 6, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 11, 7, 0, 0}, {0, 0, 7, 5, 6, 1, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 6, 7, 0, 0}, {0, 0, 7, 1, 1, 5, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 6, 5, 7, 0, 0}, {0, 0, 7, 11, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 5, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 7, 2, 2, 2, 7, 0, 0},
{0, 7, 1, 1, 1, 1, 2, 7, 0}, {0, 7, 5, 2, 2, 6, 2, 7, 0}, {0, 7, 2, 2, 2, 2, 2, 7, 0},
{0, 0, 7, 2, 2, 2, 7, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 7, 3, 3, 3, 7, 0, 0},
{0, 7, 1, 1, 1, 1, 3, 7, 0}, {0, 7, 1, 1, 1, 1, 3, 7, 0}, {0, 7, 1, 1, 10, 1, 3, 7, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 7, 11, 1, 11, 7, 0, 0},
{0, 7, 1, 1, 1, 1, 3, 7, 0}, {0, 8, 1, 1, 1, 1, 3, 7, 0}, {0, 7, 1, 1, 1, 1, 3, 7, 0},
{0, 0, 7, 11, 1, 11, 7, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 4, 7, 7, 7, 4, 0, 0}, {0, 4, 7, 1, 1, 1, 7, 4, 0},
{4, 7, 1, 1, 1, 1, 3, 7, 4}, {4, 7, 1, 1, 1, 1, 3, 7, 4}, {4, 7, 1, 1, 1, 1, 12, 7, 4},
{0, 4, 7, 1, 1, 1, 7, 4, 0}, {0, 0, 4, 7, 7, 7, 4, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0},
{0, 4, 1, 1, 1, 1, 1, 4, 0}, {0, 4, 1, 1, 1, 1, 1, 4, 0}, {0, 4, 1, 1, 1, 1, 1, 4, 0},
{0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0},
{0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0},
{0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0},
{0, 0, 0, 4, 1, 4, 0, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 0, 4, 1, 4, 0, 0, 0},
{0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 0, 4, 1, 4, 0, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 4, 1, 4, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}};
/**
* 3D matrix of integers representing the different blocks which make up the wizard tower. The blocks corresponding
* to each integer are as follows (note that some blocks change depending on the biome):
* <p>
* 0 Nothing (keep existing block)<br>
* 1 Air (remove existing block)<br>
* 2 Floor (planks)<br>
* 3 Bookshelf<br>
* 4 Roof (stained clay)<br>
* 5 Floor slab (lower half)<br>
* 6 Floor slab (upper half)<br>
* 7 Wall (cobblestone by default)<br>
* 8 Glass pane<br>
* 9 Door (metadata is handled by the built-in vanilla method)<br>
* 10 Arcane workbench<br>
* 11 Torch (metadata is handled separately depending on adjacent blocks)<br>
* 12 Chest (only generates if the wizard is evil, otherwise places a bookshelf instead)
*/
private static final int[][][] towerBlueprintTall = {
// x is horizontal, z is vertical, y is layers
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 2, 2, 2, 0, 0, 0}, {0, 0, 0, 2, 2, 2, 0, 0, 0}, {0, 0, 0, 2, 2, 2, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 6, 5, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 9, 7, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 5, 1, 1, 7, 0, 0}, {0, 0, 7, 6, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 0, 7, 0, 0, 0}, {0, 0, 0, 11, 1, 11, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 11, 7, 0, 0}, {0, 0, 7, 5, 6, 1, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 6, 7, 0, 0}, {0, 0, 7, 1, 1, 5, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 6, 5, 7, 0, 0}, {0, 0, 7, 11, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 5, 1, 1, 7, 0, 0}, {0, 0, 7, 6, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 11, 7, 0, 0}, {0, 0, 7, 5, 6, 1, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 6, 7, 0, 0}, {0, 0, 7, 1, 1, 5, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 6, 5, 7, 0, 0}, {0, 0, 7, 11, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 5, 1, 1, 7, 0, 0}, {0, 0, 7, 6, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 11, 7, 0, 0}, {0, 0, 7, 5, 6, 1, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 6, 7, 0, 0}, {0, 0, 7, 1, 1, 5, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 1, 6, 5, 7, 0, 0}, {0, 0, 7, 11, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0},
{0, 0, 7, 5, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 7, 1, 1, 1, 7, 0, 0},
{0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 7, 2, 2, 2, 7, 0, 0},
{0, 7, 1, 1, 1, 1, 2, 7, 0}, {0, 7, 5, 2, 2, 6, 2, 7, 0}, {0, 7, 2, 2, 2, 2, 2, 7, 0},
{0, 0, 7, 2, 2, 2, 7, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 7, 3, 3, 3, 7, 0, 0},
{0, 7, 1, 1, 1, 1, 3, 7, 0}, {0, 7, 1, 1, 1, 1, 3, 7, 0}, {0, 7, 1, 1, 10, 1, 3, 7, 0},
{0, 0, 7, 1, 1, 1, 7, 0, 0}, {0, 0, 0, 7, 7, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 7, 11, 1, 11, 7, 0, 0},
{0, 7, 1, 1, 1, 1, 3, 7, 0}, {0, 8, 1, 1, 1, 1, 3, 7, 0}, {0, 7, 1, 1, 1, 1, 3, 7, 0},
{0, 0, 7, 11, 1, 11, 7, 0, 0}, {0, 0, 0, 7, 8, 7, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 4, 7, 7, 7, 4, 0, 0}, {0, 4, 7, 1, 1, 1, 7, 4, 0},
{4, 7, 1, 1, 1, 1, 3, 7, 4}, {4, 7, 1, 1, 1, 1, 3, 7, 4}, {4, 7, 1, 1, 1, 1, 12, 7, 4},
{0, 4, 7, 1, 1, 1, 7, 4, 0}, {0, 0, 4, 7, 7, 7, 4, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0},
{0, 4, 1, 1, 1, 1, 1, 4, 0}, {0, 4, 1, 1, 1, 1, 1, 4, 0}, {0, 4, 1, 1, 1, 1, 1, 4, 0},
{0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0},
{0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0},
{0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0},
{0, 0, 0, 4, 1, 4, 0, 0, 0}, {0, 0, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 0, 4, 1, 4, 0, 0, 0},
{0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 4, 4, 4, 0, 0, 0}, {0, 0, 0, 4, 1, 4, 0, 0, 0}, {0, 0, 0, 4, 4, 4, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 4, 1, 4, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0},}};
/**
* 3D matrix of integers representing the different blocks which make up the wizard tower. The blocks corresponding
* to each integer are as follows (note that some blocks change depending on the biome):
* <p>
* 0 Nothing (keep existing block)<br>
* 1 Air (remove existing block)<br>
* 2 Floor (planks)<br>
* 3 Bookshelf<br>
* 4 Roof (stained clay)<br>
* 5 Floor slab (lower half)<br>
* 6 Floor slab (upper half)<br>
* 7 Wall (cobblestone by default)<br>
* 8 Glass pane<br>
* 9 Door (metadata is handled by the built-in vanilla method)<br>
* 10 Arcane workbench<br>
* 11 Torch (metadata is handled separately depending on adjacent blocks)<br>
* 12 Chest (only generates if the wizard is evil, otherwise places a bookshelf instead)
*/
private static final int[][][] towerBlueprintDouble = {
// x is horizontal, z is vertical, y is layers
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 1, 6, 5, 7, 0, 0, 0, 0},
{0, 0, 0, 0, 7, 1, 1, 1, 7, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 9, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 5, 1, 1, 7, 0, 0, 0, 0},
{0, 0, 0, 0, 7, 6, 1, 1, 7, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 0, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 11, 1, 11, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 8, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 0, 0, 0, 0},
{0, 0, 0, 0, 7, 1, 1, 11, 7, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 5, 6, 1, 7, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 0, 0, 0, 0},
{0, 0, 0, 0, 7, 1, 1, 6, 7, 7, 7, 0, 0}, {0, 0, 0, 0, 7, 1, 1, 5, 7, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 1, 6, 5, 7, 7, 7, 0, 0},
{0, 0, 0, 0, 7, 11, 1, 1, 1, 1, 1, 7, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 7, 7, 0, 0},
{0, 0, 0, 0, 0, 7, 8, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 5, 1, 1, 7, 7, 8, 7, 0},
{0, 0, 0, 0, 7, 6, 1, 1, 1, 1, 1, 8, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 7, 8, 7, 0},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 8, 7, 0, 4, 4, 4, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 7, 7, 7, 4},
{0, 0, 0, 0, 7, 1, 1, 11, 7, 1, 1, 7, 4}, {0, 0, 0, 0, 7, 5, 6, 1, 7, 7, 7, 7, 4},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 4, 4, 4, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 4, 4, 4, 0},
{0, 0, 0, 0, 7, 1, 1, 6, 7, 4, 1, 4, 0}, {0, 0, 0, 0, 7, 1, 1, 5, 7, 4, 4, 4, 0},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 1, 6, 5, 7, 0, 4, 0, 0},
{0, 0, 0, 0, 7, 11, 1, 1, 7, 4, 1, 4, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 0, 4, 0, 0},
{0, 0, 0, 0, 0, 7, 8, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 7, 5, 1, 1, 7, 0, 0, 0, 0},
{0, 0, 0, 0, 7, 1, 1, 1, 7, 0, 4, 0, 0}, {0, 0, 0, 0, 7, 1, 1, 1, 7, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 7, 2, 2, 2, 7, 0, 0, 0, 0}, {0, 0, 0, 7, 1, 1, 1, 1, 2, 7, 0, 0, 0},
{0, 0, 0, 7, 5, 2, 2, 6, 2, 7, 4, 0, 0}, {0, 0, 0, 7, 2, 2, 2, 2, 2, 7, 0, 0, 0},
{0, 0, 0, 0, 7, 2, 2, 2, 7, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 7, 3, 3, 3, 7, 0, 0, 0, 0}, {0, 0, 0, 7, 1, 1, 1, 1, 3, 7, 0, 0, 0},
{0, 0, 0, 7, 1, 1, 1, 1, 3, 7, 0, 0, 0}, {0, 0, 0, 7, 1, 1, 10, 1, 3, 7, 0, 0, 0},
{0, 0, 0, 0, 7, 1, 1, 1, 7, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 7, 8, 7, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 7, 11, 1, 11, 7, 0, 0, 0, 0}, {0, 0, 0, 7, 1, 1, 1, 1, 3, 7, 0, 0, 0},
{0, 0, 0, 8, 1, 1, 1, 1, 3, 7, 0, 0, 0}, {0, 0, 0, 7, 1, 1, 1, 1, 3, 7, 0, 0, 0},
{0, 0, 0, 0, 7, 11, 1, 11, 7, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 7, 8, 7, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 7, 7, 7, 4, 0, 0, 0, 0},
{0, 0, 0, 4, 7, 1, 1, 1, 7, 4, 0, 0, 0}, {0, 0, 4, 7, 1, 1, 1, 1, 3, 7, 4, 0, 0},
{0, 0, 4, 7, 1, 1, 1, 1, 3, 7, 4, 0, 0}, {0, 0, 4, 7, 1, 1, 1, 1, 12, 7, 4, 0, 0},
{0, 0, 0, 4, 7, 1, 1, 1, 7, 4, 0, 0, 0}, {0, 0, 0, 0, 4, 7, 7, 7, 4, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 4, 1, 1, 1, 4, 0, 0, 0, 0}, {0, 0, 0, 4, 1, 1, 1, 1, 1, 4, 0, 0, 0},
{0, 0, 0, 4, 1, 1, 1, 1, 1, 4, 0, 0, 0}, {0, 0, 0, 4, 1, 1, 1, 1, 1, 4, 0, 0, 0},
{0, 0, 0, 0, 4, 1, 1, 1, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 1, 1, 1, 4, 0, 0, 0, 0},
{0, 0, 0, 0, 4, 1, 1, 1, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 4, 1, 1, 1, 4, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 4, 1, 4, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 4, 1, 1, 1, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 4, 1, 4, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 4, 1, 4, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 4, 1, 4, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},},
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},}};
}
@@ -0,0 +1,135 @@
package electroblob.wizardry.advancement;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonObject;
import net.minecraft.advancements.ICriterionTrigger;
import net.minecraft.advancements.PlayerAdvancements;
import net.minecraft.advancements.critereon.AbstractCriterionInstance;
import net.minecraft.advancements.critereon.ItemPredicate;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import java.util.List;
import java.util.Map;
import java.util.Set;
/** Advancement trigger for things done in the arcane workbench. The majority of any
* ICriterionTrigger class is just boilerplate, and this is no exception. */
public class ArcaneWorkbenchTrigger implements ICriterionTrigger<ArcaneWorkbenchTrigger.Instance> {
private final ResourceLocation id;
private final Map<PlayerAdvancements, ArcaneWorkbenchTrigger.Listeners> listeners = Maps.newHashMap();
public ArcaneWorkbenchTrigger(ResourceLocation id){
this.id = id;
}
public ResourceLocation getId(){
return this.id;
}
public void addListener(PlayerAdvancements advancements, Listener<ArcaneWorkbenchTrigger.Instance> listener){
ArcaneWorkbenchTrigger.Listeners listeners = this.listeners.get(advancements);
if(listeners == null){
listeners = new ArcaneWorkbenchTrigger.Listeners(advancements);
this.listeners.put(advancements, listeners);
}
listeners.add(listener);
}
public void removeListener(PlayerAdvancements advancements, Listener<ArcaneWorkbenchTrigger.Instance> listener){
ArcaneWorkbenchTrigger.Listeners listeners = this.listeners.get(advancements);
if(listeners != null){
listeners.remove(listener);
if(listeners.isEmpty()){
this.listeners.remove(advancements);
}
}
}
public void removeAllListeners(PlayerAdvancements advancements){
this.listeners.remove(advancements);
}
public ArcaneWorkbenchTrigger.Instance deserializeInstance(JsonObject json, JsonDeserializationContext context){
return new ArcaneWorkbenchTrigger.Instance(this.id, ItemPredicate.deserialize(json.get("item")));
}
public void trigger(EntityPlayerMP player, ItemStack stack){
ArcaneWorkbenchTrigger.Listeners listeners = this.listeners.get(player.getAdvancements());
if(listeners != null){
listeners.trigger(stack);
}
}
public static class Instance extends AbstractCriterionInstance {
private final ItemPredicate item;
public Instance(ResourceLocation criterionIn, ItemPredicate item){
super(criterionIn);
this.item = item;
}
public boolean test(ItemStack stack){
return this.item.test(stack);
}
}
static class Listeners {
private final PlayerAdvancements playerAdvancements;
private final Set<Listener<ArcaneWorkbenchTrigger.Instance>> listeners = Sets.newHashSet();
public Listeners(PlayerAdvancements advancements){
this.playerAdvancements = advancements;
}
public boolean isEmpty(){
return this.listeners.isEmpty();
}
public void add(Listener<ArcaneWorkbenchTrigger.Instance> listener){
this.listeners.add(listener);
}
public void remove(Listener<ArcaneWorkbenchTrigger.Instance> listener){
this.listeners.remove(listener);
}
public void trigger(ItemStack stack){
List<Listener<ArcaneWorkbenchTrigger.Instance>> list = null;
for(Listener<ArcaneWorkbenchTrigger.Instance> listener : this.listeners){
if(listener.getCriterionInstance().test(stack)){
if(list == null){
list = Lists.newArrayList();
}
list.add(listener);
}
}
if(list != null){
for(Listener<ArcaneWorkbenchTrigger.Instance> listener : list){
listener.grantCriterion(this.playerAdvancements);
}
}
}
}
}
@@ -1,10 +1,9 @@
package electroblob.wizardry.util; package electroblob.wizardry.advancement;
import com.google.common.collect.HashMultimap; import com.google.common.collect.HashMultimap;
import com.google.common.collect.SetMultimap; import com.google.common.collect.SetMultimap;
import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import electroblob.wizardry.Wizardry; import electroblob.wizardry.Wizardry;
import net.minecraft.advancements.ICriterionInstance; import net.minecraft.advancements.ICriterionInstance;
import net.minecraft.advancements.ICriterionTrigger; import net.minecraft.advancements.ICriterionTrigger;
@@ -34,7 +33,7 @@ public class CustomAdvancementTrigger implements ICriterionTrigger<CustomAdvance
* advancement to happen. * advancement to happen.
*/ */
public static class Instance extends AbstractCriterionInstance { public static class Instance extends AbstractCriterionInstance {
public Instance(ResourceLocation triggerId) { public Instance(ResourceLocation triggerId) {
super(triggerId); super(triggerId);
} }
} }
@@ -0,0 +1,139 @@
package electroblob.wizardry.advancement;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonObject;
import electroblob.wizardry.spell.Spell;
import net.minecraft.advancements.ICriterionTrigger;
import net.minecraft.advancements.PlayerAdvancements;
import net.minecraft.advancements.critereon.AbstractCriterionInstance;
import net.minecraft.advancements.critereon.ItemPredicate;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import java.util.List;
import java.util.Map;
import java.util.Set;
/** Advancement trigger which is triggered when a spell is cast. The majority of any
* ICriterionTrigger class is just boilerplate, and this is no exception. */
public class SpellCastTrigger implements ICriterionTrigger<SpellCastTrigger.Instance> {
private final ResourceLocation id;
private final Map<PlayerAdvancements, SpellCastTrigger.Listeners> listeners = Maps.newHashMap();
public SpellCastTrigger(ResourceLocation id){
this.id = id;
}
public ResourceLocation getId(){
return this.id;
}
public void addListener(PlayerAdvancements advancements, Listener<SpellCastTrigger.Instance> listener){
SpellCastTrigger.Listeners listeners = this.listeners.get(advancements);
if(listeners == null){
listeners = new SpellCastTrigger.Listeners(advancements);
this.listeners.put(advancements, listeners);
}
listeners.add(listener);
}
public void removeListener(PlayerAdvancements advancements, Listener<SpellCastTrigger.Instance> listener){
SpellCastTrigger.Listeners listeners = this.listeners.get(advancements);
if(listeners != null){
listeners.remove(listener);
if(listeners.isEmpty()){
this.listeners.remove(advancements);
}
}
}
public void removeAllListeners(PlayerAdvancements advancements){
this.listeners.remove(advancements);
}
public SpellCastTrigger.Instance deserializeInstance(JsonObject json, JsonDeserializationContext context){
return new SpellCastTrigger.Instance(this.id, SpellPredicate.deserialize(json.get("spell")),
ItemPredicate.deserialize(json.get("item")));
}
public void trigger(EntityPlayerMP player, Spell spell, ItemStack stack){
SpellCastTrigger.Listeners listeners = this.listeners.get(player.getAdvancements());
if(listeners != null){
listeners.trigger(spell, stack);
}
}
public static class Instance extends AbstractCriterionInstance {
private final SpellPredicate spell;
private final ItemPredicate item;
public Instance(ResourceLocation criterion, SpellPredicate spell, ItemPredicate item){
super(criterion);
this.spell = spell;
this.item = item;
}
public boolean test(Spell spell, ItemStack stack){
return this.spell.test(spell) && item.test(stack);
}
}
static class Listeners {
private final PlayerAdvancements playerAdvancements;
private final Set<Listener<SpellCastTrigger.Instance>> listeners = Sets.newHashSet();
public Listeners(PlayerAdvancements advancements){
this.playerAdvancements = advancements;
}
public boolean isEmpty(){
return this.listeners.isEmpty();
}
public void add(Listener<SpellCastTrigger.Instance> listener){
this.listeners.add(listener);
}
public void remove(Listener<SpellCastTrigger.Instance> listener){
this.listeners.remove(listener);
}
public void trigger(Spell spell, ItemStack stack){
List<Listener<SpellCastTrigger.Instance>> list = null;
for(Listener<SpellCastTrigger.Instance> listener : this.listeners){
if(listener.getCriterionInstance().test(spell, stack)){
if(list == null){
list = Lists.newArrayList();
}
list.add(listener);
}
}
if(list != null){
for(Listener<SpellCastTrigger.Instance> listener : list){
listener.grantCriterion(this.playerAdvancements);
}
}
}
}
}
@@ -0,0 +1,143 @@
package electroblob.wizardry.advancement;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import electroblob.wizardry.event.DiscoverSpellEvent;
import electroblob.wizardry.spell.Spell;
import net.minecraft.advancements.ICriterionTrigger;
import net.minecraft.advancements.PlayerAdvancements;
import net.minecraft.advancements.critereon.AbstractCriterionInstance;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.util.JsonUtils;
import net.minecraft.util.ResourceLocation;
import java.util.List;
import java.util.Map;
import java.util.Set;
/** Advancement trigger that is triggered when a spell is discovered. The majority of any
* ICriterionTrigger class is just boilerplate, and this is no exception. */
public class SpellDiscoveryTrigger implements ICriterionTrigger<SpellDiscoveryTrigger.Instance> {
private final ResourceLocation id;
private final Map<PlayerAdvancements, SpellDiscoveryTrigger.Listeners> listeners = Maps.newHashMap();
public SpellDiscoveryTrigger(ResourceLocation id){
this.id = id;
}
public ResourceLocation getId(){
return this.id;
}
public void addListener(PlayerAdvancements advancements, Listener<SpellDiscoveryTrigger.Instance> listener){
SpellDiscoveryTrigger.Listeners listeners = this.listeners.get(advancements);
if(listeners == null){
listeners = new SpellDiscoveryTrigger.Listeners(advancements);
this.listeners.put(advancements, listeners);
}
listeners.add(listener);
}
public void removeListener(PlayerAdvancements advancements, Listener<SpellDiscoveryTrigger.Instance> listener){
SpellDiscoveryTrigger.Listeners listeners = this.listeners.get(advancements);
if(listeners != null){
listeners.remove(listener);
if(listeners.isEmpty()){
this.listeners.remove(advancements);
}
}
}
public void removeAllListeners(PlayerAdvancements advancements){
this.listeners.remove(advancements);
}
public SpellDiscoveryTrigger.Instance deserializeInstance(JsonObject json, JsonDeserializationContext context){
String s = JsonUtils.getString(json, "source");
DiscoverSpellEvent.Source source = DiscoverSpellEvent.Source.byName(s);
if(source == null) throw new JsonSyntaxException("No such spell discovery source: " + s);
return new SpellDiscoveryTrigger.Instance(this.id, SpellPredicate.deserialize(json.get("spell")), source);
}
public void trigger(EntityPlayerMP player, Spell spell, DiscoverSpellEvent.Source source){
SpellDiscoveryTrigger.Listeners listeners = this.listeners.get(player.getAdvancements());
if(listeners != null){
listeners.trigger(spell, source);
}
}
public static class Instance extends AbstractCriterionInstance {
private final SpellPredicate spell;
private final DiscoverSpellEvent.Source source;
public Instance(ResourceLocation criterion, SpellPredicate spell, DiscoverSpellEvent.Source source){
super(criterion);
this.spell = spell;
this.source = source;
}
public boolean test(Spell spell, DiscoverSpellEvent.Source source){
return this.spell.test(spell) && source == this.source;
}
}
static class Listeners {
private final PlayerAdvancements playerAdvancements;
private final Set<Listener<SpellDiscoveryTrigger.Instance>> listeners = Sets.newHashSet();
public Listeners(PlayerAdvancements advancements){
this.playerAdvancements = advancements;
}
public boolean isEmpty(){
return this.listeners.isEmpty();
}
public void add(Listener<SpellDiscoveryTrigger.Instance> listener){
this.listeners.add(listener);
}
public void remove(Listener<SpellDiscoveryTrigger.Instance> listener){
this.listeners.remove(listener);
}
public void trigger(Spell spell, DiscoverSpellEvent.Source source){
List<Listener<SpellDiscoveryTrigger.Instance>> list = null;
for(Listener<SpellDiscoveryTrigger.Instance> listener : this.listeners){
if(listener.getCriterionInstance().test(spell, source)){
if(list == null){
list = Lists.newArrayList();
}
list.add(listener);
}
}
if(list != null){
for(Listener<SpellDiscoveryTrigger.Instance> listener : list){
listener.grantCriterion(this.playerAdvancements);
}
}
}
}
}
@@ -0,0 +1,117 @@
package electroblob.wizardry.advancement;
import com.google.common.collect.Streams;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.spell.Spell;
import net.minecraft.util.JsonUtils;
import javax.annotation.Nullable;
import java.util.Arrays;
/** Predicate used by advancement triggers to match spells. */
public class SpellPredicate {
public static final SpellPredicate ANY = new SpellPredicate();
private final Spell spell;
private final Tier[] tiers;
private final Element[] elements;
public SpellPredicate(){
this.spell = null;
this.tiers = Tier.values();
this.elements = Element.values();
}
public SpellPredicate(@Nullable Spell spell, Tier[] tiers, Element[] elements){
this.spell = spell;
this.tiers = tiers;
this.elements = elements;
}
public boolean test(Spell spell){
if(this.spell != null && spell != this.spell){
return false;
}else if(!Arrays.asList(this.tiers).contains(spell.getTier())){
return false;
}else if(!Arrays.asList(this.elements).contains(spell.getElement())){
return false;
}
return true;
}
public static SpellPredicate deserialize(@Nullable JsonElement element){
if(element != null && !element.isJsonNull()){
JsonObject jsonobject = JsonUtils.getJsonObject(element, "spell");
Spell spell = null;
if(jsonobject.has("spell")){
String s = JsonUtils.getString(jsonobject, "spell");
spell = Spell.get(s);
if(spell == null){
throw new JsonSyntaxException("Unknown spell id '" + s + "'");
}
}
Tier[] tiers = Tier.values();
if(jsonobject.has("tiers")){
try{
JsonArray array = JsonUtils.getJsonArray(jsonobject, "tiers");
tiers = Streams.stream(array)
.map(je -> Tier.fromName(JsonUtils.getString(je, "element of array tiers")))
.toArray(Tier[]::new);
}catch(IllegalArgumentException e){
throw new JsonSyntaxException("Incorrect spell predicate value", e);
}
}
Element[] elements = Element.values();
if(jsonobject.has("elements")){
try{
JsonArray array = JsonUtils.getJsonArray(jsonobject, "elements");
elements = Streams.stream(array)
.map(je -> Element.fromName(JsonUtils.getString(je, "element of array elements")))
.toArray(Element[]::new);
}catch(IllegalArgumentException e){
throw new JsonSyntaxException("Incorrect spell predicate value", e);
}
}
return new SpellPredicate(spell, tiers, elements);
}else{
return ANY;
}
}
public static SpellPredicate[] deserializeArray(@Nullable JsonElement element){
if(element != null && !element.isJsonNull()){
JsonArray jsonarray = JsonUtils.getJsonArray(element, "spells");
SpellPredicate[] predicates = new SpellPredicate[jsonarray.size()];
for(int i = 0; i < predicates.length; ++i){
predicates[i] = deserialize(jsonarray.get(i));
}
return predicates;
}else{
return new SpellPredicate[0];
}
}
}
@@ -0,0 +1,136 @@
package electroblob.wizardry.advancement;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonObject;
import electroblob.wizardry.worldgen.WorldGenSurfaceStructure;
import net.minecraft.advancements.ICriterionTrigger;
import net.minecraft.advancements.PlayerAdvancements;
import net.minecraft.advancements.critereon.AbstractCriterionInstance;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.util.JsonUtils;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.WorldServer;
import java.util.List;
import java.util.Map;
import java.util.Set;
/** Copied from PositionTrigger and modified to work with wizardry's structures. The majority of any
* ICriterionTrigger class is just boilerplate, and this is no exception. */
public class StructureTrigger implements ICriterionTrigger<StructureTrigger.Instance> {
private final ResourceLocation id;
private final Map<PlayerAdvancements, StructureTrigger.Listeners> listeners = Maps.newHashMap();
public StructureTrigger(ResourceLocation id){
this.id = id;
}
public ResourceLocation getId(){
return this.id;
}
public void addListener(PlayerAdvancements advancements, Listener<StructureTrigger.Instance> listener){
StructureTrigger.Listeners listeners = this.listeners.get(advancements);
if(listeners == null){
listeners = new StructureTrigger.Listeners(advancements);
this.listeners.put(advancements, listeners);
}
listeners.add(listener);
}
public void removeListener(PlayerAdvancements advancements, Listener<StructureTrigger.Instance> listener){
StructureTrigger.Listeners listeners = this.listeners.get(advancements);
if(listeners != null){
listeners.remove(listener);
if(listeners.isEmpty()){
this.listeners.remove(advancements);
}
}
}
public void removeAllListeners(PlayerAdvancements advancements){
this.listeners.remove(advancements);
}
public StructureTrigger.Instance deserializeInstance(JsonObject json, JsonDeserializationContext context){
return new StructureTrigger.Instance(this.id, JsonUtils.getString(json, "structure_type"));
}
public void trigger(EntityPlayerMP player){
StructureTrigger.Listeners listeners = this.listeners.get(player.getAdvancements());
if(listeners != null){
listeners.trigger(player.getServerWorld(), player.posX, player.posY, player.posZ);
}
}
public static class Instance extends AbstractCriterionInstance {
private final WorldGenSurfaceStructure structureType;
public Instance(ResourceLocation criterionIn, String name){
super(criterionIn);
this.structureType = WorldGenSurfaceStructure.byName(name);
}
public boolean test(WorldServer world, double x, double y, double z){
return structureType.isInsideStructure(world, x, y, z);
}
}
static class Listeners {
private final PlayerAdvancements playerAdvancements;
private final Set<Listener<StructureTrigger.Instance>> listeners = Sets.newHashSet();
public Listeners(PlayerAdvancements advancements){
this.playerAdvancements = advancements;
}
public boolean isEmpty(){
return this.listeners.isEmpty();
}
public void add(Listener<StructureTrigger.Instance> listener){
this.listeners.add(listener);
}
public void remove(Listener<StructureTrigger.Instance> listener){
this.listeners.remove(listener);
}
public void trigger(WorldServer world, double x, double y, double z){
List<Listener<StructureTrigger.Instance>> list = null;
for(Listener<StructureTrigger.Instance> listener : this.listeners){
if(listener.getCriterionInstance().test(world, x, y, z)){
if(list == null){
list = Lists.newArrayList();
}
list.add(listener);
}
}
if(list != null){
for(Listener<StructureTrigger.Instance> listener : list){
listener.grantCriterion(this.playerAdvancements);
}
}
}
}
}
@@ -0,0 +1,96 @@
package electroblob.wizardry.api;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.util.SpellProperties;
import net.minecraft.util.text.Style;
import net.minecraftforge.common.util.EnumHelper;
/**
* This class contains methods similar to those in {@link EnumHelper} specific to wizardry's enum types.
*
* @author Electroblob
* @since Wizardry 4.2
*/
public final class WizardryEnumHelper {
// Make sure these are updated if the relevant constructors are updated!
// Can't we do some kind of reflection to access them and generate these arrays?
private static final Class[] TIER_ARGUMENTS = new Class[]{Integer.class, Integer.class, Integer.class, Style.class, String.class};
private static final Class[] ELEMENT_ARGUMENTS = new Class[]{Style.class, String.class, String.class};
private static final Class[] SPELL_TYPE_ARGUMENTS = new Class[]{String.class};
private static final Class[] SPELL_CONTEXT_ARGUMENTS = new Class[]{String.class};
/**
* Wrapper for the generic method {@link EnumHelper#addEnum(Class, String, Class[], Object...)} which is
* specifically for adding new tiers. Use this method in preference to the generic one in case the constructor
* parameters change for {@code Tier}.
* <p></p>
* As of version 4.2, wizardry now has partial support for externally-added tiers; you'll need to do some of the
* legwork yourself though.
*
* @param codeName The name of the enum constant in the code. This will be returned if you call toString() on the
* resulting enum constant; other than that it doesn't really make much difference.
* @param maxCharge The maximum charge for wands of this tier.
* @param upgradeLimit The maximum total number of special upgrades that can be applied to wands of this tier.
* @param weight The weight of this tier in the standard weighting.
* @param colour The colour of text associated with this tier, as a style object.
* @param name The unlocalised name of this tier, as used in translation keys.
* @return The resulting {@code Tier} enum constant.
*/
public static Tier addTier(String codeName, int maxCharge, int upgradeLimit, int weight, Style colour, String name){
return EnumHelper.addEnum(Tier.class, codeName, TIER_ARGUMENTS, maxCharge, upgradeLimit, weight, colour, name);
}
/**
* Wrapper for the generic method {@link EnumHelper#addEnum(Class, String, Class[], Object...)} which is
* specifically for adding new elements. Use this method in preference to the generic one in case the constructor
* parameters change for {@code Element}.
* <p></p>
* As of version 4.2, wizardry now has full support for externally-added elements.
*
* @param codeName The name of the enum constant in the code. This will be returned if you call toString() on the
* resulting enum constant; other than that it doesn't really make much difference.
* @param colour The colour of text associated with this element, as a style object.
* @param name The unlocalised name of this element, as used in translation keys.
* @param modID The mod ID of the mod that added this element, for icon rendering purposes.
* @return The resulting {@code Element} enum constant.
*/
// This is the only one that needs the mod ID argument because elements are the only ones that have icons
// For some reason Minecraft doesn't seem to care about the resource domain for lang files, it just pools them
public static Element addElement(String codeName, Style colour, String name, String modID){
return EnumHelper.addEnum(Element.class, codeName, ELEMENT_ARGUMENTS, colour, name, modID);
}
/**
* Wrapper for the generic method {@link EnumHelper#addEnum(Class, String, Class[], Object...)} which is
* specifically for adding new spell types. Use this method in preference to the generic one in case the constructor
* parameters change for {@code SpellType}.
* <p></p>
* As of version 4.2, wizardry now has full support for externally-added spell types.
*
* @param codeName The name of the enum constant in the code. This will be returned if you call toString() on the
* resulting enum constant; other than that it doesn't really make much difference.
* @param name The unlocalised name of this spell type, as used in translation keys.
* @return The resulting {@code SpellType} enum constant.
*/
public static SpellType addSpellType(String codeName, String name){
return EnumHelper.addEnum(SpellType.class, codeName, SPELL_TYPE_ARGUMENTS, name);
}
/**
* Wrapper for the generic method {@link EnumHelper#addEnum(Class, String, Class[], Object...)} which is
* specifically for adding new spell contexts (for use in spell property JSON files). Use this method in preference
* to the generic one in case the constructor parameters change for {@code Context}.
*
* @param codeName The name of the enum constant in the code. This will be returned if you call toString() on the
* resulting enum constant; other than that it doesn't really make much difference.
* @param name The identifier for this spell context, as used in the JSON file.
* @return The resulting {@code Context} enum constant.
*/
public static SpellProperties.Context addSpellContext(String codeName, String name){
return EnumHelper.addEnum(SpellProperties.Context.class, codeName, SPELL_CONTEXT_ARGUMENTS, name);
}
}
@@ -5,6 +5,7 @@ import electroblob.wizardry.WizardryGuiHandler;
import electroblob.wizardry.tileentity.TileEntityArcaneWorkbench; import electroblob.wizardry.tileentity.TileEntityArcaneWorkbench;
import net.minecraft.block.BlockContainer; import net.minecraft.block.BlockContainer;
import net.minecraft.block.material.Material; import net.minecraft.block.material.Material;
import net.minecraft.block.state.BlockFaceShape;
import net.minecraft.block.state.IBlockState; import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.InventoryHelper; import net.minecraft.inventory.InventoryHelper;
@@ -51,6 +52,16 @@ public class BlockArcaneWorkbench extends BlockContainer {
return false; 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 @Override
public boolean onBlockActivated(World world, BlockPos pos, IBlockState block, EntityPlayer player, EnumHand hand, public boolean onBlockActivated(World world, BlockPos pos, IBlockState block, EntityPlayer player, EnumHand hand,
EnumFacing side, float hitX, float hitY, float hitZ){ EnumFacing side, float hitX, float hitY, float hitZ){
@@ -0,0 +1,76 @@
package electroblob.wizardry.block;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.registry.WizardryTabs;
import net.minecraft.block.Block;
import net.minecraft.block.material.MapColor;
import net.minecraft.block.material.Material;
import net.minecraft.block.properties.PropertyEnum;
import net.minecraft.block.state.BlockStateContainer;
import net.minecraft.block.state.IBlockState;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.item.ItemStack;
import net.minecraft.util.NonNullList;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import java.util.EnumMap;
public class BlockCrystal extends Block {
public static final PropertyEnum<Element> ELEMENT = PropertyEnum.create("element", Element.class);
private static final EnumMap<Element, MapColor> map_colours = new EnumMap<>(Element.class);
static {
map_colours.put(Element.MAGIC, MapColor.PINK);
map_colours.put(Element.FIRE, MapColor.ORANGE_STAINED_HARDENED_CLAY);
map_colours.put(Element.ICE, MapColor.LIGHT_BLUE);
map_colours.put(Element.LIGHTNING, MapColor.CYAN);
map_colours.put(Element.NECROMANCY, MapColor.PURPLE);
map_colours.put(Element.EARTH, MapColor.GREEN);
map_colours.put(Element.SORCERY, MapColor.LIME);
map_colours.put(Element.HEALING, MapColor.YELLOW);
}
public BlockCrystal(Material material){
super(material);
this.setDefaultState(this.blockState.getBaseState().withProperty(ELEMENT, Element.MAGIC));
this.setCreativeTab(WizardryTabs.WIZARDRY);
this.setHarvestLevel("pickaxe", 2);
}
@Override
public int damageDropped(IBlockState state){
return (state.getValue(ELEMENT)).ordinal();
}
@Override
public MapColor getMapColor(IBlockState state, IBlockAccess world, BlockPos pos){
return map_colours.get(state.getProperties().get(ELEMENT));
}
@Override
public void getSubBlocks(CreativeTabs tab, NonNullList<ItemStack> items){
if(this.getCreativeTab() == tab){
for(Element element : Element.values()){
items.add(new ItemStack(this, 1, element.ordinal()));
}
}
}
@Override
public IBlockState getStateFromMeta(int metadata){
return this.getDefaultState().withProperty(ELEMENT, Element.values()[metadata]);
}
@Override
public int getMetaFromState(IBlockState state){
return (state.getValue(ELEMENT)).ordinal();
}
@Override
protected BlockStateContainer createBlockState(){
return new BlockStateContainer(this, ELEMENT);
}
}
@@ -1,7 +1,5 @@
package electroblob.wizardry.block; package electroblob.wizardry.block;
import java.util.Random;
import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.util.ParticleBuilder; import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.ParticleBuilder.Type;
@@ -19,6 +17,8 @@ import net.minecraftforge.event.entity.player.BonemealEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import java.util.Random;
// Extending BlockBush allows me to remove nearly everything from this class. // Extending BlockBush allows me to remove nearly everything from this class.
@Mod.EventBusSubscriber @Mod.EventBusSubscriber
public class BlockCrystalFlower extends BlockBush { public class BlockCrystalFlower extends BlockBush {
@@ -1,7 +1,5 @@
package electroblob.wizardry.block; package electroblob.wizardry.block;
import java.util.Random;
import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryItems;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.SoundType; import net.minecraft.block.SoundType;
@@ -13,6 +11,8 @@ import net.minecraft.util.math.MathHelper;
import net.minecraft.world.IBlockAccess; import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World; import net.minecraft.world.World;
import java.util.Random;
public class BlockCrystalOre extends Block { public class BlockCrystalOre extends Block {
public BlockCrystalOre(Material material){ public BlockCrystalOre(Material material){
@@ -0,0 +1,28 @@
package electroblob.wizardry.block;
import net.minecraft.block.BlockFrostedIce;
import net.minecraft.block.state.IBlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import java.util.Random;
/** Like {@link BlockFrostedIce}, but melting does not depend on light level or neighbouring blocks, and it just
* disappears instead of turning to water. */
public class BlockDryFrostedIce extends BlockFrostedIce {
@Override
protected void turnIntoWater(World world, BlockPos pos){
world.destroyBlock(pos, false);
}
@Override
public void updateTick(World worldIn, BlockPos pos, IBlockState state, Random rand){
if(rand.nextInt(3) == 0){
this.slightlyMelt(worldIn, pos, state, rand, true);
}else{
worldIn.scheduleUpdate(pos, this, MathHelper.getInt(rand, 20, 40));
}
}
}
@@ -1,23 +1,31 @@
package electroblob.wizardry.block; package electroblob.wizardry.block;
import electroblob.wizardry.item.ISpellCastingItem;
import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.tileentity.TileEntityMagicLight; import electroblob.wizardry.tileentity.TileEntityMagicLight;
import net.minecraft.block.BlockContainer; import net.minecraft.block.Block;
import net.minecraft.block.ITileEntityProvider;
import net.minecraft.block.material.Material; import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState; import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumBlockRenderType; 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.AxisAlignedBB;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess; import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World; import net.minecraft.world.World;
public class BlockMagicLight extends BlockContainer { public class BlockMagicLight extends Block implements ITileEntityProvider {
private static final AxisAlignedBB AABB = new AxisAlignedBB(0, 0, 0, 0, 0, 0); //private static final AxisAlignedBB AABB = new AxisAlignedBB(0, 0, 0, 0, 0, 0);
public BlockMagicLight(Material par2Material){ public BlockMagicLight(Material material){
super(par2Material); super(material);
this.setLightLevel(1.0f); this.setLightLevel(1.0f);
this.setBlockUnbreakable();
} }
@Override @Override
@@ -28,13 +36,38 @@ public class BlockMagicLight extends BlockContainer {
} }
@Override @Override
public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos){ public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ){
return AABB; // Let the player dispel any lights if they have the lantern charm, not just the permanent ones because that would be annoying!
if(player.getHeldItem(hand).getItem() instanceof ISpellCastingItem && ItemArtefact.isArtefactActive(player, WizardryItems.charm_light)){
world.setBlockToAir(pos);
return true;
}else{
return super.onBlockActivated(world, pos, state, player, hand, facing, hitX, hitY, hitZ);
}
} }
// @Override
// public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos){
// return AABB;
// }
@Override @Override
public boolean isCollidable(){ public boolean isCollidable(){
return false; // This method has nothing to do with entity movement, it's just for raytracing
return true;
}
@Override
public boolean addDestroyEffects(World world, BlockPos pos, net.minecraft.client.particle.ParticleManager manager){
if(world.getBlockState(pos).getBlock() == this) return true; // No break particles!
else return super.addDestroyEffects(world, pos, manager);
}
@Override
public boolean hasTileEntity(IBlockState state){
return true;
} }
@Override @Override
@@ -0,0 +1,116 @@
package electroblob.wizardry.block;
import net.minecraft.block.Block;
import net.minecraft.block.BlockObsidian;
import net.minecraft.block.properties.PropertyInteger;
import net.minecraft.block.state.BlockStateContainer;
import net.minecraft.block.state.IBlockState;
import net.minecraft.init.Blocks;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import java.util.Random;
/** Like {@link net.minecraft.block.BlockFrostedIce}, but for lava instead of water. */
// This is mostly copied from that class, with a few changes
public class BlockObsidianCrust extends BlockObsidian {
public static final PropertyInteger AGE = PropertyInteger.create("age", 0, 3);
public BlockObsidianCrust(){
this.setDefaultState(this.blockState.getBaseState().withProperty(AGE, 0));
}
@Override
public int getMetaFromState(IBlockState state){
return state.getValue(AGE);
}
@Override
public IBlockState getStateFromMeta(int meta){
return this.getDefaultState().withProperty(AGE, MathHelper.clamp(meta, 0, 3));
}
@Override
public void updateTick(World world, BlockPos pos, IBlockState state, Random random){
if((random.nextInt(3) == 0 || this.countNeighbors(world, pos) < 4) && world.getLightFromNeighbors(pos) > 11 - state.getValue(AGE) - state.getLightOpacity()){
this.slightlyMelt(world, pos, state, random, true);
}else{
world.scheduleUpdate(pos, this, MathHelper.getInt(random, 20, 40));
}
}
@Override
public void neighborChanged(IBlockState state, World world, BlockPos pos, Block block, BlockPos fromPos){
if(block == this){
int i = this.countNeighbors(world, pos);
if(i < 2){
this.melt(world, pos);
}
}
}
private int countNeighbors(World world, BlockPos pos){
int i = 0;
for(EnumFacing enumfacing : EnumFacing.values()){
if(world.getBlockState(pos.offset(enumfacing)).getBlock() == this){
++i;
if(i >= 4){
return i;
}
}
}
return i;
}
protected void slightlyMelt(World world, BlockPos pos, IBlockState state, Random random, boolean meltNeighbours){
int i = state.getValue(AGE);
if(i < 3){
world.setBlockState(pos, state.withProperty(AGE, i + 1), 2);
world.scheduleUpdate(pos, this, MathHelper.getInt(random, 20, 40));
}else{
this.melt(world, pos);
if(meltNeighbours){
for(EnumFacing enumfacing : EnumFacing.values()){
BlockPos blockpos = pos.offset(enumfacing);
IBlockState iblockstate = world.getBlockState(blockpos);
if(iblockstate.getBlock() == this){
this.slightlyMelt(world, blockpos, iblockstate, random, false);
}
}
}
}
}
protected void melt(World world, BlockPos pos){
world.setBlockState(pos, Blocks.LAVA.getDefaultState());
world.neighborChanged(pos, Blocks.LAVA, pos);
}
@Override
protected BlockStateContainer createBlockState(){
return new BlockStateContainer(this, AGE);
}
@Override
public ItemStack getItem(World world, BlockPos pos, IBlockState state){
return ItemStack.EMPTY;
}
}
@@ -0,0 +1,123 @@
package electroblob.wizardry.block;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.registry.WizardryTabs;
import electroblob.wizardry.tileentity.TileEntityShrineCore;
import net.minecraft.block.Block;
import net.minecraft.block.ITileEntityProvider;
import net.minecraft.block.material.MapColor;
import net.minecraft.block.material.Material;
import net.minecraft.block.properties.PropertyBool;
import net.minecraft.block.properties.PropertyEnum;
import net.minecraft.block.state.BlockStateContainer;
import net.minecraft.block.state.IBlockState;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.entity.Entity;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.BlockRenderLayer;
import net.minecraft.util.NonNullList;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.Explosion;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.EnumMap;
public class BlockPedestal extends Block implements ITileEntityProvider {
public static final PropertyEnum<Element> ELEMENT = PropertyEnum.create("element", Element.class,
Arrays.copyOfRange(Element.values(), 1, Element.values().length)); // Everything except MAGIC
// A 'natural' pedestal is one that was generated as part of a structure, is unbreakable and has a tileentity
public static final PropertyBool NATURAL = PropertyBool.create("natural");
private static final EnumMap<Element, MapColor> map_colours = new EnumMap<>(Element.class);
static {
map_colours.put(Element.FIRE, MapColor.RED_STAINED_HARDENED_CLAY);
map_colours.put(Element.ICE, MapColor.LIGHT_BLUE_STAINED_HARDENED_CLAY);
map_colours.put(Element.LIGHTNING, MapColor.CYAN_STAINED_HARDENED_CLAY);
map_colours.put(Element.NECROMANCY, MapColor.PURPLE_STAINED_HARDENED_CLAY);
map_colours.put(Element.EARTH, MapColor.BROWN_STAINED_HARDENED_CLAY);
map_colours.put(Element.SORCERY, MapColor.GRAY);
map_colours.put(Element.HEALING, MapColor.YELLOW_STAINED_HARDENED_CLAY);
}
public BlockPedestal(Material material){
super(material);
this.setDefaultState(this.blockState.getBaseState().withProperty(ELEMENT, Element.FIRE).withProperty(NATURAL, false));
this.setCreativeTab(WizardryTabs.WIZARDRY);
this.setHardness(1.5F);
this.setResistance(10.0F);
}
@Override
public int damageDropped(IBlockState state){
return state.getValue(ELEMENT).ordinal(); // Ignore the NATURAL state here, it's unobtainable
}
@Override
public MapColor getMapColor(IBlockState state, IBlockAccess world, BlockPos pos){
return map_colours.get(state.getProperties().get(ELEMENT));
}
@Override
public void getSubBlocks(CreativeTabs tab, NonNullList<ItemStack> items){
// Ignore the NATURAL state here, it's unobtainable
if(this.getCreativeTab() == tab){
for(Element element : Arrays.copyOfRange(Element.values(), 1, Element.values().length)){
items.add(new ItemStack(this, 1, element.ordinal()));
}
}
}
@Override
public BlockRenderLayer getRenderLayer(){
return BlockRenderLayer.CUTOUT; // Required to shade parts of the block faces differently to others
}
@Override
public float getBlockHardness(IBlockState state, World world, BlockPos pos){
return state.getValue(NATURAL) ? -1 : super.getBlockHardness(state, world, pos);
}
@Override
public float getExplosionResistance(World world, BlockPos pos, @Nullable Entity exploder, Explosion explosion){
return world.getBlockState(pos).getValue(NATURAL) ? 6000000.0F : super.getExplosionResistance(world, pos, exploder, explosion);
}
@Override
public boolean hasTileEntity(IBlockState state){
return state.getValue(NATURAL); // Only naturally-generated pedestals have a (shrine core) tile entity
}
@Nullable
@Override
public TileEntity createNewTileEntity(World world, int meta){
return new TileEntityShrineCore();
}
@Override
public IBlockState getStateFromMeta(int metadata){
boolean natural = false;
if(metadata > ELEMENT.getAllowedValues().size()){
natural = true;
metadata -= ELEMENT.getAllowedValues().size();
}
return this.getDefaultState().withProperty(ELEMENT, Element.values()[metadata]).withProperty(NATURAL, natural);
}
@Override
public int getMetaFromState(IBlockState state){
return state.getValue(ELEMENT).ordinal() + (state.getValue(NATURAL) ? ELEMENT.getAllowedValues().size() : 0);
}
@Override
protected BlockStateContainer createBlockState(){
return new BlockStateContainer(this, ELEMENT, NATURAL);
}
}
@@ -0,0 +1,85 @@
package electroblob.wizardry.block;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.registry.WizardryTabs;
import net.minecraft.block.Block;
import net.minecraft.block.material.MapColor;
import net.minecraft.block.material.Material;
import net.minecraft.block.properties.PropertyEnum;
import net.minecraft.block.state.BlockStateContainer;
import net.minecraft.block.state.IBlockState;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.item.ItemStack;
import net.minecraft.util.BlockRenderLayer;
import net.minecraft.util.NonNullList;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import java.util.Arrays;
import java.util.EnumMap;
public class BlockRunestone extends Block {
public static final PropertyEnum<Element> ELEMENT = PropertyEnum.create("element", Element.class,
Arrays.copyOfRange(Element.values(), 1, Element.values().length)); // Everything except MAGIC
private static final EnumMap<Element, MapColor> map_colours = new EnumMap<>(Element.class);
static {
map_colours.put(Element.FIRE, MapColor.RED_STAINED_HARDENED_CLAY);
map_colours.put(Element.ICE, MapColor.LIGHT_BLUE_STAINED_HARDENED_CLAY);
map_colours.put(Element.LIGHTNING, MapColor.CYAN_STAINED_HARDENED_CLAY);
map_colours.put(Element.NECROMANCY, MapColor.PURPLE_STAINED_HARDENED_CLAY);
map_colours.put(Element.EARTH, MapColor.BROWN_STAINED_HARDENED_CLAY);
map_colours.put(Element.SORCERY, MapColor.GRAY);
map_colours.put(Element.HEALING, MapColor.YELLOW_STAINED_HARDENED_CLAY);
}
public BlockRunestone(Material material){
super(material);
this.setDefaultState(this.blockState.getBaseState().withProperty(ELEMENT, Element.FIRE));
this.setCreativeTab(WizardryTabs.WIZARDRY);
this.setHardness(1.5F);
this.setResistance(10.0F);
}
@Override
public int damageDropped(IBlockState state){
return state.getValue(ELEMENT).ordinal();
}
@Override
public MapColor getMapColor(IBlockState state, IBlockAccess world, BlockPos pos){
return map_colours.get(state.getProperties().get(ELEMENT));
}
@Override
public void getSubBlocks(CreativeTabs tab, NonNullList<ItemStack> items){
if(this.getCreativeTab() == tab){
for(Element element : Arrays.copyOfRange(Element.values(), 1, Element.values().length)){
items.add(new ItemStack(this, 1, element.ordinal()));
}
}
}
@Override
public BlockRenderLayer getRenderLayer(){
return BlockRenderLayer.CUTOUT; // Required to shade parts of the block faces differently to others
}
@Override
public IBlockState getStateFromMeta(int metadata){
return this.getDefaultState().withProperty(ELEMENT, Element.values()[metadata]);
}
@Override
public int getMetaFromState(IBlockState state){
return state.getValue(ELEMENT).ordinal();
}
@Override
protected BlockStateContainer createBlockState(){
return new BlockStateContainer(this, ELEMENT);
}
}
@@ -1,13 +1,13 @@
package electroblob.wizardry.block; package electroblob.wizardry.block;
import java.util.Random; import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.tileentity.TileEntityPlayerSave; import electroblob.wizardry.tileentity.TileEntityPlayerSave;
import electroblob.wizardry.util.AllyDesignationSystem;
import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType; import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockContainer; import net.minecraft.block.ITileEntityProvider;
import net.minecraft.block.SoundType; import net.minecraft.block.SoundType;
import net.minecraft.block.material.Material; import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState; import net.minecraft.block.state.IBlockState;
@@ -25,8 +25,9 @@ import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess; import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World; import net.minecraft.world.World;
// TODO: Apparently you shouldn't extend BlockContainer. I feel like BlockArcaneWorkbench should, but what about the rest? import java.util.Random;
public class BlockSnare extends BlockContainer {
public class BlockSnare extends Block implements ITileEntityProvider {
private static final AxisAlignedBB AABB = new AxisAlignedBB(0.0f, 0.0f, 0.0f, 1.0f, 0.0625f, 1.0f); private static final AxisAlignedBB AABB = new AxisAlignedBB(0.0f, 0.0f, 0.0f, 1.0f, 0.0625f, 1.0f);
@@ -58,10 +59,14 @@ public class BlockSnare extends BlockContainer {
TileEntityPlayerSave tileentity = (TileEntityPlayerSave)world.getTileEntity(pos); TileEntityPlayerSave tileentity = (TileEntityPlayerSave)world.getTileEntity(pos);
if(WizardryUtilities.isValidTarget(tileentity.getCaster(), entity)){ if(AllyDesignationSystem.isValidTarget(tileentity.getCaster(), entity)){
((EntityLivingBase)entity).attackEntityFrom(
MagicDamage.causeDirectMagicDamage(tileentity.getCaster(), DamageType.MAGIC), 6); entity.attackEntityFrom(MagicDamage.causeDirectMagicDamage(tileentity.getCaster(), DamageType.MAGIC),
((EntityLivingBase)entity).addPotionEffect(new PotionEffect(MobEffects.SLOWNESS, 100, 2)); Spells.snare.getProperty(Spell.DAMAGE).floatValue());
((EntityLivingBase)entity).addPotionEffect(new PotionEffect(MobEffects.SLOWNESS,
Spells.snare.getProperty(Spell.EFFECT_DURATION).intValue(),
Spells.snare.getProperty(Spell.EFFECT_STRENGTH).intValue()));
world.destroyBlock(pos, false); world.destroyBlock(pos, false);
} }
@@ -1,13 +1,11 @@
package electroblob.wizardry.block; package electroblob.wizardry.block;
import java.util.Random;
import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.tileentity.TileEntityTimer; import electroblob.wizardry.tileentity.TileEntityTimer;
import electroblob.wizardry.util.ParticleBuilder; import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.ParticleBuilder.Type;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockContainer; import net.minecraft.block.ITileEntityProvider;
import net.minecraft.block.SoundType; import net.minecraft.block.SoundType;
import net.minecraft.block.material.Material; import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState; import net.minecraft.block.state.IBlockState;
@@ -24,10 +22,10 @@ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly; import net.minecraftforge.fml.relauncher.SideOnly;
// For future reference - extend BlockContainer whenever possible because it has methods for removing tile entities on import java.util.Random;
// block break.
@Mod.EventBusSubscriber @Mod.EventBusSubscriber
public class BlockSpectral extends BlockContainer { public class BlockSpectral extends Block implements ITileEntityProvider {
public BlockSpectral(Material material){ public BlockSpectral(Material material){
super(material); super(material);
@@ -76,6 +74,11 @@ public class BlockSpectral extends BlockContainer {
return 15; return 15;
} }
@Override
public boolean hasTileEntity(IBlockState state){
return true;
}
@Override @Override
public TileEntity createNewTileEntity(World world, int metadata){ public TileEntity createNewTileEntity(World world, int metadata){
return new TileEntityTimer(1200); return new TileEntityTimer(1200);
@@ -1,11 +1,9 @@
package electroblob.wizardry.block; package electroblob.wizardry.block;
import java.util.Random;
import electroblob.wizardry.tileentity.TileEntityStatue; import electroblob.wizardry.tileentity.TileEntityStatue;
import electroblob.wizardry.util.WizardryUtilities; import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockContainer; import net.minecraft.block.ITileEntityProvider;
import net.minecraft.block.SoundType; import net.minecraft.block.SoundType;
import net.minecraft.block.material.Material; import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState; import net.minecraft.block.state.IBlockState;
@@ -21,12 +19,16 @@ import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly; import net.minecraftforge.fml.relauncher.SideOnly;
public class BlockStatue extends BlockContainer { import java.util.Random;
public class BlockStatue extends Block implements ITileEntityProvider {
private boolean isIce; private boolean isIce;
/** The NBT tag name for storing the petrified flag (used for rendering) in the target's tag compound. */ /** The NBT tag name for storing the petrified flag (used for rendering) in the target's tag compound. */
public static final String NBT_KEY = "petrified"; public static final String PETRIFIED_NBT_KEY = "petrified";
/** The NBT tag name for storing the frozen flag (used for rendering) in the target's tag compound. */
public static final String FROZEN_NBT_KEY = "frozen";
public BlockStatue(Material material){ public BlockStatue(Material material){
super(material); super(material);
@@ -110,6 +112,11 @@ public class BlockStatue extends BlockContainer {
return this.isIce ? EnumBlockRenderType.MODEL : EnumBlockRenderType.ENTITYBLOCK_ANIMATED; return this.isIce ? EnumBlockRenderType.MODEL : EnumBlockRenderType.ENTITYBLOCK_ANIMATED;
} }
@Override
public boolean hasTileEntity(IBlockState state){
return true;
}
@Override @Override
public TileEntity createNewTileEntity(World world, int metadata){ public TileEntity createNewTileEntity(World world, int metadata){
return new TileEntityStatue(this.isIce); return new TileEntityStatue(this.isIce);
@@ -150,7 +157,7 @@ public class BlockStatue extends BlockContainer {
// This is only when position == 1 because world.destroyBlock calls this function for the other blocks. // This is only when position == 1 because world.destroyBlock calls this function for the other blocks.
if(tileentity != null && tileentity.position == 1 && tileentity.creature != null){ if(tileentity != null && tileentity.position == 1 && tileentity.creature != null){
tileentity.creature.getEntityData().removeTag(BlockStatue.NBT_KEY); tileentity.creature.getEntityData().removeTag(BlockStatue.PETRIFIED_NBT_KEY);
tileentity.creature.isDead = false; tileentity.creature.isDead = false;
world.spawnEntity(tileentity.creature); world.spawnEntity(tileentity.creature);
} }
@@ -198,7 +205,7 @@ public class BlockStatue extends BlockContainer {
((TileEntityStatue)world.getTileEntity(pos)).setLifetime(duration); ((TileEntityStatue)world.getTileEntity(pos)).setLifetime(duration);
} }
if(!this.isIce) entity.getEntityData().setBoolean(NBT_KEY, true); entity.getEntityData().setBoolean(this.isIce ? FROZEN_NBT_KEY : PETRIFIED_NBT_KEY, true);
entity.setDead(); entity.setDead();
return true; return true;
} }
@@ -216,8 +223,8 @@ public class BlockStatue extends BlockContainer {
if(world.getTileEntity(pos.up()) instanceof TileEntityStatue){ if(world.getTileEntity(pos.up()) instanceof TileEntityStatue){
((TileEntityStatue)world.getTileEntity(pos.up())).setCreatureAndPart(entity, 2, 2); ((TileEntityStatue)world.getTileEntity(pos.up())).setCreatureAndPart(entity, 2, 2);
} }
if(!this.isIce) entity.getEntityData().setBoolean(NBT_KEY, true); entity.getEntityData().setBoolean(this.isIce ? FROZEN_NBT_KEY : PETRIFIED_NBT_KEY, true);
entity.setDead(); entity.setDead();
return true; return true;
} }
@@ -241,8 +248,8 @@ public class BlockStatue extends BlockContainer {
if(world.getTileEntity(pos.up(2)) instanceof TileEntityStatue){ 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(entity, 3, 3);
} }
if(!this.isIce) entity.getEntityData().setBoolean(NBT_KEY, true); entity.getEntityData().setBoolean(this.isIce ? FROZEN_NBT_KEY : PETRIFIED_NBT_KEY, true);
entity.setDead(); entity.setDead();
return true; return true;
} }
@@ -0,0 +1,181 @@
package electroblob.wizardry.block;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.tileentity.TileEntityPlayerSaveTimed;
import electroblob.wizardry.util.AllyDesignationSystem;
import electroblob.wizardry.util.MagicDamage;
import net.minecraft.block.*;
import net.minecraft.block.BlockDoublePlant.EnumBlockHalf;
import net.minecraft.block.properties.PropertyEnum;
import net.minecraft.block.properties.PropertyInteger;
import net.minecraft.block.state.BlockStateContainer;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Items;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.DamageSource;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import java.util.Random;
@Mod.EventBusSubscriber
public class BlockThorns extends BlockBush implements ITileEntityProvider {
public static final int GROWTH_STAGES = 8;
public static final PropertyInteger AGE = PropertyInteger.create("age", 0, GROWTH_STAGES-1);
public static final PropertyEnum<EnumBlockHalf> HALF = PropertyEnum.create("half", EnumBlockHalf.class);
public BlockThorns(){
this.setDefaultState(this.blockState.getBaseState().withProperty(HALF, EnumBlockHalf.LOWER).withProperty(AGE, 7));
this.setHardness(4);
this.setSoundType(SoundType.PLANT);
this.setCreativeTab(null);
}
@Override
public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos){
return FULL_BLOCK_AABB;
}
@Override
public IBlockState getStateFromMeta(int meta){
return this.getDefaultState().withProperty(HALF, EnumBlockHalf.values()[meta / GROWTH_STAGES]).withProperty(AGE, meta % GROWTH_STAGES);
}
@Override
public int getMetaFromState(IBlockState state){
return state.getValue(HALF).ordinal() * GROWTH_STAGES + state.getValue(AGE);
}
// @Override
// public void updateTick(World world, BlockPos pos, IBlockState state, Random rand){
//
// super.updateTick(world, pos, state, rand);
//
// // Update the state, including on the client, but don't do a block update since it's only visual
// if(state.getValue(AGE) < GROWTH_STAGES-1) world.setBlockState(pos, state.withProperty(AGE, state.getValue(AGE) + 1), 2);
// }
@Override
protected BlockStateContainer createBlockState(){
return new BlockStateContainer(this, HALF, AGE);
}
public void placeAt(World world, BlockPos lowerPos, int flags){
world.setBlockState(lowerPos, this.getDefaultState().withProperty(HALF, EnumBlockHalf.LOWER).withProperty(AGE, 0), flags);
world.setBlockState(lowerPos.up(), this.getDefaultState().withProperty(HALF, EnumBlockHalf.UPPER).withProperty(AGE, 0), flags);
}
@Override
public void onBlockPlacedBy(World world, BlockPos pos, IBlockState state, EntityLivingBase placer, ItemStack stack){
world.setBlockState(pos.up(), this.getDefaultState().withProperty(HALF, EnumBlockHalf.UPPER), 2);
}
@Override
public void breakBlock(World world, BlockPos pos, IBlockState state){
super.breakBlock(world, pos, state);
if(state.getValue(HALF) == EnumBlockHalf.LOWER){
if(world.getBlockState(pos.up()).getBlock() == this){
world.destroyBlock(pos.up(), false);
}
}else{
if(world.getBlockState(pos.down()).getBlock() == this){
world.destroyBlock(pos.down(), false);
}
}
}
public boolean canBlockStay(World worldIn, BlockPos pos, IBlockState state){
if(state.getValue(HALF) == BlockDoublePlant.EnumBlockHalf.UPPER){
return worldIn.getBlockState(pos.down()).getBlock() == this;
}else{
IBlockState iblockstate = worldIn.getBlockState(pos.up());
return iblockstate.getBlock() == this && this.canSustainBush(worldIn.getBlockState(pos.down()));
}
}
// @Override
// public IBlockState getActualState(IBlockState state, IBlockAccess world, BlockPos pos){
// // Copied from BlockFlowerPot on authority of the Forge docs, which says this check is necessary
// SoundLoopSpellDispenser tileentity = world instanceof ChunkCache ? ((ChunkCache)world).getTileEntity(pos, Chunk.EnumCreateEntityType.CHECK) : world.getTileEntity(pos);
//
// if(tileentity instanceof TileEntityPlayerSaveTimed){
// state = state.withProperty(AGE, Math.min(7, ((TileEntityPlayerSaveTimed)tileentity).timer/2));
// }else{
// state = state.withProperty(AGE, 7);
// }
//
// return state;
// }
@Override
public void onEntityCollision(World world, BlockPos pos, IBlockState state, Entity entity){
if(!world.isRemote){
if(applyThornDamage(world, pos, entity)){
entity.setInWeb();
}
}
}
private static boolean applyThornDamage(World world, BlockPos pos, Entity target){
DamageSource source = DamageSource.CACTUS;
TileEntity tileentity = world.getTileEntity(pos);
if(tileentity instanceof TileEntityPlayerSaveTimed){
if(AllyDesignationSystem.isValidTarget(((TileEntityPlayerSaveTimed)tileentity).getCaster(), target)){
source = MagicDamage.causeDirectMagicDamage(((TileEntityPlayerSaveTimed)tileentity).getCaster(),
MagicDamage.DamageType.MAGIC);
}else{
return false; // Don't attack or slow allies of the caster
}
}
if(world.getTotalWorldTime() % 20 == 0) target.attackEntityFrom(source, Spells.forest_of_thorns.getProperty(Spell.DAMAGE).floatValue());
return true;
}
@Override
public Block.EnumOffsetType getOffsetType(){
return Block.EnumOffsetType.XZ;
}
@Override
public TileEntity createNewTileEntity(World world, int metadata){
return new TileEntityPlayerSaveTimed(600);
}
@Override
public boolean hasTileEntity(IBlockState state){
return true;
}
@Override public boolean isReplaceable(IBlockAccess world, BlockPos pos){ return false; }
@Override protected boolean canSustainBush(IBlockState state){ return state.isNormalCube(); }
@Override public Item getItemDropped(IBlockState state, Random rand, int fortune){ return Items.AIR; }
@Override public boolean canSilkHarvest(World world, BlockPos pos, IBlockState state, EntityPlayer player){ return false; }
@SubscribeEvent
public static void onLeftClickBlockEvent(PlayerInteractEvent.LeftClickBlock event){
if(!event.getWorld().isRemote && event.getWorld().getTotalWorldTime() % 20 == 0
&& event.getWorld().getBlockState(event.getPos()).getBlock() == WizardryBlocks.thorns){
applyThornDamage(event.getWorld(), event.getPos(), event.getEntity());
}
}
}
@@ -1,12 +1,16 @@
package electroblob.wizardry.block; package electroblob.wizardry.block;
import java.util.Random;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry; import electroblob.wizardry.Wizardry;
import electroblob.wizardry.item.ItemWand; import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.item.ISpellCastingItem;
import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.spell.Transportation;
import electroblob.wizardry.util.Location;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.material.Material; import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState; import net.minecraft.block.state.IBlockState;
@@ -20,6 +24,10 @@ import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.IBlockAccess; import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World; import net.minecraft.world.World;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class BlockTransportationStone extends Block { public class BlockTransportationStone extends Block {
private static final AxisAlignedBB AABB = new AxisAlignedBB(0.0625f * 5, 0, 0.0625f * 5, 0.0625f * 11, 0.0625f * 6, private static final AxisAlignedBB AABB = new AxisAlignedBB(0.0625f * 5, 0, 0.0625f * 5, 0.0625f * 11, 0.0625f * 6,
@@ -103,7 +111,7 @@ public class BlockTransportationStone extends Block {
ItemStack stack = player.getHeldItem(hand); ItemStack stack = player.getHeldItem(hand);
if(stack.getItem() instanceof ItemWand){ if(stack.getItem() instanceof ISpellCastingItem){
if(WizardData.get(player) != null){ if(WizardData.get(player) != null){
WizardData data = WizardData.get(player); WizardData data = WizardData.get(player);
@@ -112,17 +120,59 @@ public class BlockTransportationStone extends Block {
for(int z = -1; z <= 1; z++){ for(int z = -1; z <= 1; z++){
BlockPos pos1 = pos.add(x, 0, z); BlockPos pos1 = pos.add(x, 0, z);
if(testForCircle(world, pos1)){ if(testForCircle(world, pos1)){
data.setStoneCircleLocation(pos1, world.provider.getDimension());
if(!world.isRemote) player.sendMessage( Location here = new Location(pos1, player.dimension);
new TextComponentTranslation("tile." + Wizardry.MODID + ":transportation_stone.confirm",
Spells.transportation.getNameForTranslationFormatted())); List<Location> locations = data.getVariable(Transportation.LOCATIONS_KEY);
if(locations == null) data.setVariable(Transportation.LOCATIONS_KEY, locations = new ArrayList<>(Transportation.MAX_REMEMBERED_LOCATIONS));
if(ItemArtefact.isArtefactActive(player, WizardryItems.charm_transportation)){
if(locations.contains(here)){
locations.remove(here);
if(!world.isRemote) player.sendStatusMessage(new TextComponentTranslation("tile." + Wizardry.MODID + ":transportation_stone.forget", here.pos.getX(), here.pos.getY(), here.pos.getZ(), here.dimension), true);
}else{
locations.add(here);
if(!world.isRemote) player.sendStatusMessage(new TextComponentTranslation("tile." + Wizardry.MODID + ":transportation_stone.remember", here.pos.getX(), here.pos.getY(), here.pos.getZ(), here.dimension), true);
if(locations.size() > Transportation.MAX_REMEMBERED_LOCATIONS){
Location removed = locations.remove(0);
if(!world.isRemote) player.sendStatusMessage(new TextComponentTranslation("tile." + Wizardry.MODID + ":transportation_stone.forget", removed.pos.getX(), removed.pos.getY(), removed.pos.getZ(), removed.dimension), true);
}
}
}else{
if(locations.isEmpty()) locations.add(here);
else{
locations.remove(here); // Prevents duplicates
locations.set(locations.size() - 1, here);
}
if(!world.isRemote) player.sendStatusMessage(new TextComponentTranslation("tile." + Wizardry.MODID + ":transportation_stone.confirm", Spells.transportation.getNameForTranslationFormatted()), true);
}
return true; return true;
} }
} }
} }
if(!world.isRemote) if(!world.isRemote){
player.sendMessage(new TextComponentTranslation("tile." + Wizardry.MODID + ":transportation_stone.invalid")); player.sendStatusMessage(new TextComponentTranslation("tile." + Wizardry.MODID + ":transportation_stone.invalid"), true);
}else{
BlockPos centre = findMostLikelyCircle(world, pos);
// Displays particles in the required shape
for(int x = -1; x <= 1; x++){
for(int z = -1; z <= 1; z++){
if(x == 0 && z == 0) continue;
ParticleBuilder.create(ParticleBuilder.Type.PATH)
.pos(WizardryUtilities.getCentre(centre).add(x, -0.3125, z)).clr(0x86ff65)
.time(200).scale(2).spawn(world);
}
}
}
return true; return true;
} }
} }
@@ -136,12 +186,47 @@ public class BlockTransportationStone extends Block {
for(int x = -1; x <= 1; x++){ for(int x = -1; x <= 1; x++){
for(int z = -1; z <= 1; z++){ for(int z = -1; z <= 1; z++){
if(x == 0 && z == 0) continue;
if(world.getBlockState(pos.add(x, 0, z)).getBlock() != WizardryBlocks.transportation_stone){ if(world.getBlockState(pos.add(x, 0, z)).getBlock() != WizardryBlocks.transportation_stone){
if(x != 0 || z != 0) return false; return false;
} }
} }
} }
return true; return true;
} }
private static BlockPos findMostLikelyCircle(World world, BlockPos pos){
int bestSoFar = 0;
BlockPos result = null;
for(int x = -1; x <= 1; x++){
for(int z = -1; z <= 1; z++){
if(x == 0 && z == 0) continue;
BlockPos pos1 = pos.add(x, 0, z);
int n = getCircleCompleteness(world, pos1);
if(n > bestSoFar){
bestSoFar = n;
result = pos1;
}
}
}
return result;
}
private static int getCircleCompleteness(World world, BlockPos pos){
int n = 0;
for(int x = -1; x <= 1; x++){
for(int z = -1; z <= 1; z++){
if(x == 0 && z == 0) continue;
if(world.getBlockState(pos.add(x, 0, z)).getBlock() == WizardryBlocks.transportation_stone) n++;
}
}
return n;
}
} }
@@ -1,9 +1,8 @@
package electroblob.wizardry.block; package electroblob.wizardry.block;
import java.util.Random;
import electroblob.wizardry.tileentity.TileEntityTimer; import electroblob.wizardry.tileentity.TileEntityTimer;
import net.minecraft.block.BlockContainer; import net.minecraft.block.Block;
import net.minecraft.block.ITileEntityProvider;
import net.minecraft.block.material.Material; import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState; import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
@@ -17,15 +16,17 @@ import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly; import net.minecraftforge.fml.relauncher.SideOnly;
// For future reference - extend BlockContainer whenever possible because it has methods for removing tile entities on block break. import java.util.Random;
public class BlockVanishingCobweb extends BlockContainer {
public class BlockVanishingCobweb extends Block implements ITileEntityProvider {
public BlockVanishingCobweb(Material material){ public BlockVanishingCobweb(Material material){
super(material); super(material);
} }
@SideOnly(Side.CLIENT) @SideOnly(Side.CLIENT)
public BlockRenderLayer getBlockLayer(){ @Override
public BlockRenderLayer getRenderLayer(){
return BlockRenderLayer.CUTOUT; return BlockRenderLayer.CUTOUT;
} }
@@ -49,6 +50,11 @@ public class BlockVanishingCobweb extends BlockContainer {
return false; return false;
} }
@Override
public boolean hasTileEntity(IBlockState state){
return true;
}
@Override @Override
public TileEntity createNewTileEntity(World world, int metadata){ public TileEntity createNewTileEntity(World world, int metadata){
return new TileEntityTimer(400); return new TileEntityTimer(400);
@@ -1,115 +1,54 @@
package electroblob.wizardry.client; package electroblob.wizardry.client;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
import org.lwjgl.input.Keyboard;
import electroblob.wizardry.CommonProxy; import electroblob.wizardry.CommonProxy;
import electroblob.wizardry.SpellGlyphData;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry; import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.audio.MovingSoundEntity;
import electroblob.wizardry.client.audio.SoundLoop;
import electroblob.wizardry.client.audio.SoundLoopSpell;
import electroblob.wizardry.client.gui.GuiSpellDisplay; import electroblob.wizardry.client.gui.GuiSpellDisplay;
import electroblob.wizardry.client.gui.GuiWizardHandbook; import electroblob.wizardry.client.gui.config.NamedBooleanEntry;
import electroblob.wizardry.client.gui.config.SpellHUDSkinChooserEntry;
import electroblob.wizardry.client.gui.handbook.GuiWizardHandbook;
import electroblob.wizardry.client.model.ModelWizardArmour; import electroblob.wizardry.client.model.ModelWizardArmour;
import electroblob.wizardry.client.particle.*; import electroblob.wizardry.client.particle.*;
import electroblob.wizardry.client.particle.ParticleWizardry.IWizardryParticleFactory; import electroblob.wizardry.client.particle.ParticleWizardry.IWizardryParticleFactory;
import electroblob.wizardry.client.renderer.LayerStone; import electroblob.wizardry.client.renderer.*;
import electroblob.wizardry.client.renderer.RenderArcaneWorkbench; import electroblob.wizardry.command.SpellEmitter;
import electroblob.wizardry.client.renderer.RenderBlackHole; import electroblob.wizardry.data.DispenserCastingData;
import electroblob.wizardry.client.renderer.RenderBlank; import electroblob.wizardry.data.SpellEmitterData;
import electroblob.wizardry.client.renderer.RenderBubble; import electroblob.wizardry.data.SpellGlyphData;
import electroblob.wizardry.client.renderer.RenderDecay; import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.client.renderer.RenderDecoy;
import electroblob.wizardry.client.renderer.RenderEvilWizard;
import electroblob.wizardry.client.renderer.RenderFireRing;
import electroblob.wizardry.client.renderer.RenderForceArrow;
import electroblob.wizardry.client.renderer.RenderHammer;
import electroblob.wizardry.client.renderer.RenderIceGiant;
import electroblob.wizardry.client.renderer.RenderIceSpike;
import electroblob.wizardry.client.renderer.RenderLightningDisc;
import electroblob.wizardry.client.renderer.RenderMagicArrow;
import electroblob.wizardry.client.renderer.RenderMagicLight;
import electroblob.wizardry.client.renderer.RenderPhoenix;
import electroblob.wizardry.client.renderer.RenderProjectile;
import electroblob.wizardry.client.renderer.RenderSigil;
import electroblob.wizardry.client.renderer.RenderSpiritHorse;
import electroblob.wizardry.client.renderer.RenderSpiritWolf;
import electroblob.wizardry.client.renderer.RenderStatue;
import electroblob.wizardry.client.renderer.RenderWizard;
import electroblob.wizardry.entity.EntityShield; import electroblob.wizardry.entity.EntityShield;
import electroblob.wizardry.entity.construct.EntityArrowRain; import electroblob.wizardry.entity.construct.*;
import electroblob.wizardry.entity.construct.EntityBlackHole; import electroblob.wizardry.entity.living.*;
import electroblob.wizardry.entity.construct.EntityBlizzard; import electroblob.wizardry.entity.projectile.*;
import electroblob.wizardry.entity.construct.EntityBubble;
import electroblob.wizardry.entity.construct.EntityDecay;
import electroblob.wizardry.entity.construct.EntityEarthquake;
import electroblob.wizardry.entity.construct.EntityFireRing;
import electroblob.wizardry.entity.construct.EntityFireSigil;
import electroblob.wizardry.entity.construct.EntityForcefield;
import electroblob.wizardry.entity.construct.EntityFrostSigil;
import electroblob.wizardry.entity.construct.EntityHailstorm;
import electroblob.wizardry.entity.construct.EntityHammer;
import electroblob.wizardry.entity.construct.EntityHealAura;
import electroblob.wizardry.entity.construct.EntityIceSpike;
import electroblob.wizardry.entity.construct.EntityLightningSigil;
import electroblob.wizardry.entity.construct.EntityTornado;
import electroblob.wizardry.entity.living.EntityDecoy;
import electroblob.wizardry.entity.living.EntityEvilWizard;
import electroblob.wizardry.entity.living.EntityIceGiant;
import electroblob.wizardry.entity.living.EntityIceWraith;
import electroblob.wizardry.entity.living.EntityLightningWraith;
import electroblob.wizardry.entity.living.EntityPhoenix;
import electroblob.wizardry.entity.living.EntityShadowWraith;
import electroblob.wizardry.entity.living.EntitySpiritHorse;
import electroblob.wizardry.entity.living.EntitySpiritWolf;
import electroblob.wizardry.entity.living.EntityStormElemental;
import electroblob.wizardry.entity.living.EntityWizard;
import electroblob.wizardry.entity.living.ISpellCaster;
import electroblob.wizardry.entity.living.ISummonedCreature;
import electroblob.wizardry.entity.projectile.EntityDarknessOrb;
import electroblob.wizardry.entity.projectile.EntityDart;
import electroblob.wizardry.entity.projectile.EntityFirebolt;
import electroblob.wizardry.entity.projectile.EntityFirebomb;
import electroblob.wizardry.entity.projectile.EntityForceArrow;
import electroblob.wizardry.entity.projectile.EntityForceOrb;
import electroblob.wizardry.entity.projectile.EntityIceCharge;
import electroblob.wizardry.entity.projectile.EntityIceLance;
import electroblob.wizardry.entity.projectile.EntityIceShard;
import electroblob.wizardry.entity.projectile.EntityLightningArrow;
import electroblob.wizardry.entity.projectile.EntityLightningDisc;
import electroblob.wizardry.entity.projectile.EntityMagicMissile;
import electroblob.wizardry.entity.projectile.EntityPoisonBomb;
import electroblob.wizardry.entity.projectile.EntitySmokeBomb;
import electroblob.wizardry.entity.projectile.EntitySpark;
import electroblob.wizardry.entity.projectile.EntitySparkBomb;
import electroblob.wizardry.entity.projectile.EntityThunderbolt;
import electroblob.wizardry.event.SpellCastEvent; import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.event.SpellCastEvent.Source; import electroblob.wizardry.event.SpellCastEvent.Source;
import electroblob.wizardry.integration.antiqueatlas.WizardryAntiqueAtlasIntegration;
import electroblob.wizardry.item.ItemScroll; import electroblob.wizardry.item.ItemScroll;
import electroblob.wizardry.item.ItemSpellBook; import electroblob.wizardry.item.ItemSpellBook;
import electroblob.wizardry.item.ItemWand; import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.packet.PacketCastContinuousSpell; import electroblob.wizardry.packet.*;
import electroblob.wizardry.packet.PacketCastSpell; import electroblob.wizardry.potion.PotionSlowTime;
import electroblob.wizardry.packet.PacketNPCCastSpell;
import electroblob.wizardry.packet.PacketPlayerSync.Message;
import electroblob.wizardry.packet.PacketTransportation;
import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.spell.Clairvoyance; import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.spell.None; import electroblob.wizardry.spell.*;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.tileentity.TileEntityArcaneWorkbench; import electroblob.wizardry.tileentity.TileEntityArcaneWorkbench;
import electroblob.wizardry.tileentity.TileEntityMagicLight; import electroblob.wizardry.tileentity.TileEntityMagicLight;
import electroblob.wizardry.tileentity.TileEntityShrineCore;
import electroblob.wizardry.tileentity.TileEntityStatue; import electroblob.wizardry.tileentity.TileEntityStatue;
import electroblob.wizardry.util.ParticleBuilder; import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.WandHelper; import electroblob.wizardry.util.WandHelper;
import net.minecraft.block.state.IBlockState; import net.minecraft.block.state.IBlockState;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiMerchant;
import net.minecraft.client.model.ModelBiped; import net.minecraft.client.model.ModelBiped;
import net.minecraft.client.renderer.entity.RenderBlaze; import net.minecraft.client.renderer.entity.RenderBlaze;
import net.minecraft.client.renderer.entity.RenderHusk;
import net.minecraft.client.renderer.entity.RenderSkeleton;
import net.minecraft.client.resources.I18n; import net.minecraft.client.resources.I18n;
import net.minecraft.client.resources.IReloadableResourceManager; import net.minecraft.client.resources.IReloadableResourceManager;
import net.minecraft.client.resources.IResourceManager; import net.minecraft.client.resources.IResourceManager;
@@ -119,19 +58,33 @@ import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.monster.EntityBlaze; import net.minecraft.entity.monster.EntityBlaze;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents; import net.minecraft.inventory.ContainerMerchant;
import net.minecraft.item.Item; import net.minecraft.item.Item;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvent; import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.text.Style;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.village.MerchantRecipeList;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.config.Property; import net.minecraftforge.common.config.Property;
import net.minecraftforge.fml.client.config.GuiConfigEntries.NumberSliderEntry; import net.minecraftforge.fml.client.config.GuiConfigEntries.NumberSliderEntry;
import net.minecraftforge.fml.client.registry.ClientRegistry; import net.minecraftforge.fml.client.registry.ClientRegistry;
import net.minecraftforge.fml.client.registry.RenderingRegistry; import net.minecraftforge.fml.client.registry.RenderingRegistry;
import org.lwjgl.input.Keyboard;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/** /**
* The client proxy for wizardry. * The client proxy for wizardry.
@@ -145,7 +98,7 @@ public class ClientProxy extends CommonProxy {
public static MixedFontRenderer mixedFontRenderer; public static MixedFontRenderer mixedFontRenderer;
/** Static particle factory map */ /** Static particle factory map */
private static Map<Type, IWizardryParticleFactory> factories; private static final Map<ResourceLocation, IWizardryParticleFactory> factories = new HashMap<>();
// Key Bindings // 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 NEXT_SPELL = new KeyBinding("key." + Wizardry.MODID + ".next_spell", Keyboard.KEY_N, "key.categories." + Wizardry.MODID);
@@ -154,6 +107,9 @@ public class ClientProxy extends CommonProxy {
// Armour Model // Armour Model
public static final ModelBiped WIZARD_ARMOUR_MODEL = new ModelWizardArmour(0.75f); public static final ModelBiped WIZARD_ARMOUR_MODEL = new ModelWizardArmour(0.75f);
/** The wrap width for standard multi-line descriptions (see {@link ClientProxy#addMultiLineDescription(List, String, Style)}). */
private static final int TOOLTIP_WRAP_WIDTH = 140;
// SECTION Registry // SECTION Registry
// =============================================================================================================== // ===============================================================================================================
@@ -178,9 +134,18 @@ public class ClientProxy extends CommonProxy {
public void registerResourceReloadListeners(){ public void registerResourceReloadListeners(){
IResourceManager manager = Minecraft.getMinecraft().getResourceManager(); IResourceManager manager = Minecraft.getMinecraft().getResourceManager();
if(manager instanceof IReloadableResourceManager){ if(manager instanceof IReloadableResourceManager){
((IReloadableResourceManager)manager).registerReloadListener(GuiSpellDisplay::loadSkins); ((IReloadableResourceManager)manager).registerReloadListener(GuiSpellDisplay::loadSkins);
((IReloadableResourceManager)manager).registerReloadListener(GuiWizardHandbook::loadHandbookFile); ((IReloadableResourceManager)manager).registerReloadListener(GuiWizardHandbook::loadHandbookFile);
} }
}
// @Override
// public void registerSoundEventListener(){
// Minecraft.getMinecraft().getSoundHandler().addListener(ContinuousSpellSoundEntity::soundPlayed);
// }
public void registerAtlasMarkers(){
WizardryAntiqueAtlasIntegration.registerMarkers();
} }
// SECTION Misc // SECTION Misc
@@ -196,16 +161,35 @@ public class ClientProxy extends CommonProxy {
property.setConfigEntryClass(SpellHUDSkinChooserEntry.class); property.setConfigEntryClass(SpellHUDSkinChooserEntry.class);
} }
@Override
public void setToNamedBooleanEntry(Property property){
property.setConfigEntryClass(NamedBooleanEntry.class);
}
@Override @Override
public World getTheWorld(){ public World getTheWorld(){
return Minecraft.getMinecraft().world; return Minecraft.getMinecraft().world;
} }
@Override @Override
public void playMovingSound(Entity entity, SoundEvent sound, float volume, float pitch, boolean repeat){ public void playMovingSound(Entity entity, SoundEvent sound, SoundCategory category, float volume, float pitch, boolean repeat){
Minecraft.getMinecraft().getSoundHandler().playSound(new MovingSoundEntity(entity, sound, volume, pitch, repeat)); Minecraft.getMinecraft().getSoundHandler().playSound(new MovingSoundEntity<>(entity, sound, category, volume, pitch, repeat));
} }
@Override
public void playSpellSoundLoop(EntityLivingBase entity, Spell spell, SoundEvent start, SoundEvent loop, SoundEvent end, SoundCategory category, float volume, float pitch){
SoundLoop.addLoop(new SoundLoopSpell.SoundLoopSpellEntity(start, loop, end, spell, entity, volume, pitch));
}
@Override
public void playSpellSoundLoop(World world, double x, double y, double z, Spell spell, SoundEvent start, SoundEvent loop, SoundEvent end, SoundCategory category, float volume, float pitch, int duration){
if(duration == -1){
SoundLoop.addLoop(new SoundLoopSpell.SoundLoopSpellDispenser(start, loop, end, spell, world, x, y, z, volume, pitch));
}else{
SoundLoop.addLoop(new SoundLoopSpell.SoundLoopSpellPosTimed(start, loop, end, spell, duration, x, y, z, volume, pitch));
}
}
@Override @Override
public Set<String> getSpellHUDSkins(){ public Set<String> getSpellHUDSkins(){
return GuiSpellDisplay.getSkinKeys(); return GuiSpellDisplay.getSkinKeys();
@@ -214,6 +198,40 @@ public class ClientProxy extends CommonProxy {
// SECTION Items // SECTION Items
// =============================================================================================================== // ===============================================================================================================
@Override
public boolean shouldDisplayDiscovered(Spell spell, ItemStack stack){
EntityPlayerSP player = Minecraft.getMinecraft().player;
if(player == null) return false;
// Displayed recipe
if(Minecraft.getMinecraft().currentScreen instanceof GuiMerchant){
// It doesn't actually matter if the recipe is selected or not, since the itemstack will only ever
// match one of them anyway - and we'd have to reflect into GuiMerchant to get the selected recipe
MerchantRecipeList recipes = ((GuiMerchant)Minecraft.getMinecraft().currentScreen).getMerchant().getRecipes(player);
if(recipes != null && recipes.stream().anyMatch(r -> r.getItemToSell() == stack)){
// Spell books are always discovered when wizards are selling them
return true;
}
}
// Recipe output slot
// Required or players would be able to find out what the spell is without actually completing the trade
if(player.openContainer instanceof ContainerMerchant){
if(((ContainerMerchant)player.openContainer).getMerchantInventory().getStackInSlot(2) == stack){
return true;
}
}
if(!Wizardry.settings.discoveryMode) return true;
if(player.isCreative()) return true;
if(WizardData.get(player) != null && WizardData.get(player).hasSpellBeenDiscovered(spell)) return true;
return false;
}
@Override @Override
public FontRenderer getFontRenderer(ItemStack stack){ public FontRenderer getFontRenderer(ItemStack stack){
@@ -222,12 +240,10 @@ public class ClientProxy extends CommonProxy {
if(stack.getItem() instanceof ItemWand){ if(stack.getItem() instanceof ItemWand){
spell = WandHelper.getCurrentSpell(stack); spell = WandHelper.getCurrentSpell(stack);
}else if(stack.getItem() instanceof ItemSpellBook || stack.getItem() instanceof ItemScroll){ }else if(stack.getItem() instanceof ItemSpellBook || stack.getItem() instanceof ItemScroll){
spell = Spell.get(stack.getItemDamage()); spell = Spell.byMetadata(stack.getItemDamage());
} }
if(Minecraft.getMinecraft().player != null && Wizardry.settings.discoveryMode && WizardData.get(Minecraft.getMinecraft().player) != null if(!shouldDisplayDiscovered(spell, stack)){
&& !Minecraft.getMinecraft().player.capabilities.isCreativeMode
&& !WizardData.get(Minecraft.getMinecraft().player).hasSpellBeenDiscovered(spell)){
return mixedFontRenderer; return mixedFontRenderer;
} }
@@ -237,14 +253,14 @@ public class ClientProxy extends CommonProxy {
@Override @Override
public String getScrollDisplayName(ItemStack scroll){ public String getScrollDisplayName(ItemStack scroll){
Spell spell = Spell.get(scroll.getItemDamage()); Spell spell = Spell.byMetadata(scroll.getItemDamage());
EntityPlayer player = Minecraft.getMinecraft().player; EntityPlayer player = Minecraft.getMinecraft().player;
boolean discovered = true; boolean discovered = true;
// It seems that this method is called when the world is loading, before thePlayer has been initialised. // It seems that this method is called when the world is loading, before thePlayer has been initialised.
// If the player is null, the spell is assumed to be discovered. // If the player is null, the spell is assumed to be discovered.
if(player != null && Wizardry.settings.discoveryMode && !player.capabilities.isCreativeMode && WizardData.get(player) != null if(player != null && Wizardry.settings.discoveryMode && !player.isCreative() && WizardData.get(player) != null
&& !WizardData.get(player).hasSpellBeenDiscovered(spell)){ && !WizardData.get(player).hasSpellBeenDiscovered(spell)){
discovered = false; discovered = false;
} }
@@ -265,37 +281,50 @@ public class ClientProxy extends CommonProxy {
return super.getConjuredBowDurability(stack); return super.getConjuredBowDurability(stack);
} }
@Override
public void addMultiLineDescription(List<String> tooltip, String key, Style style){
String description = style.getFormattingCode() + I18n.format(key);
tooltip.addAll(Minecraft.getMinecraft().fontRenderer.listFormattedStringToWidth(description, TOOLTIP_WRAP_WIDTH));
}
// SECTION Particles // SECTION Particles
// =============================================================================================================== // ===============================================================================================================
/** Use {@link ParticleWizardry#registerParticle(ResourceLocation, IWizardryParticleFactory)}, this is internal. */
// I mean, it does exactly the same thing but I might want to make it do something else in future...
public static void addParticleFactory(ResourceLocation name, IWizardryParticleFactory factory){
factories.put(name, factory);
}
@Override @Override
public void initParticleFactories(){ 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...
factories = new HashMap<>(); ParticleWizardry.registerParticle(Type.BEAM, ParticleBeam::new);
ParticleWizardry.registerParticle(Type.BUFF, ParticleBuff::new);
factories.put(Type.BEAM, ParticleBeam::new); ParticleWizardry.registerParticle(Type.DARK_MAGIC, ParticleDarkMagic::new);
factories.put(Type.BUFF, ParticleBuff::new); ParticleWizardry.registerParticle(Type.DUST, ParticleDust::new);
factories.put(Type.DARK_MAGIC, ParticleDarkMagic::new); ParticleWizardry.registerParticle(Type.FLASH, ParticleFlash::new);
factories.put(Type.DUST, ParticleDust::new); ParticleWizardry.registerParticle(Type.ICE, ParticleIce::new);
factories.put(Type.FLASH, ParticleFlash::new); ParticleWizardry.registerParticle(Type.LEAF, ParticleLeaf::new);
factories.put(Type.ICE, ParticleIce::new); ParticleWizardry.registerParticle(Type.LIGHTNING, ParticleLightning::new);
factories.put(Type.LEAF, ParticleLeaf::new); ParticleWizardry.registerParticle(Type.LIGHTNING_PULSE, ParticleLightningPulse::new);
factories.put(Type.LIGHTNING, ParticleLightning::new); ParticleWizardry.registerParticle(Type.MAGIC_BUBBLE, ParticleMagicBubble::new);
factories.put(Type.LIGHTNING_PULSE, ParticleLightningPulse::new); ParticleWizardry.registerParticle(Type.MAGIC_FIRE, ParticleMagicFlame::new);
factories.put(Type.MAGIC_BUBBLE, ParticleMagicBubble::new); ParticleWizardry.registerParticle(Type.PATH, ParticlePath::new);
factories.put(Type.MAGIC_FIRE, ParticleMagicFlame::new); ParticleWizardry.registerParticle(Type.SCORCH, ParticleScorch::new);
factories.put(Type.PATH, ParticlePath::new); ParticleWizardry.registerParticle(Type.SNOW, ParticleSnow::new);
factories.put(Type.SCORCH, ParticleScorch::new); ParticleWizardry.registerParticle(Type.SPARK, ParticleSpark::new);
factories.put(Type.SNOW, ParticleSnow::new); ParticleWizardry.registerParticle(Type.SPARKLE, ParticleSparkle::new);
factories.put(Type.SPARK, ParticleSpark::new); ParticleWizardry.registerParticle(Type.SPHERE, ParticleSphere::new);
factories.put(Type.SPARKLE, ParticleSparkle::new); ParticleWizardry.registerParticle(Type.SUMMON, ParticleSummon::new);
ParticleWizardry.registerParticle(Type.VINE, ParticleVine::new);
} }
@Override @Override
public ParticleWizardry createParticle(Type type, World world, double x, double y, double z){ public ParticleWizardry createParticle(ResourceLocation type, World world, double x, double y, double z){
IWizardryParticleFactory factory = factories.get(type); IWizardryParticleFactory factory = factories.get(type);
if(factory == null){ if(factory == null){
Wizardry.logger.warn("Unrecognised particle type %s ! Ensure the particle is properly registered.", type); Wizardry.logger.warn("Unrecognised particle type {} ! Ensure the particle is properly registered.", type);
return null; return null;
} }
return factory.createParticle(world, x, y, z); return factory.createParticle(world, x, y, z);
@@ -315,14 +344,12 @@ public class ClientProxy extends CommonProxy {
World world = Minecraft.getMinecraft().world; World world = Minecraft.getMinecraft().world;
Entity caster = world.getEntityByID(message.casterID); Entity caster = world.getEntityByID(message.casterID);
Spell spell = Spell.get(message.spellID); Spell spell = Spell.byNetworkID(message.spellID);
// Should always be true // Should always be true
if(caster instanceof EntityPlayer){ if(caster instanceof EntityPlayer){
((EntityPlayer)caster).setActiveHand(message.hand); ((EntityPlayer)caster).setActiveHand(message.hand);
// Duration isn't needed because it only ever affects things server-side, and anything that is
// seen client-side gets synced elsewhere.
spell.cast(world, (EntityPlayer)caster, message.hand, 0, message.modifiers); spell.cast(world, (EntityPlayer)caster, message.hand, 0, message.modifiers);
Source source = Source.OTHER; Source source = Source.OTHER;
@@ -337,19 +364,34 @@ public class ClientProxy extends CommonProxy {
// No need to check if the spell succeeded, because the packet is only ever sent when it succeeds. // No need to check if the spell succeeded, because the packet is only ever sent when it succeeds.
// The handler for this event now deals with discovery. // The handler for this event now deals with discovery.
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post((EntityPlayer)caster, spell, message.modifiers, source)); MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(source, spell, (EntityPlayer)caster, message.modifiers));
}else{ }else{
Wizardry.logger.warn("Recieved a PacketCastSpell, but the caster ID was not the ID of a player"); Wizardry.logger.warn("Recieved a PacketCastSpell, but the caster ID was not the ID of a player");
} }
} }
@Override
public void handleCastSpellAtPosPacket(PacketCastSpellAtPos.Message message){
World world = Minecraft.getMinecraft().world;
Spell spell = Spell.byNetworkID(message.spellID);
spell.cast(world, message.position.x, message.position.y, message.position.z, message.direction, 0, message.duration, message.modifiers);
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.COMMAND, spell, world, message.position.x, message.position.y, message.position.z, message.direction, message.modifiers));
if(spell.isContinuous){
SpellEmitter.add(spell, world, message.position.x, message.position.y, message.position.z, message.direction, message.duration, message.modifiers);
}
}
@Override @Override
public void handleCastContinuousSpellPacket(PacketCastContinuousSpell.Message message){ public void handleCastContinuousSpellPacket(PacketCastContinuousSpell.Message message){
World world = Minecraft.getMinecraft().world; World world = Minecraft.getMinecraft().world;
Entity caster = world.getEntityByID(message.casterID); Entity caster = world.getEntityByID(message.casterID);
Spell spell = Spell.get(message.spellID); Spell spell = Spell.byNetworkID(message.spellID);
// Should always be true // Should always be true
if(caster instanceof EntityPlayer){ if(caster instanceof EntityPlayer){
@@ -359,7 +401,7 @@ public class ClientProxy extends CommonProxy {
if(data.isCasting()){ if(data.isCasting()){
WizardData.get((EntityPlayer)caster).stopCastingContinuousSpell(); WizardData.get((EntityPlayer)caster).stopCastingContinuousSpell();
}else{ }else{
WizardData.get((EntityPlayer)caster).startCastingContinuousSpell(spell, message.modifiers); WizardData.get((EntityPlayer)caster).startCastingContinuousSpell(spell, message.modifiers, message.duration);
} }
} }
}else{ }else{
@@ -373,7 +415,7 @@ public class ClientProxy extends CommonProxy {
World world = Minecraft.getMinecraft().world; World world = Minecraft.getMinecraft().world;
Entity caster = world.getEntityByID(message.casterID); Entity caster = world.getEntityByID(message.casterID);
Entity target = message.targetID == -1 ? null : world.getEntityByID(message.targetID); Entity target = message.targetID == -1 ? null : world.getEntityByID(message.targetID);
Spell spell = Spell.get(message.spellID); Spell spell = Spell.byNetworkID(message.spellID);
// Should always be true // Should always be true
if(caster instanceof EntityLiving){ if(caster instanceof EntityLiving){
@@ -382,7 +424,7 @@ public class ClientProxy extends CommonProxy {
spell.cast(world, (EntityLiving)caster, message.hand, 0, (EntityLivingBase)target, message.modifiers); spell.cast(world, (EntityLiving)caster, message.hand, 0, (EntityLivingBase)target, message.modifiers);
// Again, no need to check if the spell succeeded, because the packet is only ever sent when it // Again, no need to check if the spell succeeded, because the packet is only ever sent when it
// succeeds. // succeeds.
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post((EntityLiving)caster, spell, message.modifiers, Source.NPC)); MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.NPC, spell, (EntityLiving)caster, message.modifiers));
} }
if(caster instanceof ISpellCaster){ if(caster instanceof ISpellCaster){
@@ -391,10 +433,41 @@ public class ClientProxy extends CommonProxy {
((EntityLiving)caster).setAttackTarget((EntityLivingBase)target); ((EntityLiving)caster).setAttackTarget((EntityLivingBase)target);
} }
} }
}else{ }else if(caster != null){
Wizardry.logger.warn("Recieved a PacketNPCCastSpell, but the caster ID was not the ID of an EntityLiving"); 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 @Override
public void handleTransportationPacket(PacketTransportation.Message message){ public void handleTransportationPacket(PacketTransportation.Message message){
@@ -402,78 +475,150 @@ public class ClientProxy extends CommonProxy {
World world = Minecraft.getMinecraft().world; World world = Minecraft.getMinecraft().world;
Entity caster = world.getEntityByID(message.casterID); Entity caster = world.getEntityByID(message.casterID);
// Moved from when the packet is sent to when it is received; fixes the sound not playing in first person. // Moved from when the packet is sent to when it is received; fixes the sound not playing in first person.
caster.playSound(SoundEvents.BLOCK_PORTAL_TRAVEL, 1, 1); caster.playSound(WizardrySounds.SPELL_TRANSPORTATION_TRAVEL, 1, 1);
for(int i = 0; i < 20; i++){ for(int i = 0; i < 20; i++){
double radius = 1; double radius = 1;
double angle = world.rand.nextDouble() * Math.PI * 2; float angle = world.rand.nextFloat() * (float)Math.PI * 2;
double x = caster.posX + radius * Math.cos(angle); double x = caster.posX + radius * MathHelper.cos(angle);
double y = caster.getEntityBoundingBox().minY + world.rand.nextDouble() * 2; double y = caster.getEntityBoundingBox().minY + world.rand.nextDouble() * 2;
double z = caster.posZ + radius * Math.sin(angle); double z = caster.posZ + radius * MathHelper.sin(angle);
ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.02, 0).clr(0.6f, 1, 0.6f) 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); .time(80 + world.rand.nextInt(10)).spawn(world);
} }
for(int i = 0; i < 20; i++){ for(int i = 0; i < 20; i++){
double radius = 1; double radius = 1;
double angle = world.rand.nextDouble() * Math.PI * 2; float angle = world.rand.nextFloat() * (float)Math.PI * 2;
double x = caster.posX + radius * Math.cos(angle); double x = caster.posX + radius * MathHelper.cos(angle);
double y = caster.getEntityBoundingBox().minY + world.rand.nextDouble() * 2; double y = caster.getEntityBoundingBox().minY + world.rand.nextDouble() * 2;
double z = caster.posZ + radius * Math.sin(angle); double z = caster.posZ + radius * MathHelper.sin(angle);
world.spawnParticle(EnumParticleTypes.VILLAGER_HAPPY, x, y, z, 0, 0.02, 0); world.spawnParticle(EnumParticleTypes.VILLAGER_HAPPY, x, y, z, 0, 0.02, 0);
} }
for(int i = 0; i < 20; i++){ for(int i = 0; i < 20; i++){
double radius = 1; double radius = 1;
double angle = world.rand.nextDouble() * Math.PI * 2; float angle = world.rand.nextFloat() * (float)Math.PI * 2;
double x = caster.posX + radius * Math.cos(angle); double x = caster.posX + radius * MathHelper.cos(angle);
double y = caster.getEntityBoundingBox().minY + world.rand.nextDouble() * 2; double y = caster.getEntityBoundingBox().minY + world.rand.nextDouble() * 2;
double z = caster.posZ + radius * Math.sin(angle); double z = caster.posZ + radius * MathHelper.sin(angle);
world.spawnParticle(EnumParticleTypes.ENCHANTMENT_TABLE, x, y, z, 0, 0.02, 0); world.spawnParticle(EnumParticleTypes.ENCHANTMENT_TABLE, x, y, z, 0, 0.02, 0);
} }
} }
@Override @Override
public void handlePlayerSyncPacket(Message message){ public void handlePlayerSyncPacket(PacketPlayerSync.Message message){
WizardData properties = WizardData.get(Minecraft.getMinecraft().player); WizardData data = WizardData.get(Minecraft.getMinecraft().player);
if(properties != null){ if(data != null){
properties.spellsDiscovered = message.spellsDiscovered; data.synchronisedRandom.setSeed(message.seed);
data.spellsDiscovered = message.spellsDiscovered;
message.spellData.forEach(data::setVariable);
if(message.selectedMinionID == -1){ if(message.selectedMinionID == -1){
properties.selectedMinion = null; data.selectedMinion = null;
}else{ }else{
Entity entity = Minecraft.getMinecraft().world.getEntityByID(message.selectedMinionID); Entity entity = Minecraft.getMinecraft().world.getEntityByID(message.selectedMinionID);
if(entity instanceof ISummonedCreature){ if(entity instanceof ISummonedCreature){
properties.selectedMinion = new WeakReference<ISummonedCreature>((ISummonedCreature)entity); data.selectedMinion = new WeakReference<>((ISummonedCreature)entity);
}else{ }else{
properties.selectedMinion = null; data.selectedMinion = null;
} }
} }
} }
} }
@Override @Override
public void handleGlyphDataPacket(electroblob.wizardry.packet.PacketGlyphData.Message message){ public void handleGlyphDataPacket(PacketGlyphData.Message message){
SpellGlyphData data = SpellGlyphData.get(Minecraft.getMinecraft().world); SpellGlyphData data = SpellGlyphData.get(Minecraft.getMinecraft().world);
data.randomNames = new HashMap<Spell, String>(); data.randomNames = new HashMap<>();
data.randomDescriptions = new HashMap<Spell, String>(); data.randomDescriptions = new HashMap<>();
for(Spell spell : Spell.getSpells(Spell.allSpells)){ for(Spell spell : Spell.getSpells(Spell.allSpells)){
// -1 because the none spell isn't included // -1 because the none spell isn't included
data.randomNames.put(spell, message.names.get(spell.id() - 1)); // This is a case where we must use the network ID, not the metadata
data.randomDescriptions.put(spell, message.descriptions.get(spell.id() - 1)); data.randomNames.put(spell, message.names.get(spell.networkID() - 1));
data.randomDescriptions.put(spell, message.descriptions.get(spell.networkID() - 1));
} }
} }
@Override @Override
public void handleClairvoyancePacket(electroblob.wizardry.packet.PacketClairvoyance.Message message){ public void handleEmitterDataPacket(PacketEmitterData.Message message){
message.emitters.forEach(e -> e.setWorld(Minecraft.getMinecraft().world)); // Do this as soon as possible!
SpellEmitterData data = SpellEmitterData.get(Minecraft.getMinecraft().world);
// We shouldn't need to clear the emitters because when a player logs in or changes dimension the client world
// is wiped anyway, so the call to get() above should result in a fresh SpellEmitterData instance
message.emitters.forEach(data::add);
}
@Override
public void handleClairvoyancePacket(PacketClairvoyance.Message message){
Clairvoyance.spawnPathPaticles(Minecraft.getMinecraft().world, message.path, message.durationMultiplier); Clairvoyance.spawnPathPaticles(Minecraft.getMinecraft().world, message.path, message.durationMultiplier);
} }
@Override
public void handleAdvancementSyncPacket(PacketSyncAdvancements.Message message){
GuiWizardHandbook.updateUnlockStatus(message.showToasts, message.completedAdvancements);
}
@Override
public void handleEndSlowTimePacket(PacketEndSlowTime.Message message){
Entity entity = Minecraft.getMinecraft().world.getEntityByID(message.hostID);
if(entity instanceof EntityLivingBase) PotionSlowTime.unblockNearbyEntities((EntityLivingBase)entity);
else Wizardry.logger.warn("Received a PacketEndSlowTime, but the entity ID did not match any living entity");
}
@Override
public void handleResurrectionPacket(PacketResurrection.Message message){
Entity entity = Minecraft.getMinecraft().world.getEntityByID(message.playerID);
if(entity instanceof EntityPlayer){
((Resurrection)Spells.resurrection).resurrect((EntityPlayer)entity);
if(entity == Minecraft.getMinecraft().player){
Minecraft.getMinecraft().world.spawnEntity(entity);
Minecraft.getMinecraft().displayGuiScreen(null);
}
}
else Wizardry.logger.warn("Received a PacketResurrection, but the entity ID did not match any player");
}
@Override
public void handlePossessionPacket(PacketPossession.Message message){
Entity entity = Minecraft.getMinecraft().world.getEntityByID(message.playerID);
if(entity instanceof EntityPlayer){
EntityPlayer player = (EntityPlayer)entity;
if(message.targetID == -1){
((Possession)Spells.possession).endPossession(player);
}else{
Entity target = Minecraft.getMinecraft().world.getEntityByID(message.targetID);
if(target instanceof EntityLiving){
((Possession)Spells.possession).possess(player, (EntityLiving)target, message.duration);
player.sendStatusMessage(new TextComponentTranslation("spell." + Spells.possession.getRegistryName()
+ ".success", Minecraft.getMinecraft().gameSettings.keyBindSneak.getDisplayName()), true);
}
else Wizardry.logger.warn("Received a PacketPossession, but the target ID did not match any living entity");
}
}
else Wizardry.logger.warn("Received a PacketPossession, but the player ID did not match any player");
}
public void handleConquerShrinePacket(PacketConquerShrine.Message message){
TileEntity tileEntity = Minecraft.getMinecraft().world.getTileEntity(new BlockPos(message.x, message.y, message.z));
if(tileEntity instanceof TileEntityShrineCore){
((TileEntityShrineCore)tileEntity).conquer();
}else Wizardry.logger.warn("Received a PacketConquerShrine, but there was no shrine core at the position sent");
}
// SECTION Rendering // SECTION Rendering
// =============================================================================================================== // ===============================================================================================================
@@ -486,6 +631,7 @@ public class ClientProxy extends CommonProxy {
@Override @Override
public void initialiseLayers(){ public void initialiseLayers(){
LayerStone.initialiseLayers(); LayerStone.initialiseLayers();
LayerFrost.initialiseLayers();
} }
@Override @Override
@@ -495,6 +641,12 @@ public class ClientProxy extends CommonProxy {
// Yet another advantage to the new system: turns out you don't even need to register the renderer if you // Yet another advantage to the new system: turns out you don't even need to register the renderer if you
// just want the vanilla one for the mob you're extending. // just want the vanilla one for the mob you're extending.
// Luckily for us, the vanilla husk renderer is only parametrised to EntityZombie
RenderingRegistry.registerEntityRenderingHandler(EntityHuskMinion.class, RenderHusk::new);
// This now extends AbstractSkeleton so we need to bind the renderer ourselves
RenderingRegistry.registerEntityRenderingHandler(EntitySkeletonMinion.class, RenderSkeleton::new);
RenderingRegistry.registerEntityRenderingHandler(EntityStrayMinion.class, RenderStrayMinion::new);
// An anonymous class in a lambda expression! No point writing a separate class really, is there? // An anonymous class in a lambda expression! No point writing a separate class really, is there?
RenderingRegistry.registerEntityRenderingHandler(EntityLightningWraith.class, manager -> new RenderBlaze(manager){ RenderingRegistry.registerEntityRenderingHandler(EntityLightningWraith.class, manager -> new RenderBlaze(manager){
@Override @Override
@@ -536,7 +688,7 @@ public class ClientProxy extends CommonProxy {
// Throwables // Throwables
RenderingRegistry.registerEntityRenderingHandler(EntitySparkBomb.class, RenderingRegistry.registerEntityRenderingHandler(EntitySparkBomb.class,
manager -> new RenderProjectile(manager, 0.6f, new ResourceLocation(Wizardry.MODID, "textures/entity/spark_bomb.png"), false)); manager -> new RenderProjectile(manager, 0.6f, new ResourceLocation(Wizardry.MODID, "textures/items/spark_bomb.png"), false));
RenderingRegistry.registerEntityRenderingHandler(EntityFirebomb.class, RenderingRegistry.registerEntityRenderingHandler(EntityFirebomb.class,
manager -> new RenderProjectile(manager, 0.6f, new ResourceLocation(Wizardry.MODID, "textures/items/firebomb.png"), false)); manager -> new RenderProjectile(manager, 0.6f, new ResourceLocation(Wizardry.MODID, "textures/items/firebomb.png"), false));
RenderingRegistry.registerEntityRenderingHandler(EntityPoisonBomb.class, RenderingRegistry.registerEntityRenderingHandler(EntityPoisonBomb.class,
@@ -555,6 +707,14 @@ public class ClientProxy extends CommonProxy {
manager -> new RenderLightningDisc(manager, new ResourceLocation(Wizardry.MODID, "textures/entity/lightning_sigil.png"), 2.0f)); manager -> new RenderLightningDisc(manager, new ResourceLocation(Wizardry.MODID, "textures/entity/lightning_sigil.png"), 2.0f));
RenderingRegistry.registerEntityRenderingHandler(EntitySmokeBomb.class, RenderingRegistry.registerEntityRenderingHandler(EntitySmokeBomb.class,
manager -> new RenderProjectile(manager, 0.6f, new ResourceLocation(Wizardry.MODID, "textures/items/smoke_bomb.png"), false)); manager -> new RenderProjectile(manager, 0.6f, new ResourceLocation(Wizardry.MODID, "textures/items/smoke_bomb.png"), false));
RenderingRegistry.registerEntityRenderingHandler(EntityEmber.class,
manager -> new RenderProjectile(manager, 0.15f, new ResourceLocation(Wizardry.MODID, "textures/entity/ember.png"), false));
RenderingRegistry.registerEntityRenderingHandler(EntityMagicFireball.class,
manager -> new RenderProjectile(manager, 0.7f, new ResourceLocation(Wizardry.MODID, "textures/entity/fireball.png"), false));
RenderingRegistry.registerEntityRenderingHandler(EntityLargeMagicFireball.class,
manager -> new RenderProjectile(manager, 1.5f, new ResourceLocation(Wizardry.MODID, "textures/entity/fireball.png"), false));
RenderingRegistry.registerEntityRenderingHandler(EntityIceball.class,
manager -> new RenderProjectile(manager, 0.7f, new ResourceLocation(Wizardry.MODID, "textures/entity/iceball.png"), false));
// Effects and constructs // Effects and constructs
RenderingRegistry.registerEntityRenderingHandler(EntityBlackHole.class, RenderBlackHole::new); RenderingRegistry.registerEntityRenderingHandler(EntityBlackHole.class, RenderBlackHole::new);
@@ -562,13 +722,14 @@ public class ClientProxy extends CommonProxy {
RenderingRegistry.registerEntityRenderingHandler(EntityBubble.class, RenderBubble::new); RenderingRegistry.registerEntityRenderingHandler(EntityBubble.class, RenderBubble::new);
RenderingRegistry.registerEntityRenderingHandler(EntityHammer.class, RenderHammer::new); RenderingRegistry.registerEntityRenderingHandler(EntityHammer.class, RenderHammer::new);
RenderingRegistry.registerEntityRenderingHandler(EntityIceSpike.class, RenderIceSpike::new); RenderingRegistry.registerEntityRenderingHandler(EntityIceSpike.class, RenderIceSpike::new);
RenderingRegistry.registerEntityRenderingHandler(EntityForcefield.class, RenderForcefield::new);
//RenderingRegistry.registerEntityRenderingHandler(EntityContainmentField.class, RenderContainmentField::new);
// Stuff that doesn't render // Stuff that doesn't render
RenderingRegistry.registerEntityRenderingHandler(EntityBlizzard.class, RenderBlank::new); RenderingRegistry.registerEntityRenderingHandler(EntityBlizzard.class, RenderBlank::new);
RenderingRegistry.registerEntityRenderingHandler(EntityTornado.class, RenderBlank::new); RenderingRegistry.registerEntityRenderingHandler(EntityTornado.class, RenderBlank::new);
RenderingRegistry.registerEntityRenderingHandler(EntityArrowRain.class, RenderBlank::new); RenderingRegistry.registerEntityRenderingHandler(EntityArrowRain.class, RenderBlank::new);
RenderingRegistry.registerEntityRenderingHandler(EntityShadowWraith.class, RenderBlank::new); RenderingRegistry.registerEntityRenderingHandler(EntityShadowWraith.class, RenderBlank::new);
RenderingRegistry.registerEntityRenderingHandler(EntityForcefield.class, RenderBlank::new);
RenderingRegistry.registerEntityRenderingHandler(EntityThunderbolt.class, RenderBlank::new); RenderingRegistry.registerEntityRenderingHandler(EntityThunderbolt.class, RenderBlank::new);
RenderingRegistry.registerEntityRenderingHandler(EntityStormElemental.class, RenderBlank::new); RenderingRegistry.registerEntityRenderingHandler(EntityStormElemental.class, RenderBlank::new);
RenderingRegistry.registerEntityRenderingHandler(EntityEarthquake.class, RenderBlank::new); RenderingRegistry.registerEntityRenderingHandler(EntityEarthquake.class, RenderBlank::new);
@@ -586,6 +747,8 @@ public class ClientProxy extends CommonProxy {
RenderingRegistry.registerEntityRenderingHandler(EntityFireRing.class, 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"), 5.0f));
RenderingRegistry.registerEntityRenderingHandler(EntityDecay.class, RenderDecay::new); 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));
// TESRs // TESRs
ClientRegistry.bindTileEntitySpecialRenderer(TileEntityArcaneWorkbench.class, new RenderArcaneWorkbench()); ClientRegistry.bindTileEntitySpecialRenderer(TileEntityArcaneWorkbench.class, new RenderArcaneWorkbench());
@@ -1,19 +1,13 @@
package electroblob.wizardry.client; package electroblob.wizardry.client;
import org.lwjgl.opengl.GL11;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.inventory.GuiContainer; import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.*;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.client.renderer.RenderItem;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraftforge.fml.relauncher.Side; import net.minecraft.util.math.MathHelper;
import net.minecraftforge.fml.relauncher.SideOnly; import org.lwjgl.opengl.GL11;
/** /**
* Utility class containing some useful static methods for drawing GUIs. Previously these were spread across the main * Utility class containing some useful static methods for drawing GUIs. Previously these were spread across the main
@@ -23,7 +17,7 @@ import net.minecraftforge.fml.relauncher.SideOnly;
* @since Wizardry 4.2 * @since Wizardry 4.2
* @see MixedFontRenderer * @see MixedFontRenderer
*/ */
@SideOnly(Side.CLIENT) //@SideOnly(Side.CLIENT)
public final class DrawingUtils { public final class DrawingUtils {
/** /**
@@ -130,6 +124,31 @@ public final class DrawingUtils {
tessellator.draw(); tessellator.draw();
} }
/**
* Mixes the two given opaque colours in the proportion specified.
* @param colour1 The first colour to mix, as a 6-digit hexadecimal.
* @param colour2 The second colour to mix, as a 6-digit hexadecimal.
* @param proportion The proportion of the second colour; will be clamped to between 0 and 1.
* @return The resulting colour, as a 6-digit hexadecimal.
*/
public static int mix(int colour1, int colour2, float proportion){
proportion = MathHelper.clamp(proportion, 0, 1);
int r1 = colour1 >> 16 & 255;
int g1 = colour1 >> 8 & 255;
int b1 = colour1 & 255;
int r2 = colour2 >> 16 & 255;
int g2 = colour2 >> 8 & 255;
int b2 = colour2 & 255;
int r = (int)(r1 + (r2-r1) * proportion);
int g = (int)(g1 + (g2-g1) * proportion);
int b = (int)(b1 + (b2-b1) * proportion);
return (r << 16) + (g << 8) + b;
}
/** /**
* Makes the given opaque colour translucent with the given opacity. * Makes the given opaque colour translucent with the given opacity.
* @param colour An integer colour code, should be a 6-digit hexadecimal (i.e. opaque). * @param colour An integer colour code, should be a 6-digit hexadecimal (i.e. opaque).
@@ -5,15 +5,13 @@ import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.renderer.texture.TextureManager; import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.settings.GameSettings; import net.minecraft.client.settings.GameSettings;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
/** /**
* Font renderer that renders parts of strings surrounded by '#' (without quotes) in the SGA instead of normal text. * Font renderer that renders parts of strings surrounded by '#' (without quotes) in the SGA instead of normal text.
* *
* @since Wizardry 1.1 * @since Wizardry 1.1
*/ */
@SideOnly(Side.CLIENT) //@SideOnly(Side.CLIENT)
public class MixedFontRenderer extends FontRenderer { public class MixedFontRenderer extends FontRenderer {
public MixedFontRenderer(GameSettings p_i1035_1_, ResourceLocation p_i1035_2_, TextureManager p_i1035_3_, public MixedFontRenderer(GameSettings p_i1035_1_, ResourceLocation p_i1035_2_, TextureManager p_i1035_3_,
@@ -1,52 +0,0 @@
package electroblob.wizardry.client;
import net.minecraft.client.audio.MovingSound;
import net.minecraft.entity.Entity;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.MathHelper;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
// Copied from MovingSoundMinecart; if it ever breaks between updates take a look at that.
@SideOnly(Side.CLIENT)
public class MovingSoundEntity extends MovingSound {
private final Entity source;
private float distance = 0.0F;
public MovingSoundEntity(Entity entity, SoundEvent sound, float volume, float pitch, boolean repeat){
// Uses BLOCKS because that's the closest thing to inanimate entities. Could use NEUTRAL like
// MovingSoundMinecart.
super(sound, SoundCategory.BLOCKS);
this.source = entity;
this.repeat = repeat;
this.volume = volume;
this.pitch = pitch;
this.repeatDelay = 0;
}
/**
* Updates the JList with a new model.
*/
@Override
public void update(){
if(this.source.isDead && repeat){
this.donePlaying = true;
}else{
this.xPosF = (float)this.source.posX;
this.yPosF = (float)this.source.posY;
this.zPosF = (float)this.source.posZ;
float f = MathHelper.sqrt(this.source.motionX * this.source.motionX
+ this.source.motionY * this.source.motionY + this.source.motionZ * this.source.motionZ);
// Is this something to do with the Doppler effect?
if((double)f >= 0.01D){
this.distance = MathHelper.clamp(this.distance + 0.0025F, 0.0F, 1.0F);
this.volume = 0.0F + MathHelper.clamp(f, 0.0F, 0.5F) * 0.7F;
}else{
// this.pitch = 0.0F;
// this.volume = 0.0F;
}
}
}
}
@@ -1,51 +1,52 @@
package electroblob.wizardry.client; package electroblob.wizardry.client;
import org.lwjgl.opengl.GL11;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry; import electroblob.wizardry.Wizardry;
import electroblob.wizardry.block.BlockMagicLight;
import electroblob.wizardry.constants.Constants; import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.data.DispenserCastingData;
import electroblob.wizardry.data.SpellEmitterData;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.item.ISpellCastingItem;
import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.item.ItemSpectralBow; import electroblob.wizardry.item.ItemSpectralBow;
import electroblob.wizardry.item.ItemWand; import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.spell.Flight; import electroblob.wizardry.spell.*;
import electroblob.wizardry.spell.ShadowWard; import electroblob.wizardry.util.RayTracer;
import electroblob.wizardry.spell.Shield;
import electroblob.wizardry.tileentity.ContainerArcaneWorkbench;
import electroblob.wizardry.util.WandHelper;
import electroblob.wizardry.util.WizardryUtilities; import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiMerchant; import net.minecraft.client.gui.GuiMerchant;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper; import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.client.renderer.RenderItem;
import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.settings.KeyBinding;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.ai.attributes.AttributeModifier;
import net.minecraft.entity.item.EntityArmorStand; import net.minecraft.entity.item.EntityArmorStand;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.Slot; import net.minecraft.inventory.Slot;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.RayTraceResult; import net.minecraft.util.math.RayTraceResult;
import net.minecraft.util.math.Vec3d;
import net.minecraft.village.MerchantRecipe; import net.minecraft.village.MerchantRecipe;
import net.minecraftforge.client.event.FOVUpdateEvent; import net.minecraft.world.World;
import net.minecraftforge.client.event.GuiContainerEvent; import net.minecraftforge.client.event.*;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.client.event.RenderLivingEvent;
import net.minecraftforge.client.event.RenderPlayerEvent;
import net.minecraftforge.client.event.RenderWorldLastEvent;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly; import org.lwjgl.opengl.GL11;
import java.lang.reflect.Method;
/** /**
* Event handler responsible for client-side only events, mostly rendering. * Event handler responsible for client-side only events, mostly rendering.
@@ -53,13 +54,10 @@ import net.minecraftforge.fml.relauncher.SideOnly;
* @author Electroblob * @author Electroblob
* @since Wizardry 1.0 * @since Wizardry 1.0
*/ */
@SideOnly(Side.CLIENT) //@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT) @Mod.EventBusSubscriber(Side.CLIENT)
public final class WizardryClientEventHandler { public final class WizardryClientEventHandler {
private static final ResourceLocation shieldTexture = new ResourceLocation(Wizardry.MODID, "textures/entity/shield.png");
private static final ResourceLocation wingTexture = new ResourceLocation(Wizardry.MODID, "textures/entity/wing.png");
private static final ResourceLocation shadowWardTexture = new ResourceLocation(Wizardry.MODID, "textures/entity/shadow_ward.png");
private static final ResourceLocation sixthSenseTexture = new ResourceLocation(Wizardry.MODID, "textures/entity/sixth_sense.png"); private static final ResourceLocation 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 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 frostOverlayTexture = new ResourceLocation(Wizardry.MODID, "textures/gui/frost_overlay.png");
@@ -73,6 +71,12 @@ public final class WizardryClientEventHandler {
private static int blinkEffectTimer; private static int blinkEffectTimer;
/** The number of ticks the blink effect lasts for. */ /** The number of ticks the blink effect lasts for. */
private static final int BLINK_EFFECT_DURATION = 8; private static final int BLINK_EFFECT_DURATION = 8;
private static final Method unpressKey;
static {
unpressKey = ObfuscationReflectionHelper.findMethod(KeyBinding.class, "func_74505_d", void.class);
}
/** Starts the first person blink overlay effect. */ /** Starts the first person blink overlay effect. */
public static void playBlinkEffect(){ public static void playBlinkEffect(){
@@ -80,21 +84,97 @@ public final class WizardryClientEventHandler {
} }
@SubscribeEvent @SubscribeEvent
public static void onLivingUpdateEvent(LivingUpdateEvent event){ public static void onPlayerTickEvent(TickEvent.PlayerTickEvent event){
if(event.getEntity() == Minecraft.getMinecraft().player){
if(event.player == Minecraft.getMinecraft().player){
if(blinkEffectTimer > 0) blinkEffectTimer--; if(blinkEffectTimer > 0) blinkEffectTimer--;
// Only seems to work here...
// EntityLiving victim = Possession.getPossessee(Minecraft.getMinecraft().player);
// if(victim != null && victim.getHeldItemMainhand().isEmpty()){
// Minecraft.getMinecraft().player.setHeldItem(EnumHand.MAIN_HAND, ItemStack.EMPTY);
// }
// Reset shaders if their respective potions aren't active
// This is a player so the potion effects are synced by vanilla
if(Minecraft.getMinecraft().entityRenderer.getShaderGroup() != null){
String activeShader = Minecraft.getMinecraft().entityRenderer.getShaderGroup().getShaderGroupName();
if((activeShader.equals(SlowTime.SHADER.toString()) && !Minecraft.getMinecraft().player.isPotionActive(WizardryPotions.slow_time))
|| (activeShader.equals(SixthSense.SHADER.toString()) && !Minecraft.getMinecraft().player.isPotionActive(WizardryPotions.sixth_sense))
|| (activeShader.equals(Transience.SHADER.toString()) && !Minecraft.getMinecraft().player.isPotionActive(WizardryPotions.transience))){
if(activeShader.equals(SixthSense.SHADER.toString())
|| activeShader.equals(Transience.SHADER.toString())) playBlinkEffect();
Minecraft.getMinecraft().entityRenderer.stopUseShader();
}
}
} }
} }
@SubscribeEvent @SubscribeEvent
public static void onTextureStitchEvent(TextureStitchEvent.Pre event){ public static void onRenderHandEvent(RenderHandEvent event){
event.getMap().registerSprite(ContainerArcaneWorkbench.EMPTY_SLOT_CRYSTAL); EntityLiving victim = Possession.getPossessee(Minecraft.getMinecraft().player);
event.getMap().registerSprite(ContainerArcaneWorkbench.EMPTY_SLOT_UPGRADE);
// for(int i=0; i<7; i++){ if(victim != null){
// event.getMap().registerSprite(new ResourceLocation(Wizardry.MODID, "particle/ice_" + i));
// } 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;
}
}
@SubscribeEvent
public static void onClientTickEvent(TickEvent.ClientTickEvent event){
if(event.phase == TickEvent.Phase.END && !net.minecraft.client.Minecraft.getMinecraft().isGamePaused()){
World world = net.minecraft.client.Minecraft.getMinecraft().world;
if(world == null) return;
for(TileEntity tileentity : world.loadedTileEntityList){
if(tileentity instanceof TileEntityDispenser){
if(DispenserCastingData.get((TileEntityDispenser)tileentity) != null){
DispenserCastingData.get((TileEntityDispenser)tileentity).update();
}
}
}
SpellEmitterData.update(world);
}
}
@SubscribeEvent
public static void onMouseEvent(MouseEvent event){
// Prevents the player looking around when paralysed
if(Minecraft.getMinecraft().player.isPotionActive(WizardryPotions.paralysis)
&& Minecraft.getMinecraft().inGameHasFocus){
event.setCanceled(true);
Minecraft.getMinecraft().player.prevRotationYaw = 0;
Minecraft.getMinecraft().player.prevRotationPitch = 0;
Minecraft.getMinecraft().player.rotationYaw = 0;
Minecraft.getMinecraft().player.rotationPitch = 0;
}
} }
@SubscribeEvent @SubscribeEvent
@@ -121,11 +201,26 @@ public final class WizardryClientEventHandler {
event.setNewfov(event.getFov() + f * f * 0.7f); event.setNewfov(event.getFov() + f * f * 0.7f);
} }
} }
@SubscribeEvent
public static void onDrawBlockHighlightEvent(DrawBlockHighlightEvent event){
// Hide the block outline for magic light blocks unless the player can dispel them
if(event.getTarget().typeOfHit == RayTraceResult.Type.BLOCK
&& event.getPlayer().world.getBlockState(event.getTarget().getBlockPos()).getBlock() instanceof BlockMagicLight){
if((!(event.getPlayer().getHeldItemMainhand().getItem() instanceof ISpellCastingItem)
&& !(event.getPlayer().getHeldItemOffhand().getItem() instanceof ISpellCastingItem))
|| !ItemArtefact.isArtefactActive(event.getPlayer(), WizardryItems.charm_light)){
event.setCanceled(true);
}
}
}
// Brute-force fix for crystals not showing up when a wizard is given a spell book in the trade GUI. // Brute-force fix for crystals not showing up when a wizard is given a spell book in the trade GUI.
@SubscribeEvent @SubscribeEvent
public static void onGuiDrawForegroundEvent(GuiContainerEvent.DrawForeground event){ public static void onGuiDrawForegroundEvent(GuiContainerEvent.DrawForeground event){
if(event.getGuiContainer() instanceof GuiMerchant){ if(event.getGuiContainer() instanceof GuiMerchant){
GuiMerchant gui = (GuiMerchant)event.getGuiContainer(); GuiMerchant gui = (GuiMerchant)event.getGuiContainer();
@@ -138,81 +233,35 @@ public final class WizardryClientEventHandler {
for(MerchantRecipe trade : gui.getMerchant().getRecipes(Minecraft.getMinecraft().player)){ for(MerchantRecipe trade : gui.getMerchant().getRecipes(Minecraft.getMinecraft().player)){
if(trade.getItemToBuy().getItem() == WizardryItems.spell_book && trade.getSecondItemToBuy().isEmpty()){ if(trade.getItemToBuy().getItem() == WizardryItems.spell_book && trade.getSecondItemToBuy().isEmpty()){
Slot slot = gui.inventorySlots.getSlot(2); Slot slot = gui.inventorySlots.getSlot(2);
// Uses reflection to draw the itemstack
// It still doesn't look quite right because the slot highlight is behind the item, but it'll do // 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. // until/unless I find a better solution.
renderItemAndTooltip(gui, trade.getItemToSell(), slot.xPos, slot.yPos, event.getMouseX(), event.getMouseY(), DrawingUtils.drawItemAndTooltip(gui, trade.getItemToSell(), slot.xPos, slot.yPos, event.getMouseX(), event.getMouseY(),
gui.getSlotUnderMouse() == slot); gui.getSlotUnderMouse() == slot);
} }
} }
} }
} }
} }
private static void renderItemAndTooltip(GuiContainer gui, ItemStack stack, int x, int y, int mouseX, int mouseY, boolean tooltip){
RenderItem renderItem = Minecraft.getMinecraft().getRenderItem();
GlStateManager.pushMatrix();
RenderHelper.enableGUIStandardItemLighting();
GlStateManager.disableLighting();
GlStateManager.enableRescaleNormal();
GlStateManager.enableColorMaterial();
GlStateManager.enableLighting();
renderItem.zLevel = 100.0F;
if(!stack.isEmpty()){
renderItem.renderItemAndEffectIntoGUI(stack, x, y);
renderItem.renderItemOverlays(Minecraft.getMinecraft().fontRenderer, stack, x, y);
if(tooltip){
gui.drawHoveringText(gui.getItemToolTip(stack), mouseX + gui.getXSize()/2 - gui.width/2,
mouseY + gui.getYSize()/2 - gui.height/2);
}
}
GlStateManager.popMatrix();
GlStateManager.enableLighting();
GlStateManager.enableDepth();
RenderHelper.enableStandardItemLighting();
}
// Third person
@SubscribeEvent
public static void onRenderPlayerEvent(RenderPlayerEvent.Post event){
renderShieldIfActive(event.getEntityPlayer());
renderWingsIfActive(event.getEntityPlayer(), event.getPartialRenderTick());
renderShadowWardIfActive(event.getEntityPlayer());
}
// First person
@SubscribeEvent
public static void onRenderWorldLastEvent(RenderWorldLastEvent event){
// Now only fires in first person.
if(Minecraft.getMinecraft().gameSettings.thirdPersonView == 0){
renderShieldFirstPerson(Minecraft.getMinecraft().player);
renderShadowWardFirstPerson(Minecraft.getMinecraft().player);
}
}
@SubscribeEvent @SubscribeEvent
public static void onRenderLivingEvent(RenderLivingEvent.Post<EntityLivingBase> event){ public static void onRenderLivingEvent(RenderLivingEvent.Post<EntityLivingBase> event){
Minecraft mc = Minecraft.getMinecraft(); Minecraft mc = Minecraft.getMinecraft();
WizardData properties = WizardData.get(mc.player); WizardData data = WizardData.get(mc.player);
RenderManager renderManager = event.getRenderer().getRenderManager(); RenderManager renderManager = event.getRenderer().getRenderManager();
ItemStack wand = mc.player.getHeldItemMainhand(); ItemStack wand = mc.player.getHeldItemMainhand();
if(!(wand.getItem() instanceof ItemWand)){ if(!(wand.getItem() instanceof ISpellCastingItem)){
wand = mc.player.getHeldItemOffhand(); wand = mc.player.getHeldItemOffhand();
} }
// Target selection pointer // Target selection pointer
if(mc.player.isSneaking() && wand.getItem() instanceof ItemWand && WizardryUtilities.isLiving(event.getEntity()) if(mc.player.isSneaking() && wand.getItem() instanceof ISpellCastingItem && WizardryUtilities.isLiving(event.getEntity())
&& properties != null && properties.selectedMinion != null){ && data != null && data.selectedMinion != null){
// -> Moved this in here so it isn't called every tick // -> Moved this in here so it isn't called every tick
RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(mc.world, mc.player, 16, false); RayTraceResult rayTrace = RayTracer.standardEntityRayTrace(mc.world, mc.player, 16, false);
if(rayTrace != null && rayTrace.entityHit == event.getEntity()){ if(rayTrace != null && rayTrace.entityHit == event.getEntity()){
@@ -256,7 +305,7 @@ public final class WizardryClientEventHandler {
} }
// Summoned creature selection pointer // Summoned creature selection pointer
if(properties != null && properties.selectedMinion != null && properties.selectedMinion.get() == event.getEntity()){ if(data != null && data.selectedMinion != null && data.selectedMinion.get() == event.getEntity()){
Tessellator tessellator = Tessellator.getInstance(); Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer(); BufferBuilder buffer = tessellator.getBuffer();
@@ -297,8 +346,9 @@ public final class WizardryClientEventHandler {
} }
// Sixth sense // Sixth sense
if(mc.player.isPotionActive(WizardryPotions.sixth_sense) && !(event.getEntity() instanceof EntityArmorStand) && event.getEntity() != mc.player if(mc.player.isPotionActive(WizardryPotions.sixth_sense) && !(event.getEntity() instanceof EntityArmorStand)
&& mc.player.getActivePotionEffect(WizardryPotions.sixth_sense) != null && event.getEntity().getDistance(mc.player) < 20 && 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)){ * (1 + mc.player.getActivePotionEffect(WizardryPotions.sixth_sense).getAmplifier() * Constants.RANGE_INCREASE_PER_LEVEL)){
Tessellator tessellator = Tessellator.getInstance(); Tessellator tessellator = Tessellator.getInstance();
@@ -413,322 +463,4 @@ public final class WizardryClientEventHandler {
GlStateManager.popMatrix(); GlStateManager.popMatrix();
} }
// FIXME: Something in here is making the first person shadow ward rather translucent.
private static void renderShadowWardFirstPerson(EntityPlayer entityplayer){
ItemStack wand = entityplayer.getActiveItemStack();
if(WizardData.get(entityplayer) != null && WizardData.get(entityplayer).currentlyCasting() instanceof ShadowWard
|| (entityplayer.isHandActive() && wand.getItemDamage() < wand.getMaxDamage() && wand.getItem() instanceof ItemWand
&& WandHelper.getCurrentSpell(wand) instanceof ShadowWard)){
GlStateManager.pushMatrix();
GlStateManager.enableBlend();
GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GlStateManager.shadeModel(GL11.GL_SMOOTH);
GlStateManager.disableLighting();
GlStateManager.disableAlpha();
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f);
GlStateManager.translate(0, 1.2, 0);
GlStateManager.rotate(-entityplayer.rotationYaw, 0, 1, 0);
GlStateManager.rotate(entityplayer.rotationPitch, 1, 0, 0);
Minecraft.getMinecraft().renderEngine.bindTexture(shadowWardTexture);
GlStateManager.pushMatrix();
GlStateManager.translate(0, 0, 1.2);
GlStateManager.rotate(entityplayer.world.getWorldTime() * -2, 0, 0, 1);
GlStateManager.scale(1.1, 1.1, 1.1);
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
buffer.pos(-0.5, 0.5, -0.5).tex(0, 0).endVertex();
buffer.pos(0.5, 0.5, -0.5).tex(1, 0).endVertex();
buffer.pos(0.5, -0.5, -0.5).tex(1, 1).endVertex();
buffer.pos(-0.5, -0.5, -0.5).tex(0, 1).endVertex();
tessellator.draw();
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
buffer.pos(-0.5, 0.5, -0.5).tex(0, 0).endVertex();
buffer.pos(-0.5, -0.5, -0.5).tex(0, 1).endVertex();
buffer.pos(0.5, -0.5, -0.5).tex(1, 1).endVertex();
buffer.pos(0.5, 0.5, -0.5).tex(1, 0).endVertex();
tessellator.draw();
GlStateManager.popMatrix();
GlStateManager.shadeModel(GL11.GL_FLAT);
GlStateManager.enableLighting();
GlStateManager.disableBlend();
GlStateManager.popMatrix();
}
}
private static void renderShadowWardIfActive(EntityPlayer entityplayer){
ItemStack wand = entityplayer.getActiveItemStack();
if(WizardData.get(entityplayer).currentlyCasting() instanceof ShadowWard
|| (entityplayer.isHandActive() && wand.getItemDamage() < wand.getMaxDamage() && wand.getItem() instanceof ItemWand
&& WandHelper.getCurrentSpell(wand) instanceof ShadowWard)){
GlStateManager.pushMatrix();
GlStateManager.enableBlend();
GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GlStateManager.disableLighting();
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f);
GlStateManager.rotate(180, 0, 1, 0);
GlStateManager.rotate(-entityplayer.renderYawOffset, 0, 1, 0);
Minecraft.getMinecraft().renderEngine.bindTexture(shadowWardTexture);
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
GlStateManager.translate(0, 1.2, 0);
GlStateManager.rotate(entityplayer.world.getWorldTime() * -2, 0, 0, 1);
GlStateManager.scale(1.1, 1.1, 1.1);
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
buffer.pos(-0.5, 0.5, -0.5).tex(0, 0).endVertex();
buffer.pos(0.5, 0.5, -0.5).tex(1, 0).endVertex();
buffer.pos(0.5, -0.5, -0.5).tex(1, 1).endVertex();
buffer.pos(-0.5, -0.5, -0.5).tex(0, 1).endVertex();
tessellator.draw();
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
buffer.pos(-0.5, 0.5, -0.5).tex(0, 0).endVertex();
buffer.pos(-0.5, -0.5, -0.5).tex(0, 1).endVertex();
buffer.pos(0.5, -0.5, -0.5).tex(1, 1).endVertex();
buffer.pos(0.5, 0.5, -0.5).tex(1, 0).endVertex();
tessellator.draw();
GlStateManager.enableLighting();
GlStateManager.disableBlend();
GlStateManager.popMatrix();
}
}
private static void renderWingsIfActive(EntityPlayer entityplayer, float partialTickTime){
ItemStack wand = entityplayer.getActiveItemStack();
if(WizardData.get(entityplayer).currentlyCasting() instanceof Flight
|| (entityplayer.isHandActive() && wand.getItemDamage() < wand.getMaxDamage() && wand.getItem() instanceof ItemWand
&& WandHelper.getCurrentSpell(wand) instanceof Flight)){
GlStateManager.pushMatrix();
GlStateManager.enableBlend();
GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GlStateManager.disableLighting();
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f);
// GlStateManager.rotate(-entityplayer.rotationYawHead, 0, 1, 0);
GlStateManager.rotate(-entityplayer.renderYawOffset, 0, 1, 0);
// GlStateManager.rotate(180, 1, 0, 0);
Minecraft.getMinecraft().renderEngine.bindTexture(wingTexture);
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
GlStateManager.pushMatrix();
GlStateManager.translate(0.1, 0.4, -0.15);
GlStateManager.rotate(20 + 20 * (float)Math.sin(entityplayer.world.getWorldTime() * 0.3), 0, 1, 0);
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
buffer.pos(0, 2, 0).tex(0, 0).endVertex();
buffer.pos(2, 2, 0).tex(1, 0).endVertex();
buffer.pos(2, 0, 0).tex(1, 1).endVertex();
buffer.pos(0, 0, 0).tex(0, 1).endVertex();
tessellator.draw();
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
buffer.pos(0, 2, 0).tex(0, 0).endVertex();
buffer.pos(0, 0, 0).tex(0, 1).endVertex();
buffer.pos(2, 0, 0).tex(1, 1).endVertex();
buffer.pos(2, 2, 0).tex(1, 0).endVertex();
tessellator.draw();
GlStateManager.popMatrix();
GlStateManager.pushMatrix();
GlStateManager.translate(-0.1, 0.4, -0.15);
GlStateManager.rotate(-200 - 20 * (float)Math.sin(entityplayer.world.getWorldTime() * 0.3), 0, 1, 0);
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
buffer.pos(0, 2, 0).tex(0, 0).endVertex();
buffer.pos(2, 2, 0).tex(1, 0).endVertex();
buffer.pos(2, 0, 0).tex(1, 1).endVertex();
buffer.pos(0, 0, 0).tex(0, 1).endVertex();
tessellator.draw();
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
buffer.pos(0, 2, 0).tex(0, 0).endVertex();
buffer.pos(0, 0, 0).tex(0, 1).endVertex();
buffer.pos(2, 0, 0).tex(1, 1).endVertex();
buffer.pos(2, 2, 0).tex(1, 0).endVertex();
tessellator.draw();
GlStateManager.popMatrix();
GlStateManager.enableLighting();
GlStateManager.disableBlend();
GlStateManager.popMatrix();
}
}
private static void renderShieldFirstPerson(EntityPlayer entityplayer){
ItemStack wand = entityplayer.getActiveItemStack();
if(WizardData.get(entityplayer) != null && WizardData.get(entityplayer).shield != null
&& (WizardData.get(entityplayer).currentlyCasting() instanceof Shield
|| (entityplayer.isHandActive() && wand.getItemDamage() < wand.getMaxDamage() && wand.getItem() instanceof ItemWand
&& WandHelper.getCurrentSpell(wand) instanceof Shield))){
GlStateManager.pushMatrix();
GlStateManager.disableCull();
GlStateManager.enableBlend();
GlStateManager.blendFunc(GL11.GL_ONE, GL11.GL_SRC_ALPHA);
GlStateManager.shadeModel(GL11.GL_SMOOTH);
GlStateManager.disableLighting();
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f);
GlStateManager.translate(0, 1.4, 0);
GlStateManager.rotate(-entityplayer.rotationYaw, 0, 1, 0);
GlStateManager.rotate(entityplayer.rotationPitch, 1, 0, 0);
GlStateManager.translate(0, 0, 0.8);
Tessellator tessellator = Tessellator.getInstance();
Minecraft.getMinecraft().renderEngine.bindTexture(shieldTexture);
renderShield(tessellator);
GlStateManager.enableLighting();
GlStateManager.shadeModel(GL11.GL_FLAT);
GlStateManager.enableCull();
GlStateManager.disableBlend();
// RenderHelper.enableStandardItemLighting();
GlStateManager.popMatrix();
}
}
private static void renderShieldIfActive(EntityPlayer entityplayer){
ItemStack wand = entityplayer.getActiveItemStack();
if(WizardData.get(entityplayer).shield != null && (WizardData.get(entityplayer).currentlyCasting() instanceof Shield
|| (entityplayer.isHandActive() && wand.getItemDamage() < wand.getMaxDamage() && wand.getItem() instanceof ItemWand
&& WandHelper.getCurrentSpell(wand) instanceof Shield))){
GlStateManager.pushMatrix();
GlStateManager.disableCull();
GlStateManager.enableBlend();
// For some reason, the old blend function (GL11.GL_SRC_ALPHA, GL11.GL_SRC_ALPHA) caused the inner
// edges to appear black, so I have changed it to this, which looks very slightly different.
GlStateManager.blendFunc(GL11.GL_ONE, GL11.GL_SRC_ALPHA);
GlStateManager.shadeModel(GL11.GL_SMOOTH);
GlStateManager.disableLighting();
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f);
GlStateManager.translate(0, 1.3, 0);
// GlStateManager.rotate(180, 0, 1, 0);
GlStateManager.rotate(-entityplayer.renderYawOffset, 0, 1, 0);
// GlStateManager.rotate(-entityplayer.rotationPitch, 1, 0, 0);
GlStateManager.translate(0, 0, 0.8);
Tessellator tessellator = Tessellator.getInstance();
Minecraft.getMinecraft().renderEngine.bindTexture(shieldTexture);
renderShield(tessellator);
GlStateManager.enableLighting();
GlStateManager.shadeModel(GL11.GL_FLAT);
GlStateManager.enableCull();
GlStateManager.disableBlend();
// RenderHelper.enableStandardItemLighting();
GlStateManager.popMatrix();
}
}
private static void renderShield(Tessellator tessellator){
BufferBuilder buffer = tessellator.getBuffer();
double widthOuter = 0.6d;
double heightOuter = 0.7d;
double widthInner = 0.3d;
double heightInner = 0.4d;
double depth = 0.2d;
buffer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_COLOR);
buffer.pos(-widthOuter, heightInner, -depth).tex(0, 0.2).color(0, 0, 0, 255).endVertex();
buffer.pos(-widthInner, heightInner, 0).tex(0.2, 0.2).color(200, 200, 255, 255).endVertex();
buffer.pos(-widthInner, heightOuter, -depth).tex(0.2, 0).color(0, 0, 0, 255).endVertex();
buffer.pos(-widthInner, heightInner, 0).tex(0.2, 0.2).color(200, 200, 255, 255).endVertex();
buffer.pos(widthInner, heightOuter, -depth).tex(0.8, 0).color(0, 0, 0, 255).endVertex();
buffer.pos(widthInner, heightInner, 0).tex(0.8, 0.2).color(200, 200, 255, 255).endVertex();
buffer.pos(widthOuter, heightInner, -depth).tex(1, 0.2).color(0, 0, 0, 255).endVertex();
buffer.pos(widthInner, heightInner, 0).tex(0.8, 0.2).color(200, 200, 255, 255).endVertex();
buffer.pos(widthOuter, -heightInner, -depth).tex(1, 0.8).color(0, 0, 0, 255).endVertex();
buffer.pos(widthInner, -heightInner, 0).tex(0.8, 0.8).color(200, 200, 255, 255).endVertex();
buffer.pos(widthInner, -heightOuter, -depth).tex(0.8, 1).color(0, 0, 0, 255).endVertex();
buffer.pos(widthInner, -heightInner, 0).tex(0.8, 0.8).color(200, 200, 255, 255).endVertex();
buffer.pos(-widthInner, -heightOuter, -depth).tex(0.2, 1).color(0, 0, 0, 255).endVertex();
buffer.pos(-widthInner, -heightInner, 0).tex(0.2, 0.8).color(200, 200, 255, 255).endVertex();
buffer.pos(-widthOuter, -heightInner, -depth).tex(0, 0.8).color(0, 0, 0, 255).endVertex();
buffer.pos(-widthInner, -heightInner, 0).tex(0.2, 0.8).color(200, 200, 255, 255).endVertex();
buffer.pos(-widthOuter, heightInner, -depth).tex(0, 0.2).color(0, 0, 0, 255).endVertex();
buffer.pos(-widthInner, heightInner, 0).tex(0.2, 0.2).color(200, 200, 255, 255).endVertex();
tessellator.draw();
buffer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_COLOR);
buffer.pos(-widthInner, heightInner, 0).tex(0.2, 0.2).color(200, 200, 255, 255).endVertex();
buffer.pos(widthInner, heightInner, 0).tex(0.8, 0.2).color(200, 200, 255, 255).endVertex();
buffer.pos(-widthInner, -heightInner, 0).tex(0.2, 0.8).color(200, 200, 255, 255).endVertex();
buffer.pos(widthInner, -heightInner, 0).tex(0.8, 0.8).color(200, 200, 255, 255).endVertex();
tessellator.draw();
}
} }
@@ -1,14 +1,11 @@
package electroblob.wizardry.client; package electroblob.wizardry.client;
import org.lwjgl.input.Keyboard;
import electroblob.wizardry.Wizardry; import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.gui.GuiSpellDisplay; import electroblob.wizardry.client.gui.GuiSpellDisplay;
import electroblob.wizardry.item.ItemWand; import electroblob.wizardry.item.ISpellCastingItem;
import electroblob.wizardry.packet.PacketControlInput; import electroblob.wizardry.packet.PacketControlInput;
import electroblob.wizardry.packet.WizardryPacketHandler; import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.WandHelper;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.PositionedSoundRecord; import net.minecraft.client.audio.PositionedSoundRecord;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
@@ -16,72 +13,54 @@ import net.minecraft.item.ItemStack;
import net.minecraftforge.client.event.MouseEvent; import net.minecraftforge.client.event.MouseEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.InputEvent; import net.minecraftforge.fml.common.gameevent.TickEvent;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage; import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
/** Event handler class responsible for handling wizardry's controls. */ /** Event handler class responsible for handling wizardry's controls. */
@SideOnly(Side.CLIENT) //@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT) @Mod.EventBusSubscriber(Side.CLIENT)
public class WizardryControlHandler { public class WizardryControlHandler {
static boolean NkeyPressed = false; static boolean NkeyPressed = false;
static boolean BkeyPressed = false; static boolean BkeyPressed = false;
static boolean NkeyAlreadyPressed = false;
static boolean BkeyAlreadyPressed = false;
// Changed to a tick event to allow mouse button keybinds
// The 'lag' that happened previously was actually because the code only fired when a keyboard key was pressed!
@SubscribeEvent @SubscribeEvent
public static void onKeyInput(InputEvent.KeyInputEvent event){ public static void onTickEvent(TickEvent.ClientTickEvent event){
// Key pressed if(event.phase == TickEvent.Phase.END) return; // Only really needs to be once per tick
if(Keyboard.getEventKeyState()){
if(Wizardry.proxy instanceof ClientProxy){ if(Wizardry.proxy instanceof ClientProxy){
EntityPlayer player = Minecraft.getMinecraft().player;
ItemStack wand = player.getHeldItemMainhand();
if(!(wand.getItem() instanceof ItemWand)){ EntityPlayer player = Minecraft.getMinecraft().player;
wand = player.getHeldItemOffhand();
// If the player isn't holding a wand, then nothing else needs to be done.
if(!(wand.getItem() instanceof ItemWand)) return;
}
if(ClientProxy.NEXT_SPELL.isPressed() && Minecraft.getMinecraft().inGameHasFocus){ if(player != null){
ItemStack wand = getWandInUse(player);
if(wand == null) return;
if(ClientProxy.NEXT_SPELL.isKeyDown() && Minecraft.getMinecraft().inGameHasFocus){
if(!NkeyPressed){ if(!NkeyPressed){
NkeyPressed = true; NkeyPressed = true;
}else{
NkeyAlreadyPressed = true;
}
if(!NkeyAlreadyPressed){
selectNextSpell(wand); selectNextSpell(wand);
} }
}else{
NkeyPressed = false;
} }
if(ClientProxy.PREVIOUS_SPELL.isPressed() && Minecraft.getMinecraft().inGameHasFocus){ if(ClientProxy.PREVIOUS_SPELL.isKeyDown() && Minecraft.getMinecraft().inGameHasFocus){
if(!BkeyPressed){ if(!BkeyPressed){
BkeyPressed = true; BkeyPressed = true;
}else{ // Packet building
BkeyAlreadyPressed = true;
}
if(!BkeyAlreadyPressed){
selectPreviousSpell(wand); selectPreviousSpell(wand);
} }
}else{
BkeyPressed = false;
} }
} }
} }
// Key released
else{
if(NkeyPressed){
NkeyPressed = false;
NkeyAlreadyPressed = false;
}else if(BkeyPressed){
BkeyPressed = false;
BkeyAlreadyPressed = false;
}
}
} }
// Shift-scrolling to change spells // Shift-scrolling to change spells
@@ -89,16 +68,11 @@ public class WizardryControlHandler {
public static void onMouseEvent(MouseEvent event){ public static void onMouseEvent(MouseEvent event){
EntityPlayer player = Minecraft.getMinecraft().player; EntityPlayer player = Minecraft.getMinecraft().player;
ItemStack wand = player.getHeldItemMainhand(); ItemStack wand = getWandInUse(player);
if(wand == null) return;
if(!(wand.getItem() instanceof ItemWand)){
wand = player.getHeldItemOffhand();
// If the player isn't holding a wand, then nothing else needs to be done.
if(!(wand.getItem() instanceof ItemWand)) return;
}
if(Minecraft.getMinecraft().inGameHasFocus && !wand.isEmpty() && event.getDwheel() != 0 && player.isSneaking() if(Minecraft.getMinecraft().inGameHasFocus && !wand.isEmpty() && event.getDwheel() != 0 && player.isSneaking()
&& Wizardry.settings.enableShiftScrolling){ && Wizardry.settings.shiftScrolling){
event.setCanceled(true); event.setCanceled(true);
@@ -111,15 +85,28 @@ public class WizardryControlHandler {
} }
} }
} }
private static ItemStack getWandInUse(EntityPlayer player){
ItemStack wand = player.getHeldItemMainhand();
// Only bother sending packets if the player is holding a spellcasting item with more than one spell slot
if(!(wand.getItem() instanceof ISpellCastingItem) || ((ISpellCastingItem)wand.getItem()).getSpells(wand).length < 2){
wand = player.getHeldItemOffhand();
if(!(wand.getItem() instanceof ISpellCastingItem) || ((ISpellCastingItem)wand.getItem()).getSpells(wand).length < 2) return null;
}
return wand;
}
private static void selectNextSpell(ItemStack wand){ private static void selectNextSpell(ItemStack wand){
// Packet building // Packet building
IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.NEXT_SPELL_KEY); IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.NEXT_SPELL_KEY);
WizardryPacketHandler.net.sendToServer(msg); WizardryPacketHandler.net.sendToServer(msg);
// GUI switch animation // GUI switch animation
WandHelper.selectNextSpell(wand); // Makes sure the spell is set immediately for the client ((ISpellCastingItem)wand.getItem()).selectNextSpell(wand); // Makes sure the spell is set immediately for the client
GuiSpellDisplay.playSpellSwitchAnimation(true); GuiSpellDisplay.playSpellSwitchAnimation(true);
Minecraft.getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.SELECT_SPELL, 1)); Minecraft.getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.ITEM_WAND_SWITCH_SPELL, 1));
} }
private static void selectPreviousSpell(ItemStack wand){ private static void selectPreviousSpell(ItemStack wand){
@@ -127,8 +114,8 @@ public class WizardryControlHandler {
IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.PREVIOUS_SPELL_KEY); IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.PREVIOUS_SPELL_KEY);
WizardryPacketHandler.net.sendToServer(msg); WizardryPacketHandler.net.sendToServer(msg);
// GUI switch animation // GUI switch animation
WandHelper.selectPreviousSpell(wand); // Makes sure the spell is set immediately for the client ((ISpellCastingItem)wand.getItem()).selectPreviousSpell(wand); // Makes sure the spell is set immediately for the client
GuiSpellDisplay.playSpellSwitchAnimation(false); GuiSpellDisplay.playSpellSwitchAnimation(false);
Minecraft.getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.SELECT_SPELL, 1)); Minecraft.getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.ITEM_WAND_SWITCH_SPELL, 1));
} }
} }
@@ -0,0 +1,35 @@
package electroblob.wizardry.client.audio;
import net.minecraft.client.audio.MovingSound;
import net.minecraft.entity.Entity;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvent;
// Copied from MovingSoundMinecart; if it ever breaks between updates take a look at that.
//@SideOnly(Side.CLIENT)
public class MovingSoundEntity<T extends Entity> extends MovingSound {
protected final T source;
protected float distance = 0.0F;
public MovingSoundEntity(T entity, SoundEvent sound, SoundCategory category, float volume, float pitch, boolean repeat){
super(sound, category);
this.source = entity;
this.repeat = repeat;
this.volume = volume;
this.pitch = pitch;
this.repeatDelay = 0;
}
@Override
public void update(){
if(this.source.isDead){
this.donePlaying = true;
}else{
this.xPosF = (float)this.source.posX;
this.yPosF = (float)this.source.posY;
this.zPosF = (float)this.source.posZ;
}
}
}
@@ -0,0 +1,94 @@
package electroblob.wizardry.client.audio;
import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.ISound;
import net.minecraft.util.ITickable;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import net.minecraftforge.fml.relauncher.Side;
import java.util.HashSet;
import java.util.Set;
/**
* Instances of this class represent a set of sounds which together form a looped sound: start, loop and end.
* Currently this is only used for continuous spell sounds in wizardry itself, but feel free to make your own
* implementations - this class will take care of the internals.
*
* @since Wizardry 4.2
* @author Electroblob
*/
// See MusicTicker, this is the same idea of just storing the sounds statically client-side and ticking them
@Mod.EventBusSubscriber(Side.CLIENT)
public abstract class SoundLoop implements ITickable {
private static final Set<SoundLoop> activeLoops = new HashSet<>();
private final ISound start;
private final ISound loop;
private final ISound end;
private boolean looping = false;
private boolean needsRemoving = false;
public SoundLoop(SoundEvent start, SoundEvent loop, SoundEvent end, SoundCategory category, ISoundFactory factory){
// The reason I've gone to the effort of having a factory for these is that we need SoundLoop to have control
// over which sounds are repeated and which aren't whilst keeping them private.
this.start = factory.create(start, category, false);
this.loop = factory.create(loop, category, true);
this.end = factory.create(end, category, false);
}
@Override
public void update(){
// Check every tick if the start sound is done playing and if so, start the loop sound
if(!looping && !Minecraft.getMinecraft().getSoundHandler().isSoundPlaying(start)){
Minecraft.getMinecraft().getSoundHandler().playSound(loop);
looping = true;
}
}
/** Stops the loop part of the sound immediately and starts playing the end part. This may be called from subclasses
* or externally depending on the implementation. */
// For continuous spell sounds it's internally
public void endLoop(){
Minecraft.getMinecraft().getSoundHandler().stopSound(start);
Minecraft.getMinecraft().getSoundHandler().stopSound(loop);
Minecraft.getMinecraft().getSoundHandler().playSound(end);
// Can't modify activeLoops directly since we'll probably be calling this method from update(), which is
// during iteration of activeLoops so it could cause a ConcurrentModificationException
this.markForRemoval();
}
/** Marks this sound loop to be removed next tick. */
protected void markForRemoval(){
this.needsRemoving = true;
}
// Static methods
public static void addLoop(SoundLoop loop){
activeLoops.add(loop);
// Do this here rather than in the constructor in case someone wants to play the loop later or reuse it
Minecraft.getMinecraft().getSoundHandler().playSound(loop.start);
}
@SubscribeEvent
public static void tick(TickEvent.ClientTickEvent event){
// Using the END phase means we can check for stopped sounds as soon as they are stopped (effectively),
// meaning we don't get the 'cut' between the start and loop sounds
// FIXME: Apparently this only works for dispensers. What the heck is the difference?!
if(event.phase == TickEvent.Phase.END){
activeLoops.forEach(SoundLoop::update);
activeLoops.removeIf(s -> s.needsRemoving);
}
}
@FunctionalInterface
public interface ISoundFactory {
ISound create(SoundEvent sound, SoundCategory category, boolean repeat);
}
}
@@ -0,0 +1,117 @@
package electroblob.wizardry.client.audio;
import electroblob.wizardry.data.DispenserCastingData;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.audio.PositionedSound;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
/** Abstract base class for sound loops associated with spells; see subclasses below for implementations. */
public abstract class SoundLoopSpell extends SoundLoop {
private final Spell spell;
public SoundLoopSpell(SoundEvent start, SoundEvent loop, SoundEvent end, ISoundFactory factory, Spell spell){
super(start, loop, end, WizardrySounds.SPELLS, factory);
this.spell = spell;
}
@Override
public void update(){
// This may be a bit overkill but I might as well put functionality in superclasses where possible
if(stillCasting(spell)){
// This can't called in the same tick as endLoop because otherwise, if the spell casting stops during the
// same tick as the transition to the loop sound it will try and stop the loop immediately after it has
// started - and for whatever reason, this causes the 'Channel null in method stop' error.
super.update();
}else{
endLoop();
}
}
protected abstract boolean stillCasting(Spell spell);
/** Implements a sound loop for continuous spells cast by entities. */
public static class SoundLoopSpellEntity extends SoundLoopSpell {
private final EntityLivingBase source;
public SoundLoopSpellEntity(SoundEvent start, SoundEvent loop, SoundEvent end, Spell spell, EntityLivingBase source, float volume, float pitch){
super(start, loop, end, (sound, category, repeat) -> new MovingSoundEntity<>(source, sound, category, volume, pitch, repeat), spell);
this.source = source;
}
@Override
protected boolean stillCasting(Spell spell){
return WizardryUtilities.isCasting(source, spell);
}
}
public static abstract class SoundLoopSpellPosition extends SoundLoopSpell {
public SoundLoopSpellPosition (SoundEvent start, SoundEvent loop, SoundEvent end, Spell spell,
double x, double y, double z, float sndVolume, float sndPitch){
// Huh, I actually found a use for a non-static initialiser block - hence the double curly brackets...
super(start, loop, end, (sound, category, r) -> new PositionedSound(sound, category){{
// ...et voila, we can just set protected fields as we please using external variables
this.xPosF = (float)x;
this.yPosF = (float)y;
this.zPosF = (float)z;
this.repeat = r;
this.volume = sndVolume;
this.pitch = sndPitch;
}}, spell);
}
}
/** Implements a sound loop for continuous spells cast by dispensers. */
public static class SoundLoopSpellDispenser extends SoundLoopSpellPosition {
private final TileEntityDispenser source;
public SoundLoopSpellDispenser(SoundEvent start, SoundEvent loop, SoundEvent end, Spell spell, World world,
double x, double y, double z, float sndVolume, float sndPitch){
super(start, loop, end, spell, x, y, z, sndVolume, sndPitch);
TileEntity tileentity = world.getTileEntity(new BlockPos(x, y, z));
if(tileentity instanceof TileEntityDispenser) this.source = (TileEntityDispenser)tileentity;
else throw new NullPointerException(String.format("Playing continuous spell sound: no dispenser found at %s, %s, %s", x, y, z));
}
@Override
protected boolean stillCasting(Spell spell){
return DispenserCastingData.get(source).currentlyCasting() == spell;
}
}
/** Implements a sound loop for continuous spells cast at a position (via commands). */
public static class SoundLoopSpellPosTimed extends SoundLoopSpellPosition {
private int timeLeft;
public SoundLoopSpellPosTimed(SoundEvent start, SoundEvent loop, SoundEvent end, Spell spell, int duration,
double x, double y, double z, float sndVolume, float sndPitch){
super(start, loop, end, spell, x, y, z, sndVolume, sndPitch);
this.timeLeft = duration;
}
@Override
public void update(){
super.update();
timeLeft--;
}
@Override
protected boolean stillCasting(Spell spell){
return timeLeft > 0;
}
}
}
@@ -1,23 +1,27 @@
package electroblob.wizardry.client.gui; package electroblob.wizardry.client.gui;
import org.lwjgl.input.Keyboard;
import electroblob.wizardry.SpellGlyphData;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry; import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.DrawingUtils;
import electroblob.wizardry.constants.Element; import electroblob.wizardry.constants.Element;
import electroblob.wizardry.item.ItemWand; import electroblob.wizardry.data.SpellGlyphData;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.item.IManaStoringItem;
import electroblob.wizardry.item.ISpellCastingItem;
import electroblob.wizardry.item.IWorkbenchItem;
import electroblob.wizardry.packet.PacketControlInput; import electroblob.wizardry.packet.PacketControlInput;
import electroblob.wizardry.packet.WizardryPacketHandler; import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.spell.Spell; import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.tileentity.ContainerArcaneWorkbench; import electroblob.wizardry.tileentity.ContainerArcaneWorkbench;
import electroblob.wizardry.tileentity.TileEntityArcaneWorkbench; import electroblob.wizardry.tileentity.TileEntityArcaneWorkbench;
import electroblob.wizardry.util.WandHelper; import electroblob.wizardry.util.WandHelper;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.PositionedSoundRecord;
import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.inventory.GuiContainer; import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.renderer.GlStateManager; 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.client.resources.I18n;
import net.minecraft.entity.player.InventoryPlayer; import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.inventory.IInventory; import net.minecraft.inventory.IInventory;
@@ -25,131 +29,260 @@ import net.minecraft.inventory.Slot;
import net.minecraft.item.Item; import net.minecraft.item.Item;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage; import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.relauncher.Side;
import org.lwjgl.input.Keyboard;
@Mod.EventBusSubscriber(Side.CLIENT)
public class GuiArcaneWorkbench extends GuiContainer { public class GuiArcaneWorkbench extends GuiContainer {
private GuiButton applyBtn; private GuiButton applyBtn;
private static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID, public static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID,
"textures/gui/arcane_workbench.png"); "textures/gui/arcane_workbench.png");
private IInventory playerInventory; private IInventory playerInventory;
private IInventory arcaneWorkbenchInventory; private IInventory arcaneWorkbenchInventory;
private final int tooltipWidth = 164; private static final int TOOLTIP_WIDTH = 164;
// We report the actual size of the GUI to Minecraft when a wand is in so JEI doesn't overdraw it. /** 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. * For calculations, we use the size without the tooltip, which is stored in this constant. */
private final int xSizeNoTip = 176; private static final int MAIN_GUI_WIDTH = 176;
private static final int RUNE_LEFT = 38;
private static final int RUNE_TOP = 22;
private static final int RUNE_WIDTH = 100;
private static final int RUNE_HEIGHT = 100;
private static final int HALO_DIAMETER = 156;
private static final int TEXTURE_WIDTH = 512;
private static final int TEXTURE_HEIGHT = 256;
private int animationTimer = 0;
private static final int ANIMATION_DURATION = 20;
public GuiArcaneWorkbench(InventoryPlayer invPlayer, TileEntityArcaneWorkbench entity){ public GuiArcaneWorkbench(InventoryPlayer invPlayer, TileEntityArcaneWorkbench entity){
super(new ContainerArcaneWorkbench(invPlayer, entity)); super(new ContainerArcaneWorkbench(invPlayer, entity));
this.playerInventory = invPlayer; this.playerInventory = invPlayer;
this.arcaneWorkbenchInventory = entity; this.arcaneWorkbenchInventory = entity;
xSize = xSizeNoTip; xSize = MAIN_GUI_WIDTH;
ySize = 220; ySize = 220;
} }
// Huh, didn't realise this method existed. Pretty neat.
@Override @Override
public void drawScreen(int p_73863_1_, int p_73863_2_, float p_73863_3_){ public void updateScreen(){
if(animationTimer > 0) animationTimer--;
this.drawDefaultBackground();
// Tests if there is a wand in the workbench and edits the positioning accordingly
if(this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getHasStack() && this.inventorySlots
.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getStack().getItem() instanceof ItemWand){
xSize = xSizeNoTip + tooltipWidth;
guiLeft = (this.width - this.xSize) / 2;
this.applyBtn.x = (this.width - tooltipWidth) / 2 + 48;
}else{
xSize = xSizeNoTip;
guiLeft = (this.width - this.xSize) / 2;
this.applyBtn.x = this.width / 2 + 48;
}
if(this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getHasStack()){
this.applyBtn.enabled = true;
}else{
this.applyBtn.enabled = false;
}
super.drawScreen(p_73863_1_, p_73863_2_, p_73863_3_);
// Required now, or item mouseover tooltips won't render.
this.renderHoveredToolTip(p_73863_1_, p_73863_2_);
} }
@Override @Override
public void drawGuiContainerBackgroundLayer(float f, int mouseX, int mouseY){ public void drawScreen(int mouseX, int mouseY, float partialTicks){
GlStateManager.color(1F, 1F, 1F, 1F); this.drawDefaultBackground();
GlStateManager.color(1, 1, 1, 1); // Just in case
Slot slot = this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT);
// Tests if there is a wand in the workbench and edits the positioning accordingly
if(slot.getHasStack() && slot.getStack().getItem() instanceof IWorkbenchItem
&& ((IWorkbenchItem)slot.getStack().getItem()).showTooltip(slot.getStack())){
xSize = MAIN_GUI_WIDTH + TOOLTIP_WIDTH;
guiLeft = (this.width - this.xSize) / 2;
this.applyBtn.x = (this.width - TOOLTIP_WIDTH) / 2 + 64;
}else{
xSize = MAIN_GUI_WIDTH;
guiLeft = (this.width - this.xSize) / 2;
this.applyBtn.x = this.width / 2 + 64;
}
this.applyBtn.enabled = slot.getHasStack();
super.drawScreen(mouseX, mouseY, partialTicks);
// Required now, or item mouseover tooltips won't render.
this.renderHoveredToolTip(mouseX, mouseY);
}
@Override
public void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY){
GlStateManager.color(1, 1, 1, 1);
Minecraft.getMinecraft().renderEngine.bindTexture(texture); Minecraft.getMinecraft().renderEngine.bindTexture(texture);
// Animation
// Grey background
DrawingUtils.drawTexturedRect(guiLeft + RUNE_LEFT, guiTop + RUNE_TOP, MAIN_GUI_WIDTH + TOOLTIP_WIDTH, 0,
RUNE_WIDTH, RUNE_HEIGHT, TEXTURE_WIDTH, TEXTURE_HEIGHT);
// Yellow 'halo'
if(animationTimer > 0){
GlStateManager.pushMatrix();
GlStateManager.enableBlend();
GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA);
int x = guiLeft + RUNE_LEFT + RUNE_WIDTH/2;
int y = guiTop + RUNE_TOP + RUNE_HEIGHT/2;
float scale = (animationTimer + partialTicks)/ANIMATION_DURATION;
scale = (float)(1 - Math.pow(1-scale, 1.4f)); // Makes it slower at the start and speed up
GlStateManager.scale(scale, scale, 1);
GlStateManager.translate(x/scale, y/scale, 0);
DrawingUtils.drawTexturedRect(-HALO_DIAMETER /2, -HALO_DIAMETER /2, MAIN_GUI_WIDTH + TOOLTIP_WIDTH, RUNE_HEIGHT,
HALO_DIAMETER, HALO_DIAMETER, TEXTURE_WIDTH, TEXTURE_HEIGHT);
GlStateManager.disableBlend();
GlStateManager.popMatrix();
}
// Main inventory // Main inventory
DrawingUtils.drawTexturedRect(guiLeft, guiTop, 0, 0, MAIN_GUI_WIDTH, ySize, TEXTURE_WIDTH, TEXTURE_HEIGHT); DrawingUtils.drawTexturedRect(guiLeft, guiTop, 0, 0, MAIN_GUI_WIDTH, ySize, TEXTURE_WIDTH, TEXTURE_HEIGHT);
float opacity = (animationTimer + partialTicks)/ANIMATION_DURATION;
// Changing slots // Changing slots
for(int i = 0; i < ContainerArcaneWorkbench.CRYSTAL_SLOT; i++){ for(int i = 0; i < ContainerArcaneWorkbench.CRYSTAL_SLOT; i++){
Slot slot = this.inventorySlots.getSlot(i); Slot slot = this.inventorySlots.getSlot(i);
if(slot.xPos >= 0 && slot.yPos >= 0)
this.drawTexturedModalRect(guiLeft + slot.xPos - 10, guiTop + slot.yPos - 10, 0, 220, 36, 36); if(slot.xPos >= 0 && slot.yPos >= 0){
// Slot background
DrawingUtils.drawTexturedRect(guiLeft + slot.xPos - 10, guiTop + slot.yPos - 10, 0, 220, 36, 36, TEXTURE_WIDTH, TEXTURE_HEIGHT);
// Slot animation
// IDEA: Somehow replace with intelligent check for whether the spell actually got applied
if(animationTimer > 0 && slot.getHasStack()){
GlStateManager.pushMatrix();
GlStateManager.enableBlend();
GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA);
GlStateManager.color(1, 1, 1, opacity);
DrawingUtils.drawTexturedRect(guiLeft + slot.xPos - 10, guiTop + slot.yPos - 10, 36, 220, 36, 36, TEXTURE_WIDTH, TEXTURE_HEIGHT);
GlStateManager.color(1, 1, 1, 1);
GlStateManager.disableBlend();
GlStateManager.popMatrix();
}
}
}
// Crystal + upgrade slot animations
if(animationTimer > 0){
Slot crystals = this.inventorySlots.getSlot(ContainerArcaneWorkbench.CRYSTAL_SLOT);
Slot upgrades = this.inventorySlots.getSlot(ContainerArcaneWorkbench.UPGRADE_SLOT);
if(crystals.getHasStack()){
GlStateManager.pushMatrix();
GlStateManager.enableBlend();
GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA);
GlStateManager.color(1, 1, 1, opacity);
DrawingUtils.drawTexturedRect(guiLeft + crystals.xPos - 8, guiTop + crystals.yPos - 8,
MAIN_GUI_WIDTH + TOOLTIP_WIDTH + RUNE_WIDTH, 0, 32, 32, TEXTURE_WIDTH, TEXTURE_HEIGHT);
GlStateManager.color(1, 1, 1, 1);
GlStateManager.disableBlend();
GlStateManager.popMatrix();
}
if(upgrades.getHasStack()){
GlStateManager.pushMatrix();
GlStateManager.enableBlend();
GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA);
GlStateManager.color(1, 1, 1, opacity);
DrawingUtils.drawTexturedRect(guiLeft + upgrades.xPos - 8, guiTop + upgrades.yPos - 8,
MAIN_GUI_WIDTH + TOOLTIP_WIDTH + RUNE_WIDTH, 0, 32, 32, TEXTURE_WIDTH, TEXTURE_HEIGHT);
GlStateManager.color(1, 1, 1, 1);
GlStateManager.disableBlend();
GlStateManager.popMatrix();
}
} }
// Tooltip only drawn if there is a wand // Tooltip only drawn if there is a wand
if(this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getHasStack() && this.inventorySlots if(this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getHasStack()){
.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getStack().getItem() instanceof ItemWand){
// Tooltip box ItemStack stack = this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getStack();
drawTexturedModalRect(guiLeft + xSizeNoTip, guiTop, xSizeNoTip, 0, 256 - xSizeNoTip - 4, ySize);
drawTexturedModalRect(guiLeft + 252, guiTop, xSizeNoTip + 4, 0, tooltipWidth - 2 * (256 - xSizeNoTip - 4), ySize);
drawTexturedModalRect(guiLeft + xSize - (256 - xSizeNoTip - 4), guiTop, xSizeNoTip + 4, 0,
256 - xSizeNoTip - 4, ySize);
ItemStack wand = this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getStack(); if(!(stack.getItem() instanceof IWorkbenchItem)){
Wizardry.logger.warn("Invalid item in central slot of arcane workbench, how did that get there?!");
Spell[] spells = WandHelper.getSpells(wand); return;
int i = 0;
for(Spell spell : spells){
boolean discovered = true;
if(!this.mc.player.capabilities.isCreativeMode && WizardData.get(this.mc.player) != null){
discovered = WizardData.get(this.mc.player).hasSpellBeenDiscovered(spell);
}
// As of Wizardry 1.2, the icons have been split off into their own texture files to allow for add-on
// mods to add their own.
Minecraft.getMinecraft().renderEngine
.bindTexture(discovered ? spell.element.getIcon() : Element.MAGIC.getIcon());
// Renders the little element icon
DrawingUtils.drawTexturedRect(guiLeft + MAIN_GUI_WIDTH + 5, guiTop + 34 + 10 * i++, 8, 8);
} }
int x = 0; if(((IWorkbenchItem)stack.getItem()).showTooltip(stack)){
int y = guiTop + 50 + spells.length * 10;
// Look how much shorter this is with the WandHelper class! // Tooltip box
for(Item item : WandHelper.getSpecialUpgrades()){ DrawingUtils.drawTexturedRect(guiLeft + MAIN_GUI_WIDTH, guiTop, MAIN_GUI_WIDTH, 0, TOOLTIP_WIDTH, ySize, TEXTURE_WIDTH, TEXTURE_HEIGHT);
int level = WandHelper.getUpgradeLevel(wand, item); int y = guiTop + 20;
if(level > 0){ if(stack.getItem() instanceof IManaStoringItem && ((IManaStoringItem)stack.getItem()).showManaInWorkbench(this.mc.player, stack)){
ItemStack stack = new ItemStack(item, level); y += 14;
GlStateManager.enableDepth(); }
this.itemRender.renderItemAndEffectIntoGUI(stack, guiLeft + xSizeNoTip + 6 + x, y);
this.itemRender.renderItemOverlayIntoGUI(this.fontRenderer, stack, guiLeft + xSizeNoTip + 6 + x, y, if(stack.getItem() instanceof ISpellCastingItem && ((ISpellCastingItem)stack.getItem()).showSpellsInWorkbench(this.mc.player, stack)){
null);
x += 18; Spell[] spells = ((ISpellCastingItem)stack.getItem()).getSpells(stack);
GlStateManager.disableDepth();
GlStateManager.enableBlend();
for(Spell spell : spells){
boolean discovered = true;
if(!this.mc.player.isCreative() && WizardData.get(this.mc.player) != null){
discovered = WizardData.get(this.mc.player).hasSpellBeenDiscovered(spell);
}
// As of Wizardry 1.2, the icons have been split off into their own texture files to allow for add-on
// mods to add their own.
Minecraft.getMinecraft().renderEngine
.bindTexture(discovered ? spell.getElement().getIcon() : Element.MAGIC.getIcon());
// Renders the little element icon
DrawingUtils.drawTexturedRect(guiLeft + MAIN_GUI_WIDTH + 5, y, 8, 8);
y += 10;
}
}
GlStateManager.disableBlend();
int x = 0;
y += 16;
// Look how much shorter this is with the WandHelper class!
for(Item item : WandHelper.getSpecialUpgrades()){
int level = WandHelper.getUpgradeLevel(stack, item);
if(level > 0){
ItemStack stack1 = new ItemStack(item, level);
GlStateManager.enableDepth();
this.itemRender.renderItemAndEffectIntoGUI(stack1, guiLeft + MAIN_GUI_WIDTH + 6 + x, y);
this.itemRender.renderItemOverlayIntoGUI(this.fontRenderer, stack1, guiLeft + MAIN_GUI_WIDTH + 6 + x, y,
null);
x += 18;
GlStateManager.disableDepth();
}
} }
} }
} }
Minecraft.getMinecraft().renderEngine.bindTexture(texture); Minecraft.getMinecraft().renderEngine.bindTexture(texture);
// Fixes the bug that caused the slot hightlight to render opaque. I don't know why it works, it just works! // Fixes the bug that caused the slot highlight to render opaque. I don't know why it works, it just works!
GlStateManager.disableBlend(); GlStateManager.disableBlend();
GlStateManager.enableAlpha(); GlStateManager.enableAlpha();
} }
@@ -157,64 +290,86 @@ public class GuiArcaneWorkbench extends GuiContainer {
@Override @Override
protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY){ protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY){
GlStateManager.color(1, 1, 1, 1); // Just in case
this.fontRenderer this.fontRenderer
.drawString(this.arcaneWorkbenchInventory.hasCustomName() ? this.arcaneWorkbenchInventory.getName() .drawString(this.arcaneWorkbenchInventory.hasCustomName() ? this.arcaneWorkbenchInventory.getName()
: I18n.format(this.arcaneWorkbenchInventory.getName()), 8, 6, 4210752); : I18n.format(this.arcaneWorkbenchInventory.getName()), 8, 6, 4210752);
this.fontRenderer.drawString(this.playerInventory.hasCustomName() ? this.playerInventory.getName() 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()), 8, this.ySize - 96 + 2, 4210752);
if(this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getHasStack() && this.inventorySlots if(this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getHasStack()){
.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getStack().getItem() instanceof ItemWand){
ItemStack wand = this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getStack(); ItemStack stack = this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getStack();
this.fontRenderer.drawStringWithShadow("\u00A7f" + wand.getDisplayName(), xSizeNoTip + 6, 6, 0); if(!(stack.getItem() instanceof IWorkbenchItem)){
this.fontRenderer.drawStringWithShadow( Wizardry.logger.warn("Invalid item in central slot of arcane workbench, how did that get there?!");
"\u00A77" + I18n.format("container." + Wizardry.MODID + ":arcane_workbench.mana") + " " return;
+ (wand.getMaxDamage() - wand.getItemDamage()) + "/" + wand.getMaxDamage(),
xSizeNoTip + 6, 20, 0);
Spell[] spells = WandHelper.getSpells(wand);
int y = 34;
for(Spell spell : spells){
boolean discovered = true;
if(!this.mc.player.capabilities.isCreativeMode && WizardData.get(this.mc.player) != null){
discovered = WizardData.get(this.mc.player).hasSpellBeenDiscovered(spell);
}
if(discovered){
this.fontRenderer.drawStringWithShadow(spell.getDisplayNameWithFormatting(), xSizeNoTip + 16, y, 0);
}else{
this.mc.standardGalacticFontRenderer.drawStringWithShadow(
"\u00A79" + SpellGlyphData.getGlyphName(spell, this.mc.world), xSizeNoTip + 16, y, 0);
}
y += 10;
} }
if(WandHelper.getTotalUpgrades(wand) > 0){ if(((IWorkbenchItem)stack.getItem()).showTooltip(stack)){
this.fontRenderer.drawStringWithShadow( int y = 6;
"\u00A7f" + I18n.format("container." + Wizardry.MODID + ":arcane_workbench.upgrades"), xSizeNoTip + 6, y + 6, 0);
int x = 0; this.fontRenderer.drawStringWithShadow("\u00A7f" + stack.getDisplayName(), MAIN_GUI_WIDTH + 6, y, 0);
y = 50 + spells.length * 10;
// Wand upgrade tooltips
for(Item item : WandHelper.getSpecialUpgrades()){
int level = WandHelper.getUpgradeLevel(wand, item); if(stack.getItem() instanceof IManaStoringItem && ((IManaStoringItem)stack.getItem()).showManaInWorkbench(this.mc.player, stack)){
y += 14;
this.fontRenderer.drawStringWithShadow(
"\u00A77" + I18n.format("container." + Wizardry.MODID + ":arcane_workbench.mana")
+ " " + ((IManaStoringItem)stack.getItem()).getMana(stack) + "/"
+ ((IManaStoringItem)stack.getItem()).getManaCapacity(stack),
MAIN_GUI_WIDTH + 6, y, 0);
}
if(level > 0){ y += 14;
// The javadoc for isPointInRegion is ambiguous; what it means is that the REGION is
// relative to the GUI but the POINT isn't. if(stack.getItem() instanceof ISpellCastingItem && ((ISpellCastingItem)stack.getItem()).showSpellsInWorkbench(this.mc.player, stack)){
if(isPointInRegion(xSizeNoTip + 6 + x, y, 16, 16, mouseX, mouseY)){
ItemStack stack = new ItemStack(item, level); Spell[] spells = ((ISpellCastingItem)stack.getItem()).getSpells(stack);
this.renderToolTip(stack, mouseX - guiLeft, mouseY - guiTop);
for(Spell spell : spells){
boolean discovered = true;
if(!this.mc.player.isCreative() && WizardData.get(this.mc.player) != null){
discovered = WizardData.get(this.mc.player).hasSpellBeenDiscovered(spell);
}
if(discovered){
this.fontRenderer.drawStringWithShadow(spell.getDisplayNameWithFormatting(), MAIN_GUI_WIDTH + 16, y, 0);
}else{
this.mc.standardGalacticFontRenderer.drawStringWithShadow(
"\u00A79" + SpellGlyphData.getGlyphName(spell, this.mc.world), MAIN_GUI_WIDTH + 16, y, 0);
}
y += 10;
}
}
if(WandHelper.getTotalUpgrades(stack) > 0){
y += 6;
this.fontRenderer.drawStringWithShadow("\u00A7f" + I18n.format("container."
+ Wizardry.MODID + ":arcane_workbench.upgrades"), MAIN_GUI_WIDTH + 6, y, 0);
int x = 0;
y += 10;
// Wand upgrade tooltips
for(Item item : WandHelper.getSpecialUpgrades()){
int level = WandHelper.getUpgradeLevel(stack, item);
if(level > 0){
// The javadoc for isPointInRegion is ambiguous; what it means is that the REGION is
// relative to the GUI but the POINT isn't.
if(isPointInRegion(MAIN_GUI_WIDTH + 6 + x, y, 16, 16, mouseX, mouseY)){
ItemStack stack1 = new ItemStack(item, level);
this.renderToolTip(stack1, mouseX - guiLeft, mouseY - guiTop);
}
x += 18;
} }
x += 18;
} }
} }
} }
@@ -228,7 +383,7 @@ public class GuiArcaneWorkbench extends GuiContainer {
this.guiTop = (this.height - this.ySize) / 2; this.guiTop = (this.height - this.ySize) / 2;
Keyboard.enableRepeatEvents(true); Keyboard.enableRepeatEvents(true);
this.buttonList.clear(); this.buttonList.clear();
this.buttonList.add(this.applyBtn = new GuiButtonApply(0, this.width / 2 + 48, this.height / 2 + 3)); this.buttonList.add(this.applyBtn = new GuiButtonApply(0, this.width / 2 + 64, this.height / 2 + 3));
} }
@Override @Override
@@ -244,8 +399,51 @@ public class GuiArcaneWorkbench extends GuiContainer {
// Packet building // Packet building
IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.APPLY_BUTTON); IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.APPLY_BUTTON);
WizardryPacketHandler.net.sendToServer(msg); WizardryPacketHandler.net.sendToServer(msg);
// Sound
Minecraft.getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(
WizardrySounds.BLOCK_ARCANE_WORKBENCH_SPELLBIND, 1));
// Animation
animationTimer = 20;
} }
} }
} }
private class GuiButtonApply extends GuiButton {
public GuiButtonApply(int id, int x, int y){
super(id, x, y, 16, 16, I18n.format("container." + Wizardry.MODID + ":arcane_workbench.apply"));
}
@Override
public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partialTicks){
// Whether the button is highlighted
this.hovered = mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height;
int k = 72;
int l = 220;
//int colour = 14737632;
if(this.enabled){
if(this.hovered){
k += this.width * 2;
//colour = 16777120;
}
}else{
k += this.width;
//colour = 10526880;
}
DrawingUtils.drawTexturedRect(this.x, this.y, k, l, this.width, this.height, 512, 256);
//this.drawCenteredString(minecraft.fontRenderer, this.displayString, this.x + this.width / 2,
// this.y + (this.height - 8) / 2, colour);
}
}
@SubscribeEvent
public static void onTextureStitchEvent(TextureStitchEvent.Pre event){
event.getMap().registerSprite(ContainerArcaneWorkbench.EMPTY_SLOT_CRYSTAL);
event.getMap().registerSprite(ContainerArcaneWorkbench.EMPTY_SLOT_UPGRADE);
}
} }
@@ -2,10 +2,8 @@ package electroblob.wizardry.client.gui;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiButton;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT) //@SideOnly(Side.CLIENT)
public class GuiButtonInvisible extends GuiButton { public class GuiButtonInvisible extends GuiButton {
public GuiButtonInvisible(int id, int x, int y, int width, int height){ public GuiButtonInvisible(int id, int x, int y, int width, int height){
@@ -0,0 +1,90 @@
package electroblob.wizardry.client.gui;
import electroblob.wizardry.item.ISpellCastingItem;
import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.packet.PacketControlInput;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.spell.Resurrection;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiGameOver;
import net.minecraft.client.resources.I18n;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumHand;
import net.minecraftforge.client.event.GuiScreenEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import net.minecraftforge.fml.relauncher.Side;
@Mod.EventBusSubscriber(Side.CLIENT)
public class GuiButtonResurrect extends GuiButton {
private static int timeSinceDeath = -1;
private final String translationKey;
public GuiButtonResurrect(int id, int x, int y, String translationKey){
super(id, x, y, I18n.format(translationKey + "_wait", Resurrection.getRemainingWaitTime(timeSinceDeath)));
this.translationKey = translationKey;
}
@Override
public void drawButton(Minecraft mc, int mouseX, int mouseY, float partialTicks){
int waitTime = Resurrection.getRemainingWaitTime(timeSinceDeath);
this.enabled = waitTime == 0;
this.displayString = I18n.format(translationKey + (waitTime == 0 ? "_ready" : "_wait"), waitTime);
super.drawButton(mc, mouseX, mouseY, partialTicks);
}
// Event handlers
@SubscribeEvent
public static void onClientTickEvent(TickEvent.ClientTickEvent event){
if(event.phase == TickEvent.Phase.START && timeSinceDeath >= 0) timeSinceDeath++;
}
@SubscribeEvent
public static void onGuiScreenInitEvent(GuiScreenEvent.InitGuiEvent event){
if(event.getGui() instanceof GuiGameOver && ItemArtefact.isArtefactActive(Minecraft.getMinecraft().player, WizardryItems.amulet_resurrection)
&& WizardryUtilities.getHotbar(Minecraft.getMinecraft().player).stream().anyMatch(s -> Resurrection.canStackResurrect(s, Minecraft.getMinecraft().player))){
event.getButtonList().add(new GuiButtonResurrect(event.getButtonList().size(), event.getGui().width / 2 - 100,
event.getGui().height / 4 + 120, "spell." + Spells.resurrection.getRegistryName() + ".button"));
timeSinceDeath = 0;
}
}
@SubscribeEvent
public static void onGuiScreenActionPerformedEvent(GuiScreenEvent.ActionPerformedEvent event){
if(event.getGui() instanceof GuiGameOver){
ItemStack stack = WizardryUtilities.getHotbar(Minecraft.getMinecraft().player).stream()
.filter(s -> Resurrection.canStackResurrect(s, Minecraft.getMinecraft().player)).findFirst().orElse(null);
if(stack != null){
if(event.getButton() instanceof GuiButtonResurrect && timeSinceDeath >= 0){
// Cast resurrection on the client player and notify the server to do the same
// ISpellCastingItem#canCast already checked in Resurrection#canStackResurrect
((ISpellCastingItem)stack.getItem()).cast(stack, Spells.resurrection, Minecraft.getMinecraft().player, EnumHand.MAIN_HAND, 0, new SpellModifiers());
WizardryPacketHandler.net.sendToServer(new PacketControlInput.Message(PacketControlInput.ControlType.RESURRECT_BUTTON));
}else if(!Minecraft.getMinecraft().world.getGameRules().getBoolean("keepInventory")){
// Any other button drops the wand (N.B. this should be inside the stack != null check or it'll send
// packets unnecessarily and generate incorrect warnings
WizardryPacketHandler.net.sendToServer(new PacketControlInput.Message(PacketControlInput.ControlType.CANCEL_RESURRECT));
}
timeSinceDeath = -1;
}
}
}
}
@@ -1,25 +1,34 @@
package electroblob.wizardry.client.gui; package electroblob.wizardry.client.gui;
import org.lwjgl.input.Keyboard; import com.google.common.collect.ImmutableMap;
import electroblob.wizardry.SpellGlyphData;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry; import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.DrawingUtils;
import electroblob.wizardry.constants.Tier; import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.data.SpellGlyphData;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.spell.Spell; import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.PositionedSoundRecord;
import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import org.lwjgl.input.Keyboard;
import java.util.Map;
public class GuiSpellBook extends GuiScreen { public class GuiSpellBook extends GuiScreen {
private int xSize, ySize; private int xSize, ySize;
private Spell spell; private Spell spell;
private static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID, "textures/gui/spellbook.png"); private static final Map<Tier, ResourceLocation> textures = ImmutableMap.of(
Tier.NOVICE, new ResourceLocation(Wizardry.MODID, "textures/gui/spell_book_novice.png"),
Tier.APPRENTICE, new ResourceLocation(Wizardry.MODID, "textures/gui/spell_book_apprentice.png"),
Tier.ADVANCED, new ResourceLocation(Wizardry.MODID, "textures/gui/spell_book_advanced.png"),
Tier.MASTER, new ResourceLocation(Wizardry.MODID, "textures/gui/spell_book_master.png"));
public GuiSpellBook(Spell spell){ public GuiSpellBook(Spell spell){
super(); super();
@@ -39,45 +48,47 @@ public class GuiSpellBook extends GuiScreen {
EntityPlayer player = Minecraft.getMinecraft().player; EntityPlayer player = Minecraft.getMinecraft().player;
boolean discovered = true; boolean discovered = true;
if(Wizardry.settings.discoveryMode && !player.capabilities.isCreativeMode && WizardData.get(player) != null if(Wizardry.settings.discoveryMode && !player.isCreative() && WizardData.get(player) != null
&& !WizardData.get(player).hasSpellBeenDiscovered(spell)){ && !WizardData.get(player).hasSpellBeenDiscovered(spell)){
discovered = false; 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. // 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()); Minecraft.getMinecraft().renderEngine.bindTexture(discovered ? spell.getIcon() : Spells.none.getIcon());
DrawingUtils.drawTexturedRect(xPos + 146, yPos + 20, 0, 0, 128, 128, 128, 128); DrawingUtils.drawTexturedRect(xPos + 146, yPos + 20, 0, 0, 128, 128, 128, 128);
Minecraft.getMinecraft().renderEngine.bindTexture(textures.get(spell.tier)); Minecraft.getMinecraft().renderEngine.bindTexture(textures.get(spell.getTier()));
DrawingUtils.drawTexturedRect(xPos, yPos, 0, 0, xSize, ySize, xSize, 256); DrawingUtils.drawTexturedRect(xPos, yPos, 0, 0, xSize, ySize, xSize, 256);
super.drawScreen(par1, par2, par3); super.drawScreen(par1, par2, par3);
if(discovered){ if(discovered){
this.fontRenderer.drawString(spell.getDisplayName(), xPos + 17, yPos + 15, 0); this.fontRenderer.drawString(spell.getDisplayName(), xPos + 17, yPos + 15, 0);
this.fontRenderer.drawString(spell.type.getDisplayName(), xPos + 17, yPos + 26, 0x777777); this.fontRenderer.drawString(spell.getType().getDisplayName(), xPos + 17, yPos + 26, 0x777777);
}else{ }else{
this.mc.standardGalacticFontRenderer.drawString(SpellGlyphData.getGlyphName(spell, player.world), xPos + 17, this.mc.standardGalacticFontRenderer.drawString(SpellGlyphData.getGlyphName(spell, player.world), xPos + 17,
yPos + 15, 0); yPos + 15, 0);
this.mc.standardGalacticFontRenderer.drawString(spell.type.getDisplayName(), xPos + 17, yPos + 26, this.mc.standardGalacticFontRenderer.drawString(spell.getType().getDisplayName(), xPos + 17, yPos + 26,
0x777777); 0x777777);
} }
this.fontRenderer.drawString("-------------------", xPos + 17, yPos + 35, 0); //this.fontRenderer.drawString("-------------------", xPos + 17, yPos + 35, 0);
if(spell.tier == Tier.BASIC){ if(spell.getTier() == Tier.NOVICE){
// Basic is usually white but this doesn't show up. // Basic is usually white but this doesn't show up.
this.fontRenderer.drawString("Tier: \u00A77" + Tier.BASIC.getDisplayName(), xPos + 17, yPos + 45, 0); this.fontRenderer.drawString("Tier: \u00A77" + Tier.NOVICE.getDisplayName(), xPos + 17, yPos + 45, 0);
}else{ }else{
this.fontRenderer.drawString("Tier: " + spell.tier.getDisplayNameWithFormatting(), xPos + 17, yPos + 45, 0); this.fontRenderer.drawString("Tier: " + spell.getTier().getDisplayNameWithFormatting(), xPos + 17, yPos + 45, 0);
} }
String element = "Element: " + spell.element.getFormattingCode() + spell.element.getDisplayName(); String element = "Element: " + spell.getElement().getFormattingCode() + spell.getElement().getDisplayName();
if(!discovered) element = "Element: ?"; if(!discovered) element = "Element: ?";
this.fontRenderer.drawString(element, xPos + 17, yPos + 57, 0); this.fontRenderer.drawString(element, xPos + 17, yPos + 57, 0);
String manaCost = "Mana Cost: " + spell.cost; String manaCost = "Mana Cost: " + spell.getCost();
if(spell.isContinuous) manaCost = "Mana Cost: " + spell.cost + "/second"; if(spell.isContinuous) manaCost = "Mana Cost: " + spell.getCost() + "/second";
if(!discovered) manaCost = "Mana Cost: ?"; if(!discovered) manaCost = "Mana Cost: ?";
this.fontRenderer.drawString(manaCost, xPos + 17, yPos + 69, 0); this.fontRenderer.drawString(manaCost, xPos + 17, yPos + 69, 0);
@@ -93,10 +104,18 @@ public class GuiSpellBook extends GuiScreen {
super.initGui(); super.initGui();
Keyboard.enableRepeatEvents(true); Keyboard.enableRepeatEvents(true);
this.buttonList.clear(); this.buttonList.clear();
this.mc.getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.MISC_BOOK_OPEN, 1));
} }
public void onGuiClosed(){ public void onGuiClosed(){
super.onGuiClosed(); super.onGuiClosed();
Keyboard.enableRepeatEvents(false); Keyboard.enableRepeatEvents(false);
} }
@Override
public boolean doesGuiPauseGame(){
return Wizardry.settings.booksPauseGame;
}
} }
@@ -1,30 +1,17 @@
package electroblob.wizardry.client.gui; package electroblob.wizardry.client.gui;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import electroblob.wizardry.Settings; import electroblob.wizardry.Settings;
import electroblob.wizardry.SpellGlyphData;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry; import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.ClientProxy; import electroblob.wizardry.client.ClientProxy;
import electroblob.wizardry.client.DrawingUtils; import electroblob.wizardry.client.DrawingUtils;
import electroblob.wizardry.client.MixedFontRenderer; import electroblob.wizardry.client.MixedFontRenderer;
import electroblob.wizardry.constants.Constants; import electroblob.wizardry.data.SpellGlyphData;
import electroblob.wizardry.item.ItemWand; import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.item.ISpellCastingItem;
import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.spell.Spell; import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.WandHelper; import electroblob.wizardry.util.WandHelper;
@@ -37,14 +24,22 @@ import net.minecraft.client.resources.IResource;
import net.minecraft.client.resources.IResourceManager; import net.minecraft.client.resources.IResourceManager;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumHandSide;
import net.minecraft.util.JsonUtils; import net.minecraft.util.JsonUtils;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper;
import net.minecraftforge.client.event.RenderGameOverlayEvent; import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent; import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.Side;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
import java.util.Map.Entry;
@Mod.EventBusSubscriber(Side.CLIENT) @Mod.EventBusSubscriber(Side.CLIENT)
public class GuiSpellDisplay { public class GuiSpellDisplay {
@@ -53,8 +48,9 @@ public class GuiSpellDisplay {
/** A map which stores all loaded HUD skin objects. This gets wiped on resource pack reload and repopulated with /** 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 * 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 * to the keys in {@code _index.json}, and are sorted in that order, with skins belonging to resource packs sorted
* from lowest to highest priority. The skins in the base mod will therefore always be first. */ * from lowest to highest priority. The skins in the base mod will therefore always be first. (It should be noted,
private static final Map<String, Skin> skins = new LinkedHashMap<>(12); // 12 is the number of skins packaged with the mod * however, that in the gui itself the skins are always sorted in alphabetical order for some reason.) */
private static final Map<String, Skin> skins = new LinkedHashMap<>(14); // 14 is the number of skins packaged with the mod
private static final Gson gson = new Gson(); private static final Gson gson = new Gson();
@@ -66,6 +62,9 @@ public class GuiSpellDisplay {
private static final float SPELL_NAME_SCALE = 0.5f; private static final float SPELL_NAME_SCALE = 0.5f;
/** Opacity of the next/previous spell names, as a fraction. */ /** Opacity of the next/previous spell names, as a fraction. */
private static final float SPELL_NAME_OPACITY = 0.3f; private static final float SPELL_NAME_OPACITY = 0.3f;
private static final int HALF_HOTBAR_WIDTH = 97; // Half the width of the hotbar, plus a bit for clearance
private static final int OFFHAND_SLOT_WIDTH = 29; // Width of the offhand slot plus the gap between it and the hotbar
/** Controls the spell switching animation. Positive when switching to the next spell, negative when switching to /** Controls the spell switching animation. Positive when switching to the next spell, negative when switching to
* the previous spell. Decremented in magnitude by 1 each tick until it reaches 0 again. */ * the previous spell. Decremented in magnitude by 1 each tick until it reaches 0 again. */
@@ -103,14 +102,18 @@ public class GuiSpellDisplay {
EntityPlayer player = mc.player; EntityPlayer player = mc.player;
if(player.isSpectator()) return; // Spectators shouldn't have the spell HUD!
// If the player has a wand in each hand, only displays for the one in the main hand. // If the player has a wand in each hand, only displays for the one in the main hand.
ItemStack wand = player.getHeldItemMainhand(); ItemStack wand = player.getHeldItemMainhand();
boolean mainHand = true;
if(!(wand.getItem() instanceof ItemWand)){ if(!(wand.getItem() instanceof ISpellCastingItem && ((ISpellCastingItem)wand.getItem()).showSpellHUD(player, wand))){
wand = player.getHeldItemOffhand(); wand = player.getHeldItemOffhand();
// If the player isn't holding a wand, then nothing else needs to be done. mainHand = false;
if(!(wand.getItem() instanceof ItemWand)) return; // If the player isn't holding a spellcasting item that shows the HUD, then nothing else needs to be done.
if(!(wand.getItem() instanceof ISpellCastingItem && ((ISpellCastingItem)wand.getItem()).showSpellHUD(player, wand))) return;
} }
int width = event.getResolution().getScaledWidth(); int width = event.getResolution().getScaledWidth();
@@ -118,6 +121,11 @@ public class GuiSpellDisplay {
boolean flipX = Wizardry.settings.spellHUDPosition.flipX; boolean flipX = Wizardry.settings.spellHUDPosition.flipX;
boolean flipY = Wizardry.settings.spellHUDPosition.flipY; boolean flipY = Wizardry.settings.spellHUDPosition.flipY;
if(Wizardry.settings.spellHUDPosition.dynamic){
// ............. | This bit is true if the wand is on the left, false if it is on the right
flipX = flipX == ((mainHand ? player.getPrimaryHand() : player.getPrimaryHand().opposite()) == EnumHandSide.LEFT);
}
Skin skin = skins.get(Wizardry.settings.spellHUDSkin); Skin skin = skins.get(Wizardry.settings.spellHUDSkin);
@@ -134,13 +142,32 @@ public class GuiSpellDisplay {
return; return;
} }
} }
GlStateManager.pushMatrix();
// 'Origin' of the spell hud (bottom left corner of the actual texture, always in the corner of the screen) // '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 x = flipX ? width : 0;
int y = flipY ? 0: height; int y = flipY ? 0: height;
// The space available to render the spell HUD
float xSpace = (float)(width/2 - HALF_HOTBAR_WIDTH);
if(!player.getHeldItemOffhand().isEmpty()
// Tests whether the offhand slot is rendered on the same side of the hotbar as the spell HUD
&& (player.getPrimaryHand() == EnumHandSide.LEFT) == flipX){
xSpace -= OFFHAND_SLOT_WIDTH;
}
// If the skin is at the bottom and the screen width is too small, scale it to avoid the hotbar and offhand
if(!flipY && skin.getWidth() > xSpace){ // width/2 - 91 - 29 taken from GuiInGame line 547
float scale = xSpace / skin.getWidth();
GlStateManager.scale(scale, scale, 1);
x = MathHelper.ceil(x/scale);
y = MathHelper.ceil(y/scale);
}
Spell spell = WandHelper.getCurrentSpell(wand); Spell spell = WandHelper.getCurrentSpell(wand);
int cooldown = WandHelper.getCurrentCooldown(wand); int cooldown = WandHelper.getCurrentCooldown(wand);
int maxCooldown = WandHelper.getCurrentMaxCooldown(wand);
if(event.getType() == RenderGameOverlayEvent.ElementType.TEXT){ if(event.getType() == RenderGameOverlayEvent.ElementType.TEXT){
@@ -155,16 +182,9 @@ public class GuiSpellDisplay {
}else if(event.getType() == RenderGameOverlayEvent.ElementType.HOTBAR){ }else if(event.getType() == RenderGameOverlayEvent.ElementType.HOTBAR){
float cooldownMultiplier = 1.0f - WandHelper.getUpgradeLevel(wand, WizardryItems.cooldown_upgrade) * Constants.COOLDOWN_REDUCTION_PER_LEVEL;
if(player.isPotionActive(WizardryPotions.font_of_mana)){
// Dividing by this rather than setting it takes upgrades and font of mana into account simultaneously
cooldownMultiplier /= 2 + player.getActivePotionEffect(WizardryPotions.font_of_mana).getAmplifier();
}
boolean discovered = true; boolean discovered = true;
if(!player.capabilities.isCreativeMode && WizardData.get(player) != null){ if(!player.isCreative() && WizardData.get(player) != null){
discovered = WizardData.get(player).hasSpellBeenDiscovered(spell); discovered = WizardData.get(player).hasSpellBeenDiscovered(spell);
} }
@@ -172,31 +192,23 @@ public class GuiSpellDisplay {
float progress = 1; float progress = 1;
// Doesn't really matter what progress is when in creative, but we might as well avoid the calculation. // Doesn't really matter what progress is when in creative, but we might as well avoid the calculation.
if(!player.capabilities.isCreativeMode && !spell.isContinuous){ if(!player.isCreative() && !spell.isContinuous){
// Subtracted partial tick time to make it smoother // Subtracted partial tick time to make it smoother
progress = (spell.cooldown * cooldownMultiplier - (float)cooldown + event.getPartialTicks()) progress = maxCooldown == 0 ? 1 : (maxCooldown - (float)cooldown + event.getPartialTicks()) / maxCooldown;
/(spell.cooldown * cooldownMultiplier);
} }
skin.drawBackground(x, y, flipX, flipY, icon, progress, player.capabilities.isCreativeMode); skin.drawBackground(x, y, flipX, flipY, icon, progress, player.isCreative());
} }
GlStateManager.popMatrix();
} }
/** /**
* Makes the given opaque colour translucent with the given opacity. * Gets the name of the given spell, with formatting added according to its cooldown and whether the given player
* @param colour An integer colour code, should be a 6-digit hexadecimal (i.e. opaque).
* @param opacity The opacity to apply to the given colour, as a fraction between 0 and 1.
* @return The resulting integer colour code, which will be an 8-digit hexadecimal.
*/
private static int makeTranslucent(int colour, float opacity){
return colour + ((int)(0xff * opacity * 0x01000000));
}
/** Gets the name of the given spell, with formatted added according to its cooldown and whether the given player
* has discovered it. * has discovered it.
* @param spell The spell to get the name of. * @param spell The spell to get the name of.
* @param The player to test for having discovered the given spell. * @param player The player to test for having discovered the given spell.
* @param cooldown The spell's current cooldown. * @param cooldown The spell's current cooldown.
* @return The spell name, with relevant formatting added, for use with the {@link MixedFontRenderer}. * @return The spell name, with relevant formatting added, for use with the {@link MixedFontRenderer}.
*/ */
@@ -204,12 +216,12 @@ public class GuiSpellDisplay {
boolean discovered = true; boolean discovered = true;
if(!player.capabilities.isCreativeMode && WizardData.get(player) != null){ if(!player.isCreative() && WizardData.get(player) != null){
discovered = WizardData.get(player).hasSpellBeenDiscovered(spell); discovered = WizardData.get(player).hasSpellBeenDiscovered(spell);
} }
// Makes spells greyed out if they are in cooldown or if the player has the arcane jammer effect // 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.element.getFormattingCode(); String format = cooldown > 0 || player.isPotionActive(WizardryPotions.arcane_jammer) ? "\u00A78" : spell.getElement().getFormattingCode();
if(!discovered) format = "\u00A79"; if(!discovered) format = "\u00A79";
String name = discovered ? spell.getDisplayName() : SpellGlyphData.getGlyphName(spell, player.world); String name = discovered ? spell.getDisplayName() : SpellGlyphData.getGlyphName(spell, player.world);
@@ -274,10 +286,15 @@ public class GuiSpellDisplay {
} }
} }
/** Instances of this class represent individual HUD skins, complete with texture and all necessary metadata. This /**
* Instances of this class represent individual HUD skins, complete with texture and all necessary metadata. This
* class serves to separate the logic behind the spell HUD from its actual rendering. * class serves to separate the logic behind the spell HUD from its actual rendering.
* All information and processing done within this class relates only to the actual drawing; spells and such like * All information and processing done within this class relates only to the actual drawing; spells and such like
* must be queried outside of this class and fed into the methods as appropriate. */ * must be queried outside of this class and fed into the methods as appropriate.
*
* @author Electroblob
* @since Wizardry 4.2
*/
public static class Skin { public static class Skin {
/** The texture file for this skin. */ /** The texture file for this skin. */
@@ -1,21 +1,17 @@
package electroblob.wizardry.client; package electroblob.wizardry.client.gui.config;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.I18n; import net.minecraft.client.resources.I18n;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.client.config.GuiButtonExt; import net.minecraftforge.fml.client.config.*;
import net.minecraftforge.fml.client.config.GuiEditArray;
import net.minecraftforge.fml.client.config.GuiEditArrayEntries;
import net.minecraftforge.fml.client.config.GuiEditArrayEntries.StringEntry; import net.minecraftforge.fml.client.config.GuiEditArrayEntries.StringEntry;
import net.minecraftforge.fml.client.config.GuiSelectString;
import net.minecraftforge.fml.client.config.IConfigElement;
import net.minecraftforge.fml.common.registry.EntityEntry; import net.minecraftforge.fml.common.registry.EntityEntry;
import net.minecraftforge.fml.common.registry.ForgeRegistries; import net.minecraftforge.fml.common.registry.ForgeRegistries;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
/** /**
* [NYI] Intended as a way of choosing entities by name from all those currently registered, within the config file, so * [NYI] Intended as a way of choosing entities by name from all those currently registered, within the config file, so
* that users don't have to look up the entity IDs. I can't get this to work correctly at the moment. * that users don't have to look up the entity IDs. I can't get this to work correctly at the moment.
@@ -1,7 +1,4 @@
package electroblob.wizardry.client.gui; package electroblob.wizardry.client.gui.config;
import java.util.ArrayList;
import java.util.List;
import electroblob.wizardry.Settings; import electroblob.wizardry.Settings;
import electroblob.wizardry.Wizardry; import electroblob.wizardry.Wizardry;
@@ -15,6 +12,9 @@ import net.minecraftforge.fml.client.config.GuiConfigEntries;
import net.minecraftforge.fml.client.config.GuiConfigEntries.CategoryEntry; import net.minecraftforge.fml.client.config.GuiConfigEntries.CategoryEntry;
import net.minecraftforge.fml.client.config.IConfigElement; import net.minecraftforge.fml.client.config.IConfigElement;
import java.util.ArrayList;
import java.util.List;
public class GuiConfigWizardry extends GuiConfig { public class GuiConfigWizardry extends GuiConfig {
public GuiConfigWizardry(GuiScreen parent){ public GuiConfigWizardry(GuiScreen parent){
@@ -33,7 +33,8 @@ public class GuiConfigWizardry extends GuiConfig {
configList.add(new DummyCategoryElement("clientConfig", "config." + Wizardry.MODID + ".category." + Settings.CLIENT_CATEGORY, ClientCategory.class)); configList.add(new DummyCategoryElement("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("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("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()); configList.addAll(new ConfigElement(Wizardry.settings.getConfigCategory(Configuration.CATEGORY_GENERAL)).getChildElements());
return configList; return configList;
@@ -41,7 +42,7 @@ public class GuiConfigWizardry extends GuiConfig {
// The reason this system is so convoluted is that it's designed for use with the @Config annotation. The problem is, // The reason this system is so convoluted is that it's designed for use with the @Config annotation. The problem is,
// I'm not sure whether that will play well with the load phases. Hmmm... // I'm not sure whether that will play well with the load phases. Hmmm...
public static abstract class CategoryBase extends CategoryEntry { public static abstract class CategoryBase extends CategoryEntry {
public CategoryBase(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){ public CategoryBase(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
@@ -77,6 +78,36 @@ public class GuiConfigWizardry extends GuiConfig {
@Override protected String getCategory() { return Settings.GAMEPLAY_CATEGORY; } @Override protected String getCategory() { return Settings.GAMEPLAY_CATEGORY; }
} }
/** Worldgen category of the config gui. */
public static class WorldgenCategory extends CategoryBase {
public WorldgenCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
super(owningScreen, owningEntryList, prop);
}
@Override protected String getCategory() { return Settings.WORLDGEN_CATEGORY; }
}
/** Commands category of the config gui. */
public static class CommandsCategory extends CategoryBase {
public CommandsCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
super(owningScreen, owningEntryList, prop);
}
@Override protected String getCategory() { return Settings.COMMANDS_CATEGORY; }
}
/** Client category of the config gui. */
public static class ClientCategory extends CategoryBase {
public ClientCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
super(owningScreen, owningEntryList, prop);
}
@Override protected String getCategory() { return Settings.CLIENT_CATEGORY; }
}
/** Spells category of the config gui. */ /** Spells category of the config gui. */
public static class SpellsCategory extends CategoryBase { public static class SpellsCategory extends CategoryBase {
@@ -97,34 +128,14 @@ public class GuiConfigWizardry extends GuiConfig {
@Override protected String getCategory() { return Settings.RESISTANCES_CATEGORY; } @Override protected String getCategory() { return Settings.RESISTANCES_CATEGORY; }
} }
/** Worldgen category of the config gui. */
public static class WorldgenCategory extends CategoryBase {
public WorldgenCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
super(owningScreen, owningEntryList, prop);
}
@Override protected String getCategory() { return Settings.WORLDGEN_CATEGORY; }
}
/** Client category of the config gui. */
public static class ClientCategory extends CategoryBase {
public ClientCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
super(owningScreen, owningEntryList, prop);
}
@Override protected String getCategory() { return Settings.CLIENT_CATEGORY; }
}
/** Commands category of the config gui. */ /** Commands category of the config gui. */
public static class CommandsCategory extends CategoryBase { public static class CompatibilityCategory extends CategoryBase {
public CommandsCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){ public CompatibilityCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
super(owningScreen, owningEntryList, prop); super(owningScreen, owningEntryList, prop);
} }
@Override protected String getCategory() { return Settings.COMMANDS_CATEGORY; } @Override protected String getCategory() { return Settings.COMPATIBILITY_CATEGORY; }
} }
} }
@@ -1,12 +1,8 @@
package electroblob.wizardry.client.gui; package electroblob.wizardry.client.gui.config;
import java.util.Map;
import javax.annotation.Nullable;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import electroblob.wizardry.Wizardry; import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.gui.GuiSpellDisplay;
import electroblob.wizardry.client.gui.GuiSpellDisplay.Skin; import electroblob.wizardry.client.gui.GuiSpellDisplay.Skin;
import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.Spells;
import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiButton;
@@ -16,6 +12,9 @@ import net.minecraft.client.resources.I18n;
import net.minecraftforge.fml.client.config.GuiSelectString; import net.minecraftforge.fml.client.config.GuiSelectString;
import net.minecraftforge.fml.client.config.IConfigElement; import net.minecraftforge.fml.client.config.IConfigElement;
import javax.annotation.Nullable;
import java.util.Map;
public class GuiSelectHUDSkin extends GuiSelectString { public class GuiSelectHUDSkin extends GuiSelectString {
public GuiSelectHUDSkin(GuiScreen parentScreen, IConfigElement configElement, int slotIndex, Map<Object, String> selectableValues, Object currentValue, boolean enabled){ public GuiSelectHUDSkin(GuiScreen parentScreen, IConfigElement configElement, int slotIndex, Map<Object, String> selectableValues, Object currentValue, boolean enabled){
@@ -50,7 +49,7 @@ public class GuiSelectHUDSkin extends GuiSelectString {
if(this.currentValue instanceof String){ if(this.currentValue instanceof String){
this.drawString(this.fontRenderer, I18n.format("config." + Wizardry.MODID + ":spell_hud_skin.preview"), 170, 44, 0xffffff); this.drawString(this.fontRenderer, I18n.format("config." + Wizardry.MODID + ".spell_hud_skin.preview"), 170, 44, 0xffffff);
int previewLeft = 170; int previewLeft = 170;
int previewRight = width-10; int previewRight = width-10;
@@ -0,0 +1,93 @@
package electroblob.wizardry.client.gui.config;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.fml.client.config.GuiConfig;
import net.minecraftforge.fml.client.config.GuiConfigEntries;
import net.minecraftforge.fml.client.config.GuiUtils;
import net.minecraftforge.fml.client.config.IConfigElement;
/**
* Same as {@link net.minecraftforge.fml.client.config.GuiConfigEntries.BooleanEntry}, but instead of simply
* displaying 'true' or 'false', allows the two display strings to be specified in the lang file.
*/
// BooleanEntry's constructors are private, so I had to copy the whole goddamn class to change one method. Thanks Forge.
public class NamedBooleanEntry extends GuiConfigEntries.ButtonEntry {
protected final boolean beforeValue;
protected boolean currentValue;
private static final String DEFAULT_KEY = "config.ebwizardry.generic";
public NamedBooleanEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement configElement){
super(owningScreen, owningEntryList, configElement);
this.beforeValue = Boolean.valueOf(configElement.get().toString());
this.currentValue = beforeValue;
this.btnValue.enabled = enabled();
updateValueButtonText();
}
// This is the only method that's any different
@Override
public void updateValueButtonText(){
String langKey = configElement.getLanguageKey() + "." + currentValue;
this.btnValue.displayString = I18n.format(langKey);
// If the key is unspecified, it defaults to the generic 'Enabled'/'Disabled' keys and adds a red/green colour
if(this.btnValue.displayString.equals(langKey)){
this.btnValue.displayString = I18n.format(DEFAULT_KEY + "." + currentValue);
btnValue.packedFGColour = currentValue ? GuiUtils.getColorCode('a', true) : GuiUtils.getColorCode('c', true);
}
}
// Everything from here down is the same as BooleanEntry
@Override
public void valueButtonPressed(int slotIndex){
if(enabled()) currentValue = !currentValue;
}
@Override
public boolean isDefault(){
return currentValue == Boolean.valueOf(configElement.getDefault().toString());
}
@Override
public void setToDefault(){
if(enabled()){
currentValue = Boolean.valueOf(configElement.getDefault().toString());
updateValueButtonText();
}
}
@Override
public boolean isChanged(){
return currentValue != beforeValue;
}
@Override
public void undoChanges(){
if(enabled()){
currentValue = beforeValue;
updateValueButtonText();
}
}
@Override
public boolean saveConfigElement(){
if(enabled() && isChanged()){
configElement.set(currentValue);
return configElement.requiresMcRestart();
}
return false;
}
@Override
public Boolean getCurrentValue(){
return currentValue;
}
@Override
public Boolean[] getCurrentValues(){
return new Boolean[]{getCurrentValue()};
}
}
@@ -1,10 +1,5 @@
package electroblob.wizardry.client; package electroblob.wizardry.client.gui.config;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import electroblob.wizardry.client.gui.GuiSelectHUDSkin;
import electroblob.wizardry.client.gui.GuiSpellDisplay; import electroblob.wizardry.client.gui.GuiSpellDisplay;
import net.minecraftforge.client.gui.ForgeGuiFactory.ForgeConfigGui.ModIDEntry; import net.minecraftforge.client.gui.ForgeGuiFactory.ForgeConfigGui.ModIDEntry;
import net.minecraftforge.fml.client.config.GuiConfig; import net.minecraftforge.fml.client.config.GuiConfig;
@@ -12,17 +7,19 @@ import net.minecraftforge.fml.client.config.GuiConfigEntries;
import net.minecraftforge.fml.client.config.GuiConfigEntries.SelectValueEntry; import net.minecraftforge.fml.client.config.GuiConfigEntries.SelectValueEntry;
import net.minecraftforge.fml.client.config.IConfigElement; import net.minecraftforge.fml.client.config.IConfigElement;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
/** /**
* Custom config GUI for spell HUD skin selection; displays a list of all the loaded skins and a preview of the currently * Custom config GUI for spell HUD skin selection; displays a list of all the loaded skins and a preview of the currently
* selected skin. based off of {@link ModIDEntry} from Forge. * selected skin. based off of {@link ModIDEntry} from Forge.
*/ */
public class SpellHUDSkinChooserEntry extends SelectValueEntry { public class SpellHUDSkinChooserEntry extends SelectValueEntry {
public SpellHUDSkinChooserEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop) public SpellHUDSkinChooserEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
{
super(owningScreen, owningEntryList, prop, getSelectableValues()); super(owningScreen, owningEntryList, prop, getSelectableValues());
if (this.selectableValues.size() == 0) if(this.selectableValues.size() == 0) this.btnValue.enabled = false;
this.btnValue.enabled = false;
} }
private static Map<Object, String> getSelectableValues(){ private static Map<Object, String> getSelectableValues(){
@@ -27,6 +27,7 @@ class Contents {
// Final fields are mandatory, the rest are optional // Final fields are mandatory, the rest are optional
final String id; final String id;
final Section section;
private boolean hyperlinks = true; private boolean hyperlinks = true;
private boolean pageNumbers = true; private boolean pageNumbers = true;
private String separator = "."; private String separator = ".";
@@ -37,10 +38,14 @@ class Contents {
private final List<Section> entries; private final List<Section> entries;
private Contents(String id){ private List<Section> visibleEntries;
private Contents(String id, Section section){
this.id = id; this.id = id;
this.section = section;
this.entries = new ArrayList<>(); this.entries = new ArrayList<>();
this.buttons = new ArrayList<>(); this.buttons = new ArrayList<>();
this.visibleEntries = new ArrayList<>();
} }
/** Returns an unmodifiable, flattened collection of all the buttons in this contents. */ /** Returns an unmodifiable, flattened collection of all the buttons in this contents. */
@@ -83,26 +88,29 @@ class Contents {
for(int page : visiblePages){ for(int page : visiblePages){
if(page >= 0 && page < entries.size() / maxLineNumber + 1){ if(page >= 0 && page < visibleEntries.size() / maxLineNumber + 1){
int x = left + (GuiWizardHandbook.isRightPage(startPage + page) ? GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : GuiWizardHandbook.TEXT_INSET_X); int x = left + (GuiWizardHandbook.isRightPage(startPage + page) ? GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : GuiWizardHandbook.TEXT_INSET_X);
int y = top + GuiWizardHandbook.TEXT_INSET_Y + startLine * font.FONT_HEIGHT; int y = top + GuiWizardHandbook.TEXT_INSET_Y + startLine * font.FONT_HEIGHT;
for(Section entry : this.entries){ for(Section entry : this.visibleEntries){
int nameWidth = font.getStringWidth(entry.title); if(entry.isUnlocked()){
String dotsAndNumber = " " + entry.startPage; int nameWidth = font.getStringWidth(entry.title);
while(font.getStringWidth(dotsAndNumber) < GuiWizardHandbook.PAGE_WIDTH - nameWidth - 2){ String dotsAndNumber = " " + entry.startPage;
dotsAndNumber = separator + dotsAndNumber;
while(font.getStringWidth(dotsAndNumber) < GuiWizardHandbook.PAGE_WIDTH - nameWidth - 2){
dotsAndNumber = separator + dotsAndNumber;
}
font.drawString(dotsAndNumber, x + GuiWizardHandbook.PAGE_WIDTH - font.getStringWidth(dotsAndNumber), y, DrawingUtils.BLACK, false);
if(!hyperlinks) font.drawString(entry.title, x, y, DrawingUtils.BLACK, false);
y += font.FONT_HEIGHT;
} }
font.drawString(dotsAndNumber, x + GuiWizardHandbook.PAGE_WIDTH - font.getStringWidth(dotsAndNumber), y, DrawingUtils.BLACK, false);
if(!hyperlinks) font.drawString(entry.title, x, y, DrawingUtils.BLACK, false);
y += font.FONT_HEIGHT;
} }
} }
} }
@@ -124,6 +132,10 @@ class Contents {
this.buttons.clear(); this.buttons.clear();
this.visibleEntries = new ArrayList<>(entries); // Need to copy the collection first!
this.visibleEntries.removeIf(s -> !s.isUnlocked());
if(hyperlinks){ if(hyperlinks){
// FONT_HEIGHT may change between fonts, so this is calculated here. With the default font it's 14. // FONT_HEIGHT may change between fonts, so this is calculated here. With the default font it's 14.
@@ -134,12 +146,12 @@ class Contents {
List<GuiButton> list = new ArrayList<>(maxLineNumber); List<GuiButton> list = new ArrayList<>(maxLineNumber);
for(Section entry : this.entries){ for(Section entry : this.visibleEntries){
int x = GuiWizardHandbook.isRightPage(startPage) ? left + GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : left + GuiWizardHandbook.TEXT_INSET_X; int x = GuiWizardHandbook.isRightPage(startPage) ? left + GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : left + GuiWizardHandbook.TEXT_INSET_X;
int y = top + GuiWizardHandbook.TEXT_INSET_Y + startLine * font.FONT_HEIGHT; int y = top + GuiWizardHandbook.TEXT_INSET_Y + startLine * font.FONT_HEIGHT;
list.add(new GuiButtonHyperlink.Internal(0, x, y, font, entry.title, entry, 0, "")); list.add(new GuiButtonHyperlink.Internal(0, x, y, font, entry.title, entry, 0, "", maxLineNumber-startLine, GuiWizardHandbook.isRightPage(startPage)));
startLine++; startLine++;
@@ -156,21 +168,22 @@ class Contents {
// Returning this is kind of trivial at the moment but if we ever wanted to add a header or something, // Returning this is kind of trivial at the moment but if we ever wanted to add a header or something,
// it would be more useful. // it would be more useful.
return entries.size(); return visibleEntries.size();
} }
/** /**
* Parses the given JSON object and constructs a new {@code Contents} from it, setting all the relevant fields * Parses the given JSON object and constructs a new {@code Contents} from it, setting all the relevant fields
* and references. * and references.
* *
* @param parent The parent section for this contents.
* @param json A JSON object representing the contents to be constructed. This must contain at least an "id" * @param json A JSON object representing the contents to be constructed. This must contain at least an "id"
* string. * string.
* @return The resulting {@code Contents} object. * @return The resulting {@code Contents} object.
* @throws JsonSyntaxException if at any point the JSON object is found to be invalid. * @throws JsonSyntaxException if at any point the JSON object is found to be invalid.
*/ */
static Contents fromJson(JsonObject json){ static Contents fromJson(Section parent, JsonObject json){
Contents contents = new Contents(JsonUtils.getString(json, "id")); Contents contents = new Contents(JsonUtils.getString(json, "id"), parent);
contents.hyperlinks = JsonUtils.getBoolean(json, "hyperlinks", true); contents.hyperlinks = JsonUtils.getBoolean(json, "hyperlinks", true);
contents.pageNumbers = JsonUtils.getBoolean(json, "page_numbers", true); contents.pageNumbers = JsonUtils.getBoolean(json, "page_numbers", true);
@@ -1,43 +1,38 @@
package electroblob.wizardry.client.gui.handbook; package electroblob.wizardry.client.gui.handbook;
import com.google.common.collect.Streams;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;
import electroblob.wizardry.client.DrawingUtils; import electroblob.wizardry.client.DrawingUtils;
import electroblob.wizardry.spell.Mine;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.recipebook.GuiRecipeBook;
import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.RenderHelper; import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.client.renderer.RenderItem; import net.minecraft.client.renderer.RenderItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.CraftingManager; import net.minecraft.item.crafting.CraftingManager;
import net.minecraft.item.crafting.IRecipe; import net.minecraft.item.crafting.IRecipe;
import net.minecraft.item.crafting.Ingredient; import net.minecraft.item.crafting.Ingredient;
import net.minecraft.stats.RecipeBook;
import net.minecraft.util.JsonUtils; import net.minecraft.util.JsonUtils;
import net.minecraft.util.NonNullList;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import java.util.HashSet; import java.util.*;
import java.util.Map;
import java.util.Set;
class CraftingRecipe { class CraftingRecipe {
static final int WIDTH = 111, HEIGHT = 56; static final int BORDER = 7;
static final int TEXTURE_INSET_X = 40, TEXTURE_INSET_Y = 190;
static final int WIDTH = 121, HEIGHT = 66;
// Final fields are mandatory, the rest are optional // Final fields are mandatory, the rest are optional
private final ResourceLocation location; private final ResourceLocation[] locations;
// Derived fields, not specifically defined in JSON // Derived fields, not specifically defined in JSON
private IRecipe recipe; private List<IRecipe> recipes;
private final Set<int[]> instances = new HashSet<>(); private final Set<int[]> instances = new HashSet<>();
private CraftingRecipe(ResourceLocation location){ private CraftingRecipe(ResourceLocation[] locations){
this.location = location; this.locations = locations;
} }
/** /**
@@ -60,9 +55,14 @@ class CraftingRecipe {
* the recipes aren't necessarily loaded at that point. */ * the recipes aren't necessarily loaded at that point. */
void load(){ void load(){
this.recipe = CraftingManager.getRecipe(location); recipes = new ArrayList<>(locations.length);
if(recipe == null) throw new JsonSyntaxException("No such recipe: " + location); for(ResourceLocation location : locations){
IRecipe recipe = CraftingManager.getRecipe(location);
if(recipe == null) throw new JsonSyntaxException("No such recipe: " + location);
recipes.add(recipe);
}
} }
/** /**
@@ -75,9 +75,12 @@ class CraftingRecipe {
* @param top The y coordinate of the top of the GUI. * @param top The y coordinate of the top of the GUI.
*/ */
void draw(FontRenderer font, RenderItem itemRenderer, int doublePage, int left, int top){ void draw(FontRenderer font, RenderItem itemRenderer, int doublePage, int left, int top){
int index = (int)(Minecraft.getSystemTime() % Integer.MAX_VALUE)/2000;
for(int[] instance : instances){ for(int[] instance : instances){
if(GuiWizardHandbook.singleToDoublePage(instance[0]) == doublePage){ if(GuiWizardHandbook.singleToDoublePage(instance[0]) == doublePage){
renderCraftingRecipe(font, itemRenderer, left + instance[1], top + instance[2], this.recipe); renderCraftingRecipe(font, itemRenderer, left + instance[1], top + instance[2], recipes.get(index % recipes.size()));
} }
} }
} }
@@ -92,9 +95,12 @@ class CraftingRecipe {
* @param top The y coordinate of the top of the GUI. * @param top The y coordinate of the top of the GUI.
*/ */
void drawTooltips(GuiWizardHandbook gui, FontRenderer font, RenderItem itemRenderer, int doublePage, int left, int top, int mouseX, int mouseY){ void drawTooltips(GuiWizardHandbook gui, FontRenderer font, RenderItem itemRenderer, int doublePage, int left, int top, int mouseX, int mouseY){
int index = (int)(Minecraft.getSystemTime() % Integer.MAX_VALUE)/2000;
for(int[] instance : instances){ for(int[] instance : instances){
if(GuiWizardHandbook.singleToDoublePage(instance[0]) == doublePage){ if(GuiWizardHandbook.singleToDoublePage(instance[0]) == doublePage){
renderCraftingTooltips(gui, itemRenderer, left + instance[1], top + instance[2], mouseX, mouseY, this.recipe); renderCraftingTooltips(gui, itemRenderer, left + instance[1], top + instance[2], mouseX, mouseY, recipes.get(index % recipes.size()));
} }
} }
} }
@@ -103,15 +109,16 @@ class CraftingRecipe {
* Parses the given JSON object and constructs a new {@code Image} from it, setting all the relevant fields * Parses the given JSON object and constructs a new {@code Image} from it, setting all the relevant fields
* and references. * and references.
* *
* @param json A JSON object representing the image to be constructed. This must contain at least a "location" * @param json A JSON object representing the image to be constructed. This must contain at least a "locations"
* string. * string.
* @return The resulting {@code Image} object. * @return The resulting {@code Image} object.
* @throws JsonSyntaxException if at any point the JSON object is found to be invalid. * @throws JsonSyntaxException if at any point the JSON object is found to be invalid.
*/ */
static CraftingRecipe fromJson(JsonObject json){ static CraftingRecipe fromJson(JsonObject json){
ResourceLocation location = new ResourceLocation(JsonUtils.getString(json, "location")); ResourceLocation[] locations = Streams.stream(JsonUtils.getJsonArray(json, "locations"))
return new CraftingRecipe(location); .map(je -> new ResourceLocation(je.getAsString())).toArray(ResourceLocation[]::new);
return new CraftingRecipe(locations);
} }
static void populate(Map<String, CraftingRecipe> map, JsonObject json){ static void populate(Map<String, CraftingRecipe> map, JsonObject json){
@@ -135,17 +142,15 @@ class CraftingRecipe {
GlStateManager.color(1, 1, 1, 1); GlStateManager.color(1, 1, 1, 1);
Minecraft.getMinecraft().renderEngine.bindTexture(GuiWizardHandbook.texture); Minecraft.getMinecraft().renderEngine.bindTexture(GuiWizardHandbook.texture);
DrawingUtils.drawTexturedRect(x - 2, y - 2, 60, 190, WIDTH, HEIGHT, 512, 256); DrawingUtils.drawTexturedRect(x, y, TEXTURE_INSET_X, TEXTURE_INSET_Y, WIDTH, HEIGHT, GuiWizardHandbook.TEXTURE_WIDTH, GuiWizardHandbook.TEXTURE_HEIGHT);
GlStateManager.pushMatrix(); GlStateManager.pushMatrix();
RenderHelper.enableGUIStandardItemLighting(); RenderHelper.enableGUIStandardItemLighting();
GlStateManager.disableLighting(); GlStateManager.disableLighting();
GlStateManager.enableRescaleNormal(); GlStateManager.enableRescaleNormal();
GlStateManager.enableColorMaterial(); GlStateManager.enableColorMaterial();
GlStateManager.enableLighting();
itemRenderer.zLevel = 100.0F; itemRenderer.zLevel = 100.0F;
// TODO: There may be a better way of doing this
int index = (int)(Minecraft.getSystemTime() % Integer.MAX_VALUE)/2000; int index = (int)(Minecraft.getSystemTime() % Integer.MAX_VALUE)/2000;
int i = 0; int i = 0;
@@ -155,8 +160,8 @@ class CraftingRecipe {
if(ingredient != Ingredient.EMPTY){ if(ingredient != Ingredient.EMPTY){
ItemStack stack = ingredient.getMatchingStacks()[index % ingredient.getMatchingStacks().length]; ItemStack stack = ingredient.getMatchingStacks()[index % ingredient.getMatchingStacks().length];
if(!stack.isEmpty()){ if(!stack.isEmpty()){
itemRenderer.renderItemAndEffectIntoGUI(stack, x + 18 * (i%3), y + 18 * (i/3)); itemRenderer.renderItemAndEffectIntoGUI(stack, x + BORDER + 18 * (i%3), y + BORDER + 18 * (i/3));
itemRenderer.renderItemOverlays(font, stack, x + 18 * (i%3), y + 18 * (i/3)); itemRenderer.renderItemOverlays(font, stack, x + BORDER + 18 * (i%3), y + BORDER + 18 * (i/3));
} }
} }
@@ -164,16 +169,15 @@ class CraftingRecipe {
} }
if(!result.isEmpty()){ if(!result.isEmpty()){
itemRenderer.renderItemAndEffectIntoGUI(result, x + 86, y + 18); itemRenderer.renderItemAndEffectIntoGUI(result, x + BORDER + 86, y + BORDER + 18);
itemRenderer.renderItemOverlays(font, result, x + 86, y + 18); itemRenderer.renderItemOverlays(font, result, x + BORDER + 86, y + BORDER + 18);
} }
GlStateManager.popMatrix(); GlStateManager.popMatrix();
GlStateManager.enableLighting();
GlStateManager.enableDepth(); GlStateManager.enableDepth();
GlStateManager.disableColorMaterial(); GlStateManager.disableColorMaterial();
itemRenderer.zLevel = 0.0F; itemRenderer.zLevel = 0.0F;
//RenderHelper.enableStandardItemLighting(); RenderHelper.disableStandardItemLighting();
} }
@@ -187,9 +191,7 @@ class CraftingRecipe {
GlStateManager.enableRescaleNormal(); GlStateManager.enableRescaleNormal();
GlStateManager.enableColorMaterial(); GlStateManager.enableColorMaterial();
itemRenderer.zLevel = 0.0F; itemRenderer.zLevel = 0.0F;
GlStateManager.disableLighting();
// TODO: There may be a better way of doing this
int index = (int)(Minecraft.getSystemTime() % Integer.MAX_VALUE)/2000; int index = (int)(Minecraft.getSystemTime() % Integer.MAX_VALUE)/2000;
int i = 0; int i = 0;
@@ -198,7 +200,7 @@ class CraftingRecipe {
if(ingredient != Ingredient.EMPTY){ if(ingredient != Ingredient.EMPTY){
ItemStack stack = ingredient.getMatchingStacks()[index % ingredient.getMatchingStacks().length]; ItemStack stack = ingredient.getMatchingStacks()[index % ingredient.getMatchingStacks().length];
if(!stack.isEmpty() && isPointInRegion(x + 18 * (i%3), y + 18 * (i/3), 16, 16, mouseX, mouseY)){ if(!stack.isEmpty() && isPointInRegion(x + BORDER + 18 * (i%3), y + BORDER + 18 * (i/3), 16, 16, mouseX, mouseY)){
gui.renderToolTip(stack, mouseX, mouseY); gui.renderToolTip(stack, mouseX, mouseY);
} }
} }
@@ -206,15 +208,14 @@ class CraftingRecipe {
i++; i++;
} }
if(!result.isEmpty() && isPointInRegion(x + 86, y + 18, 16, 16, mouseX, mouseY)){ if(!result.isEmpty() && isPointInRegion(x + BORDER + 86, y + BORDER + 18, 16, 16, mouseX, mouseY)){
gui.renderToolTip(result, mouseX, mouseY); gui.renderToolTip(result, mouseX, mouseY);
} }
GlStateManager.popMatrix(); GlStateManager.popMatrix();
GlStateManager.enableLighting();
GlStateManager.enableDepth(); GlStateManager.enableDepth();
GlStateManager.disableColorMaterial(); GlStateManager.disableColorMaterial();
RenderHelper.enableStandardItemLighting(); RenderHelper.disableStandardItemLighting();
} }
@@ -1,12 +1,14 @@
package electroblob.wizardry.client.gui.handbook; package electroblob.wizardry.client.gui.handbook;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;
import electroblob.wizardry.client.DrawingUtils;
import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.registry.WizardrySounds;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.PositionedSoundRecord; import net.minecraft.client.audio.PositionedSoundRecord;
import net.minecraft.client.audio.SoundHandler; import net.minecraft.client.audio.SoundHandler;
import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiButton;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentString; import net.minecraft.util.text.TextComponentString;
import net.minecraft.util.text.TextFormatting; import net.minecraft.util.text.TextFormatting;
@@ -19,10 +21,14 @@ public abstract class GuiButtonHyperlink extends GuiButton {
public static final String URL_REGEX = "^((https?|ftp)://|(www|ftp)\\.)?[a-z0-9-]+(\\.[a-z0-9-]+)+([/?].*)?$"; public static final String URL_REGEX = "^((https?|ftp)://|(www|ftp)\\.)?[a-z0-9-]+(\\.[a-z0-9-]+)+([/?].*)?$";
/** Pulse period of links to new sections, in milliseconds. */
private static final float PULSATION_PERIOD = 1500;
final int indent; final int indent;
final List<String> lines; final List<String> lines;
final int linesLeft;
GuiButtonHyperlink(int id, int x, int y, FontRenderer font, String text, int indent, String suffix){ GuiButtonHyperlink(int id, int x, int y, FontRenderer font, String text, int indent, String suffix, int linesLeft, boolean rightPage){
super(id, x, y, font.getStringWidth(text), font.FONT_HEIGHT, text); super(id, x, y, font.getStringWidth(text), font.FONT_HEIGHT, text);
@@ -36,6 +42,7 @@ public abstract class GuiButtonHyperlink extends GuiButton {
} }
this.indent = indent; // Assigned here in case it was corrected above this.indent = indent; // Assigned here in case it was corrected above
this.linesLeft = linesLeft;
String line1 = font.listFormattedStringToWidth(linkWithSuffix, GuiWizardHandbook.PAGE_WIDTH - indent).get(0); String line1 = font.listFormattedStringToWidth(linkWithSuffix, GuiWizardHandbook.PAGE_WIDTH - indent).get(0);
// Without trim(), there will be at least 1 leading space due to the custom wrapping // Without trim(), there will be at least 1 leading space due to the custom wrapping
@@ -59,6 +66,11 @@ public abstract class GuiButtonHyperlink extends GuiButton {
} }
} }
} }
// Remove any lines that overflowed onto the next double-page
if(rightPage){
while(lines.size() > linesLeft) lines.remove(lines.size() - 1);
}
} }
public boolean isHovered(net.minecraft.client.gui.FontRenderer font, int mouseX, int mouseY){ public boolean isHovered(net.minecraft.client.gui.FontRenderer font, int mouseX, int mouseY){
@@ -72,6 +84,11 @@ public abstract class GuiButtonHyperlink extends GuiButton {
int t = y + font.FONT_HEIGHT * i; int t = y + font.FONT_HEIGHT * i;
if(i > linesLeft){
l = l + GuiWizardHandbook.GUI_WIDTH - 2 * GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH;
t -= GuiWizardHandbook.PAGE_HEIGHT - (GuiWizardHandbook.PAGE_HEIGHT % font.FONT_HEIGHT);
}
if(mouseX >= l && mouseY >= t && mouseX < l + font.getStringWidth(line) && mouseY < t + font.FONT_HEIGHT){ if(mouseX >= l && mouseY >= t && mouseX < l + font.getStringWidth(line) && mouseY < t + font.FONT_HEIGHT){
return true; return true;
} }
@@ -93,7 +110,6 @@ public abstract class GuiButtonHyperlink extends GuiButton {
if(this.visible){ if(this.visible){
this.hovered = isHovered(minecraft.fontRenderer, mouseX, mouseY); this.hovered = isHovered(minecraft.fontRenderer, mouseX, mouseY);
int colour = hovered ? GuiWizardHandbook.colours.get("highlight") : GuiWizardHandbook.colours.get("hyperlink");
int i = 0; int i = 0;
@@ -104,13 +120,22 @@ public abstract class GuiButtonHyperlink extends GuiButton {
int t = y + minecraft.fontRenderer.FONT_HEIGHT * i; int t = y + minecraft.fontRenderer.FONT_HEIGHT * i;
minecraft.fontRenderer.drawString(line, l, t, colour); if(i > linesLeft){
l = l + GuiWizardHandbook.GUI_WIDTH - 2 * GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH;
t -= GuiWizardHandbook.PAGE_HEIGHT - (GuiWizardHandbook.PAGE_HEIGHT % minecraft.fontRenderer.FONT_HEIGHT);
}
minecraft.fontRenderer.drawString(line, l, t, getColour());
i++; i++;
} }
} }
} }
protected int getColour(){
return hovered ? GuiWizardHandbook.colours.get("highlight") : GuiWizardHandbook.colours.get("hyperlink");
}
/** /**
* Creates a new hyperlink button from the given arguments, automatically differentiating between URLs and sections. * Creates a new hyperlink button from the given arguments, automatically differentiating between URLs and sections.
* @param x The x position of the button * @param x The x position of the button
@@ -124,7 +149,7 @@ public abstract class GuiButtonHyperlink extends GuiButton {
* @throws IllegalArgumentException if the given argument array is empty or contains more than 2 arguments * @throws IllegalArgumentException if the given argument array is empty or contains more than 2 arguments
* @throws JsonSyntaxException if the specified link target is not a URL or a valid section ID * @throws JsonSyntaxException if the specified link target is not a URL or a valid section ID
*/ */
public static GuiButtonHyperlink create(int x, int y, FontRenderer font, List<String> upToLink, String[] arguments, String suffix){ public static GuiButtonHyperlink create(int x, int y, FontRenderer font, List<String> upToLink, String[] arguments, String suffix, int linesLeft, boolean rightPage){
if(arguments.length == 0 || arguments.length > 2) throw new IllegalArgumentException("Incorrect array length!"); if(arguments.length == 0 || arguments.length > 2) throw new IllegalArgumentException("Incorrect array length!");
@@ -133,7 +158,7 @@ public abstract class GuiButtonHyperlink extends GuiButton {
if(arguments[0].matches(URL_REGEX)){ if(arguments[0].matches(URL_REGEX)){
button = new GuiButtonHyperlink.External(0, x, y, font, arguments[arguments.length - 1], arguments[0], button = new GuiButtonHyperlink.External(0, x, y, font, arguments[arguments.length - 1], arguments[0],
font.getStringWidth(upToLink.get(upToLink.size() - 1)), suffix); font.getStringWidth(upToLink.get(upToLink.size() - 1)), suffix, linesLeft, rightPage);
}else{ }else{
@@ -142,7 +167,7 @@ public abstract class GuiButtonHyperlink extends GuiButton {
if(target == null) throw new JsonSyntaxException("Hyperlink points to nonexistent section id " + arguments[0]); if(target == null) throw new JsonSyntaxException("Hyperlink points to nonexistent section id " + arguments[0]);
button = new GuiButtonHyperlink.Internal(0, x, y, font, arguments[arguments.length - 1], button = new GuiButtonHyperlink.Internal(0, x, y, font, arguments[arguments.length - 1],
target, font.getStringWidth(upToLink.get(upToLink.size() - 1)), suffix); target, font.getStringWidth(upToLink.get(upToLink.size() - 1)), suffix, linesLeft, rightPage);
} }
return button; return button;
@@ -152,26 +177,49 @@ public abstract class GuiButtonHyperlink extends GuiButton {
final Section target; final Section target;
Internal(int id, int x, int y, FontRenderer font, String text, Section target, int indent, String suffix){ Internal(int id, int x, int y, FontRenderer font, String text, Section target, int indent, String suffix, int linesLeft, boolean rightPage){
super(id, x, y, font, text, indent, suffix); super(id, x, y, font, text, indent, suffix, linesLeft, rightPage);
this.target = target; this.target = target;
} }
@Override
public boolean mousePressed(Minecraft minecraft, int mouseX, int mouseY){
if(!target.isUnlocked()) return false;
return super.mousePressed(minecraft, mouseX, mouseY);
}
@Override @Override
public void playPressSound(SoundHandler soundHandler){ public void playPressSound(SoundHandler soundHandler){
soundHandler.playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.MISC_PAGE_TURN, 1)); soundHandler.playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.MISC_PAGE_TURN, 1));
} }
@Override
protected int getColour(){
if(!target.isUnlocked()) return GuiWizardHandbook.colours.get("text");
if(!hovered && target.isNew() && !Minecraft.getMinecraft().player.isCreative()){
int c = GuiWizardHandbook.colours.get("new_section");
int d = GuiWizardHandbook.colours.get("hyperlink");
float f = (MathHelper.sin((Minecraft.getSystemTime() % PULSATION_PERIOD) / PULSATION_PERIOD * 2 * (float)Math.PI) + 1) / 2f;
return DrawingUtils.mix(c, d, f);
}
return super.getColour();
}
} }
static class External extends GuiButtonHyperlink { static class External extends GuiButtonHyperlink {
final ITextComponent link; final ITextComponent link;
External(int id, int x, int y, FontRenderer font, String text, String url, int indent, String suffix){ External(int id, int x, int y, FontRenderer font, String text, String url, int indent, String suffix, int linesLeft, boolean rightPage){
super(id, x, y, font, text, indent, suffix); super(id, x, y, font, text, indent, suffix, linesLeft, rightPage);
this.link = new TextComponentString(text); this.link = new TextComponentString(text);
link.getStyle().setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, url)); link.getStyle().setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, url)).setColor(TextFormatting.DARK_BLUE);
} }
} }
@@ -9,10 +9,8 @@ import net.minecraft.client.audio.SoundHandler;
import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT) //@SideOnly(Side.CLIENT)
class GuiButtonTurnPage extends GuiButton { class GuiButtonTurnPage extends GuiButton {
static final int WIDTH = 20; static final int WIDTH = 20;
@@ -3,6 +3,7 @@ package electroblob.wizardry.client.gui.handbook;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import electroblob.wizardry.Wizardry; import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.ClientProxy; import electroblob.wizardry.client.ClientProxy;
import electroblob.wizardry.client.DrawingUtils; import electroblob.wizardry.client.DrawingUtils;
@@ -11,34 +12,28 @@ import electroblob.wizardry.client.gui.handbook.GuiButtonTurnPage.Type;
import electroblob.wizardry.constants.Constants; import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.constants.Element; import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.Tier; import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.packet.PacketRequestAdvancementSync;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.registry.WizardrySounds;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.PositionedSoundRecord; import net.minecraft.client.audio.PositionedSoundRecord;
import net.minecraft.client.audio.SoundHandler; import net.minecraft.client.audio.SoundHandler;
import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.multiplayer.ClientAdvancementManager;
import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.resources.IResource; import net.minecraft.client.resources.IResource;
import net.minecraft.client.resources.IResourceManager; import net.minecraft.client.resources.IResourceManager;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.util.JsonUtils; import net.minecraft.util.JsonUtils;
import net.minecraft.util.NonNullList;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraftforge.event.entity.player.AdvancementEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.ReflectionHelper;
import org.apache.commons.lang3.tuple.Pair;
import org.lwjgl.input.Keyboard; import org.lwjgl.input.Keyboard;
import java.awt.*; import java.awt.*;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.util.*;
import java.util.List; import java.util.List;
import java.util.*;
/** /**
* GUI class for the wizard's handbook. Like any GUI class, this is instantiated each time the book is opened. As of * GUI class for the wizard's handbook. Like any GUI class, this is instantiated each time the book is opened. As of
@@ -54,7 +49,6 @@ import java.util.List;
* @see Image * @see Image
* @see CraftingRecipe * @see CraftingRecipe
*/ */
@Mod.EventBusSubscriber
public class GuiWizardHandbook extends GuiScreen { public class GuiWizardHandbook extends GuiScreen {
private static final ResourceLocation DEFAULT = new ResourceLocation(Wizardry.MODID, "texts/handbook_en_us.json"); private static final ResourceLocation DEFAULT = new ResourceLocation(Wizardry.MODID, "texts/handbook_en_us.json");
@@ -71,6 +65,7 @@ public class GuiWizardHandbook extends GuiScreen {
static final String IMAGE_TAG = "image"; static final String IMAGE_TAG = "image";
static final String RECIPE_TAG = "recipe"; static final String RECIPE_TAG = "recipe";
static final String RULER_TAG = "ruler";
static final Map<String, String> FORMAT_TAGS = new HashMap<>(); static final Map<String, String> FORMAT_TAGS = new HashMap<>();
@@ -79,6 +74,8 @@ public class GuiWizardHandbook extends GuiScreen {
/** The dimensions of the rendered GUI area. */ /** The dimensions of the rendered GUI area. */
static final int GUI_WIDTH = 288, GUI_HEIGHT = 180; static final int GUI_WIDTH = 288, GUI_HEIGHT = 180;
/** The dimensions of the GUI texture itself. */
static final int TEXTURE_WIDTH = 512, TEXTURE_HEIGHT = 256;
/** The dimensions of the area of a single page in which text can be drawn. */ /** The dimensions of the area of a single page in which text can be drawn. */
static final int PAGE_WIDTH = 120, PAGE_HEIGHT = 140; static final int PAGE_WIDTH = 120, PAGE_HEIGHT = 140;
/** The distance of the text from the top outside corner of each page. */ /** The distance of the text from the top outside corner of each page. */
@@ -107,12 +104,12 @@ public class GuiWizardHandbook extends GuiScreen {
* The <b>double-page</b> number where the bookmark is currently set, <b>relative to the section stored in * The <b>double-page</b> number where the bookmark is currently set, <b>relative to the section stored in
* {@link GuiWizardHandbook#bookmarkSection}</b>. Static because it persists when the book is closed. * {@link GuiWizardHandbook#bookmarkSection}</b>. Static because it persists when the book is closed.
*/ */
private static int bookmarkPage = 1; private static int bookmarkPage = 0;
/** /**
* A reference to the section in which the bookmark is currently set. Static because it persists when the book is * The key corresponding to the section in which the bookmark is currently set. Static because it persists when the
* closed. Storing a section means the bookmark doesn't change location when new sections are unlocked. * book is closed. Storing a section means the bookmark doesn't change location when new sections are unlocked.
*/ */
private static Section bookmarkSection; private static String bookmarkSection;
// Buttons // Buttons
private GuiButton bookmark, next, previous, nextSection, previousSection, menu; private GuiButton bookmark, next, previous, nextSection, previousSection, menu;
@@ -164,19 +161,6 @@ public class GuiWizardHandbook extends GuiScreen {
*/ */
static final Map<String, CraftingRecipe> recipes = new HashMap<>(); static final Map<String, CraftingRecipe> recipes = new HashMap<>();
static String rulerText = "--------------------"; // Assigned here as a fallback
private static final List<Pair<ItemStack, NonNullList<NonNullList<ItemStack>>>> RECIPES = new ArrayList<>();
// Reflection
static final Field ADVANCEMENT_TO_PROGRESS;
static {
// TODO: Obf field name
ADVANCEMENT_TO_PROGRESS = ReflectionHelper.findField(ClientAdvancementManager.class, "advancementToProgress");
}
/** /**
* Adds a format tag to the handbook. All occurrences of the given tag string preceded by a # will be replaced with * Adds a format tag to the handbook. All occurrences of the given tag string preceded by a # will be replaced with
* the result of the given value string on GUI load. The value string, therefore, can be anything that should be * the result of the given value string on GUI load. The value string, therefore, can be anything that should be
@@ -193,9 +177,9 @@ public class GuiWizardHandbook extends GuiScreen {
addFormatTag("next_spell_key", ClientProxy.NEXT_SPELL.getDisplayName()); addFormatTag("next_spell_key", ClientProxy.NEXT_SPELL.getDisplayName());
addFormatTag("previous_spell_key", ClientProxy.PREVIOUS_SPELL.getDisplayName()); addFormatTag("previous_spell_key", ClientProxy.PREVIOUS_SPELL.getDisplayName());
addFormatTag("mana_per_crystal_minus_30", "" + (Constants.MANA_PER_CRYSTAL - 30)); addFormatTag("example_charging_loss", "" + (Constants.MANA_PER_CRYSTAL - 30));
addFormatTag("mana_per_crystal", "" + Constants.MANA_PER_CRYSTAL); addFormatTag("mana_per_crystal", "" + Constants.MANA_PER_CRYSTAL);
addFormatTag("novice_max_charge", "" + Tier.BASIC.maxCharge); addFormatTag("novice_max_charge", "" + Tier.NOVICE.maxCharge);
addFormatTag("apprentice_max_charge", "" + Tier.APPRENTICE.maxCharge); addFormatTag("apprentice_max_charge", "" + Tier.APPRENTICE.maxCharge);
addFormatTag("advanced_max_charge", "" + Tier.ADVANCED.maxCharge); addFormatTag("advanced_max_charge", "" + Tier.ADVANCED.maxCharge);
addFormatTag("master_max_charge", "" + Tier.MASTER.maxCharge); addFormatTag("master_max_charge", "" + Tier.MASTER.maxCharge);
@@ -264,12 +248,14 @@ public class GuiWizardHandbook extends GuiScreen {
Minecraft.getMinecraft().renderEngine.bindTexture(texture); Minecraft.getMinecraft().renderEngine.bindTexture(texture);
GlStateManager.color(1, 1, 1, 1);
// Main background // Main background
DrawingUtils.drawTexturedRect(left, top, 0, 0, GUI_WIDTH, GUI_HEIGHT, 512, 256); DrawingUtils.drawTexturedRect(left, top, 0, 0, GUI_WIDTH, GUI_HEIGHT, TEXTURE_WIDTH, TEXTURE_HEIGHT);
// First page background // First page background
if(currentPage == 0){ if(currentPage == 0){
DrawingUtils.drawTexturedRect(left, top, 368, 0, GUI_WIDTH / 2, GUI_HEIGHT, 512, 256); DrawingUtils.drawTexturedRect(left, top, 368, 0, GUI_WIDTH / 2, GUI_HEIGHT, TEXTURE_WIDTH, TEXTURE_HEIGHT);
previous.visible = false; previous.visible = false;
previousSection.visible = false; // Not worth testing if we're in the first section every frame previousSection.visible = false; // Not worth testing if we're in the first section every frame
menu.visible = false; menu.visible = false;
@@ -281,7 +267,7 @@ public class GuiWizardHandbook extends GuiScreen {
// Last page background // Last page background
if(currentPage == singleToDoublePage(pageCount)){ if(currentPage == singleToDoublePage(pageCount)){
DrawingUtils.drawTexturedFlippedRect(left + GUI_WIDTH / 2, top, 368, 0, GUI_WIDTH / 2, GUI_HEIGHT, 512, 256, true, false); DrawingUtils.drawTexturedFlippedRect(left + GUI_WIDTH / 2, top, 368, 0, GUI_WIDTH / 2, GUI_HEIGHT, TEXTURE_WIDTH, TEXTURE_HEIGHT, true, false);
next.visible = false; next.visible = false;
nextSection.visible = false; nextSection.visible = false;
}else{ }else{
@@ -302,9 +288,10 @@ public class GuiWizardHandbook extends GuiScreen {
} }
// Main content // Main content
contentsList.values().forEach(c -> { if(c.section.isUnlocked()) c.draw(fontRenderer, currentPage, left, top); } );
sections.values().forEach(s -> { if(s.isUnlocked()) s.draw(fontRenderer, currentPage, left, top); } );
// These only get populated if the sections are unlocked so no checks are necessary
images.values().forEach(i -> i.draw(fontRenderer, currentPage, left, top)); images.values().forEach(i -> i.draw(fontRenderer, currentPage, left, top));
contentsList.values().forEach(c -> c.draw(fontRenderer, currentPage, left, top));
sections.values().forEach(s -> s.draw(fontRenderer, currentPage, left, top));
recipes.values().forEach(r -> r.draw(fontRenderer, itemRender, currentPage, left, top)); recipes.values().forEach(r -> r.draw(fontRenderer, itemRender, currentPage, left, top));
// Buttons // Buttons
@@ -314,14 +301,15 @@ public class GuiWizardHandbook extends GuiScreen {
GlStateManager.color(1, 1, 1, 1); GlStateManager.color(1, 1, 1, 1);
Minecraft.getMinecraft().renderEngine.bindTexture(texture); Minecraft.getMinecraft().renderEngine.bindTexture(texture);
if(currentPage == singleToDoublePage(bookmarkSection.startPage) + bookmarkPage){ if(currentPage == singleToDoublePage(sections.get(bookmarkSection).startPage) + bookmarkPage){
// If the current page is the bookmarked page, the (invisible) bookmark button is disabled
bookmark.visible = false; bookmark.visible = false;
DrawingUtils.drawTexturedRect(left + 138, top, 299, 0, 11, 191, 512, 256); DrawingUtils.drawTexturedRect(left + 138, top, 299, 0, 11, 191, TEXTURE_WIDTH, TEXTURE_HEIGHT);
}else{ }else{
bookmark.visible = true; bookmark.visible = true;
bookmark.x = left + (currentPage > singleToDoublePage(bookmarkSection.startPage) + bookmarkPage ? 130 : 147); bookmark.x = left + (currentPage > singleToDoublePage(sections.get(bookmarkSection).startPage) + bookmarkPage ? 130 : 147);
DrawingUtils.drawTexturedRect(bookmark.x, top, DrawingUtils.drawTexturedRect(bookmark.x, top,
bookmark.isMouseOver() ? 310 : 288, 0, 11, 191, 512, 256); bookmark.isMouseOver() ? 310 : 288, 0, 11, 191, TEXTURE_WIDTH, TEXTURE_HEIGHT);
} }
// Recipe tooltips // Recipe tooltips
@@ -426,8 +414,6 @@ public class GuiWizardHandbook extends GuiScreen {
JsonElement je = gson.fromJson(reader, JsonElement.class); JsonElement je = gson.fromJson(reader, JsonElement.class);
JsonObject json = je.getAsJsonObject(); JsonObject json = je.getAsJsonObject();
rulerText = JsonUtils.getString(json, "ruler_text");
JsonUtils.getJsonObject(json, "colours").entrySet().forEach(e -> colours.put(e.getKey(), JsonUtils.getJsonObject(json, "colours").entrySet().forEach(e -> colours.put(e.getKey(),
Color.decode(e.getValue().getAsString()).getRGB())); Color.decode(e.getValue().getAsString()).getRGB()));
@@ -443,10 +429,12 @@ public class GuiWizardHandbook extends GuiScreen {
return; return;
} }
// Attempts to find a suitable place to put the bookmark to start with bookmarkSection = JsonUtils.getString(json, "bookmark_start_section");
bookmarkSection = sections.get("introduction"); if(!sections.containsKey(bookmarkSection)) throw new JsonSyntaxException("Section with id " + bookmarkSection + " is undefined");
if(bookmarkSection == null) bookmarkSection = sectionList.get(0);
} }
// The first resource load on startup is done before the packet handler is loaded
if(WizardryPacketHandler.net != null) WizardryPacketHandler.net.sendToServer(new PacketRequestAdvancementSync.Message());
} }
/** /**
@@ -460,6 +448,8 @@ public class GuiWizardHandbook extends GuiScreen {
*/ */
private static IResource getHandbookResource(IResourceManager manager){ private static IResource getHandbookResource(IResourceManager manager){
// TODO: Implement resource pack stacking to allow addon mods and texture packs to add/overwrite content
IResource handbookFile = null; IResource handbookFile = null;
try{ try{
@@ -507,12 +497,15 @@ public class GuiWizardHandbook extends GuiScreen {
if(currentSection != null){ if(currentSection != null){
int index = sectionList.indexOf(currentSection); List<Section> visibleSections = new ArrayList<>(sectionList);
visibleSections.removeIf(s -> !s.isUnlocked());
if(button == nextSection && index + 1 < sections.size()){ int index = visibleSections.indexOf(currentSection);
currentPage = singleToDoublePage(sectionList.get(index + 1).startPage);
if(button == nextSection && index + 1 < visibleSections.size()){
currentPage = singleToDoublePage(visibleSections.get(index + 1).startPage);
}else if(index > 0){ }else if(index > 0){
currentPage = singleToDoublePage(sectionList.get(index - 1).startPage); currentPage = singleToDoublePage(visibleSections.get(index - 1).startPage);
} }
} }
@@ -520,7 +513,7 @@ public class GuiWizardHandbook extends GuiScreen {
currentPage = singleToDoublePage(sections.get("main_contents").startPage); currentPage = singleToDoublePage(sections.get("main_contents").startPage);
}else if(button == bookmark && bookmarkSection != null){ }else if(button == bookmark && bookmarkSection != null){
currentPage = singleToDoublePage(bookmarkSection.startPage) + bookmarkPage; currentPage = singleToDoublePage(sections.get(bookmarkSection).startPage) + bookmarkPage;
}else{ }else{
if(button instanceof GuiButtonHyperlink.Internal){ if(button instanceof GuiButtonHyperlink.Internal){
@@ -540,12 +533,12 @@ public class GuiWizardHandbook extends GuiScreen {
this.selectedButton = bookmark; this.selectedButton = bookmark;
for(Section section : sections.values()){ for(String key : sections.keySet()){
// The bookmark is assumed to bookmark the left-hand page // The bookmark is assumed to bookmark the left-hand page
if(section.containsPage(doubleToSinglePage(bookmarkPage, false))) bookmarkSection = section; if(sections.get(key).containsPage(doubleToSinglePage(currentPage, false))) bookmarkSection = key;
} }
bookmarkPage = currentPage - singleToDoublePage(bookmarkSection.startPage); bookmarkPage = currentPage - singleToDoublePage(sections.get(bookmarkSection).startPage);
} }
}else{ }else{
super.mouseClicked(mouseX, mouseY, mouseButton); super.mouseClicked(mouseX, mouseY, mouseButton);
@@ -558,9 +551,14 @@ public class GuiWizardHandbook extends GuiScreen {
super.renderToolTip(stack, x, y); super.renderToolTip(stack, x, y);
} }
@SubscribeEvent @Override
public static void onAdvancementEvent(AdvancementEvent event){ public boolean doesGuiPauseGame(){
sections.values().forEach(s -> s.onAdvancement(event.getEntityPlayer(), event.getAdvancement())); return Wizardry.settings.booksPauseGame;
}
public static void updateUnlockStatus(boolean showToasts, ResourceLocation... completedAdvancements){
sections.values().forEach(s -> s.updateUnlockStatus(showToasts, completedAdvancements));
} }
} }
@@ -0,0 +1,54 @@
package electroblob.wizardry.client.gui.handbook;
import electroblob.wizardry.registry.WizardryItems;
import net.minecraft.client.gui.toasts.GuiToast;
import net.minecraft.client.gui.toasts.IToast;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.client.resources.I18n;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.MathHelper;
import java.util.List;
//@SideOnly(Side.CLIENT)
public class HandbookToast implements IToast {
private final Section section;
public HandbookToast(Section section){
this.section = section;
}
public IToast.Visibility draw(GuiToast toastGui, long delta){
toastGui.getMinecraft().getTextureManager().bindTexture(TEXTURE_TOASTS);
GlStateManager.color(1.0F, 1.0F, 1.0F);
toastGui.drawTexturedModalRect(0, 0, 0, 32, 160, 32);
boolean firstPart = delta < 1500L;
int a = firstPart ? MathHelper.floor(MathHelper.clamp((float)(1500L - delta) / 300.0F, 0.0F, 1.0F) * 255.0F) << 24 | 67108864
: MathHelper.floor(MathHelper.clamp((float)(delta - 1500L) / 300.0F, 0.0F, 1.0F) * 252.0F) << 24 | 67108864;
String s = firstPart ? I18n.format("handbook.toast.title") : section.title;
int c = firstPart ? -11534256 : -16777216;
List<String> list = toastGui.getMinecraft().fontRenderer.listFormattedStringToWidth(s, 125);
int h = 16 - list.size() * toastGui.getMinecraft().fontRenderer.FONT_HEIGHT / 2;
for(String line : list){
toastGui.getMinecraft().fontRenderer.drawString(line, 30, h, c | a);
h += toastGui.getMinecraft().fontRenderer.FONT_HEIGHT;
}
RenderHelper.enableGUIStandardItemLighting();
toastGui.getMinecraft().getRenderItem().renderItemAndEffectIntoGUI(null, new ItemStack(WizardryItems.wizard_handbook), 8, 8);
return delta >= 5000L ? IToast.Visibility.HIDE : IToast.Visibility.SHOW;
}
}
@@ -9,6 +9,7 @@ import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.util.JsonUtils; import net.minecraft.util.JsonUtils;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
@@ -22,11 +23,15 @@ class Image {
private int textureWidth, textureHeight; private int textureWidth, textureHeight;
private int u = 0, v = 0; private int u = 0, v = 0;
private String caption = ""; private String caption = "";
private boolean border = true;
// Derived fields, not specifically defined in JSON // Derived fields, not specifically defined in JSON
private final Set<int[]> instances = new HashSet<>(); private final Set<int[]> instances = new HashSet<>();
private static final int CAPTION_OFFSET = 4; private static final int CAPTION_OFFSET = 4;
private static final int TEXTURE_INSET_X = 180;
private static final int BORDER = 1;
private Image(ResourceLocation location, int width, int height){ private Image(ResourceLocation location, int width, int height){
this.location = location; this.location = location;
this.width = width; this.width = width;
@@ -68,6 +73,7 @@ class Image {
* @param top The y coordinate of the top of the GUI. * @param top The y coordinate of the top of the GUI.
*/ */
void draw(FontRenderer font, int doublePage, int left, int top){ void draw(FontRenderer font, int doublePage, int left, int top){
// Images
for(int[] instance : instances){ for(int[] instance : instances){
if(GuiWizardHandbook.singleToDoublePage(instance[0]) == doublePage){ if(GuiWizardHandbook.singleToDoublePage(instance[0]) == doublePage){
Minecraft.getMinecraft().renderEngine.bindTexture(location); Minecraft.getMinecraft().renderEngine.bindTexture(location);
@@ -77,6 +83,29 @@ class Image {
top + instance[2] + height + CAPTION_OFFSET, GuiWizardHandbook.colours.get("caption")); top + instance[2] + height + CAPTION_OFFSET, GuiWizardHandbook.colours.get("caption"));
} }
} }
if(border){
// Borders - do this after all the images are drawn so we only have to bind the handbook texture again once
Minecraft.getMinecraft().renderEngine.bindTexture(GuiWizardHandbook.texture);
GlStateManager.color(1, 1, 1, 1);
for(int[] instance : instances){
if(GuiWizardHandbook.singleToDoublePage(instance[0]) == doublePage){
// Math.ceil accounts for odd-numbered image dimensions
DrawingUtils.drawTexturedFlippedRect(left + instance[1] - BORDER, top + instance[2] - BORDER,
TEXTURE_INSET_X, GuiWizardHandbook.GUI_HEIGHT, width / 2 + BORDER, height / 2 + BORDER,
GuiWizardHandbook.TEXTURE_WIDTH, GuiWizardHandbook.TEXTURE_HEIGHT, false, false);
DrawingUtils.drawTexturedFlippedRect(left + instance[1] + width / 2, top + instance[2] - BORDER,
TEXTURE_INSET_X, GuiWizardHandbook.GUI_HEIGHT, MathHelper.ceil(width / 2f) + BORDER, height / 2 + BORDER,
GuiWizardHandbook.TEXTURE_WIDTH, GuiWizardHandbook.TEXTURE_HEIGHT, true, false);
DrawingUtils.drawTexturedFlippedRect(left + instance[1] - BORDER, top + instance[2] + height / 2,
TEXTURE_INSET_X, GuiWizardHandbook.GUI_HEIGHT, width / 2 + BORDER, MathHelper.ceil(height / 2f) + BORDER,
GuiWizardHandbook.TEXTURE_WIDTH, GuiWizardHandbook.TEXTURE_HEIGHT, false, true);
DrawingUtils.drawTexturedFlippedRect(left + instance[1] + width / 2, top + instance[2] + height / 2,
TEXTURE_INSET_X, GuiWizardHandbook.GUI_HEIGHT, MathHelper.ceil(width / 2f) + BORDER, MathHelper.ceil(height / 2f) + BORDER,
GuiWizardHandbook.TEXTURE_WIDTH, GuiWizardHandbook.TEXTURE_HEIGHT, true, true);
}
}
}
} }
/** /**
@@ -98,7 +127,7 @@ class Image {
image.textureWidth = JsonUtils.getInt(json, "texture_width", image.width); image.textureWidth = JsonUtils.getInt(json, "texture_width", image.width);
image.textureHeight = JsonUtils.getInt(json, "texture_height", image.height); image.textureHeight = JsonUtils.getInt(json, "texture_height", image.height);
image.caption = JsonUtils.getString(json, "caption", ""); image.caption = JsonUtils.getString(json, "caption", "");
image.border = JsonUtils.getBoolean(json, "border", true);
return image; return image;
} }
@@ -7,13 +7,12 @@ import com.google.gson.JsonSyntaxException;
import electroblob.wizardry.Wizardry; import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.DrawingUtils; import electroblob.wizardry.client.DrawingUtils;
import electroblob.wizardry.util.WizardryUtilities; import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.advancements.Advancement;
import net.minecraft.advancements.AdvancementProgress;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiButton;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.util.JsonUtils; import net.minecraft.util.JsonUtils;
import net.minecraft.util.ResourceLocation;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import java.util.*; import java.util.*;
@@ -23,7 +22,7 @@ import java.util.*;
* everything within the section itself, including JSON parsing, unlock triggers and drawing the actual rawText. * everything within the section itself, including JSON parsing, unlock triggers and drawing the actual rawText.
* Sections may now also be nested and have other elements within them, such as images and a table of contents, a * Sections may now also be nested and have other elements within them, such as images and a table of contents, a
* behaviour which is also handled within this class. * behaviour which is also handled within this class.
* <p> * <p></p>
* The formatting of the book is now done 'dynamically' - that is, the exact positions and page numbers of * The formatting of the book is now done 'dynamically' - that is, the exact positions and page numbers of
* sections, images and so on are determined on GUI load and depend on which of the previous sections have been * sections, images and so on are determined on GUI load and depend on which of the previous sections have been
* unlocked, amongst other factors. This means that all of the unlocked sections must be formatted in order on GUI * unlocked, amongst other factors. This means that all of the unlocked sections must be formatted in order on GUI
@@ -32,15 +31,15 @@ import java.util.*;
* @author Electroblob * @author Electroblob
* @since Wizardry 4.2 * @since Wizardry 4.2
*/ */
// Because these are now generated on resource pack reload (not on handbook open, as before), this class must now // Because these are now generated on resource pack reload (not on handbook open, as before), this class can no longer
// be static // be a non-static inner class
class Section { class Section {
// Final fields are mandatory (none here though), the rest are optional // Final fields are mandatory (none here though), the rest are optional
String title; String title;
private String[] rawText; private String[] rawText;
private Contents contents; private Contents contents;
private Advancement[] triggers; private ResourceLocation[] triggers;
private Map<String, Section> subsections; private Map<String, Section> subsections;
private boolean centreX, centreY; private boolean centreX, centreY;
@@ -54,6 +53,9 @@ class Section {
*/ */
private final List<List<String>> pages; private final List<List<String>> pages;
private boolean unlocked = false;
private boolean isNew = false;
private Section(){ private Section(){
this.buttons = new ArrayList<>(); this.buttons = new ArrayList<>();
this.pages = new ArrayList<>(); this.pages = new ArrayList<>();
@@ -65,10 +67,10 @@ class Section {
} }
/** /**
* Returns true if the given page is within this section, false if not. * Returns true if the given page is within this section, false if not (or if the section is locked).
*/ */
boolean containsPage(int page){ boolean containsPage(int page){
return startPage <= page && startPage + pages.size() > page; return this.isUnlocked() && startPage <= page && startPage + pages.size() > page;
} }
/** /**
@@ -77,32 +79,24 @@ class Section {
*/ */
boolean isUnlocked(){ boolean isUnlocked(){
if(Minecraft.getMinecraft().player.isCreative()) return true;
if(!Wizardry.settings.handbookProgression) return true; // Always unlocked if handbook progression is off if(!Wizardry.settings.handbookProgression) return true; // Always unlocked if handbook progression is off
if(triggers == null) return true; // If no triggers were defined, the section is unlocked from the start
// A section is automatically unlocked if one of its subsections is unlocked // A section is automatically unlocked if one of its subsections is unlocked
for(Section subsection : subsections.values()){ for(Section subsection : subsections.values()){
if(subsection.isUnlocked()) return true; if(subsection.isUnlocked()) return true;
} }
if(triggers == null) return true; // If no triggers were defined, the section is unlocked from the start return unlocked;
}
for(Advancement trigger : triggers){ /**
* Returns true if this section has been unlocked and not read yet. Also returns true if any subsections are new.
try{ */
boolean isNew(){
// TESTME: Is this properly synced or do we need to do that as well? if(!Wizardry.settings.handbookProgression) return false;
Map<Advancement, AdvancementProgress> advancements = (Map)GuiWizardHandbook.ADVANCEMENT_TO_PROGRESS.get(Minecraft.getMinecraft().player.connection.getAdvancementManager()); return isNew || this.subsections.values().stream().anyMatch(Section::isNew);
if(advancements.get(trigger).isDone()){
return true;
}
}catch(IllegalAccessException e){
e.printStackTrace();
}
}
return false;
} }
/** /**
@@ -143,12 +137,19 @@ class Section {
for(String line : lines){ for(String line : lines){
int lx = centreX ? x + GuiWizardHandbook.PAGE_WIDTH / 2 - font.getStringWidth(line) / 2 : x; if(line.startsWith(GuiWizardHandbook.FORMAT_MARKER + GuiWizardHandbook.RULER_TAG)){
GlStateManager.color(1, 1, 1, 1);
font.drawString(line, lx, y, DrawingUtils.BLACK, false); Minecraft.getMinecraft().renderEngine.bindTexture(GuiWizardHandbook.texture);
DrawingUtils.drawTexturedRect(x-1, y-1, 0, GuiWizardHandbook.GUI_HEIGHT, GuiWizardHandbook.PAGE_WIDTH + 2, 9, GuiWizardHandbook.TEXTURE_WIDTH, GuiWizardHandbook.TEXTURE_HEIGHT);
}else{
int lx = centreX ? x + GuiWizardHandbook.PAGE_WIDTH / 2 - font.getStringWidth(line) / 2 : x;
font.drawString(line, lx, y, DrawingUtils.BLACK, false);
}
y += font.FONT_HEIGHT; y += font.FONT_HEIGHT;
} }
isNew = false; // Now a page has been drawn, the player must have seen it so it's not new any more
} }
} }
} }
@@ -180,7 +181,7 @@ class Section {
// Adds the header if present // Adds the header if present
if(!this.title.isEmpty()){ if(!this.title.isEmpty()){
lines.add(this.title); lines.add(this.title);
lines.add(GuiWizardHandbook.rulerText); lines.add(GuiWizardHandbook.FORMAT_MARKER + GuiWizardHandbook.RULER_TAG);
} }
// Adds space for the contents if it exists // Adds space for the contents if it exists
@@ -212,7 +213,7 @@ class Section {
+ StringUtils.abbreviate(raw, 50)); + StringUtils.abbreviate(raw, 50));
Image image = GuiWizardHandbook.images.get(arguments[1]); Image image = GuiWizardHandbook.images.get(arguments[1]);
if(image == null) throw new JsonSyntaxException("Image with id " + arguments[1] + "is undefined"); if(image == null) throw new JsonSyntaxException("Image with id " + arguments[1] + " is undefined");
// Starts a new page if the image will not fit on the current one // Starts a new page if the image will not fit on the current one
if((lines.size() % maxLineNumber) * font.FONT_HEIGHT + image.getHeight(font) > GuiWizardHandbook.PAGE_HEIGHT){ if((lines.size() % maxLineNumber) * font.FONT_HEIGHT + image.getHeight(font) > GuiWizardHandbook.PAGE_HEIGHT){
@@ -248,27 +249,35 @@ class Section {
+ StringUtils.abbreviate(raw, 50)); + StringUtils.abbreviate(raw, 50));
CraftingRecipe recipe = GuiWizardHandbook.recipes.get(arguments[1]); CraftingRecipe recipe = GuiWizardHandbook.recipes.get(arguments[1]);
if(recipe == null) throw new JsonSyntaxException("Recipe with id " + arguments[1] + "is undefined"); if(recipe == null) throw new JsonSyntaxException("Recipe with id " + arguments[1] + " is undefined");
// Starts a new page if the recipe will not fit on the current one // Starts a new page if the recipe will not fit on the current one
if((lines.size() % maxLineNumber) * font.FONT_HEIGHT + CraftingRecipe.HEIGHT > GuiWizardHandbook.PAGE_HEIGHT){ if((lines.size() % maxLineNumber) * font.FONT_HEIGHT + CraftingRecipe.HEIGHT > GuiWizardHandbook.PAGE_HEIGHT){
// Remaining number of lines on the page // Remaining number of lines on the page, plus the first blank one on the new page
lines.addAll(Collections.nCopies(maxLineNumber - (lines.size() % maxLineNumber), "")); lines.addAll(Collections.nCopies(maxLineNumber - (lines.size() % maxLineNumber), " "));
} }
int page = startPage + (lines.size() / maxLineNumber); int page = startPage + (lines.size() / maxLineNumber);
if(lines.size() % maxLineNumber == 0) lines.add(" ");
int startLine = lines.size() % maxLineNumber - 1;
recipe.addInstance(page, GuiWizardHandbook.PAGE_WIDTH / 2 - CraftingRecipe.WIDTH / 2 recipe.addInstance(page, GuiWizardHandbook.PAGE_WIDTH / 2 - CraftingRecipe.WIDTH / 2
+ (GuiWizardHandbook.isRightPage(page) ? GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : GuiWizardHandbook.TEXT_INSET_X), + (GuiWizardHandbook.isRightPage(page) ? GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : GuiWizardHandbook.TEXT_INSET_X),
GuiWizardHandbook.TEXT_INSET_Y + (lines.size() % maxLineNumber) * font.FONT_HEIGHT); GuiWizardHandbook.TEXT_INSET_Y + startLine * font.FONT_HEIGHT);
// Height of the recipe in lines, rounded up // Height of the recipe in lines, rounded up
// Uses a single space instead of an empty string so that the page trimming doesn't remove them // Uses a single space instead of an empty string so that the page trimming doesn't remove them
lines.addAll(Collections.nCopies(CraftingRecipe.HEIGHT / font.FONT_HEIGHT, " ")); lines.addAll(Collections.nCopies(CraftingRecipe.HEIGHT / font.FONT_HEIGHT - 1, " "));
// This time we're not adding an extra space because it's not really needed // This time we're not adding an extra space because it's not really needed
}else{ // All other paragraphs }else{ // All other paragraphs
// Formatting
for(Map.Entry<String, String> entry : GuiWizardHandbook.FORMAT_TAGS.entrySet()){
paragraph = paragraph.replace(GuiWizardHandbook.FORMAT_MARKER + entry.getKey(), entry.getValue());
}
// Hyperlinks // Hyperlinks
int linkStart; int linkStart;
@@ -284,15 +293,17 @@ class Section {
String linkRaw = paragraph.substring(linkStart, linkEnd + 1); String linkRaw = paragraph.substring(linkStart, linkEnd + 1);
String[] arguments = paragraph.substring(linkStart + 1, linkEnd).split("\\s", 2); String[] arguments = paragraph.substring(linkStart + 1, linkEnd).split("\\s", 2);
String suffix = paragraph.substring(linkEnd).split("\\s", 2)[0]; String suffix = paragraph.substring(linkEnd).split("\\s", 2)[0].substring(1); // substring(1) to remove the @
// The index of the single page currently being formatted, relative to the section // The index of the single page currently being formatted, relative to the section
int pageRelative = (lines.size() + upToLink.size() - 1) / maxLineNumber; int pageRelative = (lines.size() + upToLink.size() - 1) / maxLineNumber;
// The overall index of the single page currently being formatted // The overall index of the single page currently being formatted
int page = startPage + pageRelative; int page = startPage + pageRelative;
// The line number on this page
int lineNumber = (lines.size() + upToLink.size() - 1) % maxLineNumber;
int x = GuiWizardHandbook.isRightPage(page) ? left + GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : left + GuiWizardHandbook.TEXT_INSET_X; int x = GuiWizardHandbook.isRightPage(page) ? left + GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : left + GuiWizardHandbook.TEXT_INSET_X;
int y = top + GuiWizardHandbook.TEXT_INSET_Y + (((lines.size() + upToLink.size() - 1) % maxLineNumber)) * font.FONT_HEIGHT; int y = top + GuiWizardHandbook.TEXT_INSET_Y + lineNumber * font.FONT_HEIGHT;
// Adds any missing sub-lists // Adds any missing sub-lists
while(this.buttons.size() <= pageRelative){ while(this.buttons.size() <= pageRelative){
@@ -300,18 +311,13 @@ class Section {
} }
// The button id only does what you use it for, so we're just not using it at all. // The button id only does what you use it for, so we're just not using it at all.
this.buttons.get(pageRelative).add(GuiButtonHyperlink.create(x, y, font, upToLink, arguments, suffix)); this.buttons.get(pageRelative).add(GuiButtonHyperlink.create(x, y, font, upToLink, arguments, suffix, maxLineNumber - lineNumber - 1, GuiWizardHandbook.isRightPage(page)));
// The link button should exactly overlay the display rawText in the main string // The link button should exactly overlay the display rawText in the main string
// If the link has no display rawText specified, it displays the unformatted target string // If the link has no display rawText specified, it displays the unformatted target string
paragraph = paragraph.replace(linkRaw, arguments[arguments.length - 1]); paragraph = paragraph.replace(linkRaw, arguments[arguments.length - 1]);
} }
// Formatting
for(Map.Entry<String, String> entry : GuiWizardHandbook.FORMAT_TAGS.entrySet()){
paragraph = paragraph.replace(GuiWizardHandbook.FORMAT_MARKER + entry.getKey(), entry.getValue());
}
lines.addAll(font.listFormattedStringToWidth(paragraph, GuiWizardHandbook.PAGE_WIDTH)); lines.addAll(font.listFormattedStringToWidth(paragraph, GuiWizardHandbook.PAGE_WIDTH));
} }
@@ -373,18 +379,22 @@ class Section {
} }
if(JsonUtils.hasField(json, "contents")){ if(JsonUtils.hasField(json, "contents")){
section.contents = Contents.fromJson(JsonUtils.getJsonObject(json, "contents")); section.contents = Contents.fromJson(section, JsonUtils.getJsonObject(json, "contents"));
GuiWizardHandbook.contentsList.put(section.contents.id, section.contents); GuiWizardHandbook.contentsList.put(section.contents.id, section.contents);
} }
if(JsonUtils.hasField(json, "text")){ if(JsonUtils.hasField(json, "text")){
// TESTME: Does this always preserve order?
section.rawText = Streams.stream(JsonUtils.getJsonArray(json, "text")) section.rawText = Streams.stream(JsonUtils.getJsonArray(json, "text"))
.map(e -> JsonUtils.getString(e, "element of array rawText")) .map(e -> JsonUtils.getString(e, "element of array rawText"))
.toArray(String[]::new); .toArray(String[]::new);
} }
// TODO: Triggers if(JsonUtils.hasField(json, "triggers")){
section.triggers = Streams.stream(JsonUtils.getJsonArray(json, "triggers"))
.map(e -> new ResourceLocation(JsonUtils.getString(e, "element of array triggers")))
.toArray(ResourceLocation[]::new);
// TODO: Can we validate this and throw a JSON exception if no such advancement exists?
}
if(JsonUtils.hasField(json, "centre")){ if(JsonUtils.hasField(json, "centre")){
JsonObject centre = JsonUtils.getJsonObject(json,"centre"); JsonObject centre = JsonUtils.getJsonObject(json,"centre");
@@ -416,14 +426,26 @@ class Section {
} }
} }
public void onAdvancement(EntityPlayer player, Advancement advancement){ /**
Minecraft minecraft = Minecraft.getMinecraft(); * Called on login and advancement completion to update this section's unlock status and display toast
//System.out.println(title); * notifications if applicable.
if(triggers != null && Arrays.asList(triggers).contains(advancement) && player == minecraft.player){ */
//minecraft.getToastGui().drawToast(IToast); public void updateUnlockStatus(boolean showToasts, ResourceLocation... completedAdvancements){
// Debug if(triggers == null) return;
//System.out.println(title);
List<ResourceLocation> completed = new ArrayList<>(Arrays.asList(completedAdvancements));
completed.retainAll(Arrays.asList(triggers));
// Only shows the toast when the section was locked before and is now unlocked
if(!this.unlocked && !completed.isEmpty() && showToasts && Wizardry.settings.handbookProgression){
// Mmmmm toast...
Minecraft minecraft = Minecraft.getMinecraft();
minecraft.getToastGui().add(new HandbookToast(this));
this.isNew = true;
} }
// Currently, this will not take subsections into account
this.unlocked = !completed.isEmpty();
} }
} }
@@ -0,0 +1,234 @@
package electroblob.wizardry.client.model;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.block.model.IBakedModel;
import net.minecraft.client.renderer.block.model.ItemCameraTransforms;
import net.minecraft.client.renderer.block.model.ItemOverrideList;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.util.EnumFacing;
import net.minecraftforge.client.model.pipeline.UnpackedBakedQuad;
import net.minecraftforge.client.model.pipeline.VertexLighterFlat;
import net.minecraftforge.common.ForgeModContainer;
import net.minecraftforge.fml.client.FMLClientHandler;
import org.apache.commons.lang3.tuple.Pair;
import javax.annotation.Nullable;
import javax.vecmath.Matrix4f;
import java.util.ArrayList;
import java.util.List;
/**
* Custom baked model that stores a list of texture names and sets the lighting to full brightness for any quads
* with one of those textures. Most of this code was copied and adapted from refined storage, which is licensed under
* the MIT license. https://github.com/raoulvdberge/refinedstorage<br>
* <br>
* <i>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?)</i>
*
* @author Electroblob
* @author raoulvdberge
*/
public class BakedModelGlowingOverlay implements IBakedModel {
// Something something something cache, says Forge
// This breaks randomised block models (because it's cached, duh...) but simply including the rand parameter will
// completely defeat the point of the cache so I need some way of storing the randomised model variants... hmmm...
// See WeightedBakedModel for more on randomisation (it's pretty similar to my 1.7 implementation from years ago)
// private class CacheKey {
//
// private IBakedModel base;
// private String suffix;
// private IBlockState state;
// private EnumFacing side;
//
// public CacheKey(IBakedModel base, String suffix, IBlockState state, EnumFacing side){
// this.base = base;
// this.suffix = suffix;
// this.state = state;
// this.side = side;
// }
//
// @Override
// public boolean equals(Object o){
//
// if(this == o) return true;
// if(o == null || getClass() != o.getClass()) return false;
//
// CacheKey cacheKey = (CacheKey)o;
//
// if(cacheKey.side != side) return false;
// if(!state.equals(cacheKey.state)) return false;
//
// return true;
// }
//
// @Override
// public int hashCode() {
// return state.hashCode() + (31 * (side != null ? side.hashCode() : 0));
// }
// }
//
// private static final LoadingCache<CacheKey, List<BakedQuad>> CACHE = CacheBuilder.newBuilder().build(new CacheLoader<CacheKey, List<BakedQuad>>() {
// @Override
// public List<BakedQuad> load(CacheKey key) {
// return transformQuads(key.base.getQuads(key.state, key.side, 0), key.suffix);
// }
// });
private final IBakedModel delegate;
private String suffix;
public BakedModelGlowingOverlay(IBakedModel delegate, String suffix){
this.delegate = delegate;
this.suffix = suffix;
}
@Override
public List<BakedQuad> getQuads(@Nullable IBlockState state, @Nullable EnumFacing side, long rand){
if(state == null) return delegate.getQuads(state, side, rand);
return transformQuads(delegate.getQuads(state, side, rand), suffix);
//return CACHE.getUnchecked(new CacheKey(delegate, suffix, state instanceof IExtendedBlockState ? ((IExtendedBlockState) state).getClean() : state, side));
}
// I would write these myself but I'd end up with almost the exact same thing anyway
// They replace the quads from the original (delegate) model with full-brightness quads if their texture has the given suffix
private static List<BakedQuad> transformQuads(List<BakedQuad> oldQuads, String suffix){
List<BakedQuad> quads = new ArrayList<>(oldQuads);
for(int i = 0; i < quads.size(); ++i){
BakedQuad quad = quads.get(i);
if(quad.getSprite().getIconName().endsWith(suffix)){
quads.set(i, transformQuad(quad, 0.007F)); // What's the significance of 0.007?
}
}
return quads;
}
private static BakedQuad transformQuad(BakedQuad quad, float light){
if(isLightMapDisabled()){
return quad;
}
VertexFormat newFormat = getFormatWithLightMap(quad.getFormat());
UnpackedBakedQuad.Builder builder = new UnpackedBakedQuad.Builder(newFormat);
VertexLighterFlat trans = new VertexLighterFlat(Minecraft.getMinecraft().getBlockColors()) {
@Override
protected void updateLightmap(float[] normal, float[] lightmap, float x, float y, float z){
lightmap[0] = light;
lightmap[1] = light;
}
@Override
public void setQuadTint(int tint){
// NO OP
}
};
trans.setParent(builder);
quad.pipe(trans);
builder.setQuadTint(quad.getTintIndex());
builder.setQuadOrientation(quad.getFace());
builder.setTexture(quad.getSprite());
builder.setApplyDiffuseLighting(false);
return builder.build();
}
@Override
public boolean isAmbientOcclusion(){
return delegate.isAmbientOcclusion();
}
@Override
public boolean isGui3d(){
return delegate.isGui3d();
}
@Override
public boolean isBuiltInRenderer(){
return delegate.isBuiltInRenderer();
}
@Override
public TextureAtlasSprite getParticleTexture(){
return delegate.getParticleTexture();
}
@Override
public ItemCameraTransforms getItemCameraTransforms(){
return delegate.getItemCameraTransforms();
}
@Override
public ItemOverrideList getOverrides(){
return delegate.getOverrides();//BakedModelItemOverride.instance;
}
@Override
public boolean isAmbientOcclusion(IBlockState state){
return delegate.isAmbientOcclusion(state);
}
@Override
public Pair<? extends IBakedModel, Matrix4f> handlePerspective(ItemCameraTransforms.TransformType cameraTransformType){
return delegate.handlePerspective(cameraTransformType);
}
// Utilities
private static boolean isLightMapDisabled(){
return FMLClientHandler.instance().hasOptifine() || !ForgeModContainer.forgeLightPipelineEnabled;
}
private static final VertexFormat ITEM_FORMAT_WITH_LIGHTMAP = new VertexFormat(DefaultVertexFormats.ITEM).addElement(DefaultVertexFormats.TEX_2S);
private static VertexFormat getFormatWithLightMap(VertexFormat format){
if(isLightMapDisabled()){
return format;
}
if(format == DefaultVertexFormats.BLOCK){
return DefaultVertexFormats.BLOCK;
}else if(format == DefaultVertexFormats.ITEM){
return ITEM_FORMAT_WITH_LIGHTMAP;
}else if(!format.hasUvOffset(1)){
VertexFormat result = new VertexFormat(format);
result.addElement(DefaultVertexFormats.TEX_2S);
return result;
}
return format;
}
// Bit of a weird way of doing things if you ask me, but as far as I can tell it's how you're supposed to do it
// public static final class BakedModelItemOverride extends ItemOverrideList {
//
// // This class doesn't contain any data of its own so it can be a singleton
// public static final BakedModelItemOverride instance = new BakedModelItemOverride();
//
// private BakedModelItemOverride(){
// super(ImmutableList.of()); // We're not using the list functionality
// }
//
// @Override
// public IBakedModel handleItemState(IBakedModel originalModel, ItemStack stack, @Nullable World world, @Nullable EntityLivingBase entity){
// return new BakedModelGlowingOverlay(originalModel, "overlay"); // Bish bash bosh
// }
// }
}
@@ -1,18 +1,16 @@
package electroblob.wizardry.client.model; package electroblob.wizardry.client.model;
import javax.vecmath.Matrix4f;
import javax.vecmath.Vector3f;
import electroblob.wizardry.entity.living.EntityIceGiant; import electroblob.wizardry.entity.living.EntityIceGiant;
import net.minecraft.client.model.ModelBase; import net.minecraft.client.model.ModelBase;
import net.minecraft.client.model.ModelRenderer; import net.minecraft.client.model.ModelRenderer;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.EntityLivingBase;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT) import javax.vecmath.Matrix4f;
import javax.vecmath.Vector3f;
//@SideOnly(Side.CLIENT)
public class ModelIceGiant extends ModelBase { public class ModelIceGiant extends ModelBase {
/** The head model for the iron golem. */ /** The head model for the iron golem. */
public ModelRenderer iceGiantHead; public ModelRenderer iceGiantHead;
@@ -97,9 +97,9 @@ public class ModelWizard extends ModelBiped {
setRotation(Shape13, 0F, 0F, 0F); setRotation(Shape13, 0F, 0F, 0F);
// Makes head bits move with head // Makes head bits move with head
// bipedHead.addChild(Shape5); // bipedHead.addChild(hatSegment4);
bipedHead.addChild(beard); bipedHead.addChild(beard);
// bipedHead.addChild(Shape7); // bipedHead.addChild(hatSegment6);
// bipedHead.addChild(Shape8); // bipedHead.addChild(Shape8);
// bipedHead.addChild(Shape9); // bipedHead.addChild(Shape9);
// bipedHead.addChild(Shape10); // bipedHead.addChild(Shape10);
@@ -116,8 +116,8 @@ public class ModelWizard extends ModelBiped {
/* public void render(Entity entity, float f, float f1, float f2, float f3, float f4, float f5) { /* public void render(Entity entity, float f, float f1, float f2, float f3, float f4, float f5) {
* super.render(entity, f, f1, f2, f3, f4, f5); setRotationAngles(f, f1, f2, f3, f4, f5, entity); * super.render(entity, f, f1, f2, f3, f4, f5); setRotationAngles(f, f1, f2, f3, f4, f5, entity);
* bipedRightLeg.render(f5); bipedLeftLeg.render(f5); bipedBody.render(f5); bipedLeftArm.render(f5); * bipedRightLeg.render(f5); bipedLeftLeg.render(f5); bipedBody.render(f5); bipedLeftArm.render(f5);
* bipedRightArm.render(f5); bipedHead.render(f5); Shape5.render(f5); Shape8.render(f5); Shape9.render(f5); * bipedRightArm.render(f5); bipedHead.render(f5); hatSegment4.render(f5); Shape8.render(f5); Shape9.render(f5);
* Shape10.render(f5); Shape7.render(f5); Shape11.render(f5); Shape12.render(f5); Shape6.render(f5); * Shape10.render(f5); hatSegment6.render(f5); Shape11.render(f5); Shape12.render(f5); hatSegment5.render(f5);
* Shape13.render(f5); } */ * Shape13.render(f5); } */
private void setRotation(ModelRenderer model, float x, float y, float z){ private void setRotation(ModelRenderer model, float x, float y, float z){
model.rotateAngleX = x; model.rotateAngleX = x;
@@ -1,99 +1,110 @@
package electroblob.wizardry.client.model; package electroblob.wizardry.client.model;
import electroblob.wizardry.block.BlockStatue;
import net.minecraft.client.model.ModelRenderer; import net.minecraft.client.model.ModelRenderer;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
public class ModelWizardArmour extends ModelArmourFixer { public class ModelWizardArmour extends ModelArmourFixer {
ModelRenderer Shape1;
ModelRenderer Shape2; ModelRenderer hatBrim;
ModelRenderer Shape3; ModelRenderer hatSegment1;
ModelRenderer Shape4; ModelRenderer hatSegment2;
ModelRenderer Shape5; ModelRenderer hatSegment3;
ModelRenderer Shape6; ModelRenderer hatSegment4;
ModelRenderer Shape7; ModelRenderer hatSegment5;
ModelRenderer hatSegment6;
ModelRenderer robe; ModelRenderer robe;
public ModelWizardArmour(float scale){ public ModelWizardArmour(float delta){
super(scale, 0, 64, 64); super(delta, 0, 64, 64);
// This is necessary to stop the head from scaling. // This is necessary to stop the head from scaling.
this.bipedHead = new ModelRenderer(this, 0, 0); this.bipedHead = new ModelRenderer(this, 0, 0);
this.bipedHead.addBox(-4.0F, -8.0F, -4.0F, 8, 8, 8, 0.1f); // The hat layer has an offset of 0.5, so 0.6 is about the smallest we can get away with
this.bipedHead.addBox(-4.0F, -8.0F, -4.0F, 8, 8, 8, 0.6f);
this.bipedHead.setRotationPoint(0.0F, 0.0F + 0, 0.0F); this.bipedHead.setRotationPoint(0.0F, 0.0F + 0, 0.0F);
Shape1 = new ModelRenderer(this, -16, 32); hatBrim = new ModelRenderer(this, 0, 47);
Shape1.addBox(-8F, -7F, -8F, 16, 0, 16); // Making the height 1 stops the top and bottom z-fighting when the hat is enchanted
Shape1.setRotationPoint(0F, 0F, 0F); hatBrim.addBox(-8F, -6.85F, -8F, 16, 1, 16, 0.6f);
Shape1.setTextureSize(64, 64); hatBrim.setRotationPoint(0F, 0F, 0F);
Shape1.mirror = true; hatBrim.setTextureSize(64, 64);
setRotation(Shape1, 0F, 0F, 0F); hatBrim.mirror = true;
setRotation(hatBrim, 0F, 0F, 0F);
Shape2 = new ModelRenderer(this, 0, 48); hatSegment1 = new ModelRenderer(this, 0, 32);
Shape2.addBox(0F, 0F, 0F, 6, 2, 6); hatSegment1.addBox(0F, 0F, 0F, 6, 2, 6, 0.2f);
Shape2.setRotationPoint(-3F, -10F, -3F); hatSegment1.setRotationPoint(-3F, -10.6F, -3F);
Shape2.setTextureSize(64, 64); hatSegment1.setTextureSize(64, 64);
Shape2.mirror = true; hatSegment1.mirror = true;
setRotation(Shape2, -0.1396263F, 0F, 0F); setRotation(hatSegment1, -0.1396263F, 0F, 0F);
Shape3 = new ModelRenderer(this, 0, 56); hatSegment2 = new ModelRenderer(this, 0, 40);
Shape3.addBox(0F, 0F, 0F, 5, 2, 5); hatSegment2.addBox(0F, 0F, 0F, 5, 2, 5, 0.1f);
Shape3.setRotationPoint(-2.5F, -11.53333F, -2F); hatSegment2.setRotationPoint(-2.5F, -12.13333F, -2F);
Shape3.setTextureSize(64, 64); hatSegment2.setTextureSize(64, 64);
Shape3.mirror = true; hatSegment2.mirror = true;
setRotation(Shape3, -0.2443461F, 0F, 0F); setRotation(hatSegment2, -0.2443461F, 0F, 0F);
Shape4 = new ModelRenderer(this, 24, 48); hatSegment3 = new ModelRenderer(this, 24, 32);
Shape4.addBox(0F, 0F, 0F, 4, 2, 4); hatSegment3.addBox(0F, 0F, 0F, 4, 2, 4);
Shape4.setRotationPoint(-2F, -13F, -1F); hatSegment3.setRotationPoint(-2F, -13.6F, -1F);
Shape4.setTextureSize(64, 64); hatSegment3.setTextureSize(64, 64);
Shape4.mirror = true; hatSegment3.mirror = true;
setRotation(Shape4, -0.4014257F, 0F, 0F); setRotation(hatSegment3, -0.4014257F, 0F, 0F);
Shape5 = new ModelRenderer(this, 24, 54); hatSegment4 = new ModelRenderer(this, 24, 38);
Shape5.addBox(0F, 0F, 0F, 3, 2, 3); hatSegment4.addBox(0F, 0F, 0F, 3, 2, 3);
Shape5.setRotationPoint(-1.5F, -14F, 0F); hatSegment4.setRotationPoint(-1.5F, -14.6F, 0F);
Shape5.setTextureSize(64, 64); hatSegment4.setTextureSize(64, 64);
Shape5.mirror = true; hatSegment4.mirror = true;
setRotation(Shape5, -0.5759587F, 0F, 0F); setRotation(hatSegment4, -0.5759587F, 0F, 0F);
Shape6 = new ModelRenderer(this, 20, 59); hatSegment5 = new ModelRenderer(this, 20, 43);
Shape6.addBox(0F, 0F, 0F, 2, 2, 2); hatSegment5.addBox(0F, 0F, 0F, 2, 2, 2);
Shape6.setRotationPoint(-1F, -14F, 0F); hatSegment5.setRotationPoint(-1F, -14.6F, 0F);
Shape6.setTextureSize(64, 64); hatSegment5.setTextureSize(64, 64);
Shape6.mirror = true; hatSegment5.mirror = true;
setRotation(Shape6, 0.3316126F, 0F, 0F); setRotation(hatSegment5, 0.3316126F, 0F, 0F);
Shape7 = new ModelRenderer(this, 28, 59); hatSegment6 = new ModelRenderer(this, 28, 43);
Shape7.addBox(0F, 0F, 0F, 1, 1, 3); hatSegment6.addBox(0F, 0F, 0F, 1, 1, 3);
Shape7.setRotationPoint(-0.5F, -14.5F, 2F); hatSegment6.setRotationPoint(-0.5F, -15.1F, 2F);
Shape7.setTextureSize(64, 64); hatSegment6.setTextureSize(64, 64);
Shape7.mirror = true; hatSegment6.mirror = true;
setRotation(Shape7, -0.5585054F, 0F, 0F); setRotation(hatSegment6, -0.5585054F, 0F, 0F);
// The robe is now the body bipedBody = new ModelRenderer(this, 16, 16);
bipedBody = new ModelRenderer(this, 40, 42); bipedBody.addBox(-4F, 0F, -2F, 8, 11, 4, delta);
bipedBody.addBox(-4F, 0F, -2F, 8, 18, 4, scale);
bipedBody.setRotationPoint(0F, 0F, 0F); bipedBody.setRotationPoint(0F, 0F, 0F);
bipedBody.setTextureSize(64, 64); bipedBody.setTextureSize(64, 64);
bipedBody.mirror = true; bipedBody.mirror = true;
setRotation(bipedBody, 0F, 0F, 0F); setRotation(bipedBody, 0F, 0F, 0F);
robe = new ModelRenderer(this, 40, 32);
robe.addBox(-4F, 0F, -2F, 8, 7, 4, delta);
robe.setRotationPoint(0F, 12, 0F); // 12.5 accounts for the expansion of each box
robe.setTextureSize(64, 64);
robe.mirror = true;
setRotation(robe, 0F, 0F, 0F);
// Makes the hat rotate with the head. // Makes the hat rotate with the head.
bipedHead.addChild(Shape1); bipedHead.addChild(hatBrim);
bipedHead.addChild(Shape2); bipedHead.addChild(hatSegment1);
bipedHead.addChild(Shape3); bipedHead.addChild(hatSegment2);
bipedHead.addChild(Shape4); bipedHead.addChild(hatSegment3);
bipedHead.addChild(Shape5); bipedHead.addChild(hatSegment4);
bipedHead.addChild(Shape6); bipedHead.addChild(hatSegment5);
bipedHead.addChild(Shape7); bipedHead.addChild(hatSegment6);
// Makes the robe move with the body
// bipedBody.addChild(robe);
} }
public void render(Entity entity, float f, float f1, float f2, float f3, float f4, float f5){ public void render(Entity entity, float f, float f1, float f2, float f3, float f4, float f5){
if(entity.isInvisible() && !entity.getEntityData().getBoolean(BlockStatue.PETRIFIED_NBT_KEY)
&& !entity.getEntityData().getBoolean(BlockStatue.FROZEN_NBT_KEY)) return;
super.render(entity, f, f1, f2, f3, f4, f5); super.render(entity, f, f1, f2, f3, f4, f5);
setRotationAngles(f, f1, f2, f3, f4, f5, entity); setRotationAngles(f, f1, f2, f3, f4, f5, entity);
this.robe.render(f5);
} }
private void setRotation(ModelRenderer model, float x, float y, float z){ private void setRotation(ModelRenderer model, float x, float y, float z){
@@ -104,6 +115,20 @@ public class ModelWizardArmour extends ModelArmourFixer {
public void setRotationAngles(float f, float f1, float f2, float f3, float f4, float f5, Entity entity){ public void setRotationAngles(float f, float f1, float f2, float f3, float f4, float f5, Entity entity){
super.setRotationAngles(f, f1, f2, f3, f4, f5, entity); super.setRotationAngles(f, f1, f2, f3, f4, f5, entity);
this.robe.showModel = this.bipedBody.showModel;
if(this.isSneak){
//this.robe.rotationPointY = 10.5f;
this.robe.rotationPointZ = 4;
}else{
//this.robe.rotationPointY = 12.5f;
this.robe.rotationPointZ = 0;
}
// The bottom part of the robe takes the y rotation from the rest of the robe but the x/z rotation
// from the average of the two legs
this.robe.rotateAngleX = (this.bipedLeftLeg.rotateAngleX + this.bipedRightLeg.rotateAngleX) / 2f;
this.robe.rotateAngleY = this.bipedBody.rotateAngleY;
this.robe.rotateAngleZ = (this.bipedLeftLeg.rotateAngleZ + this.bipedRightLeg.rotateAngleZ) / 2f;
} }
} }
@@ -1,197 +0,0 @@
package electroblob.wizardry.client.model;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.registry.WizardryItems;
import net.minecraft.client.renderer.block.model.ModelResourceLocation;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.NonNullList;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import net.minecraftforge.oredict.OreDictionary;
/**
* Class responsible for registering all of wizardry's item (and itemblock) models.
*
* @author Electroblob
* @since Wizardry 2.1
*/
@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT)
public final class WizardryItemModels {
@SubscribeEvent
public static void register(ModelRegistryEvent event){
// ItemBlocks
registerItemModel(Item.getItemFromBlock(WizardryBlocks.arcane_workbench));
registerItemModel(Item.getItemFromBlock(WizardryBlocks.crystal_ore));
registerItemModel(Item.getItemFromBlock(WizardryBlocks.crystal_flower));
registerItemModel(Item.getItemFromBlock(WizardryBlocks.transportation_stone));
registerItemModel(Item.getItemFromBlock(WizardryBlocks.crystal_block));
// Items
registerItemModel(WizardryItems.magic_crystal);
registerItemModel(WizardryItems.magic_wand);
registerItemModel(WizardryItems.apprentice_wand);
registerItemModel(WizardryItems.advanced_wand);
registerItemModel(WizardryItems.master_wand);
registerItemModel(WizardryItems.spell_book);
// Wildcard registered for wizard trades.
registerItemModel(WizardryItems.spell_book, OreDictionary.WILDCARD_VALUE, "normal");
registerItemModel(WizardryItems.arcane_tome);
registerItemModel(WizardryItems.wizard_handbook);
registerItemModel(WizardryItems.basic_fire_wand);
registerItemModel(WizardryItems.basic_ice_wand);
registerItemModel(WizardryItems.basic_lightning_wand);
registerItemModel(WizardryItems.basic_necromancy_wand);
registerItemModel(WizardryItems.basic_earth_wand);
registerItemModel(WizardryItems.basic_sorcery_wand);
registerItemModel(WizardryItems.basic_healing_wand);
registerItemModel(WizardryItems.apprentice_fire_wand);
registerItemModel(WizardryItems.apprentice_ice_wand);
registerItemModel(WizardryItems.apprentice_lightning_wand);
registerItemModel(WizardryItems.apprentice_necromancy_wand);
registerItemModel(WizardryItems.apprentice_earth_wand);
registerItemModel(WizardryItems.apprentice_sorcery_wand);
registerItemModel(WizardryItems.apprentice_healing_wand);
registerItemModel(WizardryItems.advanced_fire_wand);
registerItemModel(WizardryItems.advanced_ice_wand);
registerItemModel(WizardryItems.advanced_lightning_wand);
registerItemModel(WizardryItems.advanced_necromancy_wand);
registerItemModel(WizardryItems.advanced_earth_wand);
registerItemModel(WizardryItems.advanced_sorcery_wand);
registerItemModel(WizardryItems.advanced_healing_wand);
registerItemModel(WizardryItems.master_fire_wand);
registerItemModel(WizardryItems.master_ice_wand);
registerItemModel(WizardryItems.master_lightning_wand);
registerItemModel(WizardryItems.master_necromancy_wand);
registerItemModel(WizardryItems.master_earth_wand);
registerItemModel(WizardryItems.master_sorcery_wand);
registerItemModel(WizardryItems.master_healing_wand);
registerItemModel(WizardryItems.spectral_sword);
registerItemModel(WizardryItems.spectral_pickaxe);
registerItemModel(WizardryItems.spectral_bow);
registerItemModel(WizardryItems.mana_flask);
registerItemModel(WizardryItems.storage_upgrade);
registerItemModel(WizardryItems.siphon_upgrade);
registerItemModel(WizardryItems.condenser_upgrade);
registerItemModel(WizardryItems.range_upgrade);
registerItemModel(WizardryItems.duration_upgrade);
registerItemModel(WizardryItems.cooldown_upgrade);
registerItemModel(WizardryItems.blast_upgrade);
registerItemModel(WizardryItems.attunement_upgrade);
registerItemModel(WizardryItems.flaming_axe);
registerItemModel(WizardryItems.frost_axe);
registerItemModel(WizardryItems.firebomb);
registerItemModel(WizardryItems.poison_bomb);
registerItemModel(WizardryItems.blank_scroll);
registerItemModel(WizardryItems.scroll);
registerItemModel(WizardryItems.armour_upgrade);
registerItemModel(WizardryItems.magic_silk);
registerItemModel(WizardryItems.wizard_hat);
registerItemModel(WizardryItems.wizard_robe);
registerItemModel(WizardryItems.wizard_leggings);
registerItemModel(WizardryItems.wizard_boots);
registerItemModel(WizardryItems.wizard_hat_fire);
registerItemModel(WizardryItems.wizard_robe_fire);
registerItemModel(WizardryItems.wizard_leggings_fire);
registerItemModel(WizardryItems.wizard_boots_fire);
registerItemModel(WizardryItems.wizard_hat_ice);
registerItemModel(WizardryItems.wizard_robe_ice);
registerItemModel(WizardryItems.wizard_leggings_ice);
registerItemModel(WizardryItems.wizard_boots_ice);
registerItemModel(WizardryItems.wizard_hat_lightning);
registerItemModel(WizardryItems.wizard_robe_lightning);
registerItemModel(WizardryItems.wizard_leggings_lightning);
registerItemModel(WizardryItems.wizard_boots_lightning);
registerItemModel(WizardryItems.wizard_hat_necromancy);
registerItemModel(WizardryItems.wizard_robe_necromancy);
registerItemModel(WizardryItems.wizard_leggings_necromancy);
registerItemModel(WizardryItems.wizard_boots_necromancy);
registerItemModel(WizardryItems.wizard_hat_earth);
registerItemModel(WizardryItems.wizard_robe_earth);
registerItemModel(WizardryItems.wizard_leggings_earth);
registerItemModel(WizardryItems.wizard_boots_earth);
registerItemModel(WizardryItems.wizard_hat_sorcery);
registerItemModel(WizardryItems.wizard_robe_sorcery);
registerItemModel(WizardryItems.wizard_leggings_sorcery);
registerItemModel(WizardryItems.wizard_boots_sorcery);
registerItemModel(WizardryItems.wizard_hat_healing);
registerItemModel(WizardryItems.wizard_robe_healing);
registerItemModel(WizardryItems.wizard_leggings_healing);
registerItemModel(WizardryItems.wizard_boots_healing);
registerItemModel(WizardryItems.spectral_helmet);
registerItemModel(WizardryItems.spectral_chestplate);
registerItemModel(WizardryItems.spectral_leggings);
registerItemModel(WizardryItems.spectral_boots);
registerItemModel(WizardryItems.smoke_bomb);
registerItemModel(WizardryItems.identification_scroll);
}
// Moved from the proxies
/**
* Registers an item model, using the item's registry name as the model name (this convention makes it easier to
* keep track of everything). Variant defaults to "normal". Registers the model for metadata 0 automatically, plus
* all the other metadata values that the item can take, as defined in
* {@link Item#getSubItems(Item, net.minecraft.creativetab.CreativeTabs, java.util.List)}. The creative tab supplied
* to the aforementioned method will be whichever one the item is in.
*/
private static void registerItemModel(Item item){
if(item.getHasSubtypes()){
NonNullList<ItemStack> items = NonNullList.create();
item.getSubItems(item.getCreativeTab(), items); // Client-only method, but we're client-side so this is OK.
for(ItemStack stack : items){
ModelLoader.setCustomModelResourceLocation(item, stack.getMetadata(),
new ModelResourceLocation(item.getRegistryName(), "inventory"));
}
}
// Changing the last parameter from null to "inventory" fixed the item/block model weirdness. No idea why!
ModelLoader.setCustomModelResourceLocation(item, 0,
new ModelResourceLocation(item.getRegistryName(), "inventory"));
}
/**
* Registers an item model for the given metadata, using the item's registry name as the model name (this convention
* makes it easier to keep track of everything). This is intended for registering additional metadata values which
* aren't displayed in the creative menu, for example the wildcard spell book used in wizard trades.
*/
private static void registerItemModel(Item item, int metadata, String variant){
ModelLoader.setCustomModelResourceLocation(item, metadata,
new ModelResourceLocation(item.getRegistryName(), variant));
}
}
@@ -0,0 +1,343 @@
package electroblob.wizardry.client.model;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.block.BlockCrystal;
import electroblob.wizardry.block.BlockPedestal;
import electroblob.wizardry.block.BlockRunestone;
import electroblob.wizardry.item.IMultiTexturedItem;
import electroblob.wizardry.item.ItemBlockMultiTexturedElemental;
import electroblob.wizardry.item.ItemCrystal;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.registry.WizardryItems;
import net.minecraft.client.renderer.block.model.IBakedModel;
import net.minecraft.client.renderer.block.model.ModelResourceLocation;
import net.minecraft.client.renderer.block.statemap.StateMap;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.NonNullList;
import net.minecraftforge.client.event.ModelBakeEvent;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.oredict.OreDictionary;
/**
* Class responsible for registering all of wizardry's item and block models.
*
* @author Electroblob
* @since Wizardry 2.1
*/
//@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT)
public final class WizardryModels {
private WizardryModels(){} // No instances!
@SubscribeEvent
public static void register(ModelRegistryEvent event){
// ItemBlocks
registerItemModel(Item.getItemFromBlock(WizardryBlocks.arcane_workbench));
registerItemModel(Item.getItemFromBlock(WizardryBlocks.crystal_ore));
registerItemModel(Item.getItemFromBlock(WizardryBlocks.crystal_flower));
registerItemModel(Item.getItemFromBlock(WizardryBlocks.transportation_stone));
ModelLoader.setCustomStateMapper(WizardryBlocks.crystal_block, new StateMap.Builder()
.withName(BlockCrystal.ELEMENT).withSuffix("_crystal_block").build());
// Yay unchecked casting! But we know it's always ok here, and it makes everything much neater.
ItemBlockMultiTexturedElemental crystalBlockItem = (ItemBlockMultiTexturedElemental)Item.getItemFromBlock(WizardryBlocks.crystal_block);
registerMultiTexturedModel(crystalBlockItem);
ModelLoader.setCustomStateMapper(WizardryBlocks.runestone, new StateMap.Builder()
.withName(BlockRunestone.ELEMENT).withSuffix("_runestone").build());
ItemBlockMultiTexturedElemental runestoneItem = (ItemBlockMultiTexturedElemental)Item.getItemFromBlock(WizardryBlocks.runestone);
registerMultiTexturedModel(runestoneItem);
ModelLoader.setCustomStateMapper(WizardryBlocks.runestone_pedestal, new StateMap.Builder()
.withName(BlockPedestal.ELEMENT).ignore(BlockPedestal.NATURAL).withSuffix("_runestone_pedestal").build()); // Don't care about NATURAL property
ItemBlockMultiTexturedElemental pedestalItem = (ItemBlockMultiTexturedElemental)Item.getItemFromBlock(WizardryBlocks.runestone_pedestal);
registerMultiTexturedModel(pedestalItem);
// Items
registerMultiTexturedModel((ItemCrystal)WizardryItems.magic_crystal);
registerItemModel(WizardryItems.magic_wand);
registerItemModel(WizardryItems.apprentice_wand);
registerItemModel(WizardryItems.advanced_wand);
registerItemModel(WizardryItems.master_wand);
registerItemModel(WizardryItems.spell_book);
// Wildcard registered for wizard trades.
registerItemModel(WizardryItems.spell_book, OreDictionary.WILDCARD_VALUE, "normal");
registerItemModel(WizardryItems.arcane_tome);
registerItemModel(WizardryItems.wizard_handbook);
registerItemModel(WizardryItems.novice_fire_wand);
registerItemModel(WizardryItems.novice_ice_wand);
registerItemModel(WizardryItems.novice_lightning_wand);
registerItemModel(WizardryItems.novice_necromancy_wand);
registerItemModel(WizardryItems.novice_earth_wand);
registerItemModel(WizardryItems.novice_sorcery_wand);
registerItemModel(WizardryItems.novice_healing_wand);
registerItemModel(WizardryItems.apprentice_fire_wand);
registerItemModel(WizardryItems.apprentice_ice_wand);
registerItemModel(WizardryItems.apprentice_lightning_wand);
registerItemModel(WizardryItems.apprentice_necromancy_wand);
registerItemModel(WizardryItems.apprentice_earth_wand);
registerItemModel(WizardryItems.apprentice_sorcery_wand);
registerItemModel(WizardryItems.apprentice_healing_wand);
registerItemModel(WizardryItems.advanced_fire_wand);
registerItemModel(WizardryItems.advanced_ice_wand);
registerItemModel(WizardryItems.advanced_lightning_wand);
registerItemModel(WizardryItems.advanced_necromancy_wand);
registerItemModel(WizardryItems.advanced_earth_wand);
registerItemModel(WizardryItems.advanced_sorcery_wand);
registerItemModel(WizardryItems.advanced_healing_wand);
registerItemModel(WizardryItems.master_fire_wand);
registerItemModel(WizardryItems.master_ice_wand);
registerItemModel(WizardryItems.master_lightning_wand);
registerItemModel(WizardryItems.master_necromancy_wand);
registerItemModel(WizardryItems.master_earth_wand);
registerItemModel(WizardryItems.master_sorcery_wand);
registerItemModel(WizardryItems.master_healing_wand);
registerItemModel(WizardryItems.spectral_sword);
registerItemModel(WizardryItems.spectral_pickaxe);
registerItemModel(WizardryItems.spectral_bow);
registerItemModel(WizardryItems.small_mana_flask);
registerItemModel(WizardryItems.medium_mana_flask);
registerItemModel(WizardryItems.large_mana_flask);
registerItemModel(WizardryItems.crystal_shard);
registerItemModel(WizardryItems.grand_crystal);
registerItemModel(WizardryItems.astral_diamond);
registerItemModel(WizardryItems.purifying_elixir);
registerItemModel(WizardryItems.storage_upgrade);
registerItemModel(WizardryItems.siphon_upgrade);
registerItemModel(WizardryItems.condenser_upgrade);
registerItemModel(WizardryItems.range_upgrade);
registerItemModel(WizardryItems.duration_upgrade);
registerItemModel(WizardryItems.cooldown_upgrade);
registerItemModel(WizardryItems.blast_upgrade);
registerItemModel(WizardryItems.attunement_upgrade);
registerItemModel(WizardryItems.melee_upgrade);
registerItemModel(WizardryItems.flaming_axe);
registerItemModel(WizardryItems.frost_axe);
registerItemModel(WizardryItems.firebomb);
registerItemModel(WizardryItems.poison_bomb);
registerItemModel(WizardryItems.smoke_bomb);
registerItemModel(WizardryItems.spark_bomb);
registerItemModel(WizardryItems.blank_scroll);
registerItemModel(WizardryItems.scroll);
registerItemModel(WizardryItems.identification_scroll);
registerItemModel(WizardryItems.armour_upgrade);
registerItemModel(WizardryItems.magic_silk);
registerItemModel(WizardryItems.wizard_hat);
registerItemModel(WizardryItems.wizard_robe);
registerItemModel(WizardryItems.wizard_leggings);
registerItemModel(WizardryItems.wizard_boots);
registerItemModel(WizardryItems.wizard_hat_fire);
registerItemModel(WizardryItems.wizard_robe_fire);
registerItemModel(WizardryItems.wizard_leggings_fire);
registerItemModel(WizardryItems.wizard_boots_fire);
registerItemModel(WizardryItems.wizard_hat_ice);
registerItemModel(WizardryItems.wizard_robe_ice);
registerItemModel(WizardryItems.wizard_leggings_ice);
registerItemModel(WizardryItems.wizard_boots_ice);
registerItemModel(WizardryItems.wizard_hat_lightning);
registerItemModel(WizardryItems.wizard_robe_lightning);
registerItemModel(WizardryItems.wizard_leggings_lightning);
registerItemModel(WizardryItems.wizard_boots_lightning);
registerItemModel(WizardryItems.wizard_hat_necromancy);
registerItemModel(WizardryItems.wizard_robe_necromancy);
registerItemModel(WizardryItems.wizard_leggings_necromancy);
registerItemModel(WizardryItems.wizard_boots_necromancy);
registerItemModel(WizardryItems.wizard_hat_earth);
registerItemModel(WizardryItems.wizard_robe_earth);
registerItemModel(WizardryItems.wizard_leggings_earth);
registerItemModel(WizardryItems.wizard_boots_earth);
registerItemModel(WizardryItems.wizard_hat_sorcery);
registerItemModel(WizardryItems.wizard_robe_sorcery);
registerItemModel(WizardryItems.wizard_leggings_sorcery);
registerItemModel(WizardryItems.wizard_boots_sorcery);
registerItemModel(WizardryItems.wizard_hat_healing);
registerItemModel(WizardryItems.wizard_robe_healing);
registerItemModel(WizardryItems.wizard_leggings_healing);
registerItemModel(WizardryItems.wizard_boots_healing);
registerItemModel(WizardryItems.spectral_helmet);
registerItemModel(WizardryItems.spectral_chestplate);
registerItemModel(WizardryItems.spectral_leggings);
registerItemModel(WizardryItems.spectral_boots);
registerItemModel(WizardryItems.lightning_hammer);
registerItemModel(WizardryItems.ring_condensing);
registerItemModel(WizardryItems.ring_siphoning);
registerItemModel(WizardryItems.ring_battlemage);
registerItemModel(WizardryItems.ring_combustion);
registerItemModel(WizardryItems.ring_fire_melee);
registerItemModel(WizardryItems.ring_fire_biome);
registerItemModel(WizardryItems.ring_disintegration);
registerItemModel(WizardryItems.ring_ice_melee);
registerItemModel(WizardryItems.ring_ice_biome);
registerItemModel(WizardryItems.ring_arcane_frost);
registerItemModel(WizardryItems.ring_shattering);
registerItemModel(WizardryItems.ring_lightning_melee);
registerItemModel(WizardryItems.ring_storm);
registerItemModel(WizardryItems.ring_seeking);
registerItemModel(WizardryItems.ring_hammer);
registerItemModel(WizardryItems.ring_soulbinding);
registerItemModel(WizardryItems.ring_leeching);
registerItemModel(WizardryItems.ring_necromancy_melee);
registerItemModel(WizardryItems.ring_mind_control);
registerItemModel(WizardryItems.ring_poison);
registerItemModel(WizardryItems.ring_earth_melee);
registerItemModel(WizardryItems.ring_earth_biome);
registerItemModel(WizardryItems.ring_full_moon);
registerItemModel(WizardryItems.ring_extraction);
registerItemModel(WizardryItems.ring_mana_return);
registerItemModel(WizardryItems.ring_blockwrangler);
registerItemModel(WizardryItems.ring_conjurer);
registerItemModel(WizardryItems.ring_defender);
registerItemModel(WizardryItems.ring_paladin);
registerItemModel(WizardryItems.ring_interdiction);
registerItemModel(WizardryItems.amulet_arcane_defence);
registerItemModel(WizardryItems.amulet_warding);
registerItemModel(WizardryItems.amulet_wisdom);
registerItemModel(WizardryItems.amulet_fire_protection);
registerItemModel(WizardryItems.amulet_fire_cloaking);
registerItemModel(WizardryItems.amulet_ice_immunity);
registerItemModel(WizardryItems.amulet_ice_protection);
registerItemModel(WizardryItems.amulet_potential);
registerItemModel(WizardryItems.amulet_channeling);
registerItemModel(WizardryItems.amulet_lich);
registerItemModel(WizardryItems.amulet_wither_immunity);
registerItemModel(WizardryItems.amulet_glide);
registerItemModel(WizardryItems.amulet_banishing);
registerItemModel(WizardryItems.amulet_anchoring);
registerItemModel(WizardryItems.amulet_recovery);
registerItemModel(WizardryItems.amulet_transience);
registerItemModel(WizardryItems.amulet_resurrection);
registerItemModel(WizardryItems.amulet_auto_shield);
registerItemModel(WizardryItems.charm_haggler);
registerItemModel(WizardryItems.charm_experience_tome);
registerItemModel(WizardryItems.charm_auto_smelt);
registerItemModel(WizardryItems.charm_lava_walking);
registerItemModel(WizardryItems.charm_storm);
registerItemModel(WizardryItems.charm_minion_health);
registerItemModel(WizardryItems.charm_minion_variants);
registerItemModel(WizardryItems.charm_flight);
registerItemModel(WizardryItems.charm_growth);
registerItemModel(WizardryItems.charm_abseiling);
registerItemModel(WizardryItems.charm_silk_touch);
registerItemModel(WizardryItems.charm_stop_time);
registerItemModel(WizardryItems.charm_light);
registerItemModel(WizardryItems.charm_transportation);
registerItemModel(WizardryItems.charm_feeding);
}
@SubscribeEvent
public static void bake(ModelBakeEvent event){
// MMMmmmm I love the smell of freshly-baked models...
// This stuff is the boilerplate for making runestone overlay render with full brightness
// See https://www.minecraftforge.net/forum/topic/66005-how-do-i-make-a-tileentityspecialrenderer-solved-with-ibakedmodel/
// As usual the Forge documentation is just a description of each class and not an explanation of how to use them
// I had to work out where this goes from the refined storage repo linked in the above thread, which is mixed in
// with a more extensive registration system (which is super neat, but it's overkill for our purposes)
// https://github.com/raoulvdberge/refinedstorage/blob/13d6e7f2b92f41b5009187aa2cbde50dbc72082f/src/main/java/com/raoulvdberge/refinedstorage/proxy/ProxyClient.java#L59
for(ModelResourceLocation location : event.getModelRegistry().getKeys()){
if(location.getNamespace().equals(Wizardry.MODID)){
if(location.getPath().contains("runestone") || location.getPath().contains("runestone_pedestal")){
IBakedModel original = event.getModelRegistry().getObject(location);
event.getModelRegistry().putObject(location, new BakedModelGlowingOverlay(original, "overlay"));
}
}
}
}
// Moved from the proxies
/**
* Registers an item model, using the item's registry name as the model name (this convention makes it easier to
* keep track of everything). Variant defaults to "normal". Registers the model for metadata 0 automatically, plus
* all the other metadata values that the item can take, as defined in
* {@link Item#getSubItems(CreativeTabs, NonNullList)}. The creative tab supplied
* to the aforementioned method will be whichever one the item is in.
*/
private static void registerItemModel(Item item){
if(item.getHasSubtypes()){
NonNullList<ItemStack> items = NonNullList.create();
item.getSubItems(item.getCreativeTab(), items); // Client-only method, but we're client-side so this is OK.
for(ItemStack stack : items){
ModelLoader.setCustomModelResourceLocation(item, stack.getMetadata(),
new ModelResourceLocation(item.getRegistryName(), "inventory"));
}
}
// Changing the last parameter from null to "inventory" fixed the item/block model weirdness. No idea why!
ModelLoader.setCustomModelResourceLocation(item, 0,
new ModelResourceLocation(item.getRegistryName(), "inventory"));
}
/**
* Registers an item model, using the itemstack-sensitive {@link IMultiTexturedItem#getModelName(ItemStack)} as the
* model name. This allows items to change their texture based on metadata/NBT. Variant defaults to "normal". Registers the
* model for metadata 0 automatically, plus all the other metadata values that the item can take, as defined in
* {@link Item#getSubItems(CreativeTabs, NonNullList)}. The creative tab supplied
* to the aforementioned method will be whichever one the item is in.
*/
private static <T extends Item & IMultiTexturedItem> void registerMultiTexturedModel(T item){
if(item.getHasSubtypes()){
NonNullList<ItemStack> items = NonNullList.create();
item.getSubItems(item.getCreativeTab(), items);
for(ItemStack stack : items){
ModelLoader.setCustomModelResourceLocation(item, stack.getMetadata(),
new ModelResourceLocation(item.getModelName(stack), "inventory"));
}
}
}
/**
* Registers an item model for the given metadata, using the item's registry name as the model name (this convention
* makes it easier to keep track of everything). This is intended for registering additional metadata values which
* aren't displayed in the creative menu, for example the wildcard spell book used in wizard trades.
*/
private static void registerItemModel(Item item, int metadata, String variant){
ModelLoader.setCustomModelResourceLocation(item, metadata,
new ModelResourceLocation(item.getRegistryName(), variant));
}
}
@@ -1,13 +1,12 @@
package electroblob.wizardry.client.particle; package electroblob.wizardry.client.particle;
import org.lwjgl.opengl.GL11;
import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper; import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.world.World; import net.minecraft.world.World;
import org.lwjgl.opengl.GL11;
public class ParticleBeam extends ParticleTargeted { public class ParticleBeam extends ParticleTargeted {
@@ -17,10 +16,15 @@ public class ParticleBeam extends ParticleTargeted {
public ParticleBeam(World world, double x, double y, double z){ public ParticleBeam(World world, double x, double y, double z){
super(world, x, y, z); // Does not have a texture! super(world, x, y, z); // Does not have a texture!
this.setRBGColorF(1, 1, 1); this.setRBGColorF(1, 1, 1);
this.setMaxAge(1); this.setMaxAge(0);
this.particleScale = 1; this.particleScale = 1;
} }
@Override
public boolean shouldDisableDepth(){
return true;
}
@Override @Override
public int getFXLayer(){ public int getFXLayer(){
return 3; return 3;
@@ -31,7 +35,7 @@ public class ParticleBeam extends ParticleTargeted {
float scale = this.particleScale; float scale = this.particleScale;
if(this.particleMaxAge > 1){ if(this.particleMaxAge > 0){
float ageFraction = (particleAge + partialTicks - 1)/particleMaxAge; float ageFraction = (particleAge + partialTicks - 1)/particleMaxAge;
// Squaring this makes it look smoother than a linear shrinking effect // Squaring this makes it look smoother than a linear shrinking effect
scale = this.particleScale * (1 - ageFraction*ageFraction); scale = this.particleScale * (1 - ageFraction*ageFraction);
@@ -1,26 +1,18 @@
package electroblob.wizardry.client.particle; package electroblob.wizardry.client.particle;
import org.lwjgl.opengl.GL11;
import electroblob.wizardry.Wizardry; import electroblob.wizardry.Wizardry;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.particle.Particle; import net.minecraft.client.renderer.*;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.GlStateManager.DestFactor; import net.minecraft.client.renderer.GlStateManager.DestFactor;
import net.minecraft.client.renderer.GlStateManager.SourceFactor; import net.minecraft.client.renderer.GlStateManager.SourceFactor;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side; import org.lwjgl.opengl.GL11;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT) //@SideOnly(Side.CLIENT)
public class ParticleBuff extends ParticleWizardry { public class ParticleBuff extends ParticleWizardry {
private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/particle/buff.png"); private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/particle/buff.png");
@@ -28,13 +20,19 @@ public class ParticleBuff extends ParticleWizardry {
public ParticleBuff(World world, double x, double y, double z){ public ParticleBuff(World world, double x, double y, double z){
super(world, x, y, z); super(world, x, y, z);
this.setVelocity(0, 0.27, 0); // Approximately what it was before this.setVelocity(0, 0.162, 0); // Approximately what it was before
this.mirror = world.rand.nextBoolean(); this.mirror = random.nextBoolean();
this.setMaxAge(15); this.setMaxAge(15);
this.setGravity(false); this.setGravity(false);
this.canCollide = false; this.canCollide = false;
} }
@Override
public boolean shouldDisableDepth(){
return true;
}
@Override @Override
public void onUpdate(){ public void onUpdate(){
super.onUpdate(); super.onUpdate();
@@ -59,14 +57,13 @@ public class ParticleBuff extends ParticleWizardry {
@Override @Override
public void renderParticle(BufferBuilder buffer, Entity viewer, float partialTicks, float rotationX, float rotationZ, public void renderParticle(BufferBuilder buffer, Entity viewer, float partialTicks, float rotationX, float rotationZ,
float rotationYZ, float rotationXY, float rotationXZ){ float rotationYZ, float rotationXY, float rotationXZ){
// Copied from ParticleWizardry, needs to be here since we're not calling super
updateEntityLinking(partialTicks);
GlStateManager.pushMatrix(); GlStateManager.pushMatrix();
GlStateManager.pushAttrib(); GlStateManager.pushAttrib();
float scale = 0.6f;
GlStateManager.scale(scale, scale, scale);
if(mirror) GlStateManager.scale(-1, 1, 1);
GlStateManager.enableBlend(); GlStateManager.enableBlend();
GlStateManager.disableAlpha(); GlStateManager.disableAlpha();
GlStateManager.disableCull(); GlStateManager.disableCull();
@@ -94,31 +91,29 @@ public class ParticleBuff extends ParticleWizardry {
buffer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_COLOR); buffer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_COLOR);
// I'm pretty sure these were always static.
Particle.interpPosX = viewer.lastTickPosX + (viewer.posX - viewer.lastTickPosX) * (double)partialTicks;
Particle.interpPosY = viewer.lastTickPosY + (viewer.posY - viewer.lastTickPosY) * (double)partialTicks;
Particle.interpPosZ = viewer.lastTickPosZ + (viewer.posZ - viewer.lastTickPosZ) * (double)partialTicks;
float x = (float)(this.prevPosX + (this.posX - this.prevPosX) * (double)partialTicks - interpPosX); float x = (float)(this.prevPosX + (this.posX - this.prevPosX) * (double)partialTicks - interpPosX);
float y = (float)(this.prevPosY + (this.posY - this.prevPosY) * (double)partialTicks - interpPosY); float y = (float)(this.prevPosY + (this.posY - this.prevPosY) * (double)partialTicks - interpPosY);
float z = (float)(this.prevPosZ + (this.posZ - this.prevPosZ) * (double)partialTicks - interpPosZ); 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 // Increases from 0 to 1 in steps of 0.125 evenly throughout the particle's lifetime
float f = 0.875f - 0.125f * MathHelper.floor((float)this.particleAge/(float)this.particleMaxAge * 8 - 0.000001f); float f = 0.875f - 0.125f * MathHelper.floor((float)this.particleAge/(float)this.particleMaxAge * 8 - 0.000001f);
float g = f + 0.125f; float g = f + 0.125f;
float hrepeat = 1; float hrepeat = 1;
float yScale = 0.7f; float scale = 0.6f;
float yScale = 0.7f * scale;
float dx = mirror ? -scale : scale;
float dz = scale;
buffer.pos(x-1, y-yScale, z-1).tex(0, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); buffer.pos(x-dx, y-yScale, z-dz).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-dx, y+yScale, z-dz).tex(0, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x+1, y-yScale, z-1).tex(0.25*hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); buffer.pos(x+dx, y-yScale, z-dz).tex(0.25*hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x+1, y+yScale, z-1).tex(0.25*hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); buffer.pos(x+dx, y+yScale, z-dz).tex(0.25*hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x+1, y-yScale, z+1).tex(0.5*hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); buffer.pos(x+dx, y-yScale, z+dz).tex(0.5*hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x+1, y+yScale, z+1).tex(0.5*hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); buffer.pos(x+dx, y+yScale, z+dz).tex(0.5*hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x-1, y-yScale, z+1).tex(0.75*hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); buffer.pos(x-dx, y-yScale, z+dz).tex(0.75*hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x-1, y+yScale, z+1).tex(0.75*hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); buffer.pos(x-dx, y+yScale, z+dz).tex(0.75*hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x-1, y-yScale, z-1).tex(hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); buffer.pos(x-dx, y-yScale, z-dz).tex(hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x-1, y+yScale, z-1).tex(hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex(); buffer.pos(x-dx, y+yScale, z-dz).tex(hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
Tessellator.getInstance().draw(); Tessellator.getInstance().draw();
@@ -3,10 +3,8 @@ package electroblob.wizardry.client.particle;
import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT) //@SideOnly(Side.CLIENT)
public class ParticleDarkMagic extends ParticleWizardry { public class ParticleDarkMagic extends ParticleWizardry {
/** Base spell texture index */ /** Base spell texture index */
@@ -1,10 +1,8 @@
package electroblob.wizardry.client.particle; package electroblob.wizardry.client.particle;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT) //@SideOnly(Side.CLIENT)
public class ParticleDust extends ParticleWizardry { public class ParticleDust extends ParticleWizardry {
public ParticleDust(World world, double x, double y, double z){ public ParticleDust(World world, double x, double y, double z){
@@ -24,7 +22,7 @@ public class ParticleDust extends ParticleWizardry {
this.prevPosX = this.posX; this.prevPosX = this.posX;
this.prevPosY = this.posY; this.prevPosY = this.posY;
this.prevPosZ = this.posZ; this.prevPosZ = this.posZ;
// this.moveEntity(this.motionX, this.motionY, this.motionZ); this.move(this.motionX, this.motionY, this.motionZ);
if(this.particleMaxAge-- <= 0){ if(this.particleMaxAge-- <= 0){
this.setExpired(); this.setExpired();
@@ -19,10 +19,15 @@ public class ParticleFlash extends ParticleWizardry {
this.particleScale = 0.6f; // 7.1f is the value used in fireworks this.particleScale = 0.6f; // 7.1f is the value used in fireworks
this.particleMaxAge = 6; this.particleMaxAge = 6;
} }
@Override @Override
public void renderParticle(BufferBuilder buffer, Entity entityIn, float partialTicks, float rotationX, float rotationZ, float rotationYZ, float rotationXY, float rotationXZ){ public boolean shouldDisableDepth(){
float f4 = particleScale * MathHelper.sin(((float)this.particleAge + partialTicks - 1.0F)/particleMaxAge * (float)Math.PI); return true; // Well this fixes everything... let's hope it doesn't cause any side-effects!
}
@Override
public void drawParticle(BufferBuilder buffer, Entity entityIn, float partialTicks, float rotationX, float rotationZ, float rotationYZ, float rotationXY, float rotationXZ){
float f4 = particleScale * MathHelper.sin(((float)this.particleAge + partialTicks - 1.0F)/particleMaxAge * (float)Math.PI);
this.setAlphaF(0.6F - ((float)this.particleAge + partialTicks - 1.0F)/particleMaxAge * 0.5F); this.setAlphaF(0.6F - ((float)this.particleAge + partialTicks - 1.0F)/particleMaxAge * 0.5F);
float f5 = (float)(this.prevPosX + (this.posX - this.prevPosX) * (double)partialTicks - interpPosX); float f5 = (float)(this.prevPosX + (this.posX - this.prevPosX) * (double)partialTicks - interpPosX);
float f6 = (float)(this.prevPosY + (this.posY - this.prevPosY) * (double)partialTicks - interpPosY); float f6 = (float)(this.prevPosY + (this.posY - this.prevPosY) * (double)partialTicks - interpPosY);
@@ -34,7 +39,7 @@ public class ParticleFlash extends ParticleWizardry {
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.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.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(); 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 @Override
public int getBrightnessForRender(float partialTicks){ public int getBrightnessForRender(float partialTicks){
@@ -6,9 +6,8 @@ import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT) //@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT) @Mod.EventBusSubscriber(Side.CLIENT)
public class ParticleIce extends ParticleWizardry { public class ParticleIce extends ParticleWizardry {
@@ -6,9 +6,8 @@ import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT) //@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT) @Mod.EventBusSubscriber(Side.CLIENT)
public class ParticleLeaf extends ParticleWizardry { public class ParticleLeaf extends ParticleWizardry {
@@ -24,7 +23,7 @@ public class ParticleLeaf extends ParticleWizardry {
this.particleGravity = 0; this.particleGravity = 0;
this.canCollide = true; this.canCollide = true;
// Produces a variety of browns and greens // Produces a variety of browns and greens
this.setRBGColorF(0.1f + 0.3f * world.rand.nextFloat(), 0.5f + 0.3f * world.rand.nextFloat(), 0.1f); this.setRBGColorF(0.1f + 0.3f * random.nextFloat(), 0.5f + 0.3f * random.nextFloat(), 0.1f);
} }
@Override @Override
@@ -1,15 +1,12 @@
package electroblob.wizardry.client.particle; package electroblob.wizardry.client.particle;
import java.util.Random;
import org.lwjgl.opengl.GL11;
import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper; import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.world.World; import net.minecraft.world.World;
import org.lwjgl.opengl.GL11;
public class ParticleLightning extends ParticleTargeted { public class ParticleLightning extends ParticleTargeted {
@@ -28,10 +25,6 @@ public class ParticleLightning extends ParticleTargeted {
/** Number of ticks to wait before the arc changes shape again. */ /** Number of ticks to wait before the arc changes shape again. */
private static final int UPDATE_PERIOD = 1; private static final int UPDATE_PERIOD = 1;
/** A random long value used by the renderer as a seed to generate its vertices from, ensuring they remain the same
* across multiple frames. Not synced. */
public final long seed;
public ParticleLightning(World world, double x, double y, double z){ public ParticleLightning(World world, double x, double y, double z){
super(world, x, y, z); // Does not have a texture! super(world, x, y, z); // Does not have a texture!
seed = this.rand.nextLong(); seed = this.rand.nextLong();
@@ -39,7 +32,12 @@ public class ParticleLightning extends ParticleTargeted {
this.setMaxAge(3); this.setMaxAge(3);
this.particleScale = 1; this.particleScale = 1;
} }
@Override
public boolean shouldDisableDepth(){
return true;
}
@Override @Override
public int getFXLayer(){ public int getFXLayer(){
return 3; return 3;
@@ -67,7 +65,7 @@ public class ParticleLightning extends ParticleTargeted {
// Creates a random from the arc's seed field + the number of ticks it has existed/the update period. // Creates a random from the arc's seed field + the number of ticks it has existed/the update period.
// By using a seed, we can ensure the vertex positions and forks are identical a) for each layer, even // By using a seed, we can ensure the vertex positions and forks are identical a) for each layer, even
// though they are rendered sequentially, and b) across many frames (and ticks, if updateTime > 1). // though they are rendered sequentially, and b) across many frames (and ticks, if updateTime > 1).
Random random = new Random(this.seed + this.particleAge/UPDATE_PERIOD); random.setSeed(this.seed + this.particleAge/UPDATE_PERIOD);
// numberOfSegments-1 because the last segment is handled separately. // numberOfSegments-1 because the last segment is handled separately.
for(int i=0; i<numberOfSegments-1; i++){ for(int i=0; i<numberOfSegments-1; i++){
@@ -6,9 +6,8 @@ import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT) //@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT) @Mod.EventBusSubscriber(Side.CLIENT)
public class ParticleLightningPulse extends ParticleWizardry { public class ParticleLightningPulse extends ParticleWizardry {
@@ -27,7 +26,12 @@ public class ParticleLightningPulse extends ParticleWizardry {
this.pitch = 90; this.pitch = 90;
this.yaw = 0; this.yaw = 0;
} }
@Override
public boolean shouldDisableDepth(){
return true;
}
@SubscribeEvent @SubscribeEvent
public static void onTextureStitchEvent(TextureStitchEvent.Pre event){ public static void onTextureStitchEvent(TextureStitchEvent.Pre event){
for(ResourceLocation texture : TEXTURES){ for(ResourceLocation texture : TEXTURES){
@@ -1,10 +1,8 @@
package electroblob.wizardry.client.particle; package electroblob.wizardry.client.particle;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT) //@SideOnly(Side.CLIENT)
public class ParticleMagicBubble extends ParticleWizardry { public class ParticleMagicBubble extends ParticleWizardry {
public ParticleMagicBubble(World world, double x, double y, double z){ public ParticleMagicBubble(World world, double x, double y, double z){
@@ -1,46 +1,36 @@
package electroblob.wizardry.client.particle; package electroblob.wizardry.client.particle;
import net.minecraft.client.particle.Particle; import net.minecraft.util.ResourceLocation;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.entity.Entity;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT) //@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT)
public class ParticleMagicFlame extends ParticleWizardry { public class ParticleMagicFlame extends ParticleWizardry {
/** The scale of the flame particle */ // 4 different animation strips, 8 frames in each strip
private float flameScale; private static final ResourceLocation[][] TEXTURES = generateTextures("flame", 4, 8);
public ParticleMagicFlame(World world, double x, double y, double z){ public ParticleMagicFlame(World world, double x, double y, double z){
super(world, x, y, z);
this.flameScale = 1 + world.rand.nextFloat(); super(world, x, y, z, TEXTURES[world.rand.nextInt(TEXTURES.length)]);
this.setRBGColorF(1, 1, 1); this.setRBGColorF(1, 1, 1);
this.particleAlpha = 1; this.particleAlpha = 1;
this.particleMaxAge = (int)(2.0D / (Math.random() * 0.8D + 0.2D)); this.particleMaxAge = 12 + rand.nextInt(4);
this.shaded = false; this.shaded = false;
// IDEA: Make the particles for ray spells collide properly and not spawn on the other side of stuff. this.canCollide = true;
this.canCollide = false;
this.setParticleTextureIndex(48);
} }
@Override @SubscribeEvent
public void renderParticle(BufferBuilder buffer, Entity entity, float partialTicks, float rotationX, float rotationZ, public static void onTextureStitchEvent(TextureStitchEvent.Pre event){
float rotationYZ, float rotationXY, float rotationXZ){ for(ResourceLocation[] array : TEXTURES){
float f6 = ((float)this.particleAge + partialTicks) / (float)this.particleMaxAge; for(ResourceLocation texture : array){
this.particleScale = this.flameScale * (1.0F - f6 * f6 * 0.5F); event.getMap().registerSprite(texture);
super.renderParticle(buffer, entity, partialTicks, rotationX, rotationZ, rotationYZ, rotationXY, rotationXZ); }
} }
@Override
public int getBrightnessForRender(float par1){
return 256;
}
@Override
public Particle multipleParticleScaleBy(float scale){
this.flameScale *= scale;
return super.multipleParticleScaleBy(scale);
} }
} }
@@ -8,9 +8,8 @@ import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT) //@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT) @Mod.EventBusSubscriber(Side.CLIENT)
public class ParticlePath extends ParticleWizardry { public class ParticlePath extends ParticleWizardry {
@@ -34,6 +33,11 @@ public class ParticlePath extends ParticleWizardry {
this.setRBGColorF(1, 1, 1); this.setRBGColorF(1, 1, 1);
} }
@Override
public boolean shouldDisableDepth(){
return true;
}
@Override @Override
public void onUpdate(){ public void onUpdate(){
@@ -8,9 +8,8 @@ import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT) //@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT) @Mod.EventBusSubscriber(Side.CLIENT)
public class ParticleScorch extends ParticleWizardry { public class ParticleScorch extends ParticleWizardry {
@@ -27,7 +26,12 @@ public class ParticleScorch extends ParticleWizardry {
this.setRBGColorF(0, 0, 0); this.setRBGColorF(0, 0, 0);
this.shaded = false; this.shaded = false;
} }
@Override
public boolean shouldDisableDepth(){
return true;
}
@Override @Override
public void setRBGColorF(float r, float g, float b){ public void setRBGColorF(float r, float g, float b){
super.setRBGColorF(r, g, b); super.setRBGColorF(r, g, b);
@@ -48,7 +52,7 @@ public class ParticleScorch extends ParticleWizardry {
// Fading // Fading
if(this.particleAge > this.particleMaxAge/2){ if(this.particleAge > this.particleMaxAge/2){
this.setAlphaF(1 - ((float)this.particleAge - (float)(this.particleMaxAge/2)) / (float)this.particleMaxAge); this.setAlphaF(1 - ((float)this.particleAge - this.particleMaxAge/2f) / (this.particleMaxAge/2f));
} }
EnumFacing facing = EnumFacing.fromAngle(yaw); EnumFacing facing = EnumFacing.fromAngle(yaw);
@@ -6,9 +6,8 @@ import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT) //@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT) @Mod.EventBusSubscriber(Side.CLIENT)
public class ParticleSnow extends ParticleWizardry { public class ParticleSnow extends ParticleWizardry {
@@ -24,7 +23,7 @@ public class ParticleSnow extends ParticleWizardry {
this.canCollide = true; this.canCollide = true;
this.setMaxAge(40 + rand.nextInt(10)); this.setMaxAge(40 + rand.nextInt(10));
// Produces a variety of light blues and whites // Produces a variety of light blues and whites
this.setRBGColorF(0.9f + 0.1f * world.rand.nextFloat(), 0.95f + 0.05f * world.rand.nextFloat(), 1); this.setRBGColorF(0.9f + 0.1f * random.nextFloat(), 0.95f + 0.05f * random.nextFloat(), 1);
} }
@SubscribeEvent @SubscribeEvent
@@ -6,9 +6,8 @@ import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT) //@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT) @Mod.EventBusSubscriber(Side.CLIENT)
public class ParticleSpark extends ParticleWizardry { public class ParticleSpark extends ParticleWizardry {
@@ -25,14 +24,18 @@ public class ParticleSpark extends ParticleWizardry {
this.canCollide = false; this.canCollide = false;
this.setMaxAge(3); // Lifetime defaults to 3 (and is very unlikely to be changed) this.setMaxAge(3); // Lifetime defaults to 3 (and is very unlikely to be changed)
} }
@Override
public boolean shouldDisableDepth(){
return true;
}
// May no longer be necessary, ParticleManager seems to enable blending now // May no longer be necessary, ParticleManager seems to enable blending now
// @Override // @Override
// public void applyGLStateChanges(){ // public void applyGLStateChanges(){
// GlStateManager.enableBlend(); // GlStateManager.enableBlend();
// GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); // GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
// // TESTME: Are these two actually necessary?
// GlStateManager.disableLighting(); // GlStateManager.disableLighting();
// OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240, 240); // OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240, 240);
// } // }
@@ -6,9 +6,8 @@ import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT) //@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT) @Mod.EventBusSubscriber(Side.CLIENT)
public class ParticleSparkle extends ParticleWizardry { public class ParticleSparkle extends ParticleWizardry {
@@ -0,0 +1,131 @@
package electroblob.wizardry.client.particle;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.entity.Entity;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import org.lwjgl.opengl.GL11;
public class ParticleSphere extends ParticleWizardry {
public ParticleSphere(World world, double x, double y, double z){
super(world, x, y, z);
this.setRBGColorF(1, 1, 1);
this.particleMaxAge = 5;
this.particleAlpha = 0.8f;
}
@Override
public boolean shouldDisableDepth(){
return true;
}
@Override
public int getFXLayer(){
return 3;
}
@Override
public void onUpdate(){
super.onUpdate();
}
@Override
public void renderParticle(BufferBuilder buffer, Entity viewer, float partialTicks, float rotationX, float rotationZ, float rotationYZ,
float rotationXY, float rotationXZ){
// Copied from ParticleWizardry, needs to be here since we're not calling super
updateEntityLinking(partialTicks);
float x = (float)(this.prevPosX + (this.posX - this.prevPosX) * (double)partialTicks);
float y = (float)(this.prevPosY + (this.posY - this.prevPosY) * (double)partialTicks);
float z = (float)(this.prevPosZ + (this.posZ - this.prevPosZ) * (double)partialTicks);
GlStateManager.pushMatrix();
GlStateManager.translate(x - interpPosX, y - interpPosY, z - interpPosZ);
GlStateManager.disableLighting();
GlStateManager.enableBlend();
GlStateManager.enableCull();
GlStateManager.disableTexture2D();
GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE);
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f);
float latStep = (float)Math.PI/20;
float longStep = (float)Math.PI/20;
float sphereRadius = this.particleScale * (this.particleAge + partialTicks - 1) / this.particleMaxAge;
float alpha = this.particleAlpha * (1 - (this.particleAge + partialTicks - 1) / this.particleMaxAge);
drawSphere(Tessellator.getInstance(), buffer, sphereRadius, latStep, longStep, true, particleRed, particleGreen, particleBlue, alpha);
drawSphere(Tessellator.getInstance(), buffer, sphereRadius, latStep, longStep, false, particleRed, particleGreen, particleBlue, alpha);
GlStateManager.enableTexture2D();
GlStateManager.enableLighting();
GlStateManager.disableCull();
GlStateManager.disableBlend();
GlStateManager.popMatrix();
}
@Override
public int getBrightnessForRender(float partialTicks){
return 15728880;
}
/**
* Draws a sphere (using lat/long triangles) with the given parameters.
* @param radius The radius of the sphere.
* @param latStep The latitude step; smaller is smoother but increases performance cost.
* @param longStep The longitude step; smaller is smoother but increases performance cost.
* @param inside Whether to draw the outside or the inside of the sphere.
* @param r The red component of the sphere colour.
* @param g The green component of the sphere colour.
* @param b The blue component of the sphere colour.
* @param a The alpha component of the sphere colour.
*/
private static void drawSphere(Tessellator tessellator, BufferBuilder buffer, float radius, float latStep, float longStep, boolean inside, float r, float g, float b, float a){
buffer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_COLOR);
boolean goingUp = inside;
buffer.pos(0, goingUp ? -radius : radius, 0).color(r, g, b, a).endVertex(); // Start at the north pole
for(float longitude = -(float)Math.PI; longitude <= (float)Math.PI; longitude += longStep){
// Leave the poles out since they only have a single point per stack instead of two
for(float theta = (float)Math.PI/2 - latStep; theta >= -(float)Math.PI/2 + latStep; theta -= latStep){
float latitude = goingUp ? -theta : theta;
float hRadius = radius * MathHelper.cos(latitude);
float vy = radius * MathHelper.sin(latitude);
float vx = hRadius * MathHelper.sin(longitude);
float vz = hRadius * MathHelper.cos(longitude);
buffer.pos(vx, vy, vz).color(r, g, b, a).endVertex();
vx = hRadius * MathHelper.sin(longitude + longStep);
vz = hRadius * MathHelper.cos(longitude + longStep);
buffer.pos(vx, vy, vz).color(r, g, b, a).endVertex();
}
// The next pole
buffer.pos(0, goingUp ? radius : -radius, 0).color(r, g, b, a).endVertex();
goingUp = !goingUp;
}
tessellator.draw();
}
}
@@ -0,0 +1,132 @@
package electroblob.wizardry.client.particle;
import electroblob.wizardry.Wizardry;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.*;
import net.minecraft.client.renderer.GlStateManager.DestFactor;
import net.minecraft.client.renderer.GlStateManager.SourceFactor;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.entity.Entity;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import org.lwjgl.opengl.GL11;
//@SideOnly(Side.CLIENT)
public class ParticleSummon extends ParticleWizardry {
private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/particle/summon.png");
private final boolean mirror;
public ParticleSummon(World world, double x, double y, double z){
super(world, x, y, z);
this.mirror = random.nextBoolean();
this.setMaxAge(10);
this.setGravity(false);
this.canCollide = false;
}
// @Override
// public void onUpdate(){
// super.onUpdate();
// if(this.particleAge > this.particleMaxAge/2) this.particleAlpha = 2f - 2f*(float)this.particleAge/(float)this.particleMaxAge;
// }
/* There are 4 layers of particles, specified as 0-3 by the method below. - Layer 0 causes the normal particles.png
* to be bound to the render engine for normal particles. - Layer 1 causes the block textures to be bound to the
* render engine for digging fx and falling fx. - Layer 2 causes the item textures to be bound to the render engine
* for tool breaking fx, snowballpoofs, slime particles, etc. - Layer 3 is not used in vanilla minecraft and was
* presumably added by forge for exactly this reason. This means no texture is bound by vanilla minecraft, meaning
* you are free to do as you wish without possibly overwriting vanilla particles. Mod particles won't be overwritten
* anyway since they bind their own textures. It is of course important to bind the texture every time you render a
* custom particle, but I don't see how you could do it any other way, since you don't have access to
* EffectRenderer. */
@Override
public int getFXLayer(){
// This can only be 0-3 or it will cause an ArrayIndexOutOfBoundsException in EffectRenderer.
return 3;
}
@Override
public void renderParticle(BufferBuilder buffer, Entity viewer, float partialTicks, float rotationX, float rotationZ,
float rotationYZ, float rotationXY, float rotationXZ){
// Copied from ParticleWizardry, needs to be here since we're not calling super
updateEntityLinking(partialTicks);
GlStateManager.pushMatrix();
GlStateManager.pushAttrib();
float scale = 0.6f;
GlStateManager.scale(scale, scale, scale);
if(mirror) GlStateManager.scale(-1, 1, 1);
GlStateManager.enableBlend();
GlStateManager.disableAlpha();
GlStateManager.disableCull();
GlStateManager.disableLighting();
GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE);
// Makes the particle colour add to the colour of the texture pixels, rather than the default multiplying
GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_ADD);
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f);
GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT);
GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT);
// Does the texture translation wrapping thing (the cool stuff)
// GlStateManager.matrixMode(GL11.GL_TEXTURE);
// GlStateManager.loadIdentity();
//
// GlStateManager.translate((this.particleAge + partialTicks)/(float)this.particleMaxAge * -2, 0, 0);
//
// GlStateManager.matrixMode(GL11.GL_MODELVIEW);
RenderHelper.disableStandardItemLighting();
Minecraft.getMinecraft().getTextureManager().bindTexture(TEXTURE);
buffer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_COLOR);
float x = (float)(this.prevPosX + (this.posX - this.prevPosX) * (double)partialTicks - interpPosX);
float y = (float)(this.prevPosY + (this.posY - this.prevPosY) * (double)partialTicks - interpPosY);
float z = (float)(this.prevPosZ + (this.posZ - this.prevPosZ) * (double)partialTicks - interpPosZ);
// Increases from 0 to 1 in steps of 0.125 evenly throughout the particle's lifetime
float f = 0.125f * MathHelper.floor((float)this.particleAge/(float)this.particleMaxAge * 8 - 0.000001f);
float g = f + 0.125f;
float hrepeat = 1;
float yScale = 3f;
this.setRBGColorF(1, 1, 1);
buffer.pos(x-1, y, z-1).tex(0, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x-1, y+yScale, z-1).tex(0, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x+1, y, z-1).tex(0.25*hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x+1, y+yScale, z-1).tex(0.25*hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x+1, y, z+1).tex(0.5*hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x+1, y+yScale, z+1).tex(0.5*hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x-1, y, z+1).tex(0.75*hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x-1, y+yScale, z+1).tex(0.75*hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x-1, y, z-1).tex(hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x-1, y+yScale, z-1).tex(hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
Tessellator.getInstance().draw();
// Undoes the texture transformations
// GlStateManager.matrixMode(GL11.GL_TEXTURE);
// GlStateManager.loadIdentity();
// GlStateManager.matrixMode(GL11.GL_MODELVIEW);
GlStateManager.disableBlend();
GlStateManager.enableAlpha();
GlStateManager.enableCull();
GlStateManager.enableLighting();
// Reverses the colour addition change from before
GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);
GlStateManager.popAttrib();
GlStateManager.popMatrix();
}
}
@@ -1,23 +1,28 @@
package electroblob.wizardry.client.particle; package electroblob.wizardry.client.particle;
import javax.annotation.Nullable;
import org.lwjgl.opengl.GL11;
import electroblob.wizardry.Wizardry; import electroblob.wizardry.Wizardry;
import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.Tessellator;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World; import net.minecraft.world.World;
import org.lwjgl.opengl.GL11;
import javax.annotation.Nullable;
/** Superclass for particles with a second target entity or target position. */ /** Superclass for particles with a second target entity or target position. */
public abstract class ParticleTargeted extends ParticleWizardry { public abstract class ParticleTargeted extends ParticleWizardry {
protected double targetX; protected double targetX;
protected double targetY; protected double targetY;
protected double targetZ; protected double targetZ;
protected double targetVelX;
protected double targetVelY;
protected double targetVelZ;
protected double length;
/** The target this particle is linked to. The particle will stretch to touch this entity. */ /** The target this particle is linked to. The particle will stretch to touch this entity. */
@Nullable @Nullable
@@ -33,43 +38,83 @@ public abstract class ParticleTargeted extends ParticleWizardry {
this.targetY = y; this.targetY = y;
this.targetZ = z; this.targetZ = z;
} }
@Override
public void setTargetVelocity(double vx, double vy, double vz){
this.targetVelX = vx;
this.targetVelY = vy;
this.targetVelZ = vz;
}
@Override @Override
public void setTargetEntity(Entity target){ public void setTargetEntity(Entity target){
this.target = target; this.target = target;
} }
@Override
public void setLength(double length){
this.length = length;
}
@Override
public void onUpdate(){
super.onUpdate();
if(!Double.isNaN(targetVelX) && !Double.isNaN(targetVelY) && !Double.isNaN(targetVelZ)){
this.targetX += this.targetVelX;
this.targetY += this.targetVelY;
this.targetZ += this.targetVelZ;
}
}
@Override @Override
public void renderParticle(BufferBuilder buffer, Entity viewer, float partialTicks, float rotationX, float rotationZ, float rotationYZ, public void renderParticle(BufferBuilder buffer, Entity viewer, float partialTicks, float rotationX, float rotationZ, float rotationYZ,
float rotationXY, float rotationXZ){ float rotationXY, float rotationXZ){
// Copied from ParticleWizardry, needs to be here since we're not calling super
updateEntityLinking(partialTicks);
float x = (float)(this.prevPosX + (this.posX - this.prevPosX) * (double)partialTicks);
float y = (float)(this.prevPosY + (this.posY - this.prevPosY) * (double)partialTicks);
float z = (float)(this.prevPosZ + (this.posZ - this.prevPosZ) * (double)partialTicks);
if(this.target != null){ if(this.target != null){
this.targetX = this.target.posX;
this.targetY = this.target.getEntityBoundingBox().minY + target.height/2; this.targetX = this.target.prevPosX + (this.target.posX - this.target.prevPosX) * partialTicks;
this.targetZ = this.target.posZ; double correction = this.target.getEntityBoundingBox().minY - this.target.posY;
this.targetY = this.target.prevPosY + (this.target.posY - this.target.prevPosY) * partialTicks
+ target.height/2 + correction;
this.targetZ = this.target.prevPosZ + (this.target.posZ - this.target.prevPosZ) * partialTicks;
}else if(this.entity != null && this.length > 0){
Vec3d look = entity.getLook(partialTicks).scale(length);
this.targetX = x + look.x;
this.targetY = y + look.y;
this.targetZ = z + look.z;
} }
if(Double.isNaN(targetX) || Double.isNaN(targetY) || Double.isNaN(targetZ)){ if(Double.isNaN(targetX) || Double.isNaN(targetY) || Double.isNaN(targetZ)){
Wizardry.logger.warn("Attempted to render a targeted particle, but neither its target entity nor target" Wizardry.logger.warn("Attempted to render a targeted particle, but neither its target entity nor target"
+ "position was set!"); + "position was set, and it either had no length assigned or was not linked to an entity!");
return; return;
} }
// I'm pretty sure these were always static.
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;
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);
GlStateManager.pushMatrix(); GlStateManager.pushMatrix();
GlStateManager.translate(x, y, z); GlStateManager.translate(x - interpPosX, y - interpPosY, z - interpPosZ);
double dx = this.targetX - this.posX; double dx = this.targetX - x;
double dy = this.targetY - this.posY; double dy = this.targetY - y;
double dz = this.targetZ - this.posZ; double dz = this.targetZ - z;
// No need for previous tick target positions and all that stuff since this is the only place they're used
// and interpolating like this works just as well
if(!Double.isNaN(targetVelX) && !Double.isNaN(targetVelY) && !Double.isNaN(targetVelZ)){
dx += partialTicks * this.targetVelX;
dy += partialTicks * this.targetVelY;
dz += partialTicks * this.targetVelZ;
}
// The distance from origin to endpoint // The distance from origin to endpoint
double length = Math.sqrt(dx*dx+dy*dy+dz*dz); double length = Math.sqrt(dx*dx+dy*dy+dz*dz);
@@ -87,12 +132,12 @@ public abstract class ParticleTargeted extends ParticleWizardry {
GlStateManager.popMatrix(); GlStateManager.popMatrix();
} }
/** Called from {@link ParticleTargeted#renderParticle(BufferBuilder, Entity, float, float, float, float, float, float)}, /** 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 * once the appropriate calculations and transformations have been applied, to actually render the particle. Subclasses
* override this <i>instead</i> of overriding {@code renderParticle} directly, and inside render the particle <b>along * override this <i>instead</i> of overriding {@code renderParticle} directly, and inside render the particle <b>along
* the z-axis, starting at (0, 0, 0)</b> - it will be translated and rotated automatically. * the z-axis, starting at (0, 0, 0)</b> - it will be translated and rotated automatically.
* <p> * <p></p>
* <i>N.B. Other than transformations, no GL state changes are applied; these should be done within this method.</i> * <i>N.B. Other than transformations, no GL state changes are applied; these should be done within this method.</i>
* *
* @param tessellator A reference to the tessellator, for convenience. * @param tessellator A reference to the tessellator, for convenience.
@@ -3,14 +3,13 @@ package electroblob.wizardry.client.particle;
import net.minecraft.block.state.IBlockState; import net.minecraft.block.state.IBlockState;
import net.minecraft.client.particle.ParticleDigging; import net.minecraft.client.particle.ParticleDigging;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT) //@SideOnly(Side.CLIENT)
public class ParticleTornado extends ParticleDigging { public class ParticleTornado extends ParticleDigging {
private double angle; private float angle;
private double radius; private double radius;
private double speed; private double speed;
/** Velocity of the tornado itself; in other words the velocity of the point the particle circles around. */ /** Velocity of the tornado itself; in other words the velocity of the point the particle circles around. */
@@ -20,9 +19,9 @@ public class ParticleTornado extends ParticleDigging {
public ParticleTornado(World world, int maxAge, double originX, double originZ, double radius, double yPos, public ParticleTornado(World world, int maxAge, double originX, double originZ, double radius, double yPos,
double velX, double velZ, IBlockState block){ double velX, double velZ, IBlockState block){
super(world, 0, 0, 0, 0, 0, 0, block); super(world, 0, 0, 0, 0, 0, 0, block);
this.angle = this.rand.nextDouble() * Math.PI * 2; float angle = this.rand.nextFloat() * (float)Math.PI * 2;
double x = originX - Math.cos(angle) * radius; double x = originX - MathHelper.cos(angle) * radius;
double z = originZ + radius * Math.sin(angle); double z = originZ + radius * MathHelper.sin(angle);
this.radius = radius; this.radius = radius;
this.setPosition(x, yPos, z); this.setPosition(x, yPos, z);
this.prevPosX = x; this.prevPosX = x;
@@ -66,8 +65,8 @@ public class ParticleTornado extends ParticleDigging {
// v = r times omega; therefore the normalised velocity vector needs to be r times the angle increment / 2 pi. // v = r times omega; therefore the normalised velocity vector needs to be r times the angle increment / 2 pi.
this.angle += omega; this.angle += omega;
this.motionZ = radius * omega * Math.cos(angle); this.motionZ = radius * omega * MathHelper.cos(angle);
this.motionX = radius * omega * Math.sin(angle); this.motionX = radius * omega * MathHelper.sin(angle);
this.move(motionX + velX, 0, motionZ + velZ); this.move(motionX + velX, 0, motionZ + velZ);
if(this.particleAge > this.particleMaxAge / 2){ if(this.particleAge > this.particleMaxAge / 2){
@@ -0,0 +1,154 @@
package electroblob.wizardry.client.particle;
import electroblob.wizardry.Wizardry;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.entity.Entity;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import org.lwjgl.opengl.GL11;
//@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT)
public class ParticleVine extends ParticleTargeted {
/** Half the width of the vine. */
private static final float THICKNESS = 0.02f;
private static final float LEAF_SPACING = 0.5f;
private static final float SEGMENT_LENGTH = 1;
private static final ResourceLocation STEM_TEXTURE = new ResourceLocation(Wizardry.MODID, "particle/vine");
private static final ResourceLocation[] LEAF_TEXTURES = generateTextures("vine_leaf", 5);
public ParticleVine(World world, double x, double y, double z){
super(world, x, y, z, STEM_TEXTURE);
//this.setRBGColorF(1, 1, 1);
this.setMaxAge(0);
this.particleScale = 1;
this.setRBGColorF(0.2f, 0.65f, 0f);
}
@Override
public void renderParticle(BufferBuilder buffer, Entity viewer, float partialTicks, float rotationX, float rotationZ, float rotationYZ, float rotationXY, float rotationXZ){
// When using FX layer 1 the BufferBuilder is already drawing... but in the wrong mode :/
Tessellator.getInstance().draw();
super.renderParticle(buffer, viewer, partialTicks, rotationX, rotationZ, rotationYZ, rotationXY, rotationXZ);
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.PARTICLE_POSITION_TEX_COLOR_LMAP);
}
@Override
protected void draw(Tessellator tessellator, double length, float partialTicks){
random.setSeed(seed); // Reset the random so we get the same sequence of numbers each frame
float scale = this.particleScale;
BufferBuilder buffer = tessellator.getBuffer();
GlStateManager.disableLighting();
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f);
// Hmmmm we can't get the texture to tile using OpenGL texture space because it's on a sprite sheet...
// Solution: Draw loads of boxes. Simple!
// (Since there aren't going to be that many of these particles around we could have not used sprite sheets
// and done the OpenGL texture space thing, but this is kinda easier)
// Everything is drawn back-to-front so it looks like the vine is growing from the origin, not the endpoint
int i = 0;
while(i + SEGMENT_LENGTH < length){
drawShearedBox(tessellator, 0, 0, length-i, 0, 0, length-i-SEGMENT_LENGTH, THICKNESS * scale,
particleRed, particleGreen, particleBlue, particleAlpha);
i += SEGMENT_LENGTH;
}
drawShearedBox(tessellator, 0, 0, length-i, 0, 0, 0, THICKNESS * scale,
particleRed, particleGreen, particleBlue, particleAlpha);
for(double l=length; l>0; l-=LEAF_SPACING){
GlStateManager.pushMatrix();
GlStateManager.rotate(random.nextInt(4) * 90, 0, 0, 1);
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR);
TextureAtlasSprite leaf = Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite(
LEAF_TEXTURES[random.nextInt(LEAF_TEXTURES.length)].toString());
float w = 16 * THICKNESS * scale;
float u1 = leaf.getMinU();
float u2 = leaf.getMaxU();
float v1 = leaf.getMinV();
float v2 = leaf.getMaxV();
float colourVariation = 0.3f;
float r = MathHelper.clamp(particleRed + (random.nextFloat() - 0.5f) * colourVariation, 0, 1);
float g = MathHelper.clamp(particleGreen + (random.nextFloat() - 0.5f) * colourVariation, 0, 1);
float b = MathHelper.clamp(particleBlue + (random.nextFloat() - 0.5f) * colourVariation, 0, 1);
buffer.pos(0, 0, l).tex(u1, v1).color(r, g, b, particleAlpha).endVertex();
buffer.pos(w, 0, l).tex(u2, v1).color(r, g, b, particleAlpha).endVertex();
buffer.pos(w, w, l).tex(u2, v2).color(r, g, b, particleAlpha).endVertex();
buffer.pos(0, w, l).tex(u1, v2).color(r, g, b, particleAlpha).endVertex();
tessellator.draw();
GlStateManager.popMatrix();
}
// Makes the rain go weird
//GlStateManager.enableLighting();
}
/** Draws a single box for one segment of the arc, from the point (x1, y1, z1) to the point (x2, y2, z2), with given width and colour. */
private void drawShearedBox(Tessellator tessellator, double x1, double y1, double z1, double x2, double y2, double z2, float width, float r, float g, float b, float a){
float u1 = particleTexture.getMinU();
float u2 = u1 + (particleTexture.getMaxU() - u1) * (float)(z1-z2)/SEGMENT_LENGTH;
float v1 = particleTexture.getMinV();
float dv = particleTexture.getMaxV() - v1;
// width * 8 gives the total 'circumference' of the box
float v2 = v1 + dv * 0.0625f;
float v3 = v1 + dv * 0.125f;
float v4 = v1 + dv * 0.1875f;
float v5 = v1 + dv * 0.25f;
BufferBuilder buffer = tessellator.getBuffer();
buffer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_COLOR);
buffer.pos(x1-width, y1-width, z1).tex(u1, v1).color(r, g, b, a).endVertex();
buffer.pos(x2-width, y2-width, z2).tex(u2, v1).color(r, g, b, a).endVertex();
buffer.pos(x1-width, y1+width, z1).tex(u1, v2).color(r, g, b, a).endVertex();
buffer.pos(x2-width, y2+width, z2).tex(u2, v2).color(r, g, b, a).endVertex();
buffer.pos(x1+width, y1+width, z1).tex(u1, v3).color(r, g, b, a).endVertex();
buffer.pos(x2+width, y2+width, z2).tex(u2, v3).color(r, g, b, a).endVertex();
buffer.pos(x1+width, y1-width, z1).tex(u1, v4).color(r, g, b, a).endVertex();
buffer.pos(x2+width, y2-width, z2).tex(u2, v4).color(r, g, b, a).endVertex();
buffer.pos(x1-width, y1-width, z1).tex(u1, v5).color(r, g, b, a).endVertex();
buffer.pos(x2-width, y2-width, z2).tex(u2, v5).color(r, g, b, a).endVertex();
tessellator.draw();
}
@SubscribeEvent
public static void onTextureStitchEvent(TextureStitchEvent.Pre event){
event.getMap().registerSprite(STEM_TEXTURE);
for(ResourceLocation texture : LEAF_TEXTURES){
event.getMap().registerSprite(texture);
}
}
}
@@ -1,35 +1,41 @@
package electroblob.wizardry.client.particle; package electroblob.wizardry.client.particle;
import java.util.Arrays;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import electroblob.wizardry.Wizardry; import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.ClientProxy;
import electroblob.wizardry.entity.ICustomHitbox;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.particle.Particle; import net.minecraft.client.particle.Particle;
import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.client.event.TextureStitchEvent; import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly; import net.minecraftforge.fml.relauncher.SideOnly;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
/** /**
* Abstract superclass for all of wizardry's particles. This replaces {@code ParticleCustomTexture} (the functionality of * 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 * 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 getters, allowing them to be set on the fly
* rather than needing to be passed into the constructor. * rather than needing to be passed into the constructor.
* <p> * <p></p>
* The new system is as follows: * The new system is as follows:
* <p> * <p></p>
* - All particle classes have a single constructor which takes a world and a position only.<br> * - All particle classes have a single constructor which takes a world and a position only.<br>
* - Each particle class defines any relevant default values in its constructor, including velocity.<br> * - Each particle class defines any relevant default values in its constructor, including velocity.<br>
* - The particle builder then overwrites any other values that were set during building. * - The particle builder then overwrites any other values that were set during building.
* <p> * <p></p>
* This beauty of this system is that there are never any redundant parameters when spawning particles, since you can set * This beauty of this system is that there are never any redundant parameters when spawning particles, since you can set
* as many or as few parameters as necessary - and in addition, common defaults don't need setting at all. For example, * as many or as few parameters as necessary - and in addition, common defaults don't need setting at all. For example,
* snow particles nearly always fall at the same speed, which can now be defined in the particle class and no longer * snow particles nearly always fall at the same speed, which can now be defined in the particle class and no longer
@@ -39,12 +45,24 @@ import net.minecraftforge.fml.relauncher.SideOnly;
* @since Wizardry 4.2.0 * @since Wizardry 4.2.0
* @see electroblob.wizardry.util.ParticleBuilder ParticleBuilder * @see electroblob.wizardry.util.ParticleBuilder ParticleBuilder
*/ */
@SideOnly(Side.CLIENT) //@SideOnly(Side.CLIENT)
public abstract class ParticleWizardry extends Particle { public abstract class ParticleWizardry extends Particle {
/** Implementation of animated particles using the TextureAtlasSprite system. Why vanilla doesn't support this I /** Implementation of animated particles using the TextureAtlasSprite system. Why vanilla doesn't support this I
* don't know, considering it too has animated particles. */ * don't know, considering it too has animated particles. */
protected final TextureAtlasSprite[] sprites; protected final TextureAtlasSprite[] sprites;
/** A long value used by the renderer as a random number seed, ensuring anything that is randomised remains the
* same across multiple frames. For example, lightning particles use this to keep their shape across ticks.
* This value can also be set during particle creation, allowing users to keep randomised properties the same
* even across multiple particles. If unspecified, the seed is chosen at random. */
protected long seed;
/** This particle's random number generator. All particles should use this in preference to any other random
* instance (like random), even if it isn't actually necessary to keep properties across frames. Note that
* if you <b>do</b> need to generate the same sequence of random numbers each frame, you must call
* {@code random.setSeed(seed)} from the {@link ParticleWizardry#renderParticle(BufferBuilder, Entity, float, float, float, float, float, float)}
* method - this is not done automatically. */
protected Random random = new Random(); // If we're not using a seed, this defaults to any old seed
/** True if the particle is shaded, false if the particle always renders at full brightness. Defaults to false. */ /** True if the particle is shaded, false if the particle always renders at full brightness. Defaults to false. */
protected boolean shaded = false; protected boolean shaded = false;
@@ -64,12 +82,12 @@ public abstract class ParticleWizardry extends Particle {
/** The entity this particle is linked to. The particle will move with this entity. */ /** The entity this particle is linked to. The particle will move with this entity. */
@Nullable @Nullable
protected Entity entity = null; protected Entity entity = null;
/** Coordinates of this particle relative to the linked entity. If the linked entity is null, these are not used. */ /** Coordinates of this particle relative to the linked entity. If the linked entity is null, these are used as
* the absolute coordinates of the centre of rotation for particles with spin. If the particle has neither a
* linked entity nor spin, these are not used. */
protected double relativeX, relativeY, relativeZ; protected double relativeX, relativeY, relativeZ;
/** Velocity of this particle relative to the linked entity. The relative x and z velocities are also used when /** Velocity of this particle relative to the linked entity. If the linked entity is null, these are not used. */
* the particle has spin to move the centre of rotation. If the linked entity is null and the particle is not protected double relativeMotionX, relativeMotionY, relativeMotionZ;
* spinning, these are not used and will be {@code NaN}. */
protected double relativeMotionX = Double.NaN, relativeMotionY = Double.NaN, relativeMotionZ = Double.NaN;
// Note that roll (equivalent to rotating the texture) is effectively handled by particleAngle - although that is // Note that roll (equivalent to rotating the texture) is effectively handled by particleAngle - although that is
// actually the rotation speed and not the angle itself. // actually the rotation speed and not the angle itself.
/** The yaw angle this particle is facing, or {@code NaN} if this particle always faces the viewer (default behaviour). */ /** The yaw angle this particle is facing, or {@code NaN} if this particle always faces the viewer (default behaviour). */
@@ -77,6 +95,14 @@ public abstract class ParticleWizardry extends Particle {
/** The pitch angle this particle is facing, or {@code NaN} if this particle always faces the viewer (default behaviour). */ /** The pitch angle this particle is facing, or {@code NaN} if this particle always faces the viewer (default behaviour). */
protected float pitch = Float.NaN; protected float pitch = Float.NaN;
/** The fraction of the impact velocity that should be the maximum spread speed added on impact. */
private static final double SPREAD_FACTOR = 0.2;
/** Lateral velocity is reduced by this factor on impact, before adding random spread velocity. */
private static final double IMPACT_FRICTION = 0.2;
/** Previous-tick velocity, used in collision detection. */
private double prevVelX, prevVelY, prevVelZ;
/** /**
* Creates a new particle in the given world at the given position. All other parameters are set via the various * Creates a new particle in the given world at the given position. All other parameters are set via the various
* setter methods ({@link electroblob.wizardry.util.ParticleBuilder ParticleBuilder} deals with all of that anyway). * setter methods ({@link electroblob.wizardry.util.ParticleBuilder ParticleBuilder} deals with all of that anyway).
@@ -108,7 +134,6 @@ public abstract class ParticleWizardry extends Particle {
this.setParticleTexture(sprites[0]); this.setParticleTexture(sprites[0]);
}else{ }else{
sprites = new TextureAtlasSprite[0]; sprites = new TextureAtlasSprite[0];
} }
} }
@@ -117,6 +142,15 @@ public abstract class ParticleWizardry extends Particle {
// Setters for parameters that affect all particles - these are implemented in this class (although they may be // Setters for parameters that affect all particles - these are implemented in this class (although they may be
// reimplemented in subclasses) // reimplemented in subclasses)
/** Sets the seed for this particle's randomly generated values and resets {@link ParticleWizardry#random} to use
* that seed. Implementations will differ between particle types; for example, ParticleLightning has an update
* period which changes the seed every few ticks, whereas ParticleVine simply retains the same seed for its entire
* lifetime. */
public void setSeed(long seed){
this.seed = seed;
this.random = new Random(seed);
}
/** Sets whether the particle should render at full brightness or not. True if the particle is shaded, false if /** Sets whether the particle should render at full brightness or not. True if the particle is shaded, false if
* the particle always renders at full brightness. Defaults to false.*/ * the particle always renders at full brightness. Defaults to false.*/
@@ -154,13 +188,10 @@ public abstract class ParticleWizardry extends Particle {
public void setSpin(double radius, double speed){ public void setSpin(double radius, double speed){
this.radius = radius; this.radius = radius;
this.speed = speed * 2 * Math.PI; // Converts rotations per tick into radians per tick for the trig functions this.speed = speed * 2 * Math.PI; // Converts rotations per tick into radians per tick for the trig functions
this.angle = this.rand.nextFloat() * (float)Math.PI * 2; // Random start angle TODO: Perhaps this should be specified? this.angle = this.rand.nextFloat() * (float)Math.PI * 2; // Random start angle
// Need to set the start position or the circle won't be centred on the correct position // Need to set the start position or the circle won't be centred on the correct position
this.relativeX = radius * -MathHelper.cos(angle); this.posX = relativeX - radius * MathHelper.cos(angle);
this.relativeZ = radius * MathHelper.sin(angle); this.posZ = relativeZ + radius * MathHelper.sin(angle);
this.setPosition(posX + relativeX, posY, posZ + relativeZ);
this.prevPosX = posX;
this.prevPosZ = posZ;
// Set these to the correct values // Set these to the correct values
this.relativeMotionX = motionX; this.relativeMotionX = motionX;
this.relativeMotionY = motionY; this.relativeMotionY = motionY;
@@ -180,6 +211,7 @@ public abstract class ParticleWizardry extends Particle {
this.prevPosX = this.posX; this.prevPosX = this.posX;
this.prevPosY = this.posY; this.prevPosY = this.posY;
this.prevPosZ = this.posZ; this.prevPosZ = this.posZ;
// Set these to the correct values
this.relativeMotionX = motionX; this.relativeMotionX = motionX;
this.relativeMotionY = motionY; this.relativeMotionY = motionY;
this.relativeMotionZ = motionZ; this.relativeMotionZ = motionZ;
@@ -218,7 +250,7 @@ public abstract class ParticleWizardry extends Particle {
/** /**
* Sets the direction this particle faces. This will cause the particle to render facing the given direction. * Sets the direction this particle faces. This will cause the particle to render facing the given direction.
* @param yaw The yaw angle of this particle in degrees, where 0 is [TODO south?]. * @param yaw The yaw angle of this particle in degrees, where 0 is south.
* @param pitch The pitch angle of this particle in degrees, where 0 is horizontal. * @param pitch The pitch angle of this particle in degrees, where 0 is horizontal.
*/ */
public void setFacing(float yaw, float pitch){ public void setFacing(float yaw, float pitch){
@@ -239,6 +271,18 @@ public abstract class ParticleWizardry extends Particle {
public void setTargetPosition(double x, double y, double z){ public void setTargetPosition(double x, double y, double z){
// Does nothing for normal particles since normal particles always render at a single point // Does nothing for normal particles since normal particles always render at a single point
} }
/**
* Sets the target point velocity for this particle. This will cause the position it stretches to touch to move
* at the given velocity. Has no effect unless {@link ParticleWizardry#setTargetVelocity(double, double, double)}
* is also used.
* @param vx The x velocity of the target point.
* @param vy The y velocity of the target point.
* @param vz The z velocity of the target point.
*/
public void setTargetVelocity(double vx, double vy, double vz){
// Does nothing for normal particles since normal particles always render at a single point
}
/** /**
* Links this particle to the given target. This will cause it to stretch to touch the target, if supported. * Links this particle to the given target. This will cause it to stretch to touch the target, if supported.
@@ -247,6 +291,15 @@ public abstract class ParticleWizardry extends Particle {
public void setTargetEntity(Entity target){ public void setTargetEntity(Entity target){
// Does nothing for normal particles since normal particles always render at a single point // Does nothing for normal particles since normal particles always render at a single point
} }
/**
* Sets the length of this particle. This will cause it to stretch to touch a point this distance along its
* linked entity's line of sight.
* @param length The length to set.
*/
public void setLength(double length){
// Does nothing for normal particles since normal particles always render at a single point
}
// ============================================== Method Overrides ============================================== // ============================================== Method Overrides ==============================================
@@ -278,15 +331,16 @@ public abstract class ParticleWizardry extends Particle {
* @param lookYZ Equal to {@code -lookZ} times the sine of {@code viewer.rotationPitch}. Will be 0 when facing directly horizontal. * @param lookYZ Equal to {@code -lookZ} times the sine of {@code viewer.rotationPitch}. Will be 0 when facing directly horizontal.
* When facing directly up, will be equal to {@code -lookZ}. When facing directly down, will be equal to {@code lookZ}. * When facing directly up, will be equal to {@code -lookZ}. When facing directly down, will be equal to {@code lookZ}.
*/ */
// Fun fact: unlike entities, particles don't seem to bother checking the camera frustum...
@Override @Override
public void renderParticle(BufferBuilder buffer, Entity viewer, float partialTicks, float lookZ, float lookY, public void renderParticle(BufferBuilder buffer, Entity viewer, float partialTicks, float lookZ, float lookY,
float lookX, float lookXY, float lookYZ){ float lookX, float lookXY, float lookYZ){
updateEntityLinking(partialTicks);
if(Float.isNaN(this.yaw) || Float.isNaN(this.pitch)){ if(Float.isNaN(this.yaw) || Float.isNaN(this.pitch)){
// Normal behaviour (rotates to face the viewer) // Normal behaviour (rotates to face the viewer)
super.renderParticle(buffer, viewer, partialTicks, lookZ, lookY, lookX, lookXY, lookYZ); drawParticle(buffer, viewer, partialTicks, lookZ, lookY, lookX, lookXY, lookYZ);
}else{ }else{
// Specific rotation // Specific rotation
@@ -302,7 +356,25 @@ public abstract class ParticleWizardry extends Particle {
float rotationYZ = -rotationZ * MathHelper.sin(pitch * degToRadFactor); float rotationYZ = -rotationZ * MathHelper.sin(pitch * degToRadFactor);
float rotationXY = rotationX * MathHelper.sin(pitch * degToRadFactor); float rotationXY = rotationX * MathHelper.sin(pitch * degToRadFactor);
super.renderParticle(buffer, viewer, partialTicks, rotationX, rotationY, rotationZ, rotationYZ, rotationXY); drawParticle(buffer, viewer, partialTicks, rotationX, rotationY, rotationZ, rotationYZ, rotationXY);
}
}
/**
* Delegate function for {@link ParticleWizardry#renderParticle(BufferBuilder, Entity, float, float, float, float, float, float)};
* does the actual rendering. Subclasses should override this method instead of renderParticle. By default, this
* method simply calls super.renderParticle.
*/
protected void drawParticle(BufferBuilder buffer, Entity viewer, float partialTicks, float rotationX, float rotationY, float rotationZ, float rotationYZ, float rotationXY){
super.renderParticle(buffer, viewer, partialTicks, rotationX, rotationY, rotationZ, rotationYZ, rotationXY);
}
protected void updateEntityLinking(float partialTicks){
if(this.entity != null){
// This is kind of cheating but we know it's always a constant velocity so it works fine
prevPosX = posX + entity.prevPosX - entity.posX - relativeMotionX * (1-partialTicks);
prevPosY = posY + entity.prevPosY - entity.posY - relativeMotionY * (1-partialTicks);
prevPosZ = posZ + entity.prevPosZ - entity.posZ - relativeMotionZ * (1-partialTicks);
} }
} }
@@ -310,38 +382,43 @@ public abstract class ParticleWizardry extends Particle {
public void onUpdate(){ public void onUpdate(){
super.onUpdate(); super.onUpdate();
// If any of these values is NaN, the particle has neither an entity nor a spin if(this.canCollide && this.onGround){
if(!Double.isNaN(relativeMotionX) && !Double.isNaN(relativeMotionY) && !Double.isNaN(relativeMotionZ)){ // I reject your friction and substitute my own!
this.motionX /= 0.699999988079071D;
// This allows velocity changes from entity linking and spin to stack this.motionZ /= 0.699999988079071D;
double vx = relativeMotionX; }
double vy = relativeMotionY;
double vz = relativeMotionZ; if(entity != null || radius > 0){
double x = relativeX;
double y = relativeY;
double z = relativeZ;
// Entity linking // Entity linking
if(this.entity != null){ if(this.entity != null){
if(this.entity.isDead){
if(this.entity.isDead) this.setExpired(); this.setExpired();
}else{
this.setPosition(this.entity.posX + relativeX, this.entity.getEntityBoundingBox().minY + relativeY, x += this.entity.posX;
this.entity.posZ + relativeZ); y += this.entity.posY;
// Velocity is set so that the renderer will interpolate correctly between ticks z += this.entity.posZ;
vx += this.entity.motionX; }
vy += this.entity.motionY;
vz += this.entity.motionZ;
} }
// Spin // Spin
if(radius > 0){ if(radius > 0){
angle += speed; angle += speed;
vx += radius * speed * MathHelper.sin(angle); // If the particle has spin, x/z relative position is used as centre and coords are changed each tick
vz += radius * speed * MathHelper.cos(angle); x += radius * -MathHelper.cos(angle);
z += radius * MathHelper.sin(angle);
} }
this.relativeX += vx; this.setPosition(x, y, z);
this.relativeY += vy;
this.relativeZ += vz; this.relativeX += relativeMotionX;
this.relativeY += relativeMotionY;
this.relativeZ += relativeMotionZ;
} }
// Colour fading // Colour fading
@@ -357,8 +434,96 @@ public abstract class ParticleWizardry extends Particle {
// (which would probably otherwise happen if particleAge == particleMaxAge) // (which would probably otherwise happen if particleAge == particleMaxAge)
this.setParticleTexture(sprites[Math.min((int)(ageFraction * sprites.length), sprites.length - 1)]); this.setParticleTexture(sprites[Math.min((int)(ageFraction * sprites.length), sprites.length - 1)]);
} }
// Collision spreading
if(canCollide){
if(this.motionX == 0 && this.prevVelX != 0){ // If the particle just collided in x
// Reduce lateral velocity so the added spread speed actually has an effect
this.motionY *= IMPACT_FRICTION;
this.motionZ *= IMPACT_FRICTION;
// Add random velocity in y and z proportional to the impact velocity
this.motionY += (rand.nextDouble()*2 - 1) * this.prevVelX * SPREAD_FACTOR;
this.motionZ += (rand.nextDouble()*2 - 1) * this.prevVelX * SPREAD_FACTOR;
}
if(this.motionY == 0 && this.prevVelY != 0){ // If the particle just collided in y
// Reduce lateral velocity so the added spread speed actually has an effect
this.motionX *= IMPACT_FRICTION;
this.motionZ *= IMPACT_FRICTION;
// Add random velocity in x and z proportional to the impact velocity
this.motionX += (rand.nextDouble()*2 - 1) * this.prevVelY * SPREAD_FACTOR;
this.motionZ += (rand.nextDouble()*2 - 1) * this.prevVelY * SPREAD_FACTOR;
}
if(this.motionZ == 0 && this.prevVelZ != 0){ // If the particle just collided in z
// Reduce lateral velocity so the added spread speed actually has an effect
this.motionX *= IMPACT_FRICTION;
this.motionY *= IMPACT_FRICTION;
// Add random velocity in x and y proportional to the impact velocity
this.motionX += (rand.nextDouble()*2 - 1) * this.prevVelZ * SPREAD_FACTOR;
this.motionY += (rand.nextDouble()*2 - 1) * this.prevVelZ * SPREAD_FACTOR;
}
double searchRadius = 20;
List<Entity> nearbyEntities = WizardryUtilities.getEntitiesWithinRadius(searchRadius, this.posX,
this.posY, this.posZ, world, Entity.class);
nearbyEntities.removeIf(e -> !(e instanceof ICustomHitbox && ((ICustomHitbox)e).contains(new Vec3d(this.posX, this.posY, this.posZ))));
if(nearbyEntities.size() > 0) this.setExpired();
}
this.prevVelX = motionX;
this.prevVelY = motionY;
this.prevVelZ = motionZ;
} }
// Overridden and copied to fix the collision behaviour
@Override
public void move(double x, double y, double z){
double origY = y;
double origX = x;
double origZ = z;
if(this.canCollide){
List<AxisAlignedBB> list = this.world.getCollisionBoxes(null, this.getBoundingBox().expand(x, y, z));
for(AxisAlignedBB axisalignedbb : list){
y = axisalignedbb.calculateYOffset(this.getBoundingBox(), y);
}
this.setBoundingBox(this.getBoundingBox().offset(0.0D, y, 0.0D));
for(AxisAlignedBB axisalignedbb1 : list){
x = axisalignedbb1.calculateXOffset(this.getBoundingBox(), x);
}
this.setBoundingBox(this.getBoundingBox().offset(x, 0.0D, 0.0D));
for(AxisAlignedBB axisalignedbb2 : list){
z = axisalignedbb2.calculateZOffset(this.getBoundingBox(), z);
}
this.setBoundingBox(this.getBoundingBox().offset(0.0D, 0.0D, z));
}else{
this.setBoundingBox(this.getBoundingBox().offset(x, y, z));
}
this.resetPositionToBB();
this.onGround = origY != y && origY < 0.0D;
if(origX != x) this.motionX = 0.0D;
if(origY != y) this.motionY = 0.0D; // Why doesn't Particle do this for y?
if(origZ != z) this.motionZ = 0.0D;
}
// =============================================== Helper Methods =============================================== // =============================================== Helper Methods ===============================================
/** Static helper method that generates an array of n ResourceLocations using the particle file naming convention, /** Static helper method that generates an array of n ResourceLocations using the particle file naming convention,
@@ -390,6 +555,21 @@ public abstract class ParticleWizardry extends Particle {
return textures; return textures;
} }
/**
* Associates the given {@link ResourceLocation} with the given {@link IWizardryParticleFactory}, allowing it to
* be used in the {@link electroblob.wizardry.util.ParticleBuilder ParticleBuilder}. This is a similar concept to
* registering entity renderers, in that it associates the client-only bit with its common-code counterpart - but
* of course, particles are client-side only so a simple identifier is all that is necessary. As with entity
* renderers, <b>this method may only be called from the client side</b>, probably a client proxy.
* @param name The {@link ResourceLocation} to use for the particle. This effectively replaces the particle type
* enum from previous versions. Keep a reference to this somewhere in <b>common</b> code for use later.
* @param factory A {@link IWizardryParticleFactory} that produces your particle. A constructor reference is usually
* sufficient.
*/
public static void registerParticle(ResourceLocation name, IWizardryParticleFactory factory){
ClientProxy.addParticleFactory(name, factory);
}
/** Simple particle factory interface which takes a world and a position and returns a particle. Used (via method /** Simple particle factory interface which takes a world and a position and returns a particle. Used (via method
* references) in the client proxy to link particle enum types to actual particle classes. */ * references) in the client proxy to link particle enum types to actual particle classes. */
@SideOnly(Side.CLIENT) @SideOnly(Side.CLIENT)
@@ -0,0 +1,153 @@
package electroblob.wizardry.client.renderer;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.block.BlockStatue;
import electroblob.wizardry.registry.WizardryPotions;
import net.minecraft.client.Minecraft;
import net.minecraft.client.model.ModelBase;
import net.minecraft.client.model.ModelBiped;
import net.minecraft.client.model.ModelPlayer;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.GlStateManager.DestFactor;
import net.minecraft.client.renderer.GlStateManager.SourceFactor;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.entity.Render;
import net.minecraft.client.renderer.entity.RenderLivingBase;
import net.minecraft.client.renderer.entity.RenderPlayer;
import net.minecraft.client.renderer.entity.layers.LayerRenderer;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import org.lwjgl.opengl.GL11;
/**
* Layer used to render the frost texture on a creature with the frostbite effect. Handles dynamic tiling of the texture.
*
* @author Electroblob
* @since Wizardry 1.2
*/
public class LayerFrost implements LayerRenderer<EntityLivingBase> {
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.get(0) != null){
scaleX = (double)model.boxList.get(0).textureWidth / 16d;
scaleY = (double)model.boxList.get(0).textureHeight / 16d;
}else{ // Fallback to model fields; should never be needed
scaleX = (double)model.textureWidth / 16d;
scaleY = (double)model.textureHeight / 16d;
}
GlStateManager.scale(scaleX, scaleY, 1);
GlStateManager.matrixMode(GL11.GL_MODELVIEW);
// Hides the hat layer for bipeds
if(this.model instanceof ModelBiped) ((ModelBiped)this.model).bipedHeadwear.isHidden = true;
if(this.model instanceof ModelPlayer){
((ModelPlayer)this.model).bipedBodyWear.isHidden = true;
((ModelPlayer)this.model).bipedLeftArmwear.isHidden = true;
((ModelPlayer)this.model).bipedRightArmwear.isHidden = true;
((ModelPlayer)this.model).bipedLeftLegwear.isHidden = true;
((ModelPlayer)this.model).bipedRightLegwear.isHidden = true;
}
this.model.setLivingAnimations(entity, limbSwing, limbSwingAmount, partialTicks);
this.model.render(entity, limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch, scale);
if(this.model instanceof ModelBiped) ((ModelBiped)this.model).bipedHeadwear.isHidden = false;
// Undoes the texture scaling
GlStateManager.matrixMode(GL11.GL_TEXTURE);
GlStateManager.loadIdentity();
GlStateManager.matrixMode(GL11.GL_MODELVIEW);
GlStateManager.popMatrix();
}
@Override
public boolean shouldCombineTextures(){
return false;
}
}
@@ -1,9 +1,5 @@
package electroblob.wizardry.client.renderer; package electroblob.wizardry.client.renderer;
import java.util.Map.Entry;
import org.lwjgl.opengl.GL11;
import electroblob.wizardry.block.BlockStatue; import electroblob.wizardry.block.BlockStatue;
import electroblob.wizardry.client.ClientProxy; import electroblob.wizardry.client.ClientProxy;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
@@ -21,6 +17,7 @@ import net.minecraft.entity.EntityLivingBase;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper; 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. * Layer used to render the stone texture on a petrified creature. Handles dynamic tiling of the stone texture.
@@ -36,16 +33,14 @@ public class LayerStone implements LayerRenderer<EntityLivingBase> {
private static final ResourceLocation texture = new ResourceLocation("textures/blocks/stone.png"); private static final ResourceLocation texture = new ResourceLocation("textures/blocks/stone.png");
public static void initialiseLayers(){ public static void initialiseLayers(){
for(Entry<Class<? extends Entity>, Render<? extends Entity>> entry : Minecraft.getMinecraft()
.getRenderManager().entityRenderMap.entrySet()){ 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. // Because the zombie classes are now split properly, their renderers play nicely like everything else.
if(entry.getValue() instanceof RenderLivingBase){ if(renderer instanceof RenderLivingBase){
// Adds a stone layer to all the living entity renderers in the game. Whether it is actually rendered // 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. // is decided in doRenderLayer below on a per-entity basis.
((RenderLivingBase<?>)entry.getValue()).addLayer(new LayerStone((RenderLivingBase<?>)entry.getValue())); ((RenderLivingBase<?>)renderer).addLayer(new LayerStone((RenderLivingBase<?>)renderer));
} }
// NOTE: May have to do some special stuff for players if they are to be added; see
// Minecraft.getMinecraft().getRenderManager().getSkinMap()
} }
} }
@@ -55,12 +50,13 @@ public class LayerStone implements LayerRenderer<EntityLivingBase> {
} }
// FIXME: Does not work with zombie pigmen, I have no idea why. // 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 @Override
public void doRenderLayer(EntityLivingBase entity, float limbSwing, float limbSwingAmount, float partialTicks, public void doRenderLayer(EntityLivingBase entity, float limbSwing, float limbSwingAmount, float partialTicks,
float ageInTicks, float netHeadYaw, float headPitch, float scale){ float ageInTicks, float netHeadYaw, float headPitch, float scale){
if(entity.getEntityData().getBoolean(BlockStatue.NBT_KEY)){ if(entity.getEntityData().getBoolean(BlockStatue.PETRIFIED_NBT_KEY)){
GlStateManager.enableLighting(); GlStateManager.enableLighting();
int i = this.getBlockBrightnessForEntity(entity, partialTicks); int i = this.getBlockBrightnessForEntity(entity, partialTicks);
@@ -108,7 +104,6 @@ public class LayerStone implements LayerRenderer<EntityLivingBase> {
GlStateManager.pushMatrix(); GlStateManager.pushMatrix();
// Enables tiling (Also used for guardian beam, beacon beam and ender crystal beam) // Enables tiling (Also used for guardian beam, beacon beam and ender crystal beam)
// TODO: Backport this improvement
GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT); GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT);
GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT); GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT);
@@ -0,0 +1,33 @@
package electroblob.wizardry.client.renderer;
import electroblob.wizardry.entity.living.EntityStrayMinion;
import net.minecraft.client.model.ModelSkeleton;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.entity.RenderLivingBase;
import net.minecraft.client.renderer.entity.layers.LayerRenderer;
import net.minecraft.util.ResourceLocation;
/** Had to copy this entire class just because of one unnecessarily specific type parameter. The type parameter has been
* changed to {@code EntityStrayMinion} and parameter types updated accordingly. Everything else is identical. */
public class LayerStrayMinionClothing implements LayerRenderer<EntityStrayMinion> {
private static final ResourceLocation STRAY_CLOTHES_TEXTURES = new ResourceLocation("textures/entity/skeleton/stray_overlay.png");
private final RenderLivingBase<?> renderer;
private final ModelSkeleton layerModel = new ModelSkeleton(0.25F, true);
public LayerStrayMinionClothing(RenderLivingBase<?> renderer){
this.renderer = renderer;
}
public void doRenderLayer(EntityStrayMinion entity, float limbSwing, float limbSwingAmount, float partialTicks, float ageInTicks, float netHeadYaw, float headPitch, float scale){
this.layerModel.setModelAttributes(this.renderer.getMainModel());
this.layerModel.setLivingAnimations(entity, limbSwing, limbSwingAmount, partialTicks);
GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
this.renderer.bindTexture(STRAY_CLOTHES_TEXTURES);
this.layerModel.render(entity, limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch, scale);
}
public boolean shouldCombineTextures(){
return true;
}
}
@@ -0,0 +1,89 @@
package electroblob.wizardry.client.renderer;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.spell.ArcaneLock;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraftforge.client.event.RenderWorldLastEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import org.lwjgl.opengl.GL11;
@Mod.EventBusSubscriber(Side.CLIENT)
public class RenderArcaneLock {
private static final ResourceLocation[] textures = new ResourceLocation[8];
static {
for(int i=0; i<textures.length; i++){
textures[i] = new ResourceLocation(Wizardry.MODID, "textures/entity/arcane_lock_" + i + ".png");
}
}
@SubscribeEvent
public static void onRenderWorldLastEvent(RenderWorldLastEvent event){
EntityPlayer player = Minecraft.getMinecraft().player;
World world = Minecraft.getMinecraft().world;
GlStateManager.pushMatrix();
GlStateManager.enableBlend();
GlStateManager.disableLighting();
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240, 240);
GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
Vec3d origin = player.getPositionEyes(event.getPartialTicks());
GlStateManager.translate(-origin.x, -origin.y + player.getEyeHeight(), -origin.z);
GlStateManager.color(1, 1, 1, 1);
Minecraft.getMinecraft().renderEngine.bindTexture(textures[(player.ticksExisted % (textures.length * 2))/2]);
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
for(TileEntity tileentity : world.loadedTileEntityList){
if(tileentity.getDistanceSq(origin.x, origin.y, origin.z) <= 64*64 && tileentity.getTileData().hasUniqueId(ArcaneLock.NBT_KEY)){
Vec3d[] vertices = WizardryUtilities.getVertices(world.getBlockState(tileentity.getPos()).getBoundingBox(world, tileentity.getPos()).grow(0.05).offset(tileentity.getPos()));
drawFace(buffer, vertices[0], vertices[1], vertices[3], vertices[2], 0, 0, 1, 1); // Bottom
drawFace(buffer, vertices[6], vertices[7], vertices[2], vertices[3], 0, 0, 1, 1); // South
drawFace(buffer, vertices[5], vertices[6], vertices[1], vertices[2], 0, 0, 1, 1); // East
drawFace(buffer, vertices[4], vertices[5], vertices[0], vertices[1], 0, 0, 1, 1); // North
drawFace(buffer, vertices[7], vertices[4], vertices[3], vertices[0], 0, 0, 1, 1); // West
drawFace(buffer, vertices[5], vertices[4], vertices[6], vertices[7], 0, 0, 1, 1); // Top
}
}
tessellator.draw();
GlStateManager.disableBlend();
GlStateManager.enableTexture2D();
GlStateManager.enableLighting();
GlStateManager.disableRescaleNormal();
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();
}
}
@@ -1,7 +1,5 @@
package electroblob.wizardry.client.renderer; package electroblob.wizardry.client.renderer;
import org.lwjgl.opengl.GL11;
import electroblob.wizardry.Wizardry; import electroblob.wizardry.Wizardry;
import electroblob.wizardry.tileentity.ContainerArcaneWorkbench; import electroblob.wizardry.tileentity.ContainerArcaneWorkbench;
import electroblob.wizardry.tileentity.TileEntityArcaneWorkbench; import electroblob.wizardry.tileentity.TileEntityArcaneWorkbench;
@@ -15,6 +13,8 @@ import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper;
import org.lwjgl.opengl.GL11;
public class RenderArcaneWorkbench extends TileEntitySpecialRenderer<TileEntityArcaneWorkbench> { public class RenderArcaneWorkbench extends TileEntitySpecialRenderer<TileEntityArcaneWorkbench> {
@@ -30,19 +30,19 @@ public class RenderArcaneWorkbench extends TileEntitySpecialRenderer<TileEntityA
GlStateManager.translate((float)x + 0.5F, (float)y + 1.5F, (float)z + 0.5F); GlStateManager.translate((float)x + 0.5F, (float)y + 1.5F, (float)z + 0.5F);
GlStateManager.rotate(180, 0F, 0F, 1F); GlStateManager.rotate(180, 0F, 0F, 1F);
GlStateManager.pushMatrix(); GlStateManager.pushMatrix();
double angle = 0.0d; double angle;
if(x < -0.5){ if(x < -0.5){
angle = Math.toDegrees(Math.atan((z + 0.5) / (x + 0.5))) + 180; angle = Math.toDegrees(Math.atan((z + 0.5) / (x + 0.5))) + 180;
}else{ }else{
angle = Math.toDegrees(Math.atan((z + 0.5) / (x + 0.5))); angle = Math.toDegrees(Math.atan((z + 0.5) / (x + 0.5)));
} }
this.renderEffect(tileentity); this.renderEffect(tileentity, partialTicks);
this.renderWand(tileentity, angle); this.renderWand(tileentity, angle, partialTicks);
GlStateManager.popMatrix(); GlStateManager.popMatrix();
GlStateManager.popMatrix(); GlStateManager.popMatrix();
} }
private void renderEffect(TileEntityArcaneWorkbench tileentity){ private void renderEffect(TileEntityArcaneWorkbench tileentity, float partialTicks){
ItemStack itemstack = tileentity.getStackInSlot(ContainerArcaneWorkbench.CENTRE_SLOT); ItemStack itemstack = tileentity.getStackInSlot(ContainerArcaneWorkbench.CENTRE_SLOT);
@@ -52,7 +52,7 @@ public class RenderArcaneWorkbench extends TileEntitySpecialRenderer<TileEntityA
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f);
GlStateManager.enableBlend(); GlStateManager.enableBlend();
GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); // This line fixes the weird brightness bug. GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); // This line fixes the weird brightness bug.
GlStateManager.rotate(tileentity.timer, 0.0f, 1.0f, 0.0f); GlStateManager.rotate(tileentity.timer + partialTicks, 0.0f, 1.0f, 0.0f);
GlStateManager.translate(0.0f, 0.65f, 0.0f); GlStateManager.translate(0.0f, 0.65f, 0.0f);
Tessellator tessellator = Tessellator.getInstance(); Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer(); BufferBuilder buffer = tessellator.getBuffer();
@@ -76,7 +76,8 @@ public class RenderArcaneWorkbench extends TileEntitySpecialRenderer<TileEntityA
* *
* @param tileentity The instance of the workbench tile entity * @param tileentity The instance of the workbench tile entity
*/ */
private void renderWand(TileEntityArcaneWorkbench tileentity, double viewAngle){ private void renderWand(TileEntityArcaneWorkbench tileentity, double viewAngle, float partialTicks){
ItemStack stack = tileentity.getStackInSlot(ContainerArcaneWorkbench.CENTRE_SLOT); ItemStack stack = tileentity.getStackInSlot(ContainerArcaneWorkbench.CENTRE_SLOT);
if(!stack.isEmpty()){ if(!stack.isEmpty()){
@@ -87,8 +88,8 @@ public class RenderArcaneWorkbench extends TileEntitySpecialRenderer<TileEntityA
GlStateManager.rotate(180, 0, 1, 0); GlStateManager.rotate(180, 0, 1, 0);
GlStateManager.rotate((float)(viewAngle - 90f), 0, 0, 1); GlStateManager.rotate((float)(viewAngle - 90f), 0, 0, 1);
// Does the floaty thing // Does the floaty thing <- #bestcommentever
GlStateManager.translate(0.0F, 0.0F, (float)tileentity.yOffset / 5000.0F + 0.55f); GlStateManager.translate(0.0F, 0.0F, 0.56f + 0.05f * MathHelper.sin((tileentity.timer + partialTicks)/15));//(float)tileentity.yOffset / 5000.0F + 0.55f);
GlStateManager.scale(0.75F, 0.75F, 0.75F); GlStateManager.scale(0.75F, 0.75F, 0.75F);
// This is what the item frame uses so it's definitely what we want. // This is what the item frame uses so it's definitely what we want.
Minecraft.getMinecraft().getRenderItem().renderItem(stack, TransformType.FIXED); Minecraft.getMinecraft().getRenderItem().renderItem(stack, TransformType.FIXED);
@@ -1,22 +1,18 @@
package electroblob.wizardry.client.renderer; package electroblob.wizardry.client.renderer;
import java.util.ArrayList;
import java.util.Collections;
import org.lwjgl.opengl.GL11;
import electroblob.wizardry.Wizardry; import electroblob.wizardry.Wizardry;
import electroblob.wizardry.entity.construct.EntityBlackHole; import electroblob.wizardry.entity.construct.EntityBlackHole;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.*;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.entity.Render; import net.minecraft.client.renderer.entity.Render;
import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper;
import org.lwjgl.opengl.GL11;
import java.util.ArrayList;
import java.util.Collections;
public class RenderBlackHole extends Render<EntityBlackHole> { public class RenderBlackHole extends Render<EntityBlackHole> {
@@ -81,21 +77,21 @@ public class RenderBlackHole extends Render<EntityBlackHole> {
int sliceAngle = 20 + a; int sliceAngle = 20 + a;
double x1 = scale * Math.sin((blackhole.ticksExisted + 40 * j) * (Math.PI / 180)); double x1 = scale * MathHelper.sin((blackhole.ticksExisted + 40 * j) * ((float)Math.PI / 180f));
// double y1 = 0.7*Math.cos((blackhole.timer - 40*j)*(Math.PI/180))*j/10; // double y1 = 0.7*MathHelper.cos((blackhole.timer - 40*j)*(Math.PI/180))*j/10;
double z1 = scale * Math.cos((blackhole.ticksExisted + 40 * j) * (Math.PI / 180)); double z1 = scale * MathHelper.cos((blackhole.ticksExisted + 40 * j) * ((float)Math.PI / 180));
double x2 = scale * Math.sin((blackhole.ticksExisted + 40 * j - sliceAngle) * (Math.PI / 180)); double x2 = scale * MathHelper.sin((blackhole.ticksExisted + 40 * j - sliceAngle) * ((float)Math.PI / 180));
// double y2 = 0.7*Math.sin((blackhole.timer - 40*j)*(Math.PI/180))*j/10; // double y2 = 0.7*MathHelper.sin((blackhole.timer - 40*j)*(Math.PI/180))*j/10;
double z2 = scale * Math.cos((blackhole.ticksExisted + 40 * j - sliceAngle) * (Math.PI / 180)); double z2 = scale * MathHelper.cos((blackhole.ticksExisted + 40 * j - sliceAngle) * ((float)Math.PI / 180));
double absoluteX = x1 * Math.cos(31 * b); double absoluteX = x1 * MathHelper.cos(31 * b);
double absoluteY = z1 * Math.sin(31 * a) + x1 * Math.cos(31 * a) * Math.sin(31 * b); double absoluteY = z1 * MathHelper.sin(31 * a) + x1 * MathHelper.cos(31 * a) * MathHelper.sin(31 * b);
double absoluteZ = z1 * Math.cos(31 * a); double absoluteZ = z1 * MathHelper.cos(31 * a);
double absoluteX2 = x2 * Math.cos(31 * b); double absoluteX2 = x2 * MathHelper.cos(31 * b);
double absoluteY2 = z2 * Math.sin(31 * a) + x2 * Math.cos(31 * a) * Math.sin(31 * b); double absoluteY2 = z2 * MathHelper.sin(31 * a) + x2 * MathHelper.cos(31 * a) * MathHelper.sin(31 * b);
double absoluteZ2 = z2 * Math.cos(31 * a); double absoluteZ2 = z2 * MathHelper.cos(31 * a);
/* buffer.begin(0, DefaultVertexFormats.POSITION_TEX); /* buffer.begin(0, DefaultVertexFormats.POSITION_TEX);
* *
* tessellator.setColorOpaque(255, 255, 255); GL11.glPointSize(5); * tessellator.setColorOpaque(255, 255, 255); GL11.glPointSize(5);
@@ -1,7 +1,5 @@
package electroblob.wizardry.client.renderer; package electroblob.wizardry.client.renderer;
import org.lwjgl.opengl.GL11;
import electroblob.wizardry.Wizardry; import electroblob.wizardry.Wizardry;
import electroblob.wizardry.entity.construct.EntityBubble; import electroblob.wizardry.entity.construct.EntityBubble;
import electroblob.wizardry.util.WizardryUtilities; import electroblob.wizardry.util.WizardryUtilities;
@@ -14,6 +12,7 @@ import net.minecraft.client.renderer.entity.Render;
import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import org.lwjgl.opengl.GL11;
public class RenderBubble extends Render<EntityBubble> { public class RenderBubble extends Render<EntityBubble> {
@@ -0,0 +1,268 @@
package electroblob.wizardry.client.renderer;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.potion.PotionContainment;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTUtil;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraftforge.client.event.RenderWorldLastEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import org.lwjgl.opengl.GL11;
@Mod.EventBusSubscriber(Side.CLIENT)
public class RenderContainmentField {
private static final ResourceLocation[] textures = new ResourceLocation[8];
private static final float ANIMATION_SPEED = 0.004f;
private static final float FADE_DISTANCE_SQUARED = 15;
static {
for(int i=0; i<textures.length; i++){
textures[i] = new ResourceLocation(Wizardry.MODID, "textures/entity/containment_field_" + i + ".png");
}
}
@SubscribeEvent
public static void onRenderWorldLastEvent(RenderWorldLastEvent event){
EntityPlayer player = Minecraft.getMinecraft().player;
if(player.isPotionActive(WizardryPotions.containment)){
Vec3d centre = WizardryUtilities.getCentre(NBTUtil.getPosFromTag(player.getEntityData().getCompoundTag(PotionContainment.ENTITY_TAG)));
float r = PotionContainment.getContainmentDistance(player.getActivePotionEffect(WizardryPotions.containment).getAmplifier());
GlStateManager.pushMatrix();
GlStateManager.enableBlend();
GlStateManager.shadeModel(GL11.GL_SMOOTH); // Allows the vertex colours to produce a gradient
GlStateManager.disableLighting();
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240, 240);
GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
// 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);
// Translate the texture in the 2D space (like the creeper charge layer)
GlStateManager.matrixMode(GL11.GL_TEXTURE);
GlStateManager.loadIdentity();
float distance = (player.ticksExisted + event.getPartialTicks()) * ANIMATION_SPEED;
GlStateManager.translate(distance, distance, 0);
GlStateManager.matrixMode(GL11.GL_MODELVIEW);
GlStateManager.color(1, 1, 1, 1);
Minecraft.getMinecraft().renderEngine.bindTexture(textures[(player.ticksExisted % (textures.length * 2))/2]);
Tessellator tessellator = Tessellator.getInstance();
// The aim here is to have only the bits of the containment field near the player be visible (if any).
// There's probably a really neat, advanced way of doing this with OpenGL (stencil buffer...?) but in the
// absence of OpenGL skills, I've devised a cheat which kinda achieves the effect we want:
// - Split each face of the cube into four, where the split point is taken orthogonal to the player (the
// player is taken as (0, 0, 0) for simplicity, unlike most render classes where the entire thing is
// translated first)
// - Then have the alpha value for each vertex change based on its distance from the player
// - N.B. OpenGL renders quads as two triangles, with the 1st and 3rd vertices forming the line between them.
// This causes weirdness in the interpolation since the interpolation isn't taking the whole square into
// account. The order of the vertices below ensures that the seams between the triangles form an X shape
// on each of the faces, meaning that you always see a square shape appear rather than a random polygon.
Vec3d relative = centre.subtract(player.getPositionEyes(event.getPartialTicks())).add(0, player.getEyeHeight(), 0);
double x1 = relative.x - r;
double y1 = relative.y - r;
double z1 = relative.z - r;
double x2 = relative.x + r;
double y2 = relative.y + r;
double z2 = relative.z + r;
float alpha = Math.min(1, player.getActivePotionEffect(WizardryPotions.containment).getDuration() / 40f);
BufferBuilder buffer = tessellator.getBuffer();
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR);
// The following code is mostly an incomprehensible mess of numbers, but unfortunately without a lot of
// fancy vector stuff it has to be like this, and besides, this is faster!
// Bottom
drawVertex(buffer, x1, y1, z1, 0, 0, alpha);
drawVertex(buffer, x1, y1, 0, 0, -(float)z1, alpha);
drawVertex(buffer, 0, y1, 0, -(float)x1, -(float)z1, alpha);
drawVertex(buffer, 0, y1, z1, -(float)x1, 0, alpha);
drawVertex(buffer, 0, y1, 0, -(float)x1, -(float)z1, alpha);
drawVertex(buffer, x2, y1, 0, 2*r, -(float)z1, alpha);
drawVertex(buffer, x2, y1, z1, 2*r, 0, alpha);
drawVertex(buffer, 0, y1, z1, -(float)x1, 0, alpha);
drawVertex(buffer, x1, y1, z2, 0, 2*r, alpha);
drawVertex(buffer, 0, y1, z2, -(float)x1, 2*r, alpha);
drawVertex(buffer, 0, y1, 0, -(float)x1, -(float)z1, alpha);
drawVertex(buffer, x1, y1, 0, 0, -(float)z1, alpha);
drawVertex(buffer, 0, y1, 0, -(float)x1, -(float)z1, alpha);
drawVertex(buffer, 0, y1, z2, -(float)x1, 2*r, alpha);
drawVertex(buffer, x2, y1, z2, 2*r, 2*r, alpha);
drawVertex(buffer, x2, y1, 0, 2*r, -(float)z1, alpha);
// Top
drawVertex(buffer, x1, y2, z1, 0, 0, alpha);
drawVertex(buffer, 0, y2, z1, -(float)x1, 0, alpha);
drawVertex(buffer, 0, y2, 0, -(float)x1, -(float)z1, alpha);
drawVertex(buffer, x1, y2, 0, 0, -(float)z1, alpha);
drawVertex(buffer, x2, y2, z1, 2*r, 0, alpha);
drawVertex(buffer, x2, y2, 0, 2*r, -(float)z1, alpha);
drawVertex(buffer, 0, y2, 0, -(float)x1, -(float)z1, alpha);
drawVertex(buffer, 0, y2, z1, -(float)x1, 0, alpha);
drawVertex(buffer, 0, y2, 0, -(float)x1, -(float)z1, alpha);
drawVertex(buffer, 0, y2, z2, -(float)x1, 2*r, alpha);
drawVertex(buffer, x1, y2, z2, 0, 2*r, alpha);
drawVertex(buffer, x1, y2, 0, 0, -(float)z1, alpha);
drawVertex(buffer, 0, y2, 0, -(float)x1, -(float)z1, alpha);
drawVertex(buffer, x2, y2, 0, 2*r, -(float)z1, alpha);
drawVertex(buffer, x2, y2, z2, 2*r, 2*r, alpha);
drawVertex(buffer, 0, y2, z2, -(float)x1, 2*r, alpha);
// North
drawVertex(buffer, x1, y1, z1, 0, 0, alpha);
drawVertex(buffer, 0, y1, z1, -(float)x1, 0, alpha);
drawVertex(buffer, 0, 0, z1, -(float)x1, -(float)y1, alpha);
drawVertex(buffer, x1, 0, z1, 0, -(float)y1, alpha);
drawVertex(buffer, x2, y1, z1, 2*r, 0, alpha);
drawVertex(buffer, x2, 0, z1, 2*r, -(float)y1, alpha);
drawVertex(buffer, 0, 0, z1, -(float)x1, -(float)y1, alpha);
drawVertex(buffer, 0, y1, z1, -(float)x1, 0, alpha);
drawVertex(buffer, 0, 0, z1, -(float)x1, -(float)y1, alpha);
drawVertex(buffer, 0, y2, z1, -(float)x1, 2*r, alpha);
drawVertex(buffer, x1, y2, z1, 0, 2*r, alpha);
drawVertex(buffer, x1, 0, z1, 0, -(float)y1, alpha);
drawVertex(buffer, 0, 0, z1, -(float)x1, -(float)y1, alpha);
drawVertex(buffer, x2, 0, z1, 2*r, -(float)y1, alpha);
drawVertex(buffer, x2, y2, z1, 2*r, 2*r, alpha);
drawVertex(buffer, 0, y2, z1, -(float)x1, 2*r, alpha);
// South
drawVertex(buffer, x1, y1, z2, 0, 0, alpha);
drawVertex(buffer, x1, 0, z2, 0, -(float)y1, alpha);
drawVertex(buffer, 0, 0, z2, -(float)x1, -(float)y1, alpha);
drawVertex(buffer, 0, y1, z2, -(float)x1, 0, alpha);
drawVertex(buffer, 0, 0, z2, -(float)x1, -(float)y1, alpha);
drawVertex(buffer, x2, 0, z2, 2*r, -(float)y1, alpha);
drawVertex(buffer, x2, y1, z2, 2*r, 0, alpha);
drawVertex(buffer, 0, y1, z2, -(float)x1, 0, alpha);
drawVertex(buffer, x1, y2, z2, 0, 2*r, alpha);
drawVertex(buffer, 0, y2, z2, -(float)x1, 2*r, alpha);
drawVertex(buffer, 0, 0, z2, -(float)x1, -(float)y1, alpha);
drawVertex(buffer, x1, 0, z2, 0, -(float)y1, alpha);
drawVertex(buffer, 0, 0, z2, -(float)x1, -(float)y1, alpha);
drawVertex(buffer, 0, y2, z2, -(float)x1, 2*r, alpha);
drawVertex(buffer, x2, y2, z2, 2*r, 2*r, alpha);
drawVertex(buffer, x2, 0, z2, 2*r, -(float)y1, alpha);
// West
drawVertex(buffer, x1, y1, z1, 0, 0, alpha);
drawVertex(buffer, x1, 0, z1, 0, -(float)y1, alpha);
drawVertex(buffer, x1, 0, 0, -(float)z1, -(float)y1, alpha);
drawVertex(buffer, x1, y1, 0, -(float)z1, 0, alpha);
drawVertex(buffer, x1, 0, 0, -(float)z1, -(float)y1, alpha);
drawVertex(buffer, x1, 0, z2, 2*r, -(float)y1, alpha);
drawVertex(buffer, x1, y1, z2, 2*r, 0, alpha);
drawVertex(buffer, x1, y1, 0, -(float)z1, 0, alpha);
drawVertex(buffer, x1, y2, z1, 0, 2*r, alpha);
drawVertex(buffer, x1, y2, 0, -(float)z1, 2*r, alpha);
drawVertex(buffer, x1, 0, 0, -(float)z1, -(float)y1, alpha);
drawVertex(buffer, x1, 0, z1, 0, -(float)y1, alpha);
drawVertex(buffer, x1, 0, 0, -(float)z1, -(float)y1, alpha);
drawVertex(buffer, x1, y2, 0, -(float)z1, 2*r, alpha);
drawVertex(buffer, x1, y2, z2, 2*r, 2*r, alpha);
drawVertex(buffer, x1, 0, z2, 2*r, -(float)y1, alpha);
// East
drawVertex(buffer, x2, y1, z1, 0, 0, alpha);
drawVertex(buffer, x2, y1, 0, -(float)z1, 0, alpha);
drawVertex(buffer, x2, 0, 0, -(float)z1, -(float)y1, alpha);
drawVertex(buffer, x2, 0, z1, 0, -(float)y1, alpha);
drawVertex(buffer, x2, y1, z2, 2*r, 0, alpha);
drawVertex(buffer, x2, 0, z2, 2*r, -(float)y1, alpha);
drawVertex(buffer, x2, 0, 0, -(float)z1, -(float)y1, alpha);
drawVertex(buffer, x2, y1, 0, -(float)z1, 0, alpha);
drawVertex(buffer, x2, 0, 0, -(float)z1, -(float)y1, alpha);
drawVertex(buffer, x2, y2, 0, -(float)z1, 2*r, alpha);
drawVertex(buffer, x2, y2, z1, 0, 2*r, alpha);
drawVertex(buffer, x2, 0, z1, 0, -(float)y1, alpha);
drawVertex(buffer, x2, 0, 0, -(float)z1, -(float)y1, alpha);
drawVertex(buffer, x2, 0, z2, 2*r, -(float)y1, alpha);
drawVertex(buffer, x2, y2, z2, 2*r, 2*r, alpha);
drawVertex(buffer, x2, y2, 0, -(float)z1, 2*r, alpha);
tessellator.draw();
// Undoes the texture translation
GlStateManager.matrixMode(GL11.GL_TEXTURE);
GlStateManager.loadIdentity();
GlStateManager.matrixMode(GL11.GL_MODELVIEW);
GlStateManager.disableBlend();
GlStateManager.enableTexture2D();
GlStateManager.enableLighting();
GlStateManager.disableRescaleNormal();
GlStateManager.popMatrix();
}
}
private static void drawVertex(BufferBuilder buffer, double x, double y, double z, float u, float v, float alpha){
float f = MathHelper.clamp(1 - (float)(x*x + y*y + z*z) / FADE_DISTANCE_SQUARED, 0, 1);
buffer.pos(x, y, z).tex(u, v).color(1, 1, 1, f * alpha).endVertex();
}
// Useful to keep this mapping in case I ever want to draw cubes again
// Vec3d[] vertices = WizardryUtilities.getVertices(entity.getEntityBoundingBox().offset(entity.getPositionVector().scale(-1)));
//
// // Outside
// drawFace(tessellator, vertices[0], vertices[1], vertices[3], vertices[2], u1, v1, u2, v2); // Bottom
// drawFace(tessellator, vertices[6], vertices[7], vertices[2], vertices[3], u1, v1, u2, v2); // South
// drawFace(tessellator, vertices[5], vertices[6], vertices[1], vertices[2], u1, v1, u2, v2); // East
// drawFace(tessellator, vertices[4], vertices[5], vertices[0], vertices[1], u1, v1, u2, v2); // North
// drawFace(tessellator, vertices[7], vertices[4], vertices[3], vertices[0], u1, v1, u2, v2); // West
// drawFace(tessellator, vertices[5], vertices[4], vertices[6], vertices[7], u1, v1, u2, v2); // Top
//
// // Inside
// drawFace(tessellator, vertices[1], vertices[0], vertices[2], vertices[3], u1, v1, u2, v2); // Bottom
// drawFace(tessellator, vertices[7], vertices[6], vertices[3], vertices[2], u1, v1, u2, v2); // South
// drawFace(tessellator, vertices[6], vertices[5], vertices[2], vertices[1], u1, v1, u2, v2); // East
// drawFace(tessellator, vertices[5], vertices[4], vertices[1], vertices[0], u1, v1, u2, v2); // North
// drawFace(tessellator, vertices[4], vertices[7], vertices[0], vertices[3], u1, v1, u2, v2); // West
// drawFace(tessellator, vertices[4], vertices[5], vertices[7], vertices[6], u1, v1, u2, v2); // Top
}
@@ -1,7 +1,5 @@
package electroblob.wizardry.client.renderer; package electroblob.wizardry.client.renderer;
import org.lwjgl.opengl.GL11;
import electroblob.wizardry.Wizardry; import electroblob.wizardry.Wizardry;
import electroblob.wizardry.entity.construct.EntityDecay; import electroblob.wizardry.entity.construct.EntityDecay;
import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.BufferBuilder;
@@ -12,6 +10,7 @@ import net.minecraft.client.renderer.entity.Render;
import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import org.lwjgl.opengl.GL11;
public class RenderDecay extends Render<EntityDecay> { public class RenderDecay extends Render<EntityDecay> {
@@ -1,8 +1,5 @@
package electroblob.wizardry.client.renderer; package electroblob.wizardry.client.renderer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import electroblob.wizardry.Wizardry; import electroblob.wizardry.Wizardry;
import electroblob.wizardry.entity.living.EntityDecoy; import electroblob.wizardry.entity.living.EntityDecoy;
import net.minecraft.client.model.ModelBiped; import net.minecraft.client.model.ModelBiped;
@@ -12,10 +9,11 @@ import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.relauncher.ReflectionHelper; import net.minecraftforge.fml.relauncher.ReflectionHelper;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT) import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
//@SideOnly(Side.CLIENT)
public class RenderDecoy extends RenderBiped<EntityDecoy> { public class RenderDecoy extends RenderBiped<EntityDecoy> {
private static final ResourceLocation steveTextures = new ResourceLocation("textures/entity/steve.png"); private static final ResourceLocation steveTextures = new ResourceLocation("textures/entity/steve.png");
@@ -7,10 +7,8 @@ import net.minecraft.client.renderer.entity.RenderBiped;
import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.client.renderer.entity.layers.LayerBipedArmor; import net.minecraft.client.renderer.entity.layers.LayerBipedArmor;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT) //@SideOnly(Side.CLIENT)
public class RenderEvilWizard extends RenderBiped<EntityEvilWizard> { public class RenderEvilWizard extends RenderBiped<EntityEvilWizard> {
static final ResourceLocation[] textures = new ResourceLocation[6]; static final ResourceLocation[] textures = new ResourceLocation[6];

Some files were not shown because too many files have changed in this diff Show More