From 3343385a7c7a147a5bf3dff5f8a16a5355600c5a Mon Sep 17 00:00:00 2001 From: Electroblob77 <35599699+Electroblob77@users.noreply.github.com> Date: Sun, 19 Jul 2020 15:56:07 +0100 Subject: [PATCH] Add donor perks and tweak generalise rendering of healing totem --- .../java/electroblob/wizardry/Settings.java | 17 ++ .../java/electroblob/wizardry/Wizardry.java | 8 +- .../wizardry/WizardryEventHandler.java | 2 + .../renderer/effect/RenderDonationPerks.java | 190 ++++++++++++++++++ .../renderer/entity/RenderRadiantTotem.java | 10 +- .../wizardry/constants/Element.java | 14 ++ .../wizardry/misc/DonationPerksHandler.java | 102 ++++++++++ .../packet/PacketRequestDonationPerks.java | 58 ++++++ .../packet/PacketSyncDonationPerks.java | 55 +++++ .../packet/WizardryPacketHandler.java | 2 + .../java/electroblob/wizardry/util/Box.java | 33 +++ .../assets/ebwizardry/lang/en_us.lang | 2 + .../textures/entity/radiant_totem/flare.png | Bin 4875 -> 0 bytes .../{radiant_totem => totem}/cube_0.png | Bin .../{radiant_totem => totem}/cube_1.png | Bin .../{radiant_totem => totem}/cube_10.png | Bin .../{radiant_totem => totem}/cube_11.png | Bin .../{radiant_totem => totem}/cube_12.png | Bin .../{radiant_totem => totem}/cube_13.png | Bin .../{radiant_totem => totem}/cube_2.png | Bin .../{radiant_totem => totem}/cube_3.png | Bin .../{radiant_totem => totem}/cube_4.png | Bin .../{radiant_totem => totem}/cube_5.png | Bin .../{radiant_totem => totem}/cube_6.png | Bin .../{radiant_totem => totem}/cube_7.png | Bin .../{radiant_totem => totem}/cube_8.png | Bin .../{radiant_totem => totem}/cube_9.png | Bin .../textures/entity/totem/flare.png | Bin 0 -> 4951 bytes 28 files changed, 490 insertions(+), 3 deletions(-) create mode 100644 src/main/java/electroblob/wizardry/client/renderer/effect/RenderDonationPerks.java create mode 100644 src/main/java/electroblob/wizardry/misc/DonationPerksHandler.java create mode 100644 src/main/java/electroblob/wizardry/packet/PacketRequestDonationPerks.java create mode 100644 src/main/java/electroblob/wizardry/packet/PacketSyncDonationPerks.java create mode 100644 src/main/java/electroblob/wizardry/util/Box.java delete mode 100644 src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/flare.png rename src/main/resources/assets/ebwizardry/textures/entity/{radiant_totem => totem}/cube_0.png (100%) rename src/main/resources/assets/ebwizardry/textures/entity/{radiant_totem => totem}/cube_1.png (100%) rename src/main/resources/assets/ebwizardry/textures/entity/{radiant_totem => totem}/cube_10.png (100%) rename src/main/resources/assets/ebwizardry/textures/entity/{radiant_totem => totem}/cube_11.png (100%) rename src/main/resources/assets/ebwizardry/textures/entity/{radiant_totem => totem}/cube_12.png (100%) rename src/main/resources/assets/ebwizardry/textures/entity/{radiant_totem => totem}/cube_13.png (100%) rename src/main/resources/assets/ebwizardry/textures/entity/{radiant_totem => totem}/cube_2.png (100%) rename src/main/resources/assets/ebwizardry/textures/entity/{radiant_totem => totem}/cube_3.png (100%) rename src/main/resources/assets/ebwizardry/textures/entity/{radiant_totem => totem}/cube_4.png (100%) rename src/main/resources/assets/ebwizardry/textures/entity/{radiant_totem => totem}/cube_5.png (100%) rename src/main/resources/assets/ebwizardry/textures/entity/{radiant_totem => totem}/cube_6.png (100%) rename src/main/resources/assets/ebwizardry/textures/entity/{radiant_totem => totem}/cube_7.png (100%) rename src/main/resources/assets/ebwizardry/textures/entity/{radiant_totem => totem}/cube_8.png (100%) rename src/main/resources/assets/ebwizardry/textures/entity/{radiant_totem => totem}/cube_9.png (100%) create mode 100644 src/main/resources/assets/ebwizardry/textures/entity/totem/flare.png diff --git a/src/main/java/electroblob/wizardry/Settings.java b/src/main/java/electroblob/wizardry/Settings.java index 8827e6e4..229d3188 100644 --- a/src/main/java/electroblob/wizardry/Settings.java +++ b/src/main/java/electroblob/wizardry/Settings.java @@ -1,5 +1,6 @@ package electroblob.wizardry; +import electroblob.wizardry.constants.Element; import electroblob.wizardry.item.ItemArtefact; import electroblob.wizardry.packet.PacketSyncSettings; import electroblob.wizardry.packet.WizardryPacketHandler; @@ -25,6 +26,7 @@ import org.apache.commons.lang3.tuple.Pair; import java.io.File; import java.util.*; import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * Singleton class which deals with everything related to wizardry's config file. To access individual settings, use @@ -326,6 +328,14 @@ public final class Settings { /** [Synchronised] List of registry names of items that can be placed in a bookshelf. */ public Pair[] bookItems = parseItemMetaStrings(); + /** + * [Synchronised*] The element to render for donation perks, or null if it should not be rendered. + *

+ * * Unlike the other options, this is synchronised client -> server, then the data for all players is sent + * back to every client when they join the game, or whenever it changes. + */ + public Element donationPerkElement = Element.MAGIC; + // Client-only settings. These settings only affect client-side code and hence are not synced. Each client obeys // its own values for these, and changing them on a dedicated server will have no effect. @@ -1084,6 +1094,13 @@ public final class Settings { spellcastingAnimations = property.getBoolean(); propOrder.add(property.getName()); + List elementNames = Arrays.stream(Element.values()).map(Element::getName).collect(Collectors.toList()); + elementNames.add(""); + property = config.get(CLIENT_CATEGORY, "donationPerkElement", Element.MAGIC.getName(), "The element colour to use for the flying companion orb rendered on donor players, leave empty to disable the effect. If you're someone who was kind enough to donate, this setting will change what all players see (if not, it won't do anything).", elementNames.toArray(new String[0])); + property.setLanguageKey("config." + Wizardry.MODID + ".donation_perk_element"); + donationPerkElement = Element.fromName(property.getString(), null); // Fallback to null for no element + propOrder.add(property.getName()); + config.setCategoryPropertyOrder(CLIENT_CATEGORY, propOrder); } diff --git a/src/main/java/electroblob/wizardry/Wizardry.java b/src/main/java/electroblob/wizardry/Wizardry.java index 88066295..b0ec2966 100644 --- a/src/main/java/electroblob/wizardry/Wizardry.java +++ b/src/main/java/electroblob/wizardry/Wizardry.java @@ -10,6 +10,7 @@ import electroblob.wizardry.data.WizardData; import electroblob.wizardry.integration.antiqueatlas.WizardryAntiqueAtlasIntegration; import electroblob.wizardry.integration.baubles.WizardryBaublesIntegration; import electroblob.wizardry.inventory.ContainerBookshelf; +import electroblob.wizardry.misc.DonationPerksHandler; import electroblob.wizardry.misc.Forfeit; import electroblob.wizardry.packet.WizardryPacketHandler; import electroblob.wizardry.registry.*; @@ -197,7 +198,12 @@ public class Wizardry { public void onConfigChanged(net.minecraftforge.fml.client.event.ConfigChangedEvent.OnConfigChangedEvent event){ if(event.getModID().equals(Wizardry.MODID)){ settings.saveConfigChanges(); - // All of the synchronised settings require a world restart anyway so this doesn't need syncing. + // All of the synchronised settings require a world restart anyway so don't need syncing, except for the + // donor perks which have special behaviour (since we have to update everyone when a donor logs in anyway, + // we might as well reuse the packet and let them change the setting mid-game) + if(event.isWorldRunning() && DonationPerksHandler.isDonor(proxy.getThePlayer())){ + DonationPerksHandler.sendToServer(proxy.getThePlayer()); + } } } diff --git a/src/main/java/electroblob/wizardry/WizardryEventHandler.java b/src/main/java/electroblob/wizardry/WizardryEventHandler.java index e0754c16..af34ae59 100644 --- a/src/main/java/electroblob/wizardry/WizardryEventHandler.java +++ b/src/main/java/electroblob/wizardry/WizardryEventHandler.java @@ -79,11 +79,13 @@ public final class WizardryEventHandler { public static void onPlayerLoggedInEvent(PlayerLoggedInEvent event){ // When a player logs in, they are sent the glyph data, server settings and spell properties. if(event.player instanceof EntityPlayerMP){ + // TODO: Move these to handler classes where possible SpellGlyphData.get(event.player.world).sync((EntityPlayerMP)event.player); SpellEmitterData.get(event.player.world).sync((EntityPlayerMP)event.player); Wizardry.settings.sync((EntityPlayerMP)event.player); syncAdvancements((EntityPlayerMP)event.player, false); } + Spell.syncProperties(event.player); } diff --git a/src/main/java/electroblob/wizardry/client/renderer/effect/RenderDonationPerks.java b/src/main/java/electroblob/wizardry/client/renderer/effect/RenderDonationPerks.java new file mode 100644 index 00000000..c4d8342b --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/effect/RenderDonationPerks.java @@ -0,0 +1,190 @@ +package electroblob.wizardry.client.renderer.effect; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.block.BlockReceptacle; +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.misc.DonationPerksHandler; +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.vertex.DefaultVertexFormats; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import net.minecraftforge.client.event.RenderPlayerEvent; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import org.lwjgl.opengl.GL11; + +@EventBusSubscriber +public class RenderDonationPerks { + + private static final ResourceLocation FLARE_TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/entity/totem/flare.png"); + private static final ResourceLocation[] CUBE_TEXTURES = new ResourceLocation[14]; + + private static final float BOBBING_PERIOD = 25; + private static final float ROTATION_PERIOD = 60; + private static final double ROTATION_RADIUS = 0.7; + private static final double HEIGHT_FRACTION = 0.8; + private static final double BOBBING_DISTANCE = 0.2; + private static final double FOLLOW_DISTANCE = 1.4; + + static { + for(int i = 0; i< CUBE_TEXTURES.length; i++) CUBE_TEXTURES[i] = new ResourceLocation(Wizardry.MODID, "textures/entity/totem/cube_" + i + ".png"); + } + + @SubscribeEvent + public static void onRenderPlayerEvent(RenderPlayerEvent.Post event){ + + EntityPlayer player = event.getEntityPlayer(); + float partialTicks = event.getPartialRenderTick(); + + Element element = DonationPerksHandler.getElement(player); + + if(element != null){ + + int c1 = BlockReceptacle.PARTICLE_COLOURS.get(element)[0]; + int r1 = (c1 & 0xFF0000) >> 16; + int g1 = (c1 & 0xFF00) >> 8; + int b1 = (c1 & 0xFF); + + int c2 = BlockReceptacle.PARTICLE_COLOURS.get(element)[1]; + int r2 = (c2 & 0xFF0000) >> 16; + int g2 = (c2 & 0xFF00) >> 8; + int b2 = (c2 & 0xFF); + + Tessellator tessellator = Tessellator.getInstance(); + + GlStateManager.pushMatrix(); + + GlStateManager.enableBlend(); + GlStateManager.disableLighting(); + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240, 240); + GlStateManager.depthMask(false); + GlStateManager.disableCull(); + + double x1 = player.chasingPosX - player.posX; + double y1 = player.chasingPosY - player.posY; + double z1 = player.chasingPosZ - player.posZ; + + double x2 = player.prevChasingPosX - player.prevPosX; + double y2 = player.prevChasingPosY - player.prevPosY; + double z2 = player.prevChasingPosZ - player.prevPosZ; + + double dx = x2 + (x1 - x2) * partialTicks; + double dy = y2 + (y1 - y2) * partialTicks; + double dz = z2 + (z1 - z2) * partialTicks; + + float t = player.ticksExisted + partialTicks; + float hMoveFraction = MathHelper.clamp((float)(dx * dx + dz * dz) / 0.3f, 0, 1); + float vMoveFraction = MathHelper.clamp((float)(dy * dy) / 0.3f, 0, 1); + + double x = event.getX() + ROTATION_RADIUS * MathHelper.sin(t / ROTATION_PERIOD) * (1 - hMoveFraction) + FOLLOW_DISTANCE * dx; + double y = event.getY() + HEIGHT_FRACTION * player.height + BOBBING_DISTANCE * MathHelper.sin(t / BOBBING_PERIOD) * (1 - vMoveFraction) + FOLLOW_DISTANCE * dy; + double z = event.getZ() + ROTATION_RADIUS * MathHelper.cos(t / ROTATION_PERIOD) * (1 - hMoveFraction) + FOLLOW_DISTANCE * dz; + GlStateManager.translate(x, y, z); + + GlStateManager.scale(0.4, 0.4, 0.4); + + // 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(r1/255f, g1/255f, b1/255f); + drawFlare(tessellator); + + // Reverses the colour addition change + GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE); + GlStateManager.color(r2/255f, g2/255f, b2/255f); + drawCube(player, tessellator, partialTicks); + + GlStateManager.color(1, 1, 1,1); + GlStateManager.disableBlend(); + GlStateManager.enableLighting(); + GlStateManager.depthMask(true); + GlStateManager.enableCull(); + + GlStateManager.popMatrix(); + } + + } + + private static void drawFlare(Tessellator tessellator){ + + BufferBuilder buffer = tessellator.getBuffer(); + + GlStateManager.pushMatrix(); + + GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + + Minecraft.getMinecraft().renderEngine.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 r = 0.5f; + + buffer.pos(-r, r, 0).tex(0, 0).endVertex(); + buffer.pos( r, r, 0).tex(1, 0).endVertex(); + buffer.pos( r, -r, 0).tex(1, 1).endVertex(); + buffer.pos(-r, -r, 0).tex(0, 1).endVertex(); + + tessellator.draw(); + + GlStateManager.popMatrix(); + } + + private static void drawCube(EntityPlayer player, Tessellator tessellator, float partialTicks){ + + BufferBuilder buffer = tessellator.getBuffer(); + + GlStateManager.pushMatrix(); + + float age = player.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); + + Minecraft.getMinecraft().renderEngine.bindTexture(CUBE_TEXTURES[player.ticksExisted % CUBE_TEXTURES.length]); + + Vec3d[] vertices = GeometryUtils.getVertices(new AxisAlignedBB(-0.5, -0.5, -0.5, 0.5, 0.5, 0.5)); + + 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(); + } + +} diff --git a/src/main/java/electroblob/wizardry/client/renderer/entity/RenderRadiantTotem.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderRadiantTotem.java index 7a87c26f..fee417cb 100644 --- a/src/main/java/electroblob/wizardry/client/renderer/entity/RenderRadiantTotem.java +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderRadiantTotem.java @@ -20,11 +20,11 @@ import javax.annotation.Nullable; public class RenderRadiantTotem extends Render { - private static final ResourceLocation FLARE_TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/entity/radiant_totem/flare.png"); + 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/radiant_totem/cube_" + i + ".png"); + for(int i = 0; i< CUBE_TEXTURES.length; i++) CUBE_TEXTURES[i] = new ResourceLocation(Wizardry.MODID, "textures/entity/totem/cube_" + i + ".png"); } public RenderRadiantTotem(RenderManager manager){ @@ -72,6 +72,9 @@ public class RenderRadiantTotem extends Render { GlStateManager.pushMatrix(); + // 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(1, 0.957f, 0.608f); GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); bindTexture(FLARE_TEXTURE); @@ -95,6 +98,9 @@ public class RenderRadiantTotem extends Render { 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(); } diff --git a/src/main/java/electroblob/wizardry/constants/Element.java b/src/main/java/electroblob/wizardry/constants/Element.java index 249da655..45658f06 100644 --- a/src/main/java/electroblob/wizardry/constants/Element.java +++ b/src/main/java/electroblob/wizardry/constants/Element.java @@ -8,6 +8,8 @@ import net.minecraft.util.text.Style; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.util.text.TextFormatting; +import javax.annotation.Nullable; + public enum Element implements IStringSerializable { /** The 'default' element, with {@link electroblob.wizardry.registry.Spells#magic_missile magic missile} being its @@ -49,6 +51,18 @@ public enum Element implements IStringSerializable { throw new IllegalArgumentException("No such element with unlocalised name: " + name); } + /** Same as {@link Element#fromName(String)}, but returns the given fallback instead of throwing an exception if no + * element matches the given string. */ + @Nullable + public static Element fromName(String name, @Nullable Element fallback){ + + for(Element element : values()){ + if(element.unlocalisedName.equals(name)) return element; + } + + return fallback; + } + /** Returns the translated display name of this element, without formatting. */ public String getDisplayName(){ return Wizardry.proxy.translate("element." + getName()); diff --git a/src/main/java/electroblob/wizardry/misc/DonationPerksHandler.java b/src/main/java/electroblob/wizardry/misc/DonationPerksHandler.java new file mode 100644 index 00000000..2279ba51 --- /dev/null +++ b/src/main/java/electroblob/wizardry/misc/DonationPerksHandler.java @@ -0,0 +1,102 @@ +package electroblob.wizardry.misc; + +import com.google.common.collect.ImmutableMap; +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.packet.PacketRequestDonationPerks; +import electroblob.wizardry.packet.PacketSyncDonationPerks; +import electroblob.wizardry.packet.WizardryPacketHandler; +import electroblob.wizardry.util.Box; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerLoggedInEvent; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; + +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +/** Class responsible for storing and handling the donation perk settings for all eligible connected players */ +// We probably could have used WizardData for this, but since this is config-based and only for rendering it makes more +// sense to handle it separately, especially given that the vast majority of players aren't donors +@Mod.EventBusSubscriber +public class DonationPerksHandler { + + // Thanks to everyone who has donated their time and/or money! <3 (These are your UUIDs - don't worry, they're not + // sensitive info, they're just player identifiers that won't ever change, even if you change your username) + // Convert between usernames and UUIDs here https://mcuuid.net/ + // Normally I'd use an EnumMap for this, but somehow making the UUIDs into an enum doesn't seem right + private static final Map> DONOR_UUID_MAP = ImmutableMap.of( + UUID.fromString("4b29263e-007b-48ef-b3e6-ce86cca989e9"), new Box<>(Element.MAGIC), // Me! + UUID.fromString("2b583703-1407-4ec6-93d2-d9c03b0c08fc"), new Box<>(Element.MAGIC), + UUID.fromString("f6c7001f-4ce8-43f7-b4a1-a9d9d5fadb93"), new Box<>(Element.MAGIC), + UUID.fromString("01535a73-ff8d-4d6c-851e-c71f89e936aa"), new Box<>(Element.MAGIC) + ); + + /** Returns true if the given player's UUID appears in the list of donor UUIDs, false otherwise (thread-safe). */ + public static boolean isDonor(EntityPlayer player){ + return DONOR_UUID_MAP.containsKey(player.getUniqueID()); + } + + /** Sets each donor's perk element in order according to the given list. The given list will be modified. */ + public static void setElements(List elements){ + if(elements.size() < DONOR_UUID_MAP.size()){ + Wizardry.logger.warn("Received fewer perk settings than the number of donors, cannot sync!"); + return; + } + DONOR_UUID_MAP.forEach((k, v) -> v.set(elements.remove(0))); + if(!elements.isEmpty()) Wizardry.logger.warn("Received more perk settings than the number of donors, something must have gone wrong!"); + } + + /** Sets the donor perk to the given element for the given player and sends a packet to all clients if it changed. */ + public static void setElement(EntityPlayerMP player, Element element){ + Box box = DONOR_UUID_MAP.get(player.getUniqueID()); + if(box != null && box.get() != element){ + box.set(element); + syncAll(); + } + } + + /** Returns the given player's donor perk element, or null if they are not a donor (may also be null for donors if + * they have disabled the perk). */ + public static Element getElement(EntityPlayer player){ + Box box = DONOR_UUID_MAP.get(player.getUniqueID()); + return box == null ? null : box.get(); + } + + /** Sends the current donor perk information to all connected players (server-side only). */ + private static void syncAll(){ + Wizardry.logger.info("Sending global donation perk settings to all players"); + PacketSyncDonationPerks.Message packet = new PacketSyncDonationPerks.Message(DONOR_UUID_MAP.values().stream().map(Box::get).collect(Collectors.toList())); + WizardryPacketHandler.net.sendToAll(packet); + } + + /** Sends the current donor perk information to the given player (server-side only). */ + private static void syncWith(EntityPlayerMP player){ + Wizardry.logger.info("Sending global donation perk settings to {}", player.getName()); + PacketSyncDonationPerks.Message packet = new PacketSyncDonationPerks.Message(DONOR_UUID_MAP.values().stream().map(Box::get).collect(Collectors.toList())); + WizardryPacketHandler.net.sendTo(packet, player); + } + + /** Sends the client player's donor perk setting to the server (client-side only) */ + public static void sendToServer(EntityPlayer player){ + Wizardry.logger.info("Sending donation perk settings for {} to server", player.getName()); + IMessage message = new PacketRequestDonationPerks.Message(Wizardry.settings.donationPerkElement); + WizardryPacketHandler.net.sendToServer(message); + } + + @SubscribeEvent + public static void onPlayerLoggedInEvent(PlayerLoggedInEvent event){ + if(event.player instanceof EntityPlayerMP){ + // Send donation perks to anyone who logs in + syncWith((EntityPlayerMP)event.player); + }else{ + // Send donation perks setting to the server if the player is a donor + if(isDonor(event.player)) sendToServer(event.player); + } + } + +} diff --git a/src/main/java/electroblob/wizardry/packet/PacketRequestDonationPerks.java b/src/main/java/electroblob/wizardry/packet/PacketRequestDonationPerks.java new file mode 100644 index 00000000..f108d776 --- /dev/null +++ b/src/main/java/electroblob/wizardry/packet/PacketRequestDonationPerks.java @@ -0,0 +1,58 @@ +package electroblob.wizardry.packet; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.misc.DonationPerksHandler; +import io.netty.buffer.ByteBuf; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +/** [Client -> Server] This packet is sent when a player logs in and whenever the setting changes to set their + * donation perk element. */ +public class PacketRequestDonationPerks implements IMessageHandler { + + @Override + public IMessage onMessage(Message message, MessageContext ctx){ + + // Just to make sure that the side is correct + if(ctx.side.isServer()){ + + final EntityPlayerMP player = ctx.getServerHandler().player; + + // The UUID key set itself is immutable so we can safely access it from the networking thread + if(DonationPerksHandler.isDonor(player)){ + player.getServerWorld().addScheduledTask(() -> DonationPerksHandler.setElement(player, message.element)); + }else{ + Wizardry.logger.warn("Received a donation perk packet from a player that isn't a donor!"); + } + + } + + return null; + } + + public static class Message implements IMessage { + + private Element element; + + // This constructor is required otherwise you'll get errors (used somewhere in fml through reflection) + public Message(){} + + public Message(Element element){ + this.element = element; + } + + @Override + public void fromBytes(ByteBuf buf){ + // The order is important + element = Element.values()[buf.readShort()]; + } + + @Override + public void toBytes(ByteBuf buf){ + buf.writeShort(element.ordinal()); + } + } +} diff --git a/src/main/java/electroblob/wizardry/packet/PacketSyncDonationPerks.java b/src/main/java/electroblob/wizardry/packet/PacketSyncDonationPerks.java new file mode 100644 index 00000000..403e65b5 --- /dev/null +++ b/src/main/java/electroblob/wizardry/packet/PacketSyncDonationPerks.java @@ -0,0 +1,55 @@ +package electroblob.wizardry.packet; + +import electroblob.wizardry.constants.Element; +import electroblob.wizardry.misc.DonationPerksHandler; +import io.netty.buffer.ByteBuf; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; + +import java.util.ArrayList; +import java.util.List; + +/** + * [Server -> Client] This packet is sent to update the donation perks map for each player that logs in, and for + * all players whenever the map changes. + */ +public class PacketSyncDonationPerks implements IMessageHandler { + + @Override + public IMessage onMessage(Message message, MessageContext ctx){ + + // Just to make sure that the side is correct + if(ctx.side.isClient()){ + // Using a fully qualified name is a good course of action here; we don't really want to clutter the proxy + // methods any more than necessary. + net.minecraft.client.Minecraft.getMinecraft().addScheduledTask(() -> DonationPerksHandler.setElements(message.elements)); + } + + return null; + } + + public static class Message implements IMessage { + + public List elements; + + // This constructor is required otherwise you'll get errors (used somewhere in fml through reflection) + public Message(){} + + public Message(List elements){ + this.elements = elements; + } + + @Override + public void fromBytes(ByteBuf buf){ + // The order is important + this.elements = new ArrayList<>(); + while(buf.isReadable()) elements.add(Element.values()[buf.readShort()]); + } + + @Override + public void toBytes(ByteBuf buf){ + for(Element element : elements) buf.writeShort(element.ordinal()); + } + } +} diff --git a/src/main/java/electroblob/wizardry/packet/WizardryPacketHandler.java b/src/main/java/electroblob/wizardry/packet/WizardryPacketHandler.java index 7d7133c2..2959b5eb 100644 --- a/src/main/java/electroblob/wizardry/packet/WizardryPacketHandler.java +++ b/src/main/java/electroblob/wizardry/packet/WizardryPacketHandler.java @@ -33,6 +33,8 @@ public class WizardryPacketHandler { registerMessage(PacketConquerShrine.class, PacketConquerShrine.Message.class); registerMessage(PacketLectern.class, PacketLectern.Message.class); registerMessage(PacketSpellQuickAccess.class, PacketSpellQuickAccess.Message.class); + registerMessage(PacketRequestDonationPerks.class, PacketRequestDonationPerks.Message.class); + registerMessage(PacketSyncDonationPerks.class, PacketSyncDonationPerks.Message.class); } private static int nextPacketId = 0; diff --git a/src/main/java/electroblob/wizardry/util/Box.java b/src/main/java/electroblob/wizardry/util/Box.java new file mode 100644 index 00000000..291b0967 --- /dev/null +++ b/src/main/java/electroblob/wizardry/util/Box.java @@ -0,0 +1,33 @@ +package electroblob.wizardry.util; + +/** + * A very simple container for a single object of a given type {@code T} (referred to as the contents). Allows + * fixed-size collections to be made using their immutable (or unmodifiable) counterparts. This is particularly useful + * for maps with a fixed set of keys but whose values may be changed. + */ +public class Box { + + private T contents; + + public Box(T contents){ + this.contents = contents; + } + + /** Returns the contents of this box. */ + public T get(){ + return contents; + } + + /** Sets the contents of this box to the given object. */ + public void set(T contents){ + this.contents = contents; + } + + // Probably misleading since methods like this generally imply immutability, which is exactly what we're not doing + +// /** Creates a new box with the given contents. This is just a static factory method that calls the constructor. */ +// public static Box of(T contents){ +// return new Box<>(contents); +// } + +} diff --git a/src/main/resources/assets/ebwizardry/lang/en_us.lang b/src/main/resources/assets/ebwizardry/lang/en_us.lang index 22b419d7..ea026fe2 100644 --- a/src/main/resources/assets/ebwizardry/lang/en_us.lang +++ b/src/main/resources/assets/ebwizardry/lang/en_us.lang @@ -1505,6 +1505,8 @@ config.ebwizardry.blink_effect=Blink Effect config.ebwizardry.blink_effect.tooltip=Whether to use the screen blink effect for teleportation spells. config.ebwizardry.spellcasting_animations=Spellcasting Animations config.ebwizardry.spellcasting_animations.tooltip=Whether to use custom animations for players casting spells. Disable this option if it conflicts with other mods that add player animations. +config.ebwizardry.spellcasting_animations=Spellcasting Animations +config.ebwizardry.spellcasting_animations.tooltip=Whether to use custom animations for players casting spells. Disable this option if it conflicts with other mods that add player animations. config.ebwizardry.category.commands=Command Settings config.ebwizardry.category.commands.tooltip=Configure wizardry's commands diff --git a/src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/flare.png b/src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/flare.png deleted file mode 100644 index c3c954504e2df15b35af20a24f4b840fcfdf30c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4875 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 diff --git a/src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_0.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_0.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_0.png rename to src/main/resources/assets/ebwizardry/textures/entity/totem/cube_0.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_1.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_1.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_1.png rename to src/main/resources/assets/ebwizardry/textures/entity/totem/cube_1.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_10.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_10.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_10.png rename to src/main/resources/assets/ebwizardry/textures/entity/totem/cube_10.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_11.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_11.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_11.png rename to src/main/resources/assets/ebwizardry/textures/entity/totem/cube_11.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_12.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_12.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_12.png rename to src/main/resources/assets/ebwizardry/textures/entity/totem/cube_12.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_13.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_13.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_13.png rename to src/main/resources/assets/ebwizardry/textures/entity/totem/cube_13.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_2.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_2.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_2.png rename to src/main/resources/assets/ebwizardry/textures/entity/totem/cube_2.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_3.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_3.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_3.png rename to src/main/resources/assets/ebwizardry/textures/entity/totem/cube_3.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_4.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_4.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_4.png rename to src/main/resources/assets/ebwizardry/textures/entity/totem/cube_4.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_5.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_5.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_5.png rename to src/main/resources/assets/ebwizardry/textures/entity/totem/cube_5.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_6.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_6.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_6.png rename to src/main/resources/assets/ebwizardry/textures/entity/totem/cube_6.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_7.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_7.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_7.png rename to src/main/resources/assets/ebwizardry/textures/entity/totem/cube_7.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_8.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_8.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_8.png rename to src/main/resources/assets/ebwizardry/textures/entity/totem/cube_8.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_9.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/cube_9.png similarity index 100% rename from src/main/resources/assets/ebwizardry/textures/entity/radiant_totem/cube_9.png rename to src/main/resources/assets/ebwizardry/textures/entity/totem/cube_9.png diff --git a/src/main/resources/assets/ebwizardry/textures/entity/totem/flare.png b/src/main/resources/assets/ebwizardry/textures/entity/totem/flare.png new file mode 100644 index 0000000000000000000000000000000000000000..991dc12c9dc0f752a6171ef13c30c693377936d9 GIT binary patch literal 4951 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 BeV+gT literal 0 HcmV?d00001