Compare commits

...

No commits in common. "ac3d92fc97a2020e95c07fa365f1213a85e38f63" and "34ea071e5fc67cfa6030916a9927a1590661066b" have entirely different histories.

54 changed files with 1188 additions and 852 deletions

View file

@ -1,40 +0,0 @@
# Automatically build the project and run any configured tests for every push
# and submitted pull request. This can help catch issues that only occur on
# certain platforms or Java versions, and provides a first line of defence
# against bad commits.
name: build
on: [pull_request, push]
jobs:
build:
strategy:
matrix:
# Use these Java versions
java: [
17, # Current Java LTS & minimum supported by Minecraft
]
# and run on both Linux and Windows
os: [ubuntu-22.04, windows-2022]
runs-on: ${{ matrix.os }}
steps:
- name: checkout repository
uses: actions/checkout@v3
- name: validate gradle wrapper
uses: gradle/wrapper-validation-action@v1
- name: setup jdk ${{ matrix.java }}
uses: actions/setup-java@v3
with:
java-version: ${{ matrix.java }}
distribution: 'microsoft'
- name: make gradle wrapper executable
if: ${{ runner.os != 'Windows' }}
run: chmod +x ./gradlew
- name: build
run: ./gradlew build
- name: capture build artifacts
if: ${{ runner.os == 'Linux' && matrix.java == '17' }} # Only upload artifacts built from latest java on one OS
uses: actions/upload-artifact@v3
with:
name: Artifacts
path: build/libs/

47
.gitignore vendored
View file

@ -1,40 +1,19 @@
# gradle
.gradle/
build/
out/
classes/
# eclipse
*.launch
# idea
.idea/
*.iml
*.ipr
run/
*.iws
# vscode
.settings/
.vscode/
out/
*.iml
.gradle/
output/
bin/
libs/
.classpath
.project
# macos
*.DS_Store
# fabric
run/
# java
hs_err_*.log
replay_*.log
*.hprof
*.jfr
.idea/
classes/
.metadata
.vscode
.settings
*.launch

121
LICENSE
View file

@ -1,121 +0,0 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.

View file

@ -1,88 +1,52 @@
plugins {
id 'fabric-loom' version '1.4-SNAPSHOT'
id 'maven-publish'
id "architectury-plugin" version "3.4-SNAPSHOT"
id "dev.architectury.loom" version "1.4-SNAPSHOT" apply false
}
version = project.mod_version
group = project.maven_group
base {
archivesName = project.archives_base_name
architectury {
minecraft = rootProject.minecraft_version
}
repositories {
// Add repositories to retrieve artifacts from in here.
// You should only use this when depending on other mods because
// Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
// See https://docs.gradle.org/current/userguide/declaring_repositories.html
// for more information about repositories.
subprojects {
apply plugin: "dev.architectury.loom"
loom {
silentMojangMappingsLicense()
}
dependencies {
minecraft "com.mojang:minecraft:${rootProject.minecraft_version}"
// The following line declares the yarn mappings you may select this one as well.
mappings "net.fabricmc:yarn:1.20.1+build.10:v2"
}
}
loom {
splitEnvironmentSourceSets()
allprojects {
apply plugin: "java"
apply plugin: "architectury-plugin"
apply plugin: "maven-publish"
mods {
"knockdowns" {
sourceSet sourceSets.main
sourceSet sourceSets.client
}
}
base {
archivesName = rootProject.archives_base_name
}
version = rootProject.mod_version
group = rootProject.maven_group
repositories {
// Add repositories to retrieve artifacts from in here.
// You should only use this when depending on other mods because
// Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
// See https://docs.gradle.org/current/userguide/declaring_repositories.html
// for more information about repositories.
}
tasks.withType(JavaCompile).configureEach {
options.encoding = "UTF-8"
options.release = 17
}
java {
withSourcesJar()
}
}
dependencies {
// To change the versions see the gradle.properties file
minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
// Fabric API. This is technically optional, but you probably want it anyway.
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
include(implementation(annotationProcessor("io.github.llamalad7:mixinextras-fabric:0.2.0")))
}
processResources {
inputs.property "version", project.version
filesMatching("fabric.mod.json") {
expand "version": project.version
}
}
tasks.withType(JavaCompile).configureEach {
it.options.release = 17
}
java {
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
// if it is present.
// If you remove this line, sources will not be generated.
withSourcesJar()
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
jar {
from("LICENSE") {
rename { "${it}_${project.base.archivesName.get()}"}
}
}
// configure the maven publication
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
}
}
// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
repositories {
// Add repositories to publish to here.
// Notice: This block does NOT have the same function as the block in the top level.
// The repositories here will be used for publishing your artifact, not for
// retrieving dependencies.
}
}

31
common/build.gradle Normal file
View file

@ -0,0 +1,31 @@
architectury {
common(rootProject.enabled_platforms.split(","))
}
loom {
accessWidenerPath = file("src/main/resources/knockdowns.accesswidener")
}
dependencies {
// We depend on fabric loader here to use the fabric @Environment annotations and get the mixin dependencies
// Do NOT use other classes from fabric loader
modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}"
// Remove the next line if you don't want to depend on the API
modApi "dev.architectury:architectury:${rootProject.architectury_version}"
compileOnly(annotationProcessor("io.github.llamalad7:mixinextras-common:0.3.3"))
}
publishing {
publications {
mavenCommon(MavenPublication) {
artifactId = rootProject.archives_base_name
from components.java
}
}
// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
repositories {
// Add repositories to publish to here.
}
}

View file

@ -0,0 +1,63 @@
package ru.octol1ttle.knockdowns.common;
import dev.architectury.event.EventResult;
import net.minecraft.client.MinecraftClient;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.hit.EntityHitResult;
import net.minecraft.util.hit.HitResult;
import net.minecraft.util.math.Vec3d;
import org.jetbrains.annotations.Nullable;
import ru.octol1ttle.knockdowns.common.api.IKnockableDown;
import ru.octol1ttle.knockdowns.common.events.KnockdownsClientEvents;
import ru.octol1ttle.knockdowns.common.network.KnockdownsNetwork;
import ru.octol1ttle.knockdowns.common.network.packets.RequestStartRevivingC2SPacket;
import ru.octol1ttle.knockdowns.common.network.packets.StopRevivingC2SPacket;
import ru.octol1ttle.knockdowns.common.registries.KnockdownsSoundEvents;
import ru.octol1ttle.knockdowns.common.registries.KnockedDownSoundInstance;
public class KnockdownsClient {
@Nullable
public static Entity reviving;
public static void init() {
KnockdownsClientEvents.registerCallbacks();
}
public static void playKnockedDownSound(Vec3d pos) {
MinecraftClient.getInstance().getSoundManager().play(
new KnockedDownSoundInstance(KnockdownsSoundEvents.KNOCKED_DOWN.get(), pos)
);
}
public static EventResult onEntityUse(PlayerEntity player, Entity entity) {
if (KnockdownsUtils.isKnockedOrReviving(player) || !(entity instanceof IKnockableDown knockable) || !knockable.is_KnockedDown()) {
return EventResult.pass();
}
KnockdownsNetwork.sendToServer(new RequestStartRevivingC2SPacket(entity.getUuid()));
reviving = entity;
return EventResult.interruptTrue();
}
public static void onPlayerTick(PlayerEntity player) {
MinecraftClient client = MinecraftClient.getInstance();
if (!player.equals(client.player) || reviving == null) {
return;
}
boolean playerKnocked = ((IKnockableDown) player).is_KnockedDown();
boolean revivingTargeted = client.crosshairTarget != null && client.crosshairTarget.getType() == HitResult.Type.ENTITY
&& ((EntityHitResult) client.crosshairTarget).getEntity().equals(reviving);
if (!(reviving instanceof IKnockableDown knockable)) {
return;
}
if (!knockable.is_KnockedDown() || playerKnocked || !revivingTargeted) {
KnockdownsNetwork.sendToServer(new StopRevivingC2SPacket(reviving.getUuid()));
reviving = null;
}
}
}

View file

@ -0,0 +1,17 @@
package ru.octol1ttle.knockdowns.common;
import net.minecraft.SharedConstants;
import ru.octol1ttle.knockdowns.common.events.KnockdownsEvents;
import ru.octol1ttle.knockdowns.common.network.KnockdownsNetwork;
import ru.octol1ttle.knockdowns.common.registries.KnockdownsSoundEvents;
public class KnockdownsCommon {
public static final String MOD_ID = "knockdowns";
public static final int REVIVE_WAIT_TIME = 10 * SharedConstants.TICKS_PER_SECOND;
public static void init() {
KnockdownsSoundEvents.register();
KnockdownsNetwork.registerPackets();
KnockdownsEvents.registerCallbacks();
}
}

View file

@ -0,0 +1,31 @@
package ru.octol1ttle.knockdowns.common;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.MinecraftServer;
import ru.octol1ttle.knockdowns.common.api.IKnockableDown;
public class KnockdownsUtils {
public static boolean isKnockedOrReviving(PlayerEntity player) {
return player instanceof IKnockableDown knockable && (knockable.is_KnockedDown() || knockable.is_Reviving());
}
public static boolean allTeammatesKnocked(MinecraftServer server, PlayerEntity player) {
for (PlayerEntity teammate : server.getPlayerManager().getPlayerList()) {
if (teammate.equals(player)) {
continue;
}
IKnockableDown knockable = (IKnockableDown) teammate;
if (!knockable.is_KnockedDown() && !player.isDead()) {
return false;
}
}
return true;
}
public static void hurtTenacity(PlayerEntity player, float damage) {
player.setInvulnerable(false);
//DamageSource recent = player.getRecentDamageSource();
player.damage(/*Objects.requireNonNullElse(recent, */player.getDamageSources().generic()/*)*/, damage);
player.velocityModified = false;
}
}

View file

@ -0,0 +1,23 @@
package ru.octol1ttle.knockdowns.common.api;
public interface IKnockableDown {
boolean is_KnockedDown();
void set_KnockedDown(boolean knockedDown);
int get_ReviverCount();
void set_ReviverCount(int reviverCount);
boolean is_Reviving();
void set_Reviving(boolean reviving);
int get_ReviveTimer();
void set_ReviveTimer(int reviveTimer);
int get_KnockedAge();
void set_KnockedAge(int knockedAge);
}

View file

@ -0,0 +1,44 @@
package ru.octol1ttle.knockdowns.common.events;
import dev.architectury.event.events.client.ClientGuiEvent;
import net.minecraft.SharedConstants;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.util.Formatting;
import ru.octol1ttle.knockdowns.common.KnockdownsClient;
import ru.octol1ttle.knockdowns.common.api.IKnockableDown;
public class KnockdownsClientEvents {
public static void registerCallbacks() {
registerOnHudRender();
}
private static void registerOnHudRender() {
ClientGuiEvent.RENDER_HUD.register((drawContext, tickDelta) -> {
IKnockableDown reviving = (IKnockableDown) KnockdownsClient.reviving;
MinecraftClient client = MinecraftClient.getInstance();
if (reviving == null) {
reviving = (IKnockableDown) client.player;
if (reviving == null || reviving.get_ReviverCount() == 0) {
return;
}
}
TextRenderer renderer = client.textRenderer;
String timerText = String.format("%.1f", reviving.get_ReviveTimer() / (float) SharedConstants.TICKS_PER_SECOND);
int timerX = (drawContext.getScaledWindowWidth() - renderer.getWidth(timerText)) / 2;
int reviverCount = reviving.get_ReviverCount();
Integer color = reviverCount > 1 ? Formatting.GREEN.getColorValue() : Formatting.WHITE.getColorValue();
String reviverCountText = "x" + reviverCount;
int reviveCountX = (drawContext.getScaledWindowWidth() - renderer.getWidth(reviverCountText)) / 2;
if (color != null) {
drawContext.drawTextWithShadow(renderer, timerText, timerX, drawContext.getScaledWindowHeight() / 2 + 5, color);
drawContext.drawTextWithShadow(renderer, reviverCountText, reviveCountX, drawContext.getScaledWindowHeight() / 2 + 14, color);
}
});
}
}

View file

@ -0,0 +1,155 @@
package ru.octol1ttle.knockdowns.common.events;
import dev.architectury.event.CompoundEventResult;
import dev.architectury.event.EventResult;
import dev.architectury.event.events.common.EntityEvent;
import dev.architectury.event.events.common.InteractionEvent;
import dev.architectury.event.events.common.PlayerEvent;
import dev.architectury.event.events.common.TickEvent;
import net.minecraft.SharedConstants;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.text.TranslatableTextContent;
import net.minecraft.util.Hand;
import net.minecraft.util.math.MathHelper;
import ru.octol1ttle.knockdowns.common.KnockdownsClient;
import ru.octol1ttle.knockdowns.common.KnockdownsCommon;
import ru.octol1ttle.knockdowns.common.KnockdownsUtils;
import ru.octol1ttle.knockdowns.common.api.IKnockableDown;
import ru.octol1ttle.knockdowns.common.network.KnockdownsNetwork;
import ru.octol1ttle.knockdowns.common.network.packets.PlayKnockedDownSoundS2CPacket;
public class KnockdownsEvents {
private static final float KNOCKED_INVULNERABILITY_TICKS = 3.0f * SharedConstants.TICKS_PER_SECOND;
private static final float KNOCKED_HURT_PERIOD = 1.2f;
private static final float KNOCKED_TENACITY = 60.0f;
public static void registerCallbacks() {
registerOnLivingDeath();
registerOnPlayerTick();
registerOnPlayerInteractions();
registerOnEntityUse();
}
private static void registerOnLivingDeath() {
EntityEvent.LIVING_DEATH.register((entity, source) -> {
MinecraftServer server = entity.getServer();
if (server == null || !(entity instanceof IKnockableDown knockable)) {
return EventResult.pass();
}
ServerPlayerEntity player = (ServerPlayerEntity) entity;
if (KnockdownsUtils.allTeammatesKnocked(server, player)) {
return EventResult.pass();
}
if (knockable.is_KnockedDown()) {
knockable.set_KnockedDown(false);
knockable.set_ReviverCount(0);
knockable.set_ReviveTimer(KnockdownsCommon.REVIVE_WAIT_TIME);
knockable.set_KnockedAge(0);
return EventResult.pass();
}
entity.clearStatusEffects();
entity.setInvulnerable(true);
entity.setGlowing(true);
entity.setHealth(entity.getMaxHealth());
entity.extinguish();
entity.setAir(entity.getMaxAir());
entity.setFrozenTicks(0);
player.stopFallFlying();
knockable.set_KnockedDown(true);
knockable.set_ReviveTimer(KnockdownsCommon.REVIVE_WAIT_TIME);
knockable.set_KnockedAge(0);
KnockdownsNetwork.sendToWorld(player.getServerWorld(), new PlayKnockedDownSoundS2CPacket(player.getX(), player.getY(), player.getZ()));
Text deathMessage = entity.getDamageTracker().getDeathMessage();
TranslatableTextContent content = (TranslatableTextContent) deathMessage.getContent();
Text replaced = Text.translatableWithFallback(content.getKey().replace("death.", "knockdown."), deathMessage.getString(), content.getArgs());
server.getPlayerManager().broadcast(replaced, false);
return EventResult.interruptFalse();
});
}
private static void registerOnPlayerTick() {
TickEvent.PLAYER_POST.register(player -> {
MinecraftServer server = player.getServer();
if (server == null) {
KnockdownsClient.onPlayerTick(player);
return;
}
if (!(player instanceof IKnockableDown knockable) || !knockable.is_KnockedDown()) {
return;
}
if (KnockdownsUtils.allTeammatesKnocked(server, player)) {
KnockdownsUtils.hurtTenacity(player, player.getMaxHealth());
return;
}
if (knockable.get_ReviverCount() > 0) {
knockable.set_ReviveTimer(knockable.get_ReviveTimer() - knockable.get_ReviverCount());
if (knockable.get_ReviveTimer() <= 0) {
knockable.set_KnockedDown(false);
knockable.set_ReviverCount(0);
knockable.set_ReviveTimer(KnockdownsCommon.REVIVE_WAIT_TIME);
knockable.set_KnockedAge(0);
player.setInvulnerable(false);
player.setGlowing(false);
player.setHealth(6.0f);
}
return;
}
knockable.set_ReviveTimer(Math.min(KnockdownsCommon.REVIVE_WAIT_TIME, knockable.get_ReviveTimer() + 1));
knockable.set_KnockedAge(knockable.get_KnockedAge() + 1);
int period = MathHelper.floor(KNOCKED_HURT_PERIOD * SharedConstants.TICKS_PER_SECOND);
if (knockable.get_KnockedAge() >= KNOCKED_INVULNERABILITY_TICKS && knockable.get_KnockedAge() % period == 0) {
KnockdownsUtils.hurtTenacity(player, player.getMaxHealth() / (KNOCKED_TENACITY / KNOCKED_HURT_PERIOD));
}
});
}
private static void registerOnPlayerInteractions() {
InteractionEvent.LEFT_CLICK_BLOCK.register((player, hand, pos, direction) -> {
if (KnockdownsUtils.isKnockedOrReviving(player)) {
return EventResult.interruptFalse();
}
return EventResult.pass();
});
PlayerEvent.ATTACK_ENTITY.register((player, world, hand, entity, hitResult) -> {
if (KnockdownsUtils.isKnockedOrReviving(player)) {
return EventResult.interruptFalse();
}
return EventResult.pass();
});
InteractionEvent.RIGHT_CLICK_ITEM.register((player, hand) -> {
if (KnockdownsUtils.isKnockedOrReviving(player)) {
return CompoundEventResult.interruptFalse(hand == Hand.MAIN_HAND ? player.getMainHandStack() : player.getOffHandStack());
}
return CompoundEventResult.pass();
});
InteractionEvent.RIGHT_CLICK_BLOCK.register((player, hand, pos, direction) -> {
if (KnockdownsUtils.isKnockedOrReviving(player)) {
return EventResult.interruptFalse();
}
return EventResult.pass();
});
}
private static void registerOnEntityUse() {
InteractionEvent.INTERACT_ENTITY.register((player, entity, hand)
-> player.getWorld().isClient() ? KnockdownsClient.onEntityUse(player, entity) : EventResult.pass());
}
}

View file

@ -0,0 +1,15 @@
package ru.octol1ttle.knockdowns.common.mixin;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import net.minecraft.entity.LivingEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import ru.octol1ttle.knockdowns.common.api.IKnockableDown;
@Mixin(LivingEntity.class)
public abstract class LivingEntityMixin {
@ModifyReturnValue(method = "canTarget(Lnet/minecraft/entity/LivingEntity;)Z", at = @At("RETURN"))
private boolean dontTargetKnockedPlayers(boolean original, LivingEntity target) {
return original && !(target instanceof IKnockableDown knockable && knockable.is_KnockedDown());
}
}

View file

@ -0,0 +1,19 @@
package ru.octol1ttle.knockdowns.common.mixin;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.mob.MobEntity;
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.CallbackInfo;
import ru.octol1ttle.knockdowns.common.api.IKnockableDown;
@Mixin(MobEntity.class)
public abstract class MobEntityMixin {
@Inject(method = "setTarget", at = @At("HEAD"), cancellable = true)
private void setTarget(LivingEntity target, CallbackInfo ci) {
if (target instanceof IKnockableDown knockable && knockable.is_KnockedDown()) {
ci.cancel();
}
}
}

View file

@ -0,0 +1,129 @@
package ru.octol1ttle.knockdowns.common.mixin;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.data.DataTracker;
import net.minecraft.entity.data.TrackedData;
import net.minecraft.entity.data.TrackedDataHandlerRegistry;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import ru.octol1ttle.knockdowns.common.KnockdownsCommon;
import ru.octol1ttle.knockdowns.common.api.IKnockableDown;
@SuppressWarnings("WrongEntityDataParameterClass")
@Mixin(PlayerEntity.class)
public abstract class PlayerEntityMixin extends Entity implements IKnockableDown {
@Unique
private static final TrackedData<Boolean> KNOCKED_DOWN = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.BOOLEAN);
@Unique
private static final TrackedData<Boolean> IS_REVIVING = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.BOOLEAN);
@Unique
private static final TrackedData<Integer> REVIVER_COUNT = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.INTEGER);
@Unique
private static final TrackedData<Integer> REVIVE_TIMER = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.INTEGER);
@Unique
public int knocked_age;
private PlayerEntityMixin(EntityType<?> type, World world) {
super(type, world);
throw new AssertionError();
}
@ModifyExpressionValue(method = "updatePose", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/PlayerEntity;isSwimming()Z"))
private boolean enterSwimmingIfKnockedDown(boolean original) {
return original || this.is_KnockedDown();
}
@ModifyReturnValue(method = "canFoodHeal", at = @At("RETURN"))
private boolean dontHealIfKnockedDown(boolean original) {
return original && !this.is_KnockedDown();
}
@Inject(method = "checkFallFlying", at = @At("HEAD"), cancellable = true)
private void dontOpenElytraIfKnockedDown(CallbackInfoReturnable<Boolean> cir) {
if (this.is_KnockedDown()) {
cir.setReturnValue(false);
}
}
@Inject(method = "initDataTracker", at = @At("TAIL"))
private void initCustomDataTracker(CallbackInfo ci) {
this.dataTracker.startTracking(KNOCKED_DOWN, false);
this.dataTracker.startTracking(IS_REVIVING, false);
this.dataTracker.startTracking(REVIVER_COUNT, 0);
this.dataTracker.startTracking(REVIVE_TIMER, KnockdownsCommon.REVIVE_WAIT_TIME);
}
@Inject(method = "readCustomDataFromNbt", at = @At("TAIL"))
private void readKnockedDownFromNbt(NbtCompound nbt, CallbackInfo ci) {
this.set_KnockedDown(nbt.getBoolean("KnockedDown"));
this.set_ReviveTimer(nbt.getInt("ReviveTimer"));
this.set_KnockedAge(nbt.getInt("KnockedAge"));
}
@Inject(method = "writeCustomDataToNbt", at = @At("TAIL"))
private void writeKnockedDownToNbt(NbtCompound nbt, CallbackInfo ci) {
nbt.putBoolean("KnockedDown", this.is_KnockedDown());
nbt.putInt("ReviveTimer", this.get_ReviveTimer());
nbt.putInt("KnockedAge", this.get_KnockedAge());
}
@Override
public boolean is_KnockedDown() {
return this.dataTracker.get(KNOCKED_DOWN);
}
@Override
public void set_KnockedDown(boolean knockedDown) {
this.dataTracker.set(KNOCKED_DOWN, knockedDown);
}
@Override
public boolean is_Reviving() {
return this.dataTracker.get(IS_REVIVING);
}
@Override
public void set_Reviving(boolean reviving) {
this.dataTracker.set(IS_REVIVING, reviving);
}
@Override
public int get_ReviverCount() {
return this.dataTracker.get(REVIVER_COUNT);
}
@Override
public void set_ReviverCount(int reviverCount) {
this.dataTracker.set(REVIVER_COUNT, reviverCount);
}
@Override
public int get_ReviveTimer() {
return this.dataTracker.get(REVIVE_TIMER);
}
@Override
public void set_ReviveTimer(int reviveTimer) {
this.dataTracker.set(REVIVE_TIMER, reviveTimer);
}
@Override
public int get_KnockedAge() {
return knocked_age;
}
@Override
public void set_KnockedAge(int knockedAge) {
this.knocked_age = knockedAge;
}
}

View file

@ -1,15 +1,15 @@
package ru.octol1ttle.knockdowns.mixin.client;
package ru.octol1ttle.knockdowns.common.mixin.client;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import net.minecraft.client.network.ClientPlayerEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import ru.octol1ttle.knockdowns.api.IKnockableDown;
import ru.octol1ttle.knockdowns.common.api.IKnockableDown;
@Mixin(ClientPlayerEntity.class)
public abstract class ClientPlayerEntityMixin {
public abstract class ClientPlayerEntityMixin implements IKnockableDown {
@ModifyReturnValue(method = "shouldSlowDown", at = @At("RETURN"))
private boolean shouldSlowDown(boolean original) {
return original || ((IKnockableDown) this).knockdowns$isKnockedDown();
return original || this.is_KnockedDown();
}
}
}

View file

@ -0,0 +1,57 @@
package ru.octol1ttle.knockdowns.common.network;
import dev.architectury.networking.NetworkChannel;
import dev.architectury.networking.NetworkManager;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.packet.Packet;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier;
import ru.octol1ttle.knockdowns.common.KnockdownsCommon;
import ru.octol1ttle.knockdowns.common.network.packets.PlayKnockedDownSoundS2CPacket;
import ru.octol1ttle.knockdowns.common.network.packets.RequestStartRevivingC2SPacket;
import ru.octol1ttle.knockdowns.common.network.packets.StopRevivingC2SPacket;
public class KnockdownsNetwork {
private static final NetworkChannel CHANNEL = NetworkChannel.create(new Identifier(KnockdownsCommon.MOD_ID, "main"));
public static void registerPackets() {
CHANNEL.register(PlayKnockedDownSoundS2CPacket.class, PlayKnockedDownSoundS2CPacket::encode, PlayKnockedDownSoundS2CPacket::new, PlayKnockedDownSoundS2CPacket::apply);
CHANNEL.register(RequestStartRevivingC2SPacket.class, RequestStartRevivingC2SPacket::encode, RequestStartRevivingC2SPacket::new, RequestStartRevivingC2SPacket::apply);
CHANNEL.register(StopRevivingC2SPacket.class, StopRevivingC2SPacket::encode, StopRevivingC2SPacket::new, StopRevivingC2SPacket::apply);
}
public static <T> void sendToServer(T message) {
if (CHANNEL.canServerReceive(message.getClass())) {
CHANNEL.sendToServer(message);
}
}
public static <T> void sendToPlayer(PlayerEntity player, T message) {
Packet<?> packet = CHANNEL.toPacket(NetworkManager.Side.S2C, message);
Class<?> messageClass = message.getClass();
sendToPlayer(player, packet, messageClass);
}
public static void sendToPlayer(PlayerEntity player, Packet<?> packet, Class<?> messageClass) {
if (!(player instanceof ServerPlayerEntity serverPlayer)) {
throw new IllegalArgumentException("Cannot send to client players");
}
if (CHANNEL.canPlayerReceive(serverPlayer, messageClass)) {
serverPlayer.networkHandler.sendPacket(packet);
}
}
public static <T> void sendToWorld(ServerWorld world, T message) {
Packet<?> packet = CHANNEL.toPacket(NetworkManager.Side.S2C, message);
Class<?> messageClass = message.getClass();
sendToWorld(world, packet, messageClass);
}
private static void sendToWorld(ServerWorld world, Packet<?> packet, Class<?> messageClass) {
for (ServerPlayerEntity player : world.getPlayers()) {
sendToPlayer(player, packet, messageClass);
}
}
}

View file

@ -0,0 +1,15 @@
package ru.octol1ttle.knockdowns.common.network.packets;
import dev.architectury.networking.NetworkManager;
import java.util.function.Supplier;
import net.minecraft.network.PacketByteBuf;
public abstract class KnockdownsPacket {
public KnockdownsPacket(/* args here */) {
// Message creation
}
public abstract void encode(PacketByteBuf buf);
public abstract void apply(Supplier<NetworkManager.PacketContext> contextSupplier);
}

View file

@ -0,0 +1,36 @@
package ru.octol1ttle.knockdowns.common.network.packets;
import dev.architectury.networking.NetworkManager;
import java.util.function.Supplier;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.util.math.Vec3d;
import ru.octol1ttle.knockdowns.common.KnockdownsClient;
public class PlayKnockedDownSoundS2CPacket extends KnockdownsPacket {
private final double x;
private final double y;
private final double z;
public PlayKnockedDownSoundS2CPacket(PacketByteBuf buf) {
this(buf.readDouble(), buf.readDouble(), buf.readDouble());
}
public PlayKnockedDownSoundS2CPacket(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
@Override
public void encode(PacketByteBuf buf) {
buf.writeDouble(this.x);
buf.writeDouble(this.y);
buf.writeDouble(this.z);
}
@Override
public void apply(Supplier<NetworkManager.PacketContext> contextSupplier) {
NetworkManager.PacketContext context = contextSupplier.get();
context.queue(() -> KnockdownsClient.playKnockedDownSound(new Vec3d(this.x, this.y, this.z)));
}
}

View file

@ -0,0 +1,39 @@
package ru.octol1ttle.knockdowns.common.network.packets;
import dev.architectury.networking.NetworkManager;
import java.util.UUID;
import java.util.function.Supplier;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.PacketByteBuf;
import ru.octol1ttle.knockdowns.common.api.IKnockableDown;
public class RequestStartRevivingC2SPacket extends KnockdownsPacket {
private final UUID targetUuid;
public RequestStartRevivingC2SPacket(PacketByteBuf buf) {
this(buf.readUuid());
}
public RequestStartRevivingC2SPacket(UUID targetUuid) {
this.targetUuid = targetUuid;
}
@Override
public void encode(PacketByteBuf buf) {
buf.writeUuid(this.targetUuid);
}
@Override
public void apply(Supplier<NetworkManager.PacketContext> contextSupplier) {
NetworkManager.PacketContext context = contextSupplier.get();
context.queue(() -> {
PlayerEntity player = context.getPlayer();
IKnockableDown playerKnockable = (IKnockableDown) player;
IKnockableDown targetKnockable = (IKnockableDown) player.getWorld().getPlayerByUuid(this.targetUuid);
if (!playerKnockable.is_Reviving() && targetKnockable != null && targetKnockable.is_KnockedDown()) {
playerKnockable.set_Reviving(true);
targetKnockable.set_ReviverCount(targetKnockable.get_ReviverCount() + 1);
}
});
}
}

View file

@ -0,0 +1,41 @@
package ru.octol1ttle.knockdowns.common.network.packets;
import dev.architectury.networking.NetworkManager;
import java.util.UUID;
import java.util.function.Supplier;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.PacketByteBuf;
import ru.octol1ttle.knockdowns.common.api.IKnockableDown;
public class StopRevivingC2SPacket extends KnockdownsPacket {
private final UUID targetUuid;
public StopRevivingC2SPacket(PacketByteBuf buf) {
this(buf.readUuid());
}
public StopRevivingC2SPacket(UUID targetUuid) {
this.targetUuid = targetUuid;
}
@Override
public void encode(PacketByteBuf buf) {
buf.writeUuid(this.targetUuid);
}
@Override
public void apply(Supplier<NetworkManager.PacketContext> contextSupplier) {
NetworkManager.PacketContext context = contextSupplier.get();
context.queue(() -> {
PlayerEntity player = context.getPlayer();
IKnockableDown playerKnockable = (IKnockableDown) player;
IKnockableDown targetKnockable = (IKnockableDown) player.getWorld().getPlayerByUuid(this.targetUuid);
if (playerKnockable.is_Reviving() && targetKnockable != null) {
playerKnockable.set_Reviving(false);
if (targetKnockable.is_KnockedDown()) {
targetKnockable.set_ReviverCount(targetKnockable.get_ReviverCount() - 1);
}
}
});
}
}

View file

@ -0,0 +1,18 @@
package ru.octol1ttle.knockdowns.common.registries;
import dev.architectury.registry.registries.DeferredRegister;
import dev.architectury.registry.registries.RegistrySupplier;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.sound.SoundEvent;
import net.minecraft.util.Identifier;
import ru.octol1ttle.knockdowns.common.KnockdownsCommon;
public class KnockdownsSoundEvents {
private static final DeferredRegister<SoundEvent> SOUND_EVENTS = DeferredRegister.create(KnockdownsCommon.MOD_ID, RegistryKeys.SOUND_EVENT);
public static final RegistrySupplier<SoundEvent> KNOCKED_DOWN = SOUND_EVENTS.register(KnockdownsCommon.MOD_ID,
() -> SoundEvent.of(new Identifier(KnockdownsCommon.MOD_ID, "knocked_down")));
public static void register() {
SOUND_EVENTS.register();
}
}

View file

@ -0,0 +1,31 @@
package ru.octol1ttle.knockdowns.common.registries;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.sound.MovingSoundInstance;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvent;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.random.Random;
public class KnockedDownSoundInstance extends MovingSoundInstance {
private final Vec3d pos;
public KnockedDownSoundInstance(SoundEvent sound, Vec3d pos) {
super(sound, SoundCategory.MASTER, Random.create(0L));
this.pos = pos;
this.relative = true;
}
@Override
public void tick() {
ClientPlayerEntity player = MinecraftClient.getInstance().player;
if (player == null) {
throw new IllegalStateException();
}
Vec3d vec = pos.subtract(player.getPos()).normalize();
this.x = vec.x;
this.y = vec.y;
this.z = vec.z;
}
}

View file

@ -0,0 +1,3 @@
{
"accessWidener": "knockdowns.accesswidener"
}

View file

@ -1,4 +1,6 @@
{
"subtitles.knockdowns.knocked_down": "Player knocked down",
"knockdown.attack.anvil": "%1$s was knocked down by a falling anvil",
"knockdown.attack.anvil.player": "%1$s was knocked down by a falling anvil while fighting %2$s",
"knockdown.attack.arrow": "%1$s was knocked down due to an arrow fired by %2$s",
@ -97,6 +99,5 @@
"knockdown.fell.assist.item": "%1$s was doomed to get knocked down by %2$s using %3$s",
"knockdown.fell.finish": "%1$s fell too far and was knocked down by %2$s",
"knockdown.fell.finish.item": "%1$s fell too far and was knocked down by %2$s using %3$s",
"knockdown.fell.killer": "%1$s was doomed to get knocked down by a fall",
"subtitles.knockdowns.knocked_down": "Player knocked down"
"knockdown.fell.killer": "%1$s was doomed to get knocked down by a fall"
}

View file

@ -1,4 +1,6 @@
{
"subtitles.knockdowns.knocked_down": "Игрок тяжело ранен",
"knockdown.attack.anvil": "%1$s был тяжело ранен упавшей наковальней",
"knockdown.attack.anvil.player": "%1$s был тяжело ранен упавшей наковальней, пока сражался с %2$s",
"knockdown.attack.arrow": "%1$s тяжело ранен стрелой %2$s",
@ -97,6 +99,5 @@
"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 был тяжело ранен падением",
"subtitles.knockdowns.knocked_down": "Игрок тяжело ранен"
"knockdown.fell.killer": "%1$s был тяжело ранен падением"
}

View file

@ -0,0 +1,17 @@
{
"required": true,
"package": "ru.octol1ttle.knockdowns.common.mixin",
"compatibilityLevel": "JAVA_17",
"minVersion": "0.8",
"client": [
"client.ClientPlayerEntityMixin"
],
"mixins": [
"LivingEntityMixin",
"MobEntityMixin",
"PlayerEntityMixin"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -0,0 +1 @@
accessWidener v2 named

77
fabric/build.gradle Normal file
View file

@ -0,0 +1,77 @@
plugins {
id "com.github.johnrengelman.shadow" version "7.1.2"
}
architectury {
platformSetupLoomIde()
fabric()
}
loom {
accessWidenerPath = project(":common").loom.accessWidenerPath
}
configurations {
common
shadowCommon // Don't use shadow from the shadow plugin since it *excludes* files.
compileClasspath.extendsFrom common
runtimeClasspath.extendsFrom common
developmentFabric.extendsFrom common
}
dependencies {
modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}"
modApi "net.fabricmc.fabric-api:fabric-api:${rootProject.fabric_api_version}"
// Remove the next line if you don't want to depend on the API
modApi "dev.architectury:architectury-fabric:${rootProject.architectury_version}"
common(project(path: ":common", configuration: "namedElements")) { transitive false }
shadowCommon(project(path: ":common", configuration: "transformProductionFabric")) { transitive false }
}
processResources {
inputs.property "version", project.version
filesMatching("fabric.mod.json") {
expand "version": project.version
}
}
shadowJar {
exclude "architectury.common.json"
configurations = [project.configurations.shadowCommon]
archiveClassifier = "dev-shadow"
}
remapJar {
injectAccessWidener = true
input.set shadowJar.archiveFile
dependsOn shadowJar
}
sourcesJar {
def commonSources = project(":common").sourcesJar
dependsOn commonSources
from commonSources.archiveFile.map { zipTree(it) }
}
components.java {
withVariantsFromConfiguration(project.configurations.shadowRuntimeElements) {
skip()
}
}
publishing {
publications {
mavenFabric(MavenPublication) {
artifactId = rootProject.archives_base_name + "-" + project.name
from components.java
}
}
// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
repositories {
// Add repositories to publish to here.
}
}

View file

@ -0,0 +1,11 @@
package ru.octol1ttle.knockdowns.fabric;
import ru.octol1ttle.knockdowns.common.KnockdownsCommon;
import net.fabricmc.api.ModInitializer;
public class KnockdownsFabric implements ModInitializer {
@Override
public void onInitialize() {
KnockdownsCommon.init();
}
}

View file

@ -0,0 +1,11 @@
package ru.octol1ttle.knockdowns.fabric;
import net.fabricmc.api.ClientModInitializer;
import ru.octol1ttle.knockdowns.common.KnockdownsClient;
public class KnockdownsFabricClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
KnockdownsClient.init();
}
}

View file

@ -0,0 +1,34 @@
{
"schemaVersion": 1,
"id": "knockdowns",
"version": "${version}",
"name": "Knockdowns",
"description": "DBNO mechanic from Fortnite, ported to Minecraft",
"authors": [
"Octol1ttle"
],
"contact": {
"homepage": "https://fabricmc.net/",
"sources": "https://github.com/FabricMC/fabric-example-mod"
},
"license": "ARR",
"icon": "assets/knockdowns/icon.png",
"environment": "*",
"entrypoints": {
"main": [
"ru.octol1ttle.knockdowns.fabric.KnockdownsFabric"
],
"client": [
"ru.octol1ttle.knockdowns.fabric.KnockdownsFabricClient"
]
},
"mixins": [
"knockdowns.mixins.json",
"knockdowns-common.mixins.json"
],
"depends": {
"fabric": "*",
"minecraft": ">=1.20.1",
"architectury": ">=9.1.12"
}
}

View file

@ -0,0 +1,13 @@
{
"required": true,
"package": "ru.octol1ttle.knockdowns.fabric.mixin",
"compatibilityLevel": "JAVA_17",
"minVersion": "0.8",
"client": [
],
"mixins": [
],
"injectors": {
"defaultRequire": 1
}
}

86
forge/build.gradle Normal file
View file

@ -0,0 +1,86 @@
plugins {
id "com.github.johnrengelman.shadow" version "7.1.2"
}
architectury {
platformSetupLoomIde()
forge()
}
loom {
accessWidenerPath = project(":common").loom.accessWidenerPath
forge {
convertAccessWideners = true
extraAccessWideners.add loom.accessWidenerPath.get().asFile.name
mixinConfig "knockdowns-common.mixins.json"
mixinConfig "knockdowns.mixins.json"
}
}
configurations {
common
shadowCommon // Don't use shadow from the shadow plugin since it *excludes* files.
compileClasspath.extendsFrom common
runtimeClasspath.extendsFrom common
developmentForge.extendsFrom common
}
dependencies {
forge "net.minecraftforge:forge:${rootProject.forge_version}"
// Remove the next line if you don't want to depend on the API
modApi "dev.architectury:architectury-forge:${rootProject.architectury_version}"
common(project(path: ":common", configuration: "namedElements")) { transitive false }
shadowCommon(project(path: ":common", configuration: "transformProductionForge")) { transitive = false }
implementation(include("io.github.llamalad7:mixinextras-forge:0.3.3"))
}
processResources {
inputs.property "version", project.version
filesMatching("META-INF/mods.toml") {
expand "version": project.version
}
}
shadowJar {
exclude "fabric.mod.json"
exclude "architectury.common.json"
configurations = [project.configurations.shadowCommon]
archiveClassifier = "dev-shadow"
}
remapJar {
input.set shadowJar.archiveFile
dependsOn shadowJar
}
sourcesJar {
def commonSources = project(":common").sourcesJar
dependsOn commonSources
from commonSources.archiveFile.map { zipTree(it) }
}
components.java {
withVariantsFromConfiguration(project.configurations.shadowRuntimeElements) {
skip()
}
}
publishing {
publications {
mavenForge(MavenPublication) {
artifactId = rootProject.archives_base_name + "-" + project.name
from components.java
}
}
// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
repositories {
// Add repositories to publish to here.
}
}

1
forge/gradle.properties Normal file
View file

@ -0,0 +1 @@
loom.platform=forge

View file

@ -0,0 +1,26 @@
package ru.octol1ttle.knockdowns.forge;
import dev.architectury.platform.forge.EventBuses;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import ru.octol1ttle.knockdowns.common.KnockdownsClient;
import ru.octol1ttle.knockdowns.common.KnockdownsCommon;
@SuppressWarnings("unused")
@Mod(KnockdownsCommon.MOD_ID)
public class KnockdownsForge {
public KnockdownsForge() {
// Submit our event bus to let architectury register our content on the right time
IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
EventBuses.registerModEventBus(KnockdownsCommon.MOD_ID, modEventBus);
modEventBus.addListener(this::onInitializeClient);
KnockdownsCommon.init();
}
public void onInitializeClient(FMLClientSetupEvent event) {
KnockdownsClient.init();
}
}

View file

@ -0,0 +1,35 @@
modLoader = "javafml"
loaderVersion = "[47,)"
#issueTrackerURL = ""
license = "ARR"
[[mods]]
modId = "knockdowns"
version = "${version}"
displayName = "Knockdowns"
authors = "Octol1ttle"
description = '''
DBNO mechanic from Fortnite, ported to Minecraft
'''
#logoFile = ""
[[dependencies.knockdowns]]
modId = "forge"
mandatory = true
versionRange = "[47,)"
ordering = "NONE"
side = "BOTH"
[[dependencies.knockdowns]]
modId = "minecraft"
mandatory = true
versionRange = "[1.20.1,)"
ordering = "NONE"
side = "BOTH"
[[dependencies.knockdowns]]
modId = "architectury"
mandatory = true
versionRange = "[9.1.12,)"
ordering = "AFTER"
side = "BOTH"

View file

@ -1,11 +1,13 @@
{
"required": true,
"package": "ru.octol1ttle.knockdowns.mixin.client",
"package": "ru.octol1ttle.knockdowns.forge.mixin",
"compatibilityLevel": "JAVA_17",
"minVersion": "0.8",
"client": [
"ClientPlayerEntityMixin"
],
"mixins": [
],
"injectors": {
"defaultRequire": 1
}
}
}

View file

@ -0,0 +1,6 @@
{
"pack": {
"description": "Knockdowns",
"pack_format": 15
}
}

View file

@ -1,17 +1,17 @@
# Done to increase the memory available to gradle.
org.gradle.jvmargs=-Xmx1G
org.gradle.parallel=true
org.gradle.jvmargs=-Xmx2048M
# Fabric Properties
# check these on https://fabricmc.net/develop
minecraft_version=1.20.2
yarn_mappings=1.20.2+build.4
loader_version=0.14.24
minecraft_version=1.20.1
enabled_platforms=fabric,forge
# Mod Properties
mod_version=1.1.1
maven_group=ru.octol1ttle.knockdowns
archives_base_name=knockdowns
mod_version=2.1.0
maven_group=ru.octol1ttle.knockdowns
architectury_version=9.1.12
fabric_api_version=0.90.4+1.20.1
fabric_loader_version=0.15.5
forge_version=1.20.1-47.2.0
# Dependencies
fabric_version=0.90.4+1.20.2

Binary file not shown.

View file

@ -1,7 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

15
gradlew vendored
View file

@ -83,8 +83,10 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@ -131,13 +133,10 @@ location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
@ -198,10 +197,6 @@ if "$cygwin" || "$msys" ; then
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in

View file

@ -1,10 +1,14 @@
pluginManagement {
repositories {
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
}
mavenCentral()
gradlePluginPortal()
}
}
repositories {
maven { url "https://maven.fabricmc.net/" }
maven { url "https://maven.architectury.dev/" }
maven { url "https://maven.minecraftforge.net/" }
gradlePluginPortal()
}
}
include("common")
include("fabric")
include("forge")
rootProject.name = "knockdowns-modern"

View file

@ -1,166 +0,0 @@
package ru.octol1ttle.knockdowns;
import java.util.UUID;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback;
import net.fabricmc.fabric.api.event.player.UseEntityCallback;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.minecraft.SharedConstants;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.util.ActionResult;
import net.minecraft.util.hit.EntityHitResult;
import net.minecraft.util.hit.HitResult;
import ru.octol1ttle.knockdowns.api.IKnockableDown;
import ru.octol1ttle.knockdowns.network.KnockdownsNetworkingConstants;
public class KnockdownsClient implements ClientModInitializer {
private static final int REVIVAL_WAIT_TIME = 10 * SharedConstants.TICKS_PER_SECOND;
private static IKnockableDown reviving = null;
private static int revivalTimer = -1;
@Override
public void onInitializeClient() {
registerOnReceiveKnockedDown();
registerOnReceiveReviving();
registerOnEntityLoad();
registerOnEntityUse();
registerOnStartWorldTick();
registerOnHudRender();
}
private static void registerOnReceiveKnockedDown() {
ClientPlayNetworking.registerGlobalReceiver(KnockdownsNetworkingConstants.S2C_SEND_PLAYER_KNOCKED_DOWN, (client, handler, buf, responseSender) -> {
UUID uuid = buf.readUuid();
boolean knockedDown = buf.readBoolean();
client.execute(() -> {
IKnockableDown knockableDown = (IKnockableDown) handler.getWorld().getPlayerByUuid(uuid);
if (knockableDown != null) {
knockableDown.knockdowns$setKnockedDown(knockedDown);
}
});
});
}
private static void registerOnReceiveReviving() {
ClientPlayNetworking.registerGlobalReceiver(KnockdownsNetworkingConstants.S2C_SEND_PLAYER_REVIVING, (client, handler, buf, responseSender) -> {
UUID uuid = buf.readUuid();
boolean beingRevived = buf.readBoolean();
client.execute(() -> {
IKnockableDown knockableDown = (IKnockableDown) handler.getWorld().getPlayerByUuid(uuid);
if (knockableDown != null) {
knockableDown.knockdowns$setBeingRevived(beingRevived);
if (client.player != null && uuid.equals(client.player.getUuid())) {
if (knockableDown.knockdowns$isBeingRevived()) {
revivalTimer = REVIVAL_WAIT_TIME;
} else {
revivalTimer = -1;
}
}
if (reviving != null && knockableDown.knockdowns$getUuid().equals(reviving.knockdowns$getUuid())
&& !knockableDown.knockdowns$isBeingRevived()) {
reviving = null;
revivalTimer = -1;
}
}
});
});
}
private static void registerOnEntityLoad() {
ClientEntityEvents.ENTITY_LOAD.register((entity, world) -> {
if (entity instanceof IKnockableDown) {
PacketByteBuf buf = PacketByteBufs.create();
buf.writeUuid(entity.getUuid());
ClientPlayNetworking.send(KnockdownsNetworkingConstants.C2S_REQUEST_PLAYER_KNOCKED_DOWN, PacketByteBufs.copy(buf));
ClientPlayNetworking.send(KnockdownsNetworkingConstants.C2S_REQUEST_PLAYER_REVIVING, buf);
}
});
}
private static void registerOnEntityUse() {
UseEntityCallback.EVENT.register((player, world, hand, entity, hitResult) -> {
if (!(entity instanceof IKnockableDown knockableDown) || !knockableDown.knockdowns$isKnockedDown()
|| knockableDown.knockdowns$isBeingRevived()) {
return ActionResult.PASS;
}
if (((IKnockableDown)player).knockdowns$isKnockedDown()) {
return ActionResult.FAIL;
}
knockableDown.knockdowns$setBeingRevived(true);
PacketByteBuf buf = PacketByteBufs.create();
buf.writeUuid(entity.getUuid());
buf.writeBoolean(true);
ClientPlayNetworking.send(KnockdownsNetworkingConstants.C2S_SEND_PLAYER_REVIVING, buf);
reviving = knockableDown;
revivalTimer = REVIVAL_WAIT_TIME;
return ActionResult.SUCCESS;
});
}
private static void registerOnStartWorldTick() {
ClientTickEvents.START_WORLD_TICK.register(world -> {
boolean revived = false;
revivalTimer--;
if (revivalTimer <= 0) {
revivalTimer = -1;
revived = true;
}
if (reviving == null) {
return;
}
HitResult crosshairTarget = MinecraftClient.getInstance().crosshairTarget;
if (revived || crosshairTarget == null || crosshairTarget.getType() != HitResult.Type.ENTITY
|| !((EntityHitResult) crosshairTarget).getEntity().getUuid().equals(reviving.knockdowns$getUuid())) {
reviving.knockdowns$setBeingRevived(false);
PacketByteBuf buf = PacketByteBufs.create();
buf.writeUuid(reviving.knockdowns$getUuid());
buf.writeBoolean(false);
ClientPlayNetworking.send(KnockdownsNetworkingConstants.C2S_SEND_PLAYER_REVIVING, buf);
if (revived) {
reviving.knockdowns$setKnockedDown(false);
PacketByteBuf revivedBuf = PacketByteBufs.create();
revivedBuf.writeUuid(reviving.knockdowns$getUuid());
ClientPlayNetworking.send(KnockdownsNetworkingConstants.C2S_SEND_PLAYER_REVIVED, revivedBuf);
}
reviving = null;
revivalTimer = -1;
}
});
}
private static void registerOnHudRender() {
HudRenderCallback.EVENT.register((drawContext, tickDelta) -> {
if (revivalTimer == -1) {
return;
}
TextRenderer renderer = MinecraftClient.getInstance().textRenderer;
String text = String.format("%.1f", revivalTimer / (float) SharedConstants.TICKS_PER_SECOND);
int x = (drawContext.getScaledWindowWidth() - renderer.getWidth(text)) / 2;
drawContext.drawTextWithShadow(renderer, text, x, drawContext.getScaledWindowHeight() / 2 + 15, 0xFFFFFF);
});
}
}

View file

@ -1,219 +0,0 @@
package ru.octol1ttle.knockdowns;
import java.util.UUID;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents;
import net.fabricmc.fabric.api.event.player.AttackBlockCallback;
import net.fabricmc.fabric.api.event.player.AttackEntityCallback;
import net.fabricmc.fabric.api.event.player.UseBlockCallback;
import net.fabricmc.fabric.api.event.player.UseItemCallback;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.api.networking.v1.PlayerLookup;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.packet.s2c.play.PlaySoundS2CPacket;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvent;
import net.minecraft.text.Text;
import net.minecraft.text.TranslatableTextContent;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.Identifier;
import net.minecraft.util.TypedActionResult;
import net.minecraft.world.GameRules;
import ru.octol1ttle.knockdowns.api.IKnockableDown;
import ru.octol1ttle.knockdowns.network.KnockdownsNetworkingConstants;
public class Knockdowns implements ModInitializer {
public static final String MOD_ID = "knockdowns";
private static final Identifier KNOCKED_DOWN_ID = new Identifier("knockdowns:knocked_down");
private static final SoundEvent KNOCKED_DOWN = SoundEvent.of(KNOCKED_DOWN_ID);
@Override
public void onInitialize() {
registerSoundEvents();
registerOnDeath();
registerOnRequestKnockedDown();
registerOnRequestReviving();
registerOnReceiveReviving();
registerOnReceiveRevived();
registerOnPlayerInteractions();
}
private static void registerSoundEvents() {
Registry.register(Registries.SOUND_EVENT, KNOCKED_DOWN_ID, KNOCKED_DOWN);
}
private static void registerOnDeath() {
ServerLivingEntityEvents.ALLOW_DEATH.register((entity, damageSource, damageAmount) -> {
if (!(entity instanceof IKnockableDown knockableDown) || knockableDown.knockdowns$isKnockedDown()) {
return true;
}
ServerPlayerEntity serverPlayer = (ServerPlayerEntity) entity;
// TODO: timer
if (!serverPlayer.getWorld().getGameRules().getBoolean(GameRules.KEEP_INVENTORY)) {
serverPlayer.getInventory().dropAll();
}
entity.setHealth(1.0f);
entity.setInvulnerable(true);
entity.setGlowing(true);
entity.setAir(entity.getMaxAir());
entity.extinguish();
entity.setFrozenTicks(0);
entity.setOnFire(false);
entity.clearStatusEffects();
knockableDown.knockdowns$setKnockedDown(true);
PacketByteBuf buf = PacketByteBufs.create();
buf.writeUuid(entity.getUuid());
buf.writeBoolean(true);
ServerPlayNetworking.send(serverPlayer, KnockdownsNetworkingConstants.S2C_SEND_PLAYER_KNOCKED_DOWN, buf);
for (ServerPlayerEntity player : PlayerLookup.tracking(entity)) {
ServerPlayNetworking.send(player, KnockdownsNetworkingConstants.S2C_SEND_PLAYER_KNOCKED_DOWN, buf);
}
for (ServerPlayerEntity player : serverPlayer.getServerWorld().getPlayers()) {
player.networkHandler.sendPacket(new PlaySoundS2CPacket(Registries.SOUND_EVENT.getEntry(KNOCKED_DOWN), SoundCategory.PLAYERS,
player.getX(), player.getY(), player.getZ(), 1.0f, 1.0f, 0L));
}
TranslatableTextContent content = (TranslatableTextContent) entity.getDamageTracker().getDeathMessage().getContent();
Text replaced = Text.translatable(content.getKey().replace("death.", "knockdown."), content.getArgs());
MinecraftServer server = serverPlayer.getServer();
if (server != null) {
server.getPlayerManager().broadcast(replaced, false);
}
return false;
});
}
private static void registerOnRequestKnockedDown() {
ServerPlayNetworking.registerGlobalReceiver(KnockdownsNetworkingConstants.C2S_REQUEST_PLAYER_KNOCKED_DOWN, (server, player, handler, buf, responseSender) -> {
UUID uuid = buf.readUuid();
server.execute(() -> {
IKnockableDown knockableDown = (IKnockableDown) player.getWorld().getPlayerByUuid(uuid);
if (knockableDown == null) {
return;
}
PacketByteBuf responseBuf = PacketByteBufs.create();
responseBuf.writeUuid(uuid);
responseBuf.writeBoolean(knockableDown.knockdowns$isKnockedDown());
responseSender.sendPacket(KnockdownsNetworkingConstants.S2C_SEND_PLAYER_KNOCKED_DOWN, responseBuf);
});
});
}
private static void registerOnRequestReviving() {
ServerPlayNetworking.registerGlobalReceiver(KnockdownsNetworkingConstants.C2S_REQUEST_PLAYER_REVIVING, (server, player, handler, buf, responseSender) -> {
UUID uuid = buf.readUuid();
server.execute(() -> {
IKnockableDown knockableDown = (IKnockableDown) player.getWorld().getPlayerByUuid(uuid);
if (knockableDown == null) {
return;
}
PacketByteBuf responseBuf = PacketByteBufs.create();
responseBuf.writeUuid(uuid);
responseBuf.writeBoolean(knockableDown.knockdowns$isBeingRevived());
responseSender.sendPacket(KnockdownsNetworkingConstants.S2C_SEND_PLAYER_REVIVING, responseBuf);
});
});
}
private static void registerOnReceiveReviving() {
ServerPlayNetworking.registerGlobalReceiver(KnockdownsNetworkingConstants.C2S_SEND_PLAYER_REVIVING, (server, player, handler, buf, responseSender) -> {
UUID uuid = buf.readUuid();
boolean beingRevived = buf.readBoolean();
// TODO: revival by multiple players
server.execute(() -> {
PlayerEntity reviving = player.getWorld().getPlayerByUuid(uuid);
IKnockableDown knockableDown = (IKnockableDown) reviving;
if (knockableDown == null) {
return;
}
knockableDown.knockdowns$setBeingRevived(beingRevived);
PacketByteBuf broadcastBuf = PacketByteBufs.create();
broadcastBuf.writeUuid(uuid);
broadcastBuf.writeBoolean(beingRevived);
ServerPlayNetworking.send((ServerPlayerEntity) reviving, KnockdownsNetworkingConstants.S2C_SEND_PLAYER_REVIVING, broadcastBuf);
for (ServerPlayerEntity entity : PlayerLookup.tracking(reviving)) {
ServerPlayNetworking.send(entity, KnockdownsNetworkingConstants.S2C_SEND_PLAYER_REVIVING, broadcastBuf);
}
});
});
}
private static void registerOnReceiveRevived() {
ServerPlayNetworking.registerGlobalReceiver(KnockdownsNetworkingConstants.C2S_SEND_PLAYER_REVIVED, (server, player, handler, buf, responseSender) -> {
UUID uuid = buf.readUuid();
server.execute(() -> {
PlayerEntity reviving = player.getWorld().getPlayerByUuid(uuid);
IKnockableDown knockableDown = (IKnockableDown) reviving;
if (knockableDown == null || !knockableDown.knockdowns$isKnockedDown()) {
return;
}
reviving.setInvulnerable(false);
reviving.setGlowing(false);
reviving.setHealth(reviving.getHealth() + 5.0f);
knockableDown.knockdowns$setKnockedDown(false);
PacketByteBuf sendBuf = PacketByteBufs.create();
sendBuf.writeUuid(reviving.getUuid());
sendBuf.writeBoolean(false);
ServerPlayNetworking.send((ServerPlayerEntity) reviving, KnockdownsNetworkingConstants.S2C_SEND_PLAYER_KNOCKED_DOWN, sendBuf);
for (ServerPlayerEntity entity : PlayerLookup.tracking(reviving)) {
ServerPlayNetworking.send(entity, KnockdownsNetworkingConstants.S2C_SEND_PLAYER_KNOCKED_DOWN, sendBuf);
}
});
});
}
private static void registerOnPlayerInteractions() {
AttackBlockCallback.EVENT.register((player, world, hand, pos, direction) -> {
if (player instanceof IKnockableDown && ((IKnockableDown) player).knockdowns$isKnockedDown()) {
return ActionResult.FAIL;
}
return ActionResult.PASS;
});
AttackEntityCallback.EVENT.register((player, world, hand, entity, hitResult) -> {
if (player instanceof IKnockableDown && ((IKnockableDown) player).knockdowns$isKnockedDown()) {
return ActionResult.FAIL;
}
return ActionResult.PASS;
});
UseItemCallback.EVENT.register((player, world, hand) -> {
if (player instanceof IKnockableDown && ((IKnockableDown) player).knockdowns$isKnockedDown()) {
return TypedActionResult.fail(hand == Hand.MAIN_HAND ? player.getMainHandStack() : player.getOffHandStack());
}
return TypedActionResult.pass(hand == Hand.MAIN_HAND ? player.getMainHandStack() : player.getOffHandStack());
});
UseBlockCallback.EVENT.register((player, world, hand, hitResult) -> {
if (player instanceof IKnockableDown && ((IKnockableDown) player).knockdowns$isKnockedDown()) {
return ActionResult.FAIL;
}
return ActionResult.PASS;
});
}
}

View file

@ -1,15 +0,0 @@
package ru.octol1ttle.knockdowns.api;
import java.util.UUID;
public interface IKnockableDown {
boolean knockdowns$isKnockedDown();
void knockdowns$setKnockedDown(boolean knockedDown);
boolean knockdowns$isBeingRevived();
void knockdowns$setBeingRevived(boolean beingRevived);
UUID knockdowns$getUuid();
}

View file

@ -1,71 +0,0 @@
package ru.octol1ttle.knockdowns.mixin;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import java.util.UUID;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.nbt.NbtCompound;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import ru.octol1ttle.knockdowns.api.IKnockableDown;
@Mixin(PlayerEntity.class)
public abstract class PlayerEntityMixin implements IKnockableDown {
@Unique
private boolean knockedDown;
@Unique
private boolean beingRevived;
@ModifyExpressionValue(method = "updatePose", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/PlayerEntity;isSwimming()Z"))
private boolean enterSwimmingIfKnockedDown(boolean original) {
PlayerEntity player = (PlayerEntity)(Object)this;
if (!(player instanceof IKnockableDown knockableDown)) {
throw new IllegalStateException();
}
return original || knockableDown.knockdowns$isKnockedDown();
}
@ModifyReturnValue(method = "canFoodHeal", at = @At("RETURN"))
private boolean dontHealIfKnockedDown(boolean original) {
return original && !this.knockdowns$isKnockedDown();
}
@Inject(method = "readCustomDataFromNbt", at = @At("TAIL"))
public void readKnockedDownFromNbt(NbtCompound nbt, CallbackInfo ci) {
this.knockedDown = nbt.getBoolean("KnockedDown");
}
@Inject(method = "writeCustomDataToNbt", at = @At("TAIL"))
public void writeKnockedDownToNbt(NbtCompound nbt, CallbackInfo ci) {
nbt.putBoolean("KnockedDown", this.knockedDown);
}
@Override
public boolean knockdowns$isKnockedDown() {
return knockedDown;
}
@Override
public void knockdowns$setKnockedDown(boolean knockedDown) {
this.knockedDown = knockedDown;
}
@Override
public boolean knockdowns$isBeingRevived() {
return beingRevived;
}
@Override
public void knockdowns$setBeingRevived(boolean beingRevived) {
this.beingRevived = beingRevived;
}
@Override
public UUID knockdowns$getUuid() {
return ((PlayerEntity)(Object)this).getUuid();
}
}

View file

@ -1,13 +0,0 @@
package ru.octol1ttle.knockdowns.network;
import net.minecraft.util.Identifier;
import ru.octol1ttle.knockdowns.Knockdowns;
public class KnockdownsNetworkingConstants {
public static final Identifier S2C_SEND_PLAYER_KNOCKED_DOWN = new Identifier(Knockdowns.MOD_ID, "s2c_send_player_knocked_down");
public static final Identifier S2C_SEND_PLAYER_REVIVING = new Identifier(Knockdowns.MOD_ID, "s2c_send_player_reviving");
public static final Identifier C2S_REQUEST_PLAYER_KNOCKED_DOWN = new Identifier(Knockdowns.MOD_ID, "c2s_request_player_knocked_down");
public static final Identifier C2S_REQUEST_PLAYER_REVIVING = new Identifier(Knockdowns.MOD_ID, "c2s_request_player_reviving");
public static final Identifier C2S_SEND_PLAYER_REVIVING = new Identifier(Knockdowns.MOD_ID, "c2s_send_player_reviving");
public static final Identifier C2S_SEND_PLAYER_REVIVED = new Identifier(Knockdowns.MOD_ID, "c2s_send_player_revived");
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 453 B

View file

@ -1,38 +0,0 @@
{
"schemaVersion": 1,
"id": "knockdowns",
"version": "${version}",
"name": "Knockdowns",
"description": "man hardcode is difficult",
"authors": [
"Octol1ttle"
],
"contact": {
"homepage": "https://fabricmc.net/",
"sources": "https://github.com/FabricMC/fabric-example-mod"
},
"license": "ARR",
"icon": "assets/knockdowns/icon.png",
"environment": "*",
"entrypoints": {
"main": [
"ru.octol1ttle.knockdowns.Knockdowns"
],
"client": [
"ru.octol1ttle.knockdowns.KnockdownsClient"
]
},
"mixins": [
"knockdowns.mixins.json",
{
"config": "knockdowns.client.mixins.json",
"environment": "client"
}
],
"depends": {
"fabricloader": ">=0.14.23",
"minecraft": "~1.20.2",
"java": ">=17",
"fabric-api": "*"
}
}

View file

@ -1,11 +0,0 @@
{
"required": true,
"package": "ru.octol1ttle.knockdowns.mixin",
"compatibilityLevel": "JAVA_17",
"mixins": [
"PlayerEntityMixin"
],
"injectors": {
"defaultRequire": 1
}
}