Compare commits

..

42 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
Electroblob77 807fc61541 Merge branch '1.12.2' of https://github.com/Electroblob77/Wizardry into 1.12.2 2019-12-22 18:39:45 +00:00
Electroblob77 9741c751b3 Update version number 2019-12-22 18:37:25 +00:00
Electroblob d3003ae473 Merge pull request #311 from Azim-Palmer/1.12.2
Adding support for Lycanites Mobs to drop loot from the mob_additions table
2019-12-22 18:25:52 +00:00
Electroblob77 9fde24f341 Merge branch '1.12.2' of https://github.com/Electroblob77/Wizardry into 1.12.2 2019-12-22 15:15:40 +00:00
Electroblob 452eb17b5f Update question.md 2019-12-22 15:13:57 +00:00
Electroblob 36963aac30 Update feature_request.md 2019-12-22 15:13:47 +00:00
Electroblob f161cdfe7c Update bug_report.md 2019-12-22 15:13:36 +00:00
Electroblob77 455c5a5e07 Fix weird floaty rounding so continuous spells cost the correct amount of mana
Grapple ain't free anymore, sorry folks!
2019-12-22 14:58:12 +00:00
Electroblob77 9852f61d0f Extend smoke bomb blindness effect to mobs as well as players, closes #324 2019-12-22 14:37:19 +00:00
Electroblob77 a2c8cde4c4 Use correct mana values for grand crystals and crystal shards in scroll creation 2019-12-22 12:30:04 +00:00
Electroblob77 497d28eafd Somehow this went missing... 2019-12-22 11:45:42 +00:00
Az f82d2871d5 Adding support for Lycanites Mobs to drop loot from the mob_additions table 2019-11-23 12:44:10 +00:00
102 changed files with 862 additions and 421 deletions
+2 -2
View File
@@ -7,10 +7,10 @@ assignees: ''
---
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/CONTRIBUTING.md) before posting.
Minecraft version: 1.12.2 [change as necessary]
Wizardry version: 4.2.2 [change as necessary]
Wizardry version: 4.2.8 [change as necessary]
Environment: Singleplayer/LAN game/Server [delete as necessary]
Issue details: [describe what you were doing when the issue occurred, what went wrong, what you expected to happen, etc.]
+1 -1
View File
@@ -7,7 +7,7 @@ assignees: ''
---
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/CONTRIBUTING.md) before posting.
Minecraft versions: [if this suggestion only applies to some versions, list them here]
+2 -2
View File
@@ -7,10 +7,10 @@ assignees: ''
---
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/CONTRIBUTING.md) before posting.
Minecraft version: 1.12.2 [change as necessary]
Wizardry version: 4.2.2 [change as necessary]
Wizardry version: 4.2.8 [change as necessary]
Question details: [explain your question here]
+8 -7
View File
@@ -1,7 +1,7 @@
# Credits
Electroblob's Wizardry
Version 4.2.6
Version 4.2.8
For Minecraft 1.12.2
Designed, coded and textured by Electroblob
@@ -23,15 +23,16 @@ In addition, I'd like to thank the following individuals for their contributions
- Aeronica
- UltraHex
- Azim-Palmer
- raoulvdberge
#### Translations
- Spanish and Mexican Spanish: MadWrist
- Russian: VilagVil, kellixon
- French: Hahdrim
- Brazilian Portuguese: lorrampi
- Chinese: ZHENGLOC, dragon-evol
- Korean: shejery, rewi_wire
- Spanish and Mexican Spanish: MadWrist
- Russian: VilagVil, kellixon, bigenergy
- French: Hahdrim
- Brazilian Portuguese: lorrampi
- Chinese: ZHENGLOC, dragon-evol
- Korean: shejery, rewi_wire
- Polish: Trozuu
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.
Minecraft version: 1.12.2 [change as necessary]
Wizardry version: 4.1.4 [change as necessary]
Wizardry version: 4.2.8 [change as necessary]
Environment: Singleplayer/LAN game/Server [delete as necessary]
Issue details: [describe what you were doing when the issue occurred, what went wrong, what you expected to happen, etc.]
+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.
version = "4.2.6" // 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
archivesBaseName = "ElectroblobsWizardry"
@@ -100,9 +100,9 @@ public class CommonProxy {
return ((ItemSpectralBow)WizardryItems.spectral_bow).getDefaultDurabilityForDisplay(stack);
}
/** Like {@link CommonProxy#addMultiLineDescription(List, String, Style)}, but style defaults to light grey. */
public void addMultiLineDescription(List<String> tooltip, String key){
this.addMultiLineDescription(tooltip, key, new Style().setColor(TextFormatting.GRAY));
/** Like {@link CommonProxy#addMultiLineDescription(List, String, Style, Object...)}, but style defaults to light grey. */
public void addMultiLineDescription(List<String> tooltip, String key, Object... args){
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 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
// ===============================================================================================================
@@ -140,8 +140,6 @@ public class CommonProxy {
public void handleAdvancementSyncPacket(PacketSyncAdvancements.Message message){}
public void handleEndSlowTimePacket(PacketEndSlowTime.Message message){}
public void handleResurrectionPacket(PacketResurrection.Message message){}
public void handlePossessionPacket(PacketPossession.Message message){}
@@ -433,7 +433,7 @@ public final class Settings {
Property property;
for(Spell spell : Spell.getSpells(Spell.allSpells)){
for(Spell spell : Spell.getAllSpells()){
property = config.get(SPELLS_CATEGORY, spell.getRegistryName().toString(), true,
I18n.translateToLocal("spell." + spell.getUnlocalisedName() + ".desc"));
// 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.11.x versions, and so on.
*/
public static final String VERSION = "4.2.6";
public static final String VERSION = "4.2.8";
// IDEA: Triggering of inbuilt Forge events in relevant places?
// IDEA: Abstract the vanilla particles behind the particle builder
@@ -33,6 +33,9 @@ import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;
import net.minecraft.world.storage.loot.LootContext;
import net.minecraft.world.storage.loot.LootTable;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.util.FakePlayer;
import net.minecraftforge.event.entity.PlaySoundAtEntityEvent;
@@ -46,6 +49,7 @@ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerLoggedInEvent;
import java.util.ArrayList;
import java.util.List;
/**
* General-purpose event handler for things that don't fit anywhere else or groups of related behaviours that are better
@@ -343,18 +347,21 @@ public final class WizardryEventHandler {
Spell spell = ((ISpellCaster)event.getEntity()).getContinuousSpell();
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(!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.
// Right now that doesn't matter because NPCs don't use modifiers, but they might in future
((EntityLiving)event.getEntity()).getAttackTarget(), modifiers);
}
}
((ISpellCaster)event.getEntity()).setSpellCounter(count + 1);
}
}
}
@@ -366,6 +373,22 @@ public final class WizardryEventHandler {
EntityPlayer player = (EntityPlayer)event.getSource().getTrueSource();
// Compatibility with "Lycanites Mobs" -it uses custom loot drop logic which can't be hooked, given the
// number of mobs that spawn as Lycanites when this mod is active it would massively nerf the wizard drops
// if we didn't handle this
if(event.getEntity().getClass().getCanonicalName().contains("lycanitesmobs"))
{
WorldServer world = (WorldServer)event.getEntity().getEntityWorld();
LootTable table = world.getLootTableManager().getLootTableFromLocation(new ResourceLocation(Wizardry.MODID, "entities/mob_additions"));
LootContext ctx = new LootContext.Builder(world).withPlayer(player).build();
List<ItemStack> stacks = table.generateLootForPools(world.rand, ctx);
for(ItemStack stack : stacks) {
event.getEntity().entityDropItem(stack, 0f);
}
}
for(ItemStack stack : WizardryUtilities.getPrioritisedHotbarAndOffhand(player)){
if(stack.getItem() instanceof IManaStoringItem && !((IManaStoringItem)stack.getItem()).isManaFull(stack)
@@ -444,7 +467,8 @@ public final class WizardryEventHandler {
// 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
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;
// SECTION Registry
@@ -287,8 +287,8 @@ public class ClientProxy extends CommonProxy {
}
@Override
public void addMultiLineDescription(List<String> tooltip, String key, Style style){
String description = style.getFormattingCode() + I18n.format(key);
public void addMultiLineDescription(List<String> tooltip, String key, Style style, Object... args){
String description = style.getFormattingCode() + I18n.format(key, args);
tooltip.addAll(Minecraft.getMinecraft().fontRenderer.listFormattedStringToWidth(description, TOOLTIP_WRAP_WIDTH));
}
@@ -434,6 +434,7 @@ public class ClientProxy extends CommonProxy {
if(caster instanceof ISpellCaster){
if(spell.isContinuous || spell instanceof None){
((ISpellCaster)caster).setContinuousSpell(spell);
((ISpellCaster)caster).setSpellCounter(spell instanceof None ? 0 : 1);
((EntityLiving)caster).setAttackTarget((EntityLivingBase)target);
}
}
@@ -547,7 +548,7 @@ public class ClientProxy extends CommonProxy {
data.randomNames = 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
// This is a case where we must use the network ID, not the metadata
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);
}
@Override
public void handleEndSlowTimePacket(PacketEndSlowTime.Message message){
Entity entity = Minecraft.getMinecraft().world.getEntityByID(message.hostID);
if(entity instanceof EntityLivingBase) PotionSlowTime.unblockNearbyEntities((EntityLivingBase)entity);
else Wizardry.logger.warn("Received a PacketEndSlowTime, but the entity ID did not match any living entity");
}
@Override
public void handleResurrectionPacket(PacketResurrection.Message message){
Entity entity = Minecraft.getMinecraft().world.getEntityByID(message.playerID);
@@ -9,6 +9,7 @@ import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.item.ISpellCastingItem;
import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.item.ItemSpectralBow;
import electroblob.wizardry.potion.PotionSlowTime;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions;
@@ -153,6 +154,7 @@ public final class WizardryClientEventHandler {
}
SpellEmitterData.update(world);
PotionSlowTime.cleanUpEntities(world);
}
}
@@ -31,7 +31,6 @@ import net.minecraft.inventory.Slot;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.text.TextFormatting;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.fml.common.Mod;
@@ -525,30 +525,42 @@ public abstract class ParticleWizardry extends Particle {
// =============================================== 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,
* 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];
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;
}
/** 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
* 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. */
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];
for(int i=0; i<m; i++){
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){
// 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)){
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(
new TextComponentTranslation("commands." + Wizardry.MODID + ":discoverspell.clear", player.getName()));
}else if(all){
data.spellsDiscovered.addAll(Spell.getSpells(Spell.allSpells));
data.spellsDiscovered.addAll(Spell.getAllSpells());
if(server.sendCommandFeedback()) sender.sendMessage(
new TextComponentTranslation("commands." + Wizardry.MODID + ":discoverspell.all", player.getName()));
}else{
@@ -4,6 +4,7 @@ import electroblob.wizardry.Wizardry;
import electroblob.wizardry.data.SpellEmitterData;
import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.NBTExtras;
import electroblob.wizardry.util.SpellModifiers;
import io.netty.buffer.ByteBuf;
import net.minecraft.nbt.NBTTagCompound;
@@ -129,7 +130,7 @@ public class SpellEmitter implements ITickable {
nbt.setDouble("z", z);
nbt.setInteger("direction", direction.getIndex());
nbt.setInteger("duration", duration);
nbt.setTag("modifiers", modifiers.toNBT());
NBTExtras.storeTagSafely(nbt, "modifiers", modifiers.toNBT());
nbt.setInteger("castingTick", castingTick);
return nbt;
@@ -7,6 +7,7 @@ import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.spell.None;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.NBTExtras;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.nbt.NBTTagCompound;
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("castingTick", castingTick);
nbt.setTag("modifiers", modifiers.toNBT());
NBTExtras.storeTagSafely(nbt, "modifiers", modifiers.toNBT());
return nbt;
}
@@ -1,5 +1,6 @@
package electroblob.wizardry.data;
import electroblob.wizardry.util.NBTExtras;
import io.netty.buffer.ByteBuf;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
@@ -89,7 +90,7 @@ public interface IStoredVariable<T> extends IVariable<T> {
@Override
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
@@ -91,7 +91,7 @@ public class SpellEmitterData extends WorldSavedData {
@Override
public NBTTagCompound writeToNBT(NBTTagCompound compound){
compound.setTag("emitters", NBTExtras.listToNBT(emitters, SpellEmitter::toNBT));
NBTExtras.storeTagSafely(compound, "emitters", NBTExtras.listToNBT(emitters, SpellEmitter::toNBT));
return compound;
}
@@ -4,6 +4,7 @@ import electroblob.wizardry.Wizardry;
import electroblob.wizardry.packet.PacketGlyphData;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.NBTExtras;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.nbt.NBTTagCompound;
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. */
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));
}
for(Spell spell : Spell.getSpells(Spell.allSpells)){
for(Spell spell : Spell.getAllSpells()){
if(!randomDescriptions.containsKey(spell))
randomDescriptions.put(spell, generateRandomDescription(world.rand));
}
@@ -161,7 +162,7 @@ public class SpellGlyphData extends WorldSavedData {
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
// The description is now also included; there's no point in making a second compound tag!
NBTTagCompound tag = new NBTTagCompound();
@@ -171,7 +172,7 @@ public class SpellGlyphData extends WorldSavedData {
tagList.appendTag(tag);
}
nbt.setTag("spellGlyphData", tagList);
NBTExtras.storeTagSafely(nbt, "spellGlyphData", tagList);
return nbt;
}
@@ -515,12 +515,12 @@ public class WizardData implements INBTSerializable<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));
// Mmmmmm Java 8....
properties.setTag("allies", NBTExtras.listToNBT(this.allies, NBTUtil::createUUIDTag));
properties.setTag("allyNames", NBTExtras.listToNBT(this.allyNames, NBTTagString::new));
NBTExtras.storeTagSafely(properties, "allies", NBTExtras.listToNBT(this.allies, NBTUtil::createUUIDTag));
NBTExtras.storeTagSafely(properties, "allyNames", NBTExtras.listToNBT(this.allyNames, NBTTagString::new));
// Might be worth converting this over to WizardryUtilities.listToNBT.
int[] spells = new int[this.spellsDiscovered.size()];
@@ -531,7 +531,7 @@ public class WizardData implements INBTSerializable<NBTTagCompound> {
}
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)));
@@ -5,6 +5,7 @@ import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.AllyDesignationSystem;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.NBTExtras;
import electroblob.wizardry.util.WizardryUtilities;
import io.netty.buffer.ByteBuf;
import net.minecraft.block.Block;
@@ -183,7 +184,7 @@ public class EntityLevitatingBlock extends EntityFallingBlock implements IEntity
NBTBase nbtbase = this.tileEntityData.getTag(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(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){
WizardryUtilities.getRider(this).attackEntityFrom(
MagicDamage.causeIndirectMagicDamage(this, getCaster(), DamageType.MAGIC),
@@ -1,6 +1,5 @@
package electroblob.wizardry.entity.construct;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.WizardryUtilities;
@@ -80,8 +80,8 @@ public class EntityHammer extends EntityMagicConstruct {
if(this.world.isRemote && this.ticksExisted % 3 == 0){
ParticleBuilder.create(Type.SPARK)
.pos(this.posX - 0.5d + rand.nextDouble(), this.posY + 2 * rand.nextDouble(), this.posZ - 0.5d + rand.nextDouble())
.spawn(world);
.pos(this.posX - 0.5d + rand.nextDouble(), this.posY + 2 * rand.nextDouble(), this.posZ - 0.5d + rand.nextDouble())
.spawn(world);
}
this.prevPosX = this.posX;
@@ -103,39 +103,37 @@ public class EntityHammer extends EntityMagicConstruct {
this.rotationPitch = 0;
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,
this.posY + 1, this.posZ, world);
int maxTargets = Spells.lightning_hammer.getProperty(LightningHammer.SECONDARY_MAX_TARGETS).intValue();
while(targets.size() > maxTargets) targets.remove(targets.size() - 1);
int maxTargets = Spells.lightning_hammer.getProperty(LightningHammer.SECONDARY_MAX_TARGETS).intValue();
while(targets.size() > maxTargets) targets.remove(targets.size() - 1);
for(EntityLivingBase target : targets){
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.getEntityBoundingBox().minY + target.height, target.posZ);
}
target.playSound(WizardrySounds.ENTITY_HAMMER_ATTACK, 1.0F, rand.nextFloat() * 0.4F + 1.5F);
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(
this, getCaster(), DamageType.SHOCK), damage);
WizardryUtilities.applyStandardKnockback(this, target);
}else{
target.attackEntityFrom(DamageSource.MAGIC, damage);
}
if(this.getCaster() != null){
WizardryUtilities.attackEntityWithoutKnockback(target, MagicDamage.causeIndirectMagicDamage(
this, getCaster(), DamageType.SHOCK), damage);
WizardryUtilities.applyStandardKnockback(this, target);
}else{
target.attackEntityFrom(DamageSource.MAGIC, damage);
}
}
}
@@ -62,7 +62,7 @@ public class EntityHealAura extends EntityMagicConstruct {
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);
}
}
@@ -213,6 +213,7 @@ public class EntityAIAttackSpell<T extends EntityLiving & ISpellCaster> extends
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.isContinuous){
@@ -74,6 +74,7 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
// Field implementations
private List<Spell> spells = new ArrayList<Spell>(4);
private Spell continuousSpell;
private int spellCounter;
public EntityEvilWizard(World world){
@@ -175,6 +176,16 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
public Spell getContinuousSpell(){
return this.continuousSpell;
}
@Override
public void setSpellCounter(int count){
spellCounter = count;
}
@Override
public int getSpellCounter(){
return spellCounter;
}
@Override
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
if(player.isCreative() && stack.getItem() instanceof ItemSpellBook){
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!
player.sendMessage(new TextComponentTranslation("item." + Wizardry.MODID + ":spell_book.apply_to_wizard",
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);
nbt.setInteger("element", this.getElement().ordinal());
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.setTag("groupUUIDs", NBTExtras.listToNBT(groupUUIDs, NBTUtil::createUUIDTag));
NBTExtras.storeTagSafely(nbt, "groupUUIDs", NBTExtras.listToNBT(groupUUIDs, NBTUtil::createUUIDTag));
}
@Override
@@ -369,7 +380,7 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
// All wizards know magic missile, even if it is disabled.
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;
// 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 org.apache.commons.lang3.ArrayUtils;
import java.util.Arrays;
import java.util.function.IntPredicate;
@Mod.EventBusSubscriber
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 Spell continuousSpell;
private int spellCounter;
private static final List<Spell> attack = Collections.singletonList(Spells.flame_ray);
@@ -76,6 +77,16 @@ public class EntityPhoenix extends EntitySummonedCreature implements ISpellCaste
continuousSpell = spell;
}
@Override
public void setSpellCounter(int count){
spellCounter = count;
}
@Override
public int getSpellCounter(){
return spellCounter;
}
@Override
public boolean hasRangedAttack(){
return true;
@@ -61,21 +61,6 @@ public class EntityShadowWraith extends EntitySummonedCreature implements ISpell
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
protected void applyEntityAttributes(){
super.applyEntityAttributes();
@@ -59,21 +59,6 @@ public class EntityStormElemental extends EntitySummonedCreature implements ISpe
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
protected void applyEntityAttributes(){
super.applyEntityAttributes();
@@ -83,6 +83,7 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
// Field implementations
private List<Spell> spells = new ArrayList<Spell>(4);
private Spell continuousSpell;
private int spellCounter;
/** A set of the positions of the blocks that are part of this wizard's tower. */
private Set<BlockPos> towerBlocks;
@@ -187,7 +188,17 @@ public class EntityWizard extends EntityCreature implements INpc, IMerchant, ISp
public Spell getContinuousSpell(){
return this.continuousSpell;
}
@Override
public void setSpellCounter(int count){
spellCounter = count;
}
@Override
public int getSpellCounter(){
return spellCounter;
}
@Override
public int getAimingError(EnumDifficulty difficulty){
// 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
if(player.isCreative() && stack.getItem() instanceof ItemSpellBook){
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!
player.sendMessage(new TextComponentTranslation("item." + Wizardry.MODID + ":spell_book.apply_to_wizard",
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);
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("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){
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.
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.
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
* 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 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 random A random number generator to use.
* @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.
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));
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.
* <p></p>
* 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
* 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.
* 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
public List<Spell> getSpells();
List<Spell> getSpells();
/**
* 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.
*/
@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
* 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
* {@link Spells#none}. If the implementing class only ever uses one continuous spell, do <b>not</b> just return
* that spell; the field must still be stored.
* saved to NBT is up to you. If the implementing class only ever uses one continuous spell, do <b>not</b> just
* return that spell; the field must still be stored.
*/
@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
* 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.
*/
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).
*/
// This is what default methods are actually intended for!
public default int getAimingError(EnumDifficulty difficulty) {
default int getAimingError(EnumDifficulty difficulty) {
return WizardryUtilities.getDefaultAimingError(difficulty);
}
}
@@ -9,11 +9,15 @@ import electroblob.wizardry.util.ParticleBuilder;
import io.netty.buffer.ByteBuf;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.projectile.EntitySmallFireball;
import net.minecraft.init.Blocks;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.DamageSource;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.EntityJoinWorldEvent;
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){
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
: target.getEntityBoundingBox().minY + (double)(target.height / 2.0f) - this.posY;
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){
@@ -9,7 +9,6 @@ import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.MobEffects;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumParticleTypes;
@@ -66,12 +65,11 @@ public class EntitySmokeBomb extends EntityBomb {
for(EntityLivingBase target : targets){
if(target != this.getThrower()){
// Gives the target blindness if it is a player, mind trick otherwise (since this has the desired
// Gives the target blindness, and mind trick if it's not a player (since this has the desired
// effect of preventing targeting)
if(target instanceof EntityPlayer){
target.addPotionEffect(new PotionEffect(MobEffects.BLINDNESS, duration, 0));
}else if(target instanceof EntityLiving){
// New AI
target.addPotionEffect(new PotionEffect(MobEffects.BLINDNESS, duration, 0));
if(target instanceof EntityLiving){
((EntityLiving)target).setAttackTarget(null);
target.addPotionEffect(new PotionEffect(WizardryPotions.mind_trick, duration, 0));
}
@@ -61,11 +61,10 @@ public class ItemArcaneTome extends Item {
Tier tier = Tier.values()[stack.getItemDamage()];
Tier tier2 = Tier.values()[stack.getItemDamage() - 1];
tooltip.add(tier.getDisplayNameWithFormatting());
tooltip.add("\u00A77" + net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":arcane_tome.desc1",
tier2.getDisplayNameWithFormatting()));
tooltip.add("\u00A77" + net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":arcane_tome.desc2",
tier.getDisplayNameWithFormatting() + "\u00A77"));
Wizardry.proxy.addMultiLineDescription(tooltip, "item." + this.getRegistryName() + ".desc",
tier2.getDisplayNameWithFormatting() + "\u00A77", tier.getDisplayNameWithFormatting() + "\u00A77");
}
}
@@ -48,10 +48,14 @@ public class ItemBlankScroll extends Item implements IWorkbenchItem {
int cost = spell.getCost() * centre.getStack().getCount();
// Continuous spell scrolls require enough mana to cast them for the duration defined in ItemScroll.
if(spell.isContinuous) cost *= ItemScroll.CASTING_TIME / 20;
int manaPerItem = Constants.MANA_PER_CRYSTAL;
if(crystals.getStack().getItem() == WizardryItems.crystal_shard) manaPerItem = Constants.MANA_PER_SHARD;
if(crystals.getStack().getItem() == WizardryItems.grand_crystal) manaPerItem = Constants.GRAND_CRYSTAL_MANA;
if(crystals.getStack().getCount() * Constants.MANA_PER_CRYSTAL > cost){
if(crystals.getStack().getCount() * manaPerItem > cost){
// Rounds up to the nearest whole crystal
crystals.decrStackSize(cost / Constants.MANA_PER_CRYSTAL + 1);
crystals.decrStackSize(cost / manaPerItem + 1);
centre.putStack(new ItemStack(WizardryItems.scroll, centre.getStack().getCount(), spell.metadata()));
return true;
}
@@ -1,7 +1,6 @@
package electroblob.wizardry.item;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.data.SpellGlyphData;
import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.event.SpellCastEvent.Source;
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.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import net.minecraftforge.oredict.OreDictionary;
import java.util.List;
@@ -52,7 +50,7 @@ public class ItemScroll extends Item implements ISpellCastingItem {
if(tab == WizardryTabs.SPELLS){
List<Spell> spells = Spell.getSpells(Spell.allSpells);
List<Spell> spells = Spell.getAllSpells();
spells.removeIf(s -> !s.applicableForItem(this));
for(Spell spell : spells){
@@ -41,7 +41,7 @@ public class ItemSpellBook extends Item {
if(tab == WizardryTabs.SPELLS){
List<Spell> spells = Spell.getSpells(Spell.allSpells);
List<Spell> spells = Spell.getAllSpells();
spells.removeIf(s -> !s.applicableForItem(this));
for(Spell spell : spells){
@@ -27,7 +27,10 @@ import net.minecraft.inventory.Slot;
import net.minecraft.item.EnumAction;
import net.minecraft.item.Item;
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.RayTraceResult;
import net.minecraft.util.math.Vec3d;
@@ -397,7 +400,7 @@ public class ItemWand extends Item implements IWorkbenchItem, ISpellCastingItem,
if(MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Tick(Source.WAND, spell, caster, modifiers, castingTick))) return false;
}
int cost = (int)(spell.getCost() * modifiers.get(SpellModifiers.COST));
int cost = (int)(spell.getCost() * modifiers.get(SpellModifiers.COST) + 0.1f); // Weird floaty rounding
// As of wizardry 4.2 mana cost is only divided over two intervals each second
if(spell.isContinuous) cost = getDistributedCost(cost, castingTick);
@@ -433,7 +436,7 @@ public class ItemWand extends Item implements IWorkbenchItem, ISpellCastingItem,
caster.setActiveHand(hand);
// Mana cost
int cost = (int)(spell.getCost() * modifiers.get(SpellModifiers.COST));
int cost = (int)(spell.getCost() * modifiers.get(SpellModifiers.COST) + 0.1f); // Weird floaty rounding
// As of wizardry 4.2 mana cost is only divided over two intervals each second
if(spell.isContinuous) cost = getDistributedCost(cost, castingTick);
@@ -494,7 +497,7 @@ public class ItemWand extends Item implements IWorkbenchItem, ISpellCastingItem,
int castingTick = stack.getMaxItemUseDuration() - timeLeft; // Might as well include this
int cost = getDistributedCost((int)(spell.getCost() * modifiers.get(SpellModifiers.COST)), castingTick);
int cost = getDistributedCost((int)(spell.getCost() * modifiers.get(SpellModifiers.COST) + 0.1f), castingTick);
// Still need to check there's enough mana or the spell will finish twice, since running out of mana is
// handled separately.
@@ -330,7 +330,7 @@ public class ItemWizardArmour extends ItemArmor implements IWorkbenchItem, IMana
IAttributeInstance attribute = event.getEntityLiving().getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE);
double followRange = attribute == null ? 16 : attribute.getAttributeValue();
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;
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
@@ -62,7 +62,7 @@ public class BehaviourSpellDispense extends BehaviorDispenseOptional {
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(!spell.canBeCastByDispensers()) return super.dispenseStack(source, stack);
if(!spell.canBeCastBy(source.getBlockTileEntity())) return super.dispenseStack(source, stack);
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(PacketSyncAdvancements.class, PacketSyncAdvancements.Message.class);
registerMessage(PacketRequestAdvancementSync.class, PacketRequestAdvancementSync.Message.class);
registerMessage(PacketEndSlowTime.class, PacketEndSlowTime.Message.class);
registerMessage(PacketResurrection.class, PacketResurrection.Message.class);
registerMessage(PacketCastSpellAtPos.class, PacketCastSpellAtPos.Message.class);
registerMessage(PacketEmitterData.class, PacketEmitterData.Message.class);
@@ -2,6 +2,7 @@ package electroblob.wizardry.potion;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.util.NBTExtras;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.EntityLivingBase;
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
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)));
@@ -2,8 +2,6 @@ package electroblob.wizardry.potion;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.packet.PacketEndSlowTime;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions;
@@ -17,11 +15,15 @@ import net.minecraft.entity.IProjectile;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.projectile.EntityArrow;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent;
import net.minecraftforge.event.entity.living.PotionEvent;
import net.minecraftforge.fml.common.Mod;
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;
@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
public static final String NBT_KEY = "time_slowed";
public PotionSlowTime(boolean isBadEffect, int liquidColour){
super(isBadEffect, liquidColour, new ResourceLocation(Wizardry.MODID, "textures/gui/potion_icon_slow_time.png"));
this.setPotionName("potion." + Wizardry.MODID + ":slow_time");
@@ -61,6 +65,8 @@ public class PotionSlowTime extends PotionMagicEffect implements ISyncedPotion {
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
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
public static void onLivingUpdateEvent(LivingUpdateEvent event){
@@ -150,23 +179,16 @@ public class PotionSlowTime extends PotionMagicEffect implements ISyncedPotion {
}
@SubscribeEvent
public static void onPotionExpiryEvent(PotionEvent.PotionExpiryEvent event){
if(event.getPotionEffect() != null && event.getPotionEffect().getPotion() == WizardryPotions.slow_time){
unblockNearbyEntities(event.getEntityLiving());
if(!event.getEntity().world.isRemote){
WizardryPacketHandler.net.sendToDimension(new PacketEndSlowTime.Message(event.getEntityLiving()), event.getEntity().dimension);
}
}
public static void tick(TickEvent.WorldTickEvent event){
if(!event.world.isRemote && event.phase == TickEvent.Phase.END) cleanUpEntities(event.world);
}
// 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
public static void onPotionRemoveEvent(PotionEvent.PotionRemoveEvent event){
if(event.getPotionEffect() != null && event.getPotionEffect().getPotion() == WizardryPotions.slow_time){
unblockNearbyEntities(event.getEntityLiving());
if(!event.getEntity().world.isRemote){
WizardryPacketHandler.net.sendToDimension(new PacketEndSlowTime.Message(event.getEntityLiving()), event.getEntity().dimension);
}
}
public static void onPlayerLoggedOutEvent(PlayerEvent.PlayerLoggedOutEvent event){
if(event.player.updateBlocked) event.player.updateBlocked = false;
}
}
@@ -5,6 +5,7 @@ import electroblob.wizardry.entity.construct.*;
import electroblob.wizardry.entity.living.*;
import electroblob.wizardry.entity.projectile.*;
import electroblob.wizardry.spell.*;
import net.minecraft.entity.projectile.EntitySnowball;
import net.minecraft.init.MobEffects;
import net.minecraft.item.EnumAction;
import net.minecraft.util.ResourceLocation;
@@ -251,7 +252,7 @@ public final class Spells {
}.addProperties(Spell.DAMAGE).soundValues(1, 1.4f, 0.4f));
registry.register(new Ignite());
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 SpellProjectile<>("thunderbolt", EntityThunderbolt::new).addProperties(Spell.DAMAGE, EntityThunderbolt.KNOCKBACK_STRENGTH).soundValues(0.8f, 0.9f, 0.2f));
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 ShadowWard());
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 Glide());
registry.register(new SummonSpiritHorse());
@@ -372,7 +373,7 @@ public final class Spells {
registry.register(new Intimidate());
registry.register(new Banish());
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 PocketWorkbench());
registry.register(new ImbueWeapon());
@@ -5,7 +5,6 @@ import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.loot.RandomSpell;
import electroblob.wizardry.loot.WizardSpell;
import electroblob.wizardry.spell.Spell;
import net.minecraft.entity.EntityList;
import net.minecraft.entity.EnumCreatureType;
import net.minecraft.util.ResourceLocation;
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_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){
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.
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.
@@ -251,7 +258,7 @@ public final class WizardrySounds {
event.getRegistry().register(MISC_PAGE_TURN);
event.getRegistry().register(MISC_FREEZE);
for(Spell spell : Spell.getSpells(Spell.allSpells)){
for(Spell spell : Spell.getAllSpells()){
event.getRegistry().registerAll(spell.getSounds());
}
@@ -6,11 +6,13 @@ import electroblob.wizardry.util.NBTExtras;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.server.MinecraftServer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
@@ -32,9 +34,9 @@ public class ArcaneLock extends SpellRay {
@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
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
public boolean canBeCastByNPCs(){
public boolean canBeCastBy(EntityLiving npc, boolean override){
return true;
}
@@ -12,6 +12,7 @@ import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.monster.EntityZombie;
import net.minecraft.entity.player.EntityPlayer;
@@ -21,6 +22,7 @@ import net.minecraft.item.ItemStack;
import net.minecraft.pathfinding.Path;
import net.minecraft.pathfinding.PathNodeType;
import net.minecraft.pathfinding.PathPoint;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.TextComponentTranslation;
@@ -44,8 +46,8 @@ public class Clairvoyance extends Spell {
WizardData.registerStoredVariables(LOCATION_KEY, DIMENSION_KEY);
}
@Override public boolean canBeCastByNPCs() { return false; }
@Override public boolean canBeCastByDispensers() { return false; }
@Override public boolean canBeCastBy(EntityLiving npc, boolean override) { return false; }
@Override public boolean canBeCastBy(TileEntityDispenser dispenser) { return false; }
@Override
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 electroblob.wizardry.item.IConjuredItem;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.NBTExtras;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.player.EntityPlayer;
@@ -40,7 +41,7 @@ public class ConjureArmour extends SpellConjuration {
armour = new ItemStack(SPECTRAL_ARMOUR_MAP.get(slot));
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
armour.getTagCompound().setTag("ench", new NBTTagList());
NBTExtras.storeTagSafely(armour.getTagCompound(), "ench", new NBTTagList());
caster.setItemStackToSlot(slot, armour);
flag = true;
}
@@ -9,12 +9,14 @@ import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.*;
import electroblob.wizardry.util.ParticleBuilder.Type;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.nbt.NBTUtil;
import net.minecraft.potion.PotionEffect;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
@@ -45,9 +47,9 @@ public class CurseOfSoulbinding extends SpellRay {
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!
@Override public boolean canBeCastByDispensers() { return false; }
@Override public boolean canBeCastBy(TileEntityDispenser dispenser) { return false; }
@Override
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);
}
@Override public boolean canBeCastByNPCs(){ return true; }
@Override public boolean canBeCastBy(EntityLiving npc, boolean override){ return true; }
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
@@ -85,7 +85,7 @@ public class DragonFireball extends Spell {
}
@Override
public boolean canBeCastByNPCs(){
public boolean canBeCastBy(EntityLiving npc, boolean override){
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
public static void onSpellCastPreEvent(SpellCastEvent.Pre event){
// 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
* (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.item.EnumAction;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos;
@@ -29,8 +30,8 @@ public class ForestOfThorns extends Spell {
}
@Override public boolean requiresPacket(){ return false; }
@Override public boolean canBeCastByNPCs(){ return true; }
@Override public boolean canBeCastByDispensers(){ return true; }
@Override public boolean canBeCastBy(EntityLiving npc, boolean override){ return true; }
@Override public boolean canBeCastBy(TileEntityDispenser dispenser){ return true; }
@Override
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.item.EnumAction;
import net.minecraft.util.EnumHand;
import net.minecraft.util.SoundEvent;
import net.minecraft.world.World;
public class Glide extends Spell {
@@ -19,6 +19,7 @@ import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.EnumAction;
import net.minecraft.network.play.server.SPacketEntityVelocity;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.SoundEvent;
@@ -58,12 +59,12 @@ public class Grapple extends Spell {
}
@Override
public boolean canBeCastByNPCs(){
return super.canBeCastByNPCs();
public boolean canBeCastBy(EntityLiving npc, boolean override){
return true;
}
@Override
public boolean canBeCastByDispensers(){
public boolean canBeCastBy(TileEntityDispenser dispenser){
return true;
}
@@ -9,12 +9,14 @@ import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.item.EntityTNTPrimed;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.EnumAction;
import net.minecraft.network.play.server.SPacketEntityVelocity;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
@@ -41,8 +43,8 @@ public class GreaterTelekinesis extends SpellRay {
this.soundValues(0.8f, 1, 0.2f);
}
@Override public boolean canBeCastByNPCs() { return false; }
@Override public boolean canBeCastByDispensers() { return false; }
@Override public boolean canBeCastBy(EntityLiving npc, boolean override) { return false; }
@Override public boolean canBeCastBy(TileEntityDispenser dispenser) { return false; }
@Override
protected SoundEvent[] createSounds(){
@@ -10,6 +10,7 @@ import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.monster.*;
import net.minecraft.entity.passive.*;
@@ -52,7 +53,7 @@ public class Metamorphosis extends SpellRay {
this.soundValues(0.5f, 1f, 0);
}
@Override public boolean canBeCastByNPCs() { return false; }
@Override public boolean canBeCastBy(EntityLiving npc, boolean override) { return false; }
@Override
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.nbt.NBTTagCompound;
import net.minecraft.potion.PotionEffect;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
@@ -43,8 +44,8 @@ public class MindControl extends SpellRay {
addProperties(EFFECT_DURATION);
}
@Override public boolean canBeCastByNPCs() { return false; }
@Override public boolean canBeCastByDispensers() { return false; }
@Override public boolean canBeCastBy(EntityLiving npc, boolean override) { return false; }
@Override public boolean canBeCastBy(TileEntityDispenser dispenser) { return false; }
@Override
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.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.MobEffects;
@@ -28,7 +29,7 @@ public class Poison extends SpellRay {
}
@Override
public boolean canBeCastByNPCs(){
public boolean canBeCastBy(EntityLiving npc, boolean override){
return true;
}
@@ -16,6 +16,7 @@ import electroblob.wizardry.packet.PacketPossession;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.NBTExtras;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
@@ -43,6 +44,7 @@ import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.potion.PotionEffect;
import net.minecraft.potion.PotionUtils;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.*;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
@@ -123,8 +125,8 @@ public class Possession extends SpellRay {
addProperties(EFFECT_DURATION, CRITICAL_HEALTH);
}
@Override public boolean canBeCastByNPCs() { return false; }
@Override public boolean canBeCastByDispensers() { return false; }
@Override public boolean canBeCastBy(EntityLiving npc, boolean override) { return false; }
@Override public boolean canBeCastBy(TileEntityDispenser dispenser) { return false; }
@Override
public boolean requiresPacket(){
@@ -267,7 +269,7 @@ public class Possession extends SpellRay {
// Inventory and items
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();
@@ -1,6 +1,7 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.EnumHand;
@@ -17,7 +18,7 @@ public class ReplenishHunger extends SpellBuff {
addProperties(HUNGER_POINTS, SATURATION_MODIFIER);
}
@Override public boolean canBeCastByNPCs(){ return false; }
@Override public boolean canBeCastBy(EntityLiving npc, boolean override){ return false; }
@Override
protected boolean applyEffects(EntityLivingBase caster, SpellModifiers modifiers){
@@ -25,6 +25,8 @@ public class Resurrection extends Spell {
public static final String WAIT_TIME = "wait_time";
private static final String POTION_CORE_FIX_NBT_KEY = "Potion Core - Health Fix";
public Resurrection(){
super("resurrection", EnumAction.NONE, false);
addProperties(EFFECT_RADIUS, WAIT_TIME);
@@ -90,6 +92,8 @@ public class Resurrection extends Spell {
player.experience = 0;
player.experienceLevel = 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){
ParticleBuilder.spawnHealParticles(player.world, player);
@@ -8,6 +8,7 @@ import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.EnumAction;
import net.minecraft.potion.PotionEffect;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
@@ -28,7 +29,7 @@ public class Reversal extends SpellRay {
}
@Override
public boolean canBeCastByDispensers(){
public boolean canBeCastBy(TileEntityDispenser dispenser){
return false;
}
@@ -1,6 +1,7 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.EnumHand;
@@ -17,7 +18,7 @@ public class Satiety extends SpellBuff {
addProperties(HUNGER_POINTS, SATURATION_MODIFIER);
}
@Override public boolean canBeCastByNPCs(){ return false; }
@Override public boolean canBeCastBy(EntityLiving npc, boolean override){ return false; }
@Override
protected boolean applyEffects(EntityLivingBase caster, SpellModifiers modifiers){
@@ -1,6 +1,7 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.NBTExtras;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
@@ -12,6 +13,7 @@ import net.minecraft.entity.projectile.EntityShulkerBullet;
import net.minecraft.item.EnumAction;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTUtil;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos;
@@ -29,9 +31,9 @@ public class ShulkerBullet extends Spell {
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
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("Y", pos.getY());
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
world.spawnEntity(bullet);
@@ -3,7 +3,9 @@ package electroblob.wizardry.spell;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumHand;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
@@ -35,12 +37,12 @@ public class SlowTime extends SpellBuff {
}
@Override
public boolean canBeCastByDispensers(){
public boolean canBeCastBy(TileEntityDispenser dispenser){
return false;
}
@Override
public boolean canBeCastByNPCs(){
public boolean canBeCastBy(EntityLiving npc, boolean override){
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.item.EnumAction;
import net.minecraft.item.Item;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.ResourceLocation;
@@ -42,6 +43,7 @@ import net.minecraftforge.registries.IForgeRegistryEntry;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
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. */
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;
/** 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...
@@ -186,6 +199,8 @@ public abstract class Spell extends IForgeRegistryEntry.Impl<Spell> implements C
this.icon = new ResourceLocation(modID, "textures/spells/" + name + ".png");
this.sounds = createSounds();
this.id = nextSpellId++;
this.items(WizardryItems.spell_book, WizardryItems.scroll);
this.npcSelector((e, o) -> canBeCastByNPCs()); // Fallback to old behaviour until we remove it entirely
}
// ========================================= 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...)
*/
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
@@ -294,7 +309,7 @@ public abstract class Spell extends IForgeRegistryEntry.Impl<Spell> implements C
* @see Spell#createSoundsWithSuffixes(String[])
*/
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).
* <p></p>
* 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
* 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.
@@ -390,7 +405,7 @@ public abstract class Spell extends IForgeRegistryEntry.Impl<Spell> implements C
* won't work if the caster is on full health).
* <p></p>
* 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
* 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.
@@ -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,
@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
* {@link Spell#cast(World, EntityLiving, EnumHand, int, EntityLivingBase, SpellModifiers)}, you should override
* this to return true.
* @deprecated Use the entity-sensitive version {@link Spell#canBeCastBy(EntityLiving, boolean)}.
*/
@Deprecated
public boolean canBeCastByNPCs(){
return false;
}
/**
* Whether the given dispenser can cast this spell. If you have overridden
* {@link Spell#cast(World, double, double, double, EnumFacing, int, int, SpellModifiers)}, you should override this
* 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
* {@link Spell#cast(World, double, double, double, EnumFacing, int, int, SpellModifiers)}, you should override this
* to return true.
* @deprecated Use the tileentity-sensitive version {@link Spell#canBeCastBy(TileEntityDispenser)}.
*/
@Deprecated
public boolean canBeCastByDispensers(){
return false;
}
@@ -561,26 +606,6 @@ public abstract class Spell extends IForgeRegistryEntry.Impl<Spell> implements C
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
* 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,
// they will remain in the order they were registered.
@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
* 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(){
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
* 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
* 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
* returned list is local to this method. Never includes the {@link None} spell.
*
* @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
* empty.</i>
*
* @see TierElementFilter
*/
public static List<Spell> getSpells(Predicate<Spell> filter){
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;
/** 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;
/** 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();
/**
@@ -11,6 +11,7 @@ import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.MathHelper;
@@ -29,9 +30,9 @@ import java.util.function.Function;
* <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#canBeCastByNPCs()}
* By default, this type of spell can be cast by NPCs. {@link Spell#canBeCastBy(EntityLiving, boolean)}
* <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>
* 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);
this.arrowFactory = arrowFactory;
this.addProperties(RANGE);
this.npcSelector((e, o) -> true);
}
@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; }
/** 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
@@ -11,6 +11,7 @@ import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.potion.Potion;
import net.minecraft.potion.PotionEffect;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.AxisAlignedBB;
@@ -32,9 +33,9 @@ import java.util.stream.Collectors;
* <p></p>
* Properties added by this type of spell: {@link SpellBuff#getDurationKey(Potion)}, {@link SpellBuff#getStrengthKey(Potion)}
* <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>
* 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>
* 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.g = g;
this.b = b;
this.npcSelector((e, o) -> true);
}
@Override
@@ -103,9 +105,7 @@ public class SpellBuff extends Spell {
return this;
}
@Override public boolean canBeCastByNPCs(){ return true; }
@Override public boolean canBeCastByDispensers() { return true; }
@Override public boolean canBeCastBy(TileEntityDispenser dispenser) { return true; }
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
@@ -24,9 +24,9 @@ import net.minecraft.world.World;
* <p></p>
* Properties added by this type of spell: {@link SpellConjuration#ITEM_LIFETIME}
* <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>
* 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>
* 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.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos;
@@ -29,9 +30,9 @@ import java.util.function.Function;
* <p></p>
* Properties added by this type of spell: {@link Spell#DURATION} (if the construct is not permanent)
* <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>
* 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>
* 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);
this.constructFactory = constructFactory;
this.permanent = permanent;
this.npcSelector((e, o) -> true);
if(!permanent) this.addProperties(DURATION);
}
@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.
@@ -10,6 +10,7 @@ import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.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
* permanent)
* <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>
* 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>
* 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){
super(modID, name, EnumAction.NONE, constructFactory, permanent);
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 canBeCastByNPCs(){ return true; }
@Override public boolean canBeCastByDispensers() { return true; }
@Override public boolean canBeCastBy(TileEntityDispenser dispenser) { return true; }
@Override
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.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos;
@@ -32,9 +33,9 @@ import java.util.function.Function;
* <p></p>
* Properties added by this type of spell: {@link SpellMinion#MINION_LIFETIME}
* <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>
* 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>
* 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);
this.minionFactory = minionFactory;
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 canBeCastByNPCs(){ return true; }
@Override public boolean canBeCastByDispensers() { return true; }
@Override public boolean canBeCastBy(TileEntityDispenser dispenser) { return true; }
@Override
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.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.MathHelper;
@@ -29,9 +30,9 @@ import java.util.function.Function;
* <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#canBeCastByNPCs()}
* By default, this type of spell can be cast by NPCs. {@link Spell#canBeCastBy(EntityLiving, boolean)}
* <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>
* 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);
this.projectileFactory = projectileFactory;
addProperties(RANGE);
this.npcSelector((e, o) -> true);
}
@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; }
/** 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
@@ -10,6 +10,7 @@ import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.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
* 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
* 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
@@ -34,9 +35,9 @@ import javax.annotation.Nullable;
* <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#canBeCastByNPCs()}
* By default, this type of spell can be cast by NPCs. {@link Spell#canBeCastBy(EntityLiving, boolean)}
* <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>
* 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){
super(modID, name, action, isContinuous);
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
@@ -150,10 +152,8 @@ public abstract class SpellRay extends Spell {
this.aimAssist = aimAssist;
return this;
}
@Override public boolean canBeCastByNPCs(){ return true; }
@Override public boolean canBeCastByDispensers() { return true; }
@Override public boolean canBeCastBy(TileEntityDispenser dispenser) { return true; }
// Finally everything in here is standardised and written in a form that's actually readable - it was long overdue!
@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.util.EnumHand;
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.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
public class SummonSnowGolem extends Spell {
@@ -6,9 +6,6 @@ import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import javax.annotation.Nullable;
public class Tornado extends SpellConstruct<EntityTornado> {
@@ -33,7 +33,7 @@ public class WitherSkull extends Spell {
}
@Override
public boolean canBeCastByNPCs(){
public boolean canBeCastBy(EntityLiving npc, boolean override){
return true;
}
@@ -7,6 +7,7 @@ import electroblob.wizardry.item.ItemCrystal;
import electroblob.wizardry.item.ItemSpellBook;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.NBTExtras;
import electroblob.wizardry.util.WandHelper;
import net.minecraft.block.Block;
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;
}
@@ -184,7 +184,7 @@ public class TileEntityShrineCore extends TileEntity implements ITickable {
for(EntityLivingBase entity : entities){
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){
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();
for(UUID uuid : linkedWizards){
if(uuid != null) tagList.appendTag(NBTUtil.createUUIDTag(uuid));
}
compound.setTag("wizards", tagList);
NBTExtras.storeTagSafely(compound, "wizards", tagList);
return super.writeToNBT(compound);
}
@@ -1,5 +1,6 @@
package electroblob.wizardry.tileentity;
import electroblob.wizardry.util.NBTExtras;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.block.Block;
import net.minecraft.entity.EntityList;
@@ -152,7 +153,7 @@ public class TileEntityStatue extends TileEntity implements ITickable {
tagCompound.setFloat("entityYawHead", creature.rotationYawHead);
tagCompound.setFloat("entityYawOffset", creature.renderYawOffset);
}
tagCompound.setTag("entity", entityCompound);
NBTExtras.storeTagSafely(tagCompound, "entity", entityCompound);
tagCompound.setInteger("timer", timer);
tagCompound.setInteger("lifetime", lifetime);
tagCompound.setBoolean("isIce", isIce);
@@ -8,7 +8,10 @@ import net.minecraft.entity.monster.*;
import net.minecraft.util.DamageSource;
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:
// 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()){
NBTTagCompound mapping = new NBTTagCompound();
mapping.setTag(keyTagName, keyFunction.apply(entry.getKey()));
mapping.setTag(valueTagName, valueFunction.apply(entry.getValue()));
NBTExtras.storeTagSafely(mapping, keyTagName, keyFunction.apply(entry.getKey()));
NBTExtras.storeTagSafely(mapping, valueTagName, valueFunction.apply(entry.getValue()));
tagList.appendTag(mapping);
}
@@ -205,6 +205,51 @@ public final class NBTExtras {
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
* {@link NBTTagCompound#setUniqueId(String, UUID)}. Allows for neater storage to NBTTagLists.
@@ -247,7 +247,7 @@ public final class SpellProperties {
public static void init(){
// 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();
@@ -398,7 +398,7 @@ public final class WandHelper {
if(wand.getTagCompound() == null) wand.setTagCompound((new NBTTagCompound()));
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);
@@ -406,7 +406,7 @@ public final class WandHelper {
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. */
@@ -3,6 +3,7 @@ package electroblob.wizardry.worldgen;
import com.google.common.math.Quantiles;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.registry.WizardryAdvancementTriggers;
import electroblob.wizardry.util.NBTExtras;
import electroblob.wizardry.util.WizardryUtilities;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
@@ -256,7 +257,7 @@ public abstract class WorldGenSurfaceStructure implements IWorldGenerator {
NBTTagCompound tag = new NBTTagCompound();
tag.setInteger("ChunkX", chunkX);
tag.setInteger("ChunkZ", chunkZ);
tag.setTag("BB", settings.getBoundingBox().toNBTTagIntArray());
NBTExtras.storeTagSafely(tag, "BB", settings.getBoundingBox().toNBTTagIntArray());
structureData.writeInstance(tag, chunkX, chunkZ);
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\:arcane_tome.name=Tome of Arcana
item.ebwizardry\:arcane_tome.desc1=Upgrades any %1$s
item.ebwizardry\:arcane_tome.desc2=wand to %1$s tier
item.ebwizardry\:arcane_tome.desc=Upgrades any %1$s wand with sufficient progression to %2$s tier
item.ebwizardry\:wizard_handbook.name=The Wizard's Handbook
item.ebwizardry\:wizard_handbook.desc=by %1$s
@@ -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\:arcane_tome.name=Tome of Arcana
item.ebwizardry\:arcane_tome.desc1=Upgrades any %1$s
item.ebwizardry\:arcane_tome.desc2=wand to %1$s tier
item.ebwizardry\:arcane_tome.desc=Upgrades any %1$s wand with sufficient progression to %2$s tier
item.ebwizardry\:wizard_handbook.name=The Wizard's Handbook
item.ebwizardry\:wizard_handbook.desc=by %1$s
@@ -7,8 +7,30 @@ tile.ebwizardry:snare.name=Ловушка
tile.ebwizardry:transportation_stone.name=Камень Транспортировки
tile.ebwizardry:spectral_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:apprentice_wand.name=Жезл Ученика
item.ebwizardry:advanced_wand.name=Продвинутый Жезл
@@ -62,7 +84,21 @@ item.ebwizardry:spectral_sword.name=Спектральный Меч
item.ebwizardry:spectral_pickaxe.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:siphon_upgrade.name=Улучшение Сифона Жезла
item.ebwizardry:condenser_upgrade.name=Улучшение Конденсатора Жезла
@@ -75,9 +111,13 @@ item.ebwizardry:attunement_upgrade.name=Улучшение Хранилища З
item.ebwizardry:flaming_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:poison_bomb.name=Ядовитая Бомба
item.ebwizardry:smoke_bomb.name=Дымовая Бомба
item.ebwizardry:spark_bomb.name=Бомба Искр
item.ebwizardry:blank_scroll.name=Пустой Свиток
item.ebwizardry:scroll.name=Свиток %1$s
@@ -137,6 +177,75 @@ item.ebwizardry:wizard_leggings_healing.name=Поножи Целителя
item.ebwizardry:wizard_boots_healing.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: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.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.

After

Width:  |  Height:  |  Size: 1.8 KiB

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