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
+}