Add withering totem spell

This commit is contained in:
Electroblob77
2020-08-21 00:08:31 +01:00
parent 6cdff07180
commit ba487bea41
12 changed files with 403 additions and 0 deletions
@@ -816,6 +816,7 @@ public class ClientProxy extends CommonProxy {
RenderingRegistry.registerEntityRenderingHandler(EntityZombieSpawner.class, RenderZombieSpawner::new);
RenderingRegistry.registerEntityRenderingHandler(EntityRadiantTotem.class, RenderRadiantTotem::new);
RenderingRegistry.registerEntityRenderingHandler(EntityBoulder.class, RenderBoulder::new);
RenderingRegistry.registerEntityRenderingHandler(EntityWitheringTotem.class, RenderWitheringTotem::new);
//RenderingRegistry.registerEntityRenderingHandler(EntityContainmentField.class, RenderContainmentField::new);
// Stuff that doesn't render
@@ -0,0 +1,159 @@
package electroblob.wizardry.client.renderer.entity;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.DrawingUtils;
import electroblob.wizardry.entity.construct.EntityWitheringTotem;
import electroblob.wizardry.util.GeometryUtils;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.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.Vec3d;
import org.lwjgl.opengl.GL11;
import javax.annotation.Nullable;
public class RenderWitheringTotem extends Render<EntityWitheringTotem> {
private static final ResourceLocation FLARE_TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/entity/totem/flare.png");
private static final ResourceLocation[] CUBE_TEXTURES = new ResourceLocation[14];
static {
for(int i = 0; i< CUBE_TEXTURES.length; i++) CUBE_TEXTURES[i] = new ResourceLocation(Wizardry.MODID, "textures/entity/totem/cube_" + i + ".png");
}
public RenderWitheringTotem(RenderManager manager){
super(manager);
}
@Nullable
@Override
protected ResourceLocation getEntityTexture(EntityWitheringTotem entity){
return CUBE_TEXTURES[entity.ticksExisted % CUBE_TEXTURES.length];
}
@Override
public void doRender(EntityWitheringTotem entity, double x, double y, double z, float entityYaw, float partialTicks){
Tessellator tessellator = Tessellator.getInstance();
GlStateManager.pushMatrix();
GlStateManager.enableBlend();
GlStateManager.disableLighting();
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240, 240);
GlStateManager.depthMask(false);
GlStateManager.disableCull();
GlStateManager.translate(x, y + entity.height/2, z);
float charge = entity.getHealthDrained() / 50f;
float s = DrawingUtils.smoothScaleFactor(entity.lifetime, entity.ticksExisted, partialTicks, 10, 10);
s *= 1 + charge * 0.3f; // Gets bigger the more health it drains
GlStateManager.scale(s, s, s);
drawFlare(charge, tessellator);
drawCube(entity, tessellator, partialTicks);
GlStateManager.disableBlend();
GlStateManager.enableLighting();
GlStateManager.depthMask(true);
GlStateManager.enableCull();
GlStateManager.popMatrix();
}
private void drawFlare(float redness, Tessellator tessellator){
BufferBuilder buffer = tessellator.getBuffer();
GlStateManager.pushMatrix();
int c = DrawingUtils.mix(0xb333e6, 0xff0044, redness);
int r = c >> 16 & 255;
int g = c >> 8 & 255;
int b = c & 255;
// Makes the colour add to the colour of the texture pixels, rather than the default multiplying
GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_ADD);
GlStateManager.color(r/255f, g/255f, b/255f); // Gets redder the more health it drains
GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
bindTexture(FLARE_TEXTURE);
// This counteracts the reverse rotation behaviour when in front f5 view.
// Fun fact: this is a bug with vanilla too! Look at a snowball in front f5 view, for example.
float yaw = Minecraft.getMinecraft().gameSettings.thirdPersonView == 2
? Minecraft.getMinecraft().getRenderManager().playerViewX
: -Minecraft.getMinecraft().getRenderManager().playerViewX;
GlStateManager.rotate(180.0F - Minecraft.getMinecraft().getRenderManager().playerViewY, 0.0F, 1.0F, 0.0F);
GlStateManager.rotate(yaw, 1.0F, 0.0F, 0.0F);
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
float radius = 0.5f;
buffer.pos(-radius, radius, 0).tex(0, 0).endVertex();
buffer.pos( radius, radius, 0).tex(1, 0).endVertex();
buffer.pos( radius, -radius, 0).tex(1, 1).endVertex();
buffer.pos(-radius, -radius, 0).tex(0, 1).endVertex();
tessellator.draw();
// Reverses the colour addition change
GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);
GlStateManager.color(1, 1, 1, 1);
GlStateManager.popMatrix();
}
private void drawCube(EntityWitheringTotem entity, Tessellator tessellator, float partialTicks){
BufferBuilder buffer = tessellator.getBuffer();
GlStateManager.pushMatrix();
float age = entity.ticksExisted + partialTicks;
float rotationSpeed = 2;
GlStateManager.rotate(age * rotationSpeed/2, 0.0F, 1.0F, 0.0F);
GlStateManager.rotate(60.0F, 0.7071F, 0.0F, 0.7071F);
GlStateManager.rotate(age * rotationSpeed, 0.0F, 1.0F, 0.0F);
GlStateManager.scale(0.5, 0.5, 0.5);
GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
bindEntityTexture(entity);
Vec3d[] vertices = GeometryUtils.getVertices(entity.getEntityBoundingBox().offset(entity.getPositionVector()
.add(0, entity.height/2, 0).scale(-1)));
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
// Outside
drawFace(buffer, vertices[0], vertices[1], vertices[3], vertices[2], 0.5f, 0, 0.75f, 0.5f); // Bottom
drawFace(buffer, vertices[6], vertices[7], vertices[2], vertices[3], 0.75f, 0.5f, 1, 1); // South
drawFace(buffer, vertices[5], vertices[6], vertices[1], vertices[2], 0, 0.5f, 0.25f, 1); // East
drawFace(buffer, vertices[4], vertices[5], vertices[0], vertices[1], 0.25f, 0.5f, 0.5f, 1); // North
drawFace(buffer, vertices[7], vertices[4], vertices[3], vertices[0], 0.5f, 0.5f, 0.75f, 1); // West
drawFace(buffer, vertices[5], vertices[4], vertices[6], vertices[7], 0.25f, 0, 0.5f, 0.5f); // Top
tessellator.draw();
GlStateManager.popMatrix();
}
private static void drawFace(BufferBuilder buffer, Vec3d topLeft, Vec3d topRight, Vec3d bottomLeft, Vec3d bottomRight, float u1, float v1, float u2, float v2){
buffer.pos(topLeft.x, topLeft.y, topLeft.z).tex(u1, v1).endVertex();
buffer.pos(topRight.x, topRight.y, topRight.z).tex(u2, v1).endVertex();
buffer.pos(bottomRight.x, bottomRight.y, bottomRight.z).tex(u2, v2).endVertex();
buffer.pos(bottomLeft.x, bottomLeft.y, bottomLeft.z).tex(u1, v2).endVertex();
}
}
@@ -0,0 +1,175 @@
package electroblob.wizardry.entity.construct;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.spell.WitheringTotem;
import electroblob.wizardry.util.*;
import electroblob.wizardry.util.BlockUtils.SurfaceCriteria;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.ParticleBuilder.Type;
import io.netty.buffer.ByteBuf;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.init.MobEffects;
import net.minecraft.network.datasync.DataParameter;
import net.minecraft.network.datasync.DataSerializers;
import net.minecraft.network.datasync.EntityDataManager;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import java.util.Comparator;
import java.util.List;
public class EntityWitheringTotem extends EntityScaledConstruct {
private static final int PERIMETER_PARTICLE_DENSITY = 6;
private static final DataParameter<Float> HEALTH_DRAINED = EntityDataManager.createKey(EntityWitheringTotem.class, DataSerializers.FLOAT);
public EntityWitheringTotem(World world){
super(world);
this.setSize(1, 1); // This entity is different in that its area of effect is kind of 'outside' it
}
@Override
protected void entityInit(){
dataManager.register(HEALTH_DRAINED, 0f);
}
public float getHealthDrained(){
return dataManager.get(HEALTH_DRAINED);
}
public void addHealthDrained(float health){
dataManager.set(HEALTH_DRAINED, getHealthDrained() + health);
}
@Override
protected boolean shouldScaleWidth(){
return false;
}
@Override
protected boolean shouldScaleHeight(){
return false;
}
@Override
public void onUpdate(){
if(world.isRemote && this.ticksExisted == 1){
Wizardry.proxy.playMovingSound(this, WizardrySounds.ENTITY_WITHERING_TOTEM_AMBIENT, WizardrySounds.SPELLS, 1, 1, true);
}
super.onUpdate();
double radius = Spells.withering_totem.getProperty(Spell.EFFECT_RADIUS).floatValue() * sizeMultiplier;
if(world.isRemote){
ParticleBuilder.create(Type.DUST, rand, posX, posY + 0.2, posZ, 0.3, false)
.vel(0, -0.02 - world.rand.nextFloat() * 0.01, 0).clr(0xf575f5).fade(0x382366).spawn(world);
for(int i=0; i<PERIMETER_PARTICLE_DENSITY; i++){
float angle = ((float)Math.PI * 2)/PERIMETER_PARTICLE_DENSITY * (i + rand.nextFloat());
double x = posX + radius * MathHelper.sin(angle);
double z = posZ + radius * MathHelper.cos(angle);
Integer y = BlockUtils.getNearestSurface(world, new BlockPos(x, posY, z), EnumFacing.UP, 5, true, SurfaceCriteria.COLLIDABLE);
if(y != null){
ParticleBuilder.create(Type.DUST).pos(x, y, z).vel(0, 0.01, 0).clr(0xf575f5).fade(0x382366).spawn(world);
}
}
}
List<EntityLivingBase> nearby = EntityUtils.getLivingWithinRadius(radius, posX, posY, posZ, world);
nearby.removeIf(e -> !isValidTarget(e));
nearby.sort(Comparator.comparingDouble(e -> e.getDistanceSq(this)));
int targetsRemaining = Spells.withering_totem.getProperty(WitheringTotem.MAX_TARGETS).intValue()
+ (int)((damageMultiplier - 1) / Constants.POTENCY_INCREASE_PER_TIER);
while(!nearby.isEmpty() && targetsRemaining > 0){
EntityLivingBase target = nearby.remove(0);
if(EntityUtils.isLiving(target)){
if(target.ticksExisted % target.maxHurtResistantTime == 1){
float damage = Spells.withering_totem.getProperty(Spell.DAMAGE).floatValue();
if(EntityUtils.attackEntityWithoutKnockback(target, MagicDamage.causeIndirectMagicDamage(this,
getCaster(), DamageType.WITHER), damage)){
addHealthDrained(damage);
}
}
targetsRemaining--;
if(world.isRemote){
Vec3d centre = GeometryUtils.getCentre(this);
Vec3d pos = GeometryUtils.getCentre(target);
ParticleBuilder.create(Type.BEAM).pos(centre).target(target)
.clr(0.1f + 0.2f * world.rand.nextFloat(), 0, 0.3f).spawn(world);
for(int i = 0; i < 3; i++){
ParticleBuilder.create(Type.DUST, rand, pos.x, pos.y, pos.z, 0.3, false)
.vel(pos.subtract(centre).normalize().scale(-0.1)).clr(0x0c0024).fade(0x610017).spawn(world);
}
}
}
}
}
@Override
public void despawn(){
double radius = Spells.withering_totem.getProperty(Spell.EFFECT_RADIUS).floatValue() * sizeMultiplier;
List<EntityLivingBase> nearby = EntityUtils.getLivingWithinRadius(radius, posX, posY, posZ, world);
nearby.removeIf(e -> !isValidTarget(e));
float damage = Math.min(getHealthDrained() * 0.2f, Spells.withering_totem.getProperty(WitheringTotem.MAX_EXPLOSION_DAMAGE).floatValue());
for(EntityLivingBase target : nearby){
if(EntityUtils.attackEntityWithoutKnockback(target, MagicDamage.causeIndirectMagicDamage(this,
getCaster(), DamageType.MAGIC), damage)){
target.addPotionEffect(new PotionEffect(MobEffects.WITHER, Spells.withering_totem.getProperty(Spell.EFFECT_DURATION).intValue(),
Spells.withering_totem.getProperty(Spell.EFFECT_STRENGTH).intValue()));
}
}
if(world.isRemote) ParticleBuilder.create(Type.SPHERE).pos(GeometryUtils.getCentre(this)).scale((float)radius).clr(0xbe1a53)
.fade(0x210f4a).spawn(world);
this.playSound(WizardrySounds.ENTITY_WITHERING_TOTEM_EXPLODE, 1, 1);
super.despawn();
}
// Usually damage multipliers don't need syncing, but here we're using it for the non-standard purpose of targeting
@Override
public void writeSpawnData(ByteBuf data){
super.writeSpawnData(data);
data.writeFloat(damageMultiplier);
}
@Override
public void readSpawnData(ByteBuf data){
super.readSpawnData(data);
damageMultiplier = data.readFloat();
}
}
@@ -244,6 +244,7 @@ public final class Spells {
public static final Spell mark_sacrifice = placeholder();
public static final Spell stormcloud = placeholder();
public static final Spell withering_totem = placeholder();
public static final Spell fangs = placeholder();
public static final Spell radiant_totem = placeholder();
@@ -456,6 +457,7 @@ public final class Spells {
registry.register(new MarkSacrifice());
registry.register(new Stormcloud());
registry.register(new WitheringTotem());
registry.register(new Fangs());
registry.register(new RadiantTotem());
@@ -148,6 +148,7 @@ public class WizardryEntities {
registry.register(createEntry(EntityDecay.class, "decay", TrackingType.CONSTRUCT).build());
registry.register(createEntry(EntityZombieSpawner.class, "zombie_spawner", TrackingType.CONSTRUCT).build());
registry.register(createEntry(EntityRadiantTotem.class, "radiant_totem", TrackingType.CONSTRUCT).build());
registry.register(createEntry(EntityWitheringTotem.class, "withering_totem", 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());
@@ -73,6 +73,8 @@ public final class WizardrySounds {
public static final SoundEvent ENTITY_RADIANT_TOTEM_VANISH = createSound("entity.radiant_totem.vanish");
public static final SoundEvent ENTITY_SHIELD_DEFLECT = createSound("entity.shield.deflect");
public static final SoundEvent ENTITY_TORNADO_AMBIENT = createSound("entity.tornado.ambient");
public static final SoundEvent ENTITY_WITHERING_TOTEM_AMBIENT = createSound("entity.withering_totem.ambient");
public static final SoundEvent ENTITY_WITHERING_TOTEM_EXPLODE = createSound("entity.withering_totem.explode");
public static final SoundEvent ENTITY_ZOMBIE_SPAWNER_SPAWN = createSound("entity.zombie_spawner.spawn");
public static final SoundEvent ENTITY_EVIL_WIZARD_AMBIENT = createSound("entity.evil_wizard.ambient");
@@ -0,0 +1,25 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.entity.construct.EntityWitheringTotem;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.util.EnumFacing;
import javax.annotation.Nullable;
public class WitheringTotem extends SpellConstructRanged<EntityWitheringTotem> {
public static final String MAX_TARGETS = "max_targets";
public static final String MAX_EXPLOSION_DAMAGE = "max_explosion_damage";
public WitheringTotem(){
super("withering_totem", EntityWitheringTotem::new, false);
this.addProperties(EFFECT_RADIUS, MAX_TARGETS, DAMAGE, MAX_EXPLOSION_DAMAGE, EFFECT_DURATION, EFFECT_STRENGTH);
this.floor(false);
}
@Override
protected void addConstructExtras(EntityWitheringTotem construct, EnumFacing side, @Nullable EntityLivingBase caster, SpellModifiers modifiers){
construct.posY += 1.2;
}
}
@@ -629,6 +629,7 @@ entity.ebwizardry\:stormcloud.name=Stormcloud
entity.ebwizardry\:zombie_spawner.name=Zombie Spawner
entity.ebwizardry\:boulder.name=Boulder
entity.ebwizardry\:radiant_totem.name=Radiant Totem
entity.ebwizardry\:withering_totem.name=Withering Totem
itemGroup.ebwizardry=Wizardry
itemGroup.ebwizardryspells=Spells
@@ -938,6 +939,7 @@ spell.ebwizardry\:water_breathing=Water Breathing
spell.ebwizardry\:whirlwind=Whirlwind
spell.ebwizardry\:wither=Wither
spell.ebwizardry\:wither_skull=Wither Skull
spell.ebwizardry\:withering_totem=Withering Totem
spell.ebwizardry\:zombie_apocalypse=Zombie Apocalypse
spell.ebwizardry\:agility.desc=Grants the caster faster movement speed and greater jump height for 30 seconds.
@@ -1120,6 +1122,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\:withering_totem.desc=Summons a withering totem where you are pointing, which slowly drains health from nearby creatures. After 30 seconds, the totem explodes, releasing a wave of withering energy that deals more damage the more health that was drained.
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...
@@ -629,6 +629,7 @@ entity.ebwizardry\:stormcloud.name=Stormcloud
entity.ebwizardry\:zombie_spawner.name=Zombie Spawner
entity.ebwizardry\:boulder.name=Boulder
entity.ebwizardry\:radiant_totem.name=Radiant Totem
entity.ebwizardry\:withering_totem.name=Withering Totem
itemGroup.ebwizardry=Wizardry
itemGroup.ebwizardryspells=Spells
@@ -938,6 +939,7 @@ spell.ebwizardry\:water_breathing=Water Breathing
spell.ebwizardry\:whirlwind=Whirlwind
spell.ebwizardry\:wither=Wither
spell.ebwizardry\:wither_skull=Wither Skull
spell.ebwizardry\:withering_totem=Withering Totem
spell.ebwizardry\:zombie_apocalypse=Zombie Apocalypse
spell.ebwizardry\:agility.desc=Grants the caster faster movement speed and greater jump height for 30 seconds.
@@ -1120,6 +1122,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\:withering_totem.desc=Summons a withering totem where you are pointing, which slowly drains health from nearby creatures. After 30 seconds, the totem explodes, releasing a wave of withering energy that deals more damage the more health that was drained.
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...
@@ -46,6 +46,8 @@
"entity.stormcloud.thunder": {"category": "spells", "sounds": ["ambient/weather/thunder1", "ambient/weather/thunder2", "ambient/weather/thunder3"]},
"entity.stormcloud.attack": {"category": "spells", "sounds": ["ebwizardry:arc1", "ebwizardry:arc2", "ebwizardry:arc3"]},
"entity.tornado.ambient": {"category": "spells", "sounds": ["ebwizardry:wind"]},
"entity.withering_totem.ambient": {"category": "spells", "sounds": ["ebwizardry:dark_aura_loop"]},
"entity.withering_totem.explode": {"category": "spells", "sounds": ["mob/wither/death"]},
"entity.zombie_spawner.spawn": {"category": "spells", "sounds": ["mob/guardian/elder_death"]},
"entity.evil_wizard.ambient": {"category": "hostile", "sounds": ["mob/evocation_illager/idle1", "mob/evocation_illager/idle2", "mob/evocation_illager/idle3", "mob/evocation_illager/idle4"]},
@@ -341,6 +343,7 @@
"spell.whirlwind": {"category": "spells", "sounds": ["ebwizardry:ice"]},
"spell.wither": {"category": "spells", "sounds": ["mob/wither/hurt1", "mob/wither/hurt2", "mob/wither/hurt3", "mob/wither/hurt4"]},
"spell.wither_skull": {"category": "spells", "sounds": ["mob/wither/shoot"]},
"spell.withering_totem": {"category": "spells", "sounds": ["ebwizardry:beam_start"]},
"spell.zombie_apocalypse.start": {"category": "spells", "sounds": ["ebwizardry:dark_aura_start"]},
"spell.zombie_apocalypse.loop": {"category": "spells", "sounds": ["ebwizardry:dark_aura_loop"]},
"spell.zombie_apocalypse.end": {"category": "spells", "sounds": ["ebwizardry:dark_aura_end"]},
@@ -0,0 +1,29 @@
{
"enabled": {
"book": true,
"scroll": true,
"wands": true,
"npcs": true,
"dispensers": true,
"commands": true,
"treasure": true,
"trades": true,
"looting": true
},
"tier": "advanced",
"element": "necromancy",
"type": "construct",
"cost": 50,
"chargeup": 20,
"cooldown": 200,
"base_properties": {
"range": 8,
"duration": 600,
"effect_radius": 6,
"max_targets": 1,
"damage": 1,
"max_explosion_damage": 10,
"effect_duration": 120,
"effect_strength": 0
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB