diff --git a/gradle.properties b/gradle.properties index 861338a..3a3daea 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,16 +16,16 @@ show_testing_output = false # Mod Information # HIGHLY RECOMMEND complying with SemVer for mod_version: https://semver.org/ mod_version = 1.0.0 -root_package = com.example -mod_id = modid -mod_name = Mod Name +root_package = ru.octol1ttle +mod_id = knockdowns +mod_name = Knockdowns (Legacy) # Mod Metadata (Optional) mod_description = mod_url = mod_update_json = # Delimit authors with commas -mod_authors = +mod_authors = Octol1ttle mod_credits = mod_logo_path = @@ -97,7 +97,7 @@ access_transformer_locations = ${mod_id}_at.cfg # Powerful tool to do runtime description changes of classes # Wiki: https://github.com/SpongePowered/Mixin/wiki + https://github.com/CleanroomMC/MixinBooter/ + https://cleanroommc.com/wiki/forge-mod-development/mixin/preface # Only use mixins once you understand the underlying structure -use_mixins = false +use_mixins = true mixin_booter_version = 9.1 # A configuration defines a mixin set, and you may have as many mixin sets as you require for your application. # Each config can only have one and only one package root. @@ -115,12 +115,12 @@ mixin_refmap = mixins.${mod_id}.refmap.json # Only make a coremod if you are absolutely sure of what you are doing # Change the property `coremod_includes_mod` to false if your coremod doesn't have a @Mod annotation # You MUST state a class name for `coremod_plugin_class_name` if you are making a coremod, the class should implement `IFMLLoadingPlugin` -is_coremod = false +is_coremod = true coremod_includes_mod = true -coremod_plugin_class_name = +coremod_plugin_class_name = ru.octol1ttle.knockdowns.common.KnockdownsFMLLoadingPlugin # AssetMover # Convenient way to allow downloading of assets from official vanilla Minecraft servers, CurseForge, or any direct links # Documentation: https://github.com/CleanroomMC/AssetMover use_asset_mover = false -asset_mover_version = 2.5 \ No newline at end of file +asset_mover_version = 2.5 diff --git a/src/main/java/com/example/modid/ExampleMod.java b/src/main/java/com/example/modid/ExampleMod.java deleted file mode 100644 index 4dd9e46..0000000 --- a/src/main/java/com/example/modid/ExampleMod.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.example.modid; - -import com.example.modid.Tags; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@Mod(modid = Tags.MOD_ID, name = Tags.MOD_NAME, version = Tags.VERSION) -public class ExampleMod { - - public static final Logger LOGGER = LogManager.getLogger(Tags.MOD_NAME); - - /** - * - * Take a look at how many FMLStateEvents you can listen to via the @Mod.EventHandler annotation here - * - */ - @Mod.EventHandler - public void preInit(FMLPreInitializationEvent event) { - LOGGER.info("Hello From {}!", Tags.MOD_NAME); - } - -} diff --git a/src/main/java/ru/octol1ttle/knockdowns/client/ClientProxy.java b/src/main/java/ru/octol1ttle/knockdowns/client/ClientProxy.java new file mode 100644 index 0000000..1a0fbf1 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/client/ClientProxy.java @@ -0,0 +1,15 @@ +package ru.octol1ttle.knockdowns.client; + +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import ru.octol1ttle.knockdowns.client.event.KnockdownsKeyListener; +import ru.octol1ttle.knockdowns.common.IClientProxy; + +@SideOnly(Side.CLIENT) +public class ClientProxy implements IClientProxy { + @Override + public void onFMLInit(FMLInitializationEvent event) { + KnockdownsKeyListener.registerKeyBindings(); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/client/communication/CalloutManager.java b/src/main/java/ru/octol1ttle/knockdowns/client/communication/CalloutManager.java new file mode 100644 index 0000000..a6ecefe --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/client/communication/CalloutManager.java @@ -0,0 +1,34 @@ +package ru.octol1ttle.knockdowns.client.communication; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import net.minecraft.client.Minecraft; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import ru.octol1ttle.knockdowns.client.util.Callout; +import ru.octol1ttle.knockdowns.client.util.DirectionalCallSound; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.PlayerCalloutS2CPacket; +import ru.octol1ttle.knockdowns.common.registry.KnockdownsSoundEvents; + +@SideOnly(Side.CLIENT) +public class CalloutManager { + private static final Minecraft client = Minecraft.getMinecraft(); + private static final Map callouts = new HashMap<>(); + + public static Set> getCallouts() { + return callouts.entrySet(); + } + + public static boolean addOrUpdateCallout(PlayerCalloutS2CPacket message) { + return callouts.put(message.playerId, new Callout(message.position, message.type, client.world.getTotalWorldTime())) == null; + } + + public static void playCalloutSound(PlayerCalloutS2CPacket message) { + client.getSoundHandler().playSound(new DirectionalCallSound(KnockdownsSoundEvents.CALLOUT, client.world.getEntityByID(message.playerId), message.position)); + } + + public static void clearCallouts() { + callouts.clear(); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/client/communication/KnockedNotificationManager.java b/src/main/java/ru/octol1ttle/knockdowns/client/communication/KnockedNotificationManager.java new file mode 100644 index 0000000..923e768 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/client/communication/KnockedNotificationManager.java @@ -0,0 +1,28 @@ +package ru.octol1ttle.knockdowns.client.communication; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import net.minecraft.client.Minecraft; +import net.minecraft.util.math.Vec3d; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import ru.octol1ttle.knockdowns.client.util.KnockedPlayerData; + +@SideOnly(Side.CLIENT) +public class KnockedNotificationManager { + private static final Minecraft client = Minecraft.getMinecraft(); + private static final List knockedDatas = new ArrayList<>(); + + public static void addKnockedNotification(int playerId, Vec3d position) { + knockedDatas.add(new KnockedPlayerData(playerId, position, client.world.getTotalWorldTime())); + } + + public static Collection getKnockedPlayerDatas() { + return knockedDatas; + } + + public static void clearDatas() { + knockedDatas.clear(); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/client/event/KnockdownsClientEventListener.java b/src/main/java/ru/octol1ttle/knockdowns/client/event/KnockdownsClientEventListener.java new file mode 100644 index 0000000..ebf5b56 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/client/event/KnockdownsClientEventListener.java @@ -0,0 +1,71 @@ +package ru.octol1ttle.knockdowns.client.event; + +import java.util.List; +import net.minecraft.client.Minecraft; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraftforge.client.event.RenderGameOverlayEvent; +import net.minecraftforge.client.event.RenderWorldLastEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.InputEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import ru.octol1ttle.knockdowns.Tags; +import ru.octol1ttle.knockdowns.client.communication.CalloutManager; +import ru.octol1ttle.knockdowns.client.communication.KnockedNotificationManager; +import ru.octol1ttle.knockdowns.client.gui.CommunicationGui; +import ru.octol1ttle.knockdowns.client.gui.KnockedNotificationGui; +import ru.octol1ttle.knockdowns.client.gui.ReviveGui; +import ru.octol1ttle.knockdowns.common.ReviverTracker; +import ru.octol1ttle.knockdowns.common.network.KnockdownsNetwork; +import ru.octol1ttle.knockdowns.common.network.packets.c2s.CancelReviveC2SPacket; + +@SideOnly(Side.CLIENT) +@Mod.EventBusSubscriber(value = Side.CLIENT, modid = Tags.MOD_ID) +public class KnockdownsClientEventListener { + private static final Minecraft client = Minecraft.getMinecraft(); + private static final CommunicationGui communicationGui = new CommunicationGui(); + private static final KnockedNotificationGui notificationGui = new KnockedNotificationGui(); + private static final ReviveGui reviveGui = new ReviveGui(); + + @SubscribeEvent + public static void onTick(TickEvent.ClientTickEvent event) { + if (event.phase == TickEvent.Phase.START) { + return; + } + if (client.world == null) { + CalloutManager.clearCallouts(); + KnockedNotificationManager.clearDatas(); + return; + } + CalloutManager.getCallouts().removeIf(callout -> client.world.getTotalWorldTime() - callout.getValue().getReceiveTime() > 60); + KnockedNotificationManager.getKnockedPlayerDatas().removeIf(notification -> client.world.getTotalWorldTime() - notification.getReceiveTime() > 100); + } + + @SubscribeEvent + public static void onPlayerTick(TickEvent.PlayerTickEvent event) { + List revivers = ReviverTracker.getRevivers(event.player); + if (revivers.contains(client.player) && !event.player.equals(client.pointedEntity)) { + KnockdownsNetwork.sendToServer(new CancelReviveC2SPacket()); + revivers.remove(client.player); + } + } + + @SubscribeEvent + public static void onRenderWorldLast(RenderWorldLastEvent event) { + communicationGui.renderCallouts(event.getPartialTicks()); + notificationGui.renderNotifications(event.getPartialTicks()); + } + + @SubscribeEvent + public static void onRenderGameOverlay(RenderGameOverlayEvent.Chat event) { + communicationGui.render(event.getPartialTicks(), event.getResolution()); + reviveGui.render(event.getPartialTicks(), event.getResolution()); + } + + @SubscribeEvent + public static void onKeyInput(InputEvent.KeyInputEvent event) { + KnockdownsKeyListener.tickKeys(); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/client/event/KnockdownsKeyListener.java b/src/main/java/ru/octol1ttle/knockdowns/client/event/KnockdownsKeyListener.java new file mode 100644 index 0000000..e2c1057 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/client/event/KnockdownsKeyListener.java @@ -0,0 +1,56 @@ +package ru.octol1ttle.knockdowns.client.event; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; +import net.minecraft.client.Minecraft; +import net.minecraft.client.settings.KeyBinding; +import net.minecraftforge.fml.client.registry.ClientRegistry; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import org.lwjgl.input.Keyboard; +import ru.octol1ttle.knockdowns.Tags; +import ru.octol1ttle.knockdowns.common.communication.CalloutType; +import ru.octol1ttle.knockdowns.common.data.IKnockdownsPlayerData; +import ru.octol1ttle.knockdowns.common.network.KnockdownsNetwork; +import ru.octol1ttle.knockdowns.common.network.packets.c2s.PlayerCalloutC2SPacket; + +@SideOnly(Side.CLIENT) +@Mod.EventBusSubscriber(value = Side.CLIENT, modid = Tags.MOD_ID) +public class KnockdownsKeyListener { + private static final Minecraft client = Minecraft.getMinecraft(); + public static final Map> calloutBindings = new HashMap<>(); + + public static void registerKeyBindings() { + calloutBindings.put( + new KeyBinding("knockdowns.key.callout.danger", Keyboard.KEY_LEFT, "knockdowns.key.category"), + () -> CalloutType.DANGER + ); + calloutBindings.put( + new KeyBinding("knockdowns.key.callout.booyah", Keyboard.KEY_DOWN, "knockdowns.key.category"), + () -> CalloutType.BOOYAH + ); + calloutBindings.put( + new KeyBinding("knockdowns.key.callout.this_way_help", Keyboard.KEY_UP, "knockdowns.key.category"), + () -> IKnockdownsPlayerData.get(client.player).isKnockedDown() ? CalloutType.HELP : CalloutType.THIS_WAY + ); + calloutBindings.put( + new KeyBinding("knockdowns.key.callout.ouch", Keyboard.KEY_RIGHT, "knockdowns.key.category"), + () -> CalloutType.OUCH + ); + + for (KeyBinding binding : calloutBindings.keySet()) { + ClientRegistry.registerKeyBinding(binding); + } + } + + public static void tickKeys() { + for (KeyBinding binding : calloutBindings.keySet()) { + if (binding.isPressed()) { + KnockdownsNetwork.sendToServer(new PlayerCalloutC2SPacket(calloutBindings.get(binding).get())); + break; + } + } + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/client/gui/CommunicationGui.java b/src/main/java/ru/octol1ttle/knockdowns/client/gui/CommunicationGui.java new file mode 100644 index 0000000..ac1920c --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/client/gui/CommunicationGui.java @@ -0,0 +1,175 @@ +package ru.octol1ttle.knockdowns.client.gui; + +import java.util.Map; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.resources.I18n; +import net.minecraft.client.settings.KeyBinding; +import net.minecraft.entity.Entity; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import org.lwjgl.input.Keyboard; +import org.lwjgl.opengl.GL11; +import ru.octol1ttle.knockdowns.Tags; +import ru.octol1ttle.knockdowns.client.communication.CalloutManager; +import ru.octol1ttle.knockdowns.client.event.KnockdownsKeyListener; +import ru.octol1ttle.knockdowns.client.util.Callout; + +@SideOnly(Side.CLIENT) +public class CommunicationGui extends KnockdownsBaseGui { + private static final Minecraft client = Minecraft.getMinecraft(); + private static final ResourceLocation LEFT_ARROW = new ResourceLocation(Tags.MOD_ID, "textures/gui/left_arrow.png"); + private static final ResourceLocation DOWN_ARROW = new ResourceLocation(Tags.MOD_ID, "textures/gui/down_arrow.png"); + private static final ResourceLocation UP_ARROW = new ResourceLocation(Tags.MOD_ID, "textures/gui/up_arrow.png"); + private static final ResourceLocation RIGHT_ARROW = new ResourceLocation(Tags.MOD_ID, "textures/gui/right_arrow.png"); + private static final int SCREEN_EDGE_MARGIN = 5; + private static final int SEPARATOR_MARGIN = 2; + private static final int KEY_SIZE = 17; + private float totalPartialTicks; + + @Override + public void render(float partialTicks, ScaledResolution resolution) { + FontRenderer font = client.fontRenderer; + + int x = SCREEN_EDGE_MARGIN; + int y = resolution.getScaledHeight() - SCREEN_EDGE_MARGIN - font.FONT_HEIGHT; + + KeyBinding[] sortedBindings = new KeyBinding[4]; + for (KeyBinding binding : KnockdownsKeyListener.calloutBindings.keySet()) + { + switch (binding.getKeyCode()) { + case Keyboard.KEY_LEFT: + sortedBindings[0] = binding; + break; + case Keyboard.KEY_DOWN: + sortedBindings[1] = binding; + break; + case Keyboard.KEY_UP: + sortedBindings[2] = binding; + break; + case Keyboard.KEY_RIGHT: + sortedBindings[3] = binding; + break; + } + } + + KeyBinding leftCallout = sortedBindings[0]; + if (leftCallout != null) { + String text = I18n.format(KnockdownsKeyListener.calloutBindings.get(leftCallout).get().getTextKey()); + font.drawStringWithShadow( + text, + x, + y - 12, + 0xFFFFFF + ); + + x += font.getStringWidth(text) + SEPARATOR_MARGIN; + client.getTextureManager().bindTexture(LEFT_ARROW); + this.drawTexture( + x, + y - KEY_SIZE, + KEY_SIZE, + KEY_SIZE + ); + x += KEY_SIZE + SEPARATOR_MARGIN; + } + + KeyBinding downCallout = sortedBindings[1]; + if (downCallout != null) { + String text = I18n.format(KnockdownsKeyListener.calloutBindings.get(downCallout).get().getTextKey()); + font.drawStringWithShadow( + text, + x + KEY_SIZE * 0.5f - font.getStringWidth(text) * 0.5f, + y + SEPARATOR_MARGIN, + 0xFFFFFF + ); + client.getTextureManager().bindTexture(DOWN_ARROW); + this.drawTexture( + x, + y - KEY_SIZE, + KEY_SIZE, + KEY_SIZE + ); + } + + KeyBinding upCallout = sortedBindings[2]; + if (upCallout != null) { + String text = I18n.format(KnockdownsKeyListener.calloutBindings.get(upCallout).get().getTextKey()); + font.drawStringWithShadow( + text, + x + KEY_SIZE * 0.5f - font.getStringWidth(text) * 0.5f, + y - KEY_SIZE * 2 - 12, + 0xFFFFFF + ); + + client.getTextureManager().bindTexture(UP_ARROW); + this.drawTexture( + x, + y - KEY_SIZE * 2 - 2, + KEY_SIZE, + KEY_SIZE + ); + } + + KeyBinding rightCallout = sortedBindings[3]; + if (rightCallout != null) { + x += KEY_SIZE + SEPARATOR_MARGIN; + String text = I18n.format(KnockdownsKeyListener.calloutBindings.get(rightCallout).get().getTextKey()); + font.drawStringWithShadow( + text, + x + KEY_SIZE + SEPARATOR_MARGIN, + y - 12, + 0xFFFFFF + ); + + client.getTextureManager().bindTexture(RIGHT_ARROW); + this.drawTexture( + x, + y - KEY_SIZE, + KEY_SIZE, + KEY_SIZE + ); + } + } + + public void renderCallouts(float partialTicks) { + totalPartialTicks += partialTicks; + for (Map.Entry calloutEntry : CalloutManager.getCallouts()) { + Entity entity = client.world.getEntityByID(calloutEntry.getKey()); + renderCallout( + I18n.format(calloutEntry.getValue().getType().getTextKey()), + entity != null ? entity.getPositionEyes(partialTicks).add(0, 1, 0) : calloutEntry.getValue().getPosition(), + client.getRenderManager().playerViewX, + client.getRenderManager().playerViewY, + client.getRenderManager().options.thirdPersonView == 2 + ); + } + } + + private void renderCallout(String text, Vec3d position, float pitch, float yaw, boolean isThirdPersonFrontal) { + FontRenderer font = client.fontRenderer; + + Vec3d deltaPos = position.subtract(client.getRenderManager().viewerPosX, client.getRenderManager().viewerPosY, client.getRenderManager().viewerPosZ); + + GlStateManager.pushMatrix(); + GlStateManager.translate(deltaPos.x, deltaPos.y, deltaPos.z); + GlStateManager.rotate(-yaw, 0.0F, 1.0F, 0.0F); + GlStateManager.rotate((float)(isThirdPersonFrontal ? -1 : 1) * pitch, 1.0F, 0.0F, 0.0F); + float scale = (float) Math.max(deltaPos.length() / 8.0f, 1.0f) * (0.75f + MathHelper.abs((float) (MathHelper.sin(totalPartialTicks / 10F) / Math.PI))); + GlStateManager.scale(-0.025F * scale, -0.025F * scale, 0.025F * scale); + GlStateManager.color(1f, 1f, 1f, 1f); + GlStateManager.disableCull(); + GlStateManager.depthFunc(GL11.GL_ALWAYS); + + font.drawStringWithShadow(text, -font.getStringWidth(text) * 0.5f, -font.FONT_HEIGHT * 0.5f, 0xFFFFFF); + + GlStateManager.depthFunc(GL11.GL_LEQUAL); + GlStateManager.enableCull(); + GlStateManager.popMatrix(); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/client/gui/KnockdownsBaseGui.java b/src/main/java/ru/octol1ttle/knockdowns/client/gui/KnockdownsBaseGui.java new file mode 100644 index 0000000..d2e635e --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/client/gui/KnockdownsBaseGui.java @@ -0,0 +1,25 @@ +package ru.octol1ttle.knockdowns.client.gui; + +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public abstract class KnockdownsBaseGui extends Gui { + public abstract void render(float partialTicks, ScaledResolution resolution); + + protected void drawTexture(int x, int y, int width, int height) { + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder bufferbuilder = tessellator.getBuffer(); + bufferbuilder.begin(7, DefaultVertexFormats.POSITION_TEX); + bufferbuilder.pos(x, y, this.zLevel).tex(0, 0).endVertex(); + bufferbuilder.pos(x, y + height, this.zLevel).tex(0, 1).endVertex(); + bufferbuilder.pos(x + width, y + height, this.zLevel).tex(1, 1).endVertex(); + bufferbuilder.pos(x + width, y, this.zLevel).tex(1, 0).endVertex(); + tessellator.draw(); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/client/gui/KnockedNotificationGui.java b/src/main/java/ru/octol1ttle/knockdowns/client/gui/KnockedNotificationGui.java new file mode 100644 index 0000000..c8055fb --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/client/gui/KnockedNotificationGui.java @@ -0,0 +1,64 @@ +package ru.octol1ttle.knockdowns.client.gui; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.entity.Entity; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import org.lwjgl.opengl.GL11; +import ru.octol1ttle.knockdowns.Tags; +import ru.octol1ttle.knockdowns.client.communication.KnockedNotificationManager; +import ru.octol1ttle.knockdowns.client.util.KnockedPlayerData; + +public class KnockedNotificationGui extends KnockdownsBaseGui { + private static final Minecraft client = Minecraft.getMinecraft(); + private static final int KNOCKED_ICON_SIZE = 18; + private static final ResourceLocation KNOCKED_ICON = new ResourceLocation(Tags.MOD_ID, "textures/gui/knocked_icon.png"); + private float totalPartialTicks; + + @Deprecated + @Override + public void render(float partialTicks, ScaledResolution resolution) { + } + + public void renderNotifications(float partialTicks) { + totalPartialTicks += partialTicks; + for (KnockedPlayerData data : KnockedNotificationManager.getKnockedPlayerDatas()) { + Entity entity = client.world.getEntityByID(data.getPlayerId()); + renderKnockedNotification( + entity != null ? entity.getPositionEyes(partialTicks).add(0, 1, 0) : data.getPosition(), + client.getRenderManager().playerViewX, + client.getRenderManager().playerViewY, + client.getRenderManager().options.thirdPersonView == 2 + ); + } + } + + private void renderKnockedNotification(Vec3d position, float pitch, float yaw, boolean isThirdPersonFrontal) { + Vec3d deltaPos = position.subtract(client.getRenderManager().viewerPosX, client.getRenderManager().viewerPosY, client.getRenderManager().viewerPosZ); + + GlStateManager.pushMatrix(); + GlStateManager.translate(deltaPos.x, deltaPos.y, deltaPos.z); + GlStateManager.rotate(-yaw, 0.0F, 1.0F, 0.0F); + GlStateManager.rotate((float)(isThirdPersonFrontal ? -1 : 1) * pitch, 1.0F, 0.0F, 0.0F); + float scale = (float) Math.max(deltaPos.length() / 8.0f, 1.0f) * (0.75f + MathHelper.abs((float) (MathHelper.sin(totalPartialTicks / 10F) / Math.PI))); + GlStateManager.scale(0.05F * scale, 0.05F * scale, 0.05F * scale); + GlStateManager.color(1f, 1f, 1f, 1f); + GlStateManager.disableCull(); + GlStateManager.depthFunc(GL11.GL_ALWAYS); + + client.getTextureManager().bindTexture(KNOCKED_ICON); + this.drawTexture( + -KNOCKED_ICON_SIZE / 2, + -KNOCKED_ICON_SIZE / 2, + KNOCKED_ICON_SIZE, + KNOCKED_ICON_SIZE + ); + + GlStateManager.depthFunc(GL11.GL_LEQUAL); + GlStateManager.enableCull(); + GlStateManager.popMatrix(); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/client/gui/ReviveGui.java b/src/main/java/ru/octol1ttle/knockdowns/client/gui/ReviveGui.java new file mode 100644 index 0000000..e8df745 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/client/gui/ReviveGui.java @@ -0,0 +1,47 @@ +package ru.octol1ttle.knockdowns.client.gui; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.text.TextFormatting; +import ru.octol1ttle.knockdowns.common.KnockdownsUtils; +import ru.octol1ttle.knockdowns.common.ReviverTracker; +import ru.octol1ttle.knockdowns.common.data.IKnockdownsPlayerData; + +public class ReviveGui extends KnockdownsBaseGui { + private static final Minecraft client = Minecraft.getMinecraft(); + + @Override + public void render(float partialTicks, ScaledResolution resolution) { + EntityPlayer reviving = + client.pointedEntity instanceof EntityPlayer && ReviverTracker.getRevivers((EntityPlayer) client.pointedEntity).contains(client.player) + ? (EntityPlayer) client.pointedEntity + : client.player; + if (IKnockdownsPlayerData.get(reviving).getReviveTimeLeft() == KnockdownsUtils.INITIAL_REVIVE_TIME_LEFT) { + return; + } + IKnockdownsPlayerData data = IKnockdownsPlayerData.get(reviving); + + FontRenderer font = client.fontRenderer; + + String timerText = String.format("%.1f", data.getReviveTimeLeft() / 20.0f); + float timerX = (resolution.getScaledWidth() - font.getStringWidth(timerText)) * 0.5f; + + int reviverCount = ReviverTracker.getRevivers(reviving).size(); + TextFormatting color; + if (reviverCount == 0) { + color = TextFormatting.RED; + } else if (reviverCount == 1) { + color = TextFormatting.WHITE; + } else { + color = TextFormatting.GREEN; + } + + String reviverCountText = "x" + reviverCount; + float reviveCountX = (resolution.getScaledWidth() - font.getStringWidth(reviverCountText)) * 0.5f; + + font.drawStringWithShadow(color + timerText, timerX, resolution.getScaledHeight() * 0.5f + 5, 553648127); + font.drawStringWithShadow(color + reviverCountText, reviveCountX, resolution.getScaledHeight() * 0.5f + 14, 553648127); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/client/network/KnockdownsClientPacketHandler.java b/src/main/java/ru/octol1ttle/knockdowns/client/network/KnockdownsClientPacketHandler.java new file mode 100644 index 0000000..4770827 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/client/network/KnockdownsClientPacketHandler.java @@ -0,0 +1,145 @@ +package ru.octol1ttle.knockdowns.client.network; + +import net.minecraft.client.Minecraft; +import net.minecraft.entity.player.EntityPlayer; +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 net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import ru.octol1ttle.knockdowns.client.communication.CalloutManager; +import ru.octol1ttle.knockdowns.client.communication.KnockedNotificationManager; +import ru.octol1ttle.knockdowns.client.util.DirectionalCallSound; +import ru.octol1ttle.knockdowns.common.ReviverTracker; +import ru.octol1ttle.knockdowns.common.data.IKnockdownsPlayerData; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.PlayerCalloutS2CPacket; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.PlayerKnockedDownS2CPacket; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.SynchronizePlayerDataS2CPacket; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.SynchronizeReviversS2CPacket; +import ru.octol1ttle.knockdowns.common.registry.KnockdownsSoundEvents; + +import static ru.octol1ttle.knockdowns.common.KnockdownsUtils.INITIAL_REVIVE_TIME_LEFT; + +public class KnockdownsClientPacketHandler { + public static class Callout implements IMessageHandler { + private static final Minecraft client = Minecraft.getMinecraft(); + + @SideOnly(Side.CLIENT) + @Override + public IMessage onMessage(PlayerCalloutS2CPacket message, MessageContext ctx) { + client.addScheduledTask(() -> { + if (CalloutManager.addOrUpdateCallout(message)) { + CalloutManager.playCalloutSound(message); + } + }); + return null; + } + } + + public static class PlayerKnockedDown implements IMessageHandler { + private static final Minecraft client = Minecraft.getMinecraft(); + + @SideOnly(Side.CLIENT) + @Override + public IMessage onMessage(PlayerKnockedDownS2CPacket message, MessageContext ctx) { + client.addScheduledTask(() -> { + EntityPlayer entity = (EntityPlayer) client.world.getEntityByID(message.playerId); + if (entity != null) { + IKnockdownsPlayerData data = IKnockdownsPlayerData.get(entity); + data.setKnockedDown(true); + data.setReviveTimeLeft(INITIAL_REVIVE_TIME_LEFT); + } + + if (client.player.dimension == message.dimensionId) { + client.getSoundHandler().playSound(new DirectionalCallSound(KnockdownsSoundEvents.KNOCKED_DOWN, entity, message.position)); + KnockedNotificationManager.addKnockedNotification(message.playerId, message.position); + } + }); + return null; + } + } + + public static class SynchronizePlayerData { + private static final Minecraft client = Minecraft.getMinecraft(); + + public static class KnockedDown implements IMessageHandler { + @SideOnly(Side.CLIENT) + @Override + public IMessage onMessage(SynchronizePlayerDataS2CPacket.KnockedDown message, MessageContext ctx) { + client.addScheduledTask(() -> { + EntityPlayer entity = (EntityPlayer) client.world.getEntityByID(message.playerId); + if (entity != null) { + IKnockdownsPlayerData data = IKnockdownsPlayerData.get(entity); + data.setKnockedDown(message.knockedDown); + } + }); + return null; + } + } + + public static class ReviveTimeLeft implements IMessageHandler { + @SideOnly(Side.CLIENT) + @Override + public IMessage onMessage(SynchronizePlayerDataS2CPacket.ReviveTimeLeft message, MessageContext ctx) { + client.addScheduledTask(() -> { + EntityPlayer entity = (EntityPlayer) client.world.getEntityByID(message.playerId); + if (entity != null) { + IKnockdownsPlayerData data = IKnockdownsPlayerData.get(entity); + data.setReviveTimeLeft(message.reviveTimeLeft); + } + }); + return null; + } + } + + public static class Full implements IMessageHandler { + @SideOnly(Side.CLIENT) + @Override + public IMessage onMessage(SynchronizePlayerDataS2CPacket.Full message, MessageContext ctx) { + client.addScheduledTask(() -> { + EntityPlayer entity = (EntityPlayer) client.world.getEntityByID(message.playerId); + if (entity != null) { + IKnockdownsPlayerData data = IKnockdownsPlayerData.get(entity); + data.setKnockedDown(message.knockedDown); + data.setReviveTimeLeft(message.reviveTimeLeft); + } + }); + return null; + } + } + } + + public static class SynchronizeRevivers { + private static final Minecraft client = Minecraft.getMinecraft(); + + public static class Add implements IMessageHandler { + @SideOnly(Side.CLIENT) + @Override + public IMessage onMessage(SynchronizeReviversS2CPacket.Add message, MessageContext ctx) { + client.addScheduledTask(() -> { + EntityPlayer knocked = (EntityPlayer) client.world.getEntityByID(message.knockedId); + EntityPlayer reviver = (EntityPlayer) client.world.getEntityByID(message.reviverId); + if (knocked != null && reviver != null) { + ReviverTracker.startReviving(knocked, reviver); + } + }); + return null; + } + } + + public static class Remove implements IMessageHandler { + @SideOnly(Side.CLIENT) + @Override + public IMessage onMessage(SynchronizeReviversS2CPacket.Remove message, MessageContext ctx) { + client.addScheduledTask(() -> { + EntityPlayer knocked = (EntityPlayer) client.world.getEntityByID(message.knockedId); + EntityPlayer reviver = (EntityPlayer) client.world.getEntityByID(message.reviverId); + if (knocked != null && reviver != null) { + ReviverTracker.stopReviving(knocked, reviver); + } + }); + return null; + } + } + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/client/util/Callout.java b/src/main/java/ru/octol1ttle/knockdowns/client/util/Callout.java new file mode 100644 index 0000000..aa82df3 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/client/util/Callout.java @@ -0,0 +1,31 @@ +package ru.octol1ttle.knockdowns.client.util; + +import net.minecraft.util.math.Vec3d; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import ru.octol1ttle.knockdowns.common.communication.CalloutType; + +@SideOnly(Side.CLIENT) +public class Callout { + private final Vec3d position; + private final CalloutType type; + private final long receiveTime; + + public Callout(Vec3d position, CalloutType type, long receiveTime) { + this.position = position; + this.type = type; + this.receiveTime = receiveTime; + } + + public Vec3d getPosition() { + return position; + } + + public CalloutType getType() { + return type; + } + + public long getReceiveTime() { + return receiveTime; + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/client/util/DirectionalCallSound.java b/src/main/java/ru/octol1ttle/knockdowns/client/util/DirectionalCallSound.java new file mode 100644 index 0000000..c5089b9 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/client/util/DirectionalCallSound.java @@ -0,0 +1,42 @@ +package ru.octol1ttle.knockdowns.client.util; + +import javax.annotation.Nullable; +import net.minecraft.client.Minecraft; +import net.minecraft.client.audio.MovingSound; +import net.minecraft.entity.Entity; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.SoundEvent; +import net.minecraft.util.math.Vec3d; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public class DirectionalCallSound extends MovingSound { + private final @Nullable Entity entity; + private final Vec3d position; + private int time; + + public DirectionalCallSound(SoundEvent event, @Nullable Entity entity, Vec3d position) { + super(event, SoundCategory.PLAYERS); + this.entity = entity; + this.position = position; + } + + @Override + public void update() { + this.time++; + if (this.time > 40 || this.entity != null && this.entity.isDead) { + this.donePlaying = true; + return; + } + + Minecraft client = Minecraft.getMinecraft(); + Vec3d calloutPos = this.entity != null ? this.entity.getPositionVector() : this.position; + Vec3d directionVec = calloutPos.subtract(client.player.getPositionVector()).normalize(); + Vec3d finalPos = client.player.getPositionVector().add(directionVec); + + this.xPosF = (float) finalPos.x; + this.yPosF = (float) finalPos.y; + this.zPosF = (float) finalPos.z; + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/client/util/KnockedPlayerData.java b/src/main/java/ru/octol1ttle/knockdowns/client/util/KnockedPlayerData.java new file mode 100644 index 0000000..8cebdde --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/client/util/KnockedPlayerData.java @@ -0,0 +1,27 @@ +package ru.octol1ttle.knockdowns.client.util; + +import net.minecraft.util.math.Vec3d; + +public class KnockedPlayerData { + private final int playerId; + private final Vec3d position; + private final long receiveTime; + + public KnockedPlayerData(int playerId, Vec3d position, long receiveTime) { + this.playerId = playerId; + this.position = position; + this.receiveTime = receiveTime; + } + + public int getPlayerId() { + return playerId; + } + + public Vec3d getPosition() { + return position; + } + + public long getReceiveTime() { + return receiveTime; + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/IClientProxy.java b/src/main/java/ru/octol1ttle/knockdowns/common/IClientProxy.java new file mode 100644 index 0000000..448ccd8 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/IClientProxy.java @@ -0,0 +1,13 @@ +package ru.octol1ttle.knockdowns.common; + +import net.minecraftforge.fml.common.event.FMLInitializationEvent; + +public interface IClientProxy { + void onFMLInit(FMLInitializationEvent event); + + class Dummy implements IClientProxy { + @Override + public void onFMLInit(FMLInitializationEvent event) { + } + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsCommonEventListener.java b/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsCommonEventListener.java new file mode 100644 index 0000000..1b8b252 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsCommonEventListener.java @@ -0,0 +1,239 @@ +package ru.octol1ttle.knockdowns.common; + +import java.util.List; +import net.minecraft.client.resources.I18n; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.init.MobEffects; +import net.minecraft.potion.PotionEffect; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.DamageSource; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.SoundEvent; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraftforge.event.AttachCapabilitiesEvent; +import net.minecraftforge.event.RegistryEvent; +import net.minecraftforge.event.entity.living.LivingDeathEvent; +import net.minecraftforge.event.entity.living.LivingSetAttackTargetEvent; +import net.minecraftforge.event.entity.player.AttackEntityEvent; +import net.minecraftforge.event.entity.player.PlayerEvent; +import net.minecraftforge.event.entity.player.PlayerInteractEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerLoggedInEvent; +import net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerLoggedOutEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import ru.octol1ttle.knockdowns.Tags; +import ru.octol1ttle.knockdowns.common.data.IKnockdownsPlayerData; +import ru.octol1ttle.knockdowns.common.data.KnockdownsCapability; +import ru.octol1ttle.knockdowns.common.network.KnockdownsNetwork; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.PlayerKnockedDownS2CPacket; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.SynchronizePlayerDataS2CPacket; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.SynchronizeReviversS2CPacket; +import ru.octol1ttle.knockdowns.common.registry.KnockdownsSoundEvents; + +import static ru.octol1ttle.knockdowns.common.KnockdownsUtils.INITIAL_REVIVE_TIME_LEFT; +import static ru.octol1ttle.knockdowns.common.KnockdownsUtils.KNOCKED_HURT_PERIOD; +import static ru.octol1ttle.knockdowns.common.KnockdownsUtils.KNOCKED_INVULNERABILITY_TICKS; +import static ru.octol1ttle.knockdowns.common.KnockdownsUtils.KNOCKED_TENACITY; +import static ru.octol1ttle.knockdowns.common.KnockdownsUtils.allPlayersKnocked; +import static ru.octol1ttle.knockdowns.common.KnockdownsUtils.resetKnockedState; + +@Mod.EventBusSubscriber(modid = Tags.MOD_ID) +public class KnockdownsCommonEventListener { + public static void onFMLInit(FMLInitializationEvent event) { + KnockdownsNetwork.registerPackets(); + KnockdownsCapability.register(); + } + + @SubscribeEvent + public static void onSoundsRegister(RegistryEvent.Register event) { + event.getRegistry().register(KnockdownsSoundEvents.CALLOUT); + } + + @SubscribeEvent + public static void onCapabilitiesAttach(AttachCapabilitiesEvent event) { + if (event.getObject() instanceof EntityPlayer) { + event.addCapability(KnockdownsCapability.ID, new KnockdownsCapability()); + } + } + + @SubscribeEvent + public static void onPlayerTick(TickEvent.PlayerTickEvent event) { + MinecraftServer server = event.player.getServer(); + if (event.phase == TickEvent.Phase.START || server == null) { + return; + } + EntityPlayerMP player = (EntityPlayerMP) event.player; + IKnockdownsPlayerData data = IKnockdownsPlayerData.get(player); + if (!data.isKnockedDown()) { + return; + } + + if (allPlayersKnocked(server, player)) { + player.attackEntityFrom(DamageSource.GENERIC, player.getMaxHealth()); + return; + } + + List revivers = ReviverTracker.getRevivers(player); + if (!revivers.isEmpty()) { + data.setReviveTimeLeft(data.getReviveTimeLeft() - revivers.size()); + KnockdownsNetwork.sendToMultiple( + new SynchronizePlayerDataS2CPacket.ReviveTimeLeft(player.getEntityId(), data.getReviveTimeLeft()), + revivers, + player + ); + + if (data.getReviveTimeLeft() <= 0) { + resetKnockedState(player, data); + + player.setEntityInvulnerable(false); + player.setHealth(player.getMaxHealth() * 0.3f); + player.setAbsorptionAmount(0.0f); + } + return; + } + + int oldReviveTimeLeft = data.getReviveTimeLeft(); + data.setReviveTimeLeft(Math.min(INITIAL_REVIVE_TIME_LEFT, oldReviveTimeLeft + 1)); + if (data.getReviveTimeLeft() != oldReviveTimeLeft) { + KnockdownsNetwork.sendToPlayer( + new SynchronizePlayerDataS2CPacket.ReviveTimeLeft(player.getEntityId(), data.getReviveTimeLeft()), + player + ); + } + + data.setTicksKnocked(data.getTicksKnocked() + 1); + + int period = MathHelper.floor(KNOCKED_HURT_PERIOD * 20); + if (data.getTicksKnocked() >= KNOCKED_INVULNERABILITY_TICKS && data.getTicksKnocked() % period == 0) { + player.setEntityInvulnerable(false); + player.attackEntityFrom(DamageSource.GENERIC, player.getMaxHealth() / (KNOCKED_TENACITY / KNOCKED_HURT_PERIOD)); + } + } + + @SubscribeEvent + public static void onPlayerDeath(LivingDeathEvent event) { + if (!(event.getEntityLiving() instanceof EntityPlayerMP)) { + return; + } + + EntityPlayerMP player = (EntityPlayerMP) event.getEntityLiving(); + IKnockdownsPlayerData data = player.getCapability(KnockdownsCapability.CAPABILITY, null); + if (data == null) { + return; + } + + if (data.isKnockedDown() || allPlayersKnocked(player.getServer(), player)) { + ReviverTracker.clearRevivers(player); + return; + } + + player.clearActivePotions(); + player.setEntityInvulnerable(true); + player.setHealth(1.0f); + player.setAbsorptionAmount(player.getMaxHealth() - 1.0f); + player.extinguish(); + player.setAir(300); + player.clearElytraFlying(); + + player.addPotionEffect(new PotionEffect(MobEffects.SLOWNESS, 6000, 3)); + + Entity trueSource = event.getSource().getTrueSource(); + if (trueSource instanceof EntityLiving) { + ((EntityLiving) trueSource).setAttackTarget(null); + } + + data.setKnockedDown(true); + data.setReviveTimeLeft(INITIAL_REVIVE_TIME_LEFT); + data.setTicksKnocked(0); + + KnockdownsNetwork.sendToAll(new PlayerKnockedDownS2CPacket(player.getEntityId(), player.dimension, player.getPositionEyes(1).add(0, 1, 0))); + + TextComponentTranslation deathMessage = (TextComponentTranslation) player.getCombatTracker().getDeathMessage(); + + String knockdownKey = deathMessage.getKey().replace("death.", "knockdown."); + player.getServer().getPlayerList().sendMessage( + I18n.hasKey(knockdownKey) + ? new TextComponentTranslation(knockdownKey, deathMessage.getFormatArgs()) + : deathMessage, + true + ); + + event.setCanceled(true); + } + + @SubscribeEvent + public static void onMobTarget(LivingSetAttackTargetEvent event) { + if (event.getTarget() instanceof EntityPlayer && IKnockdownsPlayerData.get((EntityPlayer) event.getTarget()).isKnockedDown()) { + ((EntityLiving)event.getEntityLiving()).setAttackTarget(null); + } + } + + @SubscribeEvent + public static void onPlayerStartTracking(PlayerEvent.StartTracking event) { + if (event.getTarget() instanceof EntityPlayerMP) { + IKnockdownsPlayerData data = IKnockdownsPlayerData.get((EntityPlayer) event.getTarget()); + if (data.isKnockedDown()) { + KnockdownsNetwork.sendToPlayer( + new SynchronizePlayerDataS2CPacket.KnockedDown( + event.getTarget().getEntityId(), + data.isKnockedDown() + ), + (EntityPlayerMP) event.getEntityPlayer() + ); + } + } + } + + @SubscribeEvent + public static void onKnockedAttack(AttackEntityEvent event) { + if (IKnockdownsPlayerData.get(event.getEntityPlayer()).isKnockedDown()) { + event.setCanceled(true); + } + } + + @SubscribeEvent + public static void onKnockedInteraction(PlayerInteractEvent event) { + if (IKnockdownsPlayerData.get(event.getEntityPlayer()).isKnockedDown()) { + if (!(event instanceof PlayerInteractEvent.RightClickBlock) && event.isCancelable()) { + event.setCanceled(true); + event.setCancellationResult(EnumActionResult.FAIL); + } + } else if (event instanceof PlayerInteractEvent.EntityInteract) { + onPlayerInteraction((PlayerInteractEvent.EntityInteract) event); + } + } + + public static void onPlayerInteraction(PlayerInteractEvent.EntityInteract event) { + if (event.getEntityLiving() instanceof EntityPlayerMP) { + EntityPlayerMP knocked = (EntityPlayerMP) event.getEntityLiving(); + if (IKnockdownsPlayerData.get(knocked).isKnockedDown()) { + KnockdownsNetwork.sendToMultiple( + new SynchronizeReviversS2CPacket.Add(event.getEntityLiving().getEntityId(), event.getEntityPlayer().getEntityId()), + ReviverTracker.getRevivers(knocked), + knocked + ); + ReviverTracker.startReviving(knocked, event.getEntityPlayer()); + } + } + } + + @SubscribeEvent + public static void onPlayerJoin(PlayerLoggedInEvent event) { + IKnockdownsPlayerData data = IKnockdownsPlayerData.get(event.player); + KnockdownsNetwork.sendToPlayer( + new SynchronizePlayerDataS2CPacket.Full(event.player.getEntityId(), data.isKnockedDown(), data.getReviveTimeLeft()), + (EntityPlayerMP) event.player + ); + } + + @SubscribeEvent + public static void onPlayerLeave(PlayerLoggedOutEvent event) { + ReviverTracker.clearRevivers(event.player); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsFMLLoadingPlugin.java b/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsFMLLoadingPlugin.java new file mode 100644 index 0000000..77e881b --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsFMLLoadingPlugin.java @@ -0,0 +1,43 @@ +package ru.octol1ttle.knockdowns.common; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; +import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin; +import zone.rong.mixinbooter.IEarlyMixinLoader; + +@IFMLLoadingPlugin.MCVersion("1.12.2") +public class KnockdownsFMLLoadingPlugin implements IFMLLoadingPlugin, IEarlyMixinLoader { + @Override + public String[] getASMTransformerClass() { + return null; + } + + @Override + public String getModContainerClass() { + return null; + } + + @Nullable + @Override + public String getSetupClass() { + return null; + } + + @Override + public void injectData(Map data) { + } + + @Override + public String getAccessTransformerClass() { + return null; + } + + @Override + public List getMixinConfigs() { + ArrayList list = new ArrayList<>(); + list.add("mixins.knockdowns.json"); + return list; + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsMod.java b/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsMod.java new file mode 100644 index 0000000..d9e27c0 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsMod.java @@ -0,0 +1,21 @@ +package ru.octol1ttle.knockdowns.common; + +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.SidedProxy; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import ru.octol1ttle.knockdowns.Tags; + +@Mod(modid = Tags.MOD_ID, name = Tags.MOD_NAME, version = Tags.VERSION) +public class KnockdownsMod { + public static final Logger LOGGER = LogManager.getLogger(Tags.MOD_NAME); + @SidedProxy(clientSide = "ru.octol1ttle.knockdowns.client.ClientProxy", serverSide = "ru.octol1ttle.knockdowns.common.IClientProxy$Dummy") + public static IClientProxy clientProxy; + + @Mod.EventHandler + public void onFMLInit(FMLInitializationEvent event) { + clientProxy.onFMLInit(event); + KnockdownsCommonEventListener.onFMLInit(event); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsUtils.java b/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsUtils.java new file mode 100644 index 0000000..207a0ab --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsUtils.java @@ -0,0 +1,43 @@ +package ru.octol1ttle.knockdowns.common; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.init.MobEffects; +import net.minecraft.server.MinecraftServer; +import ru.octol1ttle.knockdowns.common.data.IKnockdownsPlayerData; +import ru.octol1ttle.knockdowns.common.network.KnockdownsNetwork; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.SynchronizePlayerDataS2CPacket; + +public class KnockdownsUtils { + public static final int INITIAL_REVIVE_TIME_LEFT = 200; + public static final float KNOCKED_INVULNERABILITY_TICKS = 3.0f * 20.0f; + public static final float KNOCKED_HURT_PERIOD = 1.2f; + public static final float KNOCKED_TENACITY = 60.0f; + + public static boolean allPlayersKnocked(MinecraftServer server, EntityPlayer except) { + for (EntityPlayer player : server.getPlayerList().getPlayers()) { + if (player.equals(except)) { + continue; + } + IKnockdownsPlayerData data = IKnockdownsPlayerData.get(player); + if (player.isEntityAlive() && !data.isKnockedDown()) { + return false; + } + } + return false; + } + + public static void resetKnockedState(EntityPlayerMP player, IKnockdownsPlayerData data) { + player.removePotionEffect(MobEffects.SLOWNESS); + data.setKnockedDown(false); + data.setReviveTimeLeft(INITIAL_REVIVE_TIME_LEFT); + data.setTicksKnocked(0); + + KnockdownsNetwork.sendToTrackingAndSelf( + new SynchronizePlayerDataS2CPacket.KnockedDown(player.getEntityId(), data.isKnockedDown()), + player + ); + + ReviverTracker.clearRevivers(player); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/ReviverTracker.java b/src/main/java/ru/octol1ttle/knockdowns/common/ReviverTracker.java new file mode 100644 index 0000000..ba4736c --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/ReviverTracker.java @@ -0,0 +1,32 @@ +package ru.octol1ttle.knockdowns.common; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import net.minecraft.entity.player.EntityPlayer; + +public class ReviverTracker { + private static final Map> knockedToReviversMap = new HashMap<>(); + + public static void startReviving(EntityPlayer knocked, EntityPlayer reviver) { + getRevivers(knocked).add(reviver); + } + + public static void stopReviving(EntityPlayer knocked, EntityPlayer reviver) { + getRevivers(knocked).remove(reviver); + } + + public static void clearRevivers(EntityPlayer knocked) { + getRevivers(knocked).clear(); + } + + public static List getRevivers(EntityPlayer knocked) { + return knockedToReviversMap.computeIfAbsent(knocked, player -> new ArrayList<>()); + } + + public static Set>> getAllRevivers() { + return knockedToReviversMap.entrySet(); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/communication/CalloutType.java b/src/main/java/ru/octol1ttle/knockdowns/common/communication/CalloutType.java new file mode 100644 index 0000000..56ecb03 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/communication/CalloutType.java @@ -0,0 +1,35 @@ +package ru.octol1ttle.knockdowns.common.communication; + +public enum CalloutType { + DANGER((byte) 0, "knockdowns.callout.danger"), + BOOYAH((byte) 1, "knockdowns.callout.booyah"), + THIS_WAY((byte) 2, "knockdowns.callout.this_way"), + OUCH((byte) 3, "knockdowns.callout.ouch"), + HELP((byte) 4, "knockdowns.callout.help"); + + private final byte id; + private final String textKey; + + CalloutType(byte id, String textKey) { + this.id = id; + this.textKey = textKey; + } + + public byte getId() { + return id; + } + + public String getTextKey() { + return textKey; + } + + public static CalloutType byId(byte id) { + for (CalloutType type : CalloutType.values()) { + if (id == type.getId()) { + return type; + } + } + + throw new IllegalArgumentException(); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/data/IKnockdownsPlayerData.java b/src/main/java/ru/octol1ttle/knockdowns/common/data/IKnockdownsPlayerData.java new file mode 100644 index 0000000..f0e98c9 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/data/IKnockdownsPlayerData.java @@ -0,0 +1,21 @@ +package ru.octol1ttle.knockdowns.common.data; + +import java.util.Objects; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.common.util.INBTSerializable; + +public interface IKnockdownsPlayerData extends INBTSerializable { + boolean isKnockedDown(); + void setKnockedDown(boolean knockedDown); + + int getReviveTimeLeft(); + void setReviveTimeLeft(int reviveTimeLeft); + + int getTicksKnocked(); + void setTicksKnocked(int ticksKnocked); + + static IKnockdownsPlayerData get(EntityPlayer player) { + return Objects.requireNonNull(player.getCapability(KnockdownsCapability.CAPABILITY, null)); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/data/KnockdownsCapability.java b/src/main/java/ru/octol1ttle/knockdowns/common/data/KnockdownsCapability.java new file mode 100644 index 0000000..d25ad94 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/data/KnockdownsCapability.java @@ -0,0 +1,65 @@ +package ru.octol1ttle.knockdowns.common.data; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.LazyLoadBase; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.CapabilityInject; +import net.minecraftforge.common.capabilities.CapabilityManager; +import net.minecraftforge.common.capabilities.ICapabilitySerializable; +import ru.octol1ttle.knockdowns.Tags; + +public class KnockdownsCapability implements ICapabilitySerializable { + @CapabilityInject(IKnockdownsPlayerData.class) + public static Capability CAPABILITY; + public static final ResourceLocation ID = new ResourceLocation(Tags.MOD_ID, "data"); + + private KnockdownsPlayerData playerData = null; + private final LazyLoadBase lazy = new LazyLoadBase() { + @Override + protected KnockdownsPlayerData load() { + return playerData == null ? (playerData = new KnockdownsPlayerData()) : playerData; + } + }; + + public static void register() { + CapabilityManager.INSTANCE.register(IKnockdownsPlayerData.class, new Capability.IStorage() { + @Nullable + @Override + public NBTBase writeNBT(Capability capability, IKnockdownsPlayerData instance, EnumFacing side) { + return instance.serializeNBT(); + } + + @Override + public void readNBT(Capability capability, IKnockdownsPlayerData instance, EnumFacing side, NBTBase nbt) { + instance.deserializeNBT((NBTTagCompound) nbt); + } + }, KnockdownsPlayerData::new); + } + + @Override + public boolean hasCapability(@Nonnull Capability capability, @Nullable EnumFacing facing) { + return capability == CAPABILITY; + } + + @SuppressWarnings("unchecked") + @Nullable + @Override + public T getCapability(@Nonnull Capability capability, @Nullable EnumFacing facing) { + return capability == CAPABILITY ? (T) lazy.getValue() : null; + } + + @Override + public NBTTagCompound serializeNBT() { + return lazy.getValue().serializeNBT(); + } + + @Override + public void deserializeNBT(NBTTagCompound nbt) { + lazy.getValue().deserializeNBT(nbt); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/data/KnockdownsPlayerData.java b/src/main/java/ru/octol1ttle/knockdowns/common/data/KnockdownsPlayerData.java new file mode 100644 index 0000000..7eac143 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/data/KnockdownsPlayerData.java @@ -0,0 +1,59 @@ +package ru.octol1ttle.knockdowns.common.data; + +import net.minecraft.nbt.NBTTagCompound; +import ru.octol1ttle.knockdowns.common.KnockdownsUtils; + +public class KnockdownsPlayerData implements IKnockdownsPlayerData { + private static final String KEY_KNOCKED_DOWN = "KnockedDown"; + private static final String KEY_REVIVE_TIME_LEFT = "ReviveTimeLeft"; + private static final String KEY_TICKS_KNOCKED = "TicksKnocked"; + private boolean knockedDown = false; + private int reviveTimeLeft = KnockdownsUtils.INITIAL_REVIVE_TIME_LEFT; + private int ticksKnocked = 0; + + @Override + public boolean isKnockedDown() { + return this.knockedDown; + } + + @Override + public void setKnockedDown(boolean knockedDown) { + this.knockedDown = knockedDown; + } + + @Override + public int getReviveTimeLeft() { + return this.reviveTimeLeft; + } + + @Override + public void setReviveTimeLeft(int reviveTimeLeft) { + this.reviveTimeLeft = reviveTimeLeft; + } + + @Override + public int getTicksKnocked() { + return this.ticksKnocked; + } + + @Override + public void setTicksKnocked(int ticksKnocked) { + this.ticksKnocked = ticksKnocked; + } + + @Override + public NBTTagCompound serializeNBT() { + NBTTagCompound nbt = new NBTTagCompound(); + nbt.setBoolean(KEY_KNOCKED_DOWN, this.knockedDown); + nbt.setInteger(KEY_REVIVE_TIME_LEFT, this.reviveTimeLeft); + nbt.setInteger(KEY_TICKS_KNOCKED, this.ticksKnocked); + return nbt; + } + + @Override + public void deserializeNBT(NBTTagCompound nbt) { + this.knockedDown = nbt.getBoolean(KEY_KNOCKED_DOWN); + this.reviveTimeLeft = nbt.getInteger(KEY_REVIVE_TIME_LEFT); + this.ticksKnocked = nbt.getInteger(KEY_TICKS_KNOCKED); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/mixins/EntityLivingBaseMixin.java b/src/main/java/ru/octol1ttle/knockdowns/common/mixins/EntityLivingBaseMixin.java new file mode 100644 index 0000000..8bcea94 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/mixins/EntityLivingBaseMixin.java @@ -0,0 +1,31 @@ +package ru.octol1ttle.knockdowns.common.mixins; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.world.World; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import ru.octol1ttle.knockdowns.common.KnockdownsUtils; +import ru.octol1ttle.knockdowns.common.data.IKnockdownsPlayerData; + +@SuppressWarnings("ConstantValue") +@Mixin(EntityLivingBase.class) +public abstract class EntityLivingBaseMixin extends Entity { + public EntityLivingBaseMixin(World worldIn) { + super(worldIn); + } + + @Inject(method = "checkTotemDeathProtection", at = @At("RETURN")) + public void onTotemActivation(CallbackInfoReturnable cir) { + if (cir.getReturnValue() && ((Object) this) instanceof EntityPlayerMP) { + EntityPlayerMP player = (EntityPlayerMP) (Object) this; + IKnockdownsPlayerData data = IKnockdownsPlayerData.get(player); + if (data.isKnockedDown()) { + KnockdownsUtils.resetKnockedState(player, data); + } + } + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/mixins/EntityPlayerMixin.java b/src/main/java/ru/octol1ttle/knockdowns/common/mixins/EntityPlayerMixin.java new file mode 100644 index 0000000..e1684ac --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/mixins/EntityPlayerMixin.java @@ -0,0 +1,22 @@ +package ru.octol1ttle.knockdowns.common.mixins; + +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import net.minecraft.entity.player.EntityPlayer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import ru.octol1ttle.knockdowns.common.data.IKnockdownsPlayerData; + +@SuppressWarnings("ConstantValue") +@Mixin(EntityPlayer.class) +public class EntityPlayerMixin { + @ModifyReturnValue(method = "shouldHeal", at = @At("RETURN")) + private boolean dontHealIfKnockedDown(boolean original) { + if (((Object) this) instanceof EntityPlayer) { + EntityPlayer player = (EntityPlayer) (Object) this; + if (IKnockdownsPlayerData.get(player).isKnockedDown()) { + return false; + } + } + return original; + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/network/KnockdownsNetwork.java b/src/main/java/ru/octol1ttle/knockdowns/common/network/KnockdownsNetwork.java new file mode 100644 index 0000000..4542e68 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/network/KnockdownsNetwork.java @@ -0,0 +1,71 @@ +package ru.octol1ttle.knockdowns.common.network; + +import java.util.List; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraftforge.fml.common.network.NetworkRegistry; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; +import net.minecraftforge.fml.common.network.simpleimpl.SimpleNetworkWrapper; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import ru.octol1ttle.knockdowns.Tags; +import ru.octol1ttle.knockdowns.client.network.KnockdownsClientPacketHandler; +import ru.octol1ttle.knockdowns.common.network.packets.c2s.CancelReviveC2SPacket; +import ru.octol1ttle.knockdowns.common.network.packets.c2s.PlayerCalloutC2SPacket; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.PlayerCalloutS2CPacket; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.PlayerKnockedDownS2CPacket; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.SynchronizePlayerDataS2CPacket; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.SynchronizeReviversS2CPacket; + +public class KnockdownsNetwork { + private static final SimpleNetworkWrapper INSTANCE = NetworkRegistry.INSTANCE.newSimpleChannel(Tags.MOD_ID); + private static int packetId = 0; + + public static void registerPackets() { + INSTANCE.registerMessage(KnockdownsServerPacketHandler.Callout.class, PlayerCalloutC2SPacket.class, packetId++, Side.SERVER); + INSTANCE.registerMessage(KnockdownsServerPacketHandler.CancelRevive.class, CancelReviveC2SPacket.class, packetId++, Side.SERVER); + + INSTANCE.registerMessage(KnockdownsClientPacketHandler.Callout.class, PlayerCalloutS2CPacket.class, packetId++, Side.CLIENT); + INSTANCE.registerMessage(KnockdownsClientPacketHandler.PlayerKnockedDown.class, PlayerKnockedDownS2CPacket.class, packetId++, Side.CLIENT); + INSTANCE.registerMessage(KnockdownsClientPacketHandler.SynchronizePlayerData.KnockedDown.class, SynchronizePlayerDataS2CPacket.KnockedDown.class, packetId++, Side.CLIENT); + INSTANCE.registerMessage(KnockdownsClientPacketHandler.SynchronizePlayerData.ReviveTimeLeft.class, SynchronizePlayerDataS2CPacket.ReviveTimeLeft.class, packetId++, Side.CLIENT); + INSTANCE.registerMessage(KnockdownsClientPacketHandler.SynchronizePlayerData.Full.class, SynchronizePlayerDataS2CPacket.Full.class, packetId++, Side.CLIENT); + INSTANCE.registerMessage(KnockdownsClientPacketHandler.SynchronizeRevivers.Add.class, SynchronizeReviversS2CPacket.Add.class, packetId++, Side.CLIENT); + INSTANCE.registerMessage(KnockdownsClientPacketHandler.SynchronizeRevivers.Remove.class, SynchronizeReviversS2CPacket.Remove.class, packetId++, Side.CLIENT); + } + + @SideOnly(Side.CLIENT) + public static void sendToServer(IMessage message) { + INSTANCE.sendToServer(message); + } + + public static void sendToAll(IMessage message) { + INSTANCE.sendToAll(message); + } + + public static void sendToDimension(IMessage message, MessageContext context) { + INSTANCE.sendToDimension(message, context.getServerHandler().player.dimension); + } + + public static void sendToTrackingAndSelf(IMessage message, EntityPlayerMP player) { + sendToPlayer(message, player); + sendToTracking(message, player); + } + + public static void sendToMultiple(IMessage message, List players, EntityPlayerMP player) { + sendToPlayer(message, player); + for (EntityPlayer listed : players) { + sendToPlayer(message, (EntityPlayerMP) listed); + } + } + + public static void sendToTracking(IMessage message, Entity entity) { + INSTANCE.sendToAllTracking(message, entity); + } + + public static void sendToPlayer(IMessage message, EntityPlayerMP player) { + INSTANCE.sendTo(message, player); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/network/KnockdownsServerPacketHandler.java b/src/main/java/ru/octol1ttle/knockdowns/common/network/KnockdownsServerPacketHandler.java new file mode 100644 index 0000000..d73f461 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/network/KnockdownsServerPacketHandler.java @@ -0,0 +1,51 @@ +package ru.octol1ttle.knockdowns.common.network; + +import java.util.List; +import java.util.Map; +import net.minecraft.entity.player.EntityPlayer; +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; +import ru.octol1ttle.knockdowns.common.ReviverTracker; +import ru.octol1ttle.knockdowns.common.network.packets.c2s.CancelReviveC2SPacket; +import ru.octol1ttle.knockdowns.common.network.packets.c2s.PlayerCalloutC2SPacket; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.PlayerCalloutS2CPacket; +import ru.octol1ttle.knockdowns.common.network.packets.s2c.SynchronizeReviversS2CPacket; + +public class KnockdownsServerPacketHandler { + public static class Callout implements IMessageHandler { + @Override + public IMessage onMessage(PlayerCalloutC2SPacket message, MessageContext ctx) { + KnockdownsNetwork.sendToDimension( + new PlayerCalloutS2CPacket( + ctx.getServerHandler().player.getEntityId(), + ctx.getServerHandler().player.getPositionEyes(1).add(0, 1, 0), + message.type + ), + ctx + ); + return null; + } + } + + public static class CancelRevive implements IMessageHandler { + @Override + public IMessage onMessage(CancelReviveC2SPacket message, MessageContext ctx) { + EntityPlayerMP player = ctx.getServerHandler().player; + for (Map.Entry> revivers : ReviverTracker.getAllRevivers()) { + if (revivers.getValue().contains(player)) { + revivers.getValue().remove(player); + KnockdownsNetwork.sendToMultiple( + new SynchronizeReviversS2CPacket.Remove(revivers.getKey().getEntityId(), player.getEntityId()), + revivers.getValue(), + (EntityPlayerMP) revivers.getKey() + ); + break; + } + } + + return null; + } + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/c2s/CancelReviveC2SPacket.java b/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/c2s/CancelReviveC2SPacket.java new file mode 100644 index 0000000..dce5e9a --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/c2s/CancelReviveC2SPacket.java @@ -0,0 +1,17 @@ +package ru.octol1ttle.knockdowns.common.network.packets.c2s; + +import io.netty.buffer.ByteBuf; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; + +public class CancelReviveC2SPacket implements IMessage { + public CancelReviveC2SPacket() { + } + + @Override + public void toBytes(ByteBuf buf) { + } + + @Override + public void fromBytes(ByteBuf buf) { + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/c2s/PlayerCalloutC2SPacket.java b/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/c2s/PlayerCalloutC2SPacket.java new file mode 100644 index 0000000..580d5fa --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/c2s/PlayerCalloutC2SPacket.java @@ -0,0 +1,25 @@ +package ru.octol1ttle.knockdowns.common.network.packets.c2s; + +import io.netty.buffer.ByteBuf; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import ru.octol1ttle.knockdowns.common.communication.CalloutType; + +public class PlayerCalloutC2SPacket implements IMessage { + public PlayerCalloutC2SPacket() { + } + + public CalloutType type; + public PlayerCalloutC2SPacket(CalloutType type) { + this.type = type; + } + + @Override + public void toBytes(ByteBuf buf) { + buf.writeByte(this.type.getId()); + } + + @Override + public void fromBytes(ByteBuf buf) { + this.type = CalloutType.byId(buf.readByte()); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/s2c/PlayerCalloutS2CPacket.java b/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/s2c/PlayerCalloutS2CPacket.java new file mode 100644 index 0000000..34752dd --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/s2c/PlayerCalloutS2CPacket.java @@ -0,0 +1,37 @@ +package ru.octol1ttle.knockdowns.common.network.packets.s2c; + +import io.netty.buffer.ByteBuf; +import net.minecraft.util.math.Vec3d; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; +import ru.octol1ttle.knockdowns.common.communication.CalloutType; + +public class PlayerCalloutS2CPacket implements IMessage { + public PlayerCalloutS2CPacket() { + } + + public int playerId; + public Vec3d position; + public CalloutType type; + + public PlayerCalloutS2CPacket(int playerId, Vec3d position, CalloutType type) { + this.playerId = playerId; + this.position = position; + this.type = type; + } + + @Override + public void toBytes(ByteBuf buf) { + buf.writeInt(this.playerId); + buf.writeDouble(this.position.x); + buf.writeDouble(this.position.y); + buf.writeDouble(this.position.z); + buf.writeByte(this.type.getId()); + } + + @Override + public void fromBytes(ByteBuf buf) { + this.playerId = buf.readInt(); + this.position = new Vec3d(buf.readDouble(), buf.readDouble(), buf.readDouble()); + this.type = CalloutType.byId(buf.readByte()); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/s2c/PlayerKnockedDownS2CPacket.java b/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/s2c/PlayerKnockedDownS2CPacket.java new file mode 100644 index 0000000..017bb4a --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/s2c/PlayerKnockedDownS2CPacket.java @@ -0,0 +1,36 @@ +package ru.octol1ttle.knockdowns.common.network.packets.s2c; + +import io.netty.buffer.ByteBuf; +import net.minecraft.util.math.Vec3d; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; + +public class PlayerKnockedDownS2CPacket implements IMessage { + public PlayerKnockedDownS2CPacket() { + } + + public int playerId; + public int dimensionId; + public Vec3d position; + + public PlayerKnockedDownS2CPacket(int playerId, int dimensionId, Vec3d position) { + this.playerId = playerId; + this.dimensionId = dimensionId; + this.position = position; + } + + @Override + public void toBytes(ByteBuf buf) { + buf.writeInt(this.playerId); + buf.writeInt(this.dimensionId); + buf.writeDouble(this.position.x); + buf.writeDouble(this.position.y); + buf.writeDouble(this.position.z); + } + + @Override + public void fromBytes(ByteBuf buf) { + this.playerId = buf.readInt(); + this.dimensionId = buf.readInt(); + this.position = new Vec3d(buf.readDouble(), buf.readDouble(), buf.readDouble()); + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/s2c/SynchronizePlayerDataS2CPacket.java b/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/s2c/SynchronizePlayerDataS2CPacket.java new file mode 100644 index 0000000..c5846ab --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/s2c/SynchronizePlayerDataS2CPacket.java @@ -0,0 +1,85 @@ +package ru.octol1ttle.knockdowns.common.network.packets.s2c; + +import io.netty.buffer.ByteBuf; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; + +public class SynchronizePlayerDataS2CPacket { + public static class KnockedDown implements IMessage { + public KnockedDown() { + } + + public int playerId; + public boolean knockedDown; + + public KnockedDown(int playerId, boolean knockedDown) { + this.playerId = playerId; + this.knockedDown = knockedDown; + } + + @Override + public void toBytes(ByteBuf buf) { + buf.writeInt(this.playerId); + buf.writeBoolean(this.knockedDown); + } + + @Override + public void fromBytes(ByteBuf buf) { + this.playerId = buf.readInt(); + this.knockedDown = buf.readBoolean(); + } + } + + public static class ReviveTimeLeft implements IMessage { + public ReviveTimeLeft() { + } + + public int playerId; + public int reviveTimeLeft; + + public ReviveTimeLeft(int playerId, int reviveTimeLeft) { + this.playerId = playerId; + this.reviveTimeLeft = reviveTimeLeft; + } + + @Override + public void toBytes(ByteBuf buf) { + buf.writeInt(this.playerId); + buf.writeInt(this.reviveTimeLeft); + } + + @Override + public void fromBytes(ByteBuf buf) { + this.playerId = buf.readInt(); + this.reviveTimeLeft = buf.readInt(); + } + } + + public static class Full implements IMessage { + public Full() { + } + + public int playerId; + public boolean knockedDown; + public int reviveTimeLeft; + + public Full(int playerId, boolean knockedDown, int reviveTimeLeft) { + this.playerId = playerId; + this.knockedDown = knockedDown; + this.reviveTimeLeft = reviveTimeLeft; + } + + @Override + public void toBytes(ByteBuf buf) { + buf.writeInt(this.playerId); + buf.writeBoolean(this.knockedDown); + buf.writeInt(this.reviveTimeLeft); + } + + @Override + public void fromBytes(ByteBuf buf) { + this.playerId = buf.readInt(); + this.knockedDown = buf.readBoolean(); + this.reviveTimeLeft = buf.readInt(); + } + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/s2c/SynchronizeReviversS2CPacket.java b/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/s2c/SynchronizeReviversS2CPacket.java new file mode 100644 index 0000000..480c701 --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/s2c/SynchronizeReviversS2CPacket.java @@ -0,0 +1,56 @@ +package ru.octol1ttle.knockdowns.common.network.packets.s2c; + +import io.netty.buffer.ByteBuf; +import net.minecraftforge.fml.common.network.simpleimpl.IMessage; + +public class SynchronizeReviversS2CPacket { + public static class Add implements IMessage { + public Add() { + } + + public int knockedId; + public int reviverId; + + public Add(int knockedId, int reviverId) { + this.knockedId = knockedId; + this.reviverId = reviverId; + } + + @Override + public void toBytes(ByteBuf buf) { + buf.writeInt(this.knockedId); + buf.writeInt(this.reviverId); + } + + @Override + public void fromBytes(ByteBuf buf) { + this.knockedId = buf.readInt(); + this.reviverId = buf.readInt(); + } + } + + public static class Remove implements IMessage { + public Remove() { + } + + public int knockedId; + public int reviverId; + + public Remove(int knockedId, int reviverId) { + this.knockedId = knockedId; + this.reviverId = reviverId; + } + + @Override + public void toBytes(ByteBuf buf) { + buf.writeInt(this.knockedId); + buf.writeInt(this.reviverId); + } + + @Override + public void fromBytes(ByteBuf buf) { + this.knockedId = buf.readInt(); + this.reviverId = buf.readInt(); + } + } +} diff --git a/src/main/java/ru/octol1ttle/knockdowns/common/registry/KnockdownsSoundEvents.java b/src/main/java/ru/octol1ttle/knockdowns/common/registry/KnockdownsSoundEvents.java new file mode 100644 index 0000000..c90437d --- /dev/null +++ b/src/main/java/ru/octol1ttle/knockdowns/common/registry/KnockdownsSoundEvents.java @@ -0,0 +1,17 @@ +package ru.octol1ttle.knockdowns.common.registry; + +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.SoundEvent; +import ru.octol1ttle.knockdowns.Tags; + +public class KnockdownsSoundEvents { + public static final SoundEvent CALLOUT; + public static final SoundEvent KNOCKED_DOWN; + static { + ResourceLocation callout = new ResourceLocation(Tags.MOD_ID, "callout"); + CALLOUT = new SoundEvent(callout).setRegistryName(callout); + + ResourceLocation knockedDown = new ResourceLocation(Tags.MOD_ID, "knocked_down"); + KNOCKED_DOWN = new SoundEvent(knockedDown).setRegistryName(knockedDown); + } +} diff --git a/src/main/resources/assets/knockdowns/lang/ru_ru.lang b/src/main/resources/assets/knockdowns/lang/ru_ru.lang new file mode 100644 index 0000000..a45d2e2 --- /dev/null +++ b/src/main/resources/assets/knockdowns/lang/ru_ru.lang @@ -0,0 +1,64 @@ +knockdowns.key.category=Knockdowns +knockdowns.key.callout.danger="Опасность!" +knockdowns.key.callout.booyah="Йо-хо!" +knockdowns.key.callout.this_way_help="Сюда!" ("SOS!" когда тяжело ранен) +knockdowns.key.callout.ouch="Непруха!" + +knockdowns.callout.danger=Опасность! +knockdowns.callout.booyah=Йо-хо! +knockdowns.callout.this_way=Сюда! +knockdowns.callout.ouch=Непруха! +knockdowns.callout.help=SOS! + +knockdowns.subtitles.callout=Игрок зовёт союзников +knockdowns.subtitles.knocked_down=Игрок тяжело ранен + +knockdown.attack.anvil=%1$s тяжело ранен упавшей наковальней +knockdown.attack.arrow=%1$s тяжело ранен стрелой %2$s +knockdown.attack.arrow.item=%1$s тяжело ранен стрелой %2$s с помощью %3$s +knockdown.attack.cactus=%1$s исколот до тяжелого ранения +knockdown.attack.cactus.player=%1$s тяжело ранен кактусом, спасаясь от %2$s +knockdown.attack.cramming=%1$s расплющен до тяжелого ранения +knockdown.attack.dragonBreath=%1$s тяжело ранен в драконьем дыхании +knockdown.attack.drown=%1$s тяжело ранен от нехватки воздуха +knockdown.attack.drown.player=%1$s тяжело ранен от нехватки воздуха, спасаясь от %2$s +knockdown.attack.explosion=%1$s тяжело ранен взрывом +knockdown.attack.explosion.player=%1$s был тяжело ранен взрывом %2$s +knockdown.attack.fall=%1$s разбился до тяжелого ранения +knockdown.attack.fallingBlock=%1$s тяжело ранен упавшим блоком +knockdown.attack.fireball=%1$s тяжело ранен файерболом %2$s +knockdown.attack.fireball.item=%1$s тяжело ранен файерболом %2$s с помощью %3$s +knockdown.attack.fireworks=%1$s с треском тяжело ранен +knockdown.attack.flyIntoWall=%1$s преобразовал кинетическую энергию в тяжелое ранение +knockdown.attack.generic=%1$s тяжело ранен +knockdown.attack.hotFloor=%1$s тяжело ранен, обнаружив под ногами лаву +knockdown.attack.hotFloor.player=%1$s зашёл в опасную зону тяжелого ранения из-за %2$s +knockdown.attack.inFire=%1$s сгорел до тяжелого ранения +knockdown.attack.inFire.player=%1$s тяжело ранен в огне, борясь с %2$s +knockdown.attack.inWall=%1$s погребён до тяжелого ранения +knockdown.attack.indirectMagic=%1$s был тяжело ранен %2$s с помощью магии +knockdown.attack.indirectMagic.item=%1$s был тяжело ранен %2$s с помощью %3$s +knockdown.attack.lava=%1$s решил получить тяжелое ранение в лаве +knockdown.attack.lava.player=%1$s получил тяжелое ранение от лавы, убегая от %2$s +knockdown.attack.lightningBolt=%1$s был тяжело ранен поражением молнией +knockdown.attack.magic=%1$s был тяжело ранен магией +knockdown.attack.mob=%1$s был тяжело ранен %2$s +knockdown.attack.onFire=%1$s сгорел до тяжелого ранения +knockdown.attack.onFire.player=%1$s был сожжён до тяжелого ранения, пока боролся с %2$s +knockdown.attack.outOfWorld=%1$s тяжело ранен отсутствием земли под собой +knockdown.attack.player=%1$s был тяжело ранен %2$s +knockdown.attack.player.item=%1$s был тяжело ранен %2$s с помощью %3$s +knockdown.attack.starve=%1$s тяжело ранен от голода +knockdown.attack.thorns=%1$s был тяжело ранен, пытаясь навредить %2$s +knockdown.attack.thrown=%1$s был избит до тяжелого ранения %2$s +knockdown.attack.thrown.item=%1$s был избит до тяжелого ранения %2$s с помощью %3$s +knockdown.attack.wither=%1$s тяжело ранен иссушением +knockdown.fell.accident.generic=%1$s разбился до тяжелого ранения +knockdown.fell.accident.ladder=%1$s свалился с лестницы и был тяжело ранен +knockdown.fell.accident.vines=%1$s сорвался с лианы и был тяжело ранен +knockdown.fell.accident.water=%1$s выпал из воды и был тяжело ранен +knockdown.fell.assist=%1$s свалился и был тяжело ранен благодаря %2$s +knockdown.fell.assist.item=%1$s был обречён на тяжелое ранение %2$s с помощью %3$s +knockdown.fell.finish=%1$s упал с высоты и был тяжело ранен %2$s +knockdown.fell.finish.item=%1$s упал с высоты и был тяжело ранен %2$s с помощью %3$s +knockdown.fell.killer=%1$s был обречён на тяжелое ранение diff --git a/src/main/resources/mixins.modid.json b/src/main/resources/mixins.knockdowns.json similarity index 58% rename from src/main/resources/mixins.modid.json rename to src/main/resources/mixins.knockdowns.json index ffbbdee..7e58f0a 100644 --- a/src/main/resources/mixins.modid.json +++ b/src/main/resources/mixins.knockdowns.json @@ -1,11 +1,11 @@ { - "package": "", + "package": "ru.octol1ttle.knockdowns.common.mixins", "required": true, "refmap": "${mixin_refmap}", "target": "@env(DEFAULT)", "minVersion": "0.8.5", "compatibilityLevel": "JAVA_8", - "mixins": [], + "mixins": [ "EntityLivingBaseMixin", "EntityPlayerMixin" ], "server": [], "client": [] -} \ No newline at end of file +}