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
eclipse
run
misc
libs
logs
/misc
/libs
libsfordeobf
generated
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.
version = " 4.1.4 - MC 1.12.2"
version = "4.2.0" // There, it matches semver, happy now? (I am just gonna rename the file afterwards, you know that right?)
group= "electroblob.wizardry"// http://maven.apache.org/guides/mini/guide-naming-conventions.html
archivesBaseName = "Electroblob's Wizardry "
archivesBaseName = "ElectroblobsWizardry"
sourceCompatibility = targetCompatibility = "1.8" // Need this here so eclipse task generates correctly.
compileJava {
sourceCompatibility = targetCompatibility = "1.8"
}
repositories {
maven {
// location of the maven that hosts JEI files
name = "Progwml6 maven"
url = "http://dvs1.progwml6.com/files/maven"
}
maven {
// location of a maven mirror for JEI files, as a fallback
name = "ModMaven"
url = "modmaven.k-4u.nl"
}
flatDir {
// evErYthINg hAS tO Be a mAVeN rEPoSiTOrY... (to use deobfProvided, for some reason)
// The lack of documentation is actually quite impressive, see the following threads:
// http://www.minecraftforge.net/forum/topic/44262-solved-how-to-build-mod-with-dependencies/
// https://www.minecraftforum.net/forums/mapping-and-modding-java-edition/mapping-and-modding-tutorials/2866817-setting-up-dependencies-in-minecraft-forge-with
// http://www.minecraftforge.net/forum/topic/44534-adding-dependencies-to-a-forge-project/
dirs "libsfordeobf" // Don't use libs or forge will think you have duplicate mods
}
}
minecraft {
version = "1.12.2-14.23.2.2611"
version = "1.12.2-14.23.5.2814"
runDir = "run"
// the mappings can be changed at any time, and must be in the following format.
@@ -39,6 +61,17 @@ dependencies {
//compile "some.group:artifact:version:classifier"
//compile "some.group:artifact:version"
// compile against the JEI API but do not include it at runtime
deobfProvided "mezz.jei:jei_${mc_version}:${jei_version}:api"
// at runtime, use the full JEI jar
runtime "mezz.jei:jei_${mc_version}:${jei_version}"
//deobfProvided "baubles:baubles-1.12:1.5.2:api" // #ihavenoideawhatimdoing #itjustworks
//runtime "baubles:baubles-1.12:1.5.2" // If in doubt, copy what already works
//deobfProvided "antiqueatlas:antiqueatlas-1.12.2:4.5.1:src"
//runtime "antiqueatlas:antiqueatlas-1.12.2:4.5.1"
// real examples
//compile 'com.mod-buildcraft:buildcraft:6.0.8:dev' // adds buildcraft to the dev env
//compile 'com.googlecode.efficient-java-matrix-library:ejml:0.24' // adds ejml 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.
# This is required to provide enough memory for the Minecraft decompilation process.
org.gradle.jvmargs=-Xmx3G
mc_version=1.12.2
jei_version=4.13.1.225
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
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
locations of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
locations of your Java installation."
fi
# Increase the maximum file descriptors if we can.
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.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo locations of your Java installation.
goto fail
@@ -41,7 +41,7 @@ echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo locations of your Java installation.
goto fail
@@ -1,33 +1,32 @@
package electroblob.wizardry;
import electroblob.wizardry.client.particle.ParticleWizardry;
import electroblob.wizardry.item.ItemSpectralBow;
import electroblob.wizardry.packet.PacketCastContinuousSpell;
import electroblob.wizardry.packet.PacketCastSpell;
import electroblob.wizardry.packet.PacketClairvoyance;
import electroblob.wizardry.packet.PacketGlyphData;
import electroblob.wizardry.packet.PacketNPCCastSpell.Message;
import electroblob.wizardry.packet.PacketPlayerSync;
import electroblob.wizardry.packet.PacketTransportation;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.packet.*;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.ParticleBuilder.Type;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.Style;
import net.minecraft.util.text.TextFormatting;
import net.minecraft.util.text.translation.I18n;
import net.minecraft.world.World;
import net.minecraftforge.common.config.Property;
import java.util.List;
import java.util.Set;
/**
* The common proxy for wizardry, serving the usual purpose of dealing with all things that need to be handled
* differently on the client and the server. A lot of the methods here appear to do absolutely nothing; this is because
* they do client-only things which are only handled in the client proxy.
*
* @see {@link electroblob.wizardry.client.ClientProxy}
* @see electroblob.wizardry.client.ClientProxy ClientProxy
* @author Electroblob
* @since Wizardry 1.0
*/
@@ -51,15 +50,19 @@ public class CommonProxy {
public void registerResourceReloadListeners(){}
public void registerSoundEventListener(){}
public void registerAtlasMarkers(){}
// SECTION Particles
// ===============================================================================================================
/** Called from init() in the main mod class to initialise the particle factories. */
public void initParticleFactories(){} // Does nothing since particles are client-side only
public void registerParticles(){} // Does nothing since particles are client-side only
/** Creates a new particle of the specified type from the appropriate particle factory. <i>Does not actually spawn the
* 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;
}
@@ -70,19 +73,23 @@ public class CommonProxy {
// SECTION Items
// ===============================================================================================================
public boolean shouldDisplayDiscovered(Spell spell, ItemStack stack){
return false;
}
public net.minecraft.client.gui.FontRenderer getFontRenderer(ItemStack stack){
return null;
}
/**
* Returns the translated name of the scroll, taking spell discovery into account. If, for some reason, this gets
* called server-side, it uses the deprecated server version of I18n, with a warning. Since any likely server-side
* use of this method will be for text-based logic purposes (like Bibliocraft's book checking system), and since no
* particular player instance can be accessed, spell discovery is ignored.
* called server-side, it uses the deprecated server version of I18n. Since any likely server-side
* use of this method will be for text-based logic purposes (like Bibliocraft's book checking system), and since
* no particular player instance can be accessed, spell discovery is ignored.
*/
public String getScrollDisplayName(ItemStack scroll){
Spell spell = Spell.get(scroll.getItemDamage());
Spell spell = Spell.byMetadata(scroll.getItemDamage());
return I18n.translateToLocalFormatted("item." + Wizardry.MODID + ":scroll.name",
I18n.translateToLocal("spell." + spell.getUnlocalisedName())).trim();
@@ -92,29 +99,53 @@ public class CommonProxy {
return ((ItemSpectralBow)WizardryItems.spectral_bow).getDefaultDurabilityForDisplay(stack);
}
/** Like {@link CommonProxy#addMultiLineDescription(List, String, Style)}, but style defaults to light grey. */
public void addMultiLineDescription(List<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
// ===============================================================================================================
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
// ===============================================================================================================
@@ -123,6 +154,8 @@ public class CommonProxy {
public void setToHUDChooserEntry(Property property){}
public void setToNamedBooleanEntry(Property property){}
// public void setToEntityNameEntry(Property property){}
/**
@@ -137,6 +170,75 @@ public class CommonProxy {
*/
public void playMovingSound(Entity entity, SoundEvent sound, SoundCategory category, float volume, float pitch, boolean repeat){}
/**
* Plays a continuous spell sound which moves with the given entity.
*
* @param entity The source of the sound
* @param spell The spell this sound is associated with
* @param start The starting SoundEvent to play
* @param loop The main looped SoundEvent to play
* @param end The ending SoundEvent to play
* @param category The SoundCategory to use
* @param volume Volume relative to 1
* @param pitch Pitch relative to 1
*/
public void playSpellSoundLoop(EntityLivingBase entity, Spell spell, SoundEvent start, SoundEvent loop, SoundEvent end, SoundCategory category, float volume, float pitch){}
/**
* Plays a continuous spell sound which moves with the given entity.
*
* @param entity The source of the sound
* @param spell The spell this sound is associated with
* @param sounds An array of the sound events to play, which must have three elements in the order: start, loop, end
* @param category The SoundCategory to use
* @param volume Volume relative to 1
* @param pitch Pitch relative to 1
* @throws IllegalArgumentException if the given array contains less than 3 sound events
*/
public void playSpellSoundLoop(EntityLivingBase entity, Spell spell, SoundEvent[] sounds, SoundCategory category, float volume, float pitch){
if(sounds.length < 3) throw new IllegalArgumentException("Tried to play a continuous spell sound using an array "
+ "of sound events, but the given array contained less than 3 sound events!");
playSpellSoundLoop(entity, spell, sounds[0], sounds[1], sounds[2], category, volume, pitch);
}
/**
* Plays a continuous spell sound at the given position.
* @param world The world in which to play the sound
* @param x The x coordinate to play the sound at
* @param y The y coordinate to play the sound at
* @param z The z coordinate to play the sound at
* @param spell The spell this sound is associated with
* @param start The starting SoundEvent to play
* @param loop The main looped SoundEvent to play
* @param end The ending SoundEvent to play
* @param category The SoundCategory to use
* @param volume Volume relative to 1
* @param pitch Pitch relative to 1
* @param duration The duration of the sound, or -1 to link it to a dispenser at the given coordinates
*/
public void playSpellSoundLoop(World world, double x, double y, double z, Spell spell, SoundEvent start, SoundEvent loop, SoundEvent end, SoundCategory category, float volume, float pitch, int duration){}
/**
* Plays a continuous spell sound at the given position.
*
* @param world The world in which to play the sound
* @param x The x coordinate to play the sound at
* @param y The y coordinate to play the sound at
* @param z The z coordinate to play the sound at
* @param spell The spell this sound is associated with
* @param sounds An array of the sound events to play, which must have three elements in the order: start, loop, end
* @param category The SoundCategory to use
* @param volume Volume relative to 1
* @param pitch Pitch relative to 1
* @param duration The duration of the sound, or -1 to link it to a dispenser at the given coordinates
* @throws IllegalArgumentException if the given array contains less than 3 sound events
*/
public void playSpellSoundLoop(World world, double x, double y, double z, Spell spell, SoundEvent[] sounds, SoundCategory category, float volume, float pitch, int duration){
if(sounds.length < 3) throw new IllegalArgumentException("Tried to play a continuous spell sound using an array "
+ "of sound events, but the given array contained less than 3 sound events!");
playSpellSoundLoop(world, x, y, z, spell, sounds[0], sounds[1], sounds[2], category, volume, pitch, duration);
}
/**
* Gets the client side world using Minecraft.getMinecraft().world. <b>Only to be called client side!</b> Returns
* null on the server side.
+563 -120
View File
@@ -1,13 +1,9 @@
package electroblob.wizardry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import electroblob.wizardry.packet.PacketSyncSettings;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.AllyDesignationSystem.FriendlyFire;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
import net.minecraft.entity.EntityList;
@@ -19,14 +15,18 @@ import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.common.config.Property;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.relauncher.Side;
import java.util.*;
import java.util.regex.Pattern;
/**
* Singleton class which deals with everything related to wizardry's config file. To access individual settings, use
* {@link Wizardry#settings}. Also stores a few string constants for easy access.
* <p>
* As part of the 1.2 update and code overhaul, the way the config settings work in multiplayer has been tightened up.
* <p></p>
* As part of the 2.1 update and code overhaul, the way the config settings work in multiplayer has been tightened up.
* Importantly, there are <b>three</b> different types of config options:
* <p>
* <p></p>
* <li>Server-only settings. These only affect server-side code and hence are not synced. Changing these locally only
* has an effect if the local game is the host, i.e. a dedicated server, a LAN host or a singleplayer world. Examples
* include worldgen, mob drops and commands.
@@ -38,7 +38,7 @@ import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
* <li>Client-only settings. These settings only affect client-side code and hence are not synced. Each client obeys its
* own values for these, and changing them on a dedicated server will have no effect. These are usually only display and
* controls settings.</li>
* <p>
* <p></p>
* Each of the settings fields in this class is marked with one of the above categories to indicate which it belongs to.
* This in turn dictates which logical side it should be called from: client, server or both. <b> Do not access a config
* setting from the wrong side, because it may cause unexpected or strange behaviour.</b>
@@ -46,13 +46,14 @@ import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
* @since Wizardry 1.2
* @author Electroblob
*/
// TODO: New plan: convert to the @Config system BUT separate the spells config out so it doesn't break stuff. (Maybe even
// make it a JSON instead? Either way, there's scope for then adding more options for adjusting the base attributes etc.)
// For the time being, I'm sticking with the old config system because @Config doesn't support custom config entry classes
// @Config(modid = Wizardry.MODID)
@SuppressWarnings("deprecation") // Used server I18n deliberately; we want to write the comments in english.
@SuppressWarnings("deprecation") // Used server I18n deliberately; we want to write the comments in the actual config file in english.
public final class Settings {
// Category names
/** The unlocalised name of the gameplay config category. */
public static final String GAMEPLAY_CATEGORY = "gameplay";
/** The unlocalised name of the spells config category. */
public static final String SPELLS_CATEGORY = "spells";
/** The unlocalised name of the resistances config category. */
@@ -63,8 +64,14 @@ public final class Settings {
public static final String COMMANDS_CATEGORY = "commands";
/** The unlocalised name of the worldgen config category. */
public static final String WORLDGEN_CATEGORY = "worldgen";
/** The unlocalised name of the gameplay config category. */
public static final String GAMEPLAY_CATEGORY = "gameplay";
/** The unlocalised name of the compatibility config category. */
public static final String COMPATIBILITY_CATEGORY = "compatibility";
private static final String[] DEFAULT_LOOT_INJECTION_LOCATIONS = {"minecraft:chests/simple_dungeon",
"minecraft:chests/abandoned_mineshaft", "minecraft:chests/desert_pyramid", "minecraft:chests/jungle_temple",
"minecraft:chests/stronghold_corridor", "minecraft:chests/stronghold_crossing",
"minecraft:chests/stronghold_library", "minecraft:chests/igloo_chest", "minecraft:chests/woodland_mansion",
"minecraft:chests/end_city_treasure"};
/** The wizardry config file. */
private Configuration config;
@@ -73,34 +80,88 @@ public final class Settings {
// has an effect if the local game is the host, i.e. a dedicated server, a LAN host or a singleplayer world.
// Worldgen
/** <b>[Server-only]</b> Whether to use faster worldgen at the cost of 'seamlessness'. */
public boolean fastWorldgen = false;
/** <b>[Server-only]</b> List of dimension ids in which to generate wizard towers. */
public int[] towerDimensions = {0};
/** <b>[Server-only]</b> The rarity of wizard towers, used by the world generator. Larger numbers are rarer. */
public int towerRarity = 8;
public int towerRarity = 600;
/** <b>[Server-only]</b> List of structure file locations for wizard towers without loot chests. */
public ResourceLocation[] towerFiles = {new ResourceLocation(Wizardry.MODID, "wizard_tower_0"),
new ResourceLocation(Wizardry.MODID, "wizard_tower_1"),
new ResourceLocation(Wizardry.MODID, "wizard_tower_2"),
new ResourceLocation(Wizardry.MODID, "wizard_tower_3")};
/** <b>[Server-only]</b> List of structure file locations for wizard towers with loot chests. */
public ResourceLocation[] towerWithChestFiles = {new ResourceLocation(Wizardry.MODID, "wizard_tower_chest_0"),
new ResourceLocation(Wizardry.MODID, "wizard_tower_chest_1"),
new ResourceLocation(Wizardry.MODID, "wizard_tower_chest_2"),
new ResourceLocation(Wizardry.MODID, "wizard_tower_chest_3")};
/** <b>[Server-only]</b> List of dimension ids in which to generate obelisks. */
public int[] obeliskDimensions = {0, -1};
/** <b>[Server-only]</b> The rarity of obelisks, used by the world generator. Larger numbers are rarer. */
public int obeliskRarity = 550;
/** <b>[Server-only]</b> List of structure file locations for obelisks. */
public ResourceLocation[] obeliskFiles = {new ResourceLocation(Wizardry.MODID, "obelisk_0"),
new ResourceLocation(Wizardry.MODID, "obelisk_1"),
new ResourceLocation(Wizardry.MODID, "obelisk_2"),
new ResourceLocation(Wizardry.MODID, "obelisk_3"),
new ResourceLocation(Wizardry.MODID, "obelisk_4")};
/** <b>[Server-only]</b> List of dimension ids in which to generate shrines. */
public int[] shrineDimensions = {0, -1};
/** <b>[Server-only]</b> The rarity of shrines, used by the world generator. Larger numbers are rarer. */
public int shrineRarity = 1000;
/** <b>[Server-only]</b> List of structure file locations for shrines. */
public ResourceLocation[] shrineFiles = {new ResourceLocation(Wizardry.MODID, "shrine_0"),
new ResourceLocation(Wizardry.MODID, "shrine_1"),
new ResourceLocation(Wizardry.MODID, "shrine_2"),
new ResourceLocation(Wizardry.MODID, "shrine_3"),
new ResourceLocation(Wizardry.MODID, "shrine_4"),
new ResourceLocation(Wizardry.MODID, "shrine_5"),
new ResourceLocation(Wizardry.MODID, "shrine_6"),
new ResourceLocation(Wizardry.MODID, "shrine_7")};
/** <b>[Server-only]</b> The chance for wizard towers to generate with an evil wizard and chest inside. */
public double evilWizardChance = 0.2;
/** <b>[Server-only]</b> List of dimension ids in which to generate crystal ore. */
public int[] oreDimensions = {0};
/** <b>[Server-only]</b> List of dimension ids in which to generate crystal ore. */
/** <b>[Server-only]</b> List of dimension ids in which to generate crystal flowers. */
public int[] flowerDimensions = {0};
/** <b>[Server-only]</b> List of dimension ids in which to generate crystal ore. */
public int[] towerDimensions = {0};
/**
* <b>[Server-only]</b> Whether or not wizardry loot should generate in dungeon chests. Note that this does not
* affect the generation of loot in wizard towers.
* <b>[Server-only]</b> List of resource location strings for loot tables to inject wizardry loot into. Note that
* this does not affect the generation of loot in wizard towers.
*/
public boolean generateLoot = true;
public ResourceLocation[] lootInjectionLocations = toResourceLocations(DEFAULT_LOOT_INJECTION_LOCATIONS);
// Entities' drops, targeting, damage, etc.
/** <b>[Server-only]</b> Chance (out of 200) for mobs to drop spell books. */
public int spellBookDropChance = 3;
/** <b>[Server-only]</b> Whitelist for loot tables to inject additional mob drops into. */
public ResourceLocation[] mobLootTableWhitelist = {};
/** <b>[Server-only]</b> Blacklist for loot tables to inject additional mob drops into. */
public ResourceLocation[] mobLootTableBlacklist = toResourceLocations("entities/vex", "entities/ender_dragon",
"entities/wither", "entities/silverfish", "entities/endermite",
Wizardry.MODID + "entities/evil_wizard");
/**
* <b>[Server-only]</b> Whether or not players can teleport through unbreakable blocks (e.g. bedrock) using the
* phase step spell.
*/
public boolean teleportThroughUnbreakableBlocks = false;
/** <b>[Server-only]</b> Whether to allow players to damage their designated allies using magic. */
public boolean friendlyFire = true;
public FriendlyFire friendlyFire = FriendlyFire.ALL;
/** <b>[Server-only]</b> Whether to allow players to disarm other players using the telekinesis spell. */
public boolean telekineticDisarmament = true;
/** <b>[Server-only]</b> Whether summoned creatures can revenge attack their caster if their caster attacks them. */
public boolean minionRevengeTargeting = true;
/** <b>[Server-only]</b> Whether to allow players to change the world time using the speed time spell. */
public boolean worldTimeManipulation = true;
/** <b>[Server-only]</b> Whether to allow players to move other players around using magic. */
public boolean playersMoveEachOther = true;
/** <b>[Server-only]</b> Whether spells cast by players can destroy blocks in the world. */
public boolean playerBlockDamage = true;
/** <b>[Server-only]</b> Whether to revert to the old wand upgrade system, which only requires tomes of arcana. */
public boolean legacyWandLevelling = false;
/** <b>[Server-only]</b> Whether to replace Minecraft's own fireballs with wizardry fireballs. */
public boolean replaceVanillaFireballs = true;
/** <b>[Server-only]</b> Whether to replace Minecraft's distance-based fall damage calculation with an equivalent,
* velocity-based one. */
public boolean replaceVanillaFallDamage = true;
/**
* <b>[Server-only]</b> List of registry names of entities which summoned creatures are allowed to attack, in addition
* to the defaults.
@@ -110,18 +171,39 @@ public final class Settings {
* <b>[Server-only]</b> List of registry names of entities which summoned creatures are specifically not allowed to
* attack, overriding the defaults and the whitelist.
*/
public ResourceLocation[] summonedCreatureTargetsBlacklist = {new ResourceLocation("creeper")};
public ResourceLocation[] summonedCreatureTargetsBlacklist = toResourceLocations("creeper");
/**
* <b>[Server-only]</b> List of registry names of entities which are immune to the mind control spell, in addition to
* the defaults.
*/
public ResourceLocation[] mindControlTargetsBlacklist = {};
/**
* <b>[Server-only]</b> List of registry names of items which cannot be smelted by the pocket furnace spell, in
* addition to armour, tools and weapons.
*/
public ResourceLocation[] pocketFurnaceItemBlacklist = toResourceLocations("cobblestone", "netherrack");
/** <b>[Server-only]</b> List of registry names of blocks which can be detected by the divination spell. */
public ResourceLocation[] divinationOreWhitelist = {};
/** <b>[Server-only]</b> List of registry names of items which count as swords for imbuement spells. */
public ResourceLocation[] swordItemWhitelist = {};
/** <b>[Server-only]</b> List of registry names of items which count as bows for imbuement spells. */
public ResourceLocation[] bowItemWhitelist = {};
/** <b>[Server-only]</b> Map of items to values which wizard trades may use as currency. */
public Map<ResourceLocation, Integer> currencyItems = new HashMap<>();
/** <b>[Server-only]</b> Global damage scaling factor for all player magic damage. */
public double playerDamageScale = 1.0f;
public double playerDamageScale = 1.0;
/** <b>[Server-only]</b> Global damage scaling factor for all npc magic damage. */
public double npcDamageScale = 1.0f;
/** <b>[Server-only]</b> List of dimension ids in which evil wizards can spawn. */
public int[] evilWizardDimensions = {0};
public double npcDamageScale = 1.0;
/** <b>[Server-only]</b> List of dimension ids in which wizardry's hostile mobs can spawn. */
public int[] mobSpawnDimensions = {0};
/** <b>[Server-only]</b> Spawn rate for naturally-spawned evil wizards; higher numbers mean more evil wizards will spawn. */
public int evilWizardSpawnRate = 3;
/** <b>[Server-only]</b> Spawn rate for naturally-spawned evil wizards; higher numbers mean more evil wizards will spawn. */
public int iceWraithSpawnRate = 3;
/** <b>[Server-only]</b> Spawn rate for naturally-spawned evil wizards; higher numbers mean more evil wizards will spawn. */
public int lightningWraithSpawnRate = 1;
/** <b>[Server-only]</b> List of registry names of biomes in which wizardry's hostile mobs cannot spawn. */
public ResourceLocation[] mobSpawnBiomeBlacklist = toResourceLocations("mushroom_island", "mushroom_island_shore");
// Commands (these don't need synchronising since typing a command always queries the server).
/**
@@ -138,6 +220,25 @@ public final class Settings {
/** <b>[Server-only]</b> The name of the /allies command. */
public String alliesCommandName = "allies";
/**
* <b>[Server-only]</b> List of damage source string identifiers to be ignored when re-applying damage.
*/
public String[] damageSourceBlacklist = {};
/** <b>[Server-only]</b> Whether to print compatibility warnings to the console. */
public boolean compatibilityWarnings = true;
/** <b>[Server-only]</b> Whether Baubles integration features are enabled. */
public boolean baublesIntegration = true;
// /** <b>[Server-only]</b> Whether JEI integration features are enabled. */
// public boolean jeiIntegration = true;
/** <b>[Server-only]</b> Whether Antique Atlas integration features are enabled. */
public boolean antiqueAtlasIntegration = true;
/** <b>[Server-only]</b> Whether global markers for wizard towers are added to antique atlases. */
public boolean autoTowerMarkers = true;
/** <b>[Server-only]</b> Whether global markers for obelisks are added to antique atlases. */
public boolean autoObeliskMarkers = true;
/** <b>[Server-only]</b> Whether global markers for shrines are added to antique atlases. */
public boolean autoShrineMarkers = true;
// Synchronised settings. These settings affect both client-side AND server-side code. Changing these locally
// only has an effect if the local game is the host, i.e. a dedicated server, a LAN host or a singleplayer world.
@@ -148,6 +249,18 @@ public final class Settings {
* survival mode if this is false.
*/
public boolean discoveryMode = true;
/**
* <b>[Synchronised]</b> When set to true, players in creative mode can bypass arcane locks regardless of whether
* they are an op or not.
*/
public boolean creativeBypassesArcaneLock = true;
/**
* <b>[Synchronised]</b> When set to true, players will be slowed when a nearby player or entity has the slow time
* effect.
*/
public boolean slowTimeAffectsPlayers = true;
/** <b>[Synchronised]</b> Chance of 'misreading' an undiscovered spell and triggering a forfeit instead. */
public double forfeitChance = 0.2;
// Client-only settings. These settings only affect client-side code and hence are not synced. Each client obeys
// its own values for these, and changing them on a dedicated server will have no effect.
@@ -157,13 +270,23 @@ public final class Settings {
* <b>[Client-only]</b> Whether the player can switch between spells on a wand by scrolling with the mouse wheel
* while sneaking.
*/
public boolean enableShiftScrolling = true;
/** <b>[Client-only]</b> Whether to reverse the spell switching scroll direction.*/
public boolean shiftScrolling = true;
/** <b>[Client-only]</b> Whether to reverse the spell switching scroll direction. */
public boolean reverseScrollDirection = false;
// Display
/** <b>[Client-only]</b> Whether to show summoned creatures' names and owners above their heads. */
public boolean showSummonedCreatureNames = true;
public boolean summonedCreatureNames = true;
/**
* <b>[Client-only]</b> When set to true, sections of <i>The Wizard's Handbook</i> are unlocked when a player
* gains the advancement that triggers them, and are hidden otherwise. When set to false, the entire handbook is
* readable regardless of advancement progress.
*/
public boolean handbookProgression = true;
/** <b>[Client-only]</b> Whether the various book GUIs pause the game in singleplayer worlds. */
public boolean booksPauseGame = true;
/** <b>[Client-only]</b> Whether to use custom shaders for certain spells. */
public boolean useShaders = true;
/** <b>[Client-only]</b> The position of the spell HUD. */
public GuiPosition spellHUDPosition = GuiPosition.BOTTOM_LEFT;
@@ -171,18 +294,22 @@ public final class Settings {
/** <b>[Client-only]</b> The string identifier of the skin used for the spell HUD. */
public String spellHUDSkin = DEFAULT_HUD_SKIN_KEY;
/** Set of constants for each of the four positions that the spell HUD can be in. */
/** Set of constants for each of the eight positions that the spell HUD can be in. */
public enum GuiPosition {
BOTTOM_LEFT("Bottom left", false, false),
TOP_LEFT("Top left", false, true),
TOP_RIGHT("Top right", true, true),
BOTTOM_RIGHT("Bottom right", true, false);
BOTTOM_LEFT("Bottom left", false, false, false),
TOP_LEFT("Top left", false, true, false),
TOP_RIGHT("Top right", true, true, false),
BOTTOM_RIGHT("Bottom right", true, false, false),
FOLLOW_BOTTOM("Follow wand, bottom", false, false, true),
FOLLOW_TOP("Follow wand, top", false, true, true),
OPPOSITE_BOTTOM("Opposite wand, bottom", true, false, true),
OPPOSITE_TOP("Opposite wand, top", true, true, true);
/** Constant array storing the names of each of the constants, in the order they are declared. */
public static final String[] names;
static{
static {
names = new String[values().length];
for(GuiPosition position : values()){
names[position.ordinal()] = position.name;
@@ -191,13 +318,15 @@ public final class Settings {
/** The readable name for this GUI position that will be displayed on the button in the config GUI. */
public final String name;
public boolean flipX;
public boolean flipY;
public final boolean flipX;
public final boolean flipY;
public final boolean dynamic;
GuiPosition(String name, boolean flipX, boolean flipY){
GuiPosition(String name, boolean flipX, boolean flipY, boolean dynamic){
this.name = name;
this.flipX = flipX;
this.flipY = flipY;
this.dynamic = dynamic;
}
/**
@@ -236,8 +365,9 @@ public final class Settings {
setupGeneralConfig();
setupWorldgenConfig();
setupClientConfig();
if(event.getSide() == Side.CLIENT) setupClientConfig(); // Server has no spell HUD skins so this would crash it
setupCommandsConfig();
setupCompatibilityConfig();
config.save();
}
@@ -269,6 +399,7 @@ public final class Settings {
setupWorldgenConfig();
setupClientConfig();
setupCommandsConfig();
setupCompatibilityConfig();
setupSpellsConfig();
setupResistancesConfig();
@@ -298,6 +429,7 @@ public final class Settings {
I18n.translateToLocal("spell." + spell.getUnlocalisedName() + ".desc"));
// Uses the same config key as the spell name, because - well, that's what it's called!
property.setLanguageKey("spell." + spell.getUnlocalisedName());
Wizardry.proxy.setToNamedBooleanEntry(property);
spell.setEnabled(property.getBoolean());
}
@@ -306,7 +438,7 @@ public final class Settings {
private void setupGeneralConfig(){
// This trick is borrowed from forge; it sorts the config options into the order you want them.
List<String> propOrder = new ArrayList<String>();
List<String> propOrder = new ArrayList<>();
Property property;
@@ -315,108 +447,251 @@ public final class Settings {
property = config.get(GAMEPLAY_CATEGORY, "discoveryMode", true,
"For those who like a sense of mystery! When set to true, spells you haven't cast yet will be unreadable until you cast them (on a per-world basis). Has no effect when in creative mode. Spells of identification will be unobtainable in survival mode if this is false.");
property.setLanguageKey("config." + Wizardry.MODID + ".discovery_mode");
Wizardry.proxy.setToNamedBooleanEntry(property);
property.setRequiresWorldRestart(true);
discoveryMode = property.getBoolean();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "friendlyFire", true,
"Whether to allow players to damage their designated allies using magic.");
property = config.get(GAMEPLAY_CATEGORY, "legacyWandLevelling", false,
"Controls whether wands are required to gain progression before they can be upgraded to the next tier. Enable this option to revert to the pre-4.2 system, which only requires tomes of arcana. Wands will still gain progression even when this is enabled, so if you go back to the new system you won't lose any progress.");
property.setLanguageKey("config." + Wizardry.MODID + ".legacy_wand_levelling");
Wizardry.proxy.setToNamedBooleanEntry(property);
legacyWandLevelling = property.getBoolean();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "friendlyFire", FriendlyFire.ALL.name, "Controls which creatures may be damaged by your magic when allied to you. Your spells will not target your allies or creatures summoned/owned by them regardless of this setting, but this setting prevents all magic damage to allies.", FriendlyFire.names);
property.setLanguageKey("config." + Wizardry.MODID + ".friendly_fire");
friendlyFire = property.getBoolean();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "spellBookDropChance", 3,
"The chance for mobs to drop a spell book when killed. The greater this number, the more often they will drop. Set to 0 to disable spell book drops. Set to 200 for guaranteed drops.",
0, 200);
property.setLanguageKey("config." + Wizardry.MODID + ".spell_book_drop_chance");
Wizardry.proxy.setToNumberSliderEntry(property);
spellBookDropChance = property.getInt();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "evilWizardDimensions", new int[]{0},
"List of dimension ids in which evil wizards can spawn.");
property.setLanguageKey("config." + Wizardry.MODID + ".evil_wizard_dimensions");
property.setRequiresMcRestart(true);
evilWizardDimensions = property.getIntList();
propOrder.add(property.getName());
// These two aren't sliders because using a slider makes it difficult to fine-tune the numbers; the nature of a
// scaling factor means that 0.5 is as big a change as 2.0, so whilst a slider is fine for increasing the
// damage, it doesn't give fine enough control for values less than 1.
property = config.get(GAMEPLAY_CATEGORY, "playerDamageScaling", 1.0,
"Global damage scaling factor for the damage dealt by players casting spells, relative to 1.", 0, 20);
property.setLanguageKey("config." + Wizardry.MODID + ".player_damage_scaling");
playerDamageScale = property.getDouble();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "npcDamageScaling", 1.0,
"Global damage scaling factor for the damage dealt by NPCs casting spells, relative to 1.", 0, 20);
property.setLanguageKey("config." + Wizardry.MODID + ".npc_damage_scaling");
npcDamageScale = property.getDouble();
friendlyFire = FriendlyFire.fromName(property.getString());
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "minionRevengeTargeting", true,
"Whether summoned creatures can revenge attack their owner if their owner attacks them.");
property.setLanguageKey("config." + Wizardry.MODID + ".minion_revenge_targeting");
Wizardry.proxy.setToNamedBooleanEntry(property);
property.setRequiresWorldRestart(false);
minionRevengeTargeting = property.getBoolean();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "summonedCreatureTargetsWhitelist", new String[0],
"List of names of entities which summoned creatures and wizards are allowed to attack, in addition to the defaults. Add mod creatures to this list if you want summoned creatures to attack them and they aren't already doing so. Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
property.setLanguageKey("config." + Wizardry.MODID + ".summoned_creature_targets_whitelist");
property.setRequiresWorldRestart(true);
// Converts all strings in the list to a ResourceLocation.
summonedCreatureTargetsWhitelist = Arrays.stream(property.getStringList()).map(s -> new ResourceLocation(s.toLowerCase(Locale.ROOT).trim())).toArray(ResourceLocation[]::new);
property = config.get(GAMEPLAY_CATEGORY, "playersMoveEachOther", true,
"Whether to allow players to move other players around using magic.");
property.setLanguageKey("config." + Wizardry.MODID + ".players_move_each_other");
Wizardry.proxy.setToNamedBooleanEntry(property);
playersMoveEachOther = property.getBoolean();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "summonedCreatureTargetsBlacklist",
new String[]{"creeper"},
"List of names of entities which summoned creatures and wizards are specifically not allowed to attack, overriding the defaults and the whitelist. Add creatures to this list if allowing them to be attacked causes problems or is too destructive (removing creepers from this list is done at your own risk!). Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
property.setLanguageKey("config." + Wizardry.MODID + ".summoned_creature_targets_blacklist");
property.setRequiresWorldRestart(true);
// Converts all strings in the list to a ResourceLocation.
summonedCreatureTargetsBlacklist = Arrays.stream(property.getStringList()).map(s -> new ResourceLocation(s.toLowerCase(Locale.ROOT).trim())).toArray(ResourceLocation[]::new);
property = config.get(GAMEPLAY_CATEGORY, "playerBlockDamage", true,
"Whether spells cast by players can destroy blocks in the world. Set to false to prevent griefing. To prevent non-players from destroying blocks with magic, use the mobGriefing gamerule.");
property.setLanguageKey("config." + Wizardry.MODID + ".player_block_damage");
Wizardry.proxy.setToNamedBooleanEntry(property);
playerBlockDamage = property.getBoolean();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "telekineticDisarmament", true,
"Whether to allow players to disarm other players using the telekinesis spell. Set to false to prevent stealing of items.");
property.setLanguageKey("config." + Wizardry.MODID + ".telekinetic_disarmament");
Wizardry.proxy.setToNamedBooleanEntry(property);
telekineticDisarmament = property.getBoolean();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "teleportThroughUnbreakableBlocks", false,
"Whether players are allowed to teleport through unbreakable blocks (e.g. bedrock) using the phase step spell.");
property.setLanguageKey("config." + Wizardry.MODID + ".teleport_through_unbreakable_blocks");
Wizardry.proxy.setToNamedBooleanEntry(property);
teleportThroughUnbreakableBlocks = property.getBoolean();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "worldTimeManipulation", true,
"Whether players are allowed to change the world time with the speed time spell. If this is false, the speed time spell will not change the world time but will still speed up nearby block, entity and tile entity ticks.");
property.setLanguageKey("config." + Wizardry.MODID + ".world_time_manipulation");
Wizardry.proxy.setToNamedBooleanEntry(property);
worldTimeManipulation = property.getBoolean();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "replaceVanillaFireballs", true,
"Whether to replace Minecraft's own fireballs with wizardry fireballs. If this is disabled, only wizardry spells will use the custom fireballs.");
property.setLanguageKey("config." + Wizardry.MODID + ".replace_vanilla_fireballs");
Wizardry.proxy.setToNamedBooleanEntry(property);
replaceVanillaFireballs = property.getBoolean();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "replaceVanillaFallDamage", true,
"Whether to replace Minecraft's distance-based fall damage calculation with an equivalent, velocity-based one. This is done such that mobs in freefall will take exactly the same damage as normal, so it will not break falling-based mob farms. Disable this if you experience falling-related weirdness! If this is disabled, some spells will use a more simplistic method of resetting the player's fall damage in certain cases.");
property.setLanguageKey("config." + Wizardry.MODID + ".replace_vanilla_fall_damage");
Wizardry.proxy.setToNamedBooleanEntry(property);
replaceVanillaFallDamage = property.getBoolean();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "creativeBypassesArcaneLock", true,
"Whether any player in creative mode can bypass arcane-locked blocks. If this is false, players must also be op in order to do so.");
property.setLanguageKey("config." + Wizardry.MODID + ".creative_bypasses_arcane_lock");
Wizardry.proxy.setToNamedBooleanEntry(property);
creativeBypassesArcaneLock = property.getBoolean();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "slowTimeAffectsPlayers", true,
"Whether players are slowed when another nearby player uses the slow time spell. If this is false, mobs and projectiles will still be affected but players will move at normal speed.");
property.setLanguageKey("config." + Wizardry.MODID + ".slow_time_affects_players");
Wizardry.proxy.setToNamedBooleanEntry(property);
slowTimeAffectsPlayers = property.getBoolean();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "mobLootTableWhitelist", new String[0], "Whitelist for loot tables to inject additional mob drops (as specified in loot_tables/entities/mob_additions.json) into. Wizardry makes a best guess as to which loot tables belong to hostile mobs, but this may not always be correct or appropriate; add loot table locations (not entity IDs) to this list to manually include them.");
property.setLanguageKey("config." + Wizardry.MODID + ".mob_loot_table_whitelist");
property.setRequiresMcRestart(true);
mobLootTableWhitelist = getResourceLocationList(property);
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "mobLootTableBlacklist", new String[]{"entities/vex", "entities/ender_dragon", "entities/wither", Wizardry.MODID + ":entities/evil_wizard"}, "Blacklist for loot tables to inject additional mob drops (as specified in loot_tables/entities/mob_additions.json) into. Wizardry makes a best guess as to which loot tables belong to hostile mobs, but this may not always be correct or appropriate; add loot table locations (not entity IDs) to this list to manually exclude them.");
property.setLanguageKey("config." + Wizardry.MODID + ".mob_loot_table_blacklist");
property.setRequiresMcRestart(true);
mobLootTableBlacklist = getResourceLocationList(property);
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "mobSpawnDimensions", new int[]{0},
"List of dimension ids in which wizardry's hostile mobs can spawn.");
property.setLanguageKey("config." + Wizardry.MODID + ".mob_spawn_dimensions");
property.setRequiresMcRestart(true);
mobSpawnDimensions = property.getIntList();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "mobSpawnBiomeBlacklist", new String[]{"mushroom_island", "mushroom_island_shore"},
"List of names of biomes in which wizardry's hostile mobs cannot spawn. Biome names are not case-sensitive. For mod biomes, prefix with the mod ID (e.g. biomesoplenty:mystic_grove).");
property.setLanguageKey("config." + Wizardry.MODID + ".mob_spawn_biome_blacklist");
property.setRequiresMcRestart(true);
mobSpawnBiomeBlacklist = getResourceLocationList(property);
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "evilWizardSpawnRate", 3,
"Spawn rate for naturally-spawned evil wizards; higher numbers mean more evil wizards will spawn. 5 is equivalent to witches, 100 is equivalent to zombies, skeletons and creepers. Set to 0 to disable evil wizard spawning entirely.",
0, 100);
property.setLanguageKey("config." + Wizardry.MODID + ".evil_wizard_spawn_rate");
Wizardry.proxy.setToNumberSliderEntry(property);
property.setRequiresMcRestart(true);
evilWizardSpawnRate = property.getInt();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "iceWraithSpawnRate", 3,
"Spawn rate for naturally-spawned ice wraiths; higher numbers mean more ice wraiths will spawn. 5 is equivalent to witches, 100 is equivalent to zombies, skeletons and creepers. Set to 0 to disable ice wraith spawning entirely.",
0, 100);
property.setLanguageKey("config." + Wizardry.MODID + ".ice_wraith_spawn_rate");
Wizardry.proxy.setToNumberSliderEntry(property);
property.setRequiresMcRestart(true);
iceWraithSpawnRate = property.getInt();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "lightningWraithSpawnRate", 1,
"Spawn rate for naturally-spawned lightning wraiths; higher numbers mean more lightning wraiths will spawn. 5 is equivalent to witches, 100 is equivalent to zombies, skeletons and creepers. Set to 0 to disable lightning wraith spawning entirely.",
0, 100);
property.setLanguageKey("config." + Wizardry.MODID + ".lightning_wraith_spawn_rate");
Wizardry.proxy.setToNumberSliderEntry(property);
property.setRequiresMcRestart(true);
lightningWraithSpawnRate = property.getInt();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "forfeitChance", 0.2,
"The chance to 'misread' an undiscovered spell and trigger a forfeit instead. Setting this to 0 effectively disables the forfeit mechanic. Has no effect if discovery mode is disabled.",
0, 1);
property.setLanguageKey("config." + Wizardry.MODID + ".forfeit_chance");
Wizardry.proxy.setToNumberSliderEntry(property);
forfeitChance = property.getDouble();
propOrder.add(property.getName());
// These two aren't sliders because using a slider makes it difficult to fine-tune the numbers; the nature of a
// scaling factor means that 0.5 is as big a change as 2.0, so whilst a slider is fine for increasing the
// damage, it doesn't give fine enough control for values less than 1.
property = config.get(GAMEPLAY_CATEGORY, "playerDamageScaling", 1.0,
"Global damage scaling factor for the damage dealt by players casting spells, relative to 1.", 0, 255);
property.setLanguageKey("config." + Wizardry.MODID + ".player_damage_scaling");
playerDamageScale = property.getDouble();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "npcDamageScaling", 1.0,
"Global damage scaling factor for the damage dealt by NPCs casting spells, relative to 1.", 0, 255);
property.setLanguageKey("config." + Wizardry.MODID + ".npc_damage_scaling");
npcDamageScale = property.getDouble();
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "summonedCreatureTargetsWhitelist", new String[0],
"List of names of entities which summoned creatures and wizards are allowed to attack, in addition to the defaults. Add mod creatures to this list if you want summoned creatures to attack them and they aren't already doing so. SoundLoopSpellEntity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
property.setLanguageKey("config." + Wizardry.MODID + ".summoned_creature_targets_whitelist");
property.setRequiresWorldRestart(true);
summonedCreatureTargetsWhitelist = getResourceLocationList(property);
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "summonedCreatureTargetsBlacklist", new String[]{"creeper"},
"List of names of entities which summoned creatures and wizards are specifically not allowed to attack, overriding the defaults and the whitelist. Add creatures to this list if allowing them to be attacked causes problems or is too destructive (removing creepers from this list is done at your own risk!). SoundLoopSpellEntity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
property.setLanguageKey("config." + Wizardry.MODID + ".summoned_creature_targets_blacklist");
property.setRequiresWorldRestart(true);
summonedCreatureTargetsBlacklist = getResourceLocationList(property);
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "mindControlTargetsBlacklist", new String[]{},
"List of names of entities which cannot be mind controlled, in addition to the defaults. Add creatures to this list if allowing them to be mind-controlled causes problems or could be exploited. Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
"List of names of entities which cannot be mind controlled, in addition to the defaults. Add creatures to this list if allowing them to be mind-controlled causes problems or could be exploited. SoundLoopSpellEntity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
property.setLanguageKey("config." + Wizardry.MODID + ".mind_control_targets_blacklist");
property.setRequiresWorldRestart(true);
// Converts all strings in the list to a ResourceLocation.
mindControlTargetsBlacklist = Arrays.stream(property.getStringList()).map(s -> new ResourceLocation(s.toLowerCase(Locale.ROOT).trim())).toArray(ResourceLocation[]::new);
mindControlTargetsBlacklist = getResourceLocationList(property);
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "pocketFurnaceItemBlacklist", new String[]{"cobblestone", "netherrack"},
"List of registry names of blocks or items which cannot be smelted by the pocket furnace spell, in addition to armour, tools and weapons. Block/item names are not case sensitive. For mod items, prefix with the mod ID (e.g. " + Wizardry.MODID + ":crystal_ore).");
property.setLanguageKey("config." + Wizardry.MODID + ".pocket_furnace_item_blacklist");
property.setRequiresWorldRestart(true);
pocketFurnaceItemBlacklist = getResourceLocationList(property);
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "divinationOreWhitelist", new String[0], "List of registry names of ore blocks which can be detected by the divination spell. Block names are not case sensitive. For mod blocks, prefix with the mod ID (e.g. " + Wizardry.MODID + ":crystal_ore).");
property.setLanguageKey("config." + Wizardry.MODID + ".divination_ore_whitelist");
property.setRequiresWorldRestart(true);
divinationOreWhitelist = getResourceLocationList(property);
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "swordItemWhitelist", new String[0], "List of registry names of items which should count as swords for imbuement spells. Most swords should work automatically, but those that don't can be added manually here. Item names are not case sensitive. For mod items, prefix with the mod ID (e.g. tconstruct:broadsword).");
property.setLanguageKey("config." + Wizardry.MODID + ".sword_item_whitelist");
property.setRequiresWorldRestart(true);
swordItemWhitelist = getResourceLocationList(property);
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "bowItemWhitelist", new String[0], "List of registry names of items which should count as bows for imbuement spells. Most bows should work automatically, but those that don't can be added manually here. Item names are not case sensitive. For mod items, prefix with the mod ID (e.g. tconstruct:shortbow).");
property.setLanguageKey("config." + Wizardry.MODID + ".bow_item_whitelist");
property.setRequiresWorldRestart(true);
bowItemWhitelist = getResourceLocationList(property);
propOrder.add(property.getName());
property = config.get(GAMEPLAY_CATEGORY, "currencyItems", new String[]{"gold_ingot 3", "emerald 6"}, "List of registry names of items which wizard trades can use as currency (in the first slot; the second slot is unaffected). Each entry in this list should consist of an item registry name, followed by a single space, then an integer which defines the 'value' of the item. Higher values mean fewer of that currency item are required for a given trade.",
Pattern.compile("[A-Za-z:_]+ [0-9]+"));
property.setLanguageKey("config." + Wizardry.MODID + ".currency_items");
property.setRequiresWorldRestart(true);
propOrder.add(property.getName());
currencyItems = new HashMap<>();
for(String string : property.getStringList()){
String[] args = string.split(" ");
if(args.length != 2){
Wizardry.logger.warn("Invalid entry in currency items: {}", string);
continue; // Ignore invalid entries, the pattern above should ensure this never happens
}
try {
currencyItems.put(new ResourceLocation(args[0]), Integer.parseInt(args[1]));
}catch(NumberFormatException e){
Wizardry.logger.warn("Invalid integer in currency items: {}", args[1]);
}
}
config.setCategoryPropertyOrder(GAMEPLAY_CATEGORY, propOrder);
}
private void setupWorldgenConfig(){
// This trick is borrowed from forge; it sorts the config options into the order you want them.
List<String> propOrder = new ArrayList<String>();
List<String> propOrder = new ArrayList<>();
Property property;
config.addCustomCategoryComment(WORLDGEN_CATEGORY, "Settings that affect world generation. In multiplayer, the server/LAN host settings will apply.");
property = config.get(WORLDGEN_CATEGORY, "towerRarity", 8, "Rarity of wizard towers. Higher numbers are rarer. Set to 0 to disable wizard towers completely.", 0, 50);
property.setLanguageKey("config." + Wizardry.MODID + ".tower_rarity");
property.setRequiresWorldRestart(true);
Wizardry.proxy.setToNumberSliderEntry(property);
towerRarity = property.getInt();
property = config.get(WORLDGEN_CATEGORY, "fastWorldgen", false, "Whether to use faster worldgen at the cost of 'seamlessness'. Enabling this option removes the checks for steep slopes and cleanup of floating trees that improve the look of worldgen. Performance improvement will vary depending on your setup. This option will affect randomisation; for any given seed, structures will not be the same as when it is turned off.");
property.setLanguageKey("config." + Wizardry.MODID + ".fast_worldgen");
Wizardry.proxy.setToNamedBooleanEntry(property);
fastWorldgen = property.getBoolean();
propOrder.add(property.getName());
property = config.get(WORLDGEN_CATEGORY, "towerDimensions", new int[]{0}, "List of dimension ids in which wizard towers will generate.");
@@ -425,6 +700,74 @@ public final class Settings {
towerDimensions = property.getIntList();
propOrder.add(property.getName());
property = config.get(WORLDGEN_CATEGORY, "towerRarity", 600, "Rarity of wizard towers. 1 in this many chunks will contain a wizard tower, meaning higher numbers are rarer.", 20, 5000);
property.setLanguageKey("config." + Wizardry.MODID + ".tower_rarity");
property.setRequiresWorldRestart(true);
Wizardry.proxy.setToNumberSliderEntry(property);
towerRarity = property.getInt();
propOrder.add(property.getName());
property = config.get(WORLDGEN_CATEGORY, "evilWizardChance", 0.2, "The chance for wizard towers to generate with an evil wizard and chest inside, instead of a friendly wizard.", 0, 1);
property.setLanguageKey("config." + Wizardry.MODID + ".evil_wizard_chance");
property.setRequiresWorldRestart(true);
Wizardry.proxy.setToNumberSliderEntry(property);
evilWizardChance = property.getDouble();
propOrder.add(property.getName());
property = config.get(WORLDGEN_CATEGORY, "towerFiles", new String[]{Wizardry.MODID + ":wizard_tower_0", Wizardry.MODID + ":wizard_tower_1", Wizardry.MODID + ":wizard_tower_2", Wizardry.MODID + ":wizard_tower_3"},
"List of structure file locations for wizard towers without loot chests. One of these files will be randomly selected each time a wizard tower is generated. File locations are of the format [mod id]:[filename], which refers to the file assets/[mod id]/structures/[filename].nbt. Duplicate entries are permitted, allowing for simple weighting without duplicating the structure files themselves.");
property.setLanguageKey("config." + Wizardry.MODID + ".tower_files");
property.setRequiresWorldRestart(true);
towerFiles = getResourceLocationList(property);
propOrder.add(property.getName());
property = config.get(WORLDGEN_CATEGORY, "towerWithChestFiles", new String[]{Wizardry.MODID + ":wizard_tower_chest_0", Wizardry.MODID + ":wizard_tower_chest_1", Wizardry.MODID + ":wizard_tower_chest_2", Wizardry.MODID + ":wizard_tower_chest_3"},
"List of structure file locations for wizard towers with loot chests. One of these files will be randomly selected each time a wizard tower is generated. File locations are of the format [mod id]:[filename], which refers to the file assets/[mod id]/structures/[filename].nbt. Duplicate entries are permitted, allowing for simple weighting without duplicating the structure files themselves.");
property.setLanguageKey("config." + Wizardry.MODID + ".tower_with_chest_files");
property.setRequiresWorldRestart(true);
towerWithChestFiles = getResourceLocationList(property);
propOrder.add(property.getName());
property = config.get(WORLDGEN_CATEGORY, "obeliskDimensions", new int[]{0, -1}, "List of dimension ids in which obelisks will generate.");
property.setLanguageKey("config." + Wizardry.MODID + ".obelisk_dimensions");
property.setRequiresWorldRestart(true);
obeliskDimensions = property.getIntList();
propOrder.add(property.getName());
property = config.get(WORLDGEN_CATEGORY, "obeliskRarity", 550, "Rarity of obelisks. 1 in this many chunks will contain an obelisk, meaning higher numbers are rarer.", 20, 5000);
property.setLanguageKey("config." + Wizardry.MODID + ".obelisk_rarity");
property.setRequiresWorldRestart(true);
Wizardry.proxy.setToNumberSliderEntry(property);
obeliskRarity = property.getInt();
propOrder.add(property.getName());
property = config.get(WORLDGEN_CATEGORY, "obeliskFiles", new String[]{Wizardry.MODID + ":obelisk_0", Wizardry.MODID + ":obelisk_1", Wizardry.MODID + ":obelisk_2", Wizardry.MODID + ":obelisk_3", Wizardry.MODID + ":obelisk_4"},
"List of structure file locations for obelisks. One of these files will be randomly selected each time an obelisk is generated. File locations are of the format [mod id]:[filename], which refers to the file assets/[mod id]/structures/[filename].nbt. Duplicate entries are permitted, allowing for simple weighting without duplicating the structure files themselves.");
property.setLanguageKey("config." + Wizardry.MODID + ".obelisk_files");
property.setRequiresWorldRestart(true);
obeliskFiles = getResourceLocationList(property);
propOrder.add(property.getName());
property = config.get(WORLDGEN_CATEGORY, "shrineDimensions", new int[]{0, -1}, "List of dimension ids in which shrines will generate.");
property.setLanguageKey("config." + Wizardry.MODID + ".shrine_dimensions");
property.setRequiresWorldRestart(true);
shrineDimensions = property.getIntList();
propOrder.add(property.getName());
property = config.get(WORLDGEN_CATEGORY, "shrineRarity", 1000, "Rarity of shrines. 1 in this many chunks will contain a shrine, meaning higher numbers are rarer.", 20, 5000);
property.setLanguageKey("config." + Wizardry.MODID + ".shrine_rarity");
property.setRequiresWorldRestart(true);
Wizardry.proxy.setToNumberSliderEntry(property);
shrineRarity = property.getInt();
propOrder.add(property.getName());
property = config.get(WORLDGEN_CATEGORY, "shrineFiles", new String[]{Wizardry.MODID + ":shrine_0", Wizardry.MODID + ":shrine_1", Wizardry.MODID + ":shrine_2", Wizardry.MODID + ":shrine_3", Wizardry.MODID + ":shrine_4", Wizardry.MODID + ":shrine_5", Wizardry.MODID + ":shrine_6", Wizardry.MODID + ":shrine_7"},
"List of structure file locations for shrines. One of these files will be randomly selected each time a shrine is generated. File locations are of the format [mod id]:[filename], which refers to the file assets/[mod id]/structures/[filename].nbt. Duplicate entries are permitted, allowing for simple weighting without duplicating the structure files themselves.");
property.setLanguageKey("config." + Wizardry.MODID + ".shrine_files");
property.setRequiresWorldRestart(true);
shrineFiles = getResourceLocationList(property);
propOrder.add(property.getName());
property = config.get(WORLDGEN_CATEGORY, "oreDimensions", new int[]{0}, "List of dimension ids in which crystal ore will generate. Note that removing the overworld (id 0) from this list will make the mod VERY difficult to play!");
property.setLanguageKey("config." + Wizardry.MODID + ".ore_dimensions");
property.setRequiresWorldRestart(true);
@@ -437,10 +780,10 @@ public final class Settings {
flowerDimensions = property.getIntList();
propOrder.add(property.getName());
property = config.get(WORLDGEN_CATEGORY, "generateLoot", true, "Whether to inject wizardry loot (as specified in loot_tables/chests/dungeon_additions.json) into the loot tables for vanilla dungeon chests.");
property.setLanguageKey("config." + Wizardry.MODID + ".generate_loot");
property.setRequiresWorldRestart(true);
generateLoot = property.getBoolean();
property = config.get(WORLDGEN_CATEGORY, "lootInjectionLocations", DEFAULT_LOOT_INJECTION_LOCATIONS, "List of loot tables to inject wizardry loot (as specified in loot_tables/chests/dungeon_additions.json) into.");
property.setLanguageKey("config." + Wizardry.MODID + ".loot_injection_locations");
property.setRequiresMcRestart(true);
lootInjectionLocations = getResourceLocationList(property);
propOrder.add(property.getName());
config.setCategoryPropertyOrder(WORLDGEN_CATEGORY, propOrder);
@@ -448,23 +791,24 @@ public final class Settings {
private void setupClientConfig(){
// This trick is borrowed from forge; it sorts the config options into the order you want them.
List<String> propOrder = new ArrayList<String>();
Property property;
config.addCustomCategoryComment(CLIENT_CATEGORY, "Client-side settings that only affect the local minecraft game. If this file is on a dedicated server, these settings will have no effect; in multiplayer, each player obeys their own settings.");
property = config.get(CLIENT_CATEGORY, "enableShiftScrolling", true,
property = config.get(CLIENT_CATEGORY, "shiftScrolling", true,
"Whether you can switch between spells on a wand by scrolling with the mouse wheel while sneaking. Note that this will only affect you; other players connected to the same server obey their own settings.");
property.setLanguageKey("config." + Wizardry.MODID + ".enable_shift_scrolling");
property.setLanguageKey("config." + Wizardry.MODID + ".shift_scrolling");
property.setRequiresWorldRestart(false);
enableShiftScrolling = property.getBoolean();
Wizardry.proxy.setToNamedBooleanEntry(property);
shiftScrolling = property.getBoolean();
propOrder.add(property.getName());
property = config.get(CLIENT_CATEGORY, "reverseScrollDirection", false, "Whether to reverse the scroll direction used to switch between spells on a wand while sneaking.");
property = config.get(CLIENT_CATEGORY, "reverseScrollDirection", false, "The scroll direction used to switch between spells on a wand while sneaking.");
property.setLanguageKey("config." + Wizardry.MODID + ".reverse_scroll_direction");
property.setRequiresWorldRestart(false);
Wizardry.proxy.setToNamedBooleanEntry(property);
reverseScrollDirection = property.getBoolean();
propOrder.add(property.getName());
@@ -479,9 +823,28 @@ public final class Settings {
spellHUDSkin = property.getString();
propOrder.add(property.getName());
property = config.get(CLIENT_CATEGORY, "showSummonedCreatureNames", true, "Whether to show summoned creatures' names and owners above their heads.");
property.setLanguageKey("config." + Wizardry.MODID + ".show_summoned_creature_names");
showSummonedCreatureNames = property.getBoolean();
property = config.get(CLIENT_CATEGORY, "handbookProgression", true, "When set to true, sections of The Wizard's Handbook are unlocked when a player gains the advancement that triggers them, and are hidden otherwise. When set to false, the entire handbook is readable regardless of advancement progress.");
property.setLanguageKey("config." + Wizardry.MODID + ".handbook_progression");
Wizardry.proxy.setToNamedBooleanEntry(property);
handbookProgression = property.getBoolean();
propOrder.add(property.getName());
property = config.get(CLIENT_CATEGORY, "booksPauseGame", true, "Whether opening any of wizardry's books pauses the game in singleplayer. Has no effect on servers or LAN worlds.");
property.setLanguageKey("config." + Wizardry.MODID + ".books_pause_game");
Wizardry.proxy.setToNamedBooleanEntry(property);
booksPauseGame = property.getBoolean();
propOrder.add(property.getName());
property = config.get(CLIENT_CATEGORY, "summonedCreatureNames", true, "Whether to show summoned creatures' names and owners above their heads.");
property.setLanguageKey("config." + Wizardry.MODID + ".summoned_creature_names");
Wizardry.proxy.setToNamedBooleanEntry(property);
summonedCreatureNames = property.getBoolean();
propOrder.add(property.getName());
property = config.get(CLIENT_CATEGORY, "useShaders", true, "Whether to use custom shaders for certain spells. These use the vanilla shader system (like mob spectating shaders) and shouldn't have much of an effect on performance in most cases, but they may conflict with other shaders.");
property.setLanguageKey("config." + Wizardry.MODID + ".use_shaders");
Wizardry.proxy.setToNamedBooleanEntry(property);
useShaders = property.getBoolean();
propOrder.add(property.getName());
config.setCategoryPropertyOrder(CLIENT_CATEGORY, propOrder);
@@ -489,18 +852,15 @@ public final class Settings {
private void setupCommandsConfig(){
// This trick is borrowed from forge; it sorts the config options into the order you want them.
List<String> propOrder = new ArrayList<String>();
Property property;
config.addCustomCategoryComment(COMMANDS_CATEGORY, "Settings for the commands added by Wizardry. In multiplayer, the server/LAN host settings will apply.");
// This one isn't a slider either because people are likely to want exact values ("it must be at most 50.3" is a
// bit strange!).
property = config.get(COMMANDS_CATEGORY, "castCommandMultiplierLimit", 20.0,
"Upper limit for the multipliers passed into the /cast command. This is here to stop players from accidentally breaking a world/server. Large blast mutipliers can cause extreme lag - you have been warned!",
1, 255);
1, Integer.MAX_VALUE); // Sure, I mean you COULD set it to 2^31-1... what could possibly go wrong?
property.setLanguageKey("config." + Wizardry.MODID + ".cast_command_multiplier_limit");
maxSpellCommandMultiplier = property.getDouble();
propOrder.add(property.getName());
@@ -546,7 +906,7 @@ public final class Settings {
"Settings which allow entities to be made immune to certain types of magic. In multiplayer, the server/LAN host settings will apply.");
property = config.get(RESISTANCES_CATEGORY, "mobsImmuneToFire", new String[]{},
"List of names of entities that are immune to fire, in addition to the defaults. Add mod creatures to this list if you want them to be immune to fire magic and they aren't already. Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
"List of names of entities that are immune to fire, in addition to the defaults. Add mod creatures to this list if you want them to be immune to fire magic and they aren't already. SoundLoopSpellEntity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
property.setLanguageKey("config." + Wizardry.MODID + ".mobs_immune_to_fire");
property.setRequiresMcRestart(true);
// Wizardry.proxy.setToEntityNameEntry(property);
@@ -559,7 +919,7 @@ public final class Settings {
propOrder.add(property.getName());
property = config.get(RESISTANCES_CATEGORY, "mobsImmuneToIce", new String[]{},
"List of names of entities that are immune to ice, in addition to the defaults. Add mod creatures to this list if you want them to be immune to ice magic and they aren't already. Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
"List of names of entities that are immune to ice, in addition to the defaults. Add mod creatures to this list if you want them to be immune to ice magic and they aren't already. SoundLoopSpellEntity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
property.setLanguageKey("config." + Wizardry.MODID + ".mobs_immune_to_ice");
property.setRequiresMcRestart(true);
// Wizardry.proxy.setToEntityNameEntry(property);
@@ -572,7 +932,7 @@ public final class Settings {
propOrder.add(property.getName());
property = config.get(RESISTANCES_CATEGORY, "mobsImmuneToLightning", new String[]{},
"List of names of entities that are immune to lightning, in addition to the defaults. Add mod creatures to this list if you want them to be immune to lightning magic and they aren't already. Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
"List of names of entities that are immune to lightning, in addition to the defaults. Add mod creatures to this list if you want them to be immune to lightning magic and they aren't already. SoundLoopSpellEntity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
property.setLanguageKey("config." + Wizardry.MODID + ".mobs_immune_to_lightning");
property.setRequiresMcRestart(true);
// Wizardry.proxy.setToEntityNameEntry(property);
@@ -585,7 +945,7 @@ public final class Settings {
propOrder.add(property.getName());
property = config.get(RESISTANCES_CATEGORY, "mobsImmuneToWither", new String[]{},
"List of names of entities that are immune to wither effects, in addition to the defaults. Add mod creatures to this list if you want them to be immune to withering magic and they aren't already. Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
"List of names of entities that are immune to wither effects, in addition to the defaults. Add mod creatures to this list if you want them to be immune to withering magic and they aren't already. SoundLoopSpellEntity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
property.setLanguageKey("config." + Wizardry.MODID + ".mobs_immune_to_wither");
property.setRequiresMcRestart(true);
// Wizardry.proxy.setToEntityNameEntry(property);
@@ -598,7 +958,7 @@ public final class Settings {
propOrder.add(property.getName());
property = config.get(RESISTANCES_CATEGORY, "mobsImmuneToPoison", new String[]{},
"List of names of entities that are immune to poison, in addition to the defaults. Add mod creatures to this list if you want them to be immune to poison magic and they aren't already. Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
"List of names of entities that are immune to poison, in addition to the defaults. Add mod creatures to this list if you want them to be immune to poison magic and they aren't already. SoundLoopSpellEntity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. " + Wizardry.MODID + ":wizard).");
property.setLanguageKey("config." + Wizardry.MODID + ".mobs_immune_to_poison");
property.setRequiresMcRestart(true);
// Wizardry.proxy.setToEntityNameEntry(property);
@@ -613,4 +973,87 @@ public final class Settings {
config.setCategoryPropertyOrder(RESISTANCES_CATEGORY, propOrder);
}
private void setupCompatibilityConfig(){
List<String> propOrder = new ArrayList<String>();
Property property;
config.addCustomCategoryComment(COMPATIBILITY_CATEGORY, "Settings that affect how wizardry interacts with other mods. In multiplayer, the server/LAN host settings will apply.");
property = config.get(COMPATIBILITY_CATEGORY, "damageSourceBlacklist", new String[]{},
"List of damage source string identifiers to be ignored when re-applying damage. Case-sensitive. A message will be logged if wizardry detects a damage source that should be added to this list. Otherwise, don't change unless instructed to do so.");
property.setLanguageKey("config." + Wizardry.MODID + ".damage_source_blacklist");
property.setRequiresWorldRestart(true);
damageSourceBlacklist = property.getStringList();
propOrder.add(property.getName());
property = config.get(COMPATIBILITY_CATEGORY, "compatibilityWarnings", true,
"Whether to print compatibility warnings to the console. Set to false if excessive messages are being printed.");
property.setLanguageKey("config." + Wizardry.MODID + ".compatibility_warnings");
Wizardry.proxy.setToNamedBooleanEntry(property);
compatibilityWarnings = property.getBoolean();
propOrder.add(property.getName());
property = config.get(COMPATIBILITY_CATEGORY, "baublesIntegration", true,
"If Baubles is installed, controls whether Baubles integration features are enabled. If this is disabled, wizardry will always behave as if Baubles is not installed.");
property.setLanguageKey("config." + Wizardry.MODID + ".baubles_integration");
property.setRequiresMcRestart(true);
Wizardry.proxy.setToNamedBooleanEntry(property);
baublesIntegration = property.getBoolean();
propOrder.add(property.getName());
// property = config.get(COMPATIBILITY_CATEGORY, "jeiIntegration", true,
// "If JEI (Just Enough Items) is installed, controls whether JEI integration features are enabled. If this is disabled, wizardry will always behave as if JEI is not installed.");
// property.setLanguageKey("config." + Wizardry.MODID + ".jei_integration");
// property.setRequiresMcRestart(true);
// Wizardry.proxy.setToNamedBooleanEntry(property);
// jeiIntegration = property.getBoolean();
// propOrder.add(property.getName());
property = config.get(COMPATIBILITY_CATEGORY, "antiqueAtlasIntegration", true,
"If Antique Atlas is installed, controls whether Antique Atlas integration features are enabled. If this is disabled, wizardry will always behave as if Antique Atlas is not installed.");
property.setLanguageKey("config." + Wizardry.MODID + ".antique_atlas_integration");
property.setRequiresMcRestart(true);
Wizardry.proxy.setToNamedBooleanEntry(property);
antiqueAtlasIntegration = property.getBoolean();
propOrder.add(property.getName());
property = config.get(COMPATIBILITY_CATEGORY, "autoPlaceTowerMarkers", true,
"Controls whether wizardry automatically places antique atlas markers at the locations of wizard towers.");
property.setLanguageKey("config." + Wizardry.MODID + ".auto_place_tower_markers");
property.setRequiresMcRestart(true);
Wizardry.proxy.setToNamedBooleanEntry(property);
autoTowerMarkers = property.getBoolean();
propOrder.add(property.getName());
property = config.get(COMPATIBILITY_CATEGORY, "autoPlaceObeliskMarkers", true,
"Controls whether wizardry automatically places antique atlas markers at the locations of obelisks.");
property.setLanguageKey("config." + Wizardry.MODID + ".auto_place_obelisk_markers");
property.setRequiresMcRestart(true);
Wizardry.proxy.setToNamedBooleanEntry(property);
autoObeliskMarkers = property.getBoolean();
propOrder.add(property.getName());
property = config.get(COMPATIBILITY_CATEGORY, "autoPlaceShrineMarkers", true,
"Controls whether wizardry automatically places antique atlas markers at the locations of shrines.");
property.setLanguageKey("config." + Wizardry.MODID + ".auto_place_shrine_markers");
property.setRequiresMcRestart(true);
Wizardry.proxy.setToNamedBooleanEntry(property);
autoShrineMarkers = property.getBoolean();
propOrder.add(property.getName());
config.setCategoryPropertyOrder(COMPATIBILITY_CATEGORY, propOrder);
}
/** Retrieves a string list from the given property and converts it to an array of {@link ResourceLocation}s. */
public static ResourceLocation[] getResourceLocationList(Property property){
return toResourceLocations(property.getStringList());
}
/** Converts the given strings to an array of {@link ResourceLocation}s */
public static ResourceLocation[] toResourceLocations(String... strings){
return Arrays.stream(strings).map(s -> new ResourceLocation(s.toLowerCase(Locale.ROOT).trim())).toArray(ResourceLocation[]::new);
}
}
@@ -1,23 +1,22 @@
package electroblob.wizardry;
import org.apache.logging.log4j.Logger;
import electroblob.wizardry.command.CommandCastSpell;
import electroblob.wizardry.command.CommandDiscoverSpell;
import electroblob.wizardry.command.CommandSetAlly;
import electroblob.wizardry.command.CommandViewAllies;
import electroblob.wizardry.data.DispenserCastingData;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.integration.antiqueatlas.WizardryAntiqueAtlasIntegration;
import electroblob.wizardry.integration.baubles.WizardryBaublesIntegration;
import electroblob.wizardry.misc.Forfeit;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.WizardryAdvancementTriggers;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryRegistry;
import electroblob.wizardry.registry.WizardryTabs;
import electroblob.wizardry.registry.*;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.CustomSoundCategory;
import electroblob.wizardry.util.SpellProperties;
import electroblob.wizardry.worldgen.*;
import net.minecraft.item.Item;
import net.minecraft.nbt.NBTBase;
import net.minecraft.util.EnumFacing;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.Capability.IStorage;
import net.minecraftforge.common.capabilities.CapabilityManager;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.Mod.EventHandler;
@@ -30,12 +29,13 @@ import net.minecraftforge.fml.common.event.FMLServerStartingEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import net.minecraftforge.fml.common.registry.GameRegistry;
import org.apache.logging.log4j.Logger;
/**
* <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
* 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
* in a main mod class.
* @author Electroblob
@@ -50,9 +50,9 @@ public class Wizardry {
public static final String NAME = "Electroblob's Wizardry";
/**
* The version number for this version of wizardry. The following system is used for version numbers:
* <p>
* <p></p>
* <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
* features as Wizardry 2.1, but they are for different versions of Minecraft and have separate minor versioning.
* 1.x.x represents Minecraft 1.7.x versions, 2.x.x represents Minecraft 1.10.x versions, 3.x.x represents Minecraft
@@ -60,56 +60,28 @@ public class Wizardry {
*/
public static final String VERSION = "4.2.0";
// IDEA: Improve the algorithm that finds a place to summon creatures to take walls into account.
// IDEA: Replace all uses of Math.cos and Math.sin with MathHelper versions
// IDEA: Triggering of inbuilt Forge events in relevant places?
// IDEA: Abstract the vanilla particles behind the particle builder
/* Minor bugs that need fixing at some point:
* - Player skin hat layer shows through wizard hats - Wizard armour breaks rather than just running out of mana (I
* can't seem to replicate this bug, but I have had it happen to me before...)
* - Shift-clicking a stack of special upgrades when in the arcane workbench causes the whole stack to be
* transferred when it should be just one (this is a bug with vanilla as well - try putting a stack of bottles into
* a brewing stand). I have at least made it so only one gets used now, so it has no impact on the game.
* - When a spell is on cooldown, you can't break blocks when holding a wand. */
// TODO: So somehow I have managed to overlook the fact that health is actually a float. What this means is that
// I can make the healing spells use damage multipliers - hurrah!
// TODO: Switch from IInventory to IItemHandler (Or don't. It's only useful for automation really.)
// TODO: Have particles obey Minecraft's particle setting where appropriate
// (see https://github.com/RootsTeam/Embers/blob/master/src/main/java/teamroots/embers/particle/ParticleUtil.java)
// TODO: Go over all the worldgen code, use IWorldGenerator
// TODO: Implement a continuous sound system using MovingSoundEntity, allowing continuous spells to have a long sound
// loop as well as a start and end sound
// TODO: EntityMagicArrow needs attention
// TODO: Go over particle spawning on projectile impact and make sure hitvec is used wherever appropriate
// TODO: Replace spell IDs in packets with ResourceLocation strings
// TODO: Forcefield needs looking at, esp. with regards to projectiles and explosions
// TODO: TileEntityArcaneWorkbench needs looking at, esp. regarding inventory and markDirty
// TODO: Fireskin somehow lost its particles
// TODO: Convert settings over to the @Config system
// TODO: Go through listeners of LivingHurtEvent and decide whether they should change to LivingDamageEvent
// NOTE: Add melee upgrades to loot tables when they are added.
// TODO: See what can be done with the illager spell sounds (and go over sounds in general)
// TODO: Fix possession
// TODO: Fix charge
/** Static instance of the {@link Settings} object for Wizardry. */
public static final Settings settings = new Settings();
/** Static instance of the {@link Logger} object for Wizardry.
* <p>
* <p></p>
* 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>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. */
public static Logger logger;
// private static Pattern entityNamePattern;
// EventManager
WizardryWorldGenerator generator = new WizardryWorldGenerator();
// The instance of wizardry that Forge uses.
@Instance(Wizardry.MODID)
public static Wizardry instance;
@@ -123,40 +95,27 @@ public class Wizardry {
logger = event.getModLog();
proxy.registerResourceReloadListener();
proxy.registerResourceReloadListeners();
settings.initConfig(event);
// Yes - by the looks of it, having an interface is completely unnecessary in this case.
CapabilityManager.INSTANCE.register(WizardData.class, new IStorage<WizardData>(){
// These methods are only called by Capability.writeNBT() or Capability.readNBT(), which in turn are
// NEVER CALLED. Unless I'm missing some reflective invocation, that means this entire class serves only
// to allow capabilities to be saved and loaded manually. What that would be useful for I don't know.
// (If an API forces most users to write redundant code for no reason, it's not user friendly, is it?)
// ... well, that's my rant for today!
@Override
public NBTBase writeNBT(Capability<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();
// Capabilities
WizardData.register();
DispenserCastingData.register();
// Register things that don't have registries
WizardryBlocks.registerTileEntities();
WizardryLoot.register();
WizardryAdvancementTriggers.register();
Forfeit.register();
// Moved to preInit, because apparently it has to be here now.
// Client-side stuff (via proxies)
proxy.registerRenderers();
// It seems this also has to be here
proxy.registerKeyBindings();
WizardryBaublesIntegration.init();
WizardryAntiqueAtlasIntegration.init();
}
@EventHandler
@@ -164,26 +123,41 @@ public class Wizardry {
settings.initConfigExtras();
// Event Handlers
GameRegistry.registerWorldGenerator(generator, 0);
MinecraftForge.EVENT_BUS.register(instance);
// World generators
// Weight is a misnomer, it's actually the priority (where lower numbers get generated first)
// Literally nothing on typical 'weight' values here, there isn't even an upper limit
// Examples I've managed to find:
// - Tinker's construct slime islands use 25
GameRegistry.registerWorldGenerator(new WorldGenCrystalOre(), 0);
GameRegistry.registerWorldGenerator(new WorldGenCrystalFlower(), 50);
GameRegistry.registerWorldGenerator(new WorldGenWizardTower(), 20);
GameRegistry.registerWorldGenerator(new WorldGenObelisk(), 20);
GameRegistry.registerWorldGenerator(new WorldGenShrine(), 20);
// This is for the config change and missing mappings events
MinecraftForge.EVENT_BUS.register(instance); // Since there's already an instance we might as well use it
NetworkRegistry.INSTANCE.registerGuiHandler(this, new WizardryGuiHandler());
WizardryPacketHandler.initPackets();
// Post-registry extras
WizardryItems.populateWandMap();
WizardryItems.populateArmourMap();
WizardryItems.registerDispenseBehaviours();
Spell.registry.forEach(Spell::init);
SpellProperties.init();
// Client-side stuff (via proxies)
proxy.initGuiBits();
proxy.initParticleFactories();
proxy.registerParticles();
proxy.registerSoundEventListener();
WizardrySounds.SPELLS = CustomSoundCategory.add(Wizardry.MODID + ":spells");
}
@EventHandler
public void postInit(FMLPostInitializationEvent event){
// This needs to be here or it won't necessarily include all the mods' entities.
// TODO: Re-implement this when the Forge bug is fixed.
/* Doesn't seem to be doing anything... String entityNames = ""; for(Object name :
* EntityList.classToStringMapping.values()){ if(name instanceof String){ entityNames = entityNames + name +
* '|'; } } // Cuts off the last '|' entityNames = entityNames.substring(0, entityNames.length()-1);
* entityNamePattern = Pattern.compile(entityNames); */
proxy.initialiseLayers();
WizardryTabs.sort();
}
@EventHandler
@@ -205,23 +179,30 @@ public class Wizardry {
// 2.1 changed some item ids, so this fixes them for existing worlds
// Nobody updates minecraft versions when using mods, but I may as well leave this here just in case.
@SubscribeEvent
public static void onMissingMappingEvent(RegistryEvent.MissingMappings<Item> event){
public static void onMissingItemMappingEvent(RegistryEvent.MissingMappings<Item> event){
// Just get, not getAll, since the mod id didn't change!
for(RegistryEvent.MissingMappings.Mapping<Item> mapping : event.getAllMappings()){
if(mapping.key.getResourceDomain().equals(Wizardry.MODID)){
if(mapping.key.getNamespace().equals(Wizardry.MODID)){
Item replacement;
switch(mapping.key.getResourcePath()){
switch(mapping.key.getPath()){
case "wand_basic": replacement = WizardryItems.magic_wand; break;
case "wand_basic_fire": replacement = WizardryItems.basic_fire_wand; break;
case "wand_basic_ice": replacement = WizardryItems.basic_ice_wand; break;
case "wand_basic_lightning": replacement = WizardryItems.basic_lightning_wand; break;
case "wand_basic_necromancy": replacement = WizardryItems.basic_necromancy_wand; break;
case "wand_basic_earth": replacement = WizardryItems.basic_earth_wand; break;
case "wand_basic_sorcery": replacement = WizardryItems.basic_sorcery_wand; break;
case "wand_basic_healing": replacement = WizardryItems.basic_healing_wand; break;
case "wand_basic_fire": replacement = WizardryItems.novice_fire_wand; break;
case "wand_basic_ice": replacement = WizardryItems.novice_ice_wand; break;
case "wand_basic_lightning": replacement = WizardryItems.novice_lightning_wand; break;
case "wand_basic_necromancy": replacement = WizardryItems.novice_necromancy_wand; break;
case "wand_basic_earth": replacement = WizardryItems.novice_earth_wand; break;
case "wand_basic_sorcery": replacement = WizardryItems.novice_sorcery_wand; break;
case "wand_basic_healing": replacement = WizardryItems.novice_healing_wand; break;
case "basic_fire_wand": replacement = WizardryItems.novice_fire_wand; break;
case "basic_ice_wand": replacement = WizardryItems.novice_ice_wand; break;
case "basic_lightning_wand": replacement = WizardryItems.novice_lightning_wand; break;
case "basic_necromancy_wand": replacement = WizardryItems.novice_necromancy_wand; break;
case "basic_earth_wand": replacement = WizardryItems.novice_earth_wand; break;
case "basic_sorcery_wand": replacement = WizardryItems.novice_sorcery_wand; break;
case "basic_healing_wand": replacement = WizardryItems.novice_healing_wand; break;
case "wand_apprentice": replacement = WizardryItems.apprentice_wand; break;
case "wand_apprentice_fire": replacement = WizardryItems.apprentice_fire_wand; break;
case "wand_apprentice_ice": replacement = WizardryItems.apprentice_ice_wand; break;
@@ -264,4 +245,13 @@ public class Wizardry {
}
}
@SubscribeEvent
public static void onMissingSpellMappingEvent(RegistryEvent.MissingMappings<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;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.entity.EntityArc;
import electroblob.wizardry.entity.living.EntityEvilWizard;
import electroblob.wizardry.data.SpellEmitterData;
import electroblob.wizardry.data.SpellGlyphData;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.entity.living.ISpellCaster;
import electroblob.wizardry.entity.living.ISummonedCreature;
import electroblob.wizardry.event.DiscoverSpellEvent;
import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.integration.DamageSafetyChecker;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.item.ItemWizardArmour;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryAdvancementTriggers;
import electroblob.wizardry.registry.WizardryEnchantments;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.item.IManaStoringItem;
import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.packet.PacketSyncAdvancements;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.*;
import electroblob.wizardry.spell.FreezingWeapon;
import electroblob.wizardry.spell.ImbueWeapon;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.*;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WandHelper;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.advancements.Advancement;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.monster.IMob;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.entity.projectile.EntityArrow;
import net.minecraft.init.SoundEvents;
import net.minecraft.inventory.ContainerPlayer;
import net.minecraft.inventory.ContainerWorkbench;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemSword;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EnumHand;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import net.minecraft.world.storage.loot.LootEntry;
import net.minecraft.world.storage.loot.LootEntryTable;
import net.minecraft.world.storage.loot.LootPool;
import net.minecraft.world.storage.loot.RandomValueRange;
import net.minecraft.world.storage.loot.conditions.LootCondition;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.util.FakePlayer;
import net.minecraftforge.event.LootTableLoadEvent;
import net.minecraftforge.event.entity.living.LivingAttackEvent;
import net.minecraftforge.event.entity.living.LivingDeathEvent;
import net.minecraftforge.event.entity.living.LivingDropsEvent;
import net.minecraftforge.event.entity.PlaySoundAtEntityEvent;
import net.minecraftforge.event.entity.living.*;
import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent;
import net.minecraftforge.event.entity.living.LivingHurtEvent;
import net.minecraftforge.event.entity.player.AdvancementEvent;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.EventPriority;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerLoggedInEvent;
import java.util.ArrayList;
/**
* As of Wizardry 2.1, most of the code in this class has been relocated somewhere sensible, leaving only a few
* miscellaneous things that don't make much sense anywhere else, or that are better kept together. Previously, this was
* a gigantic class with about half of the entire mod's logic in it!
* General-purpose event handler for things that don't fit anywhere else or groups of related behaviours that are better
* kept together. As of Wizardry 2.1, most of the code in this class has been relocated somewhere sensible, leaving only
* a few miscellaneous things that don't make much sense anywhere else, or that are better kept together (previously,
* this was a gigantic class with about half of the entire mod's logic in it!)
*
* @author Electroblob
* @since Wizardry 1.0
*/
// The general rules for where event-based logic goes are:
// - If the logic relates only to vanilla things or is general (e.g. gameplay settings) it lives in here
// - If there is an obvious class for the thing relevant to the logic being performed, it goes in there for the sake
// of modularity/separation-of-concerns (e.g. armour cost reductions go in ItemWizardArmour)
// - If the thing has an instance but not a separate class (e.g. most custom potions), it goes in a related class
// where applicable (e.g. a spell class), or if not it goes in here
// - If several things share a significant amount of logic, to avoid duplicate code and potentially improve efficiency
// they should be kept together either in a class relevant to all of them (probably a common superclass) or in here
// - Client-side logic goes in WizardryClientEventHandler or another relevant client-side class
@Mod.EventBusSubscriber
public final class WizardryEventHandler {
// IDEA: Config option allowing users to specify loot locations
private static final String[] LOOT_INJECTION_LOCATIONS = {"minecraft:chests/simple_dungeon",
"minecraft:chests/abandoned_mineshaft", "minecraft:chests/desert_pyramid", "minecraft:chests/jungle_temple",
"minecraft:chests/stronghold_corridor", "minecraft:chests/stronghold_crossing",
"minecraft:chests/stronghold_library", "minecraft:chests/igloo_chest", "minecraft:chests/woodland_mansion",
"minecraft:chests/end_city_treasure"};
@SubscribeEvent
public static void onLootTableLoadEvent(LootTableLoadEvent event){
if(Wizardry.settings.generateLoot){
for(String location : LOOT_INJECTION_LOCATIONS){
if(event.getName().toString().matches(location)){
event.getTable().addPool(getAdditive(Wizardry.MODID + ":chests/dungeon_additions"));
}
}
}
}
private static LootPool getAdditive(String entryName){
return new LootPool(new LootEntry[]{getAdditiveEntry(entryName, 1)}, new LootCondition[0],
new RandomValueRange(1), new RandomValueRange(0, 1), Wizardry.MODID + "_additive_pool");
}
private static LootEntryTable getAdditiveEntry(String name, int weight){
return new LootEntryTable(new ResourceLocation(name), weight, 0, new LootCondition[0],
Wizardry.MODID + "_additive_entry");
}
private WizardryEventHandler(){} // No instances!
@SubscribeEvent
public static void onPlayerLoggedInEvent(PlayerLoggedInEvent event){
// When a player logs in, they are sent the glyph data and the server's settings.
// When a player logs in, they are sent the glyph data, server settings and spell properties.
if(event.player instanceof EntityPlayerMP){
SpellGlyphData.get(event.player.world).sync((EntityPlayerMP)event.player);
SpellEmitterData.get(event.player.world).sync((EntityPlayerMP)event.player);
Wizardry.settings.sync((EntityPlayerMP)event.player);
syncAdvancements((EntityPlayerMP)event.player, false);
}
Spell.syncProperties(event.player);
}
@SubscribeEvent(priority = EventPriority.HIGH)
public static void onPlaySoundAtEntityEvent(PlaySoundAtEntityEvent event){
// Muffle (there's no spell class for it so it's here instead)
if(event.getEntity() instanceof EntityLivingBase
&& ((EntityLivingBase)event.getEntity()).isPotionActive(WizardryPotions.muffle)){
event.setCanceled(true);
}
}
@SubscribeEvent
public static void onAdvancementEvent(AdvancementEvent event){
// Forge has no hook for revoked advancements :(
// Guess we'll just have to make do
// Also, this seems to get fired on player login, so to prevent the toasts from appearing every login the
// only way I can see to do it is by testing the player has been around long enough.
if(event.getEntityPlayer() instanceof EntityPlayerMP && event.getEntityPlayer().ticksExisted > 0){
syncAdvancements((EntityPlayerMP)event.getEntityPlayer(), true);
}
}
private static void syncAdvancements(EntityPlayerMP player, boolean showToasts){
Wizardry.logger.info("Synchronising advancements for " + player.getName());
ArrayList<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){
boolean enabled = true;
switch(event.getSource()){
case WAND: enabled = event.getSpell().isEnabled(SpellProperties.Context.WANDS); break;
case SCROLL: enabled = event.getSpell().isEnabled(SpellProperties.Context.SCROLL); break;
case COMMAND: enabled = event.getSpell().isEnabled(SpellProperties.Context.COMMANDS); break;
case NPC: enabled = event.getSpell().isEnabled(SpellProperties.Context.NPCS); break;
case DISPENSER: enabled = event.getSpell().isEnabled(SpellProperties.Context.DISPENSERS); break;
case OTHER: enabled = event.getSpell().isEnabled(); break; // Any enabled context will do for this one
}
// If a spell is disabled in the config, it will not work.
if(!event.getSpell().isEnabled()){
if(!event.getEntityLiving().world.isRemote) event.getEntity().sendMessage(
if(!enabled){
if(event.getCaster() != null && !event.getCaster().world.isRemote) event.getCaster().sendMessage(
new TextComponentTranslation("spell.disabled", event.getSpell().getNameForTranslationFormatted()));
event.setCanceled(true);
}
@@ -123,32 +140,36 @@ public final class WizardryEventHandler {
@SubscribeEvent
public static void onSpellCastPostEvent(SpellCastEvent.Post event){
if(event.getCaster() instanceof EntityPlayer){
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)
if(event.getEntity() instanceof EntityPlayer){
EntityPlayer player = (EntityPlayer)event.getEntity();
WizardData data = WizardData.get(player);
if(data != null){
// Data is updated on both sides (This line was added client-side to fix a bug back in 1.1.3, so now
// it's in common code, which is nice!)
// Short-circuiting AND means that discoverSpell is only called if the event isn't cancelled.
if(!MinecraftForge.EVENT_BUS
.post(new DiscoverSpellEvent(player, event.getSpell(), DiscoverSpellEvent.Source.CASTING))
if(!MinecraftForge.EVENT_BUS.post(new DiscoverSpellEvent(player, event.getSpell(), DiscoverSpellEvent.Source.CASTING))
&& data.discoverSpell(event.getSpell())){
// If the spell wasn't already discovered, other stuff happens:
if(event.getSource() == SpellCastEvent.Source.COMMAND){
// If the spell didn't send a packet itself, the extended player needs to be synced so the
// spell discovery updates on the client.
if(!event.getSpell().doesSpellRequirePacket()) data.sync();
if(!event.getSpell().requiresPacket()) data.sync();
}else if(!event.getEntity().world.isRemote && !player.capabilities.isCreativeMode
}else if(!event.getCaster().world.isRemote && !player.isCreative()
&& Wizardry.settings.discoveryMode){
// Sound and text only happen server-side, in survival, with discovery mode on, and only when
// the spell wasn't cast using commands.
WizardryUtilities.playSoundAtPlayer(player, SoundEvents.ENTITY_PLAYER_LEVELUP, 1.25f, 1);
WizardryUtilities.playSoundAtPlayer(player, WizardrySounds.MISC_DISCOVER_SPELL, 1.25f, 1);
player.sendMessage(new TextComponentTranslation("spell.discover",
event.getSpell().getNameForTranslationFormatted()));
}
@@ -157,33 +178,59 @@ public final class WizardryEventHandler {
}
}
@SubscribeEvent(priority = EventPriority.LOW)
public static void onDiscoverSpellEvent(DiscoverSpellEvent event){
if(event.getEntityPlayer() instanceof EntityPlayerMP){
WizardryAdvancementTriggers.discover_spell.trigger((EntityPlayerMP)event.getEntityPlayer(), event.getSpell(), event.getSource());
}
}
@SubscribeEvent
public static void onLivingSetAttackTargetEvent(LivingSetAttackTargetEvent event){
if(event.getTarget() != null && event.getEntityLiving() instanceof EntityLiving
&& event.getTarget().isPotionActive(WizardryPotions.muffle)){
Vec3d vec = event.getTarget().getPositionEyes(1).subtract(event.getEntity().getPositionEyes(1));
// Find the angle between the direction the mob is looking and the direction the player is in
// Angle between a and b = acos((a.b) / (|a|*|b|))
double angle = Math.acos(vec.dotProduct(event.getEntity().getLookVec()) / vec.length());
System.out.println(angle);
// If the player is not within the 144-degree arc in front of the mob, it won't detect them
if(angle > 0.4 * Math.PI){
((EntityLiving)event.getEntityLiving()).setAttackTarget(null);
}
}
}
/* There is a subtle but important difference between LivingAttackEvent and LivingHurtEvent - LivingAttackEvent
* fires immediately when attackEntityFrom is called, whereas LivingHurtEvent only fires if the attack actually
* succeeded, i.e. if the entity in question takes damage (though the event is fired before that so you can cancel
* the damage). Things are processed in the following order: * LivingAttackEvent * - Invulnerability -
* Already-dead-ness - Fire resistance - Helmets vs. falling things - Hurt resistant time - Invulnerability (again)
* * LivingHurtEvent * - Armour - Potions - Health is finally changed Of course, there are no guarantees that other
* the damage). Things are processed in the following order:
*
* * LivingAttackEvent *
* - Invulnerability
* - Already-dead-ness
* - Fire resistance
* - Helmets vs. falling things
* - Hurt resistant time
* - Invulnerability (again)
* * LivingHurtEvent *
* - Armour
* - Potions
* * LivingDamageEvent *
* - Health is finally changed
*
* Of course, there are no guarantees that other
* mods hooking into these two events will be called before or after yours, but you can have some degree of control
* by choosing which event to use. EDIT: Actually, there are. Firstly, you can set a priority in the @SubscribeEvent
* annotation which defines how early (higher priority) or late (lower priority) the method is called. Methods with
* the same priority are sorted alphabetically by mod id (so it's safe to assume wizardry would be fairly late on!).
* I wonder if there are any conventions for what sort of things take what priority...? */
@SubscribeEvent
@SubscribeEvent(priority = EventPriority.LOW) // Low priority in case the event gets cancelled at default priority
public static void onLivingAttackEvent(LivingAttackEvent event){
// Prevents any damage to allies from magic if friendly fire is disabled
if(!Wizardry.settings.friendlyFire && event.getSource() != null
&& event.getSource().getTrueSource() instanceof EntityPlayer && event.getEntity() instanceof EntityPlayer
&& event.getSource() instanceof IElementalDamage){
if(WizardryUtilities.isPlayerAlly((EntityPlayer)event.getSource().getTrueSource(),
(EntityPlayer)event.getEntity())){
event.setCanceled(true);
// This needs to be here, since if the event is cancelled nothing else needs to happen.
return;
}
}
// Retaliatory effects
// These are better off here because the revenge effects are pretty similar, and I'd rather keep the (lengthy)
// if statement in one place.
@@ -194,38 +241,43 @@ public final class WizardryEventHandler {
EntityLivingBase attacker = (EntityLivingBase)event.getSource().getTrueSource();
World world = event.getEntityLiving().world;
if(attacker.getDistance(event.getEntityLiving()) < 10){
// Fireskin
if(event.getEntityLiving().isPotionActive(WizardryPotions.fireskin)
&& !MagicDamage.isEntityImmune(DamageType.FIRE, event.getEntityLiving()))
attacker.setFire(5);
attacker.setFire(Spells.fire_breath.getProperty(Spell.BURN_DURATION).intValue());
// Ice Shroud
if(event.getEntityLiving().isPotionActive(WizardryPotions.ice_shroud)
&& !MagicDamage.isEntityImmune(DamageType.FROST, event.getEntityLiving())
&& !(event.getEntityLiving() instanceof FakePlayer))
attacker.addPotionEffect(new PotionEffect(WizardryPotions.frost, 100, 0));
&& !(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()));
// Static Aura
if(event.getEntityLiving().isPotionActive(WizardryPotions.static_aura)){
if(world.isRemote){
ParticleBuilder.create(Type.LIGHTNING).entity(event.getEntity()).pos(0, event.getEntity().height/2, 0)
ParticleBuilder.create(Type.LIGHTNING).entity(event.getEntity()).pos(0, event.getEntity().height / 2, 0)
.target(attacker).spawn(world);
ParticleBuilder.spawnShockParticles(world, attacker.posX,
attacker.getEntityBoundingBox().minY + attacker.height, attacker.posZ);
attacker.getEntityBoundingBox().minY + attacker.height / 2, attacker.posZ);
}
DamageSafetyChecker.attackEntitySafely(attacker, MagicDamage.causeDirectMagicDamage(event.getEntityLiving(),
DamageType.SHOCK, true), 4.0f, event.getSource().getDamageType());
DamageType.SHOCK, true), Spells.static_aura.getProperty(Spell.DAMAGE).floatValue(), event.getSource().getDamageType());
attacker.playSound(WizardrySounds.SPELL_STATIC_AURA_RETALIATE, 1.0F, world.rand.nextFloat() * 0.4F + 1.5F);
}
}
}
}
@SubscribeEvent
@SubscribeEvent(priority = EventPriority.LOW) // Again, we don't want these effects if the event is cancelled
public static void onLivingHurtEvent(LivingHurtEvent event){
// Flaming and freezing swords
@@ -234,7 +286,7 @@ public final class WizardryEventHandler {
EntityLivingBase attacker = (EntityLivingBase)event.getSource().getTrueSource();
// Players can only ever attack with their main hand, so this is the right method to use here.
if(!attacker.getHeldItemMainhand().isEmpty() && attacker.getHeldItemMainhand().getItem() instanceof ItemSword){
if(!attacker.getHeldItemMainhand().isEmpty() && ImbueWeapon.isSword(attacker.getHeldItemMainhand().getItem())){
int level = EnchantmentHelper.getEnchantmentLevel(WizardryEnchantments.flaming_weapon,
attacker.getHeldItemMainhand());
@@ -275,30 +327,13 @@ public final class WizardryEventHandler {
@SubscribeEvent
public static void onLivingUpdateEvent(LivingUpdateEvent event){
if(event.getEntityLiving() instanceof EntityPlayer){
EntityPlayer player = (EntityPlayer)event.getEntityLiving();
if(player.world.isRemote) hackilyFixContinuousSpellCasting(player);
// Mana flask crafting
if(player.openContainer instanceof ContainerWorkbench){
IInventory craftMatrix = ((ContainerWorkbench)player.openContainer).craftMatrix;
ItemStack output = ((ContainerWorkbench)player.openContainer).craftResult.getStackInSlot(0);
processManaFlaskCrafting(craftMatrix, output);
}else if(player.openContainer instanceof ContainerPlayer){
IInventory craftMatrix = ((ContainerPlayer)player.openContainer).craftMatrix;
ItemStack output = ((ContainerPlayer)player.openContainer).craftResult.getStackInSlot(0);
// Unfortunately I have no choice but to call this method every tick when the player isn't using another
// inventory, since the only thing tracking whether the player is looking at their inventory is the GUI
// itself, which is client-side only.
processManaFlaskCrafting(craftMatrix, output);
}
}
// Experimental animation feature
// if(event.getEntityLiving().isHandActive() && event.getEntityLiving().getActiveItemStack().getItemUseAction() == WizardryUtilities.POINT){
// event.getEntityLiving().isSwingInProgress = true;
// event.getEntityLiving().swingProgress = 1f;
// event.getEntityLiving().prevSwingProgress = 1;
// event.getEntityLiving().swingingHand = event.getEntityLiving().getActiveHand();
// }
if(event.getEntityLiving().world.isRemote){
@@ -309,13 +344,14 @@ public final class WizardryEventHandler {
Spell spell = ((ISpellCaster)event.getEntity()).getContinuousSpell();
SpellModifiers modifiers = ((ISpellCaster)event.getEntity()).getModifiers();
if(spell != null && spell != Spells.none){
if(spell != null && spell != Spells.none){ // IntelliJ is wrong, do NOT remove the null check!
if(!MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Tick(event.getEntityLiving(), spell, modifiers,
SpellCastEvent.Source.NPC, 0))){
if(!MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Tick(SpellCastEvent.Source.NPC, spell, event.getEntityLiving(),
modifiers, 0))){
spell.cast(event.getEntity().world, (EntityLiving)event.getEntity(), EnumHand.MAIN_HAND, 0,
// TODO: This implementation of modifiers relies on them being accessible client-side.
// Right now that doesn't matter because NPCs don't use modifiers, but they might in future
((EntityLiving)event.getEntity()).getAttackTarget(), modifiers);
}
}
@@ -323,7 +359,7 @@ public final class WizardryEventHandler {
}
}
@SubscribeEvent
@SubscribeEvent(priority = EventPriority.LOWEST) // No siphoning if the event is cancelled, that could be exploited...
public static void onLivingDeathEvent(LivingDeathEvent event){
if(event.getSource().getTrueSource() instanceof EntityPlayer){
@@ -332,108 +368,83 @@ public final class WizardryEventHandler {
for(ItemStack stack : WizardryUtilities.getPrioritisedHotbarAndOffhand(player)){
if(stack.getItem() instanceof ItemWand && stack.isItemDamaged()
if(stack.getItem() instanceof IManaStoringItem && !((IManaStoringItem)stack.getItem()).isManaFull(stack)
&& WandHelper.getUpgradeLevel(stack, WizardryItems.siphon_upgrade) > 0){
int damage = stack.getItemDamage()
- Constants.SIPHON_MANA_PER_LEVEL
int mana = Constants.SIPHON_MANA_PER_LEVEL
* WandHelper.getUpgradeLevel(stack, WizardryItems.siphon_upgrade)
- player.world.rand.nextInt(Constants.SIPHON_MANA_PER_LEVEL);
if(damage < 0) damage = 0;
stack.setItemDamage(damage);
break;
}
}
+ player.world.rand.nextInt(Constants.SIPHON_MANA_PER_LEVEL);
if(event.getEntityLiving() == player && event.getSource() instanceof IElementalDamage){
WizardryAdvancementTriggers.self_destruct.triggerFor(player);
if(ItemArtefact.isArtefactActive(player, WizardryItems.ring_siphoning)) mana *= 1.3f;
((IManaStoringItem)stack.getItem()).rechargeMana(stack, mana);
break; // Only recharge one item per kill
}
}
}
}
@SubscribeEvent
public static void onLivingDropsEvent(LivingDropsEvent event){
// TODO: Really, this should be in a loot table (mob_additions), however I can't seem to find a way of
// automatically adding it to all subclasses of IMob.
// Evil wizards drop spell books themselves
if(event.getEntityLiving() instanceof IMob && !(event.getEntityLiving() instanceof EntityEvilWizard)
// TODO: Backport when you backport the new summoned creature system.
&& !(event.getEntityLiving() instanceof ISummonedCreature)
&& event.getSource().getTrueSource() instanceof EntityPlayer && Wizardry.settings.spellBookDropChance > 0){
// 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;
// This does exactly what the entity drop method does, but with a different random number so that the
// spell book doesn't always drop with other rare drops.
int rareDropNumber = event.getEntity().world.rand.nextInt(200) - event.getLootingLevel();
if(rareDropNumber < Wizardry.settings.spellBookDropChance){
// Drops a spell book
int id = WizardryUtilities.getStandardWeightedRandomSpellId(event.getEntity().world.rand);
private static final double LIVING_ENTITY_TERMINAL_VELOCITY = 3.92; // From Minecraft Wiki!
event.getDrops()
.add(new EntityItem(event.getEntityLiving().world, event.getEntityLiving().posX,
event.getEntityLiving().posY, event.getEntityLiving().posZ,
new ItemStack(WizardryItems.spell_book, 1, id)));
}
}
}
private static final double LOG_LIVING_ENTITY_DRAG = Math.log(LIVING_ENTITY_DRAG);
private static final double FALL_TICKS_ERROR_CORRECTION = 0.500841776608447;
// Private helper methods
// ================================================================================================================
@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...
/**
* Detects inconsistencies between player.getActiveItemStack and the actual itemstack and forces them to be equal.
* Fixes issue #25.
*
* @param player
*/
private static void hackilyFixContinuousSpellCasting(EntityPlayer player){
if(player.isHandActive() && player.getHeldItem(player.getActiveHand()).getItem() instanceof ItemWand
&& WandHelper.getCurrentSpell(player.getHeldItem(player.getActiveHand())).isContinuous){
if(player.getActiveItemStack() != player.getHeldItem(player.getActiveHand())){
player.setHeldItem(player.getActiveHand(), player.getActiveItemStack());
}
}
}
private static void processManaFlaskCrafting(IInventory craftMatrix, ItemStack output){
// Charges wand using mana flask. It is here rather than in the crafting handler so the result displays
// the proper damage before it is actually crafted.
boolean flag = false;
ItemStack wand = ItemStack.EMPTY;
ItemStack armour = ItemStack.EMPTY;
for(int i = 0; i < craftMatrix.getSizeInventory(); i++){
ItemStack itemstack = craftMatrix.getStackInSlot(i);
if(itemstack.getItem() == WizardryItems.mana_flask){
flag = true;
}
if(itemstack.getItem() instanceof ItemWand){
wand = itemstack;
}
if(itemstack.getItem() instanceof ItemWizardArmour){
armour = itemstack;
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(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);
}
}
// At terminal velocity, there's no way of finding fall distance from velocity, and the entity is probably dead anyway!
if(v > -3.9){
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);
// Just to make the code more readable, java will replace them all with numbers anyway
double g = LIVING_ENTITY_GRAVITY;
double f = LIVING_ENTITY_DRAG;
double lnf = LOG_LIVING_ENTITY_DRAG;
double tv = LIVING_ENTITY_TERMINAL_VELOCITY;
// Work backwards from y velocity to get fall time
// logs are probably slow but it's not like this gets calculated every tick, and we only need one log
double t = Math.log(((-v - tv) * lnf) / g) / lnf; // Log the number over log the base
// Because time is in discrete ticks, the above equation for t results in a constant error of
// +0.500841776608447, so I guess we can just subtract it... if it works it works I guess!
t -= FALL_TICKS_ERROR_CORRECTION; // Don't cast to int or perform any rounding
// Now work forwards from t to find the effective fall distance, i.e. the distance the entity would
// have to freefall to reach the same velocity
// Don't ask me where the 196 is from, again, it just works!
double y = (g * Math.pow(f, t)) / (lnf*lnf) + tv * (t) - 196;
// DEBUG
// if(event.getEntity() instanceof EntityPlayer){
// Wizardry.logger.info("Replaced fall distance {} with effective distance {} based on entity velocity", event.getDistance(), y);
// }
event.setDistance((float)y);
}
}
}
@@ -1,12 +1,12 @@
package electroblob.wizardry;
import java.util.Set;
import electroblob.wizardry.client.gui.GuiConfigWizardry;
import electroblob.wizardry.client.gui.config.GuiConfigWizardry;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import net.minecraftforge.fml.client.IModGuiFactory;
import java.util.Set;
public class WizardryGuiFactory implements IModGuiFactory {
@Override
@@ -1,6 +1,5 @@
package electroblob.wizardry;
import electroblob.wizardry.client.gui.handbook.GuiWizardHandbook;
import electroblob.wizardry.item.ItemSpellBook;
import electroblob.wizardry.item.ItemWizardHandbook;
import electroblob.wizardry.spell.Spell;
@@ -46,12 +45,12 @@ public class WizardryGuiHandler implements IGuiHandler {
}
}else if(id == WIZARD_HANDBOOK && (player.getHeldItemMainhand().getItem() instanceof ItemWizardHandbook
|| player.getHeldItemOffhand().getItem() instanceof ItemWizardHandbook)){
return new GuiWizardHandbook();
return new electroblob.wizardry.client.gui.handbook.GuiWizardHandbook();
}else if(id == SPELL_BOOK){
if(player.getHeldItemMainhand().getItem() instanceof ItemSpellBook){
return new electroblob.wizardry.client.gui.GuiSpellBook(Spell.get(player.getHeldItemMainhand().getItemDamage()));
return new electroblob.wizardry.client.gui.GuiSpellBook(Spell.byMetadata(player.getHeldItemMainhand().getItemDamage()));
}else if(player.getHeldItemOffhand().getItem() instanceof ItemSpellBook){
return new electroblob.wizardry.client.gui.GuiSpellBook(Spell.get(player.getHeldItemOffhand().getItemDamage()));
return new electroblob.wizardry.client.gui.GuiSpellBook(Spell.byMetadata(player.getHeldItemOffhand().getItemDamage()));
}
}else if(id == PORTABLE_CRAFTING){
return new electroblob.wizardry.client.gui.GuiPortableCrafting(player.inventory, world, new BlockPos(x, y, z));
@@ -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.SetMultimap;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonObject;
import electroblob.wizardry.Wizardry;
import net.minecraft.advancements.ICriterionInstance;
import net.minecraft.advancements.ICriterionTrigger;
@@ -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 net.minecraft.block.BlockContainer;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.BlockFaceShape;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.InventoryHelper;
@@ -51,6 +52,16 @@ public class BlockArcaneWorkbench extends BlockContainer {
return false;
}
@Override
public boolean isFullCube(IBlockState state){
return false;
}
@Override
public BlockFaceShape getBlockFaceShape(IBlockAccess world, IBlockState state, BlockPos pos, EnumFacing face){
return face == EnumFacing.DOWN ? BlockFaceShape.SOLID : BlockFaceShape.UNDEFINED;
}
@Override
public boolean onBlockActivated(World world, BlockPos pos, IBlockState block, EntityPlayer player, EnumHand hand,
EnumFacing side, float hitX, float hitY, float hitZ){
@@ -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;
import java.util.Random;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.util.ParticleBuilder;
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.eventhandler.SubscribeEvent;
import java.util.Random;
// Extending BlockBush allows me to remove nearly everything from this class.
@Mod.EventBusSubscriber
public class BlockCrystalFlower extends BlockBush {
@@ -1,7 +1,5 @@
package electroblob.wizardry.block;
import java.util.Random;
import electroblob.wizardry.registry.WizardryItems;
import net.minecraft.block.Block;
import net.minecraft.block.SoundType;
@@ -13,6 +11,8 @@ import net.minecraft.util.math.MathHelper;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
import java.util.Random;
public class BlockCrystalOre extends Block {
public BlockCrystalOre(Material material){
@@ -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;
import electroblob.wizardry.item.ISpellCastingItem;
import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.tileentity.TileEntityMagicLight;
import net.minecraft.block.BlockContainer;
import net.minecraft.block.Block;
import net.minecraft.block.ITileEntityProvider;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumBlockRenderType;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
public class BlockMagicLight extends BlockContainer {
public class BlockMagicLight extends Block implements ITileEntityProvider {
private static final AxisAlignedBB AABB = new AxisAlignedBB(0, 0, 0, 0, 0, 0);
//private static final AxisAlignedBB AABB = new AxisAlignedBB(0, 0, 0, 0, 0, 0);
public BlockMagicLight(Material par2Material){
super(par2Material);
public BlockMagicLight(Material material){
super(material);
this.setLightLevel(1.0f);
this.setBlockUnbreakable();
}
@Override
@@ -28,13 +36,38 @@ public class BlockMagicLight extends BlockContainer {
}
@Override
public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos){
return AABB;
public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ){
// Let the player dispel any lights if they have the lantern charm, not just the permanent ones because that would be annoying!
if(player.getHeldItem(hand).getItem() instanceof ISpellCastingItem && ItemArtefact.isArtefactActive(player, WizardryItems.charm_light)){
world.setBlockToAir(pos);
return true;
}else{
return super.onBlockActivated(world, pos, state, player, hand, facing, hitX, hitY, hitZ);
}
}
// @Override
// public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos){
// return AABB;
// }
@Override
public boolean isCollidable(){
return false;
// This method has nothing to do with entity movement, it's just for raytracing
return true;
}
@Override
public boolean addDestroyEffects(World world, BlockPos pos, net.minecraft.client.particle.ParticleManager manager){
if(world.getBlockState(pos).getBlock() == this) return true; // No break particles!
else return super.addDestroyEffects(world, pos, manager);
}
@Override
public boolean hasTileEntity(IBlockState state){
return true;
}
@Override
@@ -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;
import java.util.Random;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.tileentity.TileEntityPlayerSave;
import electroblob.wizardry.util.AllyDesignationSystem;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.block.Block;
import net.minecraft.block.BlockContainer;
import net.minecraft.block.ITileEntityProvider;
import net.minecraft.block.SoundType;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
@@ -25,8 +25,9 @@ import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
// TODO: Apparently you shouldn't extend BlockContainer. I feel like BlockArcaneWorkbench should, but what about the rest?
public class BlockSnare extends BlockContainer {
import java.util.Random;
public class BlockSnare extends Block implements ITileEntityProvider {
private static final AxisAlignedBB AABB = new AxisAlignedBB(0.0f, 0.0f, 0.0f, 1.0f, 0.0625f, 1.0f);
@@ -58,10 +59,14 @@ public class BlockSnare extends BlockContainer {
TileEntityPlayerSave tileentity = (TileEntityPlayerSave)world.getTileEntity(pos);
if(WizardryUtilities.isValidTarget(tileentity.getCaster(), entity)){
((EntityLivingBase)entity).attackEntityFrom(
MagicDamage.causeDirectMagicDamage(tileentity.getCaster(), DamageType.MAGIC), 6);
((EntityLivingBase)entity).addPotionEffect(new PotionEffect(MobEffects.SLOWNESS, 100, 2));
if(AllyDesignationSystem.isValidTarget(tileentity.getCaster(), entity)){
entity.attackEntityFrom(MagicDamage.causeDirectMagicDamage(tileentity.getCaster(), DamageType.MAGIC),
Spells.snare.getProperty(Spell.DAMAGE).floatValue());
((EntityLivingBase)entity).addPotionEffect(new PotionEffect(MobEffects.SLOWNESS,
Spells.snare.getProperty(Spell.EFFECT_DURATION).intValue(),
Spells.snare.getProperty(Spell.EFFECT_STRENGTH).intValue()));
world.destroyBlock(pos, false);
}
@@ -1,13 +1,11 @@
package electroblob.wizardry.block;
import java.util.Random;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.tileentity.TileEntityTimer;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import net.minecraft.block.Block;
import net.minecraft.block.BlockContainer;
import net.minecraft.block.ITileEntityProvider;
import net.minecraft.block.SoundType;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
@@ -24,10 +22,10 @@ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
// For future reference - extend BlockContainer whenever possible because it has methods for removing tile entities on
// block break.
import java.util.Random;
@Mod.EventBusSubscriber
public class BlockSpectral extends BlockContainer {
public class BlockSpectral extends Block implements ITileEntityProvider {
public BlockSpectral(Material material){
super(material);
@@ -76,6 +74,11 @@ public class BlockSpectral extends BlockContainer {
return 15;
}
@Override
public boolean hasTileEntity(IBlockState state){
return true;
}
@Override
public TileEntity createNewTileEntity(World world, int metadata){
return new TileEntityTimer(1200);
@@ -1,11 +1,9 @@
package electroblob.wizardry.block;
import java.util.Random;
import electroblob.wizardry.tileentity.TileEntityStatue;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.block.Block;
import net.minecraft.block.BlockContainer;
import net.minecraft.block.ITileEntityProvider;
import net.minecraft.block.SoundType;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
@@ -21,12 +19,16 @@ import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
public class BlockStatue extends BlockContainer {
import java.util.Random;
public class BlockStatue extends Block implements ITileEntityProvider {
private boolean isIce;
/** The NBT tag name for storing the petrified flag (used for rendering) in the target's tag compound. */
public static final String 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){
super(material);
@@ -110,6 +112,11 @@ public class BlockStatue extends BlockContainer {
return this.isIce ? EnumBlockRenderType.MODEL : EnumBlockRenderType.ENTITYBLOCK_ANIMATED;
}
@Override
public boolean hasTileEntity(IBlockState state){
return true;
}
@Override
public TileEntity createNewTileEntity(World world, int metadata){
return new TileEntityStatue(this.isIce);
@@ -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.
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;
world.spawnEntity(tileentity.creature);
}
@@ -198,7 +205,7 @@ public class BlockStatue extends BlockContainer {
((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();
return true;
}
@@ -217,7 +224,7 @@ public class BlockStatue extends BlockContainer {
((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();
return true;
}
@@ -242,7 +249,7 @@ public class BlockStatue extends BlockContainer {
((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();
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;
import java.util.Random;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.item.ISpellCastingItem;
import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.spell.Transportation;
import electroblob.wizardry.util.Location;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
@@ -20,6 +24,10 @@ import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class BlockTransportationStone extends Block {
private static final AxisAlignedBB AABB = new AxisAlignedBB(0.0625f * 5, 0, 0.0625f * 5, 0.0625f * 11, 0.0625f * 6,
@@ -103,7 +111,7 @@ public class BlockTransportationStone extends Block {
ItemStack stack = player.getHeldItem(hand);
if(stack.getItem() instanceof ItemWand){
if(stack.getItem() instanceof ISpellCastingItem){
if(WizardData.get(player) != null){
WizardData data = WizardData.get(player);
@@ -112,17 +120,59 @@ public class BlockTransportationStone extends Block {
for(int z = -1; z <= 1; z++){
BlockPos pos1 = pos.add(x, 0, z);
if(testForCircle(world, pos1)){
data.setStoneCircleLocation(pos1, world.provider.getDimension());
if(!world.isRemote) player.sendMessage(
new TextComponentTranslation("tile." + Wizardry.MODID + ":transportation_stone.confirm",
Spells.transportation.getNameForTranslationFormatted()));
Location here = new Location(pos1, player.dimension);
List<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;
}
}
}
if(!world.isRemote)
player.sendMessage(new TextComponentTranslation("tile." + Wizardry.MODID + ":transportation_stone.invalid"));
if(!world.isRemote){
player.sendStatusMessage(new TextComponentTranslation("tile." + Wizardry.MODID + ":transportation_stone.invalid"), true);
}else{
BlockPos centre = findMostLikelyCircle(world, pos);
// Displays particles in the required shape
for(int x = -1; x <= 1; x++){
for(int z = -1; z <= 1; z++){
if(x == 0 && z == 0) continue;
ParticleBuilder.create(ParticleBuilder.Type.PATH)
.pos(WizardryUtilities.getCentre(centre).add(x, -0.3125, z)).clr(0x86ff65)
.time(200).scale(2).spawn(world);
}
}
}
return true;
}
}
@@ -136,12 +186,47 @@ public class BlockTransportationStone extends Block {
for(int x = -1; x <= 1; x++){
for(int z = -1; z <= 1; z++){
if(x == 0 && z == 0) continue;
if(world.getBlockState(pos.add(x, 0, z)).getBlock() != WizardryBlocks.transportation_stone){
if(x != 0 || z != 0) return false;
return false;
}
}
}
return true;
}
private static BlockPos findMostLikelyCircle(World world, BlockPos pos){
int bestSoFar = 0;
BlockPos result = null;
for(int x = -1; x <= 1; x++){
for(int z = -1; z <= 1; z++){
if(x == 0 && z == 0) continue;
BlockPos pos1 = pos.add(x, 0, z);
int n = getCircleCompleteness(world, pos1);
if(n > bestSoFar){
bestSoFar = n;
result = pos1;
}
}
}
return result;
}
private static int getCircleCompleteness(World world, BlockPos pos){
int n = 0;
for(int x = -1; x <= 1; x++){
for(int z = -1; z <= 1; z++){
if(x == 0 && z == 0) continue;
if(world.getBlockState(pos.add(x, 0, z)).getBlock() == WizardryBlocks.transportation_stone) n++;
}
}
return n;
}
}
@@ -1,9 +1,8 @@
package electroblob.wizardry.block;
import java.util.Random;
import electroblob.wizardry.tileentity.TileEntityTimer;
import net.minecraft.block.BlockContainer;
import net.minecraft.block.Block;
import net.minecraft.block.ITileEntityProvider;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.Entity;
@@ -17,15 +16,17 @@ import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
// For future reference - extend BlockContainer whenever possible because it has methods for removing tile entities on block break.
public class BlockVanishingCobweb extends BlockContainer {
import java.util.Random;
public class BlockVanishingCobweb extends Block implements ITileEntityProvider {
public BlockVanishingCobweb(Material material){
super(material);
}
@SideOnly(Side.CLIENT)
public BlockRenderLayer getBlockLayer(){
@Override
public BlockRenderLayer getRenderLayer(){
return BlockRenderLayer.CUTOUT;
}
@@ -49,6 +50,11 @@ public class BlockVanishingCobweb extends BlockContainer {
return false;
}
@Override
public boolean hasTileEntity(IBlockState state){
return true;
}
@Override
public TileEntity createNewTileEntity(World world, int metadata){
return new TileEntityTimer(400);
@@ -1,115 +1,54 @@
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.SpellGlyphData;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.audio.MovingSoundEntity;
import electroblob.wizardry.client.audio.SoundLoop;
import electroblob.wizardry.client.audio.SoundLoopSpell;
import electroblob.wizardry.client.gui.GuiSpellDisplay;
import electroblob.wizardry.client.gui.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.particle.*;
import electroblob.wizardry.client.particle.ParticleWizardry.IWizardryParticleFactory;
import electroblob.wizardry.client.renderer.LayerStone;
import electroblob.wizardry.client.renderer.RenderArcaneWorkbench;
import electroblob.wizardry.client.renderer.RenderBlackHole;
import electroblob.wizardry.client.renderer.RenderBlank;
import electroblob.wizardry.client.renderer.RenderBubble;
import electroblob.wizardry.client.renderer.RenderDecay;
import electroblob.wizardry.client.renderer.RenderDecoy;
import electroblob.wizardry.client.renderer.RenderEvilWizard;
import electroblob.wizardry.client.renderer.RenderFireRing;
import electroblob.wizardry.client.renderer.RenderForceArrow;
import electroblob.wizardry.client.renderer.RenderHammer;
import electroblob.wizardry.client.renderer.RenderIceGiant;
import electroblob.wizardry.client.renderer.RenderIceSpike;
import electroblob.wizardry.client.renderer.RenderLightningDisc;
import electroblob.wizardry.client.renderer.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.client.renderer.*;
import electroblob.wizardry.command.SpellEmitter;
import electroblob.wizardry.data.DispenserCastingData;
import electroblob.wizardry.data.SpellEmitterData;
import electroblob.wizardry.data.SpellGlyphData;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.entity.EntityShield;
import electroblob.wizardry.entity.construct.EntityArrowRain;
import electroblob.wizardry.entity.construct.EntityBlackHole;
import electroblob.wizardry.entity.construct.EntityBlizzard;
import electroblob.wizardry.entity.construct.EntityBubble;
import electroblob.wizardry.entity.construct.EntityDecay;
import electroblob.wizardry.entity.construct.EntityEarthquake;
import electroblob.wizardry.entity.construct.EntityFireRing;
import electroblob.wizardry.entity.construct.EntityFireSigil;
import electroblob.wizardry.entity.construct.EntityForcefield;
import electroblob.wizardry.entity.construct.EntityFrostSigil;
import electroblob.wizardry.entity.construct.EntityHailstorm;
import electroblob.wizardry.entity.construct.EntityHammer;
import electroblob.wizardry.entity.construct.EntityHealAura;
import electroblob.wizardry.entity.construct.EntityIceSpike;
import electroblob.wizardry.entity.construct.EntityLightningSigil;
import electroblob.wizardry.entity.construct.EntityTornado;
import electroblob.wizardry.entity.living.EntityDecoy;
import electroblob.wizardry.entity.living.EntityEvilWizard;
import electroblob.wizardry.entity.living.EntityIceGiant;
import electroblob.wizardry.entity.living.EntityIceWraith;
import electroblob.wizardry.entity.living.EntityLightningWraith;
import electroblob.wizardry.entity.living.EntityPhoenix;
import electroblob.wizardry.entity.living.EntityShadowWraith;
import electroblob.wizardry.entity.living.EntitySpiritHorse;
import electroblob.wizardry.entity.living.EntitySpiritWolf;
import electroblob.wizardry.entity.living.EntityStormElemental;
import electroblob.wizardry.entity.living.EntityWizard;
import electroblob.wizardry.entity.living.ISpellCaster;
import electroblob.wizardry.entity.living.ISummonedCreature;
import electroblob.wizardry.entity.projectile.EntityDarknessOrb;
import electroblob.wizardry.entity.projectile.EntityDart;
import electroblob.wizardry.entity.projectile.EntityFirebolt;
import electroblob.wizardry.entity.projectile.EntityFirebomb;
import electroblob.wizardry.entity.projectile.EntityForceArrow;
import electroblob.wizardry.entity.projectile.EntityForceOrb;
import electroblob.wizardry.entity.projectile.EntityIceCharge;
import electroblob.wizardry.entity.projectile.EntityIceLance;
import electroblob.wizardry.entity.projectile.EntityIceShard;
import electroblob.wizardry.entity.projectile.EntityLightningArrow;
import electroblob.wizardry.entity.projectile.EntityLightningDisc;
import electroblob.wizardry.entity.projectile.EntityMagicMissile;
import electroblob.wizardry.entity.projectile.EntityPoisonBomb;
import electroblob.wizardry.entity.projectile.EntitySmokeBomb;
import electroblob.wizardry.entity.projectile.EntitySpark;
import electroblob.wizardry.entity.projectile.EntitySparkBomb;
import electroblob.wizardry.entity.projectile.EntityThunderbolt;
import electroblob.wizardry.entity.construct.*;
import electroblob.wizardry.entity.living.*;
import electroblob.wizardry.entity.projectile.*;
import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.event.SpellCastEvent.Source;
import electroblob.wizardry.integration.antiqueatlas.WizardryAntiqueAtlasIntegration;
import electroblob.wizardry.item.ItemScroll;
import electroblob.wizardry.item.ItemSpellBook;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.packet.PacketCastContinuousSpell;
import electroblob.wizardry.packet.PacketCastSpell;
import electroblob.wizardry.packet.PacketNPCCastSpell;
import electroblob.wizardry.packet.PacketPlayerSync.Message;
import electroblob.wizardry.packet.PacketTransportation;
import electroblob.wizardry.packet.*;
import electroblob.wizardry.potion.PotionSlowTime;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.spell.Clairvoyance;
import electroblob.wizardry.spell.None;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.spell.*;
import electroblob.wizardry.tileentity.TileEntityArcaneWorkbench;
import electroblob.wizardry.tileentity.TileEntityMagicLight;
import electroblob.wizardry.tileentity.TileEntityShrineCore;
import electroblob.wizardry.tileentity.TileEntityStatue;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.WandHelper;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiMerchant;
import net.minecraft.client.model.ModelBiped;
import net.minecraft.client.renderer.entity.RenderBlaze;
import net.minecraft.client.renderer.entity.RenderHusk;
import net.minecraft.client.renderer.entity.RenderSkeleton;
import net.minecraft.client.resources.I18n;
import net.minecraft.client.resources.IReloadableResourceManager;
import net.minecraft.client.resources.IResourceManager;
@@ -119,19 +58,33 @@ import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.monster.EntityBlaze;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.inventory.ContainerMerchant;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.text.Style;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.village.MerchantRecipeList;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.config.Property;
import net.minecraftforge.fml.client.config.GuiConfigEntries.NumberSliderEntry;
import net.minecraftforge.fml.client.registry.ClientRegistry;
import net.minecraftforge.fml.client.registry.RenderingRegistry;
import org.lwjgl.input.Keyboard;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* The client proxy for wizardry.
@@ -145,7 +98,7 @@ public class ClientProxy extends CommonProxy {
public static MixedFontRenderer mixedFontRenderer;
/** Static particle factory map */
private static Map<Type, IWizardryParticleFactory> factories;
private static final Map<ResourceLocation, IWizardryParticleFactory> factories = new HashMap<>();
// Key Bindings
public static final KeyBinding NEXT_SPELL = new KeyBinding("key." + Wizardry.MODID + ".next_spell", Keyboard.KEY_N, "key.categories." + Wizardry.MODID);
@@ -154,6 +107,9 @@ public class ClientProxy extends CommonProxy {
// Armour Model
public static final ModelBiped WIZARD_ARMOUR_MODEL = new ModelWizardArmour(0.75f);
/** The wrap width for standard multi-line descriptions (see {@link ClientProxy#addMultiLineDescription(List, String, Style)}). */
private static final int TOOLTIP_WRAP_WIDTH = 140;
// SECTION Registry
// ===============================================================================================================
@@ -183,6 +139,15 @@ public class ClientProxy extends CommonProxy {
}
}
// @Override
// public void registerSoundEventListener(){
// Minecraft.getMinecraft().getSoundHandler().addListener(ContinuousSpellSoundEntity::soundPlayed);
// }
public void registerAtlasMarkers(){
WizardryAntiqueAtlasIntegration.registerMarkers();
}
// SECTION Misc
// ===============================================================================================================
@@ -196,14 +161,33 @@ public class ClientProxy extends CommonProxy {
property.setConfigEntryClass(SpellHUDSkinChooserEntry.class);
}
@Override
public void setToNamedBooleanEntry(Property property){
property.setConfigEntryClass(NamedBooleanEntry.class);
}
@Override
public World getTheWorld(){
return Minecraft.getMinecraft().world;
}
@Override
public void playMovingSound(Entity entity, SoundEvent sound, float volume, float pitch, boolean repeat){
Minecraft.getMinecraft().getSoundHandler().playSound(new MovingSoundEntity(entity, sound, volume, pitch, repeat));
public void playMovingSound(Entity entity, SoundEvent sound, SoundCategory category, float volume, float pitch, boolean repeat){
Minecraft.getMinecraft().getSoundHandler().playSound(new MovingSoundEntity<>(entity, sound, category, volume, pitch, repeat));
}
@Override
public void playSpellSoundLoop(EntityLivingBase entity, Spell spell, SoundEvent start, SoundEvent loop, SoundEvent end, SoundCategory category, float volume, float pitch){
SoundLoop.addLoop(new SoundLoopSpell.SoundLoopSpellEntity(start, loop, end, spell, entity, volume, pitch));
}
@Override
public void playSpellSoundLoop(World world, double x, double y, double z, Spell spell, SoundEvent start, SoundEvent loop, SoundEvent end, SoundCategory category, float volume, float pitch, int duration){
if(duration == -1){
SoundLoop.addLoop(new SoundLoopSpell.SoundLoopSpellDispenser(start, loop, end, spell, world, x, y, z, volume, pitch));
}else{
SoundLoop.addLoop(new SoundLoopSpell.SoundLoopSpellPosTimed(start, loop, end, spell, duration, x, y, z, volume, pitch));
}
}
@Override
@@ -214,6 +198,40 @@ public class ClientProxy extends CommonProxy {
// SECTION Items
// ===============================================================================================================
@Override
public boolean shouldDisplayDiscovered(Spell spell, ItemStack stack){
EntityPlayerSP player = Minecraft.getMinecraft().player;
if(player == null) return false;
// Displayed recipe
if(Minecraft.getMinecraft().currentScreen instanceof GuiMerchant){
// It doesn't actually matter if the recipe is selected or not, since the itemstack will only ever
// match one of them anyway - and we'd have to reflect into GuiMerchant to get the selected recipe
MerchantRecipeList recipes = ((GuiMerchant)Minecraft.getMinecraft().currentScreen).getMerchant().getRecipes(player);
if(recipes != null && recipes.stream().anyMatch(r -> r.getItemToSell() == stack)){
// Spell books are always discovered when wizards are selling them
return true;
}
}
// Recipe output slot
// Required or players would be able to find out what the spell is without actually completing the trade
if(player.openContainer instanceof ContainerMerchant){
if(((ContainerMerchant)player.openContainer).getMerchantInventory().getStackInSlot(2) == stack){
return true;
}
}
if(!Wizardry.settings.discoveryMode) return true;
if(player.isCreative()) return true;
if(WizardData.get(player) != null && WizardData.get(player).hasSpellBeenDiscovered(spell)) return true;
return false;
}
@Override
public FontRenderer getFontRenderer(ItemStack stack){
@@ -222,12 +240,10 @@ public class ClientProxy extends CommonProxy {
if(stack.getItem() instanceof ItemWand){
spell = WandHelper.getCurrentSpell(stack);
}else if(stack.getItem() instanceof ItemSpellBook || stack.getItem() instanceof ItemScroll){
spell = Spell.get(stack.getItemDamage());
spell = Spell.byMetadata(stack.getItemDamage());
}
if(Minecraft.getMinecraft().player != null && Wizardry.settings.discoveryMode && WizardData.get(Minecraft.getMinecraft().player) != null
&& !Minecraft.getMinecraft().player.capabilities.isCreativeMode
&& !WizardData.get(Minecraft.getMinecraft().player).hasSpellBeenDiscovered(spell)){
if(!shouldDisplayDiscovered(spell, stack)){
return mixedFontRenderer;
}
@@ -237,14 +253,14 @@ public class ClientProxy extends CommonProxy {
@Override
public String getScrollDisplayName(ItemStack scroll){
Spell spell = Spell.get(scroll.getItemDamage());
Spell spell = Spell.byMetadata(scroll.getItemDamage());
EntityPlayer player = Minecraft.getMinecraft().player;
boolean discovered = true;
// It seems that this method is called when the world is loading, before thePlayer has been initialised.
// If the player is null, the spell is assumed to be discovered.
if(player != null && Wizardry.settings.discoveryMode && !player.capabilities.isCreativeMode && WizardData.get(player) != null
if(player != null && Wizardry.settings.discoveryMode && !player.isCreative() && WizardData.get(player) != null
&& !WizardData.get(player).hasSpellBeenDiscovered(spell)){
discovered = false;
}
@@ -265,37 +281,50 @@ public class ClientProxy extends CommonProxy {
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
// ===============================================================================================================
@Override
public void initParticleFactories(){
factories = new HashMap<>();
factories.put(Type.BEAM, ParticleBeam::new);
factories.put(Type.BUFF, ParticleBuff::new);
factories.put(Type.DARK_MAGIC, ParticleDarkMagic::new);
factories.put(Type.DUST, ParticleDust::new);
factories.put(Type.FLASH, ParticleFlash::new);
factories.put(Type.ICE, ParticleIce::new);
factories.put(Type.LEAF, ParticleLeaf::new);
factories.put(Type.LIGHTNING, ParticleLightning::new);
factories.put(Type.LIGHTNING_PULSE, ParticleLightningPulse::new);
factories.put(Type.MAGIC_BUBBLE, ParticleMagicBubble::new);
factories.put(Type.MAGIC_FIRE, ParticleMagicFlame::new);
factories.put(Type.PATH, ParticlePath::new);
factories.put(Type.SCORCH, ParticleScorch::new);
factories.put(Type.SNOW, ParticleSnow::new);
factories.put(Type.SPARK, ParticleSpark::new);
factories.put(Type.SPARKLE, ParticleSparkle::new);
/** Use {@link ParticleWizardry#registerParticle(ResourceLocation, IWizardryParticleFactory)}, this is internal. */
// I mean, it does exactly the same thing but I might want to make it do something else in future...
public static void addParticleFactory(ResourceLocation name, IWizardryParticleFactory factory){
factories.put(name, factory);
}
@Override
public ParticleWizardry createParticle(Type type, World world, double x, double y, double z){
public void registerParticles(){
// I'll be a good programmer and use the API method rather than the one above. Lead by example, as they say...
ParticleWizardry.registerParticle(Type.BEAM, ParticleBeam::new);
ParticleWizardry.registerParticle(Type.BUFF, ParticleBuff::new);
ParticleWizardry.registerParticle(Type.DARK_MAGIC, ParticleDarkMagic::new);
ParticleWizardry.registerParticle(Type.DUST, ParticleDust::new);
ParticleWizardry.registerParticle(Type.FLASH, ParticleFlash::new);
ParticleWizardry.registerParticle(Type.ICE, ParticleIce::new);
ParticleWizardry.registerParticle(Type.LEAF, ParticleLeaf::new);
ParticleWizardry.registerParticle(Type.LIGHTNING, ParticleLightning::new);
ParticleWizardry.registerParticle(Type.LIGHTNING_PULSE, ParticleLightningPulse::new);
ParticleWizardry.registerParticle(Type.MAGIC_BUBBLE, ParticleMagicBubble::new);
ParticleWizardry.registerParticle(Type.MAGIC_FIRE, ParticleMagicFlame::new);
ParticleWizardry.registerParticle(Type.PATH, ParticlePath::new);
ParticleWizardry.registerParticle(Type.SCORCH, ParticleScorch::new);
ParticleWizardry.registerParticle(Type.SNOW, ParticleSnow::new);
ParticleWizardry.registerParticle(Type.SPARK, ParticleSpark::new);
ParticleWizardry.registerParticle(Type.SPARKLE, ParticleSparkle::new);
ParticleWizardry.registerParticle(Type.SPHERE, ParticleSphere::new);
ParticleWizardry.registerParticle(Type.SUMMON, ParticleSummon::new);
ParticleWizardry.registerParticle(Type.VINE, ParticleVine::new);
}
@Override
public ParticleWizardry createParticle(ResourceLocation type, World world, double x, double y, double z){
IWizardryParticleFactory factory = factories.get(type);
if(factory == null){
Wizardry.logger.warn("Unrecognised particle type %s ! Ensure the particle is properly registered.", type);
Wizardry.logger.warn("Unrecognised particle type {} ! Ensure the particle is properly registered.", type);
return null;
}
return factory.createParticle(world, x, y, z);
@@ -315,14 +344,12 @@ public class ClientProxy extends CommonProxy {
World world = Minecraft.getMinecraft().world;
Entity caster = world.getEntityByID(message.casterID);
Spell spell = Spell.get(message.spellID);
Spell spell = Spell.byNetworkID(message.spellID);
// Should always be true
if(caster instanceof EntityPlayer){
((EntityPlayer)caster).setActiveHand(message.hand);
// Duration isn't needed because it only ever affects things server-side, and anything that is
// seen client-side gets synced elsewhere.
spell.cast(world, (EntityPlayer)caster, message.hand, 0, message.modifiers);
Source source = Source.OTHER;
@@ -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.
// The handler for this event now deals with discovery.
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post((EntityPlayer)caster, spell, message.modifiers, source));
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(source, spell, (EntityPlayer)caster, message.modifiers));
}else{
Wizardry.logger.warn("Recieved a PacketCastSpell, but the caster ID was not the ID of a player");
}
}
@Override
public void handleCastSpellAtPosPacket(PacketCastSpellAtPos.Message message){
World world = Minecraft.getMinecraft().world;
Spell spell = Spell.byNetworkID(message.spellID);
spell.cast(world, message.position.x, message.position.y, message.position.z, message.direction, 0, message.duration, message.modifiers);
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.COMMAND, spell, world, message.position.x, message.position.y, message.position.z, message.direction, message.modifiers));
if(spell.isContinuous){
SpellEmitter.add(spell, world, message.position.x, message.position.y, message.position.z, message.direction, message.duration, message.modifiers);
}
}
@Override
public void handleCastContinuousSpellPacket(PacketCastContinuousSpell.Message message){
World world = Minecraft.getMinecraft().world;
Entity caster = world.getEntityByID(message.casterID);
Spell spell = Spell.get(message.spellID);
Spell spell = Spell.byNetworkID(message.spellID);
// Should always be true
if(caster instanceof EntityPlayer){
@@ -359,7 +401,7 @@ public class ClientProxy extends CommonProxy {
if(data.isCasting()){
WizardData.get((EntityPlayer)caster).stopCastingContinuousSpell();
}else{
WizardData.get((EntityPlayer)caster).startCastingContinuousSpell(spell, message.modifiers);
WizardData.get((EntityPlayer)caster).startCastingContinuousSpell(spell, message.modifiers, message.duration);
}
}
}else{
@@ -373,7 +415,7 @@ public class ClientProxy extends CommonProxy {
World world = Minecraft.getMinecraft().world;
Entity caster = world.getEntityByID(message.casterID);
Entity target = message.targetID == -1 ? null : world.getEntityByID(message.targetID);
Spell spell = Spell.get(message.spellID);
Spell spell = Spell.byNetworkID(message.spellID);
// Should always be true
if(caster instanceof EntityLiving){
@@ -382,7 +424,7 @@ public class ClientProxy extends CommonProxy {
spell.cast(world, (EntityLiving)caster, message.hand, 0, (EntityLivingBase)target, message.modifiers);
// Again, no need to check if the spell succeeded, because the packet is only ever sent when it
// succeeds.
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post((EntityLiving)caster, spell, message.modifiers, Source.NPC));
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.NPC, spell, (EntityLiving)caster, message.modifiers));
}
if(caster instanceof ISpellCaster){
@@ -391,89 +433,192 @@ public class ClientProxy extends CommonProxy {
((EntityLiving)caster).setAttackTarget((EntityLivingBase)target);
}
}
}else{
}else if(caster != null){
Wizardry.logger.warn("Recieved a PacketNPCCastSpell, but the caster ID was not the ID of an EntityLiving");
}
}
@Override
public void handleDispenserCastSpellPacket(PacketDispenserCastSpell.Message message){
World world = Minecraft.getMinecraft().world;
if(world.getTileEntity(message.pos) instanceof TileEntityDispenser){ // Should always be true
Spell spell = Spell.byNetworkID(message.spellID);
spell.cast(world, message.x, message.y, message.z, message.direction, 0, -1, message.modifiers);
// No need to check if the spell succeeded, because the packet is only ever sent when it succeeds.
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.DISPENSER, spell, world, message.x, message.y,
message.z, message.direction, message.modifiers));
if(spell.isContinuous || spell instanceof None){
DispenserCastingData data = DispenserCastingData.get((TileEntityDispenser)world.getTileEntity(message.pos));
if(spell.isContinuous){
data.startCasting(spell, message.x, message.y, message.z, message.duration, message.modifiers);
}else{
data.stopCasting();
}
}
}else{
Wizardry.logger.warn("Recieved a PacketDispenserCastSpell, but no tileEntity was found at the supplied location.");
}
}
@Override
public void handleTransportationPacket(PacketTransportation.Message message){
World world = Minecraft.getMinecraft().world;
Entity caster = world.getEntityByID(message.casterID);
// Moved from when the packet is sent to when it is received; fixes the sound not playing in first person.
caster.playSound(SoundEvents.BLOCK_PORTAL_TRAVEL, 1, 1);
caster.playSound(WizardrySounds.SPELL_TRANSPORTATION_TRAVEL, 1, 1);
for(int i = 0; i < 20; i++){
double radius = 1;
double angle = world.rand.nextDouble() * Math.PI * 2;
double x = caster.posX + radius * Math.cos(angle);
float angle = world.rand.nextFloat() * (float)Math.PI * 2;
double x = caster.posX + radius * MathHelper.cos(angle);
double y = caster.getEntityBoundingBox().minY + world.rand.nextDouble() * 2;
double z = caster.posZ + radius * Math.sin(angle);
double z = caster.posZ + radius * MathHelper.sin(angle);
ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.02, 0).clr(0.6f, 1, 0.6f)
.time(80 + world.rand.nextInt(10)).spawn(world);
}
for(int i = 0; i < 20; i++){
double radius = 1;
double angle = world.rand.nextDouble() * Math.PI * 2;
double x = caster.posX + radius * Math.cos(angle);
float angle = world.rand.nextFloat() * (float)Math.PI * 2;
double x = caster.posX + radius * MathHelper.cos(angle);
double y = caster.getEntityBoundingBox().minY + world.rand.nextDouble() * 2;
double z = caster.posZ + radius * Math.sin(angle);
double z = caster.posZ + radius * MathHelper.sin(angle);
world.spawnParticle(EnumParticleTypes.VILLAGER_HAPPY, x, y, z, 0, 0.02, 0);
}
for(int i = 0; i < 20; i++){
double radius = 1;
double angle = world.rand.nextDouble() * Math.PI * 2;
double x = caster.posX + radius * Math.cos(angle);
float angle = world.rand.nextFloat() * (float)Math.PI * 2;
double x = caster.posX + radius * MathHelper.cos(angle);
double y = caster.getEntityBoundingBox().minY + world.rand.nextDouble() * 2;
double z = caster.posZ + radius * Math.sin(angle);
double z = caster.posZ + radius * MathHelper.sin(angle);
world.spawnParticle(EnumParticleTypes.ENCHANTMENT_TABLE, x, y, z, 0, 0.02, 0);
}
}
@Override
public void handlePlayerSyncPacket(Message message){
public void handlePlayerSyncPacket(PacketPlayerSync.Message message){
WizardData properties = WizardData.get(Minecraft.getMinecraft().player);
WizardData data = WizardData.get(Minecraft.getMinecraft().player);
if(properties != null){
if(data != null){
properties.spellsDiscovered = message.spellsDiscovered;
data.synchronisedRandom.setSeed(message.seed);
data.spellsDiscovered = message.spellsDiscovered;
message.spellData.forEach(data::setVariable);
if(message.selectedMinionID == -1){
properties.selectedMinion = null;
data.selectedMinion = null;
}else{
Entity entity = Minecraft.getMinecraft().world.getEntityByID(message.selectedMinionID);
if(entity instanceof ISummonedCreature){
properties.selectedMinion = new WeakReference<ISummonedCreature>((ISummonedCreature)entity);
data.selectedMinion = new WeakReference<>((ISummonedCreature)entity);
}else{
properties.selectedMinion = null;
data.selectedMinion = null;
}
}
}
}
@Override
public void handleGlyphDataPacket(electroblob.wizardry.packet.PacketGlyphData.Message message){
public void handleGlyphDataPacket(PacketGlyphData.Message message){
SpellGlyphData data = SpellGlyphData.get(Minecraft.getMinecraft().world);
data.randomNames = new HashMap<Spell, String>();
data.randomDescriptions = new HashMap<Spell, String>();
data.randomNames = new HashMap<>();
data.randomDescriptions = new HashMap<>();
for(Spell spell : Spell.getSpells(Spell.allSpells)){
// -1 because the none spell isn't included
data.randomNames.put(spell, message.names.get(spell.id() - 1));
data.randomDescriptions.put(spell, message.descriptions.get(spell.id() - 1));
// This is a case where we must use the network ID, not the metadata
data.randomNames.put(spell, message.names.get(spell.networkID() - 1));
data.randomDescriptions.put(spell, message.descriptions.get(spell.networkID() - 1));
}
}
@Override
public void handleClairvoyancePacket(electroblob.wizardry.packet.PacketClairvoyance.Message message){
public void handleEmitterDataPacket(PacketEmitterData.Message message){
message.emitters.forEach(e -> e.setWorld(Minecraft.getMinecraft().world)); // Do this as soon as possible!
SpellEmitterData data = SpellEmitterData.get(Minecraft.getMinecraft().world);
// We shouldn't need to clear the emitters because when a player logs in or changes dimension the client world
// is wiped anyway, so the call to get() above should result in a fresh SpellEmitterData instance
message.emitters.forEach(data::add);
}
@Override
public void handleClairvoyancePacket(PacketClairvoyance.Message message){
Clairvoyance.spawnPathPaticles(Minecraft.getMinecraft().world, message.path, message.durationMultiplier);
}
@Override
public void handleAdvancementSyncPacket(PacketSyncAdvancements.Message message){
GuiWizardHandbook.updateUnlockStatus(message.showToasts, message.completedAdvancements);
}
@Override
public void handleEndSlowTimePacket(PacketEndSlowTime.Message message){
Entity entity = Minecraft.getMinecraft().world.getEntityByID(message.hostID);
if(entity instanceof EntityLivingBase) PotionSlowTime.unblockNearbyEntities((EntityLivingBase)entity);
else Wizardry.logger.warn("Received a PacketEndSlowTime, but the entity ID did not match any living entity");
}
@Override
public void handleResurrectionPacket(PacketResurrection.Message message){
Entity entity = Minecraft.getMinecraft().world.getEntityByID(message.playerID);
if(entity instanceof EntityPlayer){
((Resurrection)Spells.resurrection).resurrect((EntityPlayer)entity);
if(entity == Minecraft.getMinecraft().player){
Minecraft.getMinecraft().world.spawnEntity(entity);
Minecraft.getMinecraft().displayGuiScreen(null);
}
}
else Wizardry.logger.warn("Received a PacketResurrection, but the entity ID did not match any player");
}
@Override
public void handlePossessionPacket(PacketPossession.Message message){
Entity entity = Minecraft.getMinecraft().world.getEntityByID(message.playerID);
if(entity instanceof EntityPlayer){
EntityPlayer player = (EntityPlayer)entity;
if(message.targetID == -1){
((Possession)Spells.possession).endPossession(player);
}else{
Entity target = Minecraft.getMinecraft().world.getEntityByID(message.targetID);
if(target instanceof EntityLiving){
((Possession)Spells.possession).possess(player, (EntityLiving)target, message.duration);
player.sendStatusMessage(new TextComponentTranslation("spell." + Spells.possession.getRegistryName()
+ ".success", Minecraft.getMinecraft().gameSettings.keyBindSneak.getDisplayName()), true);
}
else Wizardry.logger.warn("Received a PacketPossession, but the target ID did not match any living entity");
}
}
else Wizardry.logger.warn("Received a PacketPossession, but the player ID did not match any player");
}
public void handleConquerShrinePacket(PacketConquerShrine.Message message){
TileEntity tileEntity = Minecraft.getMinecraft().world.getTileEntity(new BlockPos(message.x, message.y, message.z));
if(tileEntity instanceof TileEntityShrineCore){
((TileEntityShrineCore)tileEntity).conquer();
}else Wizardry.logger.warn("Received a PacketConquerShrine, but there was no shrine core at the position sent");
}
// SECTION Rendering
// ===============================================================================================================
@@ -486,6 +631,7 @@ public class ClientProxy extends CommonProxy {
@Override
public void initialiseLayers(){
LayerStone.initialiseLayers();
LayerFrost.initialiseLayers();
}
@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
// just want the vanilla one for the mob you're extending.
// Luckily for us, the vanilla husk renderer is only parametrised to EntityZombie
RenderingRegistry.registerEntityRenderingHandler(EntityHuskMinion.class, RenderHusk::new);
// This now extends AbstractSkeleton so we need to bind the renderer ourselves
RenderingRegistry.registerEntityRenderingHandler(EntitySkeletonMinion.class, RenderSkeleton::new);
RenderingRegistry.registerEntityRenderingHandler(EntityStrayMinion.class, RenderStrayMinion::new);
// An anonymous class in a lambda expression! No point writing a separate class really, is there?
RenderingRegistry.registerEntityRenderingHandler(EntityLightningWraith.class, manager -> new RenderBlaze(manager){
@Override
@@ -536,7 +688,7 @@ public class ClientProxy extends CommonProxy {
// Throwables
RenderingRegistry.registerEntityRenderingHandler(EntitySparkBomb.class,
manager -> new RenderProjectile(manager, 0.6f, new ResourceLocation(Wizardry.MODID, "textures/entity/spark_bomb.png"), false));
manager -> new RenderProjectile(manager, 0.6f, new ResourceLocation(Wizardry.MODID, "textures/items/spark_bomb.png"), false));
RenderingRegistry.registerEntityRenderingHandler(EntityFirebomb.class,
manager -> new RenderProjectile(manager, 0.6f, new ResourceLocation(Wizardry.MODID, "textures/items/firebomb.png"), false));
RenderingRegistry.registerEntityRenderingHandler(EntityPoisonBomb.class,
@@ -555,6 +707,14 @@ public class ClientProxy extends CommonProxy {
manager -> new RenderLightningDisc(manager, new ResourceLocation(Wizardry.MODID, "textures/entity/lightning_sigil.png"), 2.0f));
RenderingRegistry.registerEntityRenderingHandler(EntitySmokeBomb.class,
manager -> new RenderProjectile(manager, 0.6f, new ResourceLocation(Wizardry.MODID, "textures/items/smoke_bomb.png"), false));
RenderingRegistry.registerEntityRenderingHandler(EntityEmber.class,
manager -> new RenderProjectile(manager, 0.15f, new ResourceLocation(Wizardry.MODID, "textures/entity/ember.png"), false));
RenderingRegistry.registerEntityRenderingHandler(EntityMagicFireball.class,
manager -> new RenderProjectile(manager, 0.7f, new ResourceLocation(Wizardry.MODID, "textures/entity/fireball.png"), false));
RenderingRegistry.registerEntityRenderingHandler(EntityLargeMagicFireball.class,
manager -> new RenderProjectile(manager, 1.5f, new ResourceLocation(Wizardry.MODID, "textures/entity/fireball.png"), false));
RenderingRegistry.registerEntityRenderingHandler(EntityIceball.class,
manager -> new RenderProjectile(manager, 0.7f, new ResourceLocation(Wizardry.MODID, "textures/entity/iceball.png"), false));
// Effects and constructs
RenderingRegistry.registerEntityRenderingHandler(EntityBlackHole.class, RenderBlackHole::new);
@@ -562,13 +722,14 @@ public class ClientProxy extends CommonProxy {
RenderingRegistry.registerEntityRenderingHandler(EntityBubble.class, RenderBubble::new);
RenderingRegistry.registerEntityRenderingHandler(EntityHammer.class, RenderHammer::new);
RenderingRegistry.registerEntityRenderingHandler(EntityIceSpike.class, RenderIceSpike::new);
RenderingRegistry.registerEntityRenderingHandler(EntityForcefield.class, RenderForcefield::new);
//RenderingRegistry.registerEntityRenderingHandler(EntityContainmentField.class, RenderContainmentField::new);
// Stuff that doesn't render
RenderingRegistry.registerEntityRenderingHandler(EntityBlizzard.class, RenderBlank::new);
RenderingRegistry.registerEntityRenderingHandler(EntityTornado.class, RenderBlank::new);
RenderingRegistry.registerEntityRenderingHandler(EntityArrowRain.class, RenderBlank::new);
RenderingRegistry.registerEntityRenderingHandler(EntityShadowWraith.class, RenderBlank::new);
RenderingRegistry.registerEntityRenderingHandler(EntityForcefield.class, RenderBlank::new);
RenderingRegistry.registerEntityRenderingHandler(EntityThunderbolt.class, RenderBlank::new);
RenderingRegistry.registerEntityRenderingHandler(EntityStormElemental.class, RenderBlank::new);
RenderingRegistry.registerEntityRenderingHandler(EntityEarthquake.class, RenderBlank::new);
@@ -586,6 +747,8 @@ public class ClientProxy extends CommonProxy {
RenderingRegistry.registerEntityRenderingHandler(EntityFireRing.class,
manager -> new RenderFireRing(manager, new ResourceLocation(Wizardry.MODID, "textures/entity/ring_of_fire.png"), 5.0f));
RenderingRegistry.registerEntityRenderingHandler(EntityDecay.class, RenderDecay::new);
RenderingRegistry.registerEntityRenderingHandler(EntityCombustionRune.class,
manager -> new RenderSigil(manager, new ResourceLocation(Wizardry.MODID, "textures/entity/combustion_rune.png"), 2.0f, true));
// TESRs
ClientRegistry.bindTileEntitySpecialRenderer(TileEntityArcaneWorkbench.class, new RenderArcaneWorkbench());
@@ -1,19 +1,13 @@
package electroblob.wizardry.client;
import org.lwjgl.opengl.GL11;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.renderer.BufferBuilder;
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.*;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.item.ItemStack;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import net.minecraft.util.math.MathHelper;
import org.lwjgl.opengl.GL11;
/**
* Utility class containing some useful static methods for drawing GUIs. Previously these were spread across the main
@@ -23,7 +17,7 @@ import net.minecraftforge.fml.relauncher.SideOnly;
* @since Wizardry 4.2
* @see MixedFontRenderer
*/
@SideOnly(Side.CLIENT)
//@SideOnly(Side.CLIENT)
public final class DrawingUtils {
/**
@@ -130,6 +124,31 @@ public final class DrawingUtils {
tessellator.draw();
}
/**
* Mixes the two given opaque colours in the proportion specified.
* @param colour1 The first colour to mix, as a 6-digit hexadecimal.
* @param colour2 The second colour to mix, as a 6-digit hexadecimal.
* @param proportion The proportion of the second colour; will be clamped to between 0 and 1.
* @return The resulting colour, as a 6-digit hexadecimal.
*/
public static int mix(int colour1, int colour2, float proportion){
proportion = MathHelper.clamp(proportion, 0, 1);
int r1 = colour1 >> 16 & 255;
int g1 = colour1 >> 8 & 255;
int b1 = colour1 & 255;
int r2 = colour2 >> 16 & 255;
int g2 = colour2 >> 8 & 255;
int b2 = colour2 & 255;
int r = (int)(r1 + (r2-r1) * proportion);
int g = (int)(g1 + (g2-g1) * proportion);
int b = (int)(b1 + (b2-b1) * proportion);
return (r << 16) + (g << 8) + b;
}
/**
* Makes the given opaque colour translucent with the given opacity.
* @param colour An integer colour code, should be a 6-digit hexadecimal (i.e. opaque).
@@ -5,15 +5,13 @@ import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.settings.GameSettings;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
/**
* Font renderer that renders parts of strings surrounded by '#' (without quotes) in the SGA instead of normal text.
*
* @since Wizardry 1.1
*/
@SideOnly(Side.CLIENT)
//@SideOnly(Side.CLIENT)
public class MixedFontRenderer extends FontRenderer {
public MixedFontRenderer(GameSettings p_i1035_1_, ResourceLocation p_i1035_2_, TextureManager p_i1035_3_,
@@ -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;
import org.lwjgl.opengl.GL11;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.block.BlockMagicLight;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.data.DispenserCastingData;
import electroblob.wizardry.data.SpellEmitterData;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.item.ISpellCastingItem;
import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.item.ItemSpectralBow;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.spell.Flight;
import electroblob.wizardry.spell.ShadowWard;
import electroblob.wizardry.spell.Shield;
import electroblob.wizardry.tileentity.ContainerArcaneWorkbench;
import electroblob.wizardry.util.WandHelper;
import electroblob.wizardry.spell.*;
import electroblob.wizardry.util.RayTracer;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiMerchant;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.client.renderer.RenderItem;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.settings.KeyBinding;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.ai.attributes.AttributeModifier;
import net.minecraft.entity.item.EntityArmorStand;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.Slot;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.util.math.Vec3d;
import net.minecraft.village.MerchantRecipe;
import net.minecraftforge.client.event.FOVUpdateEvent;
import net.minecraftforge.client.event.GuiContainerEvent;
import net.minecraftforge.client.event.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.minecraft.world.World;
import net.minecraftforge.client.event.*;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import org.lwjgl.opengl.GL11;
import java.lang.reflect.Method;
/**
* Event handler responsible for client-side only events, mostly rendering.
@@ -53,13 +54,10 @@ import net.minecraftforge.fml.relauncher.SideOnly;
* @author Electroblob
* @since Wizardry 1.0
*/
@SideOnly(Side.CLIENT)
//@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT)
public final class WizardryClientEventHandler {
private static final ResourceLocation shieldTexture = new ResourceLocation(Wizardry.MODID, "textures/entity/shield.png");
private static final ResourceLocation wingTexture = new ResourceLocation(Wizardry.MODID, "textures/entity/wing.png");
private static final ResourceLocation shadowWardTexture = new ResourceLocation(Wizardry.MODID, "textures/entity/shadow_ward.png");
private static final ResourceLocation sixthSenseTexture = new ResourceLocation(Wizardry.MODID, "textures/entity/sixth_sense.png");
private static final ResourceLocation sixthSenseOverlayTexture = new ResourceLocation(Wizardry.MODID, "textures/gui/sixth_sense_overlay.png");
private static final ResourceLocation frostOverlayTexture = new ResourceLocation(Wizardry.MODID, "textures/gui/frost_overlay.png");
@@ -74,27 +72,109 @@ public final class WizardryClientEventHandler {
/** The number of ticks the blink effect lasts for. */
private static final int BLINK_EFFECT_DURATION = 8;
private static final Method unpressKey;
static {
unpressKey = ObfuscationReflectionHelper.findMethod(KeyBinding.class, "func_74505_d", void.class);
}
/** Starts the first person blink overlay effect. */
public static void playBlinkEffect(){
blinkEffectTimer = BLINK_EFFECT_DURATION;
}
@SubscribeEvent
public static void onLivingUpdateEvent(LivingUpdateEvent event){
if(event.getEntity() == Minecraft.getMinecraft().player){
public static void onPlayerTickEvent(TickEvent.PlayerTickEvent event){
if(event.player == Minecraft.getMinecraft().player){
if(blinkEffectTimer > 0) blinkEffectTimer--;
// Only seems to work here...
// EntityLiving victim = Possession.getPossessee(Minecraft.getMinecraft().player);
// if(victim != null && victim.getHeldItemMainhand().isEmpty()){
// Minecraft.getMinecraft().player.setHeldItem(EnumHand.MAIN_HAND, ItemStack.EMPTY);
// }
// Reset shaders if their respective potions aren't active
// This is a player so the potion effects are synced by vanilla
if(Minecraft.getMinecraft().entityRenderer.getShaderGroup() != null){
String activeShader = Minecraft.getMinecraft().entityRenderer.getShaderGroup().getShaderGroupName();
if((activeShader.equals(SlowTime.SHADER.toString()) && !Minecraft.getMinecraft().player.isPotionActive(WizardryPotions.slow_time))
|| (activeShader.equals(SixthSense.SHADER.toString()) && !Minecraft.getMinecraft().player.isPotionActive(WizardryPotions.sixth_sense))
|| (activeShader.equals(Transience.SHADER.toString()) && !Minecraft.getMinecraft().player.isPotionActive(WizardryPotions.transience))){
if(activeShader.equals(SixthSense.SHADER.toString())
|| activeShader.equals(Transience.SHADER.toString())) playBlinkEffect();
Minecraft.getMinecraft().entityRenderer.stopUseShader();
}
}
}
}
@SubscribeEvent
public static void onTextureStitchEvent(TextureStitchEvent.Pre event){
public static void onRenderHandEvent(RenderHandEvent event){
event.getMap().registerSprite(ContainerArcaneWorkbench.EMPTY_SLOT_CRYSTAL);
event.getMap().registerSprite(ContainerArcaneWorkbench.EMPTY_SLOT_UPGRADE);
EntityLiving victim = Possession.getPossessee(Minecraft.getMinecraft().player);
// for(int i=0; i<7; i++){
// event.getMap().registerSprite(new ResourceLocation(Wizardry.MODID, "particle/ice_" + i));
// }
if(victim != null){
victim.rotationYawHead = Minecraft.getMinecraft().player.rotationYaw;
if(Minecraft.getMinecraft().player.getHeldItemMainhand().isEmpty()){
event.setCanceled(true);
}
}
}
// This event is called every tick, not just when a movement key is pressed
@SubscribeEvent
public static void onInputUpdateEvent(InputUpdateEvent event){
// Prevents the player moving when paralysed
if(event.getEntityPlayer().isPotionActive(WizardryPotions.paralysis)){
event.getMovementInput().moveForward = 0;
event.getMovementInput().moveStrafe = 0;
event.getMovementInput().jump = false;
event.getMovementInput().sneak = false;
}
}
@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
@@ -122,6 +202,21 @@ public final class WizardryClientEventHandler {
}
}
@SubscribeEvent
public static void onDrawBlockHighlightEvent(DrawBlockHighlightEvent event){
// Hide the block outline for magic light blocks unless the player can dispel them
if(event.getTarget().typeOfHit == RayTraceResult.Type.BLOCK
&& event.getPlayer().world.getBlockState(event.getTarget().getBlockPos()).getBlock() instanceof BlockMagicLight){
if((!(event.getPlayer().getHeldItemMainhand().getItem() instanceof ISpellCastingItem)
&& !(event.getPlayer().getHeldItemOffhand().getItem() instanceof ISpellCastingItem))
|| !ItemArtefact.isArtefactActive(event.getPlayer(), WizardryItems.charm_light)){
event.setCanceled(true);
}
}
}
// Brute-force fix for crystals not showing up when a wizard is given a spell book in the trade GUI.
@SubscribeEvent
public static void onGuiDrawForegroundEvent(GuiContainerEvent.DrawForeground event){
@@ -138,10 +233,9 @@ public final class WizardryClientEventHandler {
for(MerchantRecipe trade : gui.getMerchant().getRecipes(Minecraft.getMinecraft().player)){
if(trade.getItemToBuy().getItem() == WizardryItems.spell_book && trade.getSecondItemToBuy().isEmpty()){
Slot slot = gui.inventorySlots.getSlot(2);
// Uses reflection to draw the itemstack
// It still doesn't look quite right because the slot highlight is behind the item, but it'll do
// until/unless I find a better solution.
renderItemAndTooltip(gui, trade.getItemToSell(), slot.xPos, slot.yPos, event.getMouseX(), event.getMouseY(),
DrawingUtils.drawItemAndTooltip(gui, trade.getItemToSell(), slot.xPos, slot.yPos, event.getMouseX(), event.getMouseY(),
gui.getSlotUnderMouse() == slot);
}
}
@@ -149,70 +243,25 @@ public final class WizardryClientEventHandler {
}
}
private static void renderItemAndTooltip(GuiContainer gui, ItemStack stack, int x, int y, int mouseX, int mouseY, boolean tooltip){
RenderItem renderItem = Minecraft.getMinecraft().getRenderItem();
GlStateManager.pushMatrix();
RenderHelper.enableGUIStandardItemLighting();
GlStateManager.disableLighting();
GlStateManager.enableRescaleNormal();
GlStateManager.enableColorMaterial();
GlStateManager.enableLighting();
renderItem.zLevel = 100.0F;
if(!stack.isEmpty()){
renderItem.renderItemAndEffectIntoGUI(stack, x, y);
renderItem.renderItemOverlays(Minecraft.getMinecraft().fontRenderer, stack, x, y);
if(tooltip){
gui.drawHoveringText(gui.getItemToolTip(stack), mouseX + gui.getXSize()/2 - gui.width/2,
mouseY + gui.getYSize()/2 - gui.height/2);
}
}
GlStateManager.popMatrix();
GlStateManager.enableLighting();
GlStateManager.enableDepth();
RenderHelper.enableStandardItemLighting();
}
// Third person
@SubscribeEvent
public static void onRenderPlayerEvent(RenderPlayerEvent.Post event){
renderShieldIfActive(event.getEntityPlayer());
renderWingsIfActive(event.getEntityPlayer(), event.getPartialRenderTick());
renderShadowWardIfActive(event.getEntityPlayer());
}
// First person
@SubscribeEvent
public static void onRenderWorldLastEvent(RenderWorldLastEvent event){
// Now only fires in first person.
if(Minecraft.getMinecraft().gameSettings.thirdPersonView == 0){
renderShieldFirstPerson(Minecraft.getMinecraft().player);
renderShadowWardFirstPerson(Minecraft.getMinecraft().player);
}
}
@SubscribeEvent
public static void onRenderLivingEvent(RenderLivingEvent.Post<EntityLivingBase> event){
Minecraft mc = Minecraft.getMinecraft();
WizardData properties = WizardData.get(mc.player);
WizardData data = WizardData.get(mc.player);
RenderManager renderManager = event.getRenderer().getRenderManager();
ItemStack wand = mc.player.getHeldItemMainhand();
if(!(wand.getItem() instanceof ItemWand)){
if(!(wand.getItem() instanceof ISpellCastingItem)){
wand = mc.player.getHeldItemOffhand();
}
// Target selection pointer
if(mc.player.isSneaking() && wand.getItem() instanceof ItemWand && WizardryUtilities.isLiving(event.getEntity())
&& properties != null && properties.selectedMinion != null){
if(mc.player.isSneaking() && wand.getItem() instanceof ISpellCastingItem && WizardryUtilities.isLiving(event.getEntity())
&& data != null && data.selectedMinion != null){
// -> Moved this in here so it isn't called every tick
RayTraceResult rayTrace = 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()){
@@ -256,7 +305,7 @@ public final class WizardryClientEventHandler {
}
// Summoned creature selection pointer
if(properties != null && properties.selectedMinion != null && properties.selectedMinion.get() == event.getEntity()){
if(data != null && data.selectedMinion != null && data.selectedMinion.get() == event.getEntity()){
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
@@ -297,8 +346,9 @@ public final class WizardryClientEventHandler {
}
// Sixth sense
if(mc.player.isPotionActive(WizardryPotions.sixth_sense) && !(event.getEntity() instanceof EntityArmorStand) && event.getEntity() != mc.player
&& mc.player.getActivePotionEffect(WizardryPotions.sixth_sense) != null && event.getEntity().getDistance(mc.player) < 20
if(mc.player.isPotionActive(WizardryPotions.sixth_sense) && !(event.getEntity() instanceof EntityArmorStand)
&& event.getEntity() != mc.player && mc.player.getActivePotionEffect(WizardryPotions.sixth_sense) != null
&& event.getEntity().getDistance(mc.player) < Spells.sixth_sense.getProperty(Spell.EFFECT_RADIUS).floatValue()
* (1 + mc.player.getActivePotionEffect(WizardryPotions.sixth_sense).getAmplifier() * Constants.RANGE_INCREASE_PER_LEVEL)){
Tessellator tessellator = Tessellator.getInstance();
@@ -413,322 +463,4 @@ public final class WizardryClientEventHandler {
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;
import org.lwjgl.input.Keyboard;
import electroblob.wizardry.Wizardry;
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.WizardryPacketHandler;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.WandHelper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.PositionedSoundRecord;
import net.minecraft.entity.player.EntityPlayer;
@@ -16,70 +13,52 @@ import net.minecraft.item.ItemStack;
import net.minecraftforge.client.event.MouseEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.InputEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
/** Event handler class responsible for handling wizardry's controls. */
@SideOnly(Side.CLIENT)
//@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT)
public class WizardryControlHandler {
static boolean NkeyPressed = 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
public static void onKeyInput(InputEvent.KeyInputEvent event){
public static void onTickEvent(TickEvent.ClientTickEvent event){
// Key pressed
if(Keyboard.getEventKeyState()){
if(event.phase == TickEvent.Phase.END) return; // Only really needs to be once per tick
if(Wizardry.proxy instanceof ClientProxy){
EntityPlayer player = Minecraft.getMinecraft().player;
ItemStack wand = player.getHeldItemMainhand();
if(!(wand.getItem() instanceof ItemWand)){
wand = player.getHeldItemOffhand();
// If the player isn't holding a wand, then nothing else needs to be done.
if(!(wand.getItem() instanceof ItemWand)) return;
}
if(player != null){
if(ClientProxy.NEXT_SPELL.isPressed() && Minecraft.getMinecraft().inGameHasFocus){
ItemStack wand = getWandInUse(player);
if(wand == null) return;
if(ClientProxy.NEXT_SPELL.isKeyDown() && Minecraft.getMinecraft().inGameHasFocus){
if(!NkeyPressed){
NkeyPressed = true;
}else{
NkeyAlreadyPressed = true;
}
if(!NkeyAlreadyPressed){
selectNextSpell(wand);
}
}else{
NkeyPressed = false;
}
if(ClientProxy.PREVIOUS_SPELL.isPressed() && Minecraft.getMinecraft().inGameHasFocus){
if(ClientProxy.PREVIOUS_SPELL.isKeyDown() && Minecraft.getMinecraft().inGameHasFocus){
if(!BkeyPressed){
BkeyPressed = true;
}else{
BkeyAlreadyPressed = true;
}
if(!BkeyAlreadyPressed){
// Packet building
selectPreviousSpell(wand);
}
}
}
}
// Key released
else{
if(NkeyPressed){
NkeyPressed = false;
NkeyAlreadyPressed = false;
}else if(BkeyPressed){
}else{
BkeyPressed = false;
BkeyAlreadyPressed = false;
}
}
}
}
@@ -89,16 +68,11 @@ public class WizardryControlHandler {
public static void onMouseEvent(MouseEvent event){
EntityPlayer player = Minecraft.getMinecraft().player;
ItemStack wand = player.getHeldItemMainhand();
if(!(wand.getItem() instanceof ItemWand)){
wand = player.getHeldItemOffhand();
// If the player isn't holding a wand, then nothing else needs to be done.
if(!(wand.getItem() instanceof ItemWand)) return;
}
ItemStack wand = getWandInUse(player);
if(wand == null) return;
if(Minecraft.getMinecraft().inGameHasFocus && !wand.isEmpty() && event.getDwheel() != 0 && player.isSneaking()
&& Wizardry.settings.enableShiftScrolling){
&& Wizardry.settings.shiftScrolling){
event.setCanceled(true);
@@ -112,14 +86,27 @@ 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){
// Packet building
IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.NEXT_SPELL_KEY);
WizardryPacketHandler.net.sendToServer(msg);
// 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);
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){
@@ -127,8 +114,8 @@ public class WizardryControlHandler {
IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.PREVIOUS_SPELL_KEY);
WizardryPacketHandler.net.sendToServer(msg);
// 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);
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;
import org.lwjgl.input.Keyboard;
import electroblob.wizardry.SpellGlyphData;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.DrawingUtils;
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.WizardryPacketHandler;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.tileentity.ContainerArcaneWorkbench;
import electroblob.wizardry.tileentity.TileEntityArcaneWorkbench;
import electroblob.wizardry.util.WandHelper;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.PositionedSoundRecord;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.GlStateManager.DestFactor;
import net.minecraft.client.renderer.GlStateManager.SourceFactor;
import net.minecraft.client.resources.I18n;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.inventory.IInventory;
@@ -25,131 +29,260 @@ import net.minecraft.inventory.Slot;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.relauncher.Side;
import org.lwjgl.input.Keyboard;
@Mod.EventBusSubscriber(Side.CLIENT)
public class GuiArcaneWorkbench extends GuiContainer {
private GuiButton applyBtn;
private static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID,
public static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID,
"textures/gui/arcane_workbench.png");
private IInventory playerInventory;
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.
// For calculations, we use the size without the tooltip.
private final int xSizeNoTip = 176;
/** We report the actual size of the GUI to Minecraft when a wand is in so JEI doesn't overdraw it.
* For calculations, we use the size without the tooltip, which is stored in this constant. */
private static final int MAIN_GUI_WIDTH = 176;
private static final int RUNE_LEFT = 38;
private static final int RUNE_TOP = 22;
private static final int RUNE_WIDTH = 100;
private static final int RUNE_HEIGHT = 100;
private static final int HALO_DIAMETER = 156;
private static final int TEXTURE_WIDTH = 512;
private static final int TEXTURE_HEIGHT = 256;
private int animationTimer = 0;
private static final int ANIMATION_DURATION = 20;
public GuiArcaneWorkbench(InventoryPlayer invPlayer, TileEntityArcaneWorkbench entity){
super(new ContainerArcaneWorkbench(invPlayer, entity));
this.playerInventory = invPlayer;
this.arcaneWorkbenchInventory = entity;
xSize = xSizeNoTip;
xSize = MAIN_GUI_WIDTH;
ySize = 220;
}
// Huh, didn't realise this method existed. Pretty neat.
@Override
public void drawScreen(int p_73863_1_, int p_73863_2_, float p_73863_3_){
public void updateScreen(){
if(animationTimer > 0) animationTimer--;
}
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks){
this.drawDefaultBackground();
GlStateManager.color(1, 1, 1, 1); // Just in case
Slot slot = this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT);
// Tests if there is a wand in the workbench and edits the positioning accordingly
if(this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getHasStack() && this.inventorySlots
.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getStack().getItem() instanceof ItemWand){
xSize = xSizeNoTip + tooltipWidth;
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 - tooltipWidth) / 2 + 48;
this.applyBtn.x = (this.width - TOOLTIP_WIDTH) / 2 + 64;
}else{
xSize = xSizeNoTip;
xSize = MAIN_GUI_WIDTH;
guiLeft = (this.width - this.xSize) / 2;
this.applyBtn.x = this.width / 2 + 48;
this.applyBtn.x = this.width / 2 + 64;
}
if(this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getHasStack()){
this.applyBtn.enabled = true;
}else{
this.applyBtn.enabled = false;
}
this.applyBtn.enabled = slot.getHasStack();
super.drawScreen(p_73863_1_, p_73863_2_, p_73863_3_);
super.drawScreen(mouseX, mouseY, partialTicks);
// Required now, or item mouseover tooltips won't render.
this.renderHoveredToolTip(p_73863_1_, p_73863_2_);
this.renderHoveredToolTip(mouseX, mouseY);
}
@Override
public void drawGuiContainerBackgroundLayer(float f, int mouseX, int mouseY){
public void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY){
GlStateManager.color(1F, 1F, 1F, 1F);
GlStateManager.color(1, 1, 1, 1);
Minecraft.getMinecraft().renderEngine.bindTexture(texture);
// Animation
// Grey background
DrawingUtils.drawTexturedRect(guiLeft + RUNE_LEFT, guiTop + RUNE_TOP, MAIN_GUI_WIDTH + TOOLTIP_WIDTH, 0,
RUNE_WIDTH, RUNE_HEIGHT, TEXTURE_WIDTH, TEXTURE_HEIGHT);
// Yellow 'halo'
if(animationTimer > 0){
GlStateManager.pushMatrix();
GlStateManager.enableBlend();
GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA);
int x = guiLeft + RUNE_LEFT + RUNE_WIDTH/2;
int y = guiTop + RUNE_TOP + RUNE_HEIGHT/2;
float scale = (animationTimer + partialTicks)/ANIMATION_DURATION;
scale = (float)(1 - Math.pow(1-scale, 1.4f)); // Makes it slower at the start and speed up
GlStateManager.scale(scale, scale, 1);
GlStateManager.translate(x/scale, y/scale, 0);
DrawingUtils.drawTexturedRect(-HALO_DIAMETER /2, -HALO_DIAMETER /2, MAIN_GUI_WIDTH + TOOLTIP_WIDTH, RUNE_HEIGHT,
HALO_DIAMETER, HALO_DIAMETER, TEXTURE_WIDTH, TEXTURE_HEIGHT);
GlStateManager.disableBlend();
GlStateManager.popMatrix();
}
// Main inventory
DrawingUtils.drawTexturedRect(guiLeft, guiTop, 0, 0, MAIN_GUI_WIDTH, ySize, TEXTURE_WIDTH, TEXTURE_HEIGHT);
float opacity = (animationTimer + partialTicks)/ANIMATION_DURATION;
// Changing slots
for(int i = 0; i < ContainerArcaneWorkbench.CRYSTAL_SLOT; i++){
Slot slot = this.inventorySlots.getSlot(i);
if(slot.xPos >= 0 && slot.yPos >= 0)
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
if(this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getHasStack() && this.inventorySlots
.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getStack().getItem() instanceof ItemWand){
if(this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getHasStack()){
ItemStack stack = this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getStack();
if(!(stack.getItem() instanceof IWorkbenchItem)){
Wizardry.logger.warn("Invalid item in central slot of arcane workbench, how did that get there?!");
return;
}
if(((IWorkbenchItem)stack.getItem()).showTooltip(stack)){
// Tooltip box
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);
DrawingUtils.drawTexturedRect(guiLeft + MAIN_GUI_WIDTH, guiTop, MAIN_GUI_WIDTH, 0, TOOLTIP_WIDTH, ySize, TEXTURE_WIDTH, TEXTURE_HEIGHT);
ItemStack wand = this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getStack();
int y = guiTop + 20;
Spell[] spells = WandHelper.getSpells(wand);
if(stack.getItem() instanceof IManaStoringItem && ((IManaStoringItem)stack.getItem()).showManaInWorkbench(this.mc.player, stack)){
y += 14;
}
int i = 0;
if(stack.getItem() instanceof ISpellCastingItem && ((ISpellCastingItem)stack.getItem()).showSpellsInWorkbench(this.mc.player, stack)){
Spell[] spells = ((ISpellCastingItem)stack.getItem()).getSpells(stack);
GlStateManager.enableBlend();
for(Spell spell : spells){
boolean discovered = true;
if(!this.mc.player.capabilities.isCreativeMode && WizardData.get(this.mc.player) != null){
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.element.getIcon() : Element.MAGIC.getIcon());
.bindTexture(discovered ? spell.getElement().getIcon() : Element.MAGIC.getIcon());
// Renders the little element icon
DrawingUtils.drawTexturedRect(guiLeft + MAIN_GUI_WIDTH + 5, guiTop + 34 + 10 * i++, 8, 8);
DrawingUtils.drawTexturedRect(guiLeft + MAIN_GUI_WIDTH + 5, y, 8, 8);
y += 10;
}
}
GlStateManager.disableBlend();
int x = 0;
int y = guiTop + 50 + spells.length * 10;
y += 16;
// Look how much shorter this is with the WandHelper class!
for(Item item : WandHelper.getSpecialUpgrades()){
int level = WandHelper.getUpgradeLevel(wand, item);
int level = WandHelper.getUpgradeLevel(stack, item);
if(level > 0){
ItemStack stack = new ItemStack(item, level);
ItemStack stack1 = new ItemStack(item, level);
GlStateManager.enableDepth();
this.itemRender.renderItemAndEffectIntoGUI(stack, guiLeft + xSizeNoTip + 6 + x, y);
this.itemRender.renderItemOverlayIntoGUI(this.fontRenderer, stack, guiLeft + xSizeNoTip + 6 + x, y,
this.itemRender.renderItemAndEffectIntoGUI(stack1, guiLeft + MAIN_GUI_WIDTH + 6 + x, y);
this.itemRender.renderItemOverlayIntoGUI(this.fontRenderer, stack1, guiLeft + MAIN_GUI_WIDTH + 6 + x, y,
null);
x += 18;
GlStateManager.disableDepth();
}
}
}
}
Minecraft.getMinecraft().renderEngine.bindTexture(texture);
// Fixes the bug that caused the slot 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.enableAlpha();
}
@@ -157,62 +290,83 @@ public class GuiArcaneWorkbench extends GuiContainer {
@Override
protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY){
GlStateManager.color(1, 1, 1, 1); // Just in case
this.fontRenderer
.drawString(this.arcaneWorkbenchInventory.hasCustomName() ? this.arcaneWorkbenchInventory.getName()
: I18n.format(this.arcaneWorkbenchInventory.getName()), 8, 6, 4210752);
this.fontRenderer.drawString(this.playerInventory.hasCustomName() ? this.playerInventory.getName()
: I18n.format(this.playerInventory.getName()), 8, this.ySize - 96 + 2, 4210752);
if(this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getHasStack() && this.inventorySlots
.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getStack().getItem() instanceof ItemWand){
if(this.inventorySlots.getSlot(ContainerArcaneWorkbench.CENTRE_SLOT).getHasStack()){
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)){
Wizardry.logger.warn("Invalid item in central slot of arcane workbench, how did that get there?!");
return;
}
if(((IWorkbenchItem)stack.getItem()).showTooltip(stack)){
int y = 6;
this.fontRenderer.drawStringWithShadow("\u00A7f" + stack.getDisplayName(), MAIN_GUI_WIDTH + 6, y, 0);
if(stack.getItem() instanceof IManaStoringItem && ((IManaStoringItem)stack.getItem()).showManaInWorkbench(this.mc.player, stack)){
y += 14;
this.fontRenderer.drawStringWithShadow(
"\u00A77" + I18n.format("container." + Wizardry.MODID + ":arcane_workbench.mana") + " "
+ (wand.getMaxDamage() - wand.getItemDamage()) + "/" + wand.getMaxDamage(),
xSizeNoTip + 6, 20, 0);
"\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);
}
Spell[] spells = WandHelper.getSpells(wand);
y += 14;
int y = 34;
if(stack.getItem() instanceof ISpellCastingItem && ((ISpellCastingItem)stack.getItem()).showSpellsInWorkbench(this.mc.player, stack)){
Spell[] spells = ((ISpellCastingItem)stack.getItem()).getSpells(stack);
for(Spell spell : spells){
boolean discovered = true;
if(!this.mc.player.capabilities.isCreativeMode && WizardData.get(this.mc.player) != null){
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(), xSizeNoTip + 16, y, 0);
this.fontRenderer.drawStringWithShadow(spell.getDisplayNameWithFormatting(), MAIN_GUI_WIDTH + 16, y, 0);
}else{
this.mc.standardGalacticFontRenderer.drawStringWithShadow(
"\u00A79" + SpellGlyphData.getGlyphName(spell, this.mc.world), xSizeNoTip + 16, y, 0);
"\u00A79" + SpellGlyphData.getGlyphName(spell, this.mc.world), MAIN_GUI_WIDTH + 16, y, 0);
}
y += 10;
}
}
if(WandHelper.getTotalUpgrades(wand) > 0){
if(WandHelper.getTotalUpgrades(stack) > 0){
this.fontRenderer.drawStringWithShadow(
"\u00A7f" + I18n.format("container." + Wizardry.MODID + ":arcane_workbench.upgrades"), xSizeNoTip + 6, y + 6, 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 = 50 + spells.length * 10;
y += 10;
// Wand upgrade tooltips
for(Item item : WandHelper.getSpecialUpgrades()){
int level = WandHelper.getUpgradeLevel(wand, item);
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(xSizeNoTip + 6 + x, y, 16, 16, mouseX, mouseY)){
ItemStack stack = new ItemStack(item, level);
this.renderToolTip(stack, mouseX - guiLeft, mouseY - guiTop);
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;
}
@@ -220,6 +374,7 @@ public class GuiArcaneWorkbench extends GuiContainer {
}
}
}
}
@Override
public void initGui(){
@@ -228,7 +383,7 @@ public class GuiArcaneWorkbench extends GuiContainer {
this.guiTop = (this.height - this.ySize) / 2;
Keyboard.enableRepeatEvents(true);
this.buttonList.clear();
this.buttonList.add(this.applyBtn = new GuiButtonApply(0, this.width / 2 + 48, this.height / 2 + 3));
this.buttonList.add(this.applyBtn = new GuiButtonApply(0, this.width / 2 + 64, this.height / 2 + 3));
}
@Override
@@ -244,8 +399,51 @@ public class GuiArcaneWorkbench extends GuiContainer {
// Packet building
IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.APPLY_BUTTON);
WizardryPacketHandler.net.sendToServer(msg);
// Sound
Minecraft.getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(
WizardrySounds.BLOCK_ARCANE_WORKBENCH_SPELLBIND, 1));
// Animation
animationTimer = 20;
}
}
}
private class GuiButtonApply extends GuiButton {
public GuiButtonApply(int id, int x, int y){
super(id, x, y, 16, 16, I18n.format("container." + Wizardry.MODID + ":arcane_workbench.apply"));
}
@Override
public void drawButton(Minecraft minecraft, int mouseX, int mouseY, float partialTicks){
// Whether the button is highlighted
this.hovered = mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height;
int k = 72;
int l = 220;
//int colour = 14737632;
if(this.enabled){
if(this.hovered){
k += this.width * 2;
//colour = 16777120;
}
}else{
k += this.width;
//colour = 10526880;
}
DrawingUtils.drawTexturedRect(this.x, this.y, k, l, this.width, this.height, 512, 256);
//this.drawCenteredString(minecraft.fontRenderer, this.displayString, this.x + this.width / 2,
// this.y + (this.height - 8) / 2, colour);
}
}
@SubscribeEvent
public static void onTextureStitchEvent(TextureStitchEvent.Pre event){
event.getMap().registerSprite(ContainerArcaneWorkbench.EMPTY_SLOT_CRYSTAL);
event.getMap().registerSprite(ContainerArcaneWorkbench.EMPTY_SLOT_UPGRADE);
}
}
@@ -2,10 +2,8 @@ package electroblob.wizardry.client.gui;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
//@SideOnly(Side.CLIENT)
public class GuiButtonInvisible extends GuiButton {
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;
import org.lwjgl.input.Keyboard;
import electroblob.wizardry.SpellGlyphData;
import electroblob.wizardry.WizardData;
import com.google.common.collect.ImmutableMap;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.DrawingUtils;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.data.SpellGlyphData;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.PositionedSoundRecord;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.input.Keyboard;
import java.util.Map;
public class GuiSpellBook extends GuiScreen {
private int xSize, ySize;
private Spell spell;
private static final 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){
super();
@@ -39,45 +48,47 @@ public class GuiSpellBook extends GuiScreen {
EntityPlayer player = Minecraft.getMinecraft().player;
boolean discovered = true;
if(Wizardry.settings.discoveryMode && !player.capabilities.isCreativeMode && WizardData.get(player) != null
if(Wizardry.settings.discoveryMode && !player.isCreative() && WizardData.get(player) != null
&& !WizardData.get(player).hasSpellBeenDiscovered(spell)){
discovered = false;
}
GlStateManager.color(1, 1, 1, 1); // Just in case
// Draws spell illustration on opposite page, underneath the book so it shows through the hole.
Minecraft.getMinecraft().renderEngine.bindTexture(discovered ? spell.getIcon() : Spells.none.getIcon());
DrawingUtils.drawTexturedRect(xPos + 146, yPos + 20, 0, 0, 128, 128, 128, 128);
Minecraft.getMinecraft().renderEngine.bindTexture(textures.get(spell.tier));
Minecraft.getMinecraft().renderEngine.bindTexture(textures.get(spell.getTier()));
DrawingUtils.drawTexturedRect(xPos, yPos, 0, 0, xSize, ySize, xSize, 256);
super.drawScreen(par1, par2, par3);
if(discovered){
this.fontRenderer.drawString(spell.getDisplayName(), xPos + 17, yPos + 15, 0);
this.fontRenderer.drawString(spell.type.getDisplayName(), xPos + 17, yPos + 26, 0x777777);
this.fontRenderer.drawString(spell.getType().getDisplayName(), xPos + 17, yPos + 26, 0x777777);
}else{
this.mc.standardGalacticFontRenderer.drawString(SpellGlyphData.getGlyphName(spell, player.world), xPos + 17,
yPos + 15, 0);
this.mc.standardGalacticFontRenderer.drawString(spell.type.getDisplayName(), xPos + 17, yPos + 26,
this.mc.standardGalacticFontRenderer.drawString(spell.getType().getDisplayName(), xPos + 17, yPos + 26,
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.
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{
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: ?";
this.fontRenderer.drawString(element, xPos + 17, yPos + 57, 0);
String manaCost = "Mana Cost: " + spell.cost;
if(spell.isContinuous) manaCost = "Mana Cost: " + spell.cost + "/second";
String manaCost = "Mana Cost: " + spell.getCost();
if(spell.isContinuous) manaCost = "Mana Cost: " + spell.getCost() + "/second";
if(!discovered) manaCost = "Mana Cost: ?";
this.fontRenderer.drawString(manaCost, xPos + 17, yPos + 69, 0);
@@ -93,10 +104,18 @@ public class GuiSpellBook extends GuiScreen {
super.initGui();
Keyboard.enableRepeatEvents(true);
this.buttonList.clear();
this.mc.getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.MISC_BOOK_OPEN, 1));
}
public void onGuiClosed(){
super.onGuiClosed();
Keyboard.enableRepeatEvents(false);
}
@Override
public boolean doesGuiPauseGame(){
return Wizardry.settings.booksPauseGame;
}
}
@@ -1,30 +1,17 @@
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.JsonElement;
import com.google.gson.JsonObject;
import electroblob.wizardry.Settings;
import electroblob.wizardry.SpellGlyphData;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.ClientProxy;
import electroblob.wizardry.client.DrawingUtils;
import electroblob.wizardry.client.MixedFontRenderer;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.data.SpellGlyphData;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.item.ISpellCastingItem;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.WandHelper;
@@ -37,14 +24,22 @@ import net.minecraft.client.resources.IResource;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumHandSide;
import net.minecraft.util.JsonUtils;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
import java.util.Map.Entry;
@Mod.EventBusSubscriber(Side.CLIENT)
public class GuiSpellDisplay {
@@ -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
* mappings as specified by {@code _index.json} (these stack between resource packs). The keys in the map correspond
* to the keys in {@code _index.json}, and are sorted in that order, with skins belonging to resource packs sorted
* from lowest to highest priority. The skins in the base mod will therefore always be first. */
private static final Map<String, Skin> skins = new LinkedHashMap<>(12); // 12 is the number of skins packaged with the mod
* from lowest to highest priority. The skins in the base mod will therefore always be first. (It should be noted,
* however, that in the gui itself the skins are always sorted in alphabetical order for some reason.) */
private static final Map<String, Skin> skins = new LinkedHashMap<>(14); // 14 is the number of skins packaged with the mod
private static final Gson gson = new Gson();
@@ -67,6 +63,9 @@ public class GuiSpellDisplay {
/** Opacity of the next/previous spell names, as a fraction. */
private static final float SPELL_NAME_OPACITY = 0.3f;
private static final int HALF_HOTBAR_WIDTH = 97; // Half the width of the hotbar, plus a bit for clearance
private static final int OFFHAND_SLOT_WIDTH = 29; // Width of the offhand slot plus the gap between it and the hotbar
/** Controls the spell switching animation. Positive when switching to the next spell, negative when switching to
* the previous spell. Decremented in magnitude by 1 each tick until it reaches 0 again. */
private static int switchTimer = 0;
@@ -103,14 +102,18 @@ public class GuiSpellDisplay {
EntityPlayer player = mc.player;
if(player.isSpectator()) return; // Spectators shouldn't have the spell HUD!
// If the player has a wand in each hand, only displays for the one in the main hand.
ItemStack wand = player.getHeldItemMainhand();
boolean mainHand = true;
if(!(wand.getItem() instanceof ItemWand)){
if(!(wand.getItem() instanceof ISpellCastingItem && ((ISpellCastingItem)wand.getItem()).showSpellHUD(player, wand))){
wand = player.getHeldItemOffhand();
// If the player isn't holding a wand, then nothing else needs to be done.
if(!(wand.getItem() instanceof ItemWand)) return;
mainHand = false;
// If the player isn't holding a spellcasting item that shows the HUD, then nothing else needs to be done.
if(!(wand.getItem() instanceof ISpellCastingItem && ((ISpellCastingItem)wand.getItem()).showSpellHUD(player, wand))) return;
}
int width = event.getResolution().getScaledWidth();
@@ -119,6 +122,11 @@ public class GuiSpellDisplay {
boolean flipX = Wizardry.settings.spellHUDPosition.flipX;
boolean flipY = Wizardry.settings.spellHUDPosition.flipY;
if(Wizardry.settings.spellHUDPosition.dynamic){
// ............. | This bit is true if the wand is on the left, false if it is on the right
flipX = flipX == ((mainHand ? player.getPrimaryHand() : player.getPrimaryHand().opposite()) == EnumHandSide.LEFT);
}
Skin skin = skins.get(Wizardry.settings.spellHUDSkin);
if(skin == null){
@@ -135,12 +143,31 @@ public class GuiSpellDisplay {
}
}
GlStateManager.pushMatrix();
// 'Origin' of the spell hud (bottom left corner of the actual texture, always in the corner of the screen)
int x = flipX ? width : 0;
int y = flipY ? 0: height;
// The space available to render the spell HUD
float xSpace = (float)(width/2 - HALF_HOTBAR_WIDTH);
if(!player.getHeldItemOffhand().isEmpty()
// Tests whether the offhand slot is rendered on the same side of the hotbar as the spell HUD
&& (player.getPrimaryHand() == EnumHandSide.LEFT) == flipX){
xSpace -= OFFHAND_SLOT_WIDTH;
}
// If the skin is at the bottom and the screen width is too small, scale it to avoid the hotbar and offhand
if(!flipY && skin.getWidth() > xSpace){ // width/2 - 91 - 29 taken from GuiInGame line 547
float scale = xSpace / skin.getWidth();
GlStateManager.scale(scale, scale, 1);
x = MathHelper.ceil(x/scale);
y = MathHelper.ceil(y/scale);
}
Spell spell = WandHelper.getCurrentSpell(wand);
int cooldown = WandHelper.getCurrentCooldown(wand);
int maxCooldown = WandHelper.getCurrentMaxCooldown(wand);
if(event.getType() == RenderGameOverlayEvent.ElementType.TEXT){
@@ -155,16 +182,9 @@ public class GuiSpellDisplay {
}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;
if(!player.capabilities.isCreativeMode && WizardData.get(player) != null){
if(!player.isCreative() && WizardData.get(player) != null){
discovered = WizardData.get(player).hasSpellBeenDiscovered(spell);
}
@@ -172,31 +192,23 @@ public class GuiSpellDisplay {
float progress = 1;
// 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
progress = (spell.cooldown * cooldownMultiplier - (float)cooldown + event.getPartialTicks())
/(spell.cooldown * cooldownMultiplier);
progress = maxCooldown == 0 ? 1 : (maxCooldown - (float)cooldown + event.getPartialTicks()) / maxCooldown;
}
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.
* @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
* Gets the name of the given spell, with formatting added according to its cooldown and whether the given player
* has discovered it.
* @param spell The spell to get the name of.
* @param 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.
* @return The spell name, with relevant formatting added, for use with the {@link MixedFontRenderer}.
*/
@@ -204,12 +216,12 @@ public class GuiSpellDisplay {
boolean discovered = true;
if(!player.capabilities.isCreativeMode && WizardData.get(player) != null){
if(!player.isCreative() && WizardData.get(player) != null){
discovered = WizardData.get(player).hasSpellBeenDiscovered(spell);
}
// Makes spells greyed out if they are in cooldown or if the player has the arcane jammer effect
String format = cooldown > 0 || player.isPotionActive(WizardryPotions.arcane_jammer) ? "\u00A78" : spell.element.getFormattingCode();
String format = cooldown > 0 || player.isPotionActive(WizardryPotions.arcane_jammer) ? "\u00A78" : spell.getElement().getFormattingCode();
if(!discovered) format = "\u00A79";
String name = discovered ? spell.getDisplayName() : SpellGlyphData.getGlyphName(spell, player.world);
@@ -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.
* 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 {
/** The texture file for this skin. */
@@ -1,21 +1,17 @@
package electroblob.wizardry.client;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
package electroblob.wizardry.client.gui.config;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.client.config.GuiButtonExt;
import net.minecraftforge.fml.client.config.GuiEditArray;
import net.minecraftforge.fml.client.config.GuiEditArrayEntries;
import net.minecraftforge.fml.client.config.*;
import net.minecraftforge.fml.client.config.GuiEditArrayEntries.StringEntry;
import net.minecraftforge.fml.client.config.GuiSelectString;
import net.minecraftforge.fml.client.config.IConfigElement;
import net.minecraftforge.fml.common.registry.EntityEntry;
import net.minecraftforge.fml.common.registry.ForgeRegistries;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
/**
* [NYI] Intended as a way of choosing entities by name from all those currently registered, within the config file, so
* that users don't have to look up the entity IDs. I can't get this to work correctly at the moment.
@@ -1,7 +1,4 @@
package electroblob.wizardry.client.gui;
import java.util.ArrayList;
import java.util.List;
package electroblob.wizardry.client.gui.config;
import electroblob.wizardry.Settings;
import electroblob.wizardry.Wizardry;
@@ -15,6 +12,9 @@ import net.minecraftforge.fml.client.config.GuiConfigEntries;
import net.minecraftforge.fml.client.config.GuiConfigEntries.CategoryEntry;
import net.minecraftforge.fml.client.config.IConfigElement;
import java.util.ArrayList;
import java.util.List;
public class GuiConfigWizardry extends GuiConfig {
public GuiConfigWizardry(GuiScreen parent){
@@ -33,6 +33,7 @@ public class GuiConfigWizardry extends GuiConfig {
configList.add(new DummyCategoryElement("clientConfig", "config." + Wizardry.MODID + ".category." + Settings.CLIENT_CATEGORY, ClientCategory.class));
configList.add(new DummyCategoryElement("spellsConfig", "config." + Wizardry.MODID + ".category." + Settings.SPELLS_CATEGORY, SpellsCategory.class));
configList.add(new DummyCategoryElement("resistancesConfig", "config." + Wizardry.MODID + ".category." + Settings.RESISTANCES_CATEGORY, ResistancesCategory.class));
configList.add(new DummyCategoryElement("compatibilityConfig", "config." + Wizardry.MODID + ".category." + Settings.COMPATIBILITY_CATEGORY, CompatibilityCategory.class));
configList.addAll(new ConfigElement(Wizardry.settings.getConfigCategory(Configuration.CATEGORY_GENERAL)).getChildElements());
@@ -78,6 +79,36 @@ public class GuiConfigWizardry extends GuiConfig {
@Override protected String getCategory() { return Settings.GAMEPLAY_CATEGORY; }
}
/** Worldgen category of the config gui. */
public static class WorldgenCategory extends CategoryBase {
public WorldgenCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
super(owningScreen, owningEntryList, prop);
}
@Override protected String getCategory() { return Settings.WORLDGEN_CATEGORY; }
}
/** Commands category of the config gui. */
public static class CommandsCategory extends CategoryBase {
public CommandsCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
super(owningScreen, owningEntryList, prop);
}
@Override protected String getCategory() { return Settings.COMMANDS_CATEGORY; }
}
/** Client category of the config gui. */
public static class ClientCategory extends CategoryBase {
public ClientCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
super(owningScreen, owningEntryList, prop);
}
@Override protected String getCategory() { return Settings.CLIENT_CATEGORY; }
}
/** Spells category of the config gui. */
public static class SpellsCategory extends CategoryBase {
@@ -98,33 +129,13 @@ public class GuiConfigWizardry extends GuiConfig {
@Override protected String getCategory() { return Settings.RESISTANCES_CATEGORY; }
}
/** Worldgen category of the config gui. */
public static class WorldgenCategory extends CategoryBase {
public WorldgenCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
super(owningScreen, owningEntryList, prop);
}
@Override protected String getCategory() { return Settings.WORLDGEN_CATEGORY; }
}
/** Client category of the config gui. */
public static class ClientCategory extends CategoryBase {
public ClientCategory(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
super(owningScreen, owningEntryList, prop);
}
@Override protected String getCategory() { return Settings.CLIENT_CATEGORY; }
}
/** Commands category of the config gui. */
public static class CommandsCategory extends CategoryBase {
public 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);
}
@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;
import java.util.Map;
import javax.annotation.Nullable;
package electroblob.wizardry.client.gui.config;
import com.google.common.collect.Lists;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.gui.GuiSpellDisplay;
import electroblob.wizardry.client.gui.GuiSpellDisplay.Skin;
import electroblob.wizardry.registry.Spells;
import net.minecraft.client.gui.GuiButton;
@@ -16,6 +12,9 @@ import net.minecraft.client.resources.I18n;
import net.minecraftforge.fml.client.config.GuiSelectString;
import net.minecraftforge.fml.client.config.IConfigElement;
import javax.annotation.Nullable;
import java.util.Map;
public class GuiSelectHUDSkin extends GuiSelectString {
public GuiSelectHUDSkin(GuiScreen parentScreen, IConfigElement configElement, int slotIndex, Map<Object, String> selectableValues, Object currentValue, boolean enabled){
@@ -50,7 +49,7 @@ public class GuiSelectHUDSkin extends GuiSelectString {
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 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 net.minecraftforge.client.gui.ForgeGuiFactory.ForgeConfigGui.ModIDEntry;
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.IConfigElement;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
/**
* Custom config GUI for spell HUD skin selection; displays a list of all the loaded skins and a preview of the currently
* selected skin. based off of {@link ModIDEntry} from Forge.
*/
public class SpellHUDSkinChooserEntry extends SelectValueEntry {
public SpellHUDSkinChooserEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop)
{
public SpellHUDSkinChooserEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop){
super(owningScreen, owningEntryList, prop, getSelectableValues());
if (this.selectableValues.size() == 0)
this.btnValue.enabled = false;
if(this.selectableValues.size() == 0) this.btnValue.enabled = false;
}
private static Map<Object, String> getSelectableValues(){
@@ -27,6 +27,7 @@ class Contents {
// Final fields are mandatory, the rest are optional
final String id;
final Section section;
private boolean hyperlinks = true;
private boolean pageNumbers = true;
private String separator = ".";
@@ -37,10 +38,14 @@ class Contents {
private final List<Section> entries;
private Contents(String id){
private List<Section> visibleEntries;
private Contents(String id, Section section){
this.id = id;
this.section = section;
this.entries = new ArrayList<>();
this.buttons = new ArrayList<>();
this.visibleEntries = new ArrayList<>();
}
/** Returns an unmodifiable, flattened collection of all the buttons in this contents. */
@@ -83,12 +88,14 @@ class Contents {
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 y = top + GuiWizardHandbook.TEXT_INSET_Y + startLine * font.FONT_HEIGHT;
for(Section entry : this.entries){
for(Section entry : this.visibleEntries){
if(entry.isUnlocked()){
int nameWidth = font.getStringWidth(entry.title);
@@ -107,6 +114,7 @@ class Contents {
}
}
}
}
/**
* Called on GUI load to format the section and all subsections, contents tables and other elements. Does not
@@ -124,6 +132,10 @@ class Contents {
this.buttons.clear();
this.visibleEntries = new ArrayList<>(entries); // Need to copy the collection first!
this.visibleEntries.removeIf(s -> !s.isUnlocked());
if(hyperlinks){
// FONT_HEIGHT may change between fonts, so this is calculated here. With the default font it's 14.
@@ -134,12 +146,12 @@ class Contents {
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 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++;
@@ -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,
// 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
* and references.
*
* @param parent The parent section for this contents.
* @param json A JSON object representing the contents to be constructed. This must contain at least an "id"
* string.
* @return The resulting {@code Contents} object.
* @throws JsonSyntaxException if at any point the JSON object is found to be invalid.
*/
static Contents fromJson(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.pageNumbers = JsonUtils.getBoolean(json, "page_numbers", true);
@@ -1,43 +1,38 @@
package electroblob.wizardry.client.gui.handbook;
import com.google.common.collect.Streams;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import electroblob.wizardry.client.DrawingUtils;
import electroblob.wizardry.spell.Mine;
import net.minecraft.client.Minecraft;
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.RenderHelper;
import net.minecraft.client.renderer.RenderItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.CraftingManager;
import net.minecraft.item.crafting.IRecipe;
import net.minecraft.item.crafting.Ingredient;
import net.minecraft.stats.RecipeBook;
import net.minecraft.util.JsonUtils;
import net.minecraft.util.NonNullList;
import net.minecraft.util.ResourceLocation;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.*;
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
private final ResourceLocation location;
private final ResourceLocation[] locations;
// Derived fields, not specifically defined in JSON
private IRecipe recipe;
private List<IRecipe> recipes;
private final Set<int[]> instances = new HashSet<>();
private CraftingRecipe(ResourceLocation location){
this.location = location;
private CraftingRecipe(ResourceLocation[] locations){
this.locations = locations;
}
/**
@@ -60,9 +55,14 @@ class CraftingRecipe {
* the recipes aren't necessarily loaded at that point. */
void load(){
this.recipe = CraftingManager.getRecipe(location);
recipes = new ArrayList<>(locations.length);
for(ResourceLocation location : locations){
IRecipe recipe = CraftingManager.getRecipe(location);
if(recipe == null) throw new JsonSyntaxException("No such recipe: " + location);
recipes.add(recipe);
}
}
/**
@@ -75,9 +75,12 @@ class CraftingRecipe {
* @param top The y coordinate of the top of the GUI.
*/
void draw(FontRenderer font, RenderItem itemRenderer, int doublePage, int left, int top){
int index = (int)(Minecraft.getSystemTime() % Integer.MAX_VALUE)/2000;
for(int[] instance : instances){
if(GuiWizardHandbook.singleToDoublePage(instance[0]) == doublePage){
renderCraftingRecipe(font, itemRenderer, left + instance[1], top + instance[2], 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.
*/
void drawTooltips(GuiWizardHandbook gui, FontRenderer font, RenderItem itemRenderer, int doublePage, int left, int top, int mouseX, int mouseY){
int index = (int)(Minecraft.getSystemTime() % Integer.MAX_VALUE)/2000;
for(int[] instance : instances){
if(GuiWizardHandbook.singleToDoublePage(instance[0]) == doublePage){
renderCraftingTooltips(gui, itemRenderer, left + instance[1], top + instance[2], mouseX, mouseY, 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
* 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.
* @return The resulting {@code Image} object.
* @throws JsonSyntaxException if at any point the JSON object is found to be invalid.
*/
static CraftingRecipe fromJson(JsonObject json){
ResourceLocation location = new ResourceLocation(JsonUtils.getString(json, "location"));
return new CraftingRecipe(location);
ResourceLocation[] locations = Streams.stream(JsonUtils.getJsonArray(json, "locations"))
.map(je -> new ResourceLocation(je.getAsString())).toArray(ResourceLocation[]::new);
return new CraftingRecipe(locations);
}
static void populate(Map<String, CraftingRecipe> map, JsonObject json){
@@ -135,17 +142,15 @@ class CraftingRecipe {
GlStateManager.color(1, 1, 1, 1);
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();
RenderHelper.enableGUIStandardItemLighting();
GlStateManager.disableLighting();
GlStateManager.enableRescaleNormal();
GlStateManager.enableColorMaterial();
GlStateManager.enableLighting();
itemRenderer.zLevel = 100.0F;
// TODO: There may be a better way of doing this
int index = (int)(Minecraft.getSystemTime() % Integer.MAX_VALUE)/2000;
int i = 0;
@@ -155,8 +160,8 @@ class CraftingRecipe {
if(ingredient != Ingredient.EMPTY){
ItemStack stack = ingredient.getMatchingStacks()[index % ingredient.getMatchingStacks().length];
if(!stack.isEmpty()){
itemRenderer.renderItemAndEffectIntoGUI(stack, x + 18 * (i%3), y + 18 * (i/3));
itemRenderer.renderItemOverlays(font, 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 + BORDER + 18 * (i%3), y + BORDER + 18 * (i/3));
}
}
@@ -164,16 +169,15 @@ class CraftingRecipe {
}
if(!result.isEmpty()){
itemRenderer.renderItemAndEffectIntoGUI(result, x + 86, y + 18);
itemRenderer.renderItemOverlays(font, result, x + 86, y + 18);
itemRenderer.renderItemAndEffectIntoGUI(result, x + BORDER + 86, y + BORDER + 18);
itemRenderer.renderItemOverlays(font, result, x + BORDER + 86, y + BORDER + 18);
}
GlStateManager.popMatrix();
GlStateManager.enableLighting();
GlStateManager.enableDepth();
GlStateManager.disableColorMaterial();
itemRenderer.zLevel = 0.0F;
//RenderHelper.enableStandardItemLighting();
RenderHelper.disableStandardItemLighting();
}
@@ -187,9 +191,7 @@ class CraftingRecipe {
GlStateManager.enableRescaleNormal();
GlStateManager.enableColorMaterial();
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 i = 0;
@@ -198,7 +200,7 @@ class CraftingRecipe {
if(ingredient != Ingredient.EMPTY){
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);
}
}
@@ -206,15 +208,14 @@ class CraftingRecipe {
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);
}
GlStateManager.popMatrix();
GlStateManager.enableLighting();
GlStateManager.enableDepth();
GlStateManager.disableColorMaterial();
RenderHelper.enableStandardItemLighting();
RenderHelper.disableStandardItemLighting();
}
@@ -1,12 +1,14 @@
package electroblob.wizardry.client.gui.handbook;
import com.google.gson.JsonSyntaxException;
import electroblob.wizardry.client.DrawingUtils;
import electroblob.wizardry.registry.WizardrySounds;
import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.PositionedSoundRecord;
import net.minecraft.client.audio.SoundHandler;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentString;
import net.minecraft.util.text.TextFormatting;
@@ -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-]+)+([/?].*)?$";
/** Pulse period of links to new sections, in milliseconds. */
private static final float PULSATION_PERIOD = 1500;
final int indent;
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);
@@ -36,6 +42,7 @@ public abstract class GuiButtonHyperlink extends GuiButton {
}
this.indent = indent; // Assigned here in case it was corrected above
this.linesLeft = linesLeft;
String line1 = font.listFormattedStringToWidth(linkWithSuffix, GuiWizardHandbook.PAGE_WIDTH - indent).get(0);
// Without trim(), there will be at least 1 leading space due to the custom wrapping
@@ -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){
@@ -72,6 +84,11 @@ public abstract class GuiButtonHyperlink extends GuiButton {
int t = y + font.FONT_HEIGHT * i;
if(i > linesLeft){
l = l + GuiWizardHandbook.GUI_WIDTH - 2 * GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH;
t -= GuiWizardHandbook.PAGE_HEIGHT - (GuiWizardHandbook.PAGE_HEIGHT % font.FONT_HEIGHT);
}
if(mouseX >= l && mouseY >= t && mouseX < l + font.getStringWidth(line) && mouseY < t + font.FONT_HEIGHT){
return true;
}
@@ -93,7 +110,6 @@ public abstract class GuiButtonHyperlink extends GuiButton {
if(this.visible){
this.hovered = isHovered(minecraft.fontRenderer, mouseX, mouseY);
int colour = hovered ? GuiWizardHandbook.colours.get("highlight") : GuiWizardHandbook.colours.get("hyperlink");
int i = 0;
@@ -104,13 +120,22 @@ public abstract class GuiButtonHyperlink extends GuiButton {
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++;
}
}
}
protected int getColour(){
return hovered ? GuiWizardHandbook.colours.get("highlight") : GuiWizardHandbook.colours.get("hyperlink");
}
/**
* Creates a new hyperlink button from the given arguments, automatically differentiating between URLs and sections.
* @param x The x position of the button
@@ -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 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!");
@@ -133,7 +158,7 @@ public abstract class GuiButtonHyperlink extends GuiButton {
if(arguments[0].matches(URL_REGEX)){
button = new GuiButtonHyperlink.External(0, x, y, font, arguments[arguments.length - 1], arguments[0],
font.getStringWidth(upToLink.get(upToLink.size() - 1)), suffix);
font.getStringWidth(upToLink.get(upToLink.size() - 1)), suffix, linesLeft, rightPage);
}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]);
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;
@@ -152,26 +177,49 @@ public abstract class GuiButtonHyperlink extends GuiButton {
final Section target;
Internal(int id, int x, int y, FontRenderer font, String text, Section target, int indent, String suffix){
super(id, x, y, font, text, indent, 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, linesLeft, rightPage);
this.target = target;
}
@Override
public boolean mousePressed(Minecraft minecraft, int mouseX, int mouseY){
if(!target.isUnlocked()) return false;
return super.mousePressed(minecraft, mouseX, mouseY);
}
@Override
public void playPressSound(SoundHandler soundHandler){
soundHandler.playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.MISC_PAGE_TURN, 1));
}
@Override
protected int getColour(){
if(!target.isUnlocked()) return GuiWizardHandbook.colours.get("text");
if(!hovered && target.isNew() && !Minecraft.getMinecraft().player.isCreative()){
int c = GuiWizardHandbook.colours.get("new_section");
int d = GuiWizardHandbook.colours.get("hyperlink");
float f = (MathHelper.sin((Minecraft.getSystemTime() % PULSATION_PERIOD) / PULSATION_PERIOD * 2 * (float)Math.PI) + 1) / 2f;
return DrawingUtils.mix(c, d, f);
}
return super.getColour();
}
}
static class External extends GuiButtonHyperlink {
final ITextComponent link;
External(int id, int x, int y, FontRenderer font, String text, String url, int indent, String suffix){
super(id, x, y, font, text, indent, 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, linesLeft, rightPage);
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.renderer.GlStateManager;
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 {
static final int WIDTH = 20;
@@ -3,6 +3,7 @@ package electroblob.wizardry.client.gui.handbook;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.ClientProxy;
import electroblob.wizardry.client.DrawingUtils;
@@ -11,34 +12,28 @@ import electroblob.wizardry.client.gui.handbook.GuiButtonTurnPage.Type;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.packet.PacketRequestAdvancementSync;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.WizardrySounds;
import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.PositionedSoundRecord;
import net.minecraft.client.audio.SoundHandler;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.multiplayer.ClientAdvancementManager;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.resources.IResource;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.item.ItemStack;
import net.minecraft.util.JsonUtils;
import net.minecraft.util.NonNullList;
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 java.awt.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.util.*;
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
@@ -54,7 +49,6 @@ import java.util.List;
* @see Image
* @see CraftingRecipe
*/
@Mod.EventBusSubscriber
public class GuiWizardHandbook extends GuiScreen {
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 RECIPE_TAG = "recipe";
static final String RULER_TAG = "ruler";
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. */
static final int GUI_WIDTH = 288, GUI_HEIGHT = 180;
/** The dimensions of the GUI texture itself. */
static final int TEXTURE_WIDTH = 512, TEXTURE_HEIGHT = 256;
/** The dimensions of the area of a single page in which text can be drawn. */
static final int PAGE_WIDTH = 120, PAGE_HEIGHT = 140;
/** The distance of the text from the top outside corner of each page. */
@@ -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
* {@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
* closed. Storing a section means the bookmark doesn't change location when new sections are unlocked.
* The key corresponding to the section in which the bookmark is currently set. Static because it persists when the
* book is closed. Storing a section means the bookmark doesn't change location when new sections are unlocked.
*/
private static Section bookmarkSection;
private static String bookmarkSection;
// Buttons
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 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
* 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("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("novice_max_charge", "" + Tier.BASIC.maxCharge);
addFormatTag("novice_max_charge", "" + Tier.NOVICE.maxCharge);
addFormatTag("apprentice_max_charge", "" + Tier.APPRENTICE.maxCharge);
addFormatTag("advanced_max_charge", "" + Tier.ADVANCED.maxCharge);
addFormatTag("master_max_charge", "" + Tier.MASTER.maxCharge);
@@ -264,12 +248,14 @@ public class GuiWizardHandbook extends GuiScreen {
Minecraft.getMinecraft().renderEngine.bindTexture(texture);
GlStateManager.color(1, 1, 1, 1);
// 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
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;
previousSection.visible = false; // Not worth testing if we're in the first section every frame
menu.visible = false;
@@ -281,7 +267,7 @@ public class GuiWizardHandbook extends GuiScreen {
// Last page background
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;
nextSection.visible = false;
}else{
@@ -302,9 +288,10 @@ public class GuiWizardHandbook extends GuiScreen {
}
// 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));
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));
// Buttons
@@ -314,14 +301,15 @@ public class GuiWizardHandbook extends GuiScreen {
GlStateManager.color(1, 1, 1, 1);
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;
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{
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,
bookmark.isMouseOver() ? 310 : 288, 0, 11, 191, 512, 256);
bookmark.isMouseOver() ? 310 : 288, 0, 11, 191, TEXTURE_WIDTH, TEXTURE_HEIGHT);
}
// Recipe tooltips
@@ -426,8 +414,6 @@ public class GuiWizardHandbook extends GuiScreen {
JsonElement je = gson.fromJson(reader, JsonElement.class);
JsonObject json = je.getAsJsonObject();
rulerText = JsonUtils.getString(json, "ruler_text");
JsonUtils.getJsonObject(json, "colours").entrySet().forEach(e -> colours.put(e.getKey(),
Color.decode(e.getValue().getAsString()).getRGB()));
@@ -443,10 +429,12 @@ public class GuiWizardHandbook extends GuiScreen {
return;
}
// Attempts to find a suitable place to put the bookmark to start with
bookmarkSection = sections.get("introduction");
if(bookmarkSection == null) bookmarkSection = sectionList.get(0);
bookmarkSection = JsonUtils.getString(json, "bookmark_start_section");
if(!sections.containsKey(bookmarkSection)) throw new JsonSyntaxException("Section with id " + bookmarkSection + " is undefined");
}
// The first resource load on startup is done before the packet handler is loaded
if(WizardryPacketHandler.net != null) WizardryPacketHandler.net.sendToServer(new PacketRequestAdvancementSync.Message());
}
/**
@@ -460,6 +448,8 @@ public class GuiWizardHandbook extends GuiScreen {
*/
private static IResource getHandbookResource(IResourceManager manager){
// TODO: Implement resource pack stacking to allow addon mods and texture packs to add/overwrite content
IResource handbookFile = null;
try{
@@ -507,12 +497,15 @@ public class GuiWizardHandbook extends GuiScreen {
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()){
currentPage = singleToDoublePage(sectionList.get(index + 1).startPage);
int index = visibleSections.indexOf(currentSection);
if(button == nextSection && index + 1 < visibleSections.size()){
currentPage = singleToDoublePage(visibleSections.get(index + 1).startPage);
}else if(index > 0){
currentPage = singleToDoublePage(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);
}else if(button == bookmark && bookmarkSection != null){
currentPage = singleToDoublePage(bookmarkSection.startPage) + bookmarkPage;
currentPage = singleToDoublePage(sections.get(bookmarkSection).startPage) + bookmarkPage;
}else{
if(button instanceof GuiButtonHyperlink.Internal){
@@ -540,12 +533,12 @@ public class GuiWizardHandbook extends GuiScreen {
this.selectedButton = bookmark;
for(Section section : sections.values()){
for(String key : sections.keySet()){
// 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{
super.mouseClicked(mouseX, mouseY, mouseButton);
@@ -558,9 +551,14 @@ public class GuiWizardHandbook extends GuiScreen {
super.renderToolTip(stack, x, y);
}
@SubscribeEvent
public static void onAdvancementEvent(AdvancementEvent event){
sections.values().forEach(s -> s.onAdvancement(event.getEntityPlayer(), event.getAdvancement()));
@Override
public boolean doesGuiPauseGame(){
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.util.JsonUtils;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper;
import java.util.HashSet;
import java.util.Map;
@@ -22,11 +23,15 @@ class Image {
private int textureWidth, textureHeight;
private int u = 0, v = 0;
private String caption = "";
private boolean border = true;
// Derived fields, not specifically defined in JSON
private final Set<int[]> instances = new HashSet<>();
private static final int CAPTION_OFFSET = 4;
private static final int TEXTURE_INSET_X = 180;
private static final int BORDER = 1;
private Image(ResourceLocation location, int width, int height){
this.location = location;
this.width = width;
@@ -68,6 +73,7 @@ class Image {
* @param top The y coordinate of the top of the GUI.
*/
void draw(FontRenderer font, int doublePage, int left, int top){
// Images
for(int[] instance : instances){
if(GuiWizardHandbook.singleToDoublePage(instance[0]) == doublePage){
Minecraft.getMinecraft().renderEngine.bindTexture(location);
@@ -77,6 +83,29 @@ class Image {
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.textureHeight = JsonUtils.getInt(json, "texture_height", image.height);
image.caption = JsonUtils.getString(json, "caption", "");
image.border = JsonUtils.getBoolean(json, "border", true);
return image;
}
@@ -7,13 +7,12 @@ import com.google.gson.JsonSyntaxException;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.DrawingUtils;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.advancements.Advancement;
import net.minecraft.advancements.AdvancementProgress;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
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.ResourceLocation;
import org.apache.commons.lang3.StringUtils;
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.
* 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.
* <p>
* <p></p>
* The formatting of the book is now done 'dynamically' - that is, the exact positions and page numbers of
* sections, images and so on are determined on GUI load and depend on which of the previous sections have been
* unlocked, amongst other factors. This means that all of the unlocked sections must be formatted in order on GUI
@@ -32,15 +31,15 @@ import java.util.*;
* @author Electroblob
* @since Wizardry 4.2
*/
// Because these are now generated on resource pack reload (not on handbook open, as before), this class must now
// be static
// Because these are now generated on resource pack reload (not on handbook open, as before), this class can no longer
// be a non-static inner class
class Section {
// Final fields are mandatory (none here though), the rest are optional
String title;
private String[] rawText;
private Contents contents;
private Advancement[] triggers;
private ResourceLocation[] triggers;
private Map<String, Section> subsections;
private boolean centreX, centreY;
@@ -54,6 +53,9 @@ class Section {
*/
private final List<List<String>> pages;
private boolean unlocked = false;
private boolean isNew = false;
private Section(){
this.buttons = 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){
return startPage <= page && startPage + pages.size() > page;
return this.isUnlocked() && startPage <= page && startPage + pages.size() > page;
}
/**
@@ -77,32 +79,24 @@ class Section {
*/
boolean isUnlocked(){
if(Minecraft.getMinecraft().player.isCreative()) return true;
if(!Wizardry.settings.handbookProgression) return true; // Always unlocked if handbook progression is off
if(triggers == null) return true; // If no triggers were defined, the section is unlocked from the start
// A section is automatically unlocked if one of its subsections is unlocked
for(Section subsection : subsections.values()){
if(subsection.isUnlocked()) return true;
}
if(triggers == null) return true; // If no triggers were defined, the section is unlocked from the start
for(Advancement trigger : triggers){
try{
// TESTME: Is this properly synced or do we need to do that as well?
Map<Advancement, AdvancementProgress> advancements = (Map)GuiWizardHandbook.ADVANCEMENT_TO_PROGRESS.get(Minecraft.getMinecraft().player.connection.getAdvancementManager());
if(advancements.get(trigger).isDone()){
return true;
return unlocked;
}
}catch(IllegalAccessException e){
e.printStackTrace();
}
}
return false;
/**
* Returns true if this section has been unlocked and not read yet. Also returns true if any subsections are new.
*/
boolean isNew(){
if(!Wizardry.settings.handbookProgression) return false;
return isNew || this.subsections.values().stream().anyMatch(Section::isNew);
}
/**
@@ -143,12 +137,19 @@ class Section {
for(String line : lines){
if(line.startsWith(GuiWizardHandbook.FORMAT_MARKER + GuiWizardHandbook.RULER_TAG)){
GlStateManager.color(1, 1, 1, 1);
Minecraft.getMinecraft().renderEngine.bindTexture(GuiWizardHandbook.texture);
DrawingUtils.drawTexturedRect(x-1, y-1, 0, GuiWizardHandbook.GUI_HEIGHT, GuiWizardHandbook.PAGE_WIDTH + 2, 9, GuiWizardHandbook.TEXTURE_WIDTH, GuiWizardHandbook.TEXTURE_HEIGHT);
}else{
int lx = centreX ? x + GuiWizardHandbook.PAGE_WIDTH / 2 - font.getStringWidth(line) / 2 : x;
font.drawString(line, lx, y, DrawingUtils.BLACK, false);
}
y += font.FONT_HEIGHT;
}
isNew = false; // Now a page has been drawn, the player must have seen it so it's not new any more
}
}
}
@@ -180,7 +181,7 @@ class Section {
// Adds the header if present
if(!this.title.isEmpty()){
lines.add(this.title);
lines.add(GuiWizardHandbook.rulerText);
lines.add(GuiWizardHandbook.FORMAT_MARKER + GuiWizardHandbook.RULER_TAG);
}
// Adds space for the contents if it exists
@@ -212,7 +213,7 @@ class Section {
+ StringUtils.abbreviate(raw, 50));
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
if((lines.size() % maxLineNumber) * font.FONT_HEIGHT + image.getHeight(font) > GuiWizardHandbook.PAGE_HEIGHT){
@@ -248,27 +249,35 @@ class Section {
+ StringUtils.abbreviate(raw, 50));
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
if((lines.size() % maxLineNumber) * font.FONT_HEIGHT + CraftingRecipe.HEIGHT > GuiWizardHandbook.PAGE_HEIGHT){
// Remaining number of lines on the page
lines.addAll(Collections.nCopies(maxLineNumber - (lines.size() % maxLineNumber), ""));
// Remaining number of lines on the page, plus the first blank one on the new page
lines.addAll(Collections.nCopies(maxLineNumber - (lines.size() % maxLineNumber), " "));
}
int page = startPage + (lines.size() / maxLineNumber);
if(lines.size() % maxLineNumber == 0) lines.add(" ");
int startLine = lines.size() % maxLineNumber - 1;
recipe.addInstance(page, GuiWizardHandbook.PAGE_WIDTH / 2 - CraftingRecipe.WIDTH / 2
+ (GuiWizardHandbook.isRightPage(page) ? GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : GuiWizardHandbook.TEXT_INSET_X),
GuiWizardHandbook.TEXT_INSET_Y + (lines.size() % maxLineNumber) * font.FONT_HEIGHT);
GuiWizardHandbook.TEXT_INSET_Y + startLine * font.FONT_HEIGHT);
// Height of the recipe in lines, rounded up
// Uses a single space instead of an empty string so that the page trimming doesn't remove them
lines.addAll(Collections.nCopies(CraftingRecipe.HEIGHT / font.FONT_HEIGHT, " "));
lines.addAll(Collections.nCopies(CraftingRecipe.HEIGHT / font.FONT_HEIGHT - 1, " "));
// This time we're not adding an extra space because it's not really needed
}else{ // All other paragraphs
// Formatting
for(Map.Entry<String, String> entry : GuiWizardHandbook.FORMAT_TAGS.entrySet()){
paragraph = paragraph.replace(GuiWizardHandbook.FORMAT_MARKER + entry.getKey(), entry.getValue());
}
// Hyperlinks
int linkStart;
@@ -284,15 +293,17 @@ class Section {
String linkRaw = paragraph.substring(linkStart, linkEnd + 1);
String[] arguments = paragraph.substring(linkStart + 1, linkEnd).split("\\s", 2);
String suffix = paragraph.substring(linkEnd).split("\\s", 2)[0];
String suffix = paragraph.substring(linkEnd).split("\\s", 2)[0].substring(1); // substring(1) to remove the @
// The index of the single page currently being formatted, relative to the section
int pageRelative = (lines.size() + upToLink.size() - 1) / maxLineNumber;
// The overall index of the single page currently being formatted
int page = startPage + pageRelative;
// The line number on this page
int lineNumber = (lines.size() + upToLink.size() - 1) % maxLineNumber;
int x = GuiWizardHandbook.isRightPage(page) ? left + GuiWizardHandbook.GUI_WIDTH - GuiWizardHandbook.TEXT_INSET_X - GuiWizardHandbook.PAGE_WIDTH : left + GuiWizardHandbook.TEXT_INSET_X;
int y = top + GuiWizardHandbook.TEXT_INSET_Y + (((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
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.
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
// If the link has no display rawText specified, it displays the unformatted target string
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));
}
@@ -373,18 +379,22 @@ class Section {
}
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);
}
if(JsonUtils.hasField(json, "text")){
// TESTME: Does this always preserve order?
section.rawText = Streams.stream(JsonUtils.getJsonArray(json, "text"))
.map(e -> JsonUtils.getString(e, "element of array rawText"))
.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")){
JsonObject centre = JsonUtils.getJsonObject(json,"centre");
@@ -416,14 +426,26 @@ class Section {
}
}
public void onAdvancement(EntityPlayer player, Advancement advancement){
Minecraft minecraft = Minecraft.getMinecraft();
//System.out.println(title);
if(triggers != null && Arrays.asList(triggers).contains(advancement) && player == minecraft.player){
//minecraft.getToastGui().drawToast(IToast);
/**
* Called on login and advancement completion to update this section's unlock status and display toast
* notifications if applicable.
*/
public void updateUnlockStatus(boolean showToasts, ResourceLocation... completedAdvancements){
// Debug
//System.out.println(title);
if(triggers == null) return;
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;
import javax.vecmath.Matrix4f;
import javax.vecmath.Vector3f;
import electroblob.wizardry.entity.living.EntityIceGiant;
import net.minecraft.client.model.ModelBase;
import net.minecraft.client.model.ModelRenderer;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.util.math.MathHelper;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
import javax.vecmath.Matrix4f;
import javax.vecmath.Vector3f;
//@SideOnly(Side.CLIENT)
public class ModelIceGiant extends ModelBase {
/** The head model for the iron golem. */
public ModelRenderer iceGiantHead;
@@ -97,9 +97,9 @@ public class ModelWizard extends ModelBiped {
setRotation(Shape13, 0F, 0F, 0F);
// Makes head bits move with head
// bipedHead.addChild(Shape5);
// bipedHead.addChild(hatSegment4);
bipedHead.addChild(beard);
// bipedHead.addChild(Shape7);
// bipedHead.addChild(hatSegment6);
// bipedHead.addChild(Shape8);
// bipedHead.addChild(Shape9);
// bipedHead.addChild(Shape10);
@@ -116,8 +116,8 @@ public class ModelWizard extends ModelBiped {
/* public void render(Entity entity, float f, float f1, float f2, float f3, float f4, float f5) {
* super.render(entity, f, f1, f2, f3, f4, f5); setRotationAngles(f, f1, f2, f3, f4, f5, entity);
* bipedRightLeg.render(f5); bipedLeftLeg.render(f5); bipedBody.render(f5); bipedLeftArm.render(f5);
* bipedRightArm.render(f5); bipedHead.render(f5); Shape5.render(f5); Shape8.render(f5); Shape9.render(f5);
* Shape10.render(f5); Shape7.render(f5); Shape11.render(f5); Shape12.render(f5); Shape6.render(f5);
* bipedRightArm.render(f5); bipedHead.render(f5); hatSegment4.render(f5); Shape8.render(f5); Shape9.render(f5);
* Shape10.render(f5); hatSegment6.render(f5); Shape11.render(f5); Shape12.render(f5); hatSegment5.render(f5);
* Shape13.render(f5); } */
private void setRotation(ModelRenderer model, float x, float y, float z){
model.rotateAngleX = x;
@@ -1,99 +1,110 @@
package electroblob.wizardry.client.model;
import electroblob.wizardry.block.BlockStatue;
import net.minecraft.client.model.ModelRenderer;
import net.minecraft.entity.Entity;
public class ModelWizardArmour extends ModelArmourFixer {
ModelRenderer Shape1;
ModelRenderer Shape2;
ModelRenderer Shape3;
ModelRenderer Shape4;
ModelRenderer Shape5;
ModelRenderer Shape6;
ModelRenderer Shape7;
ModelRenderer hatBrim;
ModelRenderer hatSegment1;
ModelRenderer hatSegment2;
ModelRenderer hatSegment3;
ModelRenderer hatSegment4;
ModelRenderer hatSegment5;
ModelRenderer hatSegment6;
ModelRenderer robe;
public ModelWizardArmour(float scale){
public ModelWizardArmour(float delta){
super(scale, 0, 64, 64);
super(delta, 0, 64, 64);
// This is necessary to stop the head from scaling.
this.bipedHead = new ModelRenderer(this, 0, 0);
this.bipedHead.addBox(-4.0F, -8.0F, -4.0F, 8, 8, 8, 0.1f);
// The hat layer has an offset of 0.5, so 0.6 is about the smallest we can get away with
this.bipedHead.addBox(-4.0F, -8.0F, -4.0F, 8, 8, 8, 0.6f);
this.bipedHead.setRotationPoint(0.0F, 0.0F + 0, 0.0F);
Shape1 = new ModelRenderer(this, -16, 32);
Shape1.addBox(-8F, -7F, -8F, 16, 0, 16);
Shape1.setRotationPoint(0F, 0F, 0F);
Shape1.setTextureSize(64, 64);
Shape1.mirror = true;
setRotation(Shape1, 0F, 0F, 0F);
hatBrim = new ModelRenderer(this, 0, 47);
// Making the height 1 stops the top and bottom z-fighting when the hat is enchanted
hatBrim.addBox(-8F, -6.85F, -8F, 16, 1, 16, 0.6f);
hatBrim.setRotationPoint(0F, 0F, 0F);
hatBrim.setTextureSize(64, 64);
hatBrim.mirror = true;
setRotation(hatBrim, 0F, 0F, 0F);
Shape2 = new ModelRenderer(this, 0, 48);
Shape2.addBox(0F, 0F, 0F, 6, 2, 6);
Shape2.setRotationPoint(-3F, -10F, -3F);
Shape2.setTextureSize(64, 64);
Shape2.mirror = true;
setRotation(Shape2, -0.1396263F, 0F, 0F);
hatSegment1 = new ModelRenderer(this, 0, 32);
hatSegment1.addBox(0F, 0F, 0F, 6, 2, 6, 0.2f);
hatSegment1.setRotationPoint(-3F, -10.6F, -3F);
hatSegment1.setTextureSize(64, 64);
hatSegment1.mirror = true;
setRotation(hatSegment1, -0.1396263F, 0F, 0F);
Shape3 = new ModelRenderer(this, 0, 56);
Shape3.addBox(0F, 0F, 0F, 5, 2, 5);
Shape3.setRotationPoint(-2.5F, -11.53333F, -2F);
Shape3.setTextureSize(64, 64);
Shape3.mirror = true;
setRotation(Shape3, -0.2443461F, 0F, 0F);
hatSegment2 = new ModelRenderer(this, 0, 40);
hatSegment2.addBox(0F, 0F, 0F, 5, 2, 5, 0.1f);
hatSegment2.setRotationPoint(-2.5F, -12.13333F, -2F);
hatSegment2.setTextureSize(64, 64);
hatSegment2.mirror = true;
setRotation(hatSegment2, -0.2443461F, 0F, 0F);
Shape4 = new ModelRenderer(this, 24, 48);
Shape4.addBox(0F, 0F, 0F, 4, 2, 4);
Shape4.setRotationPoint(-2F, -13F, -1F);
Shape4.setTextureSize(64, 64);
Shape4.mirror = true;
setRotation(Shape4, -0.4014257F, 0F, 0F);
hatSegment3 = new ModelRenderer(this, 24, 32);
hatSegment3.addBox(0F, 0F, 0F, 4, 2, 4);
hatSegment3.setRotationPoint(-2F, -13.6F, -1F);
hatSegment3.setTextureSize(64, 64);
hatSegment3.mirror = true;
setRotation(hatSegment3, -0.4014257F, 0F, 0F);
Shape5 = new ModelRenderer(this, 24, 54);
Shape5.addBox(0F, 0F, 0F, 3, 2, 3);
Shape5.setRotationPoint(-1.5F, -14F, 0F);
Shape5.setTextureSize(64, 64);
Shape5.mirror = true;
setRotation(Shape5, -0.5759587F, 0F, 0F);
hatSegment4 = new ModelRenderer(this, 24, 38);
hatSegment4.addBox(0F, 0F, 0F, 3, 2, 3);
hatSegment4.setRotationPoint(-1.5F, -14.6F, 0F);
hatSegment4.setTextureSize(64, 64);
hatSegment4.mirror = true;
setRotation(hatSegment4, -0.5759587F, 0F, 0F);
Shape6 = new ModelRenderer(this, 20, 59);
Shape6.addBox(0F, 0F, 0F, 2, 2, 2);
Shape6.setRotationPoint(-1F, -14F, 0F);
Shape6.setTextureSize(64, 64);
Shape6.mirror = true;
setRotation(Shape6, 0.3316126F, 0F, 0F);
hatSegment5 = new ModelRenderer(this, 20, 43);
hatSegment5.addBox(0F, 0F, 0F, 2, 2, 2);
hatSegment5.setRotationPoint(-1F, -14.6F, 0F);
hatSegment5.setTextureSize(64, 64);
hatSegment5.mirror = true;
setRotation(hatSegment5, 0.3316126F, 0F, 0F);
Shape7 = new ModelRenderer(this, 28, 59);
Shape7.addBox(0F, 0F, 0F, 1, 1, 3);
Shape7.setRotationPoint(-0.5F, -14.5F, 2F);
Shape7.setTextureSize(64, 64);
Shape7.mirror = true;
setRotation(Shape7, -0.5585054F, 0F, 0F);
hatSegment6 = new ModelRenderer(this, 28, 43);
hatSegment6.addBox(0F, 0F, 0F, 1, 1, 3);
hatSegment6.setRotationPoint(-0.5F, -15.1F, 2F);
hatSegment6.setTextureSize(64, 64);
hatSegment6.mirror = true;
setRotation(hatSegment6, -0.5585054F, 0F, 0F);
// The robe is now the body
bipedBody = new ModelRenderer(this, 40, 42);
bipedBody.addBox(-4F, 0F, -2F, 8, 18, 4, scale);
bipedBody = new ModelRenderer(this, 16, 16);
bipedBody.addBox(-4F, 0F, -2F, 8, 11, 4, delta);
bipedBody.setRotationPoint(0F, 0F, 0F);
bipedBody.setTextureSize(64, 64);
bipedBody.mirror = true;
setRotation(bipedBody, 0F, 0F, 0F);
robe = new ModelRenderer(this, 40, 32);
robe.addBox(-4F, 0F, -2F, 8, 7, 4, delta);
robe.setRotationPoint(0F, 12, 0F); // 12.5 accounts for the expansion of each box
robe.setTextureSize(64, 64);
robe.mirror = true;
setRotation(robe, 0F, 0F, 0F);
// Makes the hat rotate with the head.
bipedHead.addChild(Shape1);
bipedHead.addChild(Shape2);
bipedHead.addChild(Shape3);
bipedHead.addChild(Shape4);
bipedHead.addChild(Shape5);
bipedHead.addChild(Shape6);
bipedHead.addChild(Shape7);
// Makes the robe move with the body
// bipedBody.addChild(robe);
bipedHead.addChild(hatBrim);
bipedHead.addChild(hatSegment1);
bipedHead.addChild(hatSegment2);
bipedHead.addChild(hatSegment3);
bipedHead.addChild(hatSegment4);
bipedHead.addChild(hatSegment5);
bipedHead.addChild(hatSegment6);
}
public void render(Entity entity, float f, float f1, float f2, float f3, float f4, float f5){
if(entity.isInvisible() && !entity.getEntityData().getBoolean(BlockStatue.PETRIFIED_NBT_KEY)
&& !entity.getEntityData().getBoolean(BlockStatue.FROZEN_NBT_KEY)) return;
super.render(entity, f, f1, f2, f3, f4, f5);
setRotationAngles(f, f1, f2, f3, f4, f5, entity);
this.robe.render(f5);
}
private void setRotation(ModelRenderer model, float x, float y, float z){
@@ -104,6 +115,20 @@ public class ModelWizardArmour extends ModelArmourFixer {
public void setRotationAngles(float f, float f1, float f2, float f3, float f4, float f5, Entity entity){
super.setRotationAngles(f, f1, f2, f3, f4, f5, entity);
this.robe.showModel = this.bipedBody.showModel;
if(this.isSneak){
//this.robe.rotationPointY = 10.5f;
this.robe.rotationPointZ = 4;
}else{
//this.robe.rotationPointY = 12.5f;
this.robe.rotationPointZ = 0;
}
// The bottom part of the robe takes the y rotation from the rest of the robe but the x/z rotation
// from the average of the two legs
this.robe.rotateAngleX = (this.bipedLeftLeg.rotateAngleX + this.bipedRightLeg.rotateAngleX) / 2f;
this.robe.rotateAngleY = this.bipedBody.rotateAngleY;
this.robe.rotateAngleZ = (this.bipedLeftLeg.rotateAngleZ + this.bipedRightLeg.rotateAngleZ) / 2f;
}
}
@@ -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;
import org.lwjgl.opengl.GL11;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.world.World;
import org.lwjgl.opengl.GL11;
public class ParticleBeam extends ParticleTargeted {
@@ -17,10 +16,15 @@ public class ParticleBeam extends ParticleTargeted {
public ParticleBeam(World world, double x, double y, double z){
super(world, x, y, z); // Does not have a texture!
this.setRBGColorF(1, 1, 1);
this.setMaxAge(1);
this.setMaxAge(0);
this.particleScale = 1;
}
@Override
public boolean shouldDisableDepth(){
return true;
}
@Override
public int getFXLayer(){
return 3;
@@ -31,7 +35,7 @@ public class ParticleBeam extends ParticleTargeted {
float scale = this.particleScale;
if(this.particleMaxAge > 1){
if(this.particleMaxAge > 0){
float ageFraction = (particleAge + partialTicks - 1)/particleMaxAge;
// Squaring this makes it look smoother than a linear shrinking effect
scale = this.particleScale * (1 - ageFraction*ageFraction);
@@ -1,26 +1,18 @@
package electroblob.wizardry.client.particle;
import org.lwjgl.opengl.GL11;
import electroblob.wizardry.Wizardry;
import net.minecraft.client.Minecraft;
import net.minecraft.client.particle.Particle;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.*;
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.RenderHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.entity.Entity;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import org.lwjgl.opengl.GL11;
@SideOnly(Side.CLIENT)
//@SideOnly(Side.CLIENT)
public class ParticleBuff extends ParticleWizardry {
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){
super(world, x, y, z);
this.setVelocity(0, 0.27, 0); // Approximately what it was before
this.mirror = world.rand.nextBoolean();
this.setVelocity(0, 0.162, 0); // Approximately what it was before
this.mirror = random.nextBoolean();
this.setMaxAge(15);
this.setGravity(false);
this.canCollide = false;
}
@Override
public boolean shouldDisableDepth(){
return true;
}
@Override
public void onUpdate(){
super.onUpdate();
@@ -60,13 +58,12 @@ public class ParticleBuff extends ParticleWizardry {
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();
@@ -94,11 +91,6 @@ public class ParticleBuff extends ParticleWizardry {
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 y = (float)(this.prevPosY + (this.posY - this.prevPosY) * (double)partialTicks - interpPosY);
float z = (float)(this.prevPosZ + (this.posZ - this.prevPosZ) * (double)partialTicks - interpPosZ);
@@ -107,18 +99,21 @@ public class ParticleBuff extends ParticleWizardry {
float f = 0.875f - 0.125f * MathHelper.floor((float)this.particleAge/(float)this.particleMaxAge * 8 - 0.000001f);
float g = f + 0.125f;
float hrepeat = 1;
float 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-1, y+yScale, z-1).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+1, y+yScale, z-1).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+1, y+yScale, z+1).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-1, y+yScale, z+1).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-1, y+yScale, z-1).tex(hrepeat, f).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-dx, y+yScale, z-dz).tex(0, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x+dx, y-yScale, z-dz).tex(0.25*hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x+dx, y+yScale, z-dz).tex(0.25*hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x+dx, y-yScale, z+dz).tex(0.5*hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x+dx, y+yScale, z+dz).tex(0.5*hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x-dx, y-yScale, z+dz).tex(0.75*hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x-dx, y+yScale, z+dz).tex(0.75*hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x-dx, y-yScale, z-dz).tex(hrepeat, g).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
buffer.pos(x-dx, y+yScale, z-dz).tex(hrepeat, f).color(particleRed, particleGreen, particleBlue, particleAlpha).endVertex();
Tessellator.getInstance().draw();
@@ -3,10 +3,8 @@ package electroblob.wizardry.client.particle;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.entity.Entity;
import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
//@SideOnly(Side.CLIENT)
public class ParticleDarkMagic extends ParticleWizardry {
/** Base spell texture index */
@@ -1,10 +1,8 @@
package electroblob.wizardry.client.particle;
import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
//@SideOnly(Side.CLIENT)
public class ParticleDust extends ParticleWizardry {
public ParticleDust(World world, double x, double y, double z){
@@ -24,7 +22,7 @@ public class ParticleDust extends ParticleWizardry {
this.prevPosX = this.posX;
this.prevPosY = this.posY;
this.prevPosZ = this.posZ;
// this.moveEntity(this.motionX, this.motionY, this.motionZ);
this.move(this.motionX, this.motionY, this.motionZ);
if(this.particleMaxAge-- <= 0){
this.setExpired();
@@ -21,7 +21,12 @@ public class ParticleFlash extends ParticleWizardry {
}
@Override
public void renderParticle(BufferBuilder buffer, Entity entityIn, float partialTicks, float rotationX, float rotationZ, float rotationYZ, float rotationXY, float rotationXZ){
public boolean shouldDisableDepth(){
return true; // Well this fixes everything... let's hope it doesn't cause any side-effects!
}
@Override
public void drawParticle(BufferBuilder buffer, Entity entityIn, float partialTicks, float rotationX, float rotationZ, float rotationYZ, float rotationXY, float rotationXZ){
float f4 = particleScale * MathHelper.sin(((float)this.particleAge + partialTicks - 1.0F)/particleMaxAge * (float)Math.PI);
this.setAlphaF(0.6F - ((float)this.particleAge + partialTicks - 1.0F)/particleMaxAge * 0.5F);
float f5 = (float)(this.prevPosX + (this.posX - this.prevPosX) * (double)partialTicks - interpPosX);
@@ -6,9 +6,8 @@ import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
//@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT)
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.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
//@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT)
public class ParticleLeaf extends ParticleWizardry {
@@ -24,7 +23,7 @@ public class ParticleLeaf extends ParticleWizardry {
this.particleGravity = 0;
this.canCollide = true;
// 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
@@ -1,15 +1,12 @@
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.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.world.World;
import org.lwjgl.opengl.GL11;
public class ParticleLightning extends ParticleTargeted {
@@ -28,10 +25,6 @@ public class ParticleLightning extends ParticleTargeted {
/** Number of ticks to wait before the arc changes shape again. */
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){
super(world, x, y, z); // Does not have a texture!
seed = this.rand.nextLong();
@@ -40,6 +33,11 @@ public class ParticleLightning extends ParticleTargeted {
this.particleScale = 1;
}
@Override
public boolean shouldDisableDepth(){
return true;
}
@Override
public int getFXLayer(){
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.
// By using a seed, we can ensure the vertex positions and forks are identical a) for each layer, even
// though they are rendered sequentially, and b) across many frames (and ticks, if updateTime > 1).
Random 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.
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.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
//@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT)
public class ParticleLightningPulse extends ParticleWizardry {
@@ -28,6 +27,11 @@ public class ParticleLightningPulse extends ParticleWizardry {
this.yaw = 0;
}
@Override
public boolean shouldDisableDepth(){
return true;
}
@SubscribeEvent
public static void onTextureStitchEvent(TextureStitchEvent.Pre event){
for(ResourceLocation texture : TEXTURES){
@@ -1,10 +1,8 @@
package electroblob.wizardry.client.particle;
import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
//@SideOnly(Side.CLIENT)
public class ParticleMagicBubble extends ParticleWizardry {
public ParticleMagicBubble(World world, double x, double y, double z){
@@ -1,46 +1,36 @@
package electroblob.wizardry.client.particle;
import net.minecraft.client.particle.Particle;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.entity.Entity;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
//@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT)
public class ParticleMagicFlame extends ParticleWizardry {
/** The scale of the flame particle */
private float flameScale;
// 4 different animation strips, 8 frames in each strip
private static final ResourceLocation[][] TEXTURES = generateTextures("flame", 4, 8);
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.particleAlpha = 1;
this.particleMaxAge = (int)(2.0D / (Math.random() * 0.8D + 0.2D));
this.particleMaxAge = 12 + rand.nextInt(4);
this.shaded = false;
// IDEA: Make the particles for ray spells collide properly and not spawn on the other side of stuff.
this.canCollide = false;
this.setParticleTextureIndex(48);
this.canCollide = true;
}
@Override
public void renderParticle(BufferBuilder buffer, Entity entity, float partialTicks, float rotationX, float rotationZ,
float rotationYZ, float rotationXY, float rotationXZ){
float f6 = ((float)this.particleAge + partialTicks) / (float)this.particleMaxAge;
this.particleScale = this.flameScale * (1.0F - f6 * f6 * 0.5F);
super.renderParticle(buffer, entity, partialTicks, rotationX, rotationZ, rotationYZ, rotationXY, rotationXZ);
@SubscribeEvent
public static void onTextureStitchEvent(TextureStitchEvent.Pre event){
for(ResourceLocation[] array : TEXTURES){
for(ResourceLocation texture : array){
event.getMap().registerSprite(texture);
}
@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.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
//@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT)
public class ParticlePath extends ParticleWizardry {
@@ -34,6 +33,11 @@ public class ParticlePath extends ParticleWizardry {
this.setRBGColorF(1, 1, 1);
}
@Override
public boolean shouldDisableDepth(){
return true;
}
@Override
public void onUpdate(){
@@ -8,9 +8,8 @@ import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
//@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT)
public class ParticleScorch extends ParticleWizardry {
@@ -28,6 +27,11 @@ public class ParticleScorch extends ParticleWizardry {
this.shaded = false;
}
@Override
public boolean shouldDisableDepth(){
return true;
}
@Override
public void setRBGColorF(float r, float g, float b){
super.setRBGColorF(r, g, b);
@@ -48,7 +52,7 @@ public class ParticleScorch extends ParticleWizardry {
// Fading
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);
@@ -6,9 +6,8 @@ import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
//@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT)
public class ParticleSnow extends ParticleWizardry {
@@ -24,7 +23,7 @@ public class ParticleSnow extends ParticleWizardry {
this.canCollide = true;
this.setMaxAge(40 + rand.nextInt(10));
// 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
@@ -6,9 +6,8 @@ import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
//@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT)
public class ParticleSpark extends ParticleWizardry {
@@ -26,13 +25,17 @@ public class ParticleSpark extends ParticleWizardry {
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
// @Override
// public void applyGLStateChanges(){
// GlStateManager.enableBlend();
// GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
// // TESTME: Are these two actually necessary?
// GlStateManager.disableLighting();
// OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240, 240);
// }
@@ -6,9 +6,8 @@ import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
//@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT)
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,16 +1,16 @@
package electroblob.wizardry.client.particle;
import javax.annotation.Nullable;
import org.lwjgl.opengl.GL11;
import electroblob.wizardry.Wizardry;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.entity.Entity;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import org.lwjgl.opengl.GL11;
import javax.annotation.Nullable;
/** Superclass for particles with a second target entity or target position. */
public abstract class ParticleTargeted extends ParticleWizardry {
@@ -18,6 +18,11 @@ public abstract class ParticleTargeted extends ParticleWizardry {
protected double targetX;
protected double targetY;
protected double targetZ;
protected double targetVelX;
protected double targetVelY;
protected double targetVelZ;
protected double length;
/** The target this particle is linked to. The particle will stretch to touch this entity. */
@Nullable
@@ -34,42 +39,82 @@ public abstract class ParticleTargeted extends ParticleWizardry {
this.targetZ = z;
}
@Override
public void setTargetVelocity(double vx, double vy, double vz){
this.targetVelX = vx;
this.targetVelY = vy;
this.targetVelZ = vz;
}
@Override
public void setTargetEntity(Entity target){
this.target = target;
}
@Override
public void setLength(double length){
this.length = length;
}
@Override
public void onUpdate(){
super.onUpdate();
if(!Double.isNaN(targetVelX) && !Double.isNaN(targetVelY) && !Double.isNaN(targetVelZ)){
this.targetX += this.targetVelX;
this.targetY += this.targetVelY;
this.targetZ += this.targetVelZ;
}
}
@Override
public void renderParticle(BufferBuilder buffer, Entity viewer, float partialTicks, float rotationX, float rotationZ, float rotationYZ,
float rotationXY, float rotationXZ){
// Copied from ParticleWizardry, needs to be here since we're not calling super
updateEntityLinking(partialTicks);
float x = (float)(this.prevPosX + (this.posX - this.prevPosX) * (double)partialTicks);
float y = (float)(this.prevPosY + (this.posY - this.prevPosY) * (double)partialTicks);
float z = (float)(this.prevPosZ + (this.posZ - this.prevPosZ) * (double)partialTicks);
if(this.target != null){
this.targetX = this.target.posX;
this.targetY = this.target.getEntityBoundingBox().minY + target.height/2;
this.targetZ = this.target.posZ;
this.targetX = this.target.prevPosX + (this.target.posX - this.target.prevPosX) * partialTicks;
double correction = this.target.getEntityBoundingBox().minY - this.target.posY;
this.targetY = this.target.prevPosY + (this.target.posY - this.target.prevPosY) * partialTicks
+ target.height/2 + correction;
this.targetZ = this.target.prevPosZ + (this.target.posZ - this.target.prevPosZ) * partialTicks;
}else if(this.entity != null && this.length > 0){
Vec3d look = entity.getLook(partialTicks).scale(length);
this.targetX = x + look.x;
this.targetY = y + look.y;
this.targetZ = z + look.z;
}
if(Double.isNaN(targetX) || Double.isNaN(targetY) || Double.isNaN(targetZ)){
Wizardry.logger.warn("Attempted to render a targeted particle, but neither its target entity nor target"
+ "position was set!");
+ "position was set, and it either had no length assigned or was not linked to an entity!");
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.translate(x, y, z);
GlStateManager.translate(x - interpPosX, y - interpPosY, z - interpPosZ);
double dx = this.targetX - this.posX;
double dy = this.targetY - this.posY;
double dz = this.targetZ - this.posZ;
double dx = this.targetX - x;
double dy = this.targetY - y;
double dz = this.targetZ - z;
// No need for previous tick target positions and all that stuff since this is the only place they're used
// and interpolating like this works just as well
if(!Double.isNaN(targetVelX) && !Double.isNaN(targetVelY) && !Double.isNaN(targetVelZ)){
dx += partialTicks * this.targetVelX;
dy += partialTicks * this.targetVelY;
dz += partialTicks * this.targetVelZ;
}
// The distance from origin to endpoint
double length = Math.sqrt(dx*dx+dy*dy+dz*dz);
@@ -92,7 +137,7 @@ public abstract class ParticleTargeted extends ParticleWizardry {
* 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
* 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>
*
* @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.client.particle.ParticleDigging;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
//@SideOnly(Side.CLIENT)
public class ParticleTornado extends ParticleDigging {
private double angle;
private float angle;
private double radius;
private double speed;
/** Velocity of the tornado itself; in other words the velocity of the point the particle circles around. */
@@ -20,9 +19,9 @@ public class ParticleTornado extends ParticleDigging {
public ParticleTornado(World world, int maxAge, double originX, double originZ, double radius, double yPos,
double velX, double velZ, IBlockState block){
super(world, 0, 0, 0, 0, 0, 0, block);
this.angle = this.rand.nextDouble() * Math.PI * 2;
double x = originX - Math.cos(angle) * radius;
double z = originZ + radius * Math.sin(angle);
float angle = this.rand.nextFloat() * (float)Math.PI * 2;
double x = originX - MathHelper.cos(angle) * radius;
double z = originZ + radius * MathHelper.sin(angle);
this.radius = radius;
this.setPosition(x, yPos, z);
this.prevPosX = x;
@@ -66,8 +65,8 @@ public class ParticleTornado extends ParticleDigging {
// v = r times omega; therefore the normalised velocity vector needs to be r times the angle increment / 2 pi.
this.angle += omega;
this.motionZ = radius * omega * Math.cos(angle);
this.motionX = radius * omega * Math.sin(angle);
this.motionZ = radius * omega * MathHelper.cos(angle);
this.motionX = radius * omega * MathHelper.sin(angle);
this.move(motionX + velX, 0, motionZ + velZ);
if(this.particleAge > this.particleMaxAge / 2){
@@ -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;
import java.util.Arrays;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.ClientProxy;
import electroblob.wizardry.entity.ICustomHitbox;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.Minecraft;
import net.minecraft.client.particle.Particle;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.entity.Entity;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
/**
* Abstract superclass for all of wizardry's particles. This replaces {@code ParticleCustomTexture} (the functionality of
* which is no longer necessary since wizardry now uses {@code TextureAtlasSprite}s to do the rendering), and fits into
* {@code ParticleBuilder} by exposing all the necessary variables through getters, allowing them to be set on the fly
* rather than needing to be passed into the constructor.
* <p>
* <p></p>
* 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>
* - 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.
* <p>
* <p></p>
* This beauty of this system is that there are never any redundant parameters when spawning particles, since you can set
* as many or as few parameters as necessary - and in addition, common defaults don't need setting at all. For example,
* snow particles nearly always fall at the same speed, which can now be defined in the particle class and no longer
@@ -39,13 +45,25 @@ import net.minecraftforge.fml.relauncher.SideOnly;
* @since Wizardry 4.2.0
* @see electroblob.wizardry.util.ParticleBuilder ParticleBuilder
*/
@SideOnly(Side.CLIENT)
//@SideOnly(Side.CLIENT)
public abstract class ParticleWizardry extends Particle {
/** Implementation of animated particles using the TextureAtlasSprite system. Why vanilla doesn't support this I
* don't know, considering it too has animated particles. */
protected final TextureAtlasSprite[] sprites;
/** A long value used by the renderer as a random number seed, ensuring anything that is randomised remains the
* same across multiple frames. For example, lightning particles use this to keep their shape across ticks.
* This value can also be set during particle creation, allowing users to keep randomised properties the same
* even across multiple particles. If unspecified, the seed is chosen at random. */
protected long seed;
/** This particle's random number generator. All particles should use this in preference to any other random
* instance (like random), even if it isn't actually necessary to keep properties across frames. Note that
* if you <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. */
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. */
@Nullable
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;
/** Velocity of this particle relative to the linked entity. The relative x and z velocities are also used when
* the particle has spin to move the centre of rotation. If the linked entity is null and the particle is not
* spinning, these are not used and will be {@code NaN}. */
protected double relativeMotionX = Double.NaN, relativeMotionY = Double.NaN, relativeMotionZ = Double.NaN;
/** Velocity of this particle relative to the linked entity. If the linked entity is null, these are not used. */
protected double relativeMotionX, relativeMotionY, relativeMotionZ;
// Note that roll (equivalent to rotating the texture) is effectively handled by particleAngle - although that is
// actually the rotation speed and not the angle itself.
/** The yaw angle this particle is facing, or {@code NaN} if this particle always faces the viewer (default behaviour). */
@@ -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). */
protected float pitch = Float.NaN;
/** The fraction of the impact velocity that should be the maximum spread speed added on impact. */
private static final double SPREAD_FACTOR = 0.2;
/** Lateral velocity is reduced by this factor on impact, before adding random spread velocity. */
private static final double IMPACT_FRICTION = 0.2;
/** Previous-tick velocity, used in collision detection. */
private double prevVelX, prevVelY, prevVelZ;
/**
* Creates a new particle in the given world at the given position. All other parameters are set via the various
* setter methods ({@link electroblob.wizardry.util.ParticleBuilder ParticleBuilder} deals with all of that anyway).
@@ -108,7 +134,6 @@ public abstract class ParticleWizardry extends Particle {
this.setParticleTexture(sprites[0]);
}else{
sprites = new TextureAtlasSprite[0];
}
}
@@ -118,6 +143,15 @@ public abstract class ParticleWizardry extends Particle {
// Setters for parameters that affect all particles - these are implemented in this class (although they may be
// reimplemented in subclasses)
/** Sets the seed for this particle's randomly generated values and resets {@link ParticleWizardry#random} to use
* that seed. Implementations will differ between particle types; for example, ParticleLightning has an update
* period which changes the seed every few ticks, whereas ParticleVine simply retains the same seed for its entire
* lifetime. */
public void setSeed(long seed){
this.seed = seed;
this.random = new Random(seed);
}
/** Sets whether the particle should render at full brightness or not. True if the particle is shaded, false if
* the particle always renders at full brightness. Defaults to false.*/
public void setShaded(boolean shaded){
@@ -154,13 +188,10 @@ public abstract class ParticleWizardry extends Particle {
public void setSpin(double radius, double speed){
this.radius = radius;
this.speed = speed * 2 * Math.PI; // Converts rotations per tick into radians per tick for the trig functions
this.angle = this.rand.nextFloat() * (float)Math.PI * 2; // Random start angle 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
this.relativeX = radius * -MathHelper.cos(angle);
this.relativeZ = radius * MathHelper.sin(angle);
this.setPosition(posX + relativeX, posY, posZ + relativeZ);
this.prevPosX = posX;
this.prevPosZ = posZ;
this.posX = relativeX - radius * MathHelper.cos(angle);
this.posZ = relativeZ + radius * MathHelper.sin(angle);
// Set these to the correct values
this.relativeMotionX = motionX;
this.relativeMotionY = motionY;
@@ -180,6 +211,7 @@ public abstract class ParticleWizardry extends Particle {
this.prevPosX = this.posX;
this.prevPosY = this.posY;
this.prevPosZ = this.posZ;
// Set these to the correct values
this.relativeMotionX = motionX;
this.relativeMotionY = motionY;
this.relativeMotionZ = motionZ;
@@ -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.
* @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.
*/
public void setFacing(float yaw, float pitch){
@@ -240,6 +272,18 @@ public abstract class ParticleWizardry extends Particle {
// Does nothing for normal particles since normal particles always render at a single point
}
/**
* Sets the target point velocity for this particle. This will cause the position it stretches to touch to move
* at the given velocity. Has no effect unless {@link ParticleWizardry#setTargetVelocity(double, double, double)}
* is also used.
* @param vx The x velocity of the target point.
* @param vy The y velocity of the target point.
* @param vz The z velocity of the target point.
*/
public void setTargetVelocity(double vx, double vy, double vz){
// Does nothing for normal particles since normal particles always render at a single point
}
/**
* Links this particle to the given target. This will cause it to stretch to touch the target, if supported.
* @param target The target to link to.
@@ -248,6 +292,15 @@ public abstract class ParticleWizardry extends Particle {
// Does nothing for normal particles since normal particles always render at a single point
}
/**
* Sets the length of this particle. This will cause it to stretch to touch a point this distance along its
* linked entity's line of sight.
* @param length The length to set.
*/
public void setLength(double length){
// Does nothing for normal particles since normal particles always render at a single point
}
// ============================================== Method Overrides ==============================================
@Override
@@ -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.
* When facing directly up, will be equal to {@code -lookZ}. When facing directly down, will be equal to {@code lookZ}.
*/
// Fun fact: unlike entities, particles don't seem to bother checking the camera frustum...
@Override
public void renderParticle(BufferBuilder buffer, Entity viewer, float partialTicks, float lookZ, float lookY,
float lookX, float lookXY, float lookYZ){
updateEntityLinking(partialTicks);
if(Float.isNaN(this.yaw) || Float.isNaN(this.pitch)){
// Normal behaviour (rotates to face the viewer)
super.renderParticle(buffer, viewer, partialTicks, lookZ, lookY, lookX, lookXY, lookYZ);
drawParticle(buffer, viewer, partialTicks, lookZ, lookY, lookX, lookXY, lookYZ);
}else{
// Specific rotation
@@ -302,8 +356,26 @@ public abstract class ParticleWizardry extends Particle {
float rotationYZ = -rotationZ * MathHelper.sin(pitch * degToRadFactor);
float rotationXY = rotationX * MathHelper.sin(pitch * degToRadFactor);
drawParticle(buffer, viewer, partialTicks, rotationX, rotationY, rotationZ, rotationYZ, rotationXY);
}
}
/**
* Delegate function for {@link ParticleWizardry#renderParticle(BufferBuilder, Entity, float, float, float, float, float, float)};
* does the actual rendering. Subclasses should override this method instead of renderParticle. By default, this
* method simply calls super.renderParticle.
*/
protected void drawParticle(BufferBuilder buffer, Entity viewer, float partialTicks, float rotationX, float rotationY, float rotationZ, float rotationYZ, float rotationXY){
super.renderParticle(buffer, viewer, partialTicks, rotationX, rotationY, rotationZ, rotationYZ, rotationXY);
}
protected void updateEntityLinking(float partialTicks){
if(this.entity != null){
// This is kind of cheating but we know it's always a constant velocity so it works fine
prevPosX = posX + entity.prevPosX - entity.posX - relativeMotionX * (1-partialTicks);
prevPosY = posY + entity.prevPosY - entity.posY - relativeMotionY * (1-partialTicks);
prevPosZ = posZ + entity.prevPosZ - entity.posZ - relativeMotionZ * (1-partialTicks);
}
}
@Override
@@ -311,37 +383,42 @@ public abstract class ParticleWizardry extends Particle {
super.onUpdate();
// If any of these values is NaN, the particle has neither an entity nor a spin
if(!Double.isNaN(relativeMotionX) && !Double.isNaN(relativeMotionY) && !Double.isNaN(relativeMotionZ)){
if(this.canCollide && this.onGround){
// I reject your friction and substitute my own!
this.motionX /= 0.699999988079071D;
this.motionZ /= 0.699999988079071D;
}
// This allows velocity changes from entity linking and spin to stack
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
if(this.entity != null){
if(this.entity.isDead) this.setExpired();
this.setPosition(this.entity.posX + relativeX, this.entity.getEntityBoundingBox().minY + relativeY,
this.entity.posZ + relativeZ);
// Velocity is set so that the renderer will interpolate correctly between ticks
vx += this.entity.motionX;
vy += this.entity.motionY;
vz += this.entity.motionZ;
if(this.entity.isDead){
this.setExpired();
}else{
x += this.entity.posX;
y += this.entity.posY;
z += this.entity.posZ;
}
}
// Spin
if(radius > 0){
angle += speed;
vx += radius * speed * MathHelper.sin(angle);
vz += radius * speed * MathHelper.cos(angle);
// If the particle has spin, x/z relative position is used as centre and coords are changed each tick
x += radius * -MathHelper.cos(angle);
z += radius * MathHelper.sin(angle);
}
this.relativeX += vx;
this.relativeY += vy;
this.relativeZ += vz;
this.setPosition(x, y, z);
this.relativeX += relativeMotionX;
this.relativeY += relativeMotionY;
this.relativeZ += relativeMotionZ;
}
// Colour fading
@@ -357,8 +434,96 @@ public abstract class ParticleWizardry extends Particle {
// (which would probably otherwise happen if particleAge == particleMaxAge)
this.setParticleTexture(sprites[Math.min((int)(ageFraction * sprites.length), sprites.length - 1)]);
}
// Collision spreading
if(canCollide){
if(this.motionX == 0 && this.prevVelX != 0){ // If the particle just collided in x
// Reduce lateral velocity so the added spread speed actually has an effect
this.motionY *= IMPACT_FRICTION;
this.motionZ *= IMPACT_FRICTION;
// Add random velocity in y and z proportional to the impact velocity
this.motionY += (rand.nextDouble()*2 - 1) * this.prevVelX * SPREAD_FACTOR;
this.motionZ += (rand.nextDouble()*2 - 1) * this.prevVelX * SPREAD_FACTOR;
}
if(this.motionY == 0 && this.prevVelY != 0){ // If the particle just collided in y
// Reduce lateral velocity so the added spread speed actually has an effect
this.motionX *= IMPACT_FRICTION;
this.motionZ *= IMPACT_FRICTION;
// Add random velocity in x and z proportional to the impact velocity
this.motionX += (rand.nextDouble()*2 - 1) * this.prevVelY * SPREAD_FACTOR;
this.motionZ += (rand.nextDouble()*2 - 1) * this.prevVelY * SPREAD_FACTOR;
}
if(this.motionZ == 0 && this.prevVelZ != 0){ // If the particle just collided in z
// Reduce lateral velocity so the added spread speed actually has an effect
this.motionX *= IMPACT_FRICTION;
this.motionY *= IMPACT_FRICTION;
// Add random velocity in x and y proportional to the impact velocity
this.motionX += (rand.nextDouble()*2 - 1) * this.prevVelZ * SPREAD_FACTOR;
this.motionY += (rand.nextDouble()*2 - 1) * this.prevVelZ * SPREAD_FACTOR;
}
double searchRadius = 20;
List<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 ===============================================
/** 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;
}
/**
* 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
* references) in the client proxy to link particle enum types to actual particle classes. */
@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;
import java.util.Map.Entry;
import org.lwjgl.opengl.GL11;
import electroblob.wizardry.block.BlockStatue;
import electroblob.wizardry.client.ClientProxy;
import net.minecraft.client.Minecraft;
@@ -21,6 +17,7 @@ import net.minecraft.entity.EntityLivingBase;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import org.lwjgl.opengl.GL11;
/**
* Layer used to render the stone texture on a petrified creature. Handles dynamic tiling of the stone texture.
@@ -36,16 +33,14 @@ public class LayerStone implements LayerRenderer<EntityLivingBase> {
private static final ResourceLocation texture = new ResourceLocation("textures/blocks/stone.png");
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.
if(entry.getValue() instanceof RenderLivingBase){
if(renderer instanceof RenderLivingBase){
// Adds a stone layer to all the living entity renderers in the game. Whether it is actually rendered
// is decided in doRenderLayer below on a per-entity basis.
((RenderLivingBase<?>)entry.getValue()).addLayer(new LayerStone((RenderLivingBase<?>)entry.getValue()));
((RenderLivingBase<?>)renderer).addLayer(new LayerStone((RenderLivingBase<?>)renderer));
}
// NOTE: May have to do some special stuff for players if they are to be added; see
// Minecraft.getMinecraft().getRenderManager().getSkinMap()
}
}
@@ -55,12 +50,13 @@ public class LayerStone implements LayerRenderer<EntityLivingBase> {
}
// FIXME: Does not work with zombie pigmen, I have no idea why.
// I believe the issue is with the TESR actually, since LayerFrost works fine
@Override
public void doRenderLayer(EntityLivingBase entity, float limbSwing, float limbSwingAmount, float partialTicks,
float ageInTicks, float netHeadYaw, float headPitch, float scale){
if(entity.getEntityData().getBoolean(BlockStatue.NBT_KEY)){
if(entity.getEntityData().getBoolean(BlockStatue.PETRIFIED_NBT_KEY)){
GlStateManager.enableLighting();
int i = this.getBlockBrightnessForEntity(entity, partialTicks);
@@ -108,7 +104,6 @@ public class LayerStone implements LayerRenderer<EntityLivingBase> {
GlStateManager.pushMatrix();
// Enables tiling (Also used for guardian beam, beacon beam and ender crystal beam)
// TODO: Backport this improvement
GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT);
GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT);
@@ -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;
import org.lwjgl.opengl.GL11;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.tileentity.ContainerArcaneWorkbench;
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.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper;
import org.lwjgl.opengl.GL11;
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.rotate(180, 0F, 0F, 1F);
GlStateManager.pushMatrix();
double angle = 0.0d;
double angle;
if(x < -0.5){
angle = Math.toDegrees(Math.atan((z + 0.5) / (x + 0.5))) + 180;
}else{
angle = Math.toDegrees(Math.atan((z + 0.5) / (x + 0.5)));
}
this.renderEffect(tileentity);
this.renderWand(tileentity, angle);
this.renderEffect(tileentity, partialTicks);
this.renderWand(tileentity, angle, partialTicks);
GlStateManager.popMatrix();
GlStateManager.popMatrix();
}
private void renderEffect(TileEntityArcaneWorkbench tileentity){
private void renderEffect(TileEntityArcaneWorkbench tileentity, float partialTicks){
ItemStack itemstack = tileentity.getStackInSlot(ContainerArcaneWorkbench.CENTRE_SLOT);
@@ -52,7 +52,7 @@ public class RenderArcaneWorkbench extends TileEntitySpecialRenderer<TileEntityA
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f);
GlStateManager.enableBlend();
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);
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
@@ -76,7 +76,8 @@ public class RenderArcaneWorkbench extends TileEntitySpecialRenderer<TileEntityA
*
* @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);
if(!stack.isEmpty()){
@@ -87,8 +88,8 @@ public class RenderArcaneWorkbench extends TileEntitySpecialRenderer<TileEntityA
GlStateManager.rotate(180, 0, 1, 0);
GlStateManager.rotate((float)(viewAngle - 90f), 0, 0, 1);
// Does the floaty thing
GlStateManager.translate(0.0F, 0.0F, (float)tileentity.yOffset / 5000.0F + 0.55f);
// Does the floaty thing <- #bestcommentever
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);
// This is what the item frame uses so it's definitely what we want.
Minecraft.getMinecraft().getRenderItem().renderItem(stack, TransformType.FIXED);
@@ -1,22 +1,18 @@
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.entity.construct.EntityBlackHole;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.*;
import net.minecraft.client.renderer.entity.Render;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper;
import org.lwjgl.opengl.GL11;
import java.util.ArrayList;
import java.util.Collections;
public class RenderBlackHole extends Render<EntityBlackHole> {
@@ -81,21 +77,21 @@ public class RenderBlackHole extends Render<EntityBlackHole> {
int sliceAngle = 20 + a;
double x1 = scale * Math.sin((blackhole.ticksExisted + 40 * j) * (Math.PI / 180));
// double y1 = 0.7*Math.cos((blackhole.timer - 40*j)*(Math.PI/180))*j/10;
double z1 = scale * Math.cos((blackhole.ticksExisted + 40 * j) * (Math.PI / 180));
double x1 = scale * MathHelper.sin((blackhole.ticksExisted + 40 * j) * ((float)Math.PI / 180f));
// double y1 = 0.7*MathHelper.cos((blackhole.timer - 40*j)*(Math.PI/180))*j/10;
double z1 = scale * MathHelper.cos((blackhole.ticksExisted + 40 * j) * ((float)Math.PI / 180));
double x2 = scale * Math.sin((blackhole.ticksExisted + 40 * j - sliceAngle) * (Math.PI / 180));
// double y2 = 0.7*Math.sin((blackhole.timer - 40*j)*(Math.PI/180))*j/10;
double z2 = scale * Math.cos((blackhole.ticksExisted + 40 * j - sliceAngle) * (Math.PI / 180));
double x2 = scale * MathHelper.sin((blackhole.ticksExisted + 40 * j - sliceAngle) * ((float)Math.PI / 180));
// double y2 = 0.7*MathHelper.sin((blackhole.timer - 40*j)*(Math.PI/180))*j/10;
double z2 = scale * MathHelper.cos((blackhole.ticksExisted + 40 * j - sliceAngle) * ((float)Math.PI / 180));
double absoluteX = x1 * Math.cos(31 * b);
double absoluteY = z1 * Math.sin(31 * a) + x1 * Math.cos(31 * a) * Math.sin(31 * b);
double absoluteZ = z1 * Math.cos(31 * a);
double absoluteX = x1 * MathHelper.cos(31 * b);
double absoluteY = z1 * MathHelper.sin(31 * a) + x1 * MathHelper.cos(31 * a) * MathHelper.sin(31 * b);
double absoluteZ = z1 * MathHelper.cos(31 * a);
double absoluteX2 = x2 * Math.cos(31 * b);
double absoluteY2 = z2 * Math.sin(31 * a) + x2 * Math.cos(31 * a) * Math.sin(31 * b);
double absoluteZ2 = z2 * Math.cos(31 * a);
double absoluteX2 = x2 * MathHelper.cos(31 * b);
double absoluteY2 = z2 * MathHelper.sin(31 * a) + x2 * MathHelper.cos(31 * a) * MathHelper.sin(31 * b);
double absoluteZ2 = z2 * MathHelper.cos(31 * a);
/* buffer.begin(0, DefaultVertexFormats.POSITION_TEX);
*
* tessellator.setColorOpaque(255, 255, 255); GL11.glPointSize(5);
@@ -1,7 +1,5 @@
package electroblob.wizardry.client.renderer;
import org.lwjgl.opengl.GL11;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.entity.construct.EntityBubble;
import electroblob.wizardry.util.WizardryUtilities;
@@ -14,6 +12,7 @@ import net.minecraft.client.renderer.entity.Render;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.opengl.GL11;
public class RenderBubble extends Render<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;
import org.lwjgl.opengl.GL11;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.entity.construct.EntityDecay;
import net.minecraft.client.renderer.BufferBuilder;
@@ -12,6 +10,7 @@ import net.minecraft.client.renderer.entity.Render;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.opengl.GL11;
public class RenderDecay extends Render<EntityDecay> {
@@ -1,8 +1,5 @@
package electroblob.wizardry.client.renderer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.entity.living.EntityDecoy;
import net.minecraft.client.model.ModelBiped;
@@ -12,10 +9,11 @@ import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.entity.Entity;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.relauncher.ReflectionHelper;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
//@SideOnly(Side.CLIENT)
public class RenderDecoy extends RenderBiped<EntityDecoy> {
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.layers.LayerBipedArmor;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
//@SideOnly(Side.CLIENT)
public class RenderEvilWizard extends RenderBiped<EntityEvilWizard> {
static final ResourceLocation[] textures = new ResourceLocation[6];

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