Compare commits

...

30 Commits

Author SHA1 Message Date
Electroblob77 0328f41564 Prevent potion core applying its health fix (whatever that is) when resurrecting, fixes #178 2020-01-28 21:19:37 +00:00
Electroblob77 9ab4684aef Update version number 2020-01-28 19:44:27 +00:00
Electroblob77 e64cf9a6af Update credits 2020-01-28 18:44:37 +00:00
Electroblob77 f13f220564 Change tome of arcana description to be more accurate, closes #330. Also adds format args support to multi-line item descriptions and fixes the 1st argument being used instead of the 2nd in the lang files. 2020-01-28 18:36:34 +00:00
Electroblob77 311b5750df Add parrying mechanic to custom fireballs, fixes #335 2020-01-28 18:14:17 +00:00
Electroblob77 e54aee332d Rework slow time to use an NBT tag to unblock entity updates when not in a slow time zone, fixes #336 (and related issues with teleporting)
It also simplifies the code and removes the end slow time packet entirely
2020-01-28 13:20:32 +00:00
Electroblob77 e4bf09f15a Implement client-side spell counter for NPCs, fixes #345
Also makes all the continuous-spell-related methods in ISpellCaster default methods so they don't need implementing unless actually necessary.
2020-01-27 23:27:56 +00:00
Electroblob77 aa166814ea Add SpellThrowable and replace Snowball class with a simple instance of it 2020-01-27 18:40:10 +00:00
Electroblob77 d37ab6072f Add chainable method for assigning spell NPC selector 2020-01-27 17:51:32 +00:00
Electroblob77 ba83cb7985 Add chainable method for assigning spell items 2020-01-27 16:43:05 +00:00
Electroblob77 3052e56d03 Add entity- and tileentity-sensitive versions of Spell#canBeCastByNPCs and Spell#canBeCastByDispensers 2020-01-27 16:42:28 +00:00
Electroblob77 b682740751 *facepalm* 2020-01-27 15:09:54 +00:00
Electroblob77 38429cb697 Update credits 2020-01-27 14:50:55 +00:00
Electroblob77 dc7b6790b8 Automatic spell sound creation, hurrah! 2020-01-27 14:39:47 +00:00
Electroblob77 7a247df3e1 Encourage users to report the circular NBT reference error 2020-01-27 14:33:45 +00:00
Electroblob77 de2087c0e8 Clean up after testing 2020-01-27 13:46:50 +00:00
Electroblob77 f5da195605 Wrap NBTTagCompound#setTag in static helper with safeguards against circular references, fixes #299 2020-01-27 13:45:47 +00:00
Electroblob77 6cd6411c25 Use target lifetime rather than construct lifetime when dealing periodic damage; means targets don't all get damaged at once 2020-01-25 22:50:45 +00:00
Electroblob77 40e77728ad Do proper floaty division, the error was kinda invisible (pun intended) but intellij spotted it 2020-01-21 20:16:59 +00:00
Electroblob77 d50ff5729f Prevent unlimited exponential stacking of the empowerment effect, fixes #317 2020-01-16 18:40:52 +00:00
Electroblob77 76b4f2f72f Change default behaviour of Spell#createSoundWithSuffix to point to assets/modid/sounds.json rather than assets/ebwizardry/sounds.json, so addons don't have to override it 2020-01-14 14:39:09 +00:00
Electroblob77 a608375433 Merge branch '1.12.2' of https://github.com/Electroblob77/Wizardry into 1.12.2 2020-01-13 18:59:19 +00:00
Electroblob77 c3f8ba245d Fixing the really important bugs
(What? That pixel was annoying me...)
2020-01-13 18:58:21 +00:00
Electroblob77 34b3fe93bb Bit of an oversight there, this is supposed to be an API after all 2020-01-13 18:55:36 +00:00
Electroblob77 996ec9a7f7 Electroblob hit the ground too hard 2020-01-13 18:54:43 +00:00
Electroblob 2b38ca9a89 Merge pull request #331 from bigenergy/patch-2
Update russian lang
2019-12-31 11:02:52 +00:00
Big Energy 67788a2bd8 Ring translation 2019-12-27 20:23:35 +03:00
Big Energy 58fcaa8af2 Update ru_ru.lang 2019-12-27 20:09:30 +03:00
Big Energy 24e89c8b8a Update ru_ru.lang 2019-12-27 20:01:35 +03:00
Big Energy 1293b89905 Update ru_ru.lang 2019-12-27 19:55:41 +03:00
99 changed files with 826 additions and 408 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ assignees: ''
Please read the [guide for contributing](https://github.com/Electroblob77/Wizardry/blob/1.12.2/CONTRIBUTING.md) before posting. Please read the [guide for contributing](https://github.com/Electroblob77/Wizardry/blob/1.12.2/CONTRIBUTING.md) before posting.
Minecraft version: 1.12.2 [change as necessary] Minecraft version: 1.12.2 [change as necessary]
Wizardry version: 4.2.7 [change as necessary] Wizardry version: 4.2.8 [change as necessary]
Environment: Singleplayer/LAN game/Server [delete as necessary] Environment: Singleplayer/LAN game/Server [delete as necessary]
Issue details: [describe what you were doing when the issue occurred, what went wrong, what you expected to happen, etc.] Issue details: [describe what you were doing when the issue occurred, what went wrong, what you expected to happen, etc.]
+1 -1
View File
@@ -10,7 +10,7 @@ assignees: ''
Please read the [guide for contributing](https://github.com/Electroblob77/Wizardry/blob/1.12.2/CONTRIBUTING.md) before posting. Please read the [guide for contributing](https://github.com/Electroblob77/Wizardry/blob/1.12.2/CONTRIBUTING.md) before posting.
Minecraft version: 1.12.2 [change as necessary] Minecraft version: 1.12.2 [change as necessary]
Wizardry version: 4.2.7 [change as necessary] Wizardry version: 4.2.8 [change as necessary]
Question details: [explain your question here] Question details: [explain your question here]
+8 -7
View File
@@ -1,7 +1,7 @@
# Credits # Credits
Electroblob's Wizardry Electroblob's Wizardry
Version 4.2.7 Version 4.2.8
For Minecraft 1.12.2 For Minecraft 1.12.2
Designed, coded and textured by Electroblob Designed, coded and textured by Electroblob
@@ -23,15 +23,16 @@ In addition, I'd like to thank the following individuals for their contributions
- Aeronica - Aeronica
- UltraHex - UltraHex
- Azim-Palmer - Azim-Palmer
- raoulvdberge
#### Translations #### Translations
- Spanish and Mexican Spanish: MadWrist - Spanish and Mexican Spanish: MadWrist
- Russian: VilagVil, kellixon - Russian: VilagVil, kellixon, bigenergy
- French: Hahdrim - French: Hahdrim
- Brazilian Portuguese: lorrampi - Brazilian Portuguese: lorrampi
- Chinese: ZHENGLOC, dragon-evol - Chinese: ZHENGLOC, dragon-evol
- Korean: shejery, rewi_wire - Korean: shejery, rewi_wire
- Polish: Trozuu - Polish: Trozuu
Lightning ray sound effect from OhhWowProductions Lightning ray sound effect from OhhWowProductions
+1 -1
View File
@@ -1,7 +1,7 @@
Please read the [guide for contributing](https://github.com/Electroblob77/Wizardry/blob/1.12.2/guide_for_contributing.md) before posting. Please read the [guide for contributing](https://github.com/Electroblob77/Wizardry/blob/1.12.2/guide_for_contributing.md) before posting.
Minecraft version: 1.12.2 [change as necessary] Minecraft version: 1.12.2 [change as necessary]
Wizardry version: 4.2.7 [change as necessary] Wizardry version: 4.2.8 [change as necessary]
Environment: Singleplayer/LAN game/Server [delete as necessary] Environment: Singleplayer/LAN game/Server [delete as necessary]
Issue details: [describe what you were doing when the issue occurred, what went wrong, what you expected to happen, etc.] Issue details: [describe what you were doing when the issue occurred, what went wrong, what you expected to happen, etc.]
+1 -1
View File
@@ -11,7 +11,7 @@ apply plugin: 'net.minecraftforge.gradle.forge'
//Only edit below this line, the above code adds and enables the necessary things for Forge to be setup. //Only edit below this line, the above code adds and enables the necessary things for Forge to be setup.
version = "4.2.7" // There, it matches semver, happy now? version = "4.2.8" // There, it matches semver, happy now?
group= "electroblob.wizardry"// http://maven.apache.org/guides/mini/guide-naming-conventions.html group= "electroblob.wizardry"// http://maven.apache.org/guides/mini/guide-naming-conventions.html
archivesBaseName = "ElectroblobsWizardry" archivesBaseName = "ElectroblobsWizardry"
@@ -100,9 +100,9 @@ public class CommonProxy {
return ((ItemSpectralBow)WizardryItems.spectral_bow).getDefaultDurabilityForDisplay(stack); return ((ItemSpectralBow)WizardryItems.spectral_bow).getDefaultDurabilityForDisplay(stack);
} }
/** Like {@link CommonProxy#addMultiLineDescription(List, String, Style)}, but style defaults to light grey. */ /** Like {@link CommonProxy#addMultiLineDescription(List, String, Style, Object...)}, but style defaults to light grey. */
public void addMultiLineDescription(List<String> tooltip, String key){ public void addMultiLineDescription(List<String> tooltip, String key, Object... args){
this.addMultiLineDescription(tooltip, key, new Style().setColor(TextFormatting.GRAY)); this.addMultiLineDescription(tooltip, key, new Style().setColor(TextFormatting.GRAY), args);
} }
/** /**
@@ -113,7 +113,7 @@ public class CommonProxy {
* @param key The translation key for the description * @param key The translation key for the description
* @param style A style to apply * @param style A style to apply
*/ */
public void addMultiLineDescription(List<String> tooltip, String key, Style style){} public void addMultiLineDescription(List<String> tooltip, String key, Style style, Object... args){}
// SECTION Packet Handlers // SECTION Packet Handlers
// =============================================================================================================== // ===============================================================================================================
@@ -140,8 +140,6 @@ public class CommonProxy {
public void handleAdvancementSyncPacket(PacketSyncAdvancements.Message message){} public void handleAdvancementSyncPacket(PacketSyncAdvancements.Message message){}
public void handleEndSlowTimePacket(PacketEndSlowTime.Message message){}
public void handleResurrectionPacket(PacketResurrection.Message message){} public void handleResurrectionPacket(PacketResurrection.Message message){}
public void handlePossessionPacket(PacketPossession.Message message){} public void handlePossessionPacket(PacketPossession.Message message){}
@@ -433,7 +433,7 @@ public final class Settings {
Property property; Property property;
for(Spell spell : Spell.getSpells(Spell.allSpells)){ for(Spell spell : Spell.getAllSpells()){
property = config.get(SPELLS_CATEGORY, spell.getRegistryName().toString(), true, property = config.get(SPELLS_CATEGORY, spell.getRegistryName().toString(), true,
I18n.translateToLocal("spell." + spell.getUnlocalisedName() + ".desc")); I18n.translateToLocal("spell." + spell.getUnlocalisedName() + ".desc"));
// Uses the same config key as the spell name, because - well, that's what it's called! // Uses the same config key as the spell name, because - well, that's what it's called!
@@ -64,7 +64,7 @@ public class Wizardry {
* 1.x.x represents Minecraft 1.7.x versions, 2.x.x represents Minecraft 1.10.x versions, 3.x.x represents Minecraft * 1.x.x represents Minecraft 1.7.x versions, 2.x.x represents Minecraft 1.10.x versions, 3.x.x represents Minecraft
* 1.11.x versions, and so on. * 1.11.x versions, and so on.
*/ */
public static final String VERSION = "4.2.7"; public static final String VERSION = "4.2.8";
// IDEA: Triggering of inbuilt Forge events in relevant places? // IDEA: Triggering of inbuilt Forge events in relevant places?
// IDEA: Abstract the vanilla particles behind the particle builder // IDEA: Abstract the vanilla particles behind the particle builder
@@ -347,18 +347,21 @@ public final class WizardryEventHandler {
Spell spell = ((ISpellCaster)event.getEntity()).getContinuousSpell(); Spell spell = ((ISpellCaster)event.getEntity()).getContinuousSpell();
SpellModifiers modifiers = ((ISpellCaster)event.getEntity()).getModifiers(); SpellModifiers modifiers = ((ISpellCaster)event.getEntity()).getModifiers();
int count = ((ISpellCaster)event.getEntity()).getSpellCounter();
if(spell != null && spell != Spells.none){ // IntelliJ is wrong, do NOT remove the null check! if(spell != null && spell != Spells.none){ // IntelliJ is wrong, do NOT remove the null check!
if(!MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Tick(SpellCastEvent.Source.NPC, spell, event.getEntityLiving(), if(!MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Tick(SpellCastEvent.Source.NPC, spell, event.getEntityLiving(),
modifiers, 0))){ modifiers, count))){
spell.cast(event.getEntity().world, (EntityLiving)event.getEntity(), EnumHand.MAIN_HAND, 0, spell.cast(event.getEntity().world, (EntityLiving)event.getEntity(), EnumHand.MAIN_HAND, count,
// TODO: This implementation of modifiers relies on them being accessible client-side. // TODO: This implementation of modifiers relies on them being accessible client-side.
// Right now that doesn't matter because NPCs don't use modifiers, but they might in future // Right now that doesn't matter because NPCs don't use modifiers, but they might in future
((EntityLiving)event.getEntity()).getAttackTarget(), modifiers); ((EntityLiving)event.getEntity()).getAttackTarget(), modifiers);
} }
} }
((ISpellCaster)event.getEntity()).setSpellCounter(count + 1);
} }
} }
} }
@@ -464,7 +467,8 @@ public final class WizardryEventHandler {
// Wizardry.logger.info("Replaced fall distance {} with effective distance {} based on entity velocity", event.getDistance(), y); // Wizardry.logger.info("Replaced fall distance {} with effective distance {} based on entity velocity", event.getDistance(), y);
// } // }
event.setDistance((float)y); // Don't let it increase fall damage beyond vanilla values
if(y < event.getDistance()) event.setDistance((float)y);
} }
} }
} }
@@ -107,7 +107,7 @@ public class ClientProxy extends CommonProxy {
// Armour Model // Armour Model
public static final ModelBiped WIZARD_ARMOUR_MODEL = new ModelWizardArmour(0.75f); public static final ModelBiped WIZARD_ARMOUR_MODEL = new ModelWizardArmour(0.75f);
/** The wrap width for standard multi-line descriptions (see {@link ClientProxy#addMultiLineDescription(List, String, Style)}). */ /** The wrap width for standard multi-line descriptions (see {@link ClientProxy#addMultiLineDescription(List, String, Style, Object...)}). */
private static final int TOOLTIP_WRAP_WIDTH = 140; private static final int TOOLTIP_WRAP_WIDTH = 140;
// SECTION Registry // SECTION Registry
@@ -287,8 +287,8 @@ public class ClientProxy extends CommonProxy {
} }
@Override @Override
public void addMultiLineDescription(List<String> tooltip, String key, Style style){ public void addMultiLineDescription(List<String> tooltip, String key, Style style, Object... args){
String description = style.getFormattingCode() + I18n.format(key); String description = style.getFormattingCode() + I18n.format(key, args);
tooltip.addAll(Minecraft.getMinecraft().fontRenderer.listFormattedStringToWidth(description, TOOLTIP_WRAP_WIDTH)); tooltip.addAll(Minecraft.getMinecraft().fontRenderer.listFormattedStringToWidth(description, TOOLTIP_WRAP_WIDTH));
} }
@@ -434,6 +434,7 @@ public class ClientProxy extends CommonProxy {
if(caster instanceof ISpellCaster){ if(caster instanceof ISpellCaster){
if(spell.isContinuous || spell instanceof None){ if(spell.isContinuous || spell instanceof None){
((ISpellCaster)caster).setContinuousSpell(spell); ((ISpellCaster)caster).setContinuousSpell(spell);
((ISpellCaster)caster).setSpellCounter(spell instanceof None ? 0 : 1);
((EntityLiving)caster).setAttackTarget((EntityLivingBase)target); ((EntityLiving)caster).setAttackTarget((EntityLivingBase)target);
} }
} }
@@ -547,7 +548,7 @@ public class ClientProxy extends CommonProxy {
data.randomNames = new HashMap<>(); data.randomNames = new HashMap<>();
data.randomDescriptions = new HashMap<>(); data.randomDescriptions = new HashMap<>();
for(Spell spell : Spell.getSpells(Spell.allSpells)){ for(Spell spell : Spell.getAllSpells()){
// -1 because the none spell isn't included // -1 because the none spell isn't included
// This is a case where we must use the network ID, not the metadata // 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.randomNames.put(spell, message.names.get(spell.networkID() - 1));
@@ -574,13 +575,6 @@ public class ClientProxy extends CommonProxy {
GuiWizardHandbook.updateUnlockStatus(message.showToasts, message.completedAdvancements); 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 @Override
public void handleResurrectionPacket(PacketResurrection.Message message){ public void handleResurrectionPacket(PacketResurrection.Message message){
Entity entity = Minecraft.getMinecraft().world.getEntityByID(message.playerID); Entity entity = Minecraft.getMinecraft().world.getEntityByID(message.playerID);
@@ -9,6 +9,7 @@ import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.item.ISpellCastingItem; import electroblob.wizardry.item.ISpellCastingItem;
import electroblob.wizardry.item.ItemArtefact; import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.item.ItemSpectralBow; import electroblob.wizardry.item.ItemSpectralBow;
import electroblob.wizardry.potion.PotionSlowTime;
import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.registry.WizardryPotions;
@@ -153,6 +154,7 @@ public final class WizardryClientEventHandler {
} }
SpellEmitterData.update(world); SpellEmitterData.update(world);
PotionSlowTime.cleanUpEntities(world);
} }
} }
@@ -31,7 +31,6 @@ import net.minecraft.inventory.Slot;
import net.minecraft.item.Item; import net.minecraft.item.Item;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.text.TextFormatting; import net.minecraft.util.text.TextFormatting;
import net.minecraftforge.client.event.TextureStitchEvent; import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
@@ -525,30 +525,42 @@ public abstract class ParticleWizardry extends Particle {
// =============================================== Helper Methods =============================================== // =============================================== Helper Methods ===============================================
/** Internal overload for {@link ParticleWizardry#generateTextures(String, String, int)} which uses wizardry's mod
* ID automatically. */
public static ResourceLocation[] generateTextures(String stem, int n){
return generateTextures(Wizardry.MODID, stem, n);
}
/** Static helper method that generates an array of n ResourceLocations using the particle file naming convention, /** Static helper method that generates an array of n ResourceLocations using the particle file naming convention,
* which is the given stem plus an underscore plus the integer index. */ * which is the given stem plus an underscore plus the integer index. */
public static ResourceLocation[] generateTextures(String stem, int n){ public static ResourceLocation[] generateTextures(String modID, String stem, int n){
ResourceLocation[] textures = new ResourceLocation[n]; ResourceLocation[] textures = new ResourceLocation[n];
for(int i=0; i<n; i++){ for(int i=0; i<n; i++){
textures[i] = new ResourceLocation(Wizardry.MODID, "particle/" + stem + "_" + i); textures[i] = new ResourceLocation(modID, "particle/" + stem + "_" + i);
} }
return textures; return textures;
} }
/** Internal overload for {@link ParticleWizardry#generateTextures(String, String, int, int)} which uses wizardry's
* mod ID automatically. */
public static ResourceLocation[][] generateTextures(String stem, int m, int n){
return generateTextures(Wizardry.MODID, stem, m, n);
}
/** Static helper method that generates a 2D m x n array of ResourceLocations using the particle file naming /** Static helper method that generates a 2D m x n array of ResourceLocations using the particle file naming
* convention, which is the given stem plus an underscore plus the first index, plus an underscore plus the second * convention, which is the given stem plus an underscore plus the first index, plus an underscore plus the second
* index. Useful for animated particles that also pick a random animation strip. */ * index. Useful for animated particles that also pick a random animation strip. */
public static ResourceLocation[][] generateTextures(String stem, int m, int n){ public static ResourceLocation[][] generateTextures(String modID, String stem, int m, int n){
ResourceLocation[][] textures = new ResourceLocation[m][n]; ResourceLocation[][] textures = new ResourceLocation[m][n];
for(int i=0; i<m; i++){ for(int i=0; i<m; i++){
for(int j=0; j<n; j++){ for(int j=0; j<n; j++){
textures[i][j] = new ResourceLocation(Wizardry.MODID, "particle/" + stem + "_" + i + "_" + j); textures[i][j] = new ResourceLocation(modID, "particle/" + stem + "_" + i + "_" + j);
} }
} }
@@ -190,7 +190,8 @@ public class CommandCastSpell extends CommandBase {
} }
if(spell.isContinuous){ if(spell.isContinuous){
// We need not query Spell#canBeCastByDispensers since with commands there's no difference between
// a spell that can't be cast positionally and one that can be cast positionally but fails
if(spell.cast(world, origin.x, origin.y, origin.z, direction, 0, duration, modifiers)){ if(spell.cast(world, origin.x, origin.y, origin.z, direction, 0, duration, modifiers)){
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.COMMAND, spell, world, origin.x, origin.y, origin.z, direction, modifiers)); MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.COMMAND, spell, world, origin.x, origin.y, origin.z, direction, modifiers));
@@ -113,7 +113,7 @@ public class CommandDiscoverSpell extends CommandBase {
if(server.sendCommandFeedback()) sender.sendMessage( if(server.sendCommandFeedback()) sender.sendMessage(
new TextComponentTranslation("commands." + Wizardry.MODID + ":discoverspell.clear", player.getName())); new TextComponentTranslation("commands." + Wizardry.MODID + ":discoverspell.clear", player.getName()));
}else if(all){ }else if(all){
data.spellsDiscovered.addAll(Spell.getSpells(Spell.allSpells)); data.spellsDiscovered.addAll(Spell.getAllSpells());
if(server.sendCommandFeedback()) sender.sendMessage( if(server.sendCommandFeedback()) sender.sendMessage(
new TextComponentTranslation("commands." + Wizardry.MODID + ":discoverspell.all", player.getName())); new TextComponentTranslation("commands." + Wizardry.MODID + ":discoverspell.all", player.getName()));
}else{ }else{
@@ -4,6 +4,7 @@ import electroblob.wizardry.Wizardry;
import electroblob.wizardry.data.SpellEmitterData; import electroblob.wizardry.data.SpellEmitterData;
import electroblob.wizardry.event.SpellCastEvent; import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.spell.Spell; import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.NBTExtras;
import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.SpellModifiers;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagCompound;
@@ -129,7 +130,7 @@ public class SpellEmitter implements ITickable {
nbt.setDouble("z", z); nbt.setDouble("z", z);
nbt.setInteger("direction", direction.getIndex()); nbt.setInteger("direction", direction.getIndex());
nbt.setInteger("duration", duration); nbt.setInteger("duration", duration);
nbt.setTag("modifiers", modifiers.toNBT()); NBTExtras.storeTagSafely(nbt, "modifiers", modifiers.toNBT());
nbt.setInteger("castingTick", castingTick); nbt.setInteger("castingTick", castingTick);
return nbt; return nbt;
@@ -7,6 +7,7 @@ import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.spell.None; import electroblob.wizardry.spell.None;
import electroblob.wizardry.spell.Spell; import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.NBTExtras;
import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntity;
@@ -141,7 +142,7 @@ public abstract class BlockCastingData<T extends TileEntity> implements INBTSeri
nbt.setInteger("spell", spell.metadata()); nbt.setInteger("spell", spell.metadata());
nbt.setInteger("castingTick", castingTick); nbt.setInteger("castingTick", castingTick);
nbt.setTag("modifiers", modifiers.toNBT()); NBTExtras.storeTagSafely(nbt, "modifiers", modifiers.toNBT());
return nbt; return nbt;
} }
@@ -1,5 +1,6 @@
package electroblob.wizardry.data; package electroblob.wizardry.data;
import electroblob.wizardry.util.NBTExtras;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
@@ -89,7 +90,7 @@ public interface IStoredVariable<T> extends IVariable<T> {
@Override @Override
public void write(NBTTagCompound nbt, T value){ public void write(NBTTagCompound nbt, T value){
if(value != null) nbt.setTag(key, serialiser.apply(value)); if(value != null) NBTExtras.storeTagSafely(nbt, key, serialiser.apply(value));
} }
@Override @Override
@@ -91,7 +91,7 @@ public class SpellEmitterData extends WorldSavedData {
@Override @Override
public NBTTagCompound writeToNBT(NBTTagCompound compound){ public NBTTagCompound writeToNBT(NBTTagCompound compound){
compound.setTag("emitters", NBTExtras.listToNBT(emitters, SpellEmitter::toNBT)); NBTExtras.storeTagSafely(compound, "emitters", NBTExtras.listToNBT(emitters, SpellEmitter::toNBT));
return compound; return compound;
} }
@@ -4,6 +4,7 @@ import electroblob.wizardry.Wizardry;
import electroblob.wizardry.packet.PacketGlyphData; import electroblob.wizardry.packet.PacketGlyphData;
import electroblob.wizardry.packet.WizardryPacketHandler; import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.spell.Spell; import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.NBTExtras;
import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList; import net.minecraft.nbt.NBTTagList;
@@ -43,11 +44,11 @@ public class SpellGlyphData extends WorldSavedData {
/** Generates random names and descriptions for any spells which don't already have them. */ /** Generates random names and descriptions for any spells which don't already have them. */
public void generateGlyphNames(World world){ public void generateGlyphNames(World world){
for(Spell spell : Spell.getSpells(Spell.allSpells)){ for(Spell spell : Spell.getAllSpells()){
if(!randomNames.containsKey(spell)) randomNames.put(spell, generateRandomName(world.rand)); if(!randomNames.containsKey(spell)) randomNames.put(spell, generateRandomName(world.rand));
} }
for(Spell spell : Spell.getSpells(Spell.allSpells)){ for(Spell spell : Spell.getAllSpells()){
if(!randomDescriptions.containsKey(spell)) if(!randomDescriptions.containsKey(spell))
randomDescriptions.put(spell, generateRandomDescription(world.rand)); randomDescriptions.put(spell, generateRandomDescription(world.rand));
} }
@@ -161,7 +162,7 @@ public class SpellGlyphData extends WorldSavedData {
NBTTagList tagList = new NBTTagList(); NBTTagList tagList = new NBTTagList();
for(Spell spell : Spell.getSpells(Spell.allSpells)){ for(Spell spell : Spell.getAllSpells()){
// Much like the enchantments tag for items, this stores a list of spell-id-to-name tag pairs // Much like the enchantments tag for items, this stores a list of spell-id-to-name tag pairs
// The description is now also included; there's no point in making a second compound tag! // The description is now also included; there's no point in making a second compound tag!
NBTTagCompound tag = new NBTTagCompound(); NBTTagCompound tag = new NBTTagCompound();
@@ -171,7 +172,7 @@ public class SpellGlyphData extends WorldSavedData {
tagList.appendTag(tag); tagList.appendTag(tag);
} }
nbt.setTag("spellGlyphData", tagList); NBTExtras.storeTagSafely(nbt, "spellGlyphData", tagList);
return nbt; return nbt;
} }
@@ -515,12 +515,12 @@ public class WizardData implements INBTSerializable<NBTTagCompound> {
NBTTagCompound properties = new NBTTagCompound(); NBTTagCompound properties = new NBTTagCompound();
properties.setTag("imbuements", NBTExtras.mapToNBT(this.imbuementDurations, NBTExtras.storeTagSafely(properties, "imbuements", NBTExtras.mapToNBT(this.imbuementDurations,
imbuement -> new NBTTagInt(Enchantment.getEnchantmentID((Enchantment)imbuement)), NBTTagInt::new)); imbuement -> new NBTTagInt(Enchantment.getEnchantmentID((Enchantment)imbuement)), NBTTagInt::new));
// Mmmmmm Java 8.... // Mmmmmm Java 8....
properties.setTag("allies", NBTExtras.listToNBT(this.allies, NBTUtil::createUUIDTag)); NBTExtras.storeTagSafely(properties, "allies", NBTExtras.listToNBT(this.allies, NBTUtil::createUUIDTag));
properties.setTag("allyNames", NBTExtras.listToNBT(this.allyNames, NBTTagString::new)); NBTExtras.storeTagSafely(properties, "allyNames", NBTExtras.listToNBT(this.allyNames, NBTTagString::new));
// Might be worth converting this over to WizardryUtilities.listToNBT. // Might be worth converting this over to WizardryUtilities.listToNBT.
int[] spells = new int[this.spellsDiscovered.size()]; int[] spells = new int[this.spellsDiscovered.size()];
@@ -531,7 +531,7 @@ public class WizardData implements INBTSerializable<NBTTagCompound> {
} }
properties.setIntArray("discoveredSpells", spells); properties.setIntArray("discoveredSpells", spells);
properties.setTag("recentSpells", NBTExtras.listToNBT(recentSpells, s -> new NBTTagInt(s.metadata()))); NBTExtras.storeTagSafely(properties, "recentSpells", NBTExtras.listToNBT(recentSpells, s -> new NBTTagInt(s.metadata())));
storedVariables.forEach(k -> k.write(properties, this.spellData.get(k))); storedVariables.forEach(k -> k.write(properties, this.spellData.get(k)));
@@ -5,6 +5,7 @@ import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.spell.Spell; import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.AllyDesignationSystem; import electroblob.wizardry.util.AllyDesignationSystem;
import electroblob.wizardry.util.MagicDamage; import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.NBTExtras;
import electroblob.wizardry.util.WizardryUtilities; import electroblob.wizardry.util.WizardryUtilities;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import net.minecraft.block.Block; import net.minecraft.block.Block;
@@ -183,7 +184,7 @@ public class EntityLevitatingBlock extends EntityFallingBlock implements IEntity
NBTBase nbtbase = this.tileEntityData.getTag(s); NBTBase nbtbase = this.tileEntityData.getTag(s);
if(!"x".equals(s) && !"y".equals(s) && !"z".equals(s)){ if(!"x".equals(s) && !"y".equals(s) && !"z".equals(s)){
nbttagcompound.setTag(s, nbtbase.copy()); NBTExtras.storeTagSafely(nbttagcompound, s, nbtbase.copy());
} }
} }
@@ -65,7 +65,7 @@ public class EntityBubble extends EntityMagicConstruct {
if(isDarkOrb){ if(isDarkOrb){
if(WizardryUtilities.getRider(this) != null if(WizardryUtilities.getRider(this) != null
&& this.ticksExisted % Spells.entrapment.getProperty(Entrapment.DAMAGE_INTERVAL).intValue() == 0){ && WizardryUtilities.getRider(this).ticksExisted % Spells.entrapment.getProperty(Entrapment.DAMAGE_INTERVAL).intValue() == 0){
if(this.getCaster() != null){ if(this.getCaster() != null){
WizardryUtilities.getRider(this).attackEntityFrom( WizardryUtilities.getRider(this).attackEntityFrom(
MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.MAGIC), MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.MAGIC),
@@ -1,6 +1,5 @@
package electroblob.wizardry.entity.construct; package electroblob.wizardry.entity.construct;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.spell.Spell; import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.WizardryUtilities; import electroblob.wizardry.util.WizardryUtilities;
@@ -80,8 +80,8 @@ public class EntityHammer extends EntityMagicConstruct {
if(this.world.isRemote && this.ticksExisted % 3 == 0){ if(this.world.isRemote && this.ticksExisted % 3 == 0){
ParticleBuilder.create(Type.SPARK) ParticleBuilder.create(Type.SPARK)
.pos(this.posX - 0.5d + rand.nextDouble(), this.posY + 2 * rand.nextDouble(), this.posZ - 0.5d + rand.nextDouble()) .pos(this.posX - 0.5d + rand.nextDouble(), this.posY + 2 * rand.nextDouble(), this.posZ - 0.5d + rand.nextDouble())
.spawn(world); .spawn(world);
} }
this.prevPosX = this.posX; this.prevPosX = this.posX;
@@ -103,39 +103,37 @@ public class EntityHammer extends EntityMagicConstruct {
this.rotationPitch = 0; this.rotationPitch = 0;
this.spin = false; this.spin = false;
if(this.ticksExisted % Spells.lightning_hammer.getProperty(LightningHammer.ATTACK_INTERVAL).floatValue() == 0){ double seekerRange = Spells.lightning_hammer.getProperty(Spell.EFFECT_RADIUS).doubleValue();
double seekerRange = Spells.lightning_hammer.getProperty(Spell.EFFECT_RADIUS).doubleValue(); List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(seekerRange, this.posX,
this.posY + 1, this.posZ, world);
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(seekerRange, this.posX, int maxTargets = Spells.lightning_hammer.getProperty(LightningHammer.SECONDARY_MAX_TARGETS).intValue();
this.posY + 1, this.posZ, world); while(targets.size() > maxTargets) targets.remove(targets.size() - 1);
int maxTargets = Spells.lightning_hammer.getProperty(LightningHammer.SECONDARY_MAX_TARGETS).intValue(); for(EntityLivingBase target : targets){
while(targets.size() > maxTargets) targets.remove(targets.size() - 1);
for(EntityLivingBase target : targets){ if(WizardryUtilities.isLiving(target) && this.isValidTarget(target)
&& target.ticksExisted % Spells.lightning_hammer.getProperty(LightningHammer.ATTACK_INTERVAL).floatValue() == 0){
if(WizardryUtilities.isLiving(target) && this.isValidTarget(target)){ if(world.isRemote){
if(world.isRemote){ ParticleBuilder.create(Type.LIGHTNING).pos(posX, posY + height - 0.1, posZ) .target(target).spawn(world);
ParticleBuilder.create(Type.LIGHTNING).pos(posX, posY + height - 0.1, posZ) .target(target).spawn(world); ParticleBuilder.spawnShockParticles(world, target.posX,
target.getEntityBoundingBox().minY + target.height, target.posZ);
}
ParticleBuilder.spawnShockParticles(world, target.posX, target.playSound(WizardrySounds.ENTITY_HAMMER_ATTACK, 1.0F, rand.nextFloat() * 0.4F + 1.5F);
target.getEntityBoundingBox().minY + target.height, target.posZ);
}
target.playSound(WizardrySounds.ENTITY_HAMMER_ATTACK, 1.0F, rand.nextFloat() * 0.4F + 1.5F); float damage = Spells.lightning_hammer.getProperty(Spell.SPLASH_DAMAGE).floatValue() * damageMultiplier;
float damage = Spells.lightning_hammer.getProperty(Spell.SPLASH_DAMAGE).floatValue() * damageMultiplier; if(this.getCaster() != null){
WizardryUtilities.attackEntityWithoutKnockback(target, MagicDamage.causeIndirectMagicDamage(
if(this.getCaster() != null){ this, getCaster(), DamageType.SHOCK), damage);
WizardryUtilities.attackEntityWithoutKnockback(target, MagicDamage.causeIndirectMagicDamage( WizardryUtilities.applyStandardKnockback(this, target);
this, getCaster(), DamageType.SHOCK), damage); }else{
WizardryUtilities.applyStandardKnockback(this, target); target.attackEntityFrom(DamageSource.MAGIC, damage);
}else{
target.attackEntityFrom(DamageSource.MAGIC, damage);
}
} }
} }
} }
@@ -62,7 +62,7 @@ public class EntityHealAura extends EntityMagicConstruct {
target.motionZ = velZ; target.motionZ = velZ;
} }
}else if(target.getHealth() < target.getMaxHealth() && this.ticksExisted % 5 == 0){ }else if(target.getHealth() < target.getMaxHealth() && target.ticksExisted % 5 == 0){
target.heal(Spells.healing_aura.getProperty(Spell.HEALTH).floatValue() * damageMultiplier); target.heal(Spells.healing_aura.getProperty(Spell.HEALTH).floatValue() * damageMultiplier);
} }
} }
@@ -213,6 +213,7 @@ public class EntityAIAttackSpell<T extends EntityLiving & ISpellCaster> extends
return false; return false;
} }
// This is only called when spell casting starts so ticksInUse is always zero
if(spell.cast(attacker.world, attacker, EnumHand.MAIN_HAND, 0, target, modifiers)){ if(spell.cast(attacker.world, attacker, EnumHand.MAIN_HAND, 0, target, modifiers)){
if(spell.isContinuous){ if(spell.isContinuous){
@@ -74,6 +74,7 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
// Field implementations // Field implementations
private List<Spell> spells = new ArrayList<Spell>(4); private List<Spell> spells = new ArrayList<Spell>(4);
private Spell continuousSpell; private Spell continuousSpell;
private int spellCounter;
public EntityEvilWizard(World world){ public EntityEvilWizard(World world){
@@ -175,6 +176,16 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
public Spell getContinuousSpell(){ public Spell getContinuousSpell(){
return this.continuousSpell; return this.continuousSpell;
} }
@Override
public void setSpellCounter(int count){
spellCounter = count;
}
@Override
public int getSpellCounter(){
return spellCounter;
}
@Override @Override
public int getAimingError(EnumDifficulty difficulty){ public int getAimingError(EnumDifficulty difficulty){
@@ -252,7 +263,7 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
// When right-clicked with a spell book in creative, sets one of the spells to that spell // When right-clicked with a spell book in creative, sets one of the spells to that spell
if(player.isCreative() && stack.getItem() instanceof ItemSpellBook){ if(player.isCreative() && stack.getItem() instanceof ItemSpellBook){
Spell spell = Spell.byMetadata(stack.getItemDamage()); Spell spell = Spell.byMetadata(stack.getItemDamage());
if(this.spells.size() >= 4 && spell.canBeCastByNPCs()){ if(this.spells.size() >= 4 && spell.canBeCastBy(this, true)){
// The set(...) method returns the element that was replaced - neat! // The set(...) method returns the element that was replaced - neat!
player.sendMessage(new TextComponentTranslation("item." + Wizardry.MODID + ":spell_book.apply_to_wizard", player.sendMessage(new TextComponentTranslation("item." + Wizardry.MODID + ":spell_book.apply_to_wizard",
this.getDisplayName(), this.spells.set(rand.nextInt(3) + 1, spell).getNameForTranslationFormatted(), this.getDisplayName(), this.spells.set(rand.nextInt(3) + 1, spell).getNameForTranslationFormatted(),
@@ -269,9 +280,9 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
super.writeEntityToNBT(nbt); super.writeEntityToNBT(nbt);
nbt.setInteger("element", this.getElement().ordinal()); nbt.setInteger("element", this.getElement().ordinal());
nbt.setInteger("skin", this.textureIndex); nbt.setInteger("skin", this.textureIndex);
nbt.setTag("spells", NBTExtras.listToNBT(spells, spell -> new NBTTagInt(spell.metadata()))); NBTExtras.storeTagSafely(nbt, "spells", NBTExtras.listToNBT(spells, spell -> new NBTTagInt(spell.metadata())));
nbt.setBoolean("hasStructure", this.hasStructure); nbt.setBoolean("hasStructure", this.hasStructure);
nbt.setTag("groupUUIDs", NBTExtras.listToNBT(groupUUIDs, NBTUtil::createUUIDTag)); NBTExtras.storeTagSafely(nbt, "groupUUIDs", NBTExtras.listToNBT(groupUUIDs, NBTUtil::createUUIDTag));
} }
@Override @Override
@@ -369,7 +380,7 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
// All wizards know magic missile, even if it is disabled. // All wizards know magic missile, even if it is disabled.
spells.add(Spells.magic_missile); spells.add(Spells.magic_missile);
Tier maxTier = EntityWizard.populateSpells(spells, element, hasStructure, 3, rand); Tier maxTier = EntityWizard.populateSpells(this, spells, element, hasStructure, 3, rand);
// Now done after the spells so it can take the tier into account. For evil wizards this is slightly different; // Now done after the spells so it can take the tier into account. For evil wizards this is slightly different;
// it picks a random wand which is at least a high enough tier for the spells the wizard has. // it picks a random wand which is at least a high enough tier for the spells the wizard has.
@@ -19,9 +19,6 @@ import net.minecraftforge.fml.common.eventhandler.Event;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import java.util.Arrays;
import java.util.function.IntPredicate;
@Mod.EventBusSubscriber @Mod.EventBusSubscriber
public class EntityLightningWraith extends EntityBlazeMinion { public class EntityLightningWraith extends EntityBlazeMinion {
@@ -30,6 +30,7 @@ public class EntityPhoenix extends EntitySummonedCreature implements ISpellCaste
private EntityAIAttackSpell<EntityPhoenix> spellAttackAI = new EntityAIAttackSpell<>(this, AISpeed, 15f, 60, 140); private EntityAIAttackSpell<EntityPhoenix> spellAttackAI = new EntityAIAttackSpell<>(this, AISpeed, 15f, 60, 140);
private Spell continuousSpell; private Spell continuousSpell;
private int spellCounter;
private static final List<Spell> attack = Collections.singletonList(Spells.flame_ray); private static final List<Spell> attack = Collections.singletonList(Spells.flame_ray);
@@ -76,6 +77,16 @@ public class EntityPhoenix extends EntitySummonedCreature implements ISpellCaste
continuousSpell = spell; continuousSpell = spell;
} }
@Override
public void setSpellCounter(int count){
spellCounter = count;
}
@Override
public int getSpellCounter(){
return spellCounter;
}
@Override @Override
public boolean hasRangedAttack(){ public boolean hasRangedAttack(){
return true; return true;
@@ -61,21 +61,6 @@ public class EntityShadowWraith extends EntitySummonedCreature implements ISpell
return attack; return attack;
} }
@Override
public SpellModifiers getModifiers(){
return new SpellModifiers();
}
@Override
public Spell getContinuousSpell(){
return Spells.none;
}
@Override
public void setContinuousSpell(Spell spell){
// Doesn't use continuous spells.
}
@Override @Override
protected void applyEntityAttributes(){ protected void applyEntityAttributes(){
super.applyEntityAttributes(); super.applyEntityAttributes();
@@ -59,21 +59,6 @@ public class EntityStormElemental extends EntitySummonedCreature implements ISpe
return attack; return attack;
} }
@Override
public SpellModifiers getModifiers(){
return new SpellModifiers();
}
@Override
public Spell getContinuousSpell(){
return Spells.none;
}
@Override
public void setContinuousSpell(Spell spell){
// Doesn't use continuous spells.
}
@Override @Override
protected void applyEntityAttributes(){ protected void applyEntityAttributes(){
super.applyEntityAttributes(); super.applyEntityAttributes();
@@ -83,6 +83,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
// Field implementations // Field implementations
private List<Spell> spells = new ArrayList<Spell>(4); private List<Spell> spells = new ArrayList<Spell>(4);
private Spell continuousSpell; private Spell continuousSpell;
private int spellCounter;
/** A set of the positions of the blocks that are part of this wizard's tower. */ /** A set of the positions of the blocks that are part of this wizard's tower. */
private Set<BlockPos> towerBlocks; private Set<BlockPos> towerBlocks;
@@ -187,7 +188,17 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
public Spell getContinuousSpell(){ public Spell getContinuousSpell(){
return this.continuousSpell; return this.continuousSpell;
} }
@Override
public void setSpellCounter(int count){
spellCounter = count;
}
@Override
public int getSpellCounter(){
return spellCounter;
}
@Override @Override
public int getAimingError(EnumDifficulty difficulty){ public int getAimingError(EnumDifficulty difficulty){
// Being more intelligent than skeletons, wizards are a little more accurate. // Being more intelligent than skeletons, wizards are a little more accurate.
@@ -359,7 +370,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
// When right-clicked with a spell book in creative, sets one of the spells to that spell // When right-clicked with a spell book in creative, sets one of the spells to that spell
if(player.isCreative() && stack.getItem() instanceof ItemSpellBook){ if(player.isCreative() && stack.getItem() instanceof ItemSpellBook){
Spell spell = Spell.byMetadata(stack.getItemDamage()); Spell spell = Spell.byMetadata(stack.getItemDamage());
if(this.spells.size() >= 4 && spell.canBeCastByNPCs()){ if(this.spells.size() >= 4 && spell.canBeCastBy(this, true)){
// The set(...) method returns the element that was replaced - neat! // The set(...) method returns the element that was replaced - neat!
player.sendMessage(new TextComponentTranslation("item." + Wizardry.MODID + ":spell_book.apply_to_wizard", player.sendMessage(new TextComponentTranslation("item." + Wizardry.MODID + ":spell_book.apply_to_wizard",
this.getDisplayName(), this.spells.set(rand.nextInt(3) + 1, spell).getNameForTranslationFormatted(), this.getDisplayName(), this.spells.set(rand.nextInt(3) + 1, spell).getNameForTranslationFormatted(),
@@ -389,15 +400,15 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
super.writeEntityToNBT(nbt); super.writeEntityToNBT(nbt);
if(this.trades != null){ if(this.trades != null){
nbt.setTag("trades", this.trades.getRecipiesAsTags()); NBTExtras.storeTagSafely(nbt, "trades", this.trades.getRecipiesAsTags());
} }
nbt.setInteger("element", this.getElement().ordinal()); nbt.setInteger("element", this.getElement().ordinal());
nbt.setInteger("skin", this.textureIndex); nbt.setInteger("skin", this.textureIndex);
nbt.setTag("spells", NBTExtras.listToNBT(spells, spell -> new NBTTagInt(spell.metadata()))); NBTExtras.storeTagSafely(nbt, "spells", NBTExtras.listToNBT(spells, spell -> new NBTTagInt(spell.metadata())));
if(this.towerBlocks != null && this.towerBlocks.size() > 0){ if(this.towerBlocks != null && this.towerBlocks.size() > 0){
nbt.setTag("towerBlocks", NBTExtras.listToNBT(this.towerBlocks, NBTUtil::createPosTag)); NBTExtras.storeTagSafely(nbt, "towerBlocks", NBTExtras.listToNBT(this.towerBlocks, NBTUtil::createPosTag));
} }
} }
@@ -752,7 +763,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
// All wizards know magic missile, even if it is disabled. // All wizards know magic missile, even if it is disabled.
spells.add(Spells.magic_missile); spells.add(Spells.magic_missile);
Tier maxTier = populateSpells(spells, element, false, 3, rand); Tier maxTier = populateSpells(this, spells, element, false, 3, rand);
// Now done after the spells so it can take the tier into account. // Now done after the spells so it can take the tier into account.
ItemStack wand = new ItemStack(WizardryItems.getWand(maxTier, element)); ItemStack wand = new ItemStack(WizardryItems.getWand(maxTier, element));
@@ -769,19 +780,21 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
/** /**
* Adds n random spells to the given list. The spells will be of the given element if possible. Extracted as a * Adds n random spells to the given list. The spells will be of the given element if possible. Extracted as a
* separate function since it was the same in both EntityWizard and EntityEvilWizard. * separate function since it was the same in both EntityWizard and EntityEvilWizard.
* *
* @param wizard The wizard whose spells are to be populated.
* @param spells The spell list to be populated. * @param spells The spell list to be populated.
* @param e The element that the spells should belong to, or {@link Element#MAGIC} for a random element each time. * @param e The element that the spells should belong to, or {@link Element#MAGIC} for a random element each time.
* @param master Whether to include master spells.
* @param n The number of spells to add. * @param n The number of spells to add.
* @param random A random number generator to use. * @param random A random number generator to use.
* @return The tier of the highest-tier spell that was added to the list. * @return The tier of the highest-tier spell that was added to the list.
*/ */
static Tier populateSpells(List<Spell> spells, Element e, boolean master, int n, Random random){ static Tier populateSpells(final EntityLiving wizard, List<Spell> spells, Element e, boolean master, int n, Random random){
// This is the tier of the highest tier spell added. // This is the tier of the highest tier spell added.
Tier maxTier = Tier.NOVICE; Tier maxTier = Tier.NOVICE;
List<Spell> npcSpells = Spell.getSpells(Spell.npcSpells); List<Spell> npcSpells = Spell.getSpells(s -> s.canBeCastBy(wizard, false));
npcSpells.removeIf(s -> !s.applicableForItem(WizardryItems.spell_book)); npcSpells.removeIf(s -> !s.applicableForItem(WizardryItems.spell_book));
for(int i = 0; i < n; i++){ for(int i = 0; i < n; i++){
@@ -19,7 +19,9 @@ import java.util.List;
* packets) is handled by that class, and all the implementor needs to do is decide which spell(s) to select. * packets) is handled by that class, and all the implementor needs to do is decide which spell(s) to select.
* <p></p> * <p></p>
* This class also allows Wizardry to do all the syncing necessary for continuous spell casting. All the implementor * This class also allows Wizardry to do all the syncing necessary for continuous spell casting. All the implementor
* needs to do is store the actual fields involved. * needs to do is store the actual fields involved, by implementing {@link ISpellCaster#setContinuousSpell(Spell)},
* {@link ISpellCaster#getContinuousSpell()}, {@link ISpellCaster#setSpellCounter(int)}, {@link ISpellCaster#getSpellCounter()}
* and {@link ISpellCaster#getModifiers()}.
*/ */
/* Perhaps this should be a capability? Though I can't help thinking they're mainly for attaching data to vanilla /* Perhaps this should be a capability? Though I can't help thinking they're mainly for attaching data to vanilla
* classes, rather than custom ones. For now, the main purpose of this is to centralise code within wizardry itself, and * classes, rather than custom ones. For now, the main purpose of this is to centralise code within wizardry itself, and
@@ -36,10 +38,10 @@ public interface ISpellCaster {
* *
* @return A list of {@link Spell} instances. A random spell from this list will be cast when the entity attacks. * @return A list of {@link Spell} instances. A random spell from this list will be cast when the entity attacks.
* The list will not be modified by the AI class and can therefore be an immutable list. The spells in the * The list will not be modified by the AI class and can therefore be an immutable list. The spells in the
* list <b>must</b> be castable by NPCs (i.e. {@link Spell#canBeCastByNPCs()} returns true). * list <b>must</b> be castable by NPCs (i.e. {@link Spell#canBeCastBy(net.minecraft.entity.EntityLiving, boolean)} returns true).
*/ */
@Nonnull @Nonnull
public List<Spell> getSpells(); List<Spell> getSpells();
/** /**
* Called each time the entity attacks to get the modifiers to apply to the spell. * Called each time the entity attacks to get the modifiers to apply to the spell.
@@ -48,31 +50,47 @@ public interface ISpellCaster {
* required, pass in an empty {@code SpellModifiers} object. * required, pass in an empty {@code SpellModifiers} object.
*/ */
@Nonnull @Nonnull
public SpellModifiers getModifiers(); default SpellModifiers getModifiers(){
return new SpellModifiers(); // May seem wasteful but this should never be called so it doesn't matter
}
/** /**
* Returns the continuous spell that is currently being cast, or the None spell if there is none. Implementors * Returns the continuous spell that is currently being cast, or the None spell if there is none. Implementors
* should simply store this as a private field and return it here. Will be synced by the AI class, but whether it is * should simply store this as a private field and return it here. Will be synced by the AI class, but whether it is
* saved to NBT is up to you. If the implementing class does not deal with continuous spells, just return * saved to NBT is up to you. If the implementing class only ever uses one continuous spell, do <b>not</b> just
* {@link Spells#none}. If the implementing class only ever uses one continuous spell, do <b>not</b> just return * return that spell; the field must still be stored.
* that spell; the field must still be stored.
*/ */
@Nonnull @Nonnull
public Spell getContinuousSpell(); default Spell getContinuousSpell(){
return Spells.none;
}
/** /**
* Sets the continuous spell that is currently being cast, or the None spell if there is none. Implementors should * Sets the continuous spell that is currently being cast, or the None spell if there is none. Implementors should
* simply store this as a private field and assign it here. Will be synced by the AI class, but whether it is saved * simply store this as a private field and assign it here. Will be synced by the AI class, but whether it is saved
* to NBT is up to you. If the implementing class does not deal with continuous spells, leave this method blank. * to NBT is up to you. If the implementing class does not deal with continuous spells, leave this method blank.
*/ */
public void setContinuousSpell(Spell spell); default void setContinuousSpell(Spell spell){
// Do nothing
}
/** Returns the number of ticks the current spell has been cast for. Implementors should simply store this as a
* private field and return it here. This is only used client-side. */
default int getSpellCounter(){
return 0;
}
/** Sets the number of ticks the current spell has been cast for. Implementors should simply store this as a
* private field and assign it here. This is only used client-side. */
default void setSpellCounter(int count){
// Do nothing
}
/** /**
* Returns the aiming arror for the given difficulty, used in projectile spells. Defaults to the values used by * Returns the aiming error for the given difficulty, used in projectile spells. Defaults to the values used by
* skeletons, which are: Easy - 10, Normal - 6, Hard - 2, Peaceful - 10 (rarely used). * skeletons, which are: Easy - 10, Normal - 6, Hard - 2, Peaceful - 10 (rarely used).
*/ */
// This is what default methods are actually intended for! default int getAimingError(EnumDifficulty difficulty) {
public default int getAimingError(EnumDifficulty difficulty) {
return WizardryUtilities.getDefaultAimingError(difficulty); return WizardryUtilities.getDefaultAimingError(difficulty);
} }
} }
@@ -9,11 +9,15 @@ import electroblob.wizardry.util.ParticleBuilder;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.projectile.EntitySmallFireball; import net.minecraft.entity.projectile.EntitySmallFireball;
import net.minecraft.init.Blocks; import net.minecraft.init.Blocks;
import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.DamageSource;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.RayTraceResult; import net.minecraft.util.math.RayTraceResult;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.event.entity.EntityJoinWorldEvent; import net.minecraftforge.event.entity.EntityJoinWorldEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
@@ -131,6 +135,54 @@ public class EntityMagicFireball extends EntityMagicProjectile {
} }
} }
@Override
public boolean canBeCollidedWith(){
return true;
}
@Override
public float getCollisionBorderSize(){
return 1.0F;
}
@Override
public boolean attackEntityFrom(DamageSource source, float amount){
if(this.isEntityInvulnerable(source)){
return false;
}else{
this.markVelocityChanged();
if(source.getTrueSource() != null){
Vec3d vec3d = source.getTrueSource().getLookVec();
if(vec3d != null){
double speed = MathHelper.sqrt(motionX * motionX + motionY * motionY + motionZ * motionZ);
this.motionX = vec3d.x * speed;
this.motionY = vec3d.y * speed;
this.motionZ = vec3d.z * speed;
this.lifetime = 160;
}
if(source.getTrueSource() instanceof EntityLivingBase){
this.setCaster((EntityLivingBase)source.getTrueSource());
}
return true;
}else{
return false;
}
}
}
public void setLifetime(int lifetime){ public void setLifetime(int lifetime){
this.lifetime = lifetime; this.lifetime = lifetime;
} }
@@ -71,7 +71,7 @@ public abstract class EntityMagicProjectile extends EntityThrowable implements I
double dy = !this.hasNoGravity() ? target.getEntityBoundingBox().minY + (double)(target.height / 3.0f) - this.posY double dy = !this.hasNoGravity() ? target.getEntityBoundingBox().minY + (double)(target.height / 3.0f) - this.posY
: target.getEntityBoundingBox().minY + (double)(target.height / 2.0f) - this.posY; : target.getEntityBoundingBox().minY + (double)(target.height / 2.0f) - this.posY;
double dz = target.posZ - caster.posZ; double dz = target.posZ - caster.posZ;
double horizontalDistance = (double)MathHelper.sqrt(dx * dx + dz * dz); double horizontalDistance = MathHelper.sqrt(dx * dx + dz * dz);
if(horizontalDistance >= 1.0E-7D){ if(horizontalDistance >= 1.0E-7D){
@@ -9,7 +9,6 @@ import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.WizardryUtilities; import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.MobEffects; import net.minecraft.init.MobEffects;
import net.minecraft.potion.PotionEffect; import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.EnumParticleTypes;
@@ -61,11 +61,10 @@ public class ItemArcaneTome extends Item {
Tier tier = Tier.values()[stack.getItemDamage()]; Tier tier = Tier.values()[stack.getItemDamage()];
Tier tier2 = Tier.values()[stack.getItemDamage() - 1]; Tier tier2 = Tier.values()[stack.getItemDamage() - 1];
tooltip.add(tier.getDisplayNameWithFormatting()); tooltip.add(tier.getDisplayNameWithFormatting());
tooltip.add("\u00A77" + net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":arcane_tome.desc1", Wizardry.proxy.addMultiLineDescription(tooltip, "item." + this.getRegistryName() + ".desc",
tier2.getDisplayNameWithFormatting())); tier2.getDisplayNameWithFormatting() + "\u00A77", tier.getDisplayNameWithFormatting() + "\u00A77");
tooltip.add("\u00A77" + net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":arcane_tome.desc2",
tier.getDisplayNameWithFormatting() + "\u00A77"));
} }
} }
@@ -1,7 +1,6 @@
package electroblob.wizardry.item; package electroblob.wizardry.item;
import electroblob.wizardry.Wizardry; import electroblob.wizardry.Wizardry;
import electroblob.wizardry.data.SpellGlyphData;
import electroblob.wizardry.event.SpellCastEvent; import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.event.SpellCastEvent.Source; import electroblob.wizardry.event.SpellCastEvent.Source;
import electroblob.wizardry.packet.PacketCastSpell; import electroblob.wizardry.packet.PacketCastSpell;
@@ -20,7 +19,6 @@ import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage; import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly; import net.minecraftforge.fml.relauncher.SideOnly;
import net.minecraftforge.oredict.OreDictionary;
import java.util.List; import java.util.List;
@@ -52,7 +50,7 @@ public class ItemScroll extends Item implements ISpellCastingItem {
if(tab == WizardryTabs.SPELLS){ if(tab == WizardryTabs.SPELLS){
List<Spell> spells = Spell.getSpells(Spell.allSpells); List<Spell> spells = Spell.getAllSpells();
spells.removeIf(s -> !s.applicableForItem(this)); spells.removeIf(s -> !s.applicableForItem(this));
for(Spell spell : spells){ for(Spell spell : spells){
@@ -41,7 +41,7 @@ public class ItemSpellBook extends Item {
if(tab == WizardryTabs.SPELLS){ if(tab == WizardryTabs.SPELLS){
List<Spell> spells = Spell.getSpells(Spell.allSpells); List<Spell> spells = Spell.getAllSpells();
spells.removeIf(s -> !s.applicableForItem(this)); spells.removeIf(s -> !s.applicableForItem(this));
for(Spell spell : spells){ for(Spell spell : spells){
@@ -27,7 +27,10 @@ import net.minecraft.inventory.Slot;
import net.minecraft.item.EnumAction; import net.minecraft.item.EnumAction;
import net.minecraft.item.Item; import net.minecraft.item.Item;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.util.*; import net.minecraft.util.ActionResult;
import net.minecraft.util.EnumActionResult;
import net.minecraft.util.EnumHand;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.RayTraceResult; import net.minecraft.util.math.RayTraceResult;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
@@ -330,7 +330,7 @@ public class ItemWizardArmour extends ItemArmor implements IWorkbenchItem, IMana
IAttributeInstance attribute = event.getEntityLiving().getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE); IAttributeInstance attribute = event.getEntityLiving().getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE);
double followRange = attribute == null ? 16 : attribute.getAttributeValue(); double followRange = attribute == null ? 16 : attribute.getAttributeValue();
if(event.getTarget().isSneaking()) followRange *= 0.8; if(event.getTarget().isSneaking()) followRange *= 0.8;
float f = armourPieces / ((EntityPlayer)event.getTarget()).inventory.armorInventory.size(); float f = (float)armourPieces / ((EntityPlayer)event.getTarget()).inventory.armorInventory.size();
if(f < 0.1F) f = 0.1F; if(f < 0.1F) f = 0.1F;
followRange *= (double)(0.7F * f); followRange *= (double)(0.7F * f);
// Don't need to worry about the isSuitableTarget check since it must already have been checked to get this far // Don't need to worry about the isSuitableTarget check since it must already have been checked to get this far
@@ -62,7 +62,7 @@ public class BehaviourSpellDispense extends BehaviorDispenseOptional {
if(world.isSideSolid(source.getBlockPos().offset(direction), direction.getOpposite())) return stack; if(world.isSideSolid(source.getBlockPos().offset(direction), direction.getOpposite())) return stack;
// If the scroll can never be cast by a dispenser, it should be dispensed as an item. // If the scroll can never be cast by a dispenser, it should be dispensed as an item.
if(!spell.canBeCastByDispensers()) return super.dispenseStack(source, stack); if(!spell.canBeCastBy(source.getBlockTileEntity())) return super.dispenseStack(source, stack);
SpellModifiers modifiers = new SpellModifiers(); SpellModifiers modifiers = new SpellModifiers();
@@ -1,47 +0,0 @@
package electroblob.wizardry.packet;
import electroblob.wizardry.Wizardry;
import io.netty.buffer.ByteBuf;
import net.minecraft.entity.Entity;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler;
import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
/** <b>[Server -> Client]</b> This packet is sent when the slow time potion effect expires or is removed from an
* entity to unblock all nearby entities' updates. */
public class PacketEndSlowTime implements IMessageHandler<PacketEndSlowTime.Message, IMessage> {
@Override
public IMessage onMessage(Message message, MessageContext ctx){
// Just to make sure that the side is correct
if(ctx.side.isClient()){
net.minecraft.client.Minecraft.getMinecraft().addScheduledTask(() -> Wizardry.proxy.handleEndSlowTimePacket(message));
}
return null;
}
public static class Message implements IMessage {
public int hostID;
// This constructor is required otherwise you'll get errors (used somewhere in fml through reflection)
public Message(){
}
public Message(Entity host){
this.hostID = host.getEntityId();
}
@Override
public void fromBytes(ByteBuf buf){
this.hostID = buf.readInt();
}
@Override
public void toBytes(ByteBuf buf){
buf.writeInt(hostID);
}
}
}
@@ -26,7 +26,6 @@ public class WizardryPacketHandler {
registerMessage(PacketSpellProperties.class, PacketSpellProperties.Message.class); registerMessage(PacketSpellProperties.class, PacketSpellProperties.Message.class);
registerMessage(PacketSyncAdvancements.class, PacketSyncAdvancements.Message.class); registerMessage(PacketSyncAdvancements.class, PacketSyncAdvancements.Message.class);
registerMessage(PacketRequestAdvancementSync.class, PacketRequestAdvancementSync.Message.class); registerMessage(PacketRequestAdvancementSync.class, PacketRequestAdvancementSync.Message.class);
registerMessage(PacketEndSlowTime.class, PacketEndSlowTime.Message.class);
registerMessage(PacketResurrection.class, PacketResurrection.Message.class); registerMessage(PacketResurrection.class, PacketResurrection.Message.class);
registerMessage(PacketCastSpellAtPos.class, PacketCastSpellAtPos.Message.class); registerMessage(PacketCastSpellAtPos.class, PacketCastSpellAtPos.Message.class);
registerMessage(PacketEmitterData.class, PacketEmitterData.Message.class); registerMessage(PacketEmitterData.class, PacketEmitterData.Message.class);
@@ -2,6 +2,7 @@ package electroblob.wizardry.potion;
import electroblob.wizardry.Wizardry; import electroblob.wizardry.Wizardry;
import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.util.NBTExtras;
import electroblob.wizardry.util.WizardryUtilities; import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.entity.player.EntityPlayerMP;
@@ -40,7 +41,7 @@ public class PotionContainment extends PotionMagicEffect {
// Initialise the containment position to the entity's position if it wasn't set already // Initialise the containment position to the entity's position if it wasn't set already
if(!target.getEntityData().hasKey(ENTITY_TAG)){ if(!target.getEntityData().hasKey(ENTITY_TAG)){
target.getEntityData().setTag(ENTITY_TAG, NBTUtil.createPosTag(new BlockPos(target.getPositionVector().subtract(0.5, 0.5, 0.5)))); NBTExtras.storeTagSafely(target.getEntityData(), ENTITY_TAG, NBTUtil.createPosTag(new BlockPos(target.getPositionVector().subtract(0.5, 0.5, 0.5))));
} }
Vec3d origin = WizardryUtilities.getCentre(NBTUtil.getPosFromTag(target.getEntityData().getCompoundTag(ENTITY_TAG))); Vec3d origin = WizardryUtilities.getCentre(NBTUtil.getPosFromTag(target.getEntityData().getCompoundTag(ENTITY_TAG)));
@@ -2,8 +2,6 @@ package electroblob.wizardry.potion;
import electroblob.wizardry.Wizardry; import electroblob.wizardry.Wizardry;
import electroblob.wizardry.item.ItemArtefact; import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.packet.PacketEndSlowTime;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.registry.WizardryPotions;
@@ -17,11 +15,15 @@ import net.minecraft.entity.IProjectile;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.projectile.EntityArrow; import net.minecraft.entity.projectile.EntityArrow;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent; import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent;
import net.minecraftforge.event.entity.living.PotionEvent; import net.minecraftforge.event.entity.living.PotionEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.PlayerEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import java.util.ArrayList;
import java.util.List; import java.util.List;
@Mod.EventBusSubscriber @Mod.EventBusSubscriber
@@ -29,6 +31,8 @@ public class PotionSlowTime extends PotionMagicEffect implements ISyncedPotion {
// FIXME: Minecarts with entities in them (and, I suspect, any other ridden entities) go crazy when time-slowed // FIXME: Minecarts with entities in them (and, I suspect, any other ridden entities) go crazy when time-slowed
public static final String NBT_KEY = "time_slowed";
public PotionSlowTime(boolean isBadEffect, int liquidColour){ public PotionSlowTime(boolean isBadEffect, int liquidColour){
super(isBadEffect, liquidColour, new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_slow_time.png")); super(isBadEffect, liquidColour, new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_slow_time.png"));
this.setPotionName("potion." + Wizardry.MODID + ":slow_time"); this.setPotionName("potion." + Wizardry.MODID + ":slow_time");
@@ -61,6 +65,8 @@ public class PotionSlowTime extends PotionMagicEffect implements ISyncedPotion {
for(Entity entity : targetsInRange){ for(Entity entity : targetsInRange){
entity.getEntityData().setBoolean(NBT_KEY, true);
// If time is stopped, block all updates; otherwise block all updates except every [interval] ticks // If time is stopped, block all updates; otherwise block all updates except every [interval] ticks
entity.updateBlocked = stopTime || host.ticksExisted % interval != 0; entity.updateBlocked = stopTime || host.ticksExisted % interval != 0;
@@ -130,6 +136,29 @@ public class PotionSlowTime extends PotionMagicEffect implements ISyncedPotion {
} }
/**
* Goes through every entity in the given world and does the following:<br>
* 1. Checks if they have the slow time NBT tag<br>
* 2. If so, scans the area nearby for players or NPCs with the slow time effect<br>
* 3. If none are found, removes the slow time NBT tag and unblocks the entity's updates
*/
public static void cleanUpEntities(World world){
// Had trouble with accessing loadedTileEntityList from tick events causing random CMEs so I'm making a
// copy of this too just in case
List<Entity> loadedEntityList = new ArrayList<>(world.loadedEntityList);
for(Entity entity : loadedEntityList){
if(entity.getEntityData().getBoolean(NBT_KEY)){
// Currently only players can cast slow time, but you could apply the effect to NPCs with commands
List<EntityLivingBase> nearby = WizardryUtilities.getEntitiesWithinRadius(getEffectRadius(), entity.posX, entity.posY, entity.posZ, entity.world, EntityLivingBase.class);
if(nearby.stream().noneMatch(e -> e.isPotionActive(WizardryPotions.slow_time))){
entity.getEntityData().removeTag(NBT_KEY);
entity.updateBlocked = false;
}
}
}
}
@SubscribeEvent @SubscribeEvent
public static void onLivingUpdateEvent(LivingUpdateEvent event){ public static void onLivingUpdateEvent(LivingUpdateEvent event){
@@ -150,23 +179,16 @@ public class PotionSlowTime extends PotionMagicEffect implements ISyncedPotion {
} }
@SubscribeEvent @SubscribeEvent
public static void onPotionExpiryEvent(PotionEvent.PotionExpiryEvent event){ public static void tick(TickEvent.WorldTickEvent event){
if(event.getPotionEffect() != null && event.getPotionEffect().getPotion() == WizardryPotions.slow_time){ if(!event.world.isRemote && event.phase == TickEvent.Phase.END) cleanUpEntities(event.world);
unblockNearbyEntities(event.getEntityLiving());
if(!event.getEntity().world.isRemote){
WizardryPacketHandler.net.sendToDimension(new PacketEndSlowTime.Message(event.getEntityLiving()), event.getEntity().dimension);
}
}
} }
// We still need this as well as tick events because the player hasn't moved anywhere, they just logged out
// In fact, it won't really matter since the tick event fixes it on login anyway, but if the mod is uninstalled or
// something else weird happens...
@SubscribeEvent @SubscribeEvent
public static void onPotionRemoveEvent(PotionEvent.PotionRemoveEvent event){ public static void onPlayerLoggedOutEvent(PlayerEvent.PlayerLoggedOutEvent event){
if(event.getPotionEffect() != null && event.getPotionEffect().getPotion() == WizardryPotions.slow_time){ if(event.player.updateBlocked) event.player.updateBlocked = false;
unblockNearbyEntities(event.getEntityLiving());
if(!event.getEntity().world.isRemote){
WizardryPacketHandler.net.sendToDimension(new PacketEndSlowTime.Message(event.getEntityLiving()), event.getEntity().dimension);
}
}
} }
} }
@@ -5,6 +5,7 @@ import electroblob.wizardry.entity.construct.*;
import electroblob.wizardry.entity.living.*; import electroblob.wizardry.entity.living.*;
import electroblob.wizardry.entity.projectile.*; import electroblob.wizardry.entity.projectile.*;
import electroblob.wizardry.spell.*; import electroblob.wizardry.spell.*;
import net.minecraft.entity.projectile.EntitySnowball;
import net.minecraft.init.MobEffects; import net.minecraft.init.MobEffects;
import net.minecraft.item.EnumAction; import net.minecraft.item.EnumAction;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
@@ -251,7 +252,7 @@ public final class Spells {
}.addProperties(Spell.DAMAGE).soundValues(1, 1.4f, 0.4f)); }.addProperties(Spell.DAMAGE).soundValues(1, 1.4f, 0.4f));
registry.register(new Ignite()); registry.register(new Ignite());
registry.register(new Freeze()); registry.register(new Freeze());
registry.register(new Snowball()); registry.register(new SpellThrowable<>("snowball", EntitySnowball::new).npcSelector((e, o) -> o).soundValues(0.5f, 0.4f, 0.2f)); // Let's spare wizards the pain of the snowball spell
registry.register(new Arc()); registry.register(new Arc());
registry.register(new SpellProjectile<>("thunderbolt", EntityThunderbolt::new).addProperties(Spell.DAMAGE, EntityThunderbolt.KNOCKBACK_STRENGTH).soundValues(0.8f, 0.9f, 0.2f)); registry.register(new SpellProjectile<>("thunderbolt", EntityThunderbolt::new).addProperties(Spell.DAMAGE, EntityThunderbolt.KNOCKBACK_STRENGTH).soundValues(0.8f, 0.9f, 0.2f));
registry.register(new SummonZombie()); registry.register(new SummonZombie());
@@ -321,7 +322,7 @@ public final class Spells {
registry.register(new SpellProjectile<>("darkness_orb", EntityDarknessOrb::new).addProperties(Spell.DAMAGE, Spell.EFFECT_DURATION, Spell.EFFECT_STRENGTH).soundValues(0.5f, 0.4f, 0.2f)); registry.register(new SpellProjectile<>("darkness_orb", EntityDarknessOrb::new).addProperties(Spell.DAMAGE, Spell.EFFECT_DURATION, Spell.EFFECT_STRENGTH).soundValues(0.5f, 0.4f, 0.2f));
registry.register(new ShadowWard()); registry.register(new ShadowWard());
registry.register(new Decay()); registry.register(new Decay());
registry.register(new SpellBuff("water_breathing", 0.3f, 0.3f, 1, () -> MobEffects.WATER_BREATHING){ @Override public boolean canBeCastByNPCs(){ return false; } }.soundValues(0.7f, 1.2f, 0.4f)); registry.register(new SpellBuff("water_breathing", 0.3f, 0.3f, 1, () -> MobEffects.WATER_BREATHING).npcSelector((e, o) -> false).soundValues(0.7f, 1.2f, 0.4f));
registry.register(new Tornado()); registry.register(new Tornado());
registry.register(new Glide()); registry.register(new Glide());
registry.register(new SummonSpiritHorse()); registry.register(new SummonSpiritHorse());
@@ -372,7 +373,7 @@ public final class Spells {
registry.register(new Intimidate()); registry.register(new Intimidate());
registry.register(new Banish()); registry.register(new Banish());
registry.register(new SixthSense()); registry.register(new SixthSense());
registry.register(new SpellBuff("darkvision", 0, 0.4f, 0.7f, () -> MobEffects.NIGHT_VISION){ @Override public boolean canBeCastByNPCs(){ return false; } }.soundValues(0.7f, 1.2f, 0.4f)); registry.register(new SpellBuff("darkvision", 0, 0.4f, 0.7f, () -> MobEffects.NIGHT_VISION).npcSelector((e, o) -> false).soundValues(0.7f, 1.2f, 0.4f));
registry.register(new Clairvoyance()); registry.register(new Clairvoyance());
registry.register(new PocketWorkbench()); registry.register(new PocketWorkbench());
registry.register(new ImbueWeapon()); registry.register(new ImbueWeapon());
@@ -5,7 +5,6 @@ import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.loot.RandomSpell; import electroblob.wizardry.loot.RandomSpell;
import electroblob.wizardry.loot.WizardSpell; import electroblob.wizardry.loot.WizardSpell;
import electroblob.wizardry.spell.Spell; import electroblob.wizardry.spell.Spell;
import net.minecraft.entity.EntityList;
import net.minecraft.entity.EnumCreatureType; import net.minecraft.entity.EnumCreatureType;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraft.world.storage.loot.*; import net.minecraft.world.storage.loot.*;
@@ -133,10 +133,17 @@ public final class WizardrySounds {
public static final SoundEvent MISC_PAGE_TURN = createSound("misc.page_turn"); public static final SoundEvent MISC_PAGE_TURN = createSound("misc.page_turn");
public static final SoundEvent MISC_FREEZE = createSound("misc.freeze"); public static final SoundEvent MISC_FREEZE = createSound("misc.freeze");
/** Trick borrowed from the Twilight Forest, makes things neater. */ // Trick borrowed from the Twilight Forest, makes things neater.
/** Overload for {@link WizardrySounds#createSound(String, String)} which assigns wizardry's mod ID automatically. */
public static SoundEvent createSound(String name){ public static SoundEvent createSound(String name){
return createSound(Wizardry.MODID, name);
}
/** Creates a sound with the given name, to be read from {@code assets/[modID]/sounds.json}. */
public static SoundEvent createSound(String modID, String name){
// All the setRegistryName methods delegate to this one, it doesn't matter which you use. // All the setRegistryName methods delegate to this one, it doesn't matter which you use.
return new SoundEvent(new ResourceLocation(Wizardry.MODID, name)).setRegistryName(name); return new SoundEvent(new ResourceLocation(modID, name)).setRegistryName(name);
} }
// For some reason, sound events seem to work even when they aren't registered, without even so much as a warning. // For some reason, sound events seem to work even when they aren't registered, without even so much as a warning.
@@ -251,7 +258,7 @@ public final class WizardrySounds {
event.getRegistry().register(MISC_PAGE_TURN); event.getRegistry().register(MISC_PAGE_TURN);
event.getRegistry().register(MISC_FREEZE); event.getRegistry().register(MISC_FREEZE);
for(Spell spell : Spell.getSpells(Spell.allSpells)){ for(Spell spell : Spell.getAllSpells()){
event.getRegistry().registerAll(spell.getSounds()); event.getRegistry().registerAll(spell.getSounds());
} }
@@ -6,11 +6,13 @@ import electroblob.wizardry.util.NBTExtras;
import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities; import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction; import net.minecraft.item.EnumAction;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
@@ -32,9 +34,9 @@ public class ArcaneLock extends SpellRay {
@Override public boolean requiresPacket(){ return true; } @Override public boolean requiresPacket(){ return true; }
@Override public boolean canBeCastByDispensers(){ return false; } @Override public boolean canBeCastBy(TileEntityDispenser dispenser){ return false; }
@Override public boolean canBeCastByNPCs(){ return false; } @Override public boolean canBeCastBy(EntityLiving npc, boolean override){ return false; }
@Override @Override
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
@@ -136,7 +136,7 @@ public class Blink extends Spell {
} }
@Override @Override
public boolean canBeCastByNPCs(){ public boolean canBeCastBy(EntityLiving npc, boolean override){
return true; return true;
} }
@@ -12,6 +12,7 @@ import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.ParticleBuilder; import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.SharedMonsterAttributes; import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.monster.EntityZombie; import net.minecraft.entity.monster.EntityZombie;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
@@ -21,6 +22,7 @@ import net.minecraft.item.ItemStack;
import net.minecraft.pathfinding.Path; import net.minecraft.pathfinding.Path;
import net.minecraft.pathfinding.PathNodeType; import net.minecraft.pathfinding.PathNodeType;
import net.minecraft.pathfinding.PathPoint; import net.minecraft.pathfinding.PathPoint;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumHand; import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.util.text.TextComponentTranslation;
@@ -44,8 +46,8 @@ public class Clairvoyance extends Spell {
WizardData.registerStoredVariables(LOCATION_KEY, DIMENSION_KEY); WizardData.registerStoredVariables(LOCATION_KEY, DIMENSION_KEY);
} }
@Override public boolean canBeCastByNPCs() { return false; } @Override public boolean canBeCastBy(EntityLiving npc, boolean override) { return false; }
@Override public boolean canBeCastByDispensers() { return false; } @Override public boolean canBeCastBy(TileEntityDispenser dispenser) { return false; }
@Override @Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
@@ -3,6 +3,7 @@ package electroblob.wizardry.spell;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import electroblob.wizardry.item.IConjuredItem; import electroblob.wizardry.item.IConjuredItem;
import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.NBTExtras;
import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities; import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
@@ -40,7 +41,7 @@ public class ConjureArmour extends SpellConjuration {
armour = new ItemStack(SPECTRAL_ARMOUR_MAP.get(slot)); armour = new ItemStack(SPECTRAL_ARMOUR_MAP.get(slot));
IConjuredItem.setDurationMultiplier(armour, modifiers.get(WizardryItems.duration_upgrade)); IConjuredItem.setDurationMultiplier(armour, modifiers.get(WizardryItems.duration_upgrade));
// Sets a blank "ench" tag to trick the renderer into showing the enchantment effect on the armour model // Sets a blank "ench" tag to trick the renderer into showing the enchantment effect on the armour model
armour.getTagCompound().setTag("ench", new NBTTagList()); NBTExtras.storeTagSafely(armour.getTagCompound(), "ench", new NBTTagList());
caster.setItemStackToSlot(slot, armour); caster.setItemStackToSlot(slot, armour);
flag = true; flag = true;
} }
@@ -9,12 +9,14 @@ import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.*; import electroblob.wizardry.util.*;
import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.ParticleBuilder.Type;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction; import net.minecraft.item.EnumAction;
import net.minecraft.nbt.NBTTagList; import net.minecraft.nbt.NBTTagList;
import net.minecraft.nbt.NBTUtil; import net.minecraft.nbt.NBTUtil;
import net.minecraft.potion.PotionEffect; import net.minecraft.potion.PotionEffect;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.DamageSource; import net.minecraft.util.DamageSource;
import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
@@ -45,9 +47,9 @@ public class CurseOfSoulbinding extends SpellRay {
WizardData.registerStoredVariables(TARGETS_KEY); WizardData.registerStoredVariables(TARGETS_KEY);
} }
@Override public boolean canBeCastByNPCs() { return false; } @Override public boolean canBeCastBy(EntityLiving npc, boolean override) { return false; }
// You can't damage a dispenser so this would be nonsense! // You can't damage a dispenser so this would be nonsense!
@Override public boolean canBeCastByDispensers() { return false; } @Override public boolean canBeCastBy(TileEntityDispenser dispenser) { return false; }
@Override @Override
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
@@ -21,7 +21,7 @@ public class Decoy extends Spell {
addProperties(DECOY_LIFETIME, MOB_TRICK_CHANCE); addProperties(DECOY_LIFETIME, MOB_TRICK_CHANCE);
} }
@Override public boolean canBeCastByNPCs(){ return true; } @Override public boolean canBeCastBy(EntityLiving npc, boolean override){ return true; }
@Override @Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
@@ -85,7 +85,7 @@ public class DragonFireball extends Spell {
} }
@Override @Override
public boolean canBeCastByNPCs(){ public boolean canBeCastBy(EntityLiving npc, boolean override){
return true; return true;
} }
@@ -75,7 +75,8 @@ public class EmpoweringPresence extends Spell {
@SubscribeEvent(priority = EventPriority.LOW) // Doesn't really matter but there's no point processing it if casting is blocked @SubscribeEvent(priority = EventPriority.LOW) // Doesn't really matter but there's no point processing it if casting is blocked
public static void onSpellCastPreEvent(SpellCastEvent.Pre event){ public static void onSpellCastPreEvent(SpellCastEvent.Pre event){
// Empowerment stacks extra potency on top of the existing potency. // Empowerment stacks extra potency on top of the existing potency.
if(event.getCaster() != null && event.getCaster().isPotionActive(WizardryPotions.empowerment)){ if(event.getCaster() != null && event.getCaster().isPotionActive(WizardryPotions.empowerment)
&& !(event.getSpell() instanceof EmpoweringPresence)){ // Prevent exponential empowerment stacking!
float potency = 1 + Constants.EMPOWERMENT_POTENCY_PER_LEVEL float potency = 1 + Constants.EMPOWERMENT_POTENCY_PER_LEVEL
* (event.getCaster().getActivePotionEffect(WizardryPotions.empowerment).getAmplifier() + 1); * (event.getCaster().getActivePotionEffect(WizardryPotions.empowerment).getAmplifier() + 1);
@@ -11,6 +11,7 @@ import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction; import net.minecraft.item.EnumAction;
import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand; import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
@@ -29,8 +30,8 @@ public class ForestOfThorns extends Spell {
} }
@Override public boolean requiresPacket(){ return false; } @Override public boolean requiresPacket(){ return false; }
@Override public boolean canBeCastByNPCs(){ return true; } @Override public boolean canBeCastBy(EntityLiving npc, boolean override){ return true; }
@Override public boolean canBeCastByDispensers(){ return true; } @Override public boolean canBeCastBy(TileEntityDispenser dispenser){ return true; }
@Override @Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
@@ -9,7 +9,6 @@ import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction; import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumHand; import net.minecraft.util.EnumHand;
import net.minecraft.util.SoundEvent;
import net.minecraft.world.World; import net.minecraft.world.World;
public class Glide extends Spell { public class Glide extends Spell {
@@ -19,6 +19,7 @@ import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.EnumAction; import net.minecraft.item.EnumAction;
import net.minecraft.network.play.server.SPacketEntityVelocity; import net.minecraft.network.play.server.SPacketEntityVelocity;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand; import net.minecraft.util.EnumHand;
import net.minecraft.util.SoundEvent; import net.minecraft.util.SoundEvent;
@@ -58,12 +59,12 @@ public class Grapple extends Spell {
} }
@Override @Override
public boolean canBeCastByNPCs(){ public boolean canBeCastBy(EntityLiving npc, boolean override){
return super.canBeCastByNPCs(); return true;
} }
@Override @Override
public boolean canBeCastByDispensers(){ public boolean canBeCastBy(TileEntityDispenser dispenser){
return true; return true;
} }
@@ -9,12 +9,14 @@ import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities; import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.item.EntityTNTPrimed; import net.minecraft.entity.item.EntityTNTPrimed;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.EnumAction; import net.minecraft.item.EnumAction;
import net.minecraft.network.play.server.SPacketEntityVelocity; import net.minecraft.network.play.server.SPacketEntityVelocity;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumFacing;
import net.minecraft.util.SoundEvent; import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
@@ -41,8 +43,8 @@ public class GreaterTelekinesis extends SpellRay {
this.soundValues(0.8f, 1, 0.2f); this.soundValues(0.8f, 1, 0.2f);
} }
@Override public boolean canBeCastByNPCs() { return false; } @Override public boolean canBeCastBy(EntityLiving npc, boolean override) { return false; }
@Override public boolean canBeCastByDispensers() { return false; } @Override public boolean canBeCastBy(TileEntityDispenser dispenser) { return false; }
@Override @Override
protected SoundEvent[] createSounds(){ protected SoundEvent[] createSounds(){
@@ -10,6 +10,7 @@ import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities; import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.monster.*; import net.minecraft.entity.monster.*;
import net.minecraft.entity.passive.*; import net.minecraft.entity.passive.*;
@@ -52,7 +53,7 @@ public class Metamorphosis extends SpellRay {
this.soundValues(0.5f, 1f, 0); this.soundValues(0.5f, 1f, 0);
} }
@Override public boolean canBeCastByNPCs() { return false; } @Override public boolean canBeCastBy(EntityLiving npc, boolean override) { return false; }
@Override @Override
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
@@ -19,6 +19,7 @@ import net.minecraft.item.EnumAction;
import net.minecraft.item.EnumDyeColor; import net.minecraft.item.EnumDyeColor;
import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.potion.PotionEffect; import net.minecraft.potion.PotionEffect;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
@@ -43,8 +44,8 @@ public class MindControl extends SpellRay {
addProperties(EFFECT_DURATION); addProperties(EFFECT_DURATION);
} }
@Override public boolean canBeCastByNPCs() { return false; } @Override public boolean canBeCastBy(EntityLiving npc, boolean override) { return false; }
@Override public boolean canBeCastByDispensers() { return false; } @Override public boolean canBeCastBy(TileEntityDispenser dispenser) { return false; }
@Override @Override
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
@@ -8,6 +8,7 @@ import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities; import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.MobEffects; import net.minecraft.init.MobEffects;
@@ -28,7 +29,7 @@ public class Poison extends SpellRay {
} }
@Override @Override
public boolean canBeCastByNPCs(){ public boolean canBeCastBy(EntityLiving npc, boolean override){
return true; return true;
} }
@@ -16,6 +16,7 @@ import electroblob.wizardry.packet.PacketPossession;
import electroblob.wizardry.packet.WizardryPacketHandler; import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.NBTExtras;
import electroblob.wizardry.util.ParticleBuilder; import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.SpellModifiers;
@@ -43,6 +44,7 @@ import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagList; import net.minecraft.nbt.NBTTagList;
import net.minecraft.potion.PotionEffect; import net.minecraft.potion.PotionEffect;
import net.minecraft.potion.PotionUtils; import net.minecraft.potion.PotionUtils;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.*; import net.minecraft.util.*;
import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
@@ -123,8 +125,8 @@ public class Possession extends SpellRay {
addProperties(EFFECT_DURATION, CRITICAL_HEALTH); addProperties(EFFECT_DURATION, CRITICAL_HEALTH);
} }
@Override public boolean canBeCastByNPCs() { return false; } @Override public boolean canBeCastBy(EntityLiving npc, boolean override) { return false; }
@Override public boolean canBeCastByDispensers() { return false; } @Override public boolean canBeCastBy(TileEntityDispenser dispenser) { return false; }
@Override @Override
public boolean requiresPacket(){ public boolean requiresPacket(){
@@ -267,7 +269,7 @@ public class Possession extends SpellRay {
// Inventory and items // Inventory and items
if(possessor.getEntityData() != null){ if(possessor.getEntityData() != null){
possessor.getEntityData().setTag(INVENTORY_NBT_KEY, possessor.inventory.writeToNBT(new NBTTagList())); NBTExtras.storeTagSafely(possessor.getEntityData(), INVENTORY_NBT_KEY, possessor.inventory.writeToNBT(new NBTTagList()));
} }
possessor.inventory.clear(); possessor.inventory.clear();
@@ -1,6 +1,7 @@
package electroblob.wizardry.spell; package electroblob.wizardry.spell;
import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.EnumHand; import net.minecraft.util.EnumHand;
@@ -17,7 +18,7 @@ public class ReplenishHunger extends SpellBuff {
addProperties(HUNGER_POINTS, SATURATION_MODIFIER); addProperties(HUNGER_POINTS, SATURATION_MODIFIER);
} }
@Override public boolean canBeCastByNPCs(){ return false; } @Override public boolean canBeCastBy(EntityLiving npc, boolean override){ return false; }
@Override @Override
protected boolean applyEffects(EntityLivingBase caster, SpellModifiers modifiers){ protected boolean applyEffects(EntityLivingBase caster, SpellModifiers modifiers){
@@ -25,6 +25,8 @@ public class Resurrection extends Spell {
public static final String WAIT_TIME = "wait_time"; public static final String WAIT_TIME = "wait_time";
private static final String POTION_CORE_FIX_NBT_KEY = "Potion Core - Health Fix";
public Resurrection(){ public Resurrection(){
super("resurrection", EnumAction.NONE, false); super("resurrection", EnumAction.NONE, false);
addProperties(EFFECT_RADIUS, WAIT_TIME); addProperties(EFFECT_RADIUS, WAIT_TIME);
@@ -90,6 +92,8 @@ public class Resurrection extends Spell {
player.experience = 0; player.experience = 0;
player.experienceLevel = 0; player.experienceLevel = 0;
player.experienceTotal = 0; player.experienceTotal = 0;
// Not sure what potion core is 'fixing' but it breaks my resurrection, so let's unfix it!
player.getEntityData().removeTag(POTION_CORE_FIX_NBT_KEY);
if(player.world.isRemote){ if(player.world.isRemote){
ParticleBuilder.spawnHealParticles(player.world, player); ParticleBuilder.spawnHealParticles(player.world, player);
@@ -8,6 +8,7 @@ import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.EnumAction; import net.minecraft.item.EnumAction;
import net.minecraft.potion.PotionEffect; import net.minecraft.potion.PotionEffect;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
@@ -28,7 +29,7 @@ public class Reversal extends SpellRay {
} }
@Override @Override
public boolean canBeCastByDispensers(){ public boolean canBeCastBy(TileEntityDispenser dispenser){
return false; return false;
} }
@@ -1,6 +1,7 @@
package electroblob.wizardry.spell; package electroblob.wizardry.spell;
import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.EnumHand; import net.minecraft.util.EnumHand;
@@ -17,7 +18,7 @@ public class Satiety extends SpellBuff {
addProperties(HUNGER_POINTS, SATURATION_MODIFIER); addProperties(HUNGER_POINTS, SATURATION_MODIFIER);
} }
@Override public boolean canBeCastByNPCs(){ return false; } @Override public boolean canBeCastBy(EntityLiving npc, boolean override){ return false; }
@Override @Override
protected boolean applyEffects(EntityLivingBase caster, SpellModifiers modifiers){ protected boolean applyEffects(EntityLivingBase caster, SpellModifiers modifiers){
@@ -1,6 +1,7 @@
package electroblob.wizardry.spell; package electroblob.wizardry.spell;
import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.NBTExtras;
import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities; import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
@@ -12,6 +13,7 @@ import net.minecraft.entity.projectile.EntityShulkerBullet;
import net.minecraft.item.EnumAction; import net.minecraft.item.EnumAction;
import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTUtil; import net.minecraft.nbt.NBTUtil;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand; import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
@@ -29,9 +31,9 @@ public class ShulkerBullet extends Spell {
addProperties(RANGE); addProperties(RANGE);
} }
@Override public boolean canBeCastByNPCs(){ return true; } @Override public boolean canBeCastBy(EntityLiving npc, boolean override){ return true; }
@Override public boolean canBeCastByDispensers(){ return true; } @Override public boolean canBeCastBy(TileEntityDispenser dispenser){ return true; }
@Override @Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
@@ -89,7 +91,7 @@ public class ShulkerBullet extends Spell {
targetTag.setInteger("X", pos.getX()); targetTag.setInteger("X", pos.getX());
targetTag.setInteger("Y", pos.getY()); targetTag.setInteger("Y", pos.getY());
targetTag.setInteger("Z", pos.getZ()); targetTag.setInteger("Z", pos.getZ());
nbt.setTag("Target", targetTag); NBTExtras.storeTagSafely(nbt, "Target", targetTag);
bullet.readFromNBT(nbt); // LOL I just modified private fields without reflection bullet.readFromNBT(nbt); // LOL I just modified private fields without reflection
world.spawnEntity(bullet); world.spawnEntity(bullet);
@@ -3,7 +3,9 @@ package electroblob.wizardry.spell;
import electroblob.wizardry.Wizardry; import electroblob.wizardry.Wizardry;
import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.util.SpellModifiers; import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumHand; import net.minecraft.util.EnumHand;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World; import net.minecraft.world.World;
@@ -35,12 +37,12 @@ public class SlowTime extends SpellBuff {
} }
@Override @Override
public boolean canBeCastByDispensers(){ public boolean canBeCastBy(TileEntityDispenser dispenser){
return false; return false;
} }
@Override @Override
public boolean canBeCastByNPCs(){ public boolean canBeCastBy(EntityLiving npc, boolean override){
return false; return false;
} }
} }
@@ -1,45 +0,0 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.projectile.EntitySnowball;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
public class Snowball extends Spell {
public Snowball(){
super("snowball", EnumAction.NONE, false);
addProperties(RANGE);
soundValues(0.5f, 0.4f, 0.2f);
}
@Override
public boolean requiresPacket(){
return false;
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
if(!world.isRemote){
// Trajectory calculation - see SpellProjectile for a more detailed explanation
float g = 0.03f;
float launchHeight = caster.getEyeHeight();
float range = getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade);
float velocity = MathHelper.sqrt(MathHelper.sqrt(g*g * (launchHeight*launchHeight + range*range)) - g*launchHeight);
EntitySnowball snowball = new EntitySnowball(world, caster);
snowball.shoot(caster, caster.rotationPitch, caster.rotationYaw, 0.0f, velocity, 1.0f);
world.spawnEntity(snowball);
}
this.playSound(world, caster, ticksInUse, -1, modifiers);
caster.swingArm(hand);
return true;
}
}
@@ -20,6 +20,7 @@ import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.EnumAction; import net.minecraft.item.EnumAction;
import net.minecraft.item.Item; import net.minecraft.item.Item;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand; import net.minecraft.util.EnumHand;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
@@ -42,6 +43,7 @@ import net.minecraftforge.registries.IForgeRegistryEntry;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.*; import java.util.*;
import java.util.function.BiPredicate;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -149,6 +151,17 @@ public abstract class Spell extends IForgeRegistryEntry.Impl<Spell> implements C
/** The pitch variation of the sound played when this spell is cast. Defaults to 0. */ /** The pitch variation of the sound played when this spell is cast. Defaults to 0. */
protected float pitchVariation = 0; protected float pitchVariation = 0;
// The following two fields are supposed to eliminate the need for boilerplate classes in spell packs.
// As an example, spells in the twilight forest spell pack only appear on custom spell book and scroll items and
// NPCs can only cast them if they spawned in the twilight forest. Without these convenience fields, spells that
// would not otherwise require their own classes have to have one just so they can override those two behaviours.
/** List of items for which this spell is applicable (used by default behaviour of {@link Spell#applicableForItem(Item)}). */
protected Item[] applicableItems;
/** Predicate that specifies a condition that NPCs must satisfy in order to spawn with this spell equipped (used by
* default behaviour of {@link Spell#canBeCastBy(EntityLiving, boolean)}). */
protected BiPredicate<EntityLiving, Boolean> npcSelector; // Kinda ugly but it's better than boilerplate classes
private static int nextSpellId = 0; private static int nextSpellId = 0;
/** The spell's integer ID, mainly used for networking. */ /** The spell's integer ID, mainly used for networking. */
// This was added after I learnt the hard way why you can't assume Forge's registry IDs are sequential... // This was added after I learnt the hard way why you can't assume Forge's registry IDs are sequential...
@@ -186,6 +199,8 @@ public abstract class Spell extends IForgeRegistryEntry.Impl<Spell> implements C
this.icon = new ResourceLocation(modID, "textures/spells/" + name + ".png"); this.icon = new ResourceLocation(modID, "textures/spells/" + name + ".png");
this.sounds = createSounds(); this.sounds = createSounds();
this.id = nextSpellId++; this.id = nextSpellId++;
this.items(WizardryItems.spell_book, WizardryItems.scroll);
this.npcSelector((e, o) -> canBeCastByNPCs()); // Fallback to old behaviour until we remove it entirely
} }
// ========================================= Initialisation methods =========================================== // ========================================= Initialisation methods ===========================================
@@ -204,7 +219,7 @@ public abstract class Spell extends IForgeRegistryEntry.Impl<Spell> implements C
* @see Spell#playSound(World, double, double, double, int, int, SpellModifiers, String...) * @see Spell#playSound(World, double, double, double, int, int, SpellModifiers, String...)
*/ */
protected SoundEvent[] createSounds(){ protected SoundEvent[] createSounds(){
return new SoundEvent[]{WizardrySounds.createSound("spell." + this.getRegistryName().getPath())}; return new SoundEvent[]{WizardrySounds.createSound(this.getRegistryName().getNamespace(), "spell." + this.getRegistryName().getPath())};
} }
// Note 1: The aim here is conciseness. Keeping the identifiers in the spell classes means we don't usually have to // Note 1: The aim here is conciseness. Keeping the identifiers in the spell classes means we don't usually have to
@@ -294,7 +309,7 @@ public abstract class Spell extends IForgeRegistryEntry.Impl<Spell> implements C
* @see Spell#createSoundsWithSuffixes(String[]) * @see Spell#createSoundsWithSuffixes(String[])
*/ */
public final SoundEvent createSoundWithSuffix(String suffix){ public final SoundEvent createSoundWithSuffix(String suffix){
return WizardrySounds.createSound("spell." + this.getRegistryName().getPath() + "." + suffix); return WizardrySounds.createSound(this.getRegistryName().getNamespace(), "spell." + this.getRegistryName().getPath() + "." + suffix);
} }
/** /**
@@ -354,7 +369,7 @@ public abstract class Spell extends IForgeRegistryEntry.Impl<Spell> implements C
* work if the caster is on full health). * work if the caster is on full health).
* <p></p> * <p></p>
* This method is intended for use by NPCs (see {@link EntityWizard}) so that they can cast spells. Override it if * This method is intended for use by NPCs (see {@link EntityWizard}) so that they can cast spells. Override it if
* you want a spell to be cast by wizards. Note that you must also override {@link Spell#canBeCastByNPCs()} to * you want a spell to be cast by wizards. Note that you must also override {@link Spell#canBeCastBy(EntityLiving, boolean)} to
* return true to allow wizards to select the spell. For some spells, this method may well be exactly the same as * return true to allow wizards to select the spell. For some spells, this method may well be exactly the same as
* the regular cast method; for others it won't be - for example, projectile-based spells are normally done using * the regular cast method; for others it won't be - for example, projectile-based spells are normally done using
* the player's look vector, but NPCs need to use a target-based method instead. * the player's look vector, but NPCs need to use a target-based method instead.
@@ -390,7 +405,7 @@ public abstract class Spell extends IForgeRegistryEntry.Impl<Spell> implements C
* won't work if the caster is on full health). * won't work if the caster is on full health).
* <p></p> * <p></p>
* This method is intended for use by dispensers and command blocks so that they can cast spells. Override it if * This method is intended for use by dispensers and command blocks so that they can cast spells. Override it if
* you want a spell to be cast by dispensers. Note that you must also override {@link Spell#canBeCastByDispensers()} to * you want a spell to be cast by dispensers. Note that you must also override {@link Spell#canBeCastBy(TileEntityDispenser)} to
* return true to allow dispensers to select the spell. For some spells, this method may well be exactly the same as * return true to allow dispensers to select the spell. For some spells, this method may well be exactly the same as
* the regular cast method; for others it won't be - for example, projectile-based spells are normally done using * the regular cast method; for others it won't be - for example, projectile-based spells are normally done using
* the player's look vector, but dispensers need to use a facing-based method instead. * the player's look vector, but dispensers need to use a facing-based method instead.
@@ -446,20 +461,50 @@ public abstract class Spell extends IForgeRegistryEntry.Impl<Spell> implements C
public void finishCasting(World world, @Nullable EntityLivingBase caster, double x, double y, double z, public void finishCasting(World world, @Nullable EntityLivingBase caster, double x, double y, double z,
@Nullable EnumFacing direction, int duration, SpellModifiers modifiers){} @Nullable EnumFacing direction, int duration, SpellModifiers modifiers){}
/**
* Whether the given entity can cast this spell. If you have overridden
* {@link Spell#cast(World, EntityLiving, EnumHand, int, EntityLivingBase, SpellModifiers)}, you should override
* this to return true (either always or under certain circumstances), or alternatively assign an NPC selector via
* {@link Spell#npcSelector(BiPredicate)} (recommended for general spell classes).
* @param npc The entity to query.
* @param override True if a player in creative mode is assigning this spell to the given entity, false otherwise.
* Usually this means situational conditions should be ignored.
*/
// We could make this final and force everyone to move over to the predicate system, but for particularly complex
// behaviour (i.e. several lines of code) it gets too ugly, and then you end up moving the contents of the predicate
// to a static method anyway and referring to it via method reference... so we may as well leave people the option.
public boolean canBeCastBy(EntityLiving npc, boolean override){
return npcSelector.test(npc, override);
}
/** /**
* Whether NPCs such as wizards can cast this spell. If you have overridden * Whether NPCs such as wizards can cast this spell. If you have overridden
* {@link Spell#cast(World, EntityLiving, EnumHand, int, EntityLivingBase, SpellModifiers)}, you should override * {@link Spell#cast(World, EntityLiving, EnumHand, int, EntityLivingBase, SpellModifiers)}, you should override
* this to return true. * this to return true.
* @deprecated Use the entity-sensitive version {@link Spell#canBeCastBy(EntityLiving, boolean)}.
*/ */
@Deprecated
public boolean canBeCastByNPCs(){ public boolean canBeCastByNPCs(){
return false; return false;
} }
/**
* Whether the given dispenser can cast this spell. If you have overridden
* {@link Spell#cast(World, double, double, double, EnumFacing, int, int, SpellModifiers)}, you should override this
* to return true (either always or under certain circumstances).
* @param dispenser The dispenser to query.
*/
public boolean canBeCastBy(TileEntityDispenser dispenser){
return canBeCastByDispensers();
}
/** /**
* Whether dispensers can cast this spell. If you have overridden * Whether dispensers can cast this spell. If you have overridden
* {@link Spell#cast(World, double, double, double, EnumFacing, int, int, SpellModifiers)}, you should override this * {@link Spell#cast(World, double, double, double, EnumFacing, int, int, SpellModifiers)}, you should override this
* to return true. * to return true.
* @deprecated Use the tileentity-sensitive version {@link Spell#canBeCastBy(TileEntityDispenser)}.
*/ */
@Deprecated
public boolean canBeCastByDispensers(){ public boolean canBeCastByDispensers(){
return false; return false;
} }
@@ -561,26 +606,6 @@ public abstract class Spell extends IForgeRegistryEntry.Impl<Spell> implements C
return properties.getBaseValue(identifier); return properties.getBaseValue(identifier);
} }
/** Returns whether the spell is enabled in any of the given {@link electroblob.wizardry.util.SpellProperties.Context Context}s.
* A spell may be disabled globally in the config, or it may be disabled for one or more specific contexts in
* its JSON file using a resource pack. If called with no arguments, defaults to any context, i.e. only returns
* false if the spell is completely disabled in all contexts. */
public final boolean isEnabled(SpellProperties.Context... contexts){
return enabled && (contexts.length == 0 || properties.isEnabled(contexts));
}
/** Sets whether the spell is enabled or not. */
public final void setEnabled(boolean isEnabled){
this.enabled = isEnabled;
}
/** Returns true if the given item has a variant for this spell. By default, returns true if the given item is
* either {@link WizardryItems#spell_book} or {@link WizardryItems#scroll}. Override to give the spell a special
* type of book or scroll. */
public boolean applicableForItem(Item item){
return item == WizardryItems.spell_book || item == WizardryItems.scroll;
}
/** /**
* Returns the unlocalised name of the spell, without any prefixes or suffixes, e.g. "flame_ray". <b>This should * Returns the unlocalised name of the spell, without any prefixes or suffixes, e.g. "flame_ray". <b>This should
* only be used for translation purposes.</b> * only be used for translation purposes.</b>
@@ -767,6 +792,51 @@ public abstract class Spell extends IForgeRegistryEntry.Impl<Spell> implements C
} }
} }
// ============================================= Misc methods ===============================================
/** Returns whether the spell is enabled in any of the given {@link electroblob.wizardry.util.SpellProperties.Context Context}s.
* A spell may be disabled globally in the config, or it may be disabled for one or more specific contexts in
* its JSON file using a resource pack. If called with no arguments, defaults to any context, i.e. only returns
* false if the spell is completely disabled in all contexts. */
public final boolean isEnabled(SpellProperties.Context... contexts){
return enabled && (contexts.length == 0 || properties.isEnabled(contexts));
}
/** Sets whether the spell is enabled or not. */
public final void setEnabled(boolean isEnabled){
this.enabled = isEnabled;
}
/** Returns true if the given item has a variant for this spell. By default, returns true if the given item is
* in this spell's {@link Spell#applicableItems} list (set using {@link Spell#items(Item...)}). Override to do
* something more complex. */
public boolean applicableForItem(Item item){
return Arrays.asList(applicableItems).contains(item);
}
/**
* Sets which items this spell can appear on (these default to the regular spell book and scroll).
* @param applicableItems The items this spell should naturally appear on (or no items at all).
* @return The spell instance, allowing this method to be chained onto the constructor. Note that since this method
* only returns a {@code Spell}, if you are chaining multiple methods onto the constructor this should be called last.
*/
public Spell items(Item... applicableItems){
this.applicableItems = applicableItems;
return this;
}
/**
* Specifies a condition that NPCs must satisfy in order to spawn with this spell equipped (this defaults to always
* true).
* @param selector A condition that NPCs must satisfy in order to spawn with this spell equipped.
* @return The spell instance, allowing this method to be chained onto the constructor. Note that since this method
* only returns a {@code Spell}, if you are chaining multiple methods onto the constructor this should be called last.
*/
public Spell npcSelector(BiPredicate<EntityLiving, Boolean> selector){
this.npcSelector = selector;
return this;
}
// Spells are sorted according to tier and element. Where several spells have the same tier and element, // Spells are sorted according to tier and element. Where several spells have the same tier and element,
// they will remain in the order they were registered. // they will remain in the order they were registered.
@Override @Override
@@ -785,7 +855,7 @@ public abstract class Spell extends IForgeRegistryEntry.Impl<Spell> implements C
/** /**
* Returns the total number of registered spells, excluding the 'None' spell. Returns the same number that would be * Returns the total number of registered spells, excluding the 'None' spell. Returns the same number that would be
* returned by {@code Spell.getSpells(Spell.allSpells).size()}, but this method is more efficient. * returned by {@code Spell.getAllSpells().size()}, but this method is more efficient.
*/ */
public static int getTotalSpellCount(){ public static int getTotalSpellCount(){
return registry.getValuesCollection().size() - 1; return registry.getValuesCollection().size() - 1;
@@ -833,31 +903,37 @@ public abstract class Spell extends IForgeRegistryEntry.Impl<Spell> implements C
/** /**
* Returns a list containing all spells matching the given {@link Predicate}. The returned list is separate from the * Returns a list containing all spells matching the given {@link Predicate}. The returned list is separate from the
* internal spells list; any changes you make to the returned list will have no effect on wizardry since the * internal spells list; any changes you make to the returned list will have no effect on wizardry since the
* returned list is local to this method. Never includes the {@link None} spell. For convenience, there are some * returned list is local to this method. Never includes the {@link None} spell.
* predefined predicates in the Spell class (some of these really aren't shortcuts any more):
* <p></p>
* {@link Spell#allSpells} will allow all spells to be returned<br>
* {@link Spell#npcSpells} will only allow enabled spells that can be cast by NPCs (see
* {@link Spell#canBeCastByNPCs()})<br>
* {@link Spell#nonContinuousSpells} will filter out continuous spells but not disabled spells<br>
* {@link TierElementFilter} will only allow enabled spells of the specified tier and element
* *
* @param filter A <code>Predicate&ltSpell&gt</code> that the returned spells must satisfy. * @param filter A <code>Predicate&ltSpell&gt</code> that the returned spells must satisfy.
* *
* @return A <b>local, modifiable</b> list of spells matching the given predicate. <i>Note that this list may be * @return A <b>local, modifiable</b> list of spells matching the given predicate. <i>Note that this list may be
* empty.</i> * empty.</i>
*
* @see TierElementFilter
*/ */
public static List<Spell> getSpells(Predicate<Spell> filter){ public static List<Spell> getSpells(Predicate<Spell> filter){
return registry.getValuesCollection().stream().filter(filter.and(s -> s != Spells.none)).collect(Collectors.toList()); return registry.getValuesCollection().stream().filter(filter.and(s -> s != Spells.none)).collect(Collectors.toList());
} }
/** Predicate which allows all spells. */ /** Returns all registered spells, except the {@link None} spell. */
public static List<Spell> getAllSpells(){
return getSpells(s -> true);
}
/** Predicate which allows all spells.
* @deprecated Use {@link Spell#getAllSpells()}. */
@Deprecated
public static Predicate<Spell> allSpells = s -> true; public static Predicate<Spell> allSpells = s -> true;
/** Predicate which allows all non-continuous spells, even those that have been disabled. */ /** Predicate which allows all non-continuous spells, even those that have been disabled.
* @deprecated Nobody ever uses this now we have continuous scrolls, if you really need it just use a lambda. */
@Deprecated
public static Predicate<Spell> nonContinuousSpells = s -> !s.isContinuous; public static Predicate<Spell> nonContinuousSpells = s -> !s.isContinuous;
/** Predicate which allows all enabled spells for which {@link Spell#canBeCastByNPCs()} returns true. */ /** Predicate which allows all enabled spells for which {@link Spell#canBeCastBy(EntityLiving, boolean)} returns true.
* @deprecated in favour of entity-sensitive version, use a lambda expression directly. */
@Deprecated
public static Predicate<Spell> npcSpells = s -> s.isEnabled(SpellProperties.Context.NPCS) && s.canBeCastByNPCs(); public static Predicate<Spell> npcSpells = s -> s.isEnabled(SpellProperties.Context.NPCS) && s.canBeCastByNPCs();
/** /**
@@ -11,6 +11,7 @@ import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction; import net.minecraft.item.EnumAction;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand; import net.minecraft.util.EnumHand;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
@@ -29,9 +30,9 @@ import java.util.function.Function;
* <p></p> * <p></p>
* Properties added by this type of spell: {@link Spell#RANGE} * Properties added by this type of spell: {@link Spell#RANGE}
* <p></p> * <p></p>
* By default, this type of spell can be cast by NPCs. {@link Spell#canBeCastByNPCs()} * By default, this type of spell can be cast by NPCs. {@link Spell#canBeCastBy(EntityLiving, boolean)}
* <p></p> * <p></p>
* By default, this type of spell can be cast by dispensers. {@link Spell#canBeCastByDispensers()} * By default, this type of spell can be cast by dispensers. {@link Spell#canBeCastBy(TileEntityDispenser)}
* <p></p> * <p></p>
* By default, this type of spell does not require a packet to be sent. {@link Spell#requiresPacket()} * By default, this type of spell does not require a packet to be sent. {@link Spell#requiresPacket()}
* *
@@ -58,13 +59,12 @@ public class SpellArrow<T extends EntityMagicArrow> extends Spell {
super(modID, name, EnumAction.NONE, false); super(modID, name, EnumAction.NONE, false);
this.arrowFactory = arrowFactory; this.arrowFactory = arrowFactory;
this.addProperties(RANGE); this.addProperties(RANGE);
this.npcSelector((e, o) -> true);
} }
@Override public boolean requiresPacket(){ return false; } @Override public boolean requiresPacket(){ return false; }
@Override public boolean canBeCastByNPCs(){ return true; } @Override public boolean canBeCastBy(TileEntityDispenser dispenser) { return true; }
@Override public boolean canBeCastByDispensers() { return true; }
/** Computes the velocity the projectile should be launched at to achieve the required range. */ /** Computes the velocity the projectile should be launched at to achieve the required range. */
// Long story short, it doesn't make much sense to me to have the JSON file specify the velocity - even less so if // Long story short, it doesn't make much sense to me to have the JSON file specify the velocity - even less so if
@@ -11,6 +11,7 @@ import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction; import net.minecraft.item.EnumAction;
import net.minecraft.potion.Potion; import net.minecraft.potion.Potion;
import net.minecraft.potion.PotionEffect; import net.minecraft.potion.PotionEffect;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand; import net.minecraft.util.EnumHand;
import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.AxisAlignedBB;
@@ -32,9 +33,9 @@ import java.util.stream.Collectors;
* <p></p> * <p></p>
* Properties added by this type of spell: {@link SpellBuff#getDurationKey(Potion)}, {@link SpellBuff#getStrengthKey(Potion)} * Properties added by this type of spell: {@link SpellBuff#getDurationKey(Potion)}, {@link SpellBuff#getStrengthKey(Potion)}
* <p></p> * <p></p>
* By default, this type of spell can be cast by NPCs. {@link Spell#canBeCastByNPCs()} * By default, this type of spell can be cast by NPCs. {@link Spell#canBeCastBy(EntityLiving, boolean)}
* <p></p> * <p></p>
* By default, this type of spell can be cast by dispensers. {@link Spell#canBeCastByDispensers()} * By default, this type of spell can be cast by dispensers. {@link Spell#canBeCastBy(TileEntityDispenser)}
* <p></p> * <p></p>
* By default, this type of spell requires a packet to be sent. {@link Spell#requiresPacket()} * By default, this type of spell requires a packet to be sent. {@link Spell#requiresPacket()}
* *
@@ -67,6 +68,7 @@ public class SpellBuff extends Spell {
this.r = r; this.r = r;
this.g = g; this.g = g;
this.b = b; this.b = b;
this.npcSelector((e, o) -> true);
} }
@Override @Override
@@ -103,9 +105,7 @@ public class SpellBuff extends Spell {
return this; return this;
} }
@Override public boolean canBeCastByNPCs(){ return true; } @Override public boolean canBeCastBy(TileEntityDispenser dispenser) { return true; }
@Override public boolean canBeCastByDispensers() { return true; }
@Override @Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
@@ -24,9 +24,9 @@ import net.minecraft.world.World;
* <p></p> * <p></p>
* Properties added by this type of spell: {@link SpellConjuration#ITEM_LIFETIME} * Properties added by this type of spell: {@link SpellConjuration#ITEM_LIFETIME}
* <p></p> * <p></p>
* By default, this type of spell cannot be cast by NPCs. {@link Spell#canBeCastByNPCs()} * By default, this type of spell cannot be cast by NPCs. {@link Spell#canBeCastBy(net.minecraft.entity.EntityLiving, boolean)}
* <p></p> * <p></p>
* By default, this type of spell cannot be cast by dispensers. {@link Spell#canBeCastByDispensers()} * By default, this type of spell cannot be cast by dispensers. {@link Spell#canBeCastBy(net.minecraft.tileentity.TileEntityDispenser)}
* <p></p> * <p></p>
* By default, this type of spell requires a packet to be sent. {@link Spell#requiresPacket()} * By default, this type of spell requires a packet to be sent. {@link Spell#requiresPacket()}
* *
@@ -9,6 +9,7 @@ import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction; import net.minecraft.item.EnumAction;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand; import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
@@ -29,9 +30,9 @@ import java.util.function.Function;
* <p></p> * <p></p>
* Properties added by this type of spell: {@link Spell#DURATION} (if the construct is not permanent) * Properties added by this type of spell: {@link Spell#DURATION} (if the construct is not permanent)
* <p></p> * <p></p>
* By default, this type of spell can be cast by NPCs. {@link Spell#canBeCastByNPCs()} * By default, this type of spell can be cast by NPCs. {@link Spell#canBeCastBy(EntityLiving, boolean)}
* <p></p> * <p></p>
* By default, this type of spell can be cast by dispensers. {@link Spell#canBeCastByDispensers()} * By default, this type of spell can be cast by dispensers. {@link Spell#canBeCastBy(TileEntityDispenser)}
* <p></p> * <p></p>
* By default, this type of spell does not require a packet to be sent. {@link Spell#requiresPacket()} * By default, this type of spell does not require a packet to be sent. {@link Spell#requiresPacket()}
* *
@@ -58,14 +59,13 @@ public class SpellConstruct<T extends EntityMagicConstruct> extends Spell {
super(modID, name, action, false); super(modID, name, action, false);
this.constructFactory = constructFactory; this.constructFactory = constructFactory;
this.permanent = permanent; this.permanent = permanent;
this.npcSelector((e, o) -> true);
if(!permanent) this.addProperties(DURATION); if(!permanent) this.addProperties(DURATION);
} }
@Override public boolean requiresPacket(){ return false; } @Override public boolean requiresPacket(){ return false; }
@Override public boolean canBeCastByNPCs(){ return true; }
@Override public boolean canBeCastByDispensers() { return true; } @Override public boolean canBeCastBy(TileEntityDispenser dispenser) { return true; }
/** /**
* Sets whether the construct must be spawned on the ground. * Sets whether the construct must be spawned on the ground.
@@ -10,6 +10,7 @@ import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction; import net.minecraft.item.EnumAction;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand; import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
@@ -30,9 +31,9 @@ import java.util.function.Function;
* Properties added by this type of spell: {@link Spell#RANGE}, {@link Spell#DURATION} (if the construct is not * Properties added by this type of spell: {@link Spell#RANGE}, {@link Spell#DURATION} (if the construct is not
* permanent) * permanent)
* <p></p> * <p></p>
* By default, this type of spell can be cast by NPCs. {@link Spell#canBeCastByNPCs()} * By default, this type of spell can be cast by NPCs. {@link Spell#canBeCastBy(EntityLiving, boolean)}
* <p></p> * <p></p>
* By default, this type of spell can be cast by dispensers. {@link Spell#canBeCastByDispensers()} * By default, this type of spell can be cast by dispensers. {@link Spell#canBeCastBy(TileEntityDispenser)}
* <p></p> * <p></p>
* By default, this type of spell does not require a packet to be sent. {@link Spell#requiresPacket()} * By default, this type of spell does not require a packet to be sent. {@link Spell#requiresPacket()}
* *
@@ -54,6 +55,7 @@ public class SpellConstructRanged<T extends EntityMagicConstruct> extends SpellC
public SpellConstructRanged(String modID, String name, Function<World, T> constructFactory, boolean permanent){ public SpellConstructRanged(String modID, String name, Function<World, T> constructFactory, boolean permanent){
super(modID, name, EnumAction.NONE, constructFactory, permanent); super(modID, name, EnumAction.NONE, constructFactory, permanent);
this.addProperties(RANGE); this.addProperties(RANGE);
this.npcSelector((e, o) -> true);
} }
/** /**
@@ -79,10 +81,8 @@ public class SpellConstructRanged<T extends EntityMagicConstruct> extends SpellC
} }
@Override public boolean requiresPacket(){ return false; } @Override public boolean requiresPacket(){ return false; }
@Override public boolean canBeCastByNPCs(){ return true; }
@Override public boolean canBeCastByDispensers() { return true; } @Override public boolean canBeCastBy(TileEntityDispenser dispenser) { return true; }
@Override @Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
@@ -14,6 +14,7 @@ import net.minecraft.entity.ai.attributes.AttributeModifier;
import net.minecraft.entity.ai.attributes.IAttributeInstance; import net.minecraft.entity.ai.attributes.IAttributeInstance;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction; import net.minecraft.item.EnumAction;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand; import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
@@ -32,9 +33,9 @@ import java.util.function.Function;
* <p></p> * <p></p>
* Properties added by this type of spell: {@link SpellMinion#MINION_LIFETIME} * Properties added by this type of spell: {@link SpellMinion#MINION_LIFETIME}
* <p></p> * <p></p>
* By default, this type of spell can be cast by NPCs. {@link Spell#canBeCastByNPCs()} * By default, this type of spell can be cast by NPCs. {@link Spell#canBeCastBy(EntityLiving, boolean)}
* <p></p> * <p></p>
* By default, this type of spell can be cast by dispensers. {@link Spell#canBeCastByDispensers()} * By default, this type of spell can be cast by dispensers. {@link Spell#canBeCastBy(TileEntityDispenser)}
* <p></p> * <p></p>
* By default, this type of spell does not require a packet to be sent. {@link Spell#requiresPacket()} * By default, this type of spell does not require a packet to be sent. {@link Spell#requiresPacket()}
* *
@@ -66,6 +67,7 @@ public class SpellMinion<T extends EntityLiving & ISummonedCreature> extends Spe
super(modID, name, EnumAction.BOW, false); super(modID, name, EnumAction.BOW, false);
this.minionFactory = minionFactory; this.minionFactory = minionFactory;
addProperties(MINION_LIFETIME, MINION_COUNT, SUMMON_RADIUS); addProperties(MINION_LIFETIME, MINION_COUNT, SUMMON_RADIUS);
this.npcSelector((e, o) -> true);
} }
/** /**
@@ -80,9 +82,7 @@ public class SpellMinion<T extends EntityLiving & ISummonedCreature> extends Spe
@Override public boolean requiresPacket(){ return false; } @Override public boolean requiresPacket(){ return false; }
@Override public boolean canBeCastByNPCs(){ return true; } @Override public boolean canBeCastBy(TileEntityDispenser dispenser) { return true; }
@Override public boolean canBeCastByDispensers() { return true; }
@Override @Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
@@ -11,6 +11,7 @@ import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction; import net.minecraft.item.EnumAction;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand; import net.minecraft.util.EnumHand;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
@@ -29,9 +30,9 @@ import java.util.function.Function;
* <p></p> * <p></p>
* Properties added by this type of spell: {@link Spell#RANGE} * Properties added by this type of spell: {@link Spell#RANGE}
* <p></p> * <p></p>
* By default, this type of spell can be cast by NPCs. {@link Spell#canBeCastByNPCs()} * By default, this type of spell can be cast by NPCs. {@link Spell#canBeCastBy(EntityLiving, boolean)}
* <p></p> * <p></p>
* By default, this type of spell can be cast by dispensers. {@link Spell#canBeCastByDispensers()} * By default, this type of spell can be cast by dispensers. {@link Spell#canBeCastBy(TileEntityDispenser)}
* <p></p> * <p></p>
* By default, this type of spell does not require a packet to be sent. {@link Spell#requiresPacket()} * By default, this type of spell does not require a packet to be sent. {@link Spell#requiresPacket()}
* *
@@ -58,13 +59,12 @@ public class SpellProjectile<T extends EntityMagicProjectile> extends Spell {
super(modID, name, EnumAction.NONE, false); super(modID, name, EnumAction.NONE, false);
this.projectileFactory = projectileFactory; this.projectileFactory = projectileFactory;
addProperties(RANGE); addProperties(RANGE);
this.npcSelector((e, o) -> true);
} }
@Override public boolean requiresPacket(){ return false; } @Override public boolean requiresPacket(){ return false; }
@Override public boolean canBeCastByNPCs(){ return true; } @Override public boolean canBeCastBy(TileEntityDispenser dispenser) { return true; }
@Override public boolean canBeCastByDispensers() { return true; }
/** Computes the velocity the projectile should be launched at to achieve the required range. */ /** Computes the velocity the projectile should be launched at to achieve the required range. */
// Long story short, it doesn't make much sense to me to have the JSON file specify the velocity - even less so if // Long story short, it doesn't make much sense to me to have the JSON file specify the velocity - even less so if
@@ -10,6 +10,7 @@ import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction; import net.minecraft.item.EnumAction;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand; import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
@@ -22,7 +23,7 @@ import javax.annotation.Nullable;
/** /**
* Generic superclass for all spells which use a raytrace to do something and (optionally) spawn particles along that * Generic superclass for all spells which use a raytrace to do something and (optionally) spawn particles along that
* trajectory. This is for both continuous ('stream') spells and non-continuous ('bolt') spells This allows all the * trajectory. This is for both continuous ('stream') spells and non-continuous ('bolt') spells. This allows all the
* relevant code to be centralised. This class differs from most other spell superclasses in that it is abstract and as * relevant code to be centralised. This class differs from most other spell superclasses in that it is abstract and as
* such must be subclassed to define what the spell actually does. This is because ray-like spells do a wider variety of * such must be subclassed to define what the spell actually does. This is because ray-like spells do a wider variety of
* different things, so it does not make sense to define more specific functions in this class since they would be * different things, so it does not make sense to define more specific functions in this class since they would be
@@ -34,9 +35,9 @@ import javax.annotation.Nullable;
* <p></p> * <p></p>
* Properties added by this type of spell: {@link Spell#RANGE} * Properties added by this type of spell: {@link Spell#RANGE}
* <p></p> * <p></p>
* By default, this type of spell can be cast by NPCs. {@link Spell#canBeCastByNPCs()} * By default, this type of spell can be cast by NPCs. {@link Spell#canBeCastBy(EntityLiving, boolean)}
* <p></p> * <p></p>
* By default, this type of spell can be cast by dispensers. {@link Spell#canBeCastByDispensers()} * By default, this type of spell can be cast by dispensers. {@link Spell#canBeCastBy(TileEntityDispenser)}
* <p></p> * <p></p>
* By default, this type of spell requires a packet to be sent. {@link Spell#requiresPacket()} * By default, this type of spell requires a packet to be sent. {@link Spell#requiresPacket()}
* *
@@ -72,6 +73,7 @@ public abstract class SpellRay extends Spell {
public SpellRay(String modID, String name, boolean isContinuous, EnumAction action){ public SpellRay(String modID, String name, boolean isContinuous, EnumAction action){
super(modID, name, action, isContinuous); super(modID, name, action, isContinuous);
this.addProperties(RANGE); this.addProperties(RANGE);
this.npcSelector((e, o) -> true);
} }
// Although this class is abstract, someone might instantiate one of its subclasses more than once to make two // Although this class is abstract, someone might instantiate one of its subclasses more than once to make two
@@ -150,10 +152,8 @@ public abstract class SpellRay extends Spell {
this.aimAssist = aimAssist; this.aimAssist = aimAssist;
return this; return this;
} }
@Override public boolean canBeCastByNPCs(){ return true; } @Override public boolean canBeCastBy(TileEntityDispenser dispenser) { return true; }
@Override public boolean canBeCastByDispensers() { return true; }
// Finally everything in here is standardised and written in a form that's actually readable - it was long overdue! // Finally everything in here is standardised and written in a form that's actually readable - it was long overdue!
@Override @Override
@@ -0,0 +1,142 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.entity.living.ISpellCaster;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.projectile.EntityThrowable;
import net.minecraft.item.EnumAction;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import java.util.function.BiFunction;
/**
* Similar to {@link electroblob.wizardry.spell.SpellProjectile}, but for any {@link EntityThrowable}.
* This allows all the relevant code to be centralised, since these spells all work in the same way. Usually, a simple
* instantiation of this class is sufficient to create a projectile spell; if something extra needs to be done, such as
* particle spawning, then methods can be overridden (perhaps using an anonymous class) to add the required functionality.
* <p></p>
* <i>N.B. It is advised that this class is only used where the projectile to be launched belongs to vanilla Minecraft
* or another mod; no guarantees are made as to the behaviour of such projectiles!</i>
* <p></p>
* Properties added by this type of spell: {@link Spell#RANGE}
* <p></p>
* By default, this type of spell can be cast by NPCs. {@link Spell#canBeCastBy(EntityLiving, boolean)}
* <p></p>
* By default, this type of spell cannot be cast by dispensers. {@link Spell#canBeCastBy(TileEntityDispenser)}
* <p></p>
* By default, this type of spell does not require a packet to be sent. {@link Spell#requiresPacket()}
*
* @author Electroblob
* @since Wizardry 4.2.8
*/
// TODO: Use events to make these projectiles seek targets when the caster is wearing a ring of attraction (is this possible?)
public class SpellThrowable<T extends EntityThrowable> extends Spell {
private static final float LAUNCH_Y_OFFSET = 0.1f;
protected final BiFunction<World, EntityLivingBase, T> projectileFactory;
public SpellThrowable(String name, BiFunction<World, EntityLivingBase, T> projectileFactory){
this(Wizardry.MODID, name, projectileFactory);
}
public SpellThrowable(String modID, String name, BiFunction<World, EntityLivingBase, T> projectileFactory){
super(modID, name, EnumAction.NONE, false);
this.projectileFactory = projectileFactory;
addProperties(RANGE);
this.npcSelector((e, o) -> true);
}
@Override
public boolean requiresPacket(){
return false;
}
/** Trajectory calculation - see {@link SpellProjectile} for a more detailed explanation */
protected float calculateVelocity(SpellModifiers modifiers, float launchHeight){
float g = 0.03f;
float range = getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade);
return range / MathHelper.sqrt(2 * launchHeight/g);
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
if(!world.isRemote){
float velocity = calculateVelocity(modifiers, caster.getEyeHeight() - LAUNCH_Y_OFFSET);
T projectile = projectileFactory.apply(world, caster);
projectile.shoot(caster, caster.rotationPitch, caster.rotationYaw, 0.0f, velocity, 1.0f);
addProjectileExtras(projectile, caster, modifiers);
world.spawnEntity(projectile);
}
this.playSound(world, caster, ticksInUse, -1, modifiers);
caster.swingArm(hand);
return true;
}
@Override
public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, SpellModifiers modifiers){
if(target != null){
if(!world.isRemote){
float velocity = calculateVelocity(modifiers, caster.getEyeHeight() - LAUNCH_Y_OFFSET);
T projectile = projectileFactory.apply(world, caster);
int aimingError = caster instanceof ISpellCaster ? ((ISpellCaster)caster).getAimingError(world.getDifficulty())
: WizardryUtilities.getDefaultAimingError(world.getDifficulty());
aim(projectile, caster, target, velocity, aimingError);
addProjectileExtras(projectile, caster, modifiers);
world.spawnEntity(projectile);
}
this.playSound(world, caster, ticksInUse, -1, modifiers);
caster.swingArm(hand);
return true;
}
return false;
}
// Copied from EntityMagicProjectile (ugh what a mess)
private void aim(T throwable, EntityLivingBase caster, Entity target, float speed, float aimingError){
throwable.ignoreEntity = caster;
throwable.posY = caster.getEntityBoundingBox().minY + (double)caster.getEyeHeight() - LAUNCH_Y_OFFSET;
double dx = target.posX - caster.posX;
double dy = !throwable.hasNoGravity() ? target.getEntityBoundingBox().minY + (double)(target.height / 3.0f) - throwable.posY
: target.getEntityBoundingBox().minY + (double)(target.height / 2.0f) - throwable.posY;
double dz = target.posZ - caster.posZ;
double horizontalDistance = MathHelper.sqrt(dx * dx + dz * dz);
if(horizontalDistance >= 1.0E-7D){
double dxNormalised = dx / horizontalDistance;
double dzNormalised = dz / horizontalDistance;
throwable.setPosition(caster.posX + dxNormalised, throwable.posY, caster.posZ + dzNormalised);
// Depends on the horizontal distance between the two entities and accounts for bullet drop,
// but of course if gravity is ignored throwable should be 0 since there is no bullet drop.
float bulletDropCompensation = !throwable.hasNoGravity() ? (float)horizontalDistance * 0.2f : 0;
// It turns out that throwable method normalises the input (x, y, z) anyway
throwable.shoot(dx, dy + (double)bulletDropCompensation, dz, speed, aimingError);
}
}
/**
* Does nothing by default, but can be overridden to call extra methods or set additional fields on the launched
* projectile.
*/
protected void addProjectileExtras(T projectile, EntityLivingBase caster, SpellModifiers modifiers){}
}
@@ -10,11 +10,7 @@ import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction; import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumHand; import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
public class SummonSnowGolem extends Spell { public class SummonSnowGolem extends Spell {
@@ -6,9 +6,6 @@ import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.EnumAction; import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import javax.annotation.Nullable;
public class Tornado extends SpellConstruct<EntityTornado> { public class Tornado extends SpellConstruct<EntityTornado> {
@@ -33,7 +33,7 @@ public class WitherSkull extends Spell {
} }
@Override @Override
public boolean canBeCastByNPCs(){ public boolean canBeCastBy(EntityLiving npc, boolean override){
return true; return true;
} }
@@ -7,6 +7,7 @@ import electroblob.wizardry.item.ItemCrystal;
import electroblob.wizardry.item.ItemSpellBook; import electroblob.wizardry.item.ItemSpellBook;
import electroblob.wizardry.registry.WizardryBlocks; import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.NBTExtras;
import electroblob.wizardry.util.WandHelper; import electroblob.wizardry.util.WandHelper;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
@@ -205,7 +206,7 @@ public class TileEntityArcaneWorkbench extends TileEntity implements IInventory,
} }
} }
tagCompound.setTag("Inventory", itemList); NBTExtras.storeTagSafely(tagCompound, "Inventory", itemList);
return tagCompound; return tagCompound;
} }
@@ -184,7 +184,7 @@ public class TileEntityShrineCore extends TileEntity implements ITickable {
for(EntityLivingBase entity : entities){ for(EntityLivingBase entity : entities){
entity.addPotionEffect(new PotionEffect(WizardryPotions.containment, 219)); entity.addPotionEffect(new PotionEffect(WizardryPotions.containment, 219));
entity.getEntityData().setTag(PotionContainment.ENTITY_TAG, NBTUtil.createPosTag(this.pos)); NBTExtras.storeTagSafely(entity.getEntityData(), PotionContainment.ENTITY_TAG, NBTUtil.createPosTag(this.pos));
} }
} }
@@ -192,13 +192,13 @@ public class TileEntityShrineCore extends TileEntity implements ITickable {
public NBTTagCompound writeToNBT(NBTTagCompound compound){ public NBTTagCompound writeToNBT(NBTTagCompound compound){
compound.setBoolean("activated", this.activated); compound.setBoolean("activated", this.activated);
if(linkedContainer != null) compound.setTag("linkedContainerPos", NBTUtil.createPosTag(linkedContainer.getPos())); if(linkedContainer != null) NBTExtras.storeTagSafely(compound, "linkedContainerPos", NBTUtil.createPosTag(linkedContainer.getPos()));
NBTTagList tagList = new NBTTagList(); NBTTagList tagList = new NBTTagList();
for(UUID uuid : linkedWizards){ for(UUID uuid : linkedWizards){
if(uuid != null) tagList.appendTag(NBTUtil.createUUIDTag(uuid)); if(uuid != null) tagList.appendTag(NBTUtil.createUUIDTag(uuid));
} }
compound.setTag("wizards", tagList); NBTExtras.storeTagSafely(compound, "wizards", tagList);
return super.writeToNBT(compound); return super.writeToNBT(compound);
} }
@@ -1,5 +1,6 @@
package electroblob.wizardry.tileentity; package electroblob.wizardry.tileentity;
import electroblob.wizardry.util.NBTExtras;
import electroblob.wizardry.util.WizardryUtilities; import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.entity.EntityList; import net.minecraft.entity.EntityList;
@@ -152,7 +153,7 @@ public class TileEntityStatue extends TileEntity implements ITickable {
tagCompound.setFloat("entityYawHead", creature.rotationYawHead); tagCompound.setFloat("entityYawHead", creature.rotationYawHead);
tagCompound.setFloat("entityYawOffset", creature.renderYawOffset); tagCompound.setFloat("entityYawOffset", creature.renderYawOffset);
} }
tagCompound.setTag("entity", entityCompound); NBTExtras.storeTagSafely(tagCompound, "entity", entityCompound);
tagCompound.setInteger("timer", timer); tagCompound.setInteger("timer", timer);
tagCompound.setInteger("lifetime", lifetime); tagCompound.setInteger("lifetime", lifetime);
tagCompound.setBoolean("isIce", isIce); tagCompound.setBoolean("isIce", isIce);
@@ -8,7 +8,10 @@ import net.minecraft.entity.monster.*;
import net.minecraft.util.DamageSource; import net.minecraft.util.DamageSource;
import net.minecraft.util.EntityDamageSource; import net.minecraft.util.EntityDamageSource;
import java.util.*; import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
// A note on the use of the vanilla damagesources: // A note on the use of the vanilla damagesources:
// When using indirect damage sources, the SECOND argument is the original entity (i.e. the caster), and the // When using indirect damage sources, the SECOND argument is the original entity (i.e. the caster), and the
@@ -51,8 +51,8 @@ public final class NBTExtras {
for(Map.Entry<K, V> entry : map.entrySet()){ for(Map.Entry<K, V> entry : map.entrySet()){
NBTTagCompound mapping = new NBTTagCompound(); NBTTagCompound mapping = new NBTTagCompound();
mapping.setTag(keyTagName, keyFunction.apply(entry.getKey())); NBTExtras.storeTagSafely(mapping, keyTagName, keyFunction.apply(entry.getKey()));
mapping.setTag(valueTagName, valueFunction.apply(entry.getValue())); NBTExtras.storeTagSafely(mapping, valueTagName, valueFunction.apply(entry.getValue()));
tagList.appendTag(mapping); tagList.appendTag(mapping);
} }
@@ -205,6 +205,51 @@ public final class NBTExtras {
tag.removeTag(key + "Least"); tag.removeTag(key + "Least");
} }
/**
* Stores the given NBT tag inside the given NBT tag compound using the given key. Under normal circumstances, this
* is equivalent to {@link NBTTagCompound#setTag(String, NBTBase)}, but this method performs safety checks to
* prevent circular references. If storing the given tag would cause a circular reference, the tag is not stored
* and an error is printed to the console.
* @param compound The {@link NBTTagCompound} in which to store the tag.
* @param key The key to store the tag under.
* @param tag The tag to store.
*/
// This is a catch-all fix for issue #299.
public static void storeTagSafely(NBTTagCompound compound, String key, NBTBase tag){
if(compound == tag || deepContains(tag, compound)){
Wizardry.logger.error("Cannot store tag of type {} under key '{}' as it would result in a circular reference! Please report this (including your full log) to wizardry's issue tracker.",
NBTBase.getTypeName(tag.getId()), key);
}else{
compound.setTag(key, tag);
}
}
/**
* Recursively searches within the first NBT tag for the second NBT tag. This handles both compound and list tags.
* @param toSearch The NBT tag to search inside. If this is not a compound or list tag, this method will always
* return false.
* @param searchFor The NBT tag to search for.
* @return True if the second tag appears anywhere within the NBT tree contained within the first tag, false if not.
*/
public static boolean deepContains(NBTBase toSearch, NBTBase searchFor){
if(toSearch instanceof NBTTagCompound){
for(String subKey : ((NBTTagCompound)toSearch).getKeySet()){
NBTBase subTag = ((NBTTagCompound)toSearch).getTag(subKey);
if(subTag == searchFor || deepContains(subTag, searchFor)) return true;
}
}else if(toSearch instanceof NBTTagList){
for(NBTBase subTag : (NBTTagList)toSearch){
if(subTag == searchFor || deepContains(subTag, searchFor)) return true;
}
}
return false;
}
/** /**
* Returns an NBTTagCompound which contains only the given UUID, stored using * Returns an NBTTagCompound which contains only the given UUID, stored using
* {@link NBTTagCompound#setUniqueId(String, UUID)}. Allows for neater storage to NBTTagLists. * {@link NBTTagCompound#setUniqueId(String, UUID)}. Allows for neater storage to NBTTagLists.
@@ -247,7 +247,7 @@ public final class SpellProperties {
public static void init(){ public static void init(){
// Collecting to a set should give us one of each mod ID // Collecting to a set should give us one of each mod ID
Set<String> modIDs = Spell.getSpells(Spell.allSpells).stream().map(s -> s.getRegistryName().getNamespace()).collect(Collectors.toSet()); Set<String> modIDs = Spell.getAllSpells().stream().map(s -> s.getRegistryName().getNamespace()).collect(Collectors.toSet());
boolean flag = loadConfigSpellProperties(); boolean flag = loadConfigSpellProperties();
@@ -398,7 +398,7 @@ public final class WandHelper {
if(wand.getTagCompound() == null) wand.setTagCompound((new NBTTagCompound())); if(wand.getTagCompound() == null) wand.setTagCompound((new NBTTagCompound()));
if(!wand.getTagCompound().hasKey(UPGRADES_KEY)) if(!wand.getTagCompound().hasKey(UPGRADES_KEY))
wand.getTagCompound().setTag(UPGRADES_KEY, new NBTTagCompound()); NBTExtras.storeTagSafely(wand.getTagCompound(), UPGRADES_KEY, new NBTTagCompound());
NBTTagCompound upgrades = wand.getTagCompound().getCompoundTag(UPGRADES_KEY); NBTTagCompound upgrades = wand.getTagCompound().getCompoundTag(UPGRADES_KEY);
@@ -406,7 +406,7 @@ public final class WandHelper {
if(key != null) upgrades.setInteger(key, upgrades.getInteger(key) + 1); if(key != null) upgrades.setInteger(key, upgrades.getInteger(key) + 1);
wand.getTagCompound().setTag(UPGRADES_KEY, upgrades); NBTExtras.storeTagSafely(wand.getTagCompound(), UPGRADES_KEY, upgrades);
} }
/** Returns true if the given item is a valid special wand upgrade. */ /** Returns true if the given item is a valid special wand upgrade. */
@@ -3,6 +3,7 @@ package electroblob.wizardry.worldgen;
import com.google.common.math.Quantiles; import com.google.common.math.Quantiles;
import electroblob.wizardry.Wizardry; import electroblob.wizardry.Wizardry;
import electroblob.wizardry.registry.WizardryAdvancementTriggers; import electroblob.wizardry.registry.WizardryAdvancementTriggers;
import electroblob.wizardry.util.NBTExtras;
import electroblob.wizardry.util.WizardryUtilities; import electroblob.wizardry.util.WizardryUtilities;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
@@ -256,7 +257,7 @@ public abstract class WorldGenSurfaceStructure implements IWorldGenerator {
NBTTagCompound tag = new NBTTagCompound(); NBTTagCompound tag = new NBTTagCompound();
tag.setInteger("ChunkX", chunkX); tag.setInteger("ChunkX", chunkX);
tag.setInteger("ChunkZ", chunkZ); tag.setInteger("ChunkZ", chunkZ);
tag.setTag("BB", settings.getBoundingBox().toNBTTagIntArray()); NBTExtras.storeTagSafely(tag, "BB", settings.getBoundingBox().toNBTTagIntArray());
structureData.writeInstance(tag, chunkX, chunkZ); structureData.writeInstance(tag, chunkX, chunkZ);
structureData.markDirty(); structureData.markDirty();
} }
@@ -44,8 +44,7 @@ item.ebwizardry\:spell_book.name=Spell Book
item.ebwizardry\:spell_book.apply_to_wizard=Replaced %1$s's spell %2$s with %3$s item.ebwizardry\:spell_book.apply_to_wizard=Replaced %1$s's spell %2$s with %3$s
item.ebwizardry\:arcane_tome.name=Tome of Arcana item.ebwizardry\:arcane_tome.name=Tome of Arcana
item.ebwizardry\:arcane_tome.desc1=Upgrades any %1$s item.ebwizardry\:arcane_tome.desc=Upgrades any %1$s wand with sufficient progression to %2$s tier
item.ebwizardry\:arcane_tome.desc2=wand to %1$s tier
item.ebwizardry\:wizard_handbook.name=The Wizard's Handbook item.ebwizardry\:wizard_handbook.name=The Wizard's Handbook
item.ebwizardry\:wizard_handbook.desc=by %1$s item.ebwizardry\:wizard_handbook.desc=by %1$s
@@ -44,8 +44,7 @@ item.ebwizardry\:spell_book.name=Spell Book
item.ebwizardry\:spell_book.apply_to_wizard=Replaced %1$s's spell %2$s with %3$s item.ebwizardry\:spell_book.apply_to_wizard=Replaced %1$s's spell %2$s with %3$s
item.ebwizardry\:arcane_tome.name=Tome of Arcana item.ebwizardry\:arcane_tome.name=Tome of Arcana
item.ebwizardry\:arcane_tome.desc1=Upgrades any %1$s item.ebwizardry\:arcane_tome.desc=Upgrades any %1$s wand with sufficient progression to %2$s tier
item.ebwizardry\:arcane_tome.desc2=wand to %1$s tier
item.ebwizardry\:wizard_handbook.name=The Wizard's Handbook item.ebwizardry\:wizard_handbook.name=The Wizard's Handbook
item.ebwizardry\:wizard_handbook.desc=by %1$s item.ebwizardry\:wizard_handbook.desc=by %1$s
@@ -7,8 +7,30 @@ tile.ebwizardry:snare.name=Ловушка
tile.ebwizardry:transportation_stone.name=Камень Транспортировки tile.ebwizardry:transportation_stone.name=Камень Транспортировки
tile.ebwizardry:spectral_block.name=Спектральный Блок tile.ebwizardry:spectral_block.name=Спектральный Блок
tile.ebwizardry:crystal_block.name=Кристальный Блок tile.ebwizardry:crystal_block.name=Кристальный Блок
tile.ebwizardry:runestone.name=Рунический камень
tile.ebwizardry:runestone_pedestal.name=Пьедестал рунического камня
tile.ebwizardry:thorns.name=Колючки
tile.ebwizardry:obsidian_crust.name=Обсидиановая кора
tile.ebwizardry:dry_frosted_ice.name=Сухой матовый лед
tile.ebwizardry:magic_crystal_block.name=Блок кристалла
tile.ebwizardry:fire_crystal_block.name=Блок огненного кристалла
tile.ebwizardry:ice_crystal_block.name=Блок ледяного кристалла
tile.ebwizardry:lightning_crystal_block.name=Блок грозового кристалла
tile.ebwizardry:necromancy_crystal_block.name=Блок темного кристалла
tile.ebwizardry:earth_crystal_block.name=Блок зеленого кристалла
tile.ebwizardry:sorcery_crystal_block.name=Блок мистического кристалла
tile.ebwizardry:healing_crystal_block.name=Блок сияющего кристалла
item.ebwizardry:crystal_magic.name=Магический Кристалл
item.ebwizardry:crystal_fire.name=Огненный кристалл
item.ebwizardry:crystal_ice.name=Ледяной кристалл
item.ebwizardry:crystal_lightning.name=Грозовой кристалл
item.ebwizardry:crystal_necromancy.name=Темный кристалл
item.ebwizardry:crystal_earth.name=Зелёный кристалл
item.ebwizardry:crystal_sorcery.name=Мистический кристалл
item.ebwizardry:crystal_healing.name=Сияющий кристалл
item.ebwizardry:magic_crystal.name=Магический Кристалл
item.ebwizardry:magic_wand.name=Волшебный Жезл item.ebwizardry:magic_wand.name=Волшебный Жезл
item.ebwizardry:apprentice_wand.name=Жезл Ученика item.ebwizardry:apprentice_wand.name=Жезл Ученика
item.ebwizardry:advanced_wand.name=Продвинутый Жезл item.ebwizardry:advanced_wand.name=Продвинутый Жезл
@@ -62,7 +84,21 @@ item.ebwizardry:spectral_sword.name=Спектральный Меч
item.ebwizardry:spectral_pickaxe.name=Спектральная Кирка item.ebwizardry:spectral_pickaxe.name=Спектральная Кирка
item.ebwizardry:spectral_bow.name=Спектральный Лук item.ebwizardry:spectral_bow.name=Спектральный Лук
item.ebwizardry:mana_flask.name=Бутылка Маны item.ebwizardry:spectral_sword_upgraded.name=Спектральный Меч
item.ebwizardry:spectral_pickaxe_upgraded.name=Спектральная Кирка
item.ebwizardry:small_mana_flask.name=Маленькая Бутылка Маны
item.ebwizardry:medium_mana_flask.name=Средняя Бутылка Маны
item.ebwizardry:large_mana_flask.name=Большая Бутылка Маны
item.ebwizardry:grand_crystal.name=Большой Магический Кристалл
item.ebwizardry:crystal_shard.name=Осколок Магического Кристалла
item.ebwizardry:astral_diamond.name=Звёздный Алмаз
item.ebwizardry:purifying_elixir.name=Очищающий Эликсир
item.ebwizardry:purifying_elixir.desc=Снимает проклятия при употреблении
item.ebwizardry:storage_upgrade.name=Улучшение Хранилища Маны Жезла item.ebwizardry:storage_upgrade.name=Улучшение Хранилища Маны Жезла
item.ebwizardry:siphon_upgrade.name=Улучшение Сифона Жезла item.ebwizardry:siphon_upgrade.name=Улучшение Сифона Жезла
item.ebwizardry:condenser_upgrade.name=Улучшение Конденсатора Жезла item.ebwizardry:condenser_upgrade.name=Улучшение Конденсатора Жезла
@@ -75,9 +111,13 @@ item.ebwizardry:attunement_upgrade.name=Улучшение Хранилища З
item.ebwizardry:flaming_axe.name=Огненный Топор item.ebwizardry:flaming_axe.name=Огненный Топор
item.ebwizardry:frost_axe.name=Ледяной Топор item.ebwizardry:frost_axe.name=Ледяной Топор
item.ebwizardry:flaming_axe_upgraded.name=Огненный Топор
item.ebwizardry:frost_axe_upgraded.name=Ледяной Топор
item.ebwizardry:firebomb.name=Огненная Бомба item.ebwizardry:firebomb.name=Огненная Бомба
item.ebwizardry:poison_bomb.name=Ядовитая Бомба item.ebwizardry:poison_bomb.name=Ядовитая Бомба
item.ebwizardry:smoke_bomb.name=Дымовая Бомба item.ebwizardry:smoke_bomb.name=Дымовая Бомба
item.ebwizardry:spark_bomb.name=Бомба Искр
item.ebwizardry:blank_scroll.name=Пустой Свиток item.ebwizardry:blank_scroll.name=Пустой Свиток
item.ebwizardry:scroll.name=Свиток %1$s item.ebwizardry:scroll.name=Свиток %1$s
@@ -137,6 +177,75 @@ item.ebwizardry:wizard_leggings_healing.name=Поножи Целителя
item.ebwizardry:wizard_boots_healing.name=Ботинки Целителя item.ebwizardry:wizard_boots_healing.name=Ботинки Целителя
item.ebwizardry:spawn_wizard.name=Спаун Волшебника item.ebwizardry:spawn_wizard.name=Спаун Волшебника
item.ebwizardry:spawn_evil_wizard.name=Спаун Злого Волшебника
item.ebwizardry:spectral_helmet.name=Призрачный шлем
item.ebwizardry:spectral_chestplate.name=Призрачный нагрудник
item.ebwizardry:spectral_leggings.name=Призрачные поножи
item.ebwizardry:spectral_boots.name=Призрачные ботинки
item.ebwizardry:lightning_hammer.name=Молот Шторма
item.ebwizardry:ring_condensing.name=Кольцо Конденсации
item.ebwizardry:ring_condensing.desc=Медленно восстанавливает ману для всех жезлов на вашем хотбаре
item.ebwizardry:ring_siphoning.name=Кольцо Сифонинга
item.ebwizardry:ring_siphoning.desc=Увеличивает сифоновую ману на 30 %%
item.ebwizardry:ring_battlemage.name=Кольцо Боевого Мага
item.ebwizardry:ring_battlemage.desc=Держа палочку в руке и меч в основной руке, вы получаете 10 %% дополнительного магического урона
item.ebwizardry:ring_combustion.name=Кольцо Горения
item.ebwizardry:ring_combustion.desc=Существа, убитые огненными заклинаниями, взрываются
item.ebwizardry:ring_fire_melee.name=Кольцо Огненного Прикосновения
item.ebwizardry:ring_fire_melee.desc=Удар по существу огненной палочкой поджигает его
item.ebwizardry:ring_fire_biome.name=Кольцо Пустынного Солнца
item.ebwizardry:ring_fire_biome.desc=Огненные заклинания на 30% более сильны в горячих биомах
item.ebwizardry:ring_disintegration.name=Кольцо Раскаленных Углей
item.ebwizardry:ring_disintegration.desc=Все заклинания огненной атаки разрушают их жертв
item.ebwizardry:ring_ice_melee.name=Кольцо Ледяного Прикосновения
item.ebwizardry:ring_ice_melee.desc=Удар по существу ледяной палочкой дает эффект обморожения
item.ebwizardry:ring_ice_biome.name=Кольцо Оледенения
item.ebwizardry:ring_ice_biome.desc=Заклинания льда в снежных биомах на 30% более мощные
item.ebwizardry:ring_arcane_frost.name=Кольцо Чародейского Мороза
item.ebwizardry:ring_arcane_frost.desc=Существа, убитые ледяными заклинаниями, выпускают осколки льда во всех направлениях
item.ebwizardry:ring_shattering.name=Кольцо Разрушителя
item.ebwizardry:ring_shattering.desc=Атаки ближнего боя на мобов с эффектом обморожения могут разбить их на ледяные осколки
item.ebwizardry:ring_lightning_melee.name=Кольцо Цепи
item.ebwizardry:ring_lightning_melee.desc=Удар молнии о существо выстреливает молнией в другого находящегося поблизости существа
item.ebwizardry:ring_storm.name=Кольцо Собирающейся Бури
item.ebwizardry:ring_storm.desc=Во время грозы заклинания молнии резко сокращают время восстановления
item.ebwizardry:ring_seeking.name=Кольцо Притяжения
item.ebwizardry:ring_seeking.desc=Все снарядовые заклинания ищут свои цели
item.ebwizardry:ring_hammer.name=Кольцо Талека Всемогущего
item.ebwizardry:ring_hammer.desc=Молнии могут быть подняты и брошены
item.ebwizardry:ring_soulbinding.name=Кольцо Странника Душ
item.ebwizardry:ring_soulbinding.desc=Существа, поврежденные заклинаниями некромантии, становятся привязанными к вам
item.ebwizardry:ring_leeching.name=Кольцо Пиявки
item.ebwizardry:ring_leeching.desc=Все атаки некромантии с вероятностью 30% могут вызвать эффект истощения жизни
item.ebwizardry:ring_necromancy_melee.name=Кольцо Некротического Прикосновения
item.ebwizardry:ring_necromancy_melee.desc=Удар по существу с помощью палочки некромантии дает ему эффект увядания
item.ebwizardry:ring_mind_control.name=Кольцо Экстрасенса
item.ebwizardry:ring_mind_control.desc=Контролируемые разумом существа могут контролировать свои близлежащие существа
item.ebwizardry:ring_poison.name=Кольцо Змеи
item.ebwizardry:ring_poison.desc=Все заклинания земли отравляют свою цель
item.ebwizardry:ring_earth_melee.name=Кольцо ядовитого прикосновения
item.ebwizardry:ring_earth_melee.desc=Удар по существу земной палочкой отравляет его
item.ebwizardry:ring_earth_biome.name=Кольцо Дриады
item.ebwizardry:ring_earth_biome.desc=Заклинания земли сильнее на 30% в лесах и крытых лесах
item.ebwizardry:ring_full_moon.name=Кольцо Воющего Волка
item.ebwizardry:ring_full_moon.desc=Земные заклинания резко сократили время восстановления при полной луне
item.ebwizardry:ring_extraction.name=Кольцо Извлечения
item.ebwizardry:ring_extraction.desc=Убийства заклинаниями магии дают бонус маны
item.ebwizardry:ring_mana_return.name=Кольцо Перфекциониста
item.ebwizardry:ring_mana_return.desc=Силовые стрелы, которые не попадают в цель, возвращают ману, которую они использовали к вашей палочке
item.ebwizardry:ring_blockwrangler.name=Кольцо Странника
item.ebwizardry:ring_blockwrangler.desc=Брошенные блоки наносят в два раза больше обычного урона
item.ebwizardry:ring_conjurer.name=Кольцо фокусника
item.ebwizardry:ring_conjurer.desc=Колдовские вещи длятся в два раза дольше
item.ebwizardry:ring_defender.name=Кольцо Защитника
item.ebwizardry:ring_defender.desc=Ваши снаряды проходят через силовые поля, принадлежащие вам или союзнику
item.ebwizardry:ring_paladin.name=Кольцо Паладина
item.ebwizardry:ring_paladin.desc=Когда вы исцеляете себя или союзника, близлежащие союзники также получают немного здоровья
item.ebwizardry:ring_interdiction.name=Кольцо Запрета
item.ebwizardry:ring_interdiction.desc=Ваше силовое поле наносит урон существам, которые его касаются
entity.ebwizardry:zombie_minion.name=Зомби entity.ebwizardry:zombie_minion.name=Зомби
entity.ebwizardry:skeleton_minion.name=Скелет entity.ebwizardry:skeleton_minion.name=Скелет
@@ -609,4 +718,4 @@ config.ebwizardry.show_summoned_creature_names=Show Summoned Creature Names
config.ebwizardry.spell_hud_position=Spell HUD Position config.ebwizardry.spell_hud_position=Spell HUD Position
config.ebwizardry.category.spells=Configure Spells config.ebwizardry.category.spells=Configure Spells
config.ebwizardry.category.spells.tooltip=Select which spells are enabled. config.ebwizardry.category.spells.tooltip=Select which spells are enabled.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 35 KiB

+2 -2
View File
@@ -2,10 +2,10 @@
{ {
"modid" : "ebwizardry", "modid" : "ebwizardry",
"name" : "Electroblob's Wizardry", "name" : "Electroblob's Wizardry",
"version" : "4.2.7", "version" : "4.2.8",
"mcversion" : "1.12.2", "mcversion" : "1.12.2",
"url" : "https://minecraft.curseforge.com/projects/electroblobs-wizardry", "url" : "https://minecraft.curseforge.com/projects/electroblobs-wizardry",
"credits" : "\nDesigned, coded and textured by Electroblob.\nCode contributed by: Corail31, 12foo, Shadows-of-Fire, Tora-B, Avatair, Aeronica, UltraHex, Azim-Palmer.\nTranslators: MadWrist (Spanish, Mexican Spanish), VilagVil & kellixon (Russian), Hahdrim (French), lorrampi (Brazilian Portuguese), ZHENGLOC & dragon-evol (Chinese), shejery & rewi_wire (Korean), Trozuu (Polish).", "credits" : "\nDesigned, coded and textured by Electroblob.\nCode contributed by: Corail31, 12foo, Shadows-of-Fire, Tora-B, Avatair, Aeronica, UltraHex, Azim-Palmer, raoulvdberge.\nTranslators: MadWrist (Spanish, Mexican Spanish), VilagVil, kellixon & bigenergy (Russian), Hahdrim (French), lorrampi (Brazilian Portuguese), ZHENGLOC & dragon-evol (Chinese), shejery & rewi_wire (Korean), Trozuu (Polish).",
"authorList" : [ "authorList" : [
"Electroblob" "Electroblob"
], ],