From a33715ad51a3d9a29f40ab58cf3641b9be487755 Mon Sep 17 00:00:00 2001 From: Electroblob77 <35599699+Electroblob77@users.noreply.github.com> Date: Sun, 31 May 2020 17:16:55 +0100 Subject: [PATCH] Add zombie apocalypse spell --- .../wizardry/client/ClientProxy.java | 1 + .../client/renderer/RenderZombieSpawner.java | 120 ++++++++++++++++++ .../entity/construct/EntityZombieSpawner.java | 63 +++++++++ .../entity/living/EntityZombieMinion.java | 4 +- .../electroblob/wizardry/registry/Spells.java | 4 + .../wizardry/registry/WizardryEntities.java | 1 + .../wizardry/spell/SpellMinion.java | 2 +- .../wizardry/spell/ZombieApocalypse.java | 27 ++++ .../assets/ebwizardry/lang/en_gb.lang | 3 + .../assets/ebwizardry/lang/en_us.lang | 3 + .../ebwizardry/spells/zombie_apocalypse.json | 24 ++++ .../textures/entity/zombie_spawner.png | Bin 0 -> 3530 bytes .../textures/spells/zombie_apocalypse.png | Bin 0 -> 5700 bytes 13 files changed, 250 insertions(+), 2 deletions(-) create mode 100644 src/main/java/electroblob/wizardry/client/renderer/RenderZombieSpawner.java create mode 100644 src/main/java/electroblob/wizardry/entity/construct/EntityZombieSpawner.java create mode 100644 src/main/java/electroblob/wizardry/spell/ZombieApocalypse.java create mode 100644 src/main/resources/assets/ebwizardry/spells/zombie_apocalypse.json create mode 100644 src/main/resources/assets/ebwizardry/textures/entity/zombie_spawner.png create mode 100644 src/main/resources/assets/ebwizardry/textures/spells/zombie_apocalypse.png diff --git a/src/main/java/electroblob/wizardry/client/ClientProxy.java b/src/main/java/electroblob/wizardry/client/ClientProxy.java index 584af884..5246f4a7 100644 --- a/src/main/java/electroblob/wizardry/client/ClientProxy.java +++ b/src/main/java/electroblob/wizardry/client/ClientProxy.java @@ -789,6 +789,7 @@ public class ClientProxy extends CommonProxy { RenderingRegistry.registerEntityRenderingHandler(EntityHammer.class, RenderHammer::new); RenderingRegistry.registerEntityRenderingHandler(EntityIceSpike.class, RenderIceSpike::new); RenderingRegistry.registerEntityRenderingHandler(EntityForcefield.class, RenderForcefield::new); + RenderingRegistry.registerEntityRenderingHandler(EntityZombieSpawner.class, RenderZombieSpawner::new); //RenderingRegistry.registerEntityRenderingHandler(EntityContainmentField.class, RenderContainmentField::new); // Stuff that doesn't render diff --git a/src/main/java/electroblob/wizardry/client/renderer/RenderZombieSpawner.java b/src/main/java/electroblob/wizardry/client/renderer/RenderZombieSpawner.java new file mode 100644 index 00000000..e5c2f8ce --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/RenderZombieSpawner.java @@ -0,0 +1,120 @@ +package electroblob.wizardry.client.renderer; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.entity.construct.EntityZombieSpawner; +import electroblob.wizardry.util.WizardryUtilities; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.entity.Render; +import net.minecraft.client.renderer.entity.RenderManager; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import org.lwjgl.opengl.GL11; + +public class RenderZombieSpawner extends Render { + + private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/entity/zombie_spawner.png"); + + private static final Vec3d[] HIDDEN_BOX = WizardryUtilities.getVertices(new AxisAlignedBB(-1, 0, -1, 1, 2.1, 1)); + + public RenderZombieSpawner(RenderManager renderManager){ + super(renderManager); + } + + @Override + public void doRender(EntityZombieSpawner entity, double x, double y, double z, float yaw, float partialTicks){ + + GlStateManager.pushMatrix(); + GlStateManager.enableBlend(); + GlStateManager.disableLighting(); + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240, 240); + GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + + GlStateManager.translate(x, y, z); + + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder buffer = tessellator.getBuffer(); + + // The visible bit + GlStateManager.pushMatrix(); + + int animationTicks = 10; + float age = entity.ticksExisted + partialTicks; + float s = age < animationTicks ? age/animationTicks : MathHelper.clamp((entity.lifetime - age) / animationTicks, 0, 1); + s = (float)Math.pow(s, 0.4); // Smooths the animation + + GlStateManager.scale(s, s, s); + GlStateManager.rotate(age, 0, 1, 0); + + this.bindTexture(TEXTURE); + + Vec3d[] vertices = WizardryUtilities.getVertices(entity.getEntityBoundingBox().offset(entity.getPositionVector().scale(-1))); + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + + drawFace(buffer, vertices[0], vertices[1], vertices[3], vertices[2], 0, 0, 1, 1); // Top + drawFace(buffer, vertices[1], vertices[0], vertices[2], vertices[3], 0, 0, 1, 1); // Bottom + + tessellator.draw(); + + GlStateManager.popMatrix(); + + // Hidden box + GlStateManager.disableTexture2D(); + GlStateManager.colorMask(false, false, false, false); // Magically hide the zombies + + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR); + + // Outside (hides everything) + drawFaceColour(buffer, HIDDEN_BOX[6], HIDDEN_BOX[7], HIDDEN_BOX[2], HIDDEN_BOX[3], 0, 0, 0, 1); // South + drawFaceColour(buffer, HIDDEN_BOX[5], HIDDEN_BOX[6], HIDDEN_BOX[1], HIDDEN_BOX[2], 0, 0, 0, 1); // East + drawFaceColour(buffer, HIDDEN_BOX[4], HIDDEN_BOX[5], HIDDEN_BOX[0], HIDDEN_BOX[1], 0, 0, 0, 1); // North + drawFaceColour(buffer, HIDDEN_BOX[7], HIDDEN_BOX[4], HIDDEN_BOX[3], HIDDEN_BOX[0], 0, 0, 0, 1); // West + drawFaceColour(buffer, HIDDEN_BOX[5], HIDDEN_BOX[4], HIDDEN_BOX[6], HIDDEN_BOX[7], 0, 0, 0, 1); // Top + + tessellator.draw(); + + GlStateManager.disableBlend(); + GlStateManager.enableLighting(); + GlStateManager.disableRescaleNormal(); + GlStateManager.enableTexture2D(); + GlStateManager.colorMask(true, true, true, true); + GlStateManager.popMatrix(); + } + + @Override + public void renderMultipass(EntityZombieSpawner entity, double x, double y, double z, float yaw, float partialTicks){ + + + } + + @Override + public boolean isMultipass(){ + return false; + } + + @Override + protected ResourceLocation getEntityTexture(EntityZombieSpawner entity){ + return TEXTURE; + } + + private static void drawFace(BufferBuilder buffer, Vec3d topLeft, Vec3d topRight, Vec3d bottomLeft, Vec3d bottomRight, float u1, float v1, float u2, float v2){ + buffer.pos(topLeft.x, topLeft.y, topLeft.z).tex(u1, v1).endVertex(); + buffer.pos(topRight.x, topRight.y, topRight.z).tex(u2, v1).endVertex(); + buffer.pos(bottomRight.x, bottomRight.y, bottomRight.z).tex(u2, v2).endVertex(); + buffer.pos(bottomLeft.x, bottomLeft.y, bottomLeft.z).tex(u1, v2).endVertex(); + } + + private static void drawFaceColour(BufferBuilder buffer, Vec3d topLeft, Vec3d topRight, Vec3d bottomLeft, Vec3d bottomRight, float r, float g, float b, float a){ + buffer.pos(topLeft.x, topLeft.y, topLeft.z).color(r, g, b, a).endVertex(); + buffer.pos(topRight.x, topRight.y, topRight.z).color(r, g, b, a).endVertex(); + buffer.pos(bottomRight.x, bottomRight.y, bottomRight.z).color(r, g, b, a).endVertex(); + buffer.pos(bottomLeft.x, bottomLeft.y, bottomLeft.z).color(r, g, b, a).endVertex(); + } + +} diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityZombieSpawner.java b/src/main/java/electroblob/wizardry/entity/construct/EntityZombieSpawner.java new file mode 100644 index 00000000..9c8f24d2 --- /dev/null +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityZombieSpawner.java @@ -0,0 +1,63 @@ +package electroblob.wizardry.entity.construct; + +import electroblob.wizardry.entity.living.EntityZombieMinion; +import electroblob.wizardry.registry.Spells; +import electroblob.wizardry.spell.SpellMinion; +import electroblob.wizardry.spell.ZombieApocalypse; +import electroblob.wizardry.util.ParticleBuilder; +import electroblob.wizardry.util.ParticleBuilder.Type; +import electroblob.wizardry.util.WizardryUtilities.Operations; +import net.minecraft.entity.SharedMonsterAttributes; +import net.minecraft.entity.ai.attributes.AttributeModifier; +import net.minecraft.entity.ai.attributes.IAttributeInstance; +import net.minecraft.world.World; + +public class EntityZombieSpawner extends EntityMagicConstruct { + + public EntityZombieSpawner(World world){ + super(world); + this.setSize(4, 2); + } + + @Override + public void onUpdate(){ + + super.onUpdate(); + + if(!world.isRemote && rand.nextInt(Spells.zombie_apocalypse.getProperty(ZombieApocalypse.MINION_SPAWN_INTERVAL).intValue()) == 0){ + + EntityZombieMinion zombie = new EntityZombieMinion(world); + + zombie.setPosition(this.posX, this.posY, this.posZ); + zombie.setCaster(this.getCaster()); + // Modifier implementation + // Attribute modifiers are pretty opaque, see https://minecraft.gamepedia.com/Attribute#Modifiers + zombie.setLifetime(Spells.zombie_apocalypse.getProperty(SpellMinion.MINION_LIFETIME).intValue()); + IAttributeInstance attribute = zombie.getEntityAttribute(SharedMonsterAttributes.ATTACK_DAMAGE); + attribute.applyModifier(new AttributeModifier(SpellMinion.POTENCY_ATTRIBUTE_MODIFIER, + damageMultiplier - 1, Operations.MULTIPLY_CUMULATIVE)); + zombie.setHealth(zombie.getMaxHealth()); // Need to set this because we may have just modified the value + zombie.hurtResistantTime = 30; // Prevent fall damage + zombie.showSpawnParticles = false; // Hide spawn particles or they pop out the top of the hidden box + + world.spawnEntity(zombie); + } + + if(world.isRemote){ + + float b = 0.15f; + + for(double r = 1.5; r < 4; r += 0.2){ + ParticleBuilder.create(Type.CLOUD).clr(b-=0.02, 0, 0).pos(posX, posY - 0.3, posZ).scale(0.5f / (float)r) + .spin(r, 0.02/r * (1 + world.rand.nextDouble())).spawn(world); + } + + } + + } + + @Override + public boolean shouldRenderInPass(int pass){ + return super.shouldRenderInPass(pass); + } +} diff --git a/src/main/java/electroblob/wizardry/entity/living/EntityZombieMinion.java b/src/main/java/electroblob/wizardry/entity/living/EntityZombieMinion.java index f61e33a3..861398cd 100644 --- a/src/main/java/electroblob/wizardry/entity/living/EntityZombieMinion.java +++ b/src/main/java/electroblob/wizardry/entity/living/EntityZombieMinion.java @@ -24,6 +24,8 @@ import java.util.UUID; public class EntityZombieMinion extends EntityZombie implements ISummonedCreature { + public boolean showSpawnParticles = true; + // Field implementations private int lifetime = -1; private UUID casterUUID; @@ -72,7 +74,7 @@ public class EntityZombieMinion extends EntityZombie implements ISummonedCreatur @Override public void onSpawn(){ - this.spawnParticleEffect(); + if(showSpawnParticles) this.spawnParticleEffect(); } @Override diff --git a/src/main/java/electroblob/wizardry/registry/Spells.java b/src/main/java/electroblob/wizardry/registry/Spells.java index 704a956f..ff1c6b60 100644 --- a/src/main/java/electroblob/wizardry/registry/Spells.java +++ b/src/main/java/electroblob/wizardry/registry/Spells.java @@ -242,6 +242,8 @@ public final class Spells { public static final Spell stormcloud = placeholder(); + public static final Spell zombie_apocalypse = placeholder(); + @SubscribeEvent public static void register(RegistryEvent.Register event){ @@ -445,6 +447,8 @@ public final class Spells { registry.register(new Stormcloud()); + registry.register(new ZombieApocalypse()); + } } \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/registry/WizardryEntities.java b/src/main/java/electroblob/wizardry/registry/WizardryEntities.java index e89b5e79..d4162a34 100644 --- a/src/main/java/electroblob/wizardry/registry/WizardryEntities.java +++ b/src/main/java/electroblob/wizardry/registry/WizardryEntities.java @@ -146,6 +146,7 @@ public class WizardryEntities { registry.register(createEntry(EntityFireRing.class, "ring_of_fire", TrackingType.CONSTRUCT).build()); registry.register(createEntry(EntityHealAura.class, "healing_aura", TrackingType.CONSTRUCT).build()); registry.register(createEntry(EntityDecay.class, "decay", TrackingType.CONSTRUCT).build()); + registry.register(createEntry(EntityZombieSpawner.class, "zombie_spawner", TrackingType.CONSTRUCT).build()); // These ones don't render, currently that makes no difference here but we might as well separate them registry.register(createEntry(EntityArrowRain.class, "arrow_rain", TrackingType.CONSTRUCT).build()); diff --git a/src/main/java/electroblob/wizardry/spell/SpellMinion.java b/src/main/java/electroblob/wizardry/spell/SpellMinion.java index c9e8b1ed..bc047627 100644 --- a/src/main/java/electroblob/wizardry/spell/SpellMinion.java +++ b/src/main/java/electroblob/wizardry/spell/SpellMinion.java @@ -52,7 +52,7 @@ public class SpellMinion extends Spe * entity attribute modifier. */ public static final String HEALTH_MODIFIER = "minion_health"; /** The string identifier for the potency attribute modifier. */ - private static final String POTENCY_ATTRIBUTE_MODIFIER = "potency"; + public static final String POTENCY_ATTRIBUTE_MODIFIER = "potency"; /** A factory that creates summoned creature entities. */ protected final Function minionFactory; diff --git a/src/main/java/electroblob/wizardry/spell/ZombieApocalypse.java b/src/main/java/electroblob/wizardry/spell/ZombieApocalypse.java new file mode 100644 index 00000000..553041e7 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/ZombieApocalypse.java @@ -0,0 +1,27 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.entity.construct.EntityZombieSpawner; +import electroblob.wizardry.item.SpellActions; +import electroblob.wizardry.util.SpellModifiers; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.util.EnumFacing; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public class ZombieApocalypse extends SpellConstruct { + + public static final String MINION_SPAWN_INTERVAL = "minion_spawn_interval"; + + public ZombieApocalypse(){ + super("zombie_apocalypse", SpellActions.POINT_UP, EntityZombieSpawner::new, false); + addProperties(SpellMinion.MINION_LIFETIME, MINION_SPAWN_INTERVAL); + } + + @Override + protected boolean spawnConstruct(World world, double x, double y, double z, EnumFacing side, @Nullable EntityLivingBase caster, SpellModifiers modifiers){ + y += 8; + return super.spawnConstruct(world, x, y, z, side, caster, modifiers); + } + +} diff --git a/src/main/resources/assets/ebwizardry/lang/en_gb.lang b/src/main/resources/assets/ebwizardry/lang/en_gb.lang index 539c1ce5..63af868e 100644 --- a/src/main/resources/assets/ebwizardry/lang/en_gb.lang +++ b/src/main/resources/assets/ebwizardry/lang/en_gb.lang @@ -494,6 +494,7 @@ entity.ebwizardry\:earthquake.name=Earthquake entity.ebwizardry\:hailstorm.name=Hailstorm entity.ebwizardry\:lightning_pulse.name=Lightning Pulse entity.ebwizardry\:stormcloud.name=Stormcloud +entity.ebwizardry\:zombie_spawner.name=Zombie Spawner itemGroup.ebwizardry=Wizardry itemGroup.ebwizardryspells=Spells @@ -786,6 +787,7 @@ spell.ebwizardry\:water_breathing=Water Breathing spell.ebwizardry\:whirlwind=Whirlwind spell.ebwizardry\:wither=Wither spell.ebwizardry\:wither_skull=Wither Skull +spell.ebwizardry\:zombie_apocalypse=Zombie Apocalypse spell.ebwizardry\:agility.desc=Grants the caster faster movement speed and greater jump height for 30 seconds. spell.ebwizardry\:arc.desc=Fires a spark of lightning at the target. @@ -961,6 +963,7 @@ spell.ebwizardry\:water_breathing.desc=Allows the caster to breathe underwater f spell.ebwizardry\:whirlwind.desc=Causes the target to be blown upwards and away from you at speed. spell.ebwizardry\:wither.desc=Fires a ray of darkness which withers anything it touches. spell.ebwizardry\:wither_skull.desc=Launches a wither skull in the direction you are pointing. +spell.ebwizardry\:zombie_apocalypse.desc=Opening a portal to the underworld is easy. The difficult part is shutting it again when things go wrong... spell.ebwizardry\:invoke_weather.sun=The rain begins to stop... spell.ebwizardry\:invoke_weather.rain=The heavens open... diff --git a/src/main/resources/assets/ebwizardry/lang/en_us.lang b/src/main/resources/assets/ebwizardry/lang/en_us.lang index 466f7a00..231d7891 100644 --- a/src/main/resources/assets/ebwizardry/lang/en_us.lang +++ b/src/main/resources/assets/ebwizardry/lang/en_us.lang @@ -494,6 +494,7 @@ entity.ebwizardry\:earthquake.name=Earthquake entity.ebwizardry\:hailstorm.name=Hailstorm entity.ebwizardry\:lightning_pulse.name=Lightning Pulse entity.ebwizardry\:stormcloud.name=Stormcloud +entity.ebwizardry\:zombie_spawner.name=Zombie Spawner itemGroup.ebwizardry=Wizardry itemGroup.ebwizardryspells=Spells @@ -786,6 +787,7 @@ spell.ebwizardry\:water_breathing=Water Breathing spell.ebwizardry\:whirlwind=Whirlwind spell.ebwizardry\:wither=Wither spell.ebwizardry\:wither_skull=Wither Skull +spell.ebwizardry\:zombie_apocalypse=Zombie Apocalypse spell.ebwizardry\:agility.desc=Grants the caster faster movement speed and greater jump height for 30 seconds. spell.ebwizardry\:arc.desc=Fires a spark of lightning at the target. @@ -961,6 +963,7 @@ spell.ebwizardry\:water_breathing.desc=Allows the caster to breathe underwater f spell.ebwizardry\:whirlwind.desc=Causes the target to be blown upwards and away from you at speed. spell.ebwizardry\:wither.desc=Fires a ray of darkness which withers anything it touches. spell.ebwizardry\:wither_skull.desc=Launches a wither skull in the direction you are pointing. +spell.ebwizardry\:zombie_apocalypse.desc=Opening a portal to the underworld is easy. The difficult part is shutting it again when things go wrong... spell.ebwizardry\:invoke_weather.sun=The rain begins to stop... spell.ebwizardry\:invoke_weather.rain=The heavens open... diff --git a/src/main/resources/assets/ebwizardry/spells/zombie_apocalypse.json b/src/main/resources/assets/ebwizardry/spells/zombie_apocalypse.json new file mode 100644 index 00000000..13576fe7 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/zombie_apocalypse.json @@ -0,0 +1,24 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "master", + "element": "necromancy", + "type": "construct", + "cost": 150, + "chargeup": 0, + "cooldown": 300, + "base_properties": { + "duration": 500, + "minion_lifetime": 600, + "minion_spawn_interval": 30 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/entity/zombie_spawner.png b/src/main/resources/assets/ebwizardry/textures/entity/zombie_spawner.png new file mode 100644 index 0000000000000000000000000000000000000000..33a4f4b4acfbdae7565bff4f6cc67edc91ccfacd GIT binary patch literal 3530 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 OpUl(M&t;ucLK6UYUwl^p literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ebwizardry/textures/spells/zombie_apocalypse.png b/src/main/resources/assets/ebwizardry/textures/spells/zombie_apocalypse.png new file mode 100644 index 0000000000000000000000000000000000000000..5d4bc1d3206c6cb230034f78a7979e473bcbb13e GIT binary patch literal 5700 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 YHJ4$>pPS;03=9kmp00i_>zopr00~j!*#H0l literal 0 HcmV?d00001