├── .gitignore
├── LICENSE
├── README.md
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── jitpack.yml
├── settings.gradle
└── src
├── main
└── java
│ └── io
│ └── github
│ └── togar2
│ └── pvp
│ ├── MinestomPvP.java
│ ├── damage
│ ├── DamageTypeInfo.java
│ └── combat
│ │ ├── CombatEntry.java
│ │ └── CombatManager.java
│ ├── enchantment
│ ├── CombatEnchantment.java
│ ├── CombatEnchantments.java
│ ├── EntityGroup.java
│ └── enchantments
│ │ ├── DamageEnchantment.java
│ │ ├── ImpalingEnchantment.java
│ │ ├── ProtectionEnchantment.java
│ │ └── ThornsEnchantment.java
│ ├── entity
│ ├── explosion
│ │ ├── CrystalEntity.java
│ │ └── TntEntity.java
│ └── projectile
│ │ ├── AbstractArrow.java
│ │ ├── Arrow.java
│ │ ├── CustomEntityProjectile.java
│ │ ├── FishingBobber.java
│ │ ├── ItemHoldingProjectile.java
│ │ ├── Snowball.java
│ │ ├── SpectralArrow.java
│ │ ├── ThrownEgg.java
│ │ ├── ThrownEnderpearl.java
│ │ ├── ThrownPotion.java
│ │ └── ThrownTrident.java
│ ├── enums
│ ├── ArmorMaterial.java
│ ├── Tool.java
│ └── ToolMaterial.java
│ ├── events
│ ├── AnchorChargeEvent.java
│ ├── AnchorExplodeEvent.java
│ ├── CrystalPlaceEvent.java
│ ├── DamageBlockEvent.java
│ ├── EntityKnockbackEvent.java
│ ├── EntityPreDeathEvent.java
│ ├── EquipmentDamageEvent.java
│ ├── ExplosionEvent.java
│ ├── ExplosivePrimeEvent.java
│ ├── FinalAttackEvent.java
│ ├── FinalDamageEvent.java
│ ├── FishingBobberRetrieveEvent.java
│ ├── PickupEntityEvent.java
│ ├── PlayerExhaustEvent.java
│ ├── PlayerRegenerateEvent.java
│ ├── PlayerSpectateEvent.java
│ ├── PotionVisibilityEvent.java
│ ├── PrepareAttackEvent.java
│ └── TotemUseEvent.java
│ ├── feature
│ ├── CombatFeature.java
│ ├── CombatFeatureSet.java
│ ├── CombatFeatures.java
│ ├── FeatureType.java
│ ├── RegistrableFeature.java
│ ├── armor
│ │ ├── ArmorFeature.java
│ │ └── VanillaArmorFeature.java
│ ├── attack
│ │ ├── AttackFeature.java
│ │ ├── AttackValues.java
│ │ ├── CriticalFeature.java
│ │ ├── SweepingFeature.java
│ │ ├── VanillaAttackFeature.java
│ │ ├── VanillaCriticalFeature.java
│ │ └── VanillaSweepingFeature.java
│ ├── attributes
│ │ ├── EquipmentFeature.java
│ │ └── VanillaEquipmentFeature.java
│ ├── block
│ │ ├── BlockFeature.java
│ │ ├── LegacyBlockFeature.java
│ │ ├── LegacyVanillaBlockFeature.java
│ │ └── VanillaBlockFeature.java
│ ├── config
│ │ ├── CombatConfiguration.java
│ │ ├── CombatFeatureRegistry.java
│ │ ├── DefinedFeature.java
│ │ └── FeatureConfiguration.java
│ ├── cooldown
│ │ ├── AttackCooldownFeature.java
│ │ ├── ItemCooldownFeature.java
│ │ ├── VanillaAttackCooldownFeature.java
│ │ └── VanillaItemCooldownFeature.java
│ ├── damage
│ │ ├── DamageFeature.java
│ │ └── VanillaDamageFeature.java
│ ├── effect
│ │ ├── EffectFeature.java
│ │ ├── PotionColorUtils.java
│ │ └── VanillaEffectFeature.java
│ ├── enchantment
│ │ ├── EnchantmentFeature.java
│ │ └── VanillaEnchantmentFeature.java
│ ├── explosion
│ │ ├── ExplosionFeature.java
│ │ ├── ExplosiveFeature.java
│ │ ├── VanillaExplosionFeature.java
│ │ ├── VanillaExplosionSupplier.java
│ │ └── VanillaExplosiveFeature.java
│ ├── fall
│ │ ├── FallFeature.java
│ │ └── VanillaFallFeature.java
│ ├── food
│ │ ├── ChorusFruitUtil.java
│ │ ├── ExhaustionFeature.java
│ │ ├── FoodFeature.java
│ │ ├── RegenerationFeature.java
│ │ ├── VanillaExhaustionFeature.java
│ │ ├── VanillaFoodFeature.java
│ │ └── VanillaRegenerationFeature.java
│ ├── item
│ │ ├── ItemDamageFeature.java
│ │ └── VanillaItemDamageFeature.java
│ ├── knockback
│ │ ├── FairKnockbackFeature.java
│ │ ├── KnockbackFeature.java
│ │ ├── KnockbackSettings.java
│ │ └── VanillaKnockbackFeature.java
│ ├── potion
│ │ ├── PotionFeature.java
│ │ └── VanillaPotionFeature.java
│ ├── projectile
│ │ ├── BowFeature.java
│ │ ├── CrossbowFeature.java
│ │ ├── FishingRodFeature.java
│ │ ├── MiscProjectileFeature.java
│ │ ├── ProjectileItemFeature.java
│ │ ├── TridentFeature.java
│ │ ├── VanillaBowFeature.java
│ │ ├── VanillaCrossbowFeature.java
│ │ ├── VanillaFishingRodFeature.java
│ │ ├── VanillaMiscProjectileFeature.java
│ │ ├── VanillaProjectileItemFeature.java
│ │ └── VanillaTridentFeature.java
│ ├── provider
│ │ └── DifficultyProvider.java
│ ├── spectate
│ │ ├── SpectateFeature.java
│ │ └── VanillaSpectateFeature.java
│ ├── state
│ │ ├── PlayerStateFeature.java
│ │ └── VanillaPlayerStateFeature.java
│ ├── totem
│ │ ├── TotemFeature.java
│ │ └── VanillaTotemFeature.java
│ └── tracking
│ │ ├── TrackingFeature.java
│ │ └── VanillaDeathMessageFeature.java
│ ├── player
│ ├── CombatPlayer.java
│ └── CombatPlayerImpl.java
│ ├── potion
│ ├── effect
│ │ ├── AbsorptionPotionEffect.java
│ │ ├── CombatPotionEffect.java
│ │ ├── CombatPotionEffects.java
│ │ ├── GlowingPotionEffect.java
│ │ └── HealthBoostPotionEffect.java
│ └── item
│ │ ├── CombatPotionType.java
│ │ └── CombatPotionTypes.java
│ └── utils
│ ├── AccurateLatencyListener.java
│ ├── CombatVersion.java
│ ├── EffectUtil.java
│ ├── EntityUtil.java
│ ├── FluidUtil.java
│ ├── ModifierId.java
│ ├── PotionFlags.java
│ ├── ProjectileUtil.java
│ └── ViewUtil.java
└── test
└── java
└── io
└── github
└── togar2
└── pvp
└── test
├── DemoGenerator.java
├── PvpTest.java
└── commands
├── ClearCommand.java
├── Commands.java
├── DamageCommand.java
└── GameModeCommand.java
/.gitignore:
--------------------------------------------------------------------------------
1 | # Project exclude paths
2 | /.idea/
3 | /.gradle/
4 | /build/
5 | /build/classes/java/main/
6 | /build/classes/java/test/
7 | .settings/
8 | .project
9 | .classpath
10 | bin/
11 |
12 | # Minestom
13 | /.minestom_tmp/
14 | /.mixin.out/
15 | /minecraft_data/
16 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java'
3 | id 'maven-publish'
4 | }
5 |
6 | group = 'io.github.togar2'
7 | version = '1.0'
8 |
9 | java.sourceCompatibility = 21
10 | java.targetCompatibility = 21
11 | compileJava.options.encoding = 'UTF-8'
12 |
13 | java {
14 | withJavadocJar()
15 | withSourcesJar()
16 | }
17 |
18 | dependencies {
19 | compileOnly 'net.minestom:minestom-snapshots:0366b58bfe'
20 | testImplementation 'net.minestom:minestom-snapshots:0366b58bfe'
21 | }
22 |
23 | publishing {
24 | publications {
25 | mavenJava(MavenPublication) {
26 | from components.java
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TogAr2/MinestomPvP/dfb8f0c34266386ccfefd8f0093446f036118983/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
4 | networkTimeout=10000
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo.
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
48 | echo.
49 | echo Please set the JAVA_HOME variable in your environment to match the
50 | echo location of your Java installation.
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo.
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
62 | echo.
63 | echo Please set the JAVA_HOME variable in your environment to match the
64 | echo location of your Java installation.
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/jitpack.yml:
--------------------------------------------------------------------------------
1 | jdk:
2 | - openjdk21
3 | before_install:
4 | - sdk install java 21.0.2-open
5 | - sdk use java 21.0.2-open
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | dependencyResolutionManagement {
2 | repositories {
3 | mavenLocal()
4 | mavenCentral()
5 | maven {
6 | name "Jitpack"
7 | url "https://jitpack.io/"
8 | }
9 | }
10 | }
11 |
12 | rootProject.name = 'MinestomPvP'
13 |
14 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/MinestomPvP.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp;
2 |
3 | import io.github.togar2.pvp.enchantment.CombatEnchantments;
4 | import io.github.togar2.pvp.feature.CombatFeatures;
5 | import io.github.togar2.pvp.feature.config.CombatFeatureRegistry;
6 | import io.github.togar2.pvp.player.CombatPlayer;
7 | import io.github.togar2.pvp.player.CombatPlayerImpl;
8 | import io.github.togar2.pvp.potion.effect.CombatPotionEffects;
9 | import io.github.togar2.pvp.potion.item.CombatPotionTypes;
10 | import io.github.togar2.pvp.utils.AccurateLatencyListener;
11 | import net.minestom.server.MinecraftServer;
12 | import net.minestom.server.entity.Player;
13 | import net.minestom.server.entity.attribute.Attribute;
14 | import net.minestom.server.entity.attribute.AttributeInstance;
15 | import net.minestom.server.event.EventNode;
16 | import net.minestom.server.event.player.PlayerPacketOutEvent;
17 | import net.minestom.server.event.trait.EntityInstanceEvent;
18 | import net.minestom.server.network.packet.client.common.ClientKeepAlivePacket;
19 |
20 | /**
21 | * The main class of MinestomPvP, which contains the {@link MinestomPvP#init()} method.
22 | *
23 | * It can also be used to set legacy attack for a player, see {@link MinestomPvP#setLegacyAttack(Player, boolean)}.
24 | */
25 | public class MinestomPvP {
26 | /**
27 | * Equivalent to creating a new event node from {@link CombatFeatures#modernVanilla()}
28 | *
29 | * @return the event node with all modern vanilla feature listeners attached
30 | */
31 | public static EventNode events() {
32 | return CombatFeatures.modernVanilla().createNode();
33 | }
34 |
35 | /**
36 | * Equivalent to creating a new event node from {@link CombatFeatures#legacyVanilla()}
37 | *
38 | * @return the event node with all legacy (pre-1.9) vanilla feature listeners attached
39 | */
40 | public static EventNode legacyEvents() {
41 | return CombatFeatures.legacyVanilla().createNode();
42 | }
43 |
44 | /**
45 | * Disables or enables legacy attack for a player.
46 | * With legacy attack, the player has no attack speed.
47 | *
48 | * @param player the player
49 | * @param legacyAttack {@code true} if legacy attack should be enabled
50 | */
51 | public static void setLegacyAttack(Player player, boolean legacyAttack) {
52 | AttributeInstance speed = player.getAttribute(Attribute.ATTACK_SPEED);
53 | if (legacyAttack) {
54 | speed.setBaseValue(100);
55 | } else {
56 | speed.setBaseValue(speed.attribute().defaultValue());
57 | }
58 | }
59 |
60 | /**
61 | * Initializes the PvP library. This has a few side effects, for more details see {@link #init(boolean, boolean)}.
62 | */
63 | public static void init() {
64 | init(true, true);
65 | }
66 |
67 | /**
68 | * Initializes the PvP library.
69 | * This method will always initialize the registries and register some global event handlers.
70 | * Depending on the value of the parameters, it might also register:
71 | * - a custom player implementation
72 | * - a custom packet listener for {@link ClientKeepAlivePacket}
73 | *
74 | * @param player When set to true, the custom player implementation will be registered
75 | * @param keepAlive When set to true, the custom packet listener will be registered
76 | */
77 | public static void init(boolean player, boolean keepAlive) {
78 | CombatEnchantments.registerAll();
79 | CombatPotionEffects.registerAll();
80 | CombatPotionTypes.registerAll();
81 |
82 | CombatFeatureRegistry.init();
83 |
84 | CombatPlayer.init(MinecraftServer.getGlobalEventHandler());
85 |
86 | if (player) {
87 | MinecraftServer.getConnectionManager().setPlayerProvider(CombatPlayerImpl::new);
88 | }
89 |
90 | if (keepAlive) {
91 | MinecraftServer.getPacketListenerManager().setPlayListener(ClientKeepAlivePacket.class, AccurateLatencyListener::listener);
92 | MinecraftServer.getGlobalEventHandler().addListener(PlayerPacketOutEvent.class, AccurateLatencyListener::onSend);
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/damage/combat/CombatEntry.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.damage.combat;
2 |
3 | import io.github.togar2.pvp.damage.DamageTypeInfo;
4 | import io.github.togar2.pvp.utils.EntityUtil;
5 | import net.kyori.adventure.text.Component;
6 | import net.minestom.server.entity.Entity;
7 | import net.minestom.server.entity.LivingEntity;
8 | import net.minestom.server.entity.damage.Damage;
9 | import org.jetbrains.annotations.Nullable;
10 |
11 | public record CombatEntry(Damage damage, @Nullable String fallLocation, double fallDistance) {
12 |
13 | public String getMessageFallLocation() {
14 | return fallLocation == null ? "generic" : fallLocation;
15 | }
16 |
17 | public double getFallDistance() {
18 | DamageTypeInfo info = DamageTypeInfo.of(damage.getType());
19 | return info.outOfWorld() ? Double.MAX_VALUE : fallDistance;
20 | }
21 |
22 | public boolean isCombat() {
23 | return damage.getAttacker() instanceof LivingEntity;
24 | }
25 |
26 | public @Nullable Entity getAttacker() {
27 | return damage.getAttacker();
28 | }
29 |
30 | public @Nullable Component getAttackerName() {
31 | return getAttacker() == null ? null : EntityUtil.getName(getAttacker());
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/enchantment/CombatEnchantment.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.enchantment;
2 |
3 | import io.github.togar2.pvp.feature.FeatureType;
4 | import io.github.togar2.pvp.feature.config.FeatureConfiguration;
5 | import io.github.togar2.pvp.feature.enchantment.EnchantmentFeature;
6 | import net.minestom.server.entity.Entity;
7 | import net.minestom.server.entity.EquipmentSlot;
8 | import net.minestom.server.entity.LivingEntity;
9 | import net.minestom.server.entity.damage.DamageType;
10 | import net.minestom.server.item.ItemStack;
11 | import net.minestom.server.item.enchant.Enchantment;
12 | import net.minestom.server.registry.DynamicRegistry;
13 |
14 | import java.util.HashMap;
15 | import java.util.Map;
16 | import java.util.Set;
17 |
18 | public class CombatEnchantment {
19 | private final DynamicRegistry.Key enchantment;
20 | private final EquipmentSlot[] slotTypes;
21 |
22 | private final Set> dependencies;
23 |
24 | public CombatEnchantment(DynamicRegistry.Key enchantment, EquipmentSlot... slotTypes) {
25 | this(enchantment, Set.of(), slotTypes);
26 | }
27 |
28 | public CombatEnchantment(DynamicRegistry.Key enchantment,
29 | Set> dependencies, EquipmentSlot... slotTypes) {
30 | this.enchantment = enchantment;
31 | this.dependencies = dependencies;
32 | this.slotTypes = slotTypes;
33 | }
34 |
35 | public DynamicRegistry.Key getEnchantment() {
36 | return enchantment;
37 | }
38 |
39 | public Set> getDependencies() {
40 | return dependencies;
41 | }
42 |
43 | public Map getEquipment(LivingEntity entity) {
44 | Map map = new HashMap<>();
45 |
46 | for (EquipmentSlot slot : this.slotTypes) {
47 | ItemStack itemStack = entity.getEquipment(slot);
48 | if (!itemStack.isAir()) {
49 | map.put(slot, itemStack);
50 | }
51 | }
52 |
53 | return map;
54 | }
55 |
56 | public int getProtectionAmount(int level, DamageType damageType, EnchantmentFeature feature, FeatureConfiguration configuration) {
57 | return 0;
58 | }
59 |
60 | public float getAttackDamage(int level, EntityGroup group, EnchantmentFeature feature, FeatureConfiguration configuration) {
61 | return 0.0F;
62 | }
63 |
64 | public void onTargetDamaged(LivingEntity user, Entity target, int level, EnchantmentFeature feature, FeatureConfiguration configuration) {}
65 | public void onUserDamaged(LivingEntity user, LivingEntity attacker, int level, EnchantmentFeature feature, FeatureConfiguration configuration) {}
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/enchantment/CombatEnchantments.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.enchantment;
2 |
3 | import io.github.togar2.pvp.enchantment.enchantments.DamageEnchantment;
4 | import io.github.togar2.pvp.enchantment.enchantments.ImpalingEnchantment;
5 | import io.github.togar2.pvp.enchantment.enchantments.ProtectionEnchantment;
6 | import io.github.togar2.pvp.enchantment.enchantments.ThornsEnchantment;
7 | import io.github.togar2.pvp.feature.FeatureType;
8 | import net.minestom.server.entity.EquipmentSlot;
9 | import net.minestom.server.item.enchant.Enchantment;
10 | import net.minestom.server.registry.DynamicRegistry;
11 |
12 | import java.util.HashMap;
13 | import java.util.HashSet;
14 | import java.util.Map;
15 | import java.util.Set;
16 |
17 | public class CombatEnchantments {
18 | private static final Map, CombatEnchantment> ENCHANTMENTS = new HashMap<>();
19 |
20 | public static CombatEnchantment get(DynamicRegistry.Key enchantment) {
21 | return ENCHANTMENTS.get(enchantment);
22 | }
23 |
24 | public static void register(CombatEnchantment... enchantments) {
25 | for (CombatEnchantment enchantment : enchantments) {
26 | ENCHANTMENTS.put(enchantment.getEnchantment(), enchantment);
27 | }
28 | }
29 |
30 | public static FeatureType>[] getAllFeatureDependencies() {
31 | Set> features = new HashSet<>();
32 |
33 | for (CombatEnchantment enchantment : ENCHANTMENTS.values()) {
34 | features.addAll(enchantment.getDependencies());
35 | }
36 |
37 | return features.toArray(FeatureType[]::new);
38 | }
39 |
40 | private static boolean registered = false;
41 |
42 | static {
43 | registerAll();
44 | }
45 |
46 | public static void registerAll() {
47 | if (registered) return;
48 | registered = true;
49 |
50 | EquipmentSlot[] ALL_ARMOR_SLOTS = EquipmentSlot.armors().toArray(EquipmentSlot[]::new);
51 |
52 | register(
53 | new ProtectionEnchantment(Enchantment.PROTECTION, ProtectionEnchantment.Type.ALL, ALL_ARMOR_SLOTS),
54 | new ProtectionEnchantment(Enchantment.FIRE_PROTECTION, ProtectionEnchantment.Type.FIRE, ALL_ARMOR_SLOTS),
55 | new ProtectionEnchantment(Enchantment.FEATHER_FALLING, ProtectionEnchantment.Type.FALL, ALL_ARMOR_SLOTS),
56 | new ProtectionEnchantment(Enchantment.BLAST_PROTECTION, ProtectionEnchantment.Type.EXPLOSION, ALL_ARMOR_SLOTS),
57 | new ProtectionEnchantment(Enchantment.PROJECTILE_PROTECTION, ProtectionEnchantment.Type.PROJECTILE, ALL_ARMOR_SLOTS),
58 | new CombatEnchantment(Enchantment.RESPIRATION, ALL_ARMOR_SLOTS),
59 | new CombatEnchantment(Enchantment.AQUA_AFFINITY, ALL_ARMOR_SLOTS),
60 | new ThornsEnchantment(ALL_ARMOR_SLOTS),
61 | new CombatEnchantment(Enchantment.DEPTH_STRIDER, ALL_ARMOR_SLOTS),
62 | new CombatEnchantment(Enchantment.FROST_WALKER, EquipmentSlot.BOOTS),
63 | new CombatEnchantment(Enchantment.BINDING_CURSE, ALL_ARMOR_SLOTS),
64 | new CombatEnchantment(Enchantment.SOUL_SPEED, EquipmentSlot.BOOTS),
65 | new DamageEnchantment(Enchantment.SHARPNESS, DamageEnchantment.Type.ALL, EquipmentSlot.MAIN_HAND),
66 | new DamageEnchantment(Enchantment.SMITE, DamageEnchantment.Type.UNDEAD, EquipmentSlot.MAIN_HAND),
67 | new DamageEnchantment(Enchantment.BANE_OF_ARTHROPODS, DamageEnchantment.Type.ARTHROPODS, EquipmentSlot.MAIN_HAND),
68 | new CombatEnchantment(Enchantment.KNOCKBACK, EquipmentSlot.MAIN_HAND),
69 | new CombatEnchantment(Enchantment.FIRE_ASPECT, EquipmentSlot.MAIN_HAND),
70 | new CombatEnchantment(Enchantment.LOOTING, EquipmentSlot.MAIN_HAND),
71 | new CombatEnchantment(Enchantment.SWEEPING_EDGE, EquipmentSlot.MAIN_HAND),
72 | new CombatEnchantment(Enchantment.EFFICIENCY, EquipmentSlot.MAIN_HAND),
73 | new CombatEnchantment(Enchantment.SILK_TOUCH, EquipmentSlot.MAIN_HAND),
74 | new CombatEnchantment(Enchantment.UNBREAKING, EquipmentSlot.MAIN_HAND),
75 | new CombatEnchantment(Enchantment.FORTUNE, EquipmentSlot.MAIN_HAND),
76 | new CombatEnchantment(Enchantment.POWER, EquipmentSlot.MAIN_HAND),
77 | new CombatEnchantment(Enchantment.PUNCH, EquipmentSlot.MAIN_HAND),
78 | new CombatEnchantment(Enchantment.FLAME, EquipmentSlot.MAIN_HAND),
79 | new CombatEnchantment(Enchantment.INFINITY, EquipmentSlot.MAIN_HAND),
80 | new CombatEnchantment(Enchantment.LUCK_OF_THE_SEA, EquipmentSlot.MAIN_HAND),
81 | new CombatEnchantment(Enchantment.LURE, EquipmentSlot.MAIN_HAND),
82 | new CombatEnchantment(Enchantment.LOYALTY, EquipmentSlot.MAIN_HAND),
83 | new ImpalingEnchantment(EquipmentSlot.MAIN_HAND),
84 | new CombatEnchantment(Enchantment.RIPTIDE, EquipmentSlot.MAIN_HAND),
85 | new CombatEnchantment(Enchantment.CHANNELING, EquipmentSlot.MAIN_HAND),
86 | new CombatEnchantment(Enchantment.MULTISHOT, EquipmentSlot.MAIN_HAND),
87 | new CombatEnchantment(Enchantment.QUICK_CHARGE, EquipmentSlot.MAIN_HAND),
88 | new CombatEnchantment(Enchantment.PIERCING, EquipmentSlot.MAIN_HAND),
89 | new CombatEnchantment(Enchantment.MENDING, EquipmentSlot.values()),
90 | new CombatEnchantment(Enchantment.VANISHING_CURSE, EquipmentSlot.values())
91 | );
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/enchantment/EntityGroup.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.enchantment;
2 |
3 | import net.minestom.server.entity.EntityType;
4 | import net.minestom.server.entity.LivingEntity;
5 |
6 | public enum EntityGroup {
7 | DEFAULT,
8 | UNDEAD,
9 | ARTHROPOD,
10 | ILLAGER,
11 | AQUATIC;
12 |
13 | public static EntityGroup ofEntity(LivingEntity entity) {
14 | EntityType entityType = entity.getEntityType();
15 | if (entityType == EntityType.BEE || entityType == EntityType.CAVE_SPIDER || entityType == EntityType.ENDERMITE || entityType == EntityType.SILVERFISH || entityType == EntityType.SPIDER) {
16 | return EntityGroup.ARTHROPOD;
17 | } else if (entityType == EntityType.COD || entityType == EntityType.DOLPHIN || entityType == EntityType.ELDER_GUARDIAN || entityType == EntityType.GUARDIAN || entityType == EntityType.PUFFERFISH || entityType == EntityType.SALMON || entityType == EntityType.SQUID || entityType == EntityType.TROPICAL_FISH || entityType == EntityType.TURTLE) {
18 | return EntityGroup.AQUATIC;
19 | } else if (EntityType.DROWNED == entityType || EntityType.HUSK == entityType || EntityType.PHANTOM == entityType || EntityType.SKELETON == entityType || EntityType.SKELETON_HORSE == entityType || EntityType.STRAY == entityType || EntityType.WITHER == entityType || EntityType.WITHER_SKELETON == entityType || EntityType.ZOGLIN == entityType || EntityType.ZOMBIE == entityType || EntityType.ZOMBIE_HORSE == entityType || EntityType.ZOMBIE_VILLAGER == entityType || EntityType.ZOMBIFIED_PIGLIN == entityType) {
20 | return EntityGroup.UNDEAD;
21 | } else if (EntityType.EVOKER == entityType || EntityType.ILLUSIONER == entityType || EntityType.PILLAGER == entityType || EntityType.VINDICATOR == entityType) {
22 | return EntityGroup.ILLAGER;
23 | }
24 |
25 | return EntityGroup.DEFAULT;
26 | }
27 |
28 | public boolean isUndead() {
29 | return this == UNDEAD;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/enchantment/enchantments/DamageEnchantment.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.enchantment.enchantments;
2 |
3 | import io.github.togar2.pvp.enchantment.CombatEnchantment;
4 | import io.github.togar2.pvp.enchantment.EntityGroup;
5 | import io.github.togar2.pvp.feature.FeatureType;
6 | import io.github.togar2.pvp.feature.config.FeatureConfiguration;
7 | import io.github.togar2.pvp.feature.enchantment.EnchantmentFeature;
8 | import io.github.togar2.pvp.utils.PotionFlags;
9 | import net.minestom.server.entity.Entity;
10 | import net.minestom.server.entity.EquipmentSlot;
11 | import net.minestom.server.entity.LivingEntity;
12 | import net.minestom.server.item.enchant.Enchantment;
13 | import net.minestom.server.potion.Potion;
14 | import net.minestom.server.potion.PotionEffect;
15 | import net.minestom.server.registry.DynamicRegistry;
16 |
17 | import java.util.Set;
18 | import java.util.concurrent.ThreadLocalRandom;
19 |
20 | public class DamageEnchantment extends CombatEnchantment {
21 | private final Type type;
22 |
23 | public DamageEnchantment(DynamicRegistry.Key enchantment, Type type, EquipmentSlot... slotTypes) {
24 | super(enchantment, Set.of(FeatureType.VERSION), slotTypes);
25 | this.type = type;
26 | }
27 |
28 | @Override
29 | public float getAttackDamage(int level, EntityGroup group,
30 | EnchantmentFeature feature, FeatureConfiguration configuration) {
31 | if (type == Type.ALL) {
32 | if (configuration.get(FeatureType.VERSION).legacy()) return level * 1.25F;
33 | return 1.0F + (float) Math.max(0, level - 1) * 0.5F;
34 | } else if (type == Type.UNDEAD && group == EntityGroup.UNDEAD) {
35 | return (float) level * 2.5F;
36 | } else {
37 | return type == Type.ARTHROPODS && group == EntityGroup.ARTHROPOD ? (float) level * 2.5F : 0.0F;
38 | }
39 | }
40 |
41 | @Override
42 | public void onTargetDamaged(LivingEntity user, Entity target, int level,
43 | EnchantmentFeature feature, FeatureConfiguration configuration) {
44 | if (target instanceof LivingEntity livingEntity) {
45 | if (type == Type.ARTHROPODS && EntityGroup.ofEntity(livingEntity) == EntityGroup.ARTHROPOD) {
46 | int i = 20 + ThreadLocalRandom.current().nextInt(10 * level);
47 | livingEntity.addEffect(new Potion(PotionEffect.SLOWNESS, (byte) 3, i, PotionFlags.defaultFlags()));
48 | }
49 | }
50 | }
51 |
52 | public enum Type {
53 | ALL, UNDEAD, ARTHROPODS
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/enchantment/enchantments/ImpalingEnchantment.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.enchantment.enchantments;
2 |
3 | import io.github.togar2.pvp.enchantment.CombatEnchantment;
4 | import io.github.togar2.pvp.enchantment.EntityGroup;
5 | import io.github.togar2.pvp.feature.config.FeatureConfiguration;
6 | import io.github.togar2.pvp.feature.enchantment.EnchantmentFeature;
7 | import net.minestom.server.entity.EquipmentSlot;
8 | import net.minestom.server.item.enchant.Enchantment;
9 |
10 | public class ImpalingEnchantment extends CombatEnchantment {
11 | public ImpalingEnchantment(EquipmentSlot... slotTypes) {
12 | super(Enchantment.IMPALING, slotTypes);
13 | }
14 |
15 | @Override
16 | public float getAttackDamage(int level, EntityGroup group,
17 | EnchantmentFeature feature, FeatureConfiguration configuration) {
18 | return group == EntityGroup.AQUATIC ? (float) level * 2.5F : 0.0F;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/enchantment/enchantments/ProtectionEnchantment.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.enchantment.enchantments;
2 |
3 | import io.github.togar2.pvp.damage.DamageTypeInfo;
4 | import io.github.togar2.pvp.enchantment.CombatEnchantment;
5 | import io.github.togar2.pvp.feature.config.FeatureConfiguration;
6 | import io.github.togar2.pvp.feature.enchantment.EnchantmentFeature;
7 | import net.minestom.server.MinecraftServer;
8 | import net.minestom.server.entity.EquipmentSlot;
9 | import net.minestom.server.entity.damage.DamageType;
10 | import net.minestom.server.item.enchant.Enchantment;
11 | import net.minestom.server.registry.DynamicRegistry;
12 |
13 | public class ProtectionEnchantment extends CombatEnchantment {
14 | private final Type type;
15 |
16 | public ProtectionEnchantment(DynamicRegistry.Key enchantment, Type type, EquipmentSlot... slotTypes) {
17 | super(enchantment, slotTypes);
18 | this.type = type;
19 | }
20 |
21 | @Override
22 | public int getProtectionAmount(int level, DamageType damageType,
23 | EnchantmentFeature feature, FeatureConfiguration configuration) {
24 | DamageTypeInfo damageTypeInfo = DamageTypeInfo.of(MinecraftServer.getDamageTypeRegistry().getKey(damageType));
25 | if (damageTypeInfo.outOfWorld()) {
26 | return 0;
27 | } else if (type == Type.ALL) {
28 | return level;
29 | } else if (type == Type.FIRE && damageTypeInfo.fire()) {
30 | return level * 2;
31 | } else if (type == Type.FALL && damageTypeInfo.fall()) {
32 | return level * 3;
33 | } else if (type == Type.EXPLOSION && damageTypeInfo.explosive()) {
34 | return level * 2;
35 | } else {
36 | return type == Type.PROJECTILE && damageTypeInfo.projectile() ? level * 2 : 0;
37 | }
38 | }
39 |
40 | public enum Type {
41 | ALL, FIRE, FALL, EXPLOSION, PROJECTILE
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/enchantment/enchantments/ThornsEnchantment.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.enchantment.enchantments;
2 |
3 | import io.github.togar2.pvp.enchantment.CombatEnchantment;
4 | import io.github.togar2.pvp.feature.FeatureType;
5 | import io.github.togar2.pvp.feature.config.FeatureConfiguration;
6 | import io.github.togar2.pvp.feature.enchantment.EnchantmentFeature;
7 | import net.minestom.server.entity.EquipmentSlot;
8 | import net.minestom.server.entity.LivingEntity;
9 | import net.minestom.server.entity.damage.Damage;
10 | import net.minestom.server.entity.damage.DamageType;
11 | import net.minestom.server.item.ItemStack;
12 | import net.minestom.server.item.enchant.Enchantment;
13 |
14 | import java.util.Map;
15 | import java.util.Set;
16 | import java.util.concurrent.ThreadLocalRandom;
17 |
18 | public class ThornsEnchantment extends CombatEnchantment {
19 | public ThornsEnchantment(EquipmentSlot... slotTypes) {
20 | super(Enchantment.THORNS, Set.of(FeatureType.ITEM_DAMAGE), slotTypes);
21 | }
22 |
23 | @Override
24 | public void onUserDamaged(LivingEntity user, LivingEntity attacker, int level,
25 | EnchantmentFeature feature, FeatureConfiguration configuration) {
26 | ThreadLocalRandom random = ThreadLocalRandom.current();
27 | if (!shouldDamageAttacker(level, random)) return;
28 |
29 | Map.Entry entry = feature.pickRandom(user, Enchantment.THORNS);
30 |
31 | if (attacker != null) {
32 | attacker.damage(new Damage(DamageType.THORNS, user, user, null, getDamageAmount(level, random)));
33 | }
34 |
35 | if (entry != null) {
36 | configuration.get(FeatureType.ITEM_DAMAGE).damageEquipment(user, entry.getKey(), 2);
37 | }
38 | }
39 |
40 | private static boolean shouldDamageAttacker(int level, ThreadLocalRandom random) {
41 | if (level <= 0) return false;
42 | return random.nextFloat() < 0.15f * level;
43 | }
44 |
45 | private static int getDamageAmount(int level, ThreadLocalRandom random) {
46 | return level > 10 ? level - 10 : 1 + random.nextInt(4);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/entity/explosion/CrystalEntity.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.entity.explosion;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 |
5 | import io.github.togar2.pvp.damage.DamageTypeInfo;
6 | import net.minestom.server.entity.EntityType;
7 | import net.minestom.server.entity.LivingEntity;
8 | import net.minestom.server.entity.damage.Damage;
9 | import net.minestom.server.entity.metadata.other.EndCrystalMeta;
10 | import net.minestom.server.instance.Instance;
11 | import net.minestom.server.instance.block.Block;
12 |
13 | public class CrystalEntity extends LivingEntity {
14 | private final boolean fire;
15 |
16 | public CrystalEntity(boolean fire, boolean showingBottom) {
17 | super(EntityType.END_CRYSTAL);
18 | this.fire = fire;
19 | setNoGravity(true);
20 | hasPhysics = false;
21 | ((EndCrystalMeta) getEntityMeta()).setShowingBottom(showingBottom);
22 | }
23 |
24 | public CrystalEntity() {
25 | this(false, false);
26 | }
27 |
28 | @Override
29 | public void update(long time) {
30 | if (fire && !instance.getBlock(position).compare(Block.FIRE))
31 | instance.setBlock(position, Block.FIRE);
32 | }
33 |
34 | @Override
35 | public boolean damage(@NotNull Damage damage) {
36 | if (isDead() || isRemoved())
37 | return false;
38 | if (isInvulnerable() || isImmune(damage.getType())) {
39 | return false;
40 | }
41 |
42 | // Set the last damage type since the event is not cancelled
43 | this.lastDamage = damage;
44 |
45 | // Save this.instance locally
46 | Instance instance = this.instance;
47 | remove();
48 | if (instance.getExplosionSupplier() != null
49 | && !DamageTypeInfo.of(damage.getType()).explosive()) {
50 | instance.explode((float) position.x(), (float) position.y(), (float) position.z(), 6.0f);
51 | }
52 |
53 | return true;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/entity/explosion/TntEntity.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.entity.explosion;
2 |
3 | import net.kyori.adventure.nbt.CompoundBinaryTag;
4 | import net.minestom.server.ServerFlag;
5 | import net.minestom.server.collision.BoundingBox;
6 | import net.minestom.server.coordinate.Pos;
7 | import net.minestom.server.coordinate.Vec;
8 | import net.minestom.server.entity.Entity;
9 | import net.minestom.server.entity.EntityType;
10 | import net.minestom.server.entity.metadata.other.PrimedTntMeta;
11 | import net.minestom.server.instance.Instance;
12 | import org.jetbrains.annotations.Nullable;
13 |
14 | import java.util.concurrent.ThreadLocalRandom;
15 |
16 | public class TntEntity extends Entity {
17 | private final Entity causingEntity;
18 |
19 | public TntEntity(@Nullable Entity causingEntity) {
20 | super(EntityType.TNT);
21 | this.causingEntity = causingEntity;
22 |
23 | double angle = ThreadLocalRandom.current().nextDouble() * 6.2831854820251465;
24 | setVelocity(new Vec(-Math.sin(angle) * 0.02, 0.2f, -Math.cos(angle) * 0.02)
25 | .mul(ServerFlag.SERVER_TICKS_PER_SECOND));
26 | }
27 |
28 | public int getFuse() {
29 | return ((PrimedTntMeta) getEntityMeta()).getFuseTime();
30 | }
31 |
32 | public void setFuse(int fuse) {
33 | ((PrimedTntMeta) getEntityMeta()).setFuseTime(fuse);
34 | }
35 |
36 | @Override
37 | public void update(long time) {
38 | if (onGround) velocity = velocity.mul(0.7, -0.5, 0.7);
39 | int newFuse = getFuse() - 1;
40 | setFuse(newFuse);
41 | if (newFuse <= 0) {
42 | Instance instance = this.instance;
43 | Pos position = this.position;
44 | BoundingBox boundingBox = this.boundingBox;
45 |
46 | remove();
47 | if (instance.getExplosionSupplier() != null) instance.explode(
48 | (float) position.x(),
49 | (float) (position.y() + boundingBox.height() * 0.0625),
50 | (float) position.z(),
51 | 4.0f,
52 | causingEntity == null ? null
53 | : CompoundBinaryTag.builder()
54 | .putString("causingEntity", causingEntity.getUuid().toString())
55 | .build()
56 | );
57 | }
58 | }
59 |
60 | @Override
61 | public double getEyeHeight() {
62 | return 0.15;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/entity/projectile/Arrow.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.entity.projectile;
2 |
3 | import io.github.togar2.pvp.feature.effect.EffectFeature;
4 | import io.github.togar2.pvp.feature.enchantment.EnchantmentFeature;
5 | import net.minestom.server.entity.Entity;
6 | import net.minestom.server.entity.EntityType;
7 | import net.minestom.server.entity.LivingEntity;
8 | import net.minestom.server.entity.metadata.projectile.ArrowMeta;
9 | import net.minestom.server.item.ItemComponent;
10 | import net.minestom.server.item.ItemStack;
11 | import net.minestom.server.item.Material;
12 | import net.minestom.server.item.component.PotionContents;
13 | import net.minestom.server.potion.CustomPotionEffect;
14 | import org.jetbrains.annotations.NotNull;
15 | import org.jetbrains.annotations.Nullable;
16 |
17 | import java.util.ArrayList;
18 | import java.util.List;
19 | import java.util.Objects;
20 | import java.util.function.UnaryOperator;
21 |
22 | public class Arrow extends AbstractArrow {
23 | public static final ItemStack DEFAULT_ARROW = ItemStack.of(Material.ARROW);
24 |
25 | private final EffectFeature effectFeature;
26 |
27 | private ItemStack itemStack = DEFAULT_ARROW;
28 |
29 | public Arrow(@Nullable Entity shooter, EffectFeature effectFeature, EnchantmentFeature enchantmentFeature) {
30 | super(shooter, EntityType.ARROW, enchantmentFeature);
31 | this.effectFeature = effectFeature;
32 | }
33 |
34 | @Override
35 | public void update(long time) {
36 | super.update(time);
37 | if (onGround && stuckTime >= 600 && (!itemStack.has(ItemComponent.POTION_CONTENTS)
38 | || !Objects.equals(itemStack.get(ItemComponent.POTION_CONTENTS), PotionContents.EMPTY))) {
39 | triggerStatus((byte) 0);
40 | itemStack = DEFAULT_ARROW;
41 | }
42 | }
43 |
44 | @Override
45 | protected ItemStack getPickupItem() {
46 | return itemStack;
47 | }
48 |
49 | public void setItemStack(ItemStack itemStack) {
50 | this.itemStack = itemStack;
51 | updateColor();
52 | }
53 |
54 | @Override
55 | protected void onHurt(LivingEntity entity) {
56 | effectFeature.addArrowEffects(entity, this);
57 | }
58 |
59 | private void updateColor() {
60 | PotionContents potionContents = itemStack.get(ItemComponent.POTION_CONTENTS);
61 | if (potionContents == null || potionContents.equals(PotionContents.EMPTY)) {
62 | setColor(-1);
63 | return;
64 | }
65 |
66 | setColor(effectFeature.getPotionColor(potionContents));
67 | }
68 |
69 | private void setColor(int color) {
70 | ((ArrowMeta) getEntityMeta()).setColor(color);
71 | }
72 |
73 | public @NotNull PotionContents getPotion() {
74 | return itemStack.get(ItemComponent.POTION_CONTENTS, PotionContents.EMPTY);
75 | }
76 |
77 | public void setPotion(@NotNull PotionContents potion) {
78 | if (itemStack.material() != Material.TIPPED_ARROW)
79 | itemStack = ItemStack.of(Material.TIPPED_ARROW);
80 | itemStack = itemStack.with(ItemComponent.POTION_CONTENTS, potion);
81 | updateColor();
82 | }
83 |
84 | public void addArrowEffect(CustomPotionEffect effect) {
85 | itemStack = itemStack.with(ItemComponent.POTION_CONTENTS, (UnaryOperator) potionContents -> {
86 | List list = new ArrayList<>(potionContents.customEffects());
87 | list.add(effect);
88 | return new PotionContents(potionContents.potion(), potionContents.customColor(), list);
89 | });
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/entity/projectile/ItemHoldingProjectile.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.entity.projectile;
2 |
3 | import net.minestom.server.item.ItemStack;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | public interface ItemHoldingProjectile {
7 | void setItem(@NotNull ItemStack item);
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/entity/projectile/Snowball.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.entity.projectile;
2 |
3 | import net.minestom.server.entity.Entity;
4 | import net.minestom.server.entity.EntityType;
5 | import net.minestom.server.entity.LivingEntity;
6 | import net.minestom.server.entity.damage.Damage;
7 | import net.minestom.server.entity.damage.DamageType;
8 | import net.minestom.server.entity.metadata.item.SnowballMeta;
9 | import net.minestom.server.item.ItemStack;
10 | import org.jetbrains.annotations.NotNull;
11 | import org.jetbrains.annotations.Nullable;
12 |
13 | public class Snowball extends CustomEntityProjectile implements ItemHoldingProjectile {
14 |
15 | public Snowball(@Nullable Entity shooter) {
16 | super(shooter, EntityType.SNOWBALL);
17 | }
18 |
19 | @Override
20 | public boolean onHit(Entity entity) {
21 | triggerStatus((byte) 3); // Snowball particles
22 |
23 | int damage = entity.getEntityType() == EntityType.BLAZE ? 3 : 0;
24 | ((LivingEntity) entity).damage(new Damage(DamageType.THROWN, this, getShooter(), null, damage));
25 |
26 | return true;
27 | }
28 |
29 | @Override
30 | public boolean onStuck() {
31 | triggerStatus((byte) 3); // Snowball particles
32 |
33 | return true;
34 | }
35 |
36 | @Override
37 | public void setItem(@NotNull ItemStack item) {
38 | ((SnowballMeta) getEntityMeta()).setItem(item);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/entity/projectile/SpectralArrow.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.entity.projectile;
2 |
3 | import io.github.togar2.pvp.feature.enchantment.EnchantmentFeature;
4 | import io.github.togar2.pvp.utils.PotionFlags;
5 | import net.minestom.server.entity.Entity;
6 | import net.minestom.server.entity.EntityType;
7 | import net.minestom.server.entity.LivingEntity;
8 | import net.minestom.server.item.ItemStack;
9 | import net.minestom.server.item.Material;
10 | import net.minestom.server.potion.Potion;
11 | import net.minestom.server.potion.PotionEffect;
12 | import org.jetbrains.annotations.Nullable;
13 |
14 | public class SpectralArrow extends AbstractArrow {
15 | private static final ItemStack PICKUP_ITEM = ItemStack.of(Material.SPECTRAL_ARROW);
16 | private int duration = 200;
17 |
18 | public SpectralArrow(@Nullable Entity shooter, EnchantmentFeature enchantmentFeature) {
19 | super(shooter, EntityType.SPECTRAL_ARROW, enchantmentFeature);
20 | }
21 |
22 | public int getDuration() {
23 | return duration;
24 | }
25 |
26 | public void setDuration(int duration) {
27 | this.duration = duration;
28 | }
29 |
30 | @Override
31 | protected ItemStack getPickupItem() {
32 | return PICKUP_ITEM;
33 | }
34 |
35 | @Override
36 | protected void onHurt(LivingEntity entity) {
37 | entity.addEffect(new Potion(PotionEffect.GLOWING, (byte) 0, duration, PotionFlags.defaultFlags()));
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/entity/projectile/ThrownEgg.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.entity.projectile;
2 |
3 | import net.minestom.server.entity.Entity;
4 | import net.minestom.server.entity.EntityType;
5 | import net.minestom.server.entity.LivingEntity;
6 | import net.minestom.server.entity.damage.Damage;
7 | import net.minestom.server.entity.damage.DamageType;
8 | import net.minestom.server.entity.metadata.item.ThrownEggMeta;
9 | import net.minestom.server.item.ItemStack;
10 | import org.jetbrains.annotations.NotNull;
11 | import org.jetbrains.annotations.Nullable;
12 |
13 | public class ThrownEgg extends CustomEntityProjectile implements ItemHoldingProjectile {
14 |
15 | public ThrownEgg(@Nullable Entity shooter) {
16 | super(shooter, EntityType.EGG);
17 | }
18 |
19 | @Override
20 | public boolean onHit(Entity entity) {
21 | triggerStatus((byte) 3); // Egg particles
22 |
23 | ((LivingEntity) entity).damage(new Damage(DamageType.THROWN, this, getShooter(), null, 0));
24 |
25 | return true;
26 | }
27 |
28 | @Override
29 | public boolean onStuck() {
30 | triggerStatus((byte) 3); // Egg particles
31 |
32 | return true;
33 | }
34 |
35 | @Override
36 | public void setItem(@NotNull ItemStack item) {
37 | ((ThrownEggMeta) getEntityMeta()).setItem(item);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/entity/projectile/ThrownEnderpearl.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.entity.projectile;
2 |
3 | import io.github.togar2.pvp.feature.fall.FallFeature;
4 | import net.minestom.server.coordinate.Pos;
5 | import net.minestom.server.entity.Entity;
6 | import net.minestom.server.entity.EntityType;
7 | import net.minestom.server.entity.LivingEntity;
8 | import net.minestom.server.entity.Player;
9 | import net.minestom.server.entity.damage.Damage;
10 | import net.minestom.server.entity.damage.DamageType;
11 | import net.minestom.server.entity.metadata.item.ThrownEnderPearlMeta;
12 | import net.minestom.server.item.ItemStack;
13 | import net.minestom.server.network.packet.server.play.ParticlePacket;
14 | import net.minestom.server.particle.Particle;
15 | import org.jetbrains.annotations.NotNull;
16 | import org.jetbrains.annotations.Nullable;
17 |
18 | import java.util.concurrent.ThreadLocalRandom;
19 |
20 | public class ThrownEnderpearl extends CustomEntityProjectile implements ItemHoldingProjectile {
21 | private Pos prevPos = Pos.ZERO;
22 |
23 | private final FallFeature fallFeature;
24 |
25 | public ThrownEnderpearl(@Nullable Entity shooter, FallFeature fallFeature) {
26 | super(shooter, EntityType.ENDER_PEARL);
27 | this.fallFeature = fallFeature;
28 | }
29 |
30 | private void teleportOwner() {
31 | Pos position = prevPos;
32 | ThreadLocalRandom random = ThreadLocalRandom.current();
33 |
34 | for (int i = 0; i < 32; i++) {
35 | sendPacketToViewersAndSelf(new ParticlePacket(
36 | Particle.PORTAL, false, false,
37 | position.x(), position.y() + random.nextDouble() * 2, position.z(),
38 | (float) random.nextGaussian(), 0.0F, (float) random.nextGaussian(),
39 | 0, 1
40 | ));
41 | }
42 |
43 | if (isRemoved()) return;
44 |
45 | Entity shooter = getShooter();
46 | if (shooter != null) {
47 | Pos shooterPos = shooter.getPosition();
48 | position = position.withPitch(shooterPos.pitch()).withYaw(shooterPos.yaw());
49 | }
50 |
51 | if (shooter instanceof Player player) {
52 | if (player.isOnline() && player.getInstance() == getInstance()
53 | && player.getPlayerMeta().getBedInWhichSleepingPosition() == null) {
54 | if (player.getVehicle() != null) {
55 | player.getVehicle().removePassenger(player);
56 | }
57 |
58 | player.teleport(position);
59 | fallFeature.resetFallDistance(player);
60 |
61 | player.damage(DamageType.FALL, 5.0F);
62 | }
63 | } else if (shooter != null) {
64 | shooter.teleport(position);
65 | }
66 | }
67 |
68 | @Override
69 | public boolean onHit(Entity entity) {
70 | ((LivingEntity) entity).damage(new Damage(DamageType.THROWN, this, getShooter(), null, 0));
71 |
72 | teleportOwner();
73 | return true;
74 | }
75 |
76 | @Override
77 | public boolean onStuck() {
78 | teleportOwner();
79 | return true;
80 | }
81 |
82 | @Override
83 | public void tick(long time) {
84 | Entity shooter = getShooter();
85 | if (shooter instanceof Player && ((Player) shooter).isDead()) {
86 | remove();
87 | } else {
88 | prevPos = getPosition();
89 | super.tick(time);
90 | }
91 | }
92 |
93 | @Override
94 | public void setItem(@NotNull ItemStack item) {
95 | ((ThrownEnderPearlMeta) getEntityMeta()).setItem(item);
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/entity/projectile/ThrownPotion.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.entity.projectile;
2 |
3 | import io.github.togar2.pvp.feature.effect.EffectFeature;
4 | import io.github.togar2.pvp.utils.EffectUtil;
5 | import net.minestom.server.collision.BoundingBox;
6 | import net.minestom.server.coordinate.Pos;
7 | import net.minestom.server.entity.*;
8 | import net.minestom.server.entity.metadata.item.ThrownPotionMeta;
9 | import net.minestom.server.item.ItemComponent;
10 | import net.minestom.server.item.ItemStack;
11 | import net.minestom.server.item.Material;
12 | import net.minestom.server.item.component.PotionContents;
13 | import net.minestom.server.potion.Potion;
14 | import net.minestom.server.worldevent.WorldEvent;
15 | import org.jetbrains.annotations.NotNull;
16 | import org.jetbrains.annotations.Nullable;
17 |
18 | import java.util.List;
19 | import java.util.Objects;
20 | import java.util.stream.Collectors;
21 |
22 | public class ThrownPotion extends CustomEntityProjectile implements ItemHoldingProjectile {
23 | private final EffectFeature effectFeature;
24 |
25 | public ThrownPotion(@Nullable Entity shooter, EffectFeature effectFeature) {
26 | super(shooter, EntityType.POTION);
27 | this.effectFeature = effectFeature;
28 |
29 | // Why does Minestom have the wrong value 0.03 in its registries?
30 | setAerodynamics(getAerodynamics().withGravity(0.05));
31 | }
32 |
33 | @Override
34 | public boolean onHit(Entity entity) {
35 | splash(entity);
36 | return true;
37 | }
38 |
39 | @Override
40 | public boolean onStuck() {
41 | splash(null);
42 | return true;
43 | }
44 |
45 | public void splash(@Nullable Entity entity) {
46 | ItemStack item = getItem();
47 |
48 | PotionContents potionContents = item.get(ItemComponent.POTION_CONTENTS);
49 | List potions = effectFeature.getAllPotions(potionContents);
50 |
51 | if (!potions.isEmpty()) {
52 | if (item.material() == Material.LINGERING_POTION) {
53 | //TODO lingering
54 | } else {
55 | applySplash(potionContents, entity);
56 | }
57 | }
58 |
59 | Pos position = getPosition();
60 |
61 | boolean instantEffect = false;
62 | for (Potion potion : potions) {
63 | if (potion.effect().registry().isInstantaneous()) {
64 | instantEffect = true;
65 | break;
66 | }
67 | }
68 |
69 | WorldEvent effect = instantEffect ? WorldEvent.PARTICLES_INSTANT_POTION_SPLASH : WorldEvent.PARTICLES_SPELL_POTION_SPLASH;
70 | EffectUtil.sendNearby(
71 | Objects.requireNonNull(getInstance()), effect, position.blockX(),
72 | position.blockY(), position.blockZ(), effectFeature.getPotionColor(potionContents),
73 | 64.0, false
74 | );
75 | }
76 |
77 | private void applySplash(PotionContents potionContents, @Nullable Entity hitEntity) {
78 | BoundingBox boundingBox = getBoundingBox().expand(8.0, 4.0, 8.0);
79 | List entities = Objects.requireNonNull(getInstance()).getEntities().stream()
80 | .filter(entity -> boundingBox.intersectEntity(getPosition().add(0, -2, 0), entity))
81 | .filter(entity -> entity instanceof LivingEntity
82 | && !(entity instanceof Player player && player.getGameMode() == GameMode.SPECTATOR))
83 | .map(entity -> (LivingEntity) entity).collect(Collectors.toList());
84 |
85 | if (hitEntity instanceof LivingEntity && !entities.contains(hitEntity))
86 | entities.add((LivingEntity) hitEntity);
87 | if (entities.isEmpty()) return;
88 |
89 | for (LivingEntity entity : entities) {
90 | if (entity.getEntityType() == EntityType.ARMOR_STAND) continue;
91 |
92 | double distanceSquared = getDistanceSquared(entity);
93 | if (distanceSquared >= 16.0) continue;
94 |
95 | double proximity = entity == hitEntity ? 1.0 : (1.0 - Math.sqrt(distanceSquared) / 4.0);
96 | effectFeature.addSplashPotionEffects(entity, potionContents, proximity, this, getShooter());
97 | }
98 | }
99 |
100 | @NotNull
101 | public ItemStack getItem() {
102 | return ((ThrownPotionMeta) getEntityMeta()).getItem();
103 | }
104 |
105 | @Override
106 | public void setItem(@NotNull ItemStack item) {
107 | ((ThrownPotionMeta) getEntityMeta()).setItem(item);
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/enums/ToolMaterial.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.enums;
2 |
3 | import net.minestom.server.item.Material;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | public enum ToolMaterial {
9 | WOOD(0, 2.0F, 0.0F, Material.WOODEN_SWORD, Material.WOODEN_SHOVEL, Material.WOODEN_PICKAXE, Material.WOODEN_AXE, Material.WOODEN_HOE),
10 | STONE(1, 4.0F, 1.0F, Material.STONE_SWORD, Material.STONE_SHOVEL, Material.STONE_PICKAXE, Material.STONE_AXE, Material.STONE_HOE),
11 | IRON(2, 6.0F, 2.0F, Material.IRON_SWORD, Material.IRON_SHOVEL, Material.IRON_PICKAXE, Material.IRON_AXE, Material.IRON_HOE),
12 | DIAMOND(3, 8.0F, 3.0F, Material.DIAMOND_SWORD, Material.DIAMOND_SHOVEL, Material.DIAMOND_PICKAXE, Material.DIAMOND_AXE, Material.DIAMOND_HOE),
13 | GOLD(0, 12.0F, 0.0F, Material.GOLDEN_SWORD, Material.GOLDEN_SHOVEL, Material.GOLDEN_PICKAXE, Material.GOLDEN_AXE, Material.GOLDEN_HOE),
14 | NETHERITE(4, 9.0F, 4.0F, Material.NETHERITE_SWORD, Material.NETHERITE_SHOVEL, Material.NETHERITE_PICKAXE, Material.NETHERITE_AXE, Material.NETHERITE_HOE);
15 |
16 | private final int miningLevel;
17 | private final float miningSpeed;
18 | private final float attackDamage;
19 | private final Material[] items;
20 |
21 | ToolMaterial(int miningLevel, float miningSpeed, float attackDamage, Material... items) {
22 | this.miningLevel = miningLevel;
23 | this.miningSpeed = miningSpeed;
24 | this.attackDamage = attackDamage;
25 | this.items = items;
26 | }
27 |
28 | public float getMiningSpeed() {
29 | return this.miningSpeed;
30 | }
31 |
32 | public float getAttackDamage() {
33 | return this.attackDamage;
34 | }
35 |
36 | public int getMiningLevel() {
37 | return this.miningLevel;
38 | }
39 |
40 | private static final Map MATERIAL_TO_TOOL_MATERIAL = new HashMap<>();
41 |
42 | public static ToolMaterial fromMaterial(Material material) {
43 | return MATERIAL_TO_TOOL_MATERIAL.get(material);
44 | }
45 |
46 | static {
47 | for (ToolMaterial toolMaterial : values()) {
48 | for (Material material : toolMaterial.items) {
49 | MATERIAL_TO_TOOL_MATERIAL.put(material, toolMaterial);
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/events/AnchorChargeEvent.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.events;
2 |
3 | import net.minestom.server.coordinate.Point;
4 | import net.minestom.server.entity.Player;
5 | import net.minestom.server.event.trait.CancellableEvent;
6 | import net.minestom.server.event.trait.PlayerInstanceEvent;
7 | import org.jetbrains.annotations.NotNull;
8 |
9 | /**
10 | * Called when a player charges a respawn anchor.
11 | */
12 | public class AnchorChargeEvent implements PlayerInstanceEvent, CancellableEvent {
13 |
14 | private final Player player;
15 | private final Point blockPosition;
16 |
17 | private boolean cancelled;
18 |
19 | public AnchorChargeEvent(@NotNull Player player, @NotNull Point blockPosition) {
20 | this.player = player;
21 | this.blockPosition = blockPosition;
22 | }
23 |
24 | @Override
25 | public @NotNull Player getPlayer() {
26 | return player;
27 | }
28 |
29 | public @NotNull Point getBlockPosition() {
30 | return blockPosition;
31 | }
32 |
33 | @Override
34 | public boolean isCancelled() {
35 | return cancelled;
36 | }
37 |
38 | @Override
39 | public void setCancelled(boolean cancel) {
40 | this.cancelled = cancel;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/events/AnchorExplodeEvent.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.events;
2 |
3 | import net.minestom.server.coordinate.Point;
4 | import net.minestom.server.entity.Player;
5 | import net.minestom.server.event.trait.CancellableEvent;
6 | import net.minestom.server.event.trait.PlayerInstanceEvent;
7 | import org.jetbrains.annotations.NotNull;
8 |
9 | /**
10 | * Called when a player clicks on respawn anchor to explode it.
11 | */
12 | public class AnchorExplodeEvent implements PlayerInstanceEvent, CancellableEvent {
13 |
14 | private final Player player;
15 | private final Point blockPosition;
16 |
17 | private boolean cancelled;
18 |
19 | public AnchorExplodeEvent(@NotNull Player player, @NotNull Point blockPosition) {
20 | this.player = player;
21 | this.blockPosition = blockPosition;
22 | }
23 |
24 | @Override
25 | public @NotNull Player getPlayer() {
26 | return player;
27 | }
28 |
29 | public @NotNull Point getBlockPosition() {
30 | return blockPosition;
31 | }
32 |
33 | @Override
34 | public boolean isCancelled() {
35 | return cancelled;
36 | }
37 |
38 | @Override
39 | public void setCancelled(boolean cancel) {
40 | this.cancelled = cancel;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/events/CrystalPlaceEvent.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.events;
2 |
3 | import net.minestom.server.coordinate.Point;
4 | import net.minestom.server.entity.Player;
5 | import net.minestom.server.event.trait.CancellableEvent;
6 | import net.minestom.server.event.trait.PlayerInstanceEvent;
7 | import org.jetbrains.annotations.NotNull;
8 |
9 | /**
10 | * Called when a player places an end crystal.
11 | */
12 | public class CrystalPlaceEvent implements PlayerInstanceEvent, CancellableEvent {
13 |
14 | private final Player player;
15 | private Point spawnPosition;
16 |
17 | private boolean cancelled;
18 |
19 | public CrystalPlaceEvent(@NotNull Player player, @NotNull Point spawnPosition) {
20 | this.player = player;
21 | this.spawnPosition = spawnPosition;
22 | }
23 |
24 | @Override
25 | public @NotNull Player getPlayer() {
26 | return player;
27 | }
28 |
29 | public @NotNull Point getSpawnPosition() {
30 | return spawnPosition;
31 | }
32 |
33 | public void setSpawnPosition(@NotNull Point spawnPosition) {
34 | this.spawnPosition = spawnPosition;
35 | }
36 |
37 | @Override
38 | public boolean isCancelled() {
39 | return cancelled;
40 | }
41 |
42 | @Override
43 | public void setCancelled(boolean cancel) {
44 | this.cancelled = cancel;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/events/DamageBlockEvent.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.events;
2 |
3 | import net.minestom.server.entity.LivingEntity;
4 | import net.minestom.server.event.trait.CancellableEvent;
5 | import net.minestom.server.event.trait.EntityInstanceEvent;
6 | import org.jetbrains.annotations.NotNull;
7 |
8 | /**
9 | * Called when an entity blocks damage using a shield.
10 | * This event can be used to set the resulting damage.
11 | */
12 | public class DamageBlockEvent implements EntityInstanceEvent, CancellableEvent {
13 |
14 | private final LivingEntity entity;
15 | private final float damage;
16 | private float resultingDamage;
17 | private boolean knockbackAttacker;
18 |
19 | private boolean cancelled;
20 |
21 | public DamageBlockEvent(@NotNull LivingEntity entity, float damage,
22 | float resultingDamage, boolean knockbackAttacker) {
23 | this.entity = entity;
24 | this.damage = damage;
25 | this.resultingDamage = resultingDamage;
26 | this.knockbackAttacker = knockbackAttacker;
27 | }
28 |
29 | @NotNull
30 | @Override
31 | public LivingEntity getEntity() {
32 | return entity;
33 | }
34 |
35 | public boolean knockbackAttacker() {
36 | return knockbackAttacker;
37 | }
38 |
39 | /**
40 | * This fixes a bug introduced in 1.14. Prior to 1.14, the attacker would receive
41 | * knockback when the victim was blocking. In 1.14 and above, this is no longer the case.
42 | * To apply the fix, set this to true (true by default).
43 | *
44 | * @param knockbackAttacker true if the attacker should be knocked back
45 | */
46 | public void setKnockbackAttacker(boolean knockbackAttacker) {
47 | this.knockbackAttacker = knockbackAttacker;
48 | }
49 |
50 | /**
51 | * Gets the original damage dealt.
52 | *
53 | * @return the original damage
54 | */
55 | public float getDamage() {
56 | return damage;
57 | }
58 |
59 | public float getResultingDamage() {
60 | return resultingDamage;
61 | }
62 |
63 | /**
64 | * Sets the resulting damage after the block.
65 | *
66 | * @param resultingDamage the resulting damage
67 | */
68 | public void setResultingDamage(float resultingDamage) {
69 | this.resultingDamage = resultingDamage;
70 | }
71 |
72 | @Override
73 | public boolean isCancelled() {
74 | return cancelled;
75 | }
76 |
77 | @Override
78 | public void setCancelled(boolean cancel) {
79 | this.cancelled = cancel;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/events/EntityKnockbackEvent.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.events;
2 |
3 | import io.github.togar2.pvp.feature.knockback.KnockbackSettings;
4 | import net.minestom.server.entity.Entity;
5 | import net.minestom.server.event.trait.CancellableEvent;
6 | import net.minestom.server.event.trait.EntityInstanceEvent;
7 | import org.jetbrains.annotations.NotNull;
8 |
9 | /**
10 | * Called when an entity gets knocked back by another entity.
11 | * This event does not apply simply when {@code Entity.takeKnockback()} is called,
12 | * but only when an entity is attacked by another entity which causes the knockback.
13 | *
14 | * You should be aware that when the attacker has a knockback weapon, this event will be called twice:
15 | * once for the default damage knockback, once for the extra knockback.
16 | *
17 | * When the attack was a sweeping attack, this event is also called twice for the affected entities:
18 | * once for the extra sweeping knockback, once for the default knockback.
19 | *
20 | * You can determine which type of knockback this is by using {@link #getType()}.
21 | */
22 | public class EntityKnockbackEvent implements EntityInstanceEvent, CancellableEvent {
23 |
24 | private final Entity entity;
25 | private final Entity attacker;
26 | private final KnockbackType type;
27 | private KnockbackSettings settings = KnockbackSettings.DEFAULT;
28 |
29 | private boolean cancelled;
30 |
31 | public EntityKnockbackEvent(@NotNull Entity entity, @NotNull Entity attacker, KnockbackType type) {
32 | this.entity = entity;
33 | this.attacker = attacker;
34 | this.type = type;
35 | }
36 |
37 | @NotNull
38 | @Override
39 | public Entity getEntity() {
40 | return entity;
41 | }
42 |
43 | /**
44 | * Gets the attacker of the entity. In case of a projectile,
45 | * this returns the projectile itself and not the owner.
46 | *
47 | * @return the attacker
48 | */
49 | @NotNull
50 | public Entity getAttacker() {
51 | return attacker;
52 | }
53 |
54 | /**
55 | * Gets the type of knockback. See the values of {@link KnockbackType} for more information.
56 | *
57 | * @return the knockback type
58 | */
59 | public KnockbackType getType() {
60 | return type;
61 | }
62 |
63 | /**
64 | * Gets the settings of the knockback.
65 | *
66 | * @return the strength
67 | */
68 | public KnockbackSettings getSettings() {
69 | return settings;
70 | }
71 |
72 | /**
73 | * Sets the settings of the knockback.
74 | *
75 | * @param settings the strength
76 | */
77 | public void setSettings(KnockbackSettings settings) {
78 | this.settings = settings;
79 | }
80 |
81 | @Override
82 | public boolean isCancelled() {
83 | return cancelled;
84 | }
85 |
86 | @Override
87 | public void setCancelled(boolean cancel) {
88 | this.cancelled = cancel;
89 | }
90 |
91 | public enum KnockbackType {
92 | /**
93 | * Default knockback from a damage source
94 | */
95 | DAMAGE,
96 | /**
97 | * Attack knockback for strong attacks by players or from the knockback enchantment
98 | */
99 | ATTACK,
100 | /**
101 | * Sweeping knockback when an entity is close to an entity which was hit by a sweeping weapon
102 | */
103 | SWEEPING
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/events/EntityPreDeathEvent.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.events;
2 |
3 | import net.minestom.server.entity.Entity;
4 | import net.minestom.server.entity.damage.Damage;
5 | import net.minestom.server.event.trait.CancellableEvent;
6 | import net.minestom.server.event.trait.EntityInstanceEvent;
7 | import org.jetbrains.annotations.NotNull;
8 |
9 | /**
10 | * A cancellable form of the death event, which includes damage type.
11 | * Can be used to cancel the death while still applying after-damage effects, such as attack sounds.
12 | * See #setCancelDeath for more information.
13 | */
14 | public class EntityPreDeathEvent implements EntityInstanceEvent, CancellableEvent {
15 |
16 | private final Entity entity;
17 | private final Damage damage;
18 |
19 | private boolean cancelled;
20 | private boolean cancelDeath;
21 |
22 | public EntityPreDeathEvent(@NotNull Entity entity, @NotNull Damage damage) {
23 | this.entity = entity;
24 | this.damage = damage;
25 | }
26 |
27 | @Override
28 | public @NotNull Entity getEntity() {
29 | return entity;
30 | }
31 |
32 | /**
33 | * Returns the damage type which was used to apply the last damage to the entity.
34 | *
35 | * WARNING: Modifying this object will have no effect.
36 | *
37 | * @return the damage object
38 | */
39 | public @NotNull Damage getDamage() {
40 | return damage;
41 | }
42 |
43 | /**
44 | * Returns whether the damage should be cancelled or not.
45 | *
46 | * WARNING: Cancelling can have unintended side effects, see #setCancelled for explanation.
47 | *
48 | * @return whether the damage should be cancelled
49 | */
50 | @Override
51 | public boolean isCancelled() {
52 | return cancelled;
53 | }
54 |
55 | /**
56 | * Use this method to cancel the damage.
57 | *
58 | * WARNING: This will also get rid of any effects applied after the damage, such as attack sounds.
59 | * You might want to use #setCancelDeath instead.
60 | *
61 | * @param cancel true if the event should be cancelled, false otherwise
62 | */
63 | @Override
64 | public void setCancelled(boolean cancel) {
65 | this.cancelled = cancel;
66 | }
67 |
68 | /**
69 | * See #setCancelDeath for explanation
70 | *
71 | * @return whether the death should be cancelled or not
72 | */
73 | public boolean isCancelDeath() {
74 | return cancelDeath;
75 | }
76 |
77 | /**
78 | * Use this method to cancel the death, but make the damage method return true.
79 | * This will make sure any effects applied after the damage, such as attack sounds, still apply.
80 | *
81 | * @param cancelDeath whether the death should be cancelled or not
82 | */
83 | public void setCancelDeath(boolean cancelDeath) {
84 | this.cancelDeath = cancelDeath;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/events/EquipmentDamageEvent.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.events;
2 |
3 | import net.minestom.server.entity.EquipmentSlot;
4 | import net.minestom.server.entity.LivingEntity;
5 | import net.minestom.server.event.trait.CancellableEvent;
6 | import net.minestom.server.event.trait.EntityInstanceEvent;
7 | import org.jetbrains.annotations.NotNull;
8 |
9 | /**
10 | * Called when an item in an equipment slot gets damaged.
11 | */
12 | public class EquipmentDamageEvent implements EntityInstanceEvent, CancellableEvent {
13 |
14 | private final LivingEntity entity;
15 | private final EquipmentSlot slot;
16 | private int amount;
17 |
18 | private boolean cancelled;
19 |
20 | public EquipmentDamageEvent(@NotNull LivingEntity entity, @NotNull EquipmentSlot slot, int amount) {
21 | this.entity = entity;
22 | this.slot = slot;
23 | this.amount = amount;
24 | }
25 |
26 | @Override
27 | public @NotNull LivingEntity getEntity() {
28 | return entity;
29 | }
30 |
31 | /**
32 | * Gets the equipment slot in which the item to be damaged is.
33 | *
34 | * @return the equipment slot
35 | */
36 | public EquipmentSlot getSlot() {
37 | return slot;
38 | }
39 |
40 | /**
41 | * Gets the amount of damage done to the item.
42 | *
43 | * @return the amount of damage
44 | */
45 | public int getAmount() {
46 | return amount;
47 | }
48 |
49 | /**
50 | * Sets the amount of damage done to the item.
51 | *
52 | * @param amount the amount of damage
53 | */
54 | public void setAmount(int amount) {
55 | this.amount = amount;
56 | }
57 |
58 | @Override
59 | public boolean isCancelled() {
60 | return cancelled;
61 | }
62 |
63 | @Override
64 | public void setCancelled(boolean cancel) {
65 | this.cancelled = cancel;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/events/ExplosionEvent.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.events;
2 |
3 | import net.minestom.server.coordinate.Point;
4 | import net.minestom.server.entity.Entity;
5 | import net.minestom.server.entity.damage.Damage;
6 | import net.minestom.server.event.trait.CancellableEvent;
7 | import net.minestom.server.event.trait.InstanceEvent;
8 | import net.minestom.server.instance.Instance;
9 | import org.jetbrains.annotations.NotNull;
10 |
11 | import java.util.List;
12 |
13 | /**
14 | * Called when an explosion will take place. Can be used to modify the affected blocks.
15 | */
16 | public class ExplosionEvent implements InstanceEvent, CancellableEvent {
17 | private final Instance instance;
18 | private final List affectedBlocks;
19 | private final List affectedEntities;
20 |
21 | private Damage damageObject;
22 | private boolean cancelled;
23 |
24 | public ExplosionEvent(@NotNull Instance instance, @NotNull List affectedBlocks,
25 | @NotNull List affectedEntities, @NotNull Damage damageObject) {
26 | this.instance = instance;
27 | this.affectedBlocks = affectedBlocks;
28 | this.affectedEntities = affectedEntities;
29 | this.damageObject = damageObject;
30 | }
31 |
32 | /**
33 | * Gets the blocks affected by this explosion.
34 | * The list may be modified.
35 | *
36 | * @return the list of blocks affected by the explosion
37 | */
38 | public @NotNull List getAffectedBlocks() {
39 | return affectedBlocks;
40 | }
41 |
42 | /**
43 | * Gets the entities affected by this explosion.
44 | * The list may be modified.
45 | *
46 | * @return the list of entities affected by the explosion
47 | */
48 | public @NotNull List getAffectedEntities() {
49 | return affectedEntities;
50 | }
51 |
52 | /**
53 | * Gets the damage object which will be used to damage any affected entities.
54 | * The damage amount of this object will be overwritten depending on the entity.
55 | *
56 | * @return the damage object
57 | */
58 | public @NotNull Damage getDamageObject() {
59 | return damageObject;
60 | }
61 |
62 | /**
63 | * Sets the damage object which will be used to damage any affected entities.
64 | * The damage amount of this object will be overwritten depending on the entity.
65 | *
66 | * @param damageObject the new damage object
67 | */
68 | public void setDamageObject(@NotNull Damage damageObject) {
69 | this.damageObject = damageObject;
70 | }
71 |
72 | @Override
73 | public @NotNull Instance getInstance() {
74 | return instance;
75 | }
76 |
77 | @Override
78 | public boolean isCancelled() {
79 | return cancelled;
80 | }
81 |
82 | @Override
83 | public void setCancelled(boolean cancel) {
84 | this.cancelled = cancel;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/events/ExplosivePrimeEvent.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.events;
2 |
3 | import io.github.togar2.pvp.feature.explosion.ExplosionFeature;
4 | import net.minestom.server.coordinate.Point;
5 | import net.minestom.server.event.trait.CancellableEvent;
6 | import net.minestom.server.event.trait.InstanceEvent;
7 | import net.minestom.server.instance.Instance;
8 | import org.jetbrains.annotations.NotNull;
9 |
10 | /**
11 | * Called when a tnt gets ignited, either by a player or by a nearby explosion.
12 | * You can get the cause by using {@link ExplosivePrimeEvent#getCause()}
13 | */
14 | public class ExplosivePrimeEvent implements InstanceEvent, CancellableEvent {
15 |
16 | private final Instance instance;
17 | private final Point blockPosition;
18 | private final ExplosionFeature.IgnitionCause cause;
19 |
20 | private int fuse;
21 | private boolean cancelled;
22 |
23 | public ExplosivePrimeEvent(@NotNull Instance instance, @NotNull Point blockPosition,
24 | @NotNull ExplosionFeature.IgnitionCause cause, int fuse) {
25 | this.instance = instance;
26 | this.blockPosition = blockPosition;
27 | this.cause = cause;
28 | this.fuse = fuse;
29 | }
30 |
31 | @Override
32 | public @NotNull Instance getInstance() {
33 | return instance;
34 | }
35 |
36 | public @NotNull Point getBlockPosition() {
37 | return blockPosition;
38 | }
39 |
40 | /**
41 | * Gets the cause. Can be {@link ExplosionFeature.IgnitionCause.ByPlayer} or {@link ExplosionFeature.IgnitionCause.Explosion}.
42 | *
43 | * @return the cause
44 | */
45 | public ExplosionFeature.IgnitionCause getCause() {
46 | return cause;
47 | }
48 |
49 | /**
50 | * Gets the fuse in ticks.
51 | *
52 | * @return the fuse in ticks
53 | */
54 | public int getFuse() {
55 | return fuse;
56 | }
57 |
58 | /**
59 | * Sets the fuse in ticks.
60 | *
61 | * @param fuse the fuse in ticks
62 | */
63 | public void setFuse(int fuse) {
64 | this.fuse = fuse;
65 | }
66 |
67 | @Override
68 | public boolean isCancelled() {
69 | return cancelled;
70 | }
71 |
72 | @Override
73 | public void setCancelled(boolean cancel) {
74 | this.cancelled = cancel;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/events/FinalDamageEvent.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.events;
2 |
3 | import net.minestom.server.entity.LivingEntity;
4 | import net.minestom.server.entity.Player;
5 | import net.minestom.server.entity.damage.Damage;
6 | import net.minestom.server.event.trait.CancellableEvent;
7 | import net.minestom.server.event.trait.EntityInstanceEvent;
8 | import org.jetbrains.annotations.NotNull;
9 |
10 | /**
11 | * The damage event containing the final damage, calculation of armor and effects included.
12 | * This event should be used, unless you want to detect how much damage was originally dealt.
13 | */
14 | public class FinalDamageEvent implements EntityInstanceEvent, CancellableEvent {
15 |
16 | private final LivingEntity entity;
17 | private final Damage damage;
18 | private int invulnerabilityTicks;
19 | private AnimationType animationType;
20 |
21 | private boolean cancelled;
22 |
23 | public FinalDamageEvent(@NotNull LivingEntity entity, @NotNull Damage damage,
24 | int invulnerabilityTicks, @NotNull AnimationType animationType) {
25 | this.entity = entity;
26 | this.damage = damage;
27 | this.invulnerabilityTicks = invulnerabilityTicks;
28 | this.animationType = animationType;
29 | }
30 |
31 | @NotNull
32 | @Override
33 | public LivingEntity getEntity() {
34 | return entity;
35 | }
36 |
37 | /**
38 | * Gets the damage info, which can be used to set the damage amount.
39 | *
40 | * @return the damage info
41 | */
42 | public Damage getDamage() {
43 | return damage;
44 | }
45 |
46 | /**
47 | * Gets the amount of ticks the entity is invulnerable after the damage has been applied.
48 | * By default it is 10 (half a second).
49 | *
50 | * @return the amount of ticks the entity is invulnerable
51 | */
52 | public int getInvulnerabilityTicks() {
53 | return invulnerabilityTicks;
54 | }
55 |
56 | /**
57 | * Sets the amount of ticks the entity is invulnerable after the damage has been applied.
58 | * By default it is 10 (half a second).
59 | *
60 | * @param invulnerabilityTicks the amount of ticks the entity is invulnerable
61 | */
62 | public void setInvulnerabilityTicks(int invulnerabilityTicks) {
63 | this.invulnerabilityTicks = invulnerabilityTicks;
64 | }
65 |
66 | /**
67 | * Gets the animation type, which determines how the client tilts the camera on damage.
68 | *
69 | * @return the animation type
70 | * @see AnimationType
71 | */
72 | public @NotNull AnimationType getAnimationType() {
73 | return animationType;
74 | }
75 |
76 | /**
77 | * Sets the animation type, which determines how the client tilts the camera on damage.
78 | *
79 | * @param animationType the animation type
80 | * @see AnimationType
81 | */
82 | public void setAnimationType(@NotNull AnimationType animationType) {
83 | this.animationType = animationType;
84 | }
85 |
86 | /**
87 | * Checks if the damage will kill the entity.
88 | *
89 | * This requires some computing, caching the result may
90 | * be better if used frequently.
91 | *
92 | * @return {@code true} if the entity will be killed, {@code false} otherwise
93 | */
94 | public boolean doesKillEntity() {
95 | float remainingDamage = damage.getAmount();
96 |
97 | // Additional hearts support
98 | if (entity instanceof final Player player) {
99 | final float additionalHearts = player.getAdditionalHearts();
100 | if (additionalHearts > 0) {
101 | if (remainingDamage > additionalHearts) {
102 | remainingDamage -= additionalHearts;
103 | } else {
104 | remainingDamage = 0;
105 | }
106 | }
107 | }
108 |
109 | float finalHealth = entity.getHealth() - remainingDamage;
110 | return finalHealth <= 0;
111 | }
112 |
113 | @Override
114 | public boolean isCancelled() {
115 | return cancelled;
116 | }
117 |
118 | @Override
119 | public void setCancelled(boolean cancel) {
120 | this.cancelled = cancel;
121 | }
122 |
123 | /**
124 | * @see #NONE
125 | * @see #MODERN
126 | * @see #LEGACY
127 | */
128 | public enum AnimationType {
129 | /**
130 | * No damage animation
131 | */
132 | NONE,
133 | /**
134 | * Modern damage animation, which tilts the camera based on the location of the damage source
135 | */
136 | MODERN,
137 | /**
138 | * Legacy damage animation, which always tilts the camera in the same way
139 | */
140 | LEGACY
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/events/FishingBobberRetrieveEvent.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.events;
2 |
3 | import io.github.togar2.pvp.entity.projectile.FishingBobber;
4 | import net.minestom.server.entity.Player;
5 | import net.minestom.server.event.trait.CancellableEvent;
6 | import net.minestom.server.event.trait.PlayerInstanceEvent;
7 | import org.jetbrains.annotations.NotNull;
8 |
9 | /**
10 | * The event called whenever a player retrieves a fishing bobber.
11 | */
12 | public class FishingBobberRetrieveEvent implements PlayerInstanceEvent, CancellableEvent {
13 |
14 | private final Player player;
15 | private final FishingBobber bobber;
16 |
17 | private boolean cancelled;
18 |
19 | public FishingBobberRetrieveEvent(Player player, FishingBobber bobber) {
20 | this.player = player;
21 | this.bobber = bobber;
22 | }
23 |
24 | @Override
25 | public boolean isCancelled() {
26 | return cancelled;
27 | }
28 |
29 | @Override
30 | public void setCancelled(boolean cancel) {
31 | this.cancelled = cancel;
32 | }
33 |
34 | @Override
35 | public @NotNull Player getPlayer() {
36 | return player;
37 | }
38 |
39 | /**
40 | * Gets the fishing bobber which was retrieved in the event.
41 | *
42 | * @return the fishing bobber
43 | */
44 | public FishingBobber getBobber() {
45 | return bobber;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/events/PickupEntityEvent.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.events;
2 |
3 | import io.github.togar2.pvp.entity.projectile.AbstractArrow;
4 | import net.minestom.server.entity.Entity;
5 | import net.minestom.server.entity.Player;
6 | import net.minestom.server.event.trait.CancellableEvent;
7 | import net.minestom.server.event.trait.EntityInstanceEvent;
8 | import org.jetbrains.annotations.NotNull;
9 |
10 | /**
11 | * Called when a player picks up an entity (arrow or trident).
12 | */
13 | public class PickupEntityEvent implements EntityInstanceEvent, CancellableEvent {
14 |
15 | private final Player player;
16 | private final AbstractArrow arrowEntity;
17 |
18 | private boolean cancelled;
19 |
20 | public PickupEntityEvent(@NotNull Player player, @NotNull AbstractArrow arrowEntity) {
21 | this.player = player;
22 | this.arrowEntity = arrowEntity;
23 | }
24 |
25 | @NotNull
26 | public Player getPlayer() {
27 | return player;
28 | }
29 |
30 | @NotNull
31 | public AbstractArrow getPickedUp() {
32 | return arrowEntity;
33 | }
34 |
35 | @Override
36 | public boolean isCancelled() {
37 | return cancelled;
38 | }
39 |
40 | @Override
41 | public void setCancelled(boolean cancel) {
42 | this.cancelled = cancel;
43 | }
44 |
45 | @Override
46 | public @NotNull Entity getEntity() {
47 | return player;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/events/PlayerExhaustEvent.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.events;
2 |
3 | import net.minestom.server.entity.Player;
4 | import net.minestom.server.event.trait.CancellableEvent;
5 | import net.minestom.server.event.trait.EntityInstanceEvent;
6 | import net.minestom.server.event.trait.PlayerEvent;
7 | import org.jetbrains.annotations.NotNull;
8 |
9 | /**
10 | * Called when a players' exhaustion level changes.
11 | * This is used to determine when their food level should change.
12 | */
13 | public class PlayerExhaustEvent implements PlayerEvent, EntityInstanceEvent, CancellableEvent {
14 |
15 | private final Player player;
16 | private float amount;
17 |
18 | private boolean cancelled;
19 |
20 | public PlayerExhaustEvent(@NotNull Player player, float amount) {
21 | this.player = player;
22 | this.amount = amount;
23 | }
24 |
25 | @Override
26 | public @NotNull Player getPlayer() {
27 | return player;
28 | }
29 |
30 | /**
31 | * Returns the amount of exhaustion.
32 | *
33 | * @return the amount of exhaustion
34 | */
35 | public float getAmount() {
36 | return amount;
37 | }
38 |
39 | /**
40 | * Sets the amount of exhaustion.
41 | * Example: One sprint jump applies 0.8 exhaustion in 1.8, and 0.2 in newer versions.
42 | *
43 | * @param amount the amount of exhaustion
44 | */
45 | public void setAmount(float amount) {
46 | this.amount = amount;
47 | }
48 |
49 | @Override
50 | public boolean isCancelled() {
51 | return cancelled;
52 | }
53 |
54 | @Override
55 | public void setCancelled(boolean cancelled) {
56 | this.cancelled = cancelled;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/events/PlayerRegenerateEvent.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.events;
2 |
3 | import net.minestom.server.entity.Player;
4 | import net.minestom.server.event.trait.CancellableEvent;
5 | import net.minestom.server.event.trait.EntityInstanceEvent;
6 | import net.minestom.server.event.trait.PlayerEvent;
7 | import org.jetbrains.annotations.NotNull;
8 |
9 | /**
10 | * Called when a player naturally regenerates health.
11 | */
12 | public class PlayerRegenerateEvent implements PlayerEvent, EntityInstanceEvent, CancellableEvent {
13 |
14 | private final Player player;
15 | private float amount;
16 | private float exhaustion;
17 |
18 | private boolean cancelled;
19 |
20 | public PlayerRegenerateEvent(@NotNull Player player, float amount, float exhaustion) {
21 | this.player = player;
22 | this.amount = amount;
23 | this.exhaustion = exhaustion;
24 | }
25 |
26 | @Override
27 | public @NotNull Player getPlayer() {
28 | return player;
29 | }
30 |
31 | /**
32 | * Returns the amount of health the player will regenerate.
33 | *
34 | * @return the amount of health
35 | */
36 | public float getAmount() {
37 | return amount;
38 | }
39 |
40 | /**
41 | * Sets the amount of health the player will regenerate.
42 | *
43 | * @param amount the amount of health
44 | */
45 | public void setAmount(float amount) {
46 | this.amount = amount;
47 | }
48 |
49 | /**
50 | * Returns the amount of exhaustion the regeneration will apply.
51 | *
52 | * @return the amount of exhaustion
53 | */
54 | public float getExhaustion() {
55 | return exhaustion;
56 | }
57 |
58 | /**
59 | * Sets the amount of exhaustion the regeneration will apply.
60 | *
61 | * @param exhaustion the amount of exhaustion
62 | */
63 | public void setExhaustion(float exhaustion) {
64 | this.exhaustion = exhaustion;
65 | }
66 |
67 | @Override
68 | public boolean isCancelled() {
69 | return cancelled;
70 | }
71 |
72 | @Override
73 | public void setCancelled(boolean cancelled) {
74 | this.cancelled = cancelled;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/events/PlayerSpectateEvent.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.events;
2 |
3 | import net.minestom.server.entity.Entity;
4 | import net.minestom.server.entity.Player;
5 | import net.minestom.server.event.trait.CancellableEvent;
6 | import net.minestom.server.event.trait.EntityInstanceEvent;
7 | import net.minestom.server.event.trait.PlayerEvent;
8 | import org.jetbrains.annotations.NotNull;
9 |
10 | /**
11 | * Called when a spectator tries to spectate an entity by attacking it.
12 | */
13 | public class PlayerSpectateEvent implements PlayerEvent, EntityInstanceEvent, CancellableEvent {
14 |
15 | private final Player player;
16 | private final Entity target;
17 |
18 | private boolean cancelled;
19 |
20 | public PlayerSpectateEvent(@NotNull Player player, @NotNull Entity target) {
21 | this.player = player;
22 | this.target = target;
23 | }
24 |
25 | @Override
26 | public @NotNull Player getPlayer() {
27 | return player;
28 | }
29 |
30 | public Entity getTarget() {
31 | return target;
32 | }
33 |
34 | @Override
35 | public boolean isCancelled() {
36 | return cancelled;
37 | }
38 |
39 | @Override
40 | public void setCancelled(boolean cancel) {
41 | this.cancelled = cancel;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/events/PotionVisibilityEvent.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.events;
2 |
3 | import net.minestom.server.entity.LivingEntity;
4 | import net.minestom.server.event.trait.CancellableEvent;
5 | import net.minestom.server.event.trait.EntityInstanceEvent;
6 | import net.minestom.server.particle.Particle;
7 | import org.jetbrains.annotations.NotNull;
8 |
9 | import java.util.List;
10 |
11 | /**
12 | * Called when an entities potion state (ambient, particle color and invisibility) is updated.
13 | */
14 | public class PotionVisibilityEvent implements EntityInstanceEvent, CancellableEvent {
15 |
16 | private final LivingEntity entity;
17 | private boolean ambient;
18 | private List particles;
19 | private boolean invisible;
20 |
21 | private boolean cancelled;
22 |
23 | public PotionVisibilityEvent(@NotNull LivingEntity entity, boolean ambient,
24 | List particles, boolean invisible) {
25 | this.entity = entity;
26 | this.ambient = ambient;
27 | this.particles = particles;
28 | this.invisible = invisible;
29 | }
30 |
31 | @Override
32 | public @NotNull LivingEntity getEntity() {
33 | return entity;
34 | }
35 |
36 | /**
37 | * Gets whether the entity effects contain ambient effects.
38 | *
39 | * @return whether the effects contain ambient effects
40 | */
41 | public boolean isAmbient() {
42 | return ambient;
43 | }
44 |
45 | /**
46 | * Sets whether the entity effects contain ambient effects.
47 | *
48 | * @param ambient whether the effects contain ambient effects
49 | */
50 | public void setAmbient(boolean ambient) {
51 | this.ambient = ambient;
52 | }
53 |
54 | /**
55 | * Gets the potion particle emitters.
56 | * Will be empty for no potion particles.
57 | *
58 | * @return the potion color
59 | */
60 | public @NotNull List getParticles() {
61 | return particles;
62 | }
63 |
64 | /**
65 | * Sets the potion particle emitters.
66 | * Set to an empty list to disable potion particles.
67 | *
68 | * @param particles the potion color
69 | */
70 | public void setParticles(@NotNull List particles) {
71 | this.particles = particles;
72 | }
73 |
74 | /**
75 | * Gets whether the entity will become invisible.
76 | *
77 | * @return whether the entity will become invisible
78 | */
79 | public boolean isInvisible() {
80 | return invisible;
81 | }
82 |
83 | /**
84 | * Sets whether the entity will become invisible.
85 | *
86 | * @param invisible whether the entity will become invisible
87 | */
88 | public void setInvisible(boolean invisible) {
89 | this.invisible = invisible;
90 | }
91 |
92 | @Override
93 | public boolean isCancelled() {
94 | return cancelled;
95 | }
96 |
97 | @Override
98 | public void setCancelled(boolean cancel) {
99 | this.cancelled = cancel;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/events/PrepareAttackEvent.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.events;
2 |
3 | import net.minestom.server.entity.Entity;
4 | import net.minestom.server.event.trait.CancellableEvent;
5 | import net.minestom.server.event.trait.EntityInstanceEvent;
6 | import org.jetbrains.annotations.NotNull;
7 |
8 | public class PrepareAttackEvent implements EntityInstanceEvent, CancellableEvent {
9 |
10 | private final Entity entity;
11 | private final Entity target;
12 |
13 | private boolean cancelled;
14 |
15 | public PrepareAttackEvent(@NotNull Entity entity, @NotNull Entity target) {
16 | this.entity = entity;
17 | this.target = target;
18 | }
19 |
20 | @Override
21 | public @NotNull Entity getEntity() {
22 | return entity;
23 | }
24 |
25 | public @NotNull Entity getTarget() {
26 | return target;
27 | }
28 |
29 |
30 | @Override
31 | public boolean isCancelled() {
32 | return cancelled;
33 | }
34 |
35 | @Override
36 | public void setCancelled(boolean cancel) {
37 | this.cancelled = cancel;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/events/TotemUseEvent.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.events;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 |
5 | import net.minestom.server.entity.LivingEntity;
6 | import net.minestom.server.entity.PlayerHand;
7 | import net.minestom.server.event.trait.CancellableEvent;
8 | import net.minestom.server.event.trait.EntityInstanceEvent;
9 |
10 | /**
11 | * Called when a totem prevents an entity from dying.
12 | */
13 | public class TotemUseEvent implements EntityInstanceEvent, CancellableEvent {
14 |
15 | private final LivingEntity entity;
16 | private final PlayerHand hand;
17 |
18 | private boolean cancelled;
19 |
20 | public TotemUseEvent(@NotNull LivingEntity entity, @NotNull PlayerHand hand) {
21 | this.entity = entity;
22 | this.hand = hand;
23 | }
24 |
25 | @NotNull
26 | @Override
27 | public LivingEntity getEntity() {
28 | return entity;
29 | }
30 |
31 | @NotNull
32 | public PlayerHand getHand() {
33 | return hand;
34 | }
35 |
36 | @Override
37 | public boolean isCancelled() {
38 | return cancelled;
39 | }
40 |
41 | @Override
42 | public void setCancelled(boolean cancel) {
43 | this.cancelled = cancel;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/feature/CombatFeature.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.feature;
2 |
3 | import io.github.togar2.pvp.feature.config.FeatureConfiguration;
4 |
5 | /**
6 | * The base interface of all combat features. See the readme for a more sophisticated explanation.
7 | */
8 | public interface CombatFeature {
9 | /**
10 | * Signals to this feature that the {@link FeatureConfiguration} it's been given in the constructor is ready to use.
11 | * Most features will use it to get instances of all their dependencies.
12 | *
13 | * This has to exist because of complications with recursive dependencies.
14 | */
15 | default void initDependencies() {}
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/feature/CombatFeatureSet.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.feature;
2 |
3 | import io.github.togar2.pvp.feature.config.FeatureConfiguration;
4 | import net.minestom.server.event.EventNode;
5 | import net.minestom.server.event.trait.EntityInstanceEvent;
6 |
7 | /**
8 | * A container for multiple {@link CombatFeature}s. Use {@link CombatFeatureSet#createNode()} to get an event node.
9 | */
10 | public class CombatFeatureSet extends FeatureConfiguration implements RegistrableFeature {
11 | private boolean initialized = false;
12 |
13 | @Override
14 | public void init(EventNode node) {
15 | for (CombatFeature feature : listFeatures()) {
16 | if (!(feature instanceof RegistrableFeature registrable)) continue;
17 | node.addChild(registrable.createNode());
18 | }
19 | }
20 |
21 | @Override
22 | public void initDependencies() {
23 | for (CombatFeature feature : listFeatures()) {
24 | feature.initDependencies();
25 | }
26 | initialized = true;
27 | }
28 |
29 | @Override
30 | public FeatureConfiguration add(FeatureType> type, CombatFeature feature) {
31 | if (initialized) throw new UnsupportedOperationException("Cannot add features after initialization");
32 | return super.add(type, feature);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/feature/RegistrableFeature.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.feature;
2 |
3 | import net.minestom.server.entity.Entity;
4 | import net.minestom.server.event.EventFilter;
5 | import net.minestom.server.event.EventNode;
6 | import net.minestom.server.event.trait.EntityEvent;
7 | import net.minestom.server.event.trait.EntityInstanceEvent;
8 |
9 | /**
10 | * A {@link CombatFeature} which listens for events and likewise needs to register its listeners to an event node.
11 | *
12 | * See {@link RegistrableFeature#createNode()}
13 | */
14 | public interface RegistrableFeature extends CombatFeature {
15 | EventFilter ENTITY_INSTANCE_FILTER = EventFilter
16 | .from(EntityInstanceEvent.class, Entity.class, EntityEvent::getEntity);
17 |
18 | /**
19 | * Returns the priority that an event node created by this feature will have.
20 | *
21 | * @return the priority
22 | */
23 | default int getPriority() {
24 | return 0;
25 | }
26 |
27 | /**
28 | * Initializes this feature by adding its listeners to the given event node.
29 | *
30 | * @param node the event node to add the listeners to
31 | */
32 | void init(EventNode node);
33 |
34 | /**
35 | * Creates an event node with all the listeners of this feature attached.
36 | * This event node can on its turn be added to another node (e.g. the global one) to get the listeners working.
37 | *
38 | * @return the event node
39 | */
40 | default EventNode createNode() {
41 | var node = EventNode.type(getClass().getTypeName(), ENTITY_INSTANCE_FILTER);
42 | node.setPriority(getPriority());
43 | init(node);
44 | return node;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/feature/armor/ArmorFeature.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.feature.armor;
2 |
3 | import io.github.togar2.pvp.feature.CombatFeature;
4 | import net.minestom.server.entity.LivingEntity;
5 | import net.minestom.server.entity.damage.DamageType;
6 |
7 | /**
8 | * Combat feature used for determining the resulting damage after armor usage.
9 | */
10 | public interface ArmorFeature extends CombatFeature {
11 | ArmorFeature NO_OP = (entity, type, amount) -> amount;
12 |
13 | float getDamageWithProtection(LivingEntity entity, DamageType type, float amount);
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/feature/armor/VanillaArmorFeature.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.feature.armor;
2 |
3 | import io.github.togar2.pvp.damage.DamageTypeInfo;
4 | import io.github.togar2.pvp.feature.FeatureType;
5 | import io.github.togar2.pvp.feature.config.DefinedFeature;
6 | import io.github.togar2.pvp.feature.config.FeatureConfiguration;
7 | import io.github.togar2.pvp.feature.enchantment.EnchantmentFeature;
8 | import io.github.togar2.pvp.utils.CombatVersion;
9 | import net.minestom.server.MinecraftServer;
10 | import net.minestom.server.entity.LivingEntity;
11 | import net.minestom.server.entity.attribute.Attribute;
12 | import net.minestom.server.entity.damage.DamageType;
13 | import net.minestom.server.potion.PotionEffect;
14 | import net.minestom.server.potion.TimedPotion;
15 | import net.minestom.server.utils.MathUtils;
16 |
17 | /**
18 | * Vanilla implementation of {@link ArmorFeature}
19 | */
20 | public class VanillaArmorFeature implements ArmorFeature {
21 | public static final DefinedFeature DEFINED = new DefinedFeature<>(
22 | FeatureType.ARMOR, VanillaArmorFeature::new,
23 | FeatureType.ENCHANTMENT, FeatureType.VERSION
24 | );
25 |
26 | private final FeatureConfiguration configuration;
27 | private EnchantmentFeature enchantmentFeature;
28 | private CombatVersion version;
29 |
30 | public VanillaArmorFeature(FeatureConfiguration configuration) {
31 | this.configuration = configuration;
32 | }
33 |
34 | @Override
35 | public void initDependencies() {
36 | this.enchantmentFeature = configuration.get(FeatureType.ENCHANTMENT);
37 | this.version = configuration.get(FeatureType.VERSION);
38 | }
39 |
40 | @Override
41 | public float getDamageWithProtection(LivingEntity entity, DamageType type, float amount) {
42 | DamageTypeInfo info = DamageTypeInfo.of(MinecraftServer.getDamageTypeRegistry().getKey(type));
43 | amount = getDamageWithArmor(entity, info, amount);
44 | return getDamageWithEnchantments(entity, type, amount);
45 | }
46 |
47 | protected float getDamageWithArmor(LivingEntity entity, DamageTypeInfo typeInfo, float amount) {
48 | if (typeInfo.bypassesArmor()) return amount;
49 |
50 | double armorValue = entity.getAttributeValue(Attribute.ARMOR);
51 | if (version.legacy()) {
52 | int armorMultiplier = 25 - (int) armorValue;
53 | return (amount * (float) armorMultiplier) / 25;
54 | } else {
55 | return getDamageLeft(
56 | amount, (float) Math.floor(armorValue),
57 | (float) entity.getAttributeValue(Attribute.ARMOR_TOUGHNESS)
58 | );
59 | }
60 | }
61 |
62 | protected float getDamageWithEnchantments(LivingEntity entity, DamageType damageType, float amount) {
63 | DamageTypeInfo damageTypeInfo = DamageTypeInfo.of(MinecraftServer.getDamageTypeRegistry().getKey(damageType));
64 | if (damageTypeInfo.unblockable()) return amount;
65 |
66 | int k;
67 | TimedPotion effect = entity.getEffect(PotionEffect.RESISTANCE);
68 | if (effect != null) {
69 | k = (effect.potion().amplifier() + 1) * 5;
70 | int j = 25 - k;
71 | float f = amount * (float) j;
72 | amount = Math.max(f / 25, 0);
73 | }
74 |
75 | if (amount <= 0) {
76 | return 0;
77 | } else {
78 | k = enchantmentFeature.getProtectionAmount(entity, damageType);
79 | if (version.modern()) {
80 | if (k > 0) {
81 | amount = getDamageAfterProtectionEnchantment(amount, (float) k);
82 | }
83 | } else {
84 | if (k > 20) {
85 | k = 20;
86 | }
87 |
88 | if (k > 0) {
89 | int j = 25 - k;
90 | float f = amount * (float) j;
91 | amount = f / 25;
92 | }
93 | }
94 |
95 | return amount;
96 | }
97 | }
98 |
99 | protected float getDamageLeft(float damage, float armor, float armorToughness) {
100 | float f = 2.0f + armorToughness / 4.0f;
101 | float g = MathUtils.clamp(armor - damage / f, armor * 0.2f, 20.0f);
102 | return damage * (1.0F - g / 25.0F);
103 | }
104 |
105 | protected float getDamageAfterProtectionEnchantment(float damageDealt, float protection) {
106 | float f = MathUtils.clamp(protection, 0.0f, 20.0f);
107 | return damageDealt * (1.0f - f / 25.0f);
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/feature/attack/AttackFeature.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.feature.attack;
2 |
3 | import io.github.togar2.pvp.feature.CombatFeature;
4 | import net.minestom.server.entity.Entity;
5 | import net.minestom.server.entity.LivingEntity;
6 |
7 | /**
8 | * Combat feature which handles an entity attacking another entity.
9 | */
10 | public interface AttackFeature extends CombatFeature {
11 | AttackFeature NO_OP = (attacker, target) -> false;
12 |
13 | /**
14 | * Performs an attack on the target entity.
15 | *
16 | * @param attacker the attacking entity
17 | * @param target the target entity
18 | * @return whether the attack was successful
19 | */
20 | boolean performAttack(LivingEntity attacker, Entity target);
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/feature/attack/AttackValues.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.feature.attack;
2 |
3 | public class AttackValues {
4 | public record PreCritical(float damage, float magicalDamage, double cooldownProgress,
5 | boolean strong, boolean sprint, int knockback, int fireAspect) {
6 | public PreSweeping withCritical(boolean critical) {
7 | return new PreSweeping(
8 | damage, magicalDamage, cooldownProgress,
9 | strong, sprint, critical, knockback, fireAspect
10 | );
11 | }
12 | }
13 |
14 | public record PreSweeping(float damage, float magicalDamage, double cooldownProgress,
15 | boolean strong, boolean sprint, boolean critical,
16 | int knockback, int fireAspect) {
17 | public PreSounds withSweeping(boolean sweeping) {
18 | return new PreSounds(
19 | damage, magicalDamage, cooldownProgress,
20 | strong, sprint, critical, sweeping,
21 | knockback, fireAspect
22 | );
23 | }
24 | }
25 |
26 | public record PreSounds(float damage, float magicalDamage, double cooldownProgress,
27 | boolean strong, boolean sprint, boolean critical, boolean sweeping,
28 | int knockback, int fireAspect) {}
29 |
30 | public record Final(float damage, boolean strong, boolean sprint,
31 | int knockback, boolean critical, boolean magical,
32 | int fireAspect, boolean sweeping, boolean sounds,
33 | boolean playSoundsOnFail) {}
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/feature/attack/CriticalFeature.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.feature.attack;
2 |
3 | import io.github.togar2.pvp.feature.CombatFeature;
4 | import net.minestom.server.entity.LivingEntity;
5 |
6 | /**
7 | * Combat feature used to determine when an attack was critical and what this means for the resulting damage.
8 | */
9 | public interface CriticalFeature extends CombatFeature {
10 | CriticalFeature NO_OP = new CriticalFeature() {
11 | @Override
12 | public boolean shouldCrit(LivingEntity attacker, AttackValues.PreCritical values) {
13 | return false;
14 | }
15 |
16 | @Override
17 | public float applyToDamage(float damage) {
18 | return damage;
19 | }
20 | };
21 |
22 | boolean shouldCrit(LivingEntity attacker, AttackValues.PreCritical values);
23 |
24 | /**
25 | * Determines the new damage amount when the attack was critical.
26 | *
27 | * @param damage the old damage amount
28 | * @return the new damage amount
29 | */
30 | float applyToDamage(float damage);
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/feature/attack/SweepingFeature.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.feature.attack;
2 |
3 | import io.github.togar2.pvp.feature.CombatFeature;
4 | import net.minestom.server.entity.LivingEntity;
5 |
6 | import java.util.Collection;
7 | import java.util.List;
8 |
9 | /**
10 | * Combat feature used to determine whether an attack is a sweeping attack and also used for applying the sweeping.
11 | */
12 | public interface SweepingFeature extends CombatFeature {
13 | SweepingFeature NO_OP = new SweepingFeature() {
14 | @Override
15 | public boolean shouldSweep(LivingEntity attacker, AttackValues.PreSweeping values) {
16 | return false;
17 | }
18 |
19 | @Override
20 | public float getSweepingDamage(LivingEntity attacker, float damage) {
21 | return 0;
22 | }
23 |
24 | @Override
25 | public Collection applySweeping(LivingEntity attacker, LivingEntity target, float damage) {
26 | return List.of();
27 | }
28 | };
29 |
30 | boolean shouldSweep(LivingEntity attacker, AttackValues.PreSweeping values);
31 |
32 | float getSweepingDamage(LivingEntity attacker, float damage);
33 |
34 | /**
35 | * Should return a collection of the affected entities.
36 | */
37 | Collection applySweeping(LivingEntity attacker, LivingEntity target, float damage);
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/feature/attack/VanillaCriticalFeature.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.feature.attack;
2 |
3 | import io.github.togar2.pvp.feature.FeatureType;
4 | import io.github.togar2.pvp.feature.config.DefinedFeature;
5 | import io.github.togar2.pvp.feature.config.FeatureConfiguration;
6 | import io.github.togar2.pvp.feature.state.PlayerStateFeature;
7 | import io.github.togar2.pvp.utils.CombatVersion;
8 | import net.minestom.server.entity.LivingEntity;
9 | import net.minestom.server.potion.PotionEffect;
10 |
11 | import java.util.concurrent.ThreadLocalRandom;
12 |
13 | /**
14 | * Vanilla implementation of {@link CriticalFeature}
15 | */
16 | public class VanillaCriticalFeature implements CriticalFeature {
17 | public static final DefinedFeature DEFINED = new DefinedFeature<>(
18 | FeatureType.CRITICAL, VanillaCriticalFeature::new,
19 | FeatureType.PLAYER_STATE, FeatureType.VERSION
20 | );
21 |
22 | private final FeatureConfiguration configuration;
23 |
24 | private PlayerStateFeature playerStateFeature;
25 | private CombatVersion version;
26 |
27 | public VanillaCriticalFeature(FeatureConfiguration configuration) {
28 | this.configuration = configuration;
29 | }
30 |
31 | @Override
32 | public void initDependencies() {
33 | this.playerStateFeature = configuration.get(FeatureType.PLAYER_STATE);
34 | this.version = configuration.get(FeatureType.VERSION);
35 | }
36 |
37 | @Override
38 | public boolean shouldCrit(LivingEntity attacker, AttackValues.PreCritical values) {
39 | boolean critical = values.strong() && !playerStateFeature.isClimbing(attacker)
40 | && attacker.getVelocity().y() < 0 && !attacker.isOnGround()
41 | && !attacker.hasEffect(PotionEffect.BLINDNESS)
42 | && attacker.getVehicle() == null;
43 | if (version.legacy()) return critical;
44 |
45 | // Not sprinting required for critical in 1.9+
46 | return critical && !attacker.isSprinting();
47 | }
48 |
49 | @Override
50 | public float applyToDamage(float damage) {
51 | if (version.legacy()) {
52 | return damage + ThreadLocalRandom.current().nextInt((int) (damage / 2 + 2));
53 | } else {
54 | return damage * 1.5f;
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/feature/attack/VanillaSweepingFeature.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.feature.attack;
2 |
3 | import io.github.togar2.pvp.enchantment.EntityGroup;
4 | import io.github.togar2.pvp.enums.Tool;
5 | import io.github.togar2.pvp.feature.FeatureType;
6 | import io.github.togar2.pvp.feature.config.DefinedFeature;
7 | import io.github.togar2.pvp.feature.config.FeatureConfiguration;
8 | import io.github.togar2.pvp.feature.enchantment.EnchantmentFeature;
9 | import io.github.togar2.pvp.feature.knockback.KnockbackFeature;
10 | import net.minestom.server.collision.BoundingBox;
11 | import net.minestom.server.coordinate.Pos;
12 | import net.minestom.server.entity.Entity;
13 | import net.minestom.server.entity.EntityType;
14 | import net.minestom.server.entity.LivingEntity;
15 | import net.minestom.server.entity.Player;
16 | import net.minestom.server.entity.attribute.Attribute;
17 | import net.minestom.server.entity.damage.Damage;
18 | import net.minestom.server.entity.damage.DamageType;
19 | import net.minestom.server.network.packet.server.play.ParticlePacket;
20 | import net.minestom.server.particle.Particle;
21 |
22 | import java.util.ArrayList;
23 | import java.util.Collection;
24 | import java.util.List;
25 |
26 | /**
27 | * Vanilla implementation of {@link SweepingFeature}
28 | */
29 | public class VanillaSweepingFeature implements SweepingFeature {
30 | public static final DefinedFeature DEFINED = new DefinedFeature<>(
31 | FeatureType.SWEEPING, VanillaSweepingFeature::new,
32 | FeatureType.ENCHANTMENT, FeatureType.KNOCKBACK
33 | );
34 |
35 | private final FeatureConfiguration configuration;
36 |
37 | private EnchantmentFeature enchantmentFeature;
38 | private KnockbackFeature knockbackFeature;
39 |
40 | public VanillaSweepingFeature(FeatureConfiguration configuration) {
41 | this.configuration = configuration;
42 | }
43 |
44 | @Override
45 | public void initDependencies() {
46 | this.enchantmentFeature = configuration.get(FeatureType.ENCHANTMENT);
47 | this.knockbackFeature = configuration.get(FeatureType.KNOCKBACK);
48 | }
49 |
50 | @Override
51 | public boolean shouldSweep(LivingEntity attacker, AttackValues.PreSweeping values) {
52 | if (!values.strong() || values.critical() || values.sprint() || !attacker.isOnGround()) return false;
53 |
54 | double lastMoveDistance = attacker.getPreviousPosition().distance(attacker.getPosition()) * 0.6;
55 | if (lastMoveDistance >= attacker.getAttributeValue(Attribute.MOVEMENT_SPEED)) return false;
56 |
57 | Tool tool = Tool.fromMaterial(attacker.getItemInMainHand().material());
58 | return tool != null && tool.isSword();
59 | }
60 |
61 | @Override
62 | public float getSweepingDamage(LivingEntity attacker, float damage) {
63 | float sweepingMultiplier = 0;
64 | int sweepingLevel = enchantmentFeature.getSweeping(attacker);
65 | if (sweepingLevel > 0) sweepingMultiplier = 1.0f - (1.0f / (float) (sweepingLevel + 1));
66 | return 1.0f + sweepingMultiplier * damage;
67 | }
68 |
69 | @Override
70 | public Collection applySweeping(LivingEntity attacker, LivingEntity target, float damage) {
71 | float sweepingDamage = getSweepingDamage(attacker, damage);
72 |
73 | // Loop and check for colliding entities
74 | List affectedEntities = new ArrayList<>();
75 | BoundingBox boundingBox = target.getBoundingBox().expand(1.0, 0.25, 1.0);
76 | assert target.getInstance() != null;
77 | for (Entity nearbyEntity : target.getInstance().getNearbyEntities(target.getPosition(), 2)) {
78 | if (nearbyEntity == target || nearbyEntity == attacker) continue;
79 | if (!(nearbyEntity instanceof LivingEntity living)) continue;
80 | if (nearbyEntity.getEntityType() == EntityType.ARMOR_STAND) continue;
81 | if (!boundingBox.intersectEntity(target.getPosition(), nearbyEntity)) continue;
82 |
83 | // Apply sweeping knockback and damage to the entity
84 | if (attacker.getPosition().distanceSquared(nearbyEntity.getPosition()) < 9.0) {
85 | affectedEntities.add(living);
86 | knockbackFeature.applySweepingKnockback(attacker, target);
87 |
88 | float currentDamage = sweepingDamage + enchantmentFeature.getAttackDamage(
89 | attacker.getItemInMainHand(), EntityGroup.ofEntity(target));
90 |
91 | living.damage(new Damage(
92 | attacker instanceof Player ? DamageType.PLAYER_ATTACK : DamageType.MOB_ATTACK,
93 | attacker, attacker,
94 | null, currentDamage
95 | ));
96 | }
97 | }
98 |
99 | // Spawn sweeping particles
100 | Pos pos = attacker.getPosition();
101 | double x = -Math.sin(Math.toRadians(pos.yaw()));
102 | double z = Math.cos(Math.toRadians(pos.yaw()));
103 |
104 | attacker.sendPacketToViewersAndSelf(new ParticlePacket(
105 | Particle.SWEEP_ATTACK, false,false,
106 | pos.x() + x, pos.y() + attacker.getBoundingBox().height() * 0.5, pos.z() + z,
107 | (float) x, 0, (float) z,
108 | 0, 0
109 | ));
110 |
111 | return affectedEntities;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/feature/attributes/EquipmentFeature.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.feature.attributes;
2 |
3 | import io.github.togar2.pvp.feature.CombatFeature;
4 |
5 | /**
6 | * Combat feature which handles equipment changes (applies weapon and armor attributes).
7 | */
8 | public interface EquipmentFeature extends CombatFeature {
9 | EquipmentFeature NO_OP = new EquipmentFeature() {};
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/feature/attributes/VanillaEquipmentFeature.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.feature.attributes;
2 |
3 | import io.github.togar2.pvp.enums.ArmorMaterial;
4 | import io.github.togar2.pvp.enums.Tool;
5 | import io.github.togar2.pvp.feature.FeatureType;
6 | import io.github.togar2.pvp.feature.RegistrableFeature;
7 | import io.github.togar2.pvp.feature.config.DefinedFeature;
8 | import io.github.togar2.pvp.feature.config.FeatureConfiguration;
9 | import io.github.togar2.pvp.utils.CombatVersion;
10 | import net.minestom.server.entity.EquipmentSlot;
11 | import net.minestom.server.entity.LivingEntity;
12 | import net.minestom.server.event.EventNode;
13 | import net.minestom.server.event.item.EntityEquipEvent;
14 | import net.minestom.server.event.player.PlayerChangeHeldSlotEvent;
15 | import net.minestom.server.event.trait.EntityInstanceEvent;
16 | import net.minestom.server.item.ItemStack;
17 |
18 | /**
19 | * Vanilla implementation of {@link EquipmentFeature}
20 | */
21 | public class VanillaEquipmentFeature implements EquipmentFeature, RegistrableFeature {
22 | public static final DefinedFeature DEFINED = new DefinedFeature<>(
23 | FeatureType.EQUIPMENT, VanillaEquipmentFeature::new,
24 | FeatureType.VERSION
25 | );
26 |
27 | private final FeatureConfiguration configuration;
28 |
29 | //TODO this probably shouldn't work this way
30 | // We probably want to store all the tools & armor separately per DataFeature
31 | private CombatVersion version;
32 |
33 | public VanillaEquipmentFeature(FeatureConfiguration configuration) {
34 | this.configuration = configuration;
35 | }
36 |
37 | @Override
38 | public void initDependencies() {
39 | this.version = configuration.get(FeatureType.VERSION);
40 | }
41 |
42 | @Override
43 | public void init(EventNode node) {
44 | node.addListener(EntityEquipEvent.class, this::onEquip);
45 | node.addListener(PlayerChangeHeldSlotEvent.class, event -> {
46 | LivingEntity entity = event.getPlayer();
47 | ItemStack newItem = event.getPlayer().getInventory().getItemStack(event.getNewSlot());
48 | Tool.updateEquipmentAttributes(entity, entity.getEquipment(EquipmentSlot.MAIN_HAND), newItem, EquipmentSlot.MAIN_HAND, version);
49 | });
50 | }
51 |
52 | protected void onEquip(EntityEquipEvent event) {
53 | if (!(event.getEntity() instanceof LivingEntity entity)) return;
54 |
55 | EquipmentSlot slot = event.getSlot();
56 | if (slot.isArmor()) {
57 | ArmorMaterial.updateEquipmentAttributes(entity, entity.getEquipment(slot), event.getEquippedItem(), slot, version);
58 | } else if (slot.isHand()) {
59 | Tool.updateEquipmentAttributes(entity, entity.getEquipment(slot), event.getEquippedItem(), slot, version);
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/feature/block/BlockFeature.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.feature.block;
2 |
3 | import io.github.togar2.pvp.feature.CombatFeature;
4 | import net.minestom.server.entity.LivingEntity;
5 | import net.minestom.server.entity.damage.Damage;
6 |
7 | /**
8 | * Combat feature used to determine whether an entity is blocking damage and how the block should be applied.
9 | */
10 | public interface BlockFeature extends CombatFeature {
11 | BlockFeature NO_OP = new BlockFeature() {
12 | @Override
13 | public boolean isDamageBlocked(LivingEntity entity, Damage damage) {
14 | return false;
15 | }
16 |
17 | @Override
18 | public boolean applyBlock(LivingEntity entity, Damage damage) {
19 | return false;
20 | }
21 | };
22 |
23 | boolean isDamageBlocked(LivingEntity entity, Damage damage);
24 |
25 | /**
26 | * Applies the block to the {@link Damage} object.
27 | *
28 | * @param entity the entity blocking the damage
29 | * @param damage the damage object
30 | * @return whether the damage was FULLY blocked
31 | */
32 | boolean applyBlock(LivingEntity entity, Damage damage);
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/feature/block/LegacyBlockFeature.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.feature.block;
2 |
3 | import net.minestom.server.entity.LivingEntity;
4 | import net.minestom.server.entity.Player;
5 | import net.minestom.server.entity.damage.Damage;
6 | import net.minestom.server.item.ItemStack;
7 |
8 | /**
9 | * Combat feature which manages legacy blocking for a player.
10 | */
11 | public interface LegacyBlockFeature extends BlockFeature {
12 | LegacyBlockFeature NO_OP = new LegacyBlockFeature() {
13 | @Override public boolean isBlocking(Player player) { return false; }
14 | @Override public void block(Player player) {}
15 | @Override public void unblock(Player player) {}
16 | @Override public boolean canBlockWith(Player player, ItemStack stack) { return false; }
17 | @Override public boolean isDamageBlocked(LivingEntity entity, Damage damage) { return false; }
18 | @Override public boolean applyBlock(LivingEntity entity, Damage damage) { return false; }
19 | };
20 |
21 | boolean isBlocking(Player player);
22 |
23 | void block(Player player);
24 |
25 | void unblock(Player player);
26 |
27 | boolean canBlockWith(Player player, ItemStack stack);
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/feature/config/CombatFeatureRegistry.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.feature.config;
2 |
3 | import net.minestom.server.MinecraftServer;
4 | import net.minestom.server.event.Event;
5 | import net.minestom.server.event.EventNode;
6 | import net.minestom.server.event.player.AsyncPlayerConfigurationEvent;
7 | import net.minestom.server.event.player.PlayerRespawnEvent;
8 | import net.minestom.server.event.player.PlayerSpawnEvent;
9 |
10 | import java.util.ArrayList;
11 | import java.util.List;
12 |
13 | public class CombatFeatureRegistry {
14 | private static final EventNode initNode = EventNode.all("combat-feature-init");
15 | private static final List> features = new ArrayList<>();
16 |
17 | public static void init(DefinedFeature> feature) {
18 | if (!features.contains(feature)) {
19 | features.add(feature);
20 | if (feature.playerInit() != null) {
21 | initNode.addListener(AsyncPlayerConfigurationEvent.class, event -> feature.playerInit().init(event.getPlayer(), true));
22 | initNode.addListener(PlayerSpawnEvent.class, event -> feature.playerInit().init(event.getPlayer(), false));
23 | initNode.addListener(PlayerRespawnEvent.class, event -> feature.playerInit().init(event.getPlayer(), false));
24 | }
25 | }
26 | }
27 |
28 | public static void init() {
29 | MinecraftServer.getGlobalEventHandler().addChild(initNode);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/feature/config/DefinedFeature.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.feature.config;
2 |
3 | import io.github.togar2.pvp.feature.CombatFeature;
4 | import io.github.togar2.pvp.feature.FeatureType;
5 | import net.minestom.server.entity.Player;
6 | import org.jetbrains.annotations.Nullable;
7 |
8 | import java.util.Set;
9 |
10 | /**
11 | * Represents a feature implementation which has been defined but not yet instantiated.
12 | * It contains the feature type, all the feature types that this feature depends on and its constructor.
13 | * The constructor always takes a {@link FeatureConfiguration} which the feature should store,
14 | * and which it can get the dependencies from once {@link CombatFeature#initDependencies()} is called.
15 | *
16 | * A defined feature can also contain a {@link PlayerInit},
17 | * which describes everything that should happen when a players state is reset (they join, they respawn, etc.).
18 | * This can for example be used to add certain tags to a player that a feature needs.
19 | * Since features might be instantiated multiple times, this player init is contained in the feature definition
20 | * and not every time when it is instantiated.
21 | *
22 | * @param the feature class
23 | */
24 | public class DefinedFeature {
25 | private final FeatureType> featureType;
26 | private final Set> dependencies;
27 | private final Constructor constructor;
28 | private final PlayerInit playerInit;
29 |
30 | public DefinedFeature(FeatureType super F> featureType, Constructor constructor,
31 | FeatureType>... dependencies) {
32 | this(featureType, constructor, null, dependencies);
33 | }
34 |
35 | public DefinedFeature(FeatureType super F> featureType, Constructor constructor,
36 | @Nullable PlayerInit playerInit, FeatureType>... dependencies) {
37 | this.featureType = featureType;
38 | this.dependencies = Set.of(dependencies);
39 | this.constructor = constructor;
40 | this.playerInit = playerInit;
41 | }
42 |
43 | public F construct(FeatureConfiguration configuration) {
44 | // Registers player init
45 | CombatFeatureRegistry.init(this);
46 | return constructor.construct(configuration);
47 | }
48 |
49 | public FeatureType> featureType() {
50 | return featureType;
51 | }
52 |
53 | public Set> dependencies() {
54 | return dependencies;
55 | }
56 |
57 | @Nullable PlayerInit playerInit() {
58 | return playerInit;
59 | }
60 |
61 | @FunctionalInterface
62 | public interface Constructor {
63 | F construct(FeatureConfiguration configuration);
64 | }
65 |
66 | @FunctionalInterface
67 | public interface PlayerInit {
68 | void init(Player player, boolean firstInit);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/feature/cooldown/AttackCooldownFeature.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.feature.cooldown;
2 |
3 | import io.github.togar2.pvp.feature.CombatFeature;
4 | import net.minestom.server.entity.Player;
5 |
6 | /**
7 | * Combat feature used to manage a players attack cooldown.
8 | */
9 | public interface AttackCooldownFeature extends CombatFeature {
10 | AttackCooldownFeature NO_OP = new AttackCooldownFeature() {
11 | @Override
12 | public void resetCooldownProgress(Player player) {
13 | }
14 |
15 | @Override
16 | public double getAttackCooldownProgress(Player player) {
17 | return 1.0;
18 | }
19 | };
20 |
21 | /**
22 | * Reset the attack cooldown progress of the player, so it can attack again.
23 | *
24 | * @param player the player to reset the attack cooldown progress from
25 | */
26 | void resetCooldownProgress(Player player);
27 |
28 | /**
29 | * Get the attack cooldown progress of the player,
30 | * a value between 0.0 and 1.0 where higher values mean more attack damage.
31 | *
32 | * @param player the player to get the attack cooldown progress from
33 | * @return the attack cooldown progress of the player
34 | */
35 | double getAttackCooldownProgress(Player player);
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/feature/cooldown/ItemCooldownFeature.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.feature.cooldown;
2 |
3 | import io.github.togar2.pvp.feature.CombatFeature;
4 | import net.minestom.server.entity.Player;
5 | import net.minestom.server.item.Material;
6 |
7 | /**
8 | * Combat feature to manage a players item cooldown animation.
9 | */
10 | public interface ItemCooldownFeature extends CombatFeature {
11 | ItemCooldownFeature NO_OP = new ItemCooldownFeature() {
12 | @Override
13 | public boolean hasCooldown(Player player, Material material) {
14 | return false;
15 | }
16 |
17 | @Override
18 | public void setCooldown(Player player, Material material, int ticks) {}
19 | };
20 |
21 | boolean hasCooldown(Player player, Material material);
22 |
23 | void setCooldown(Player player, Material material, int ticks);
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/feature/cooldown/VanillaAttackCooldownFeature.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.feature.cooldown;
2 |
3 | import io.github.togar2.pvp.feature.FeatureType;
4 | import io.github.togar2.pvp.feature.RegistrableFeature;
5 | import io.github.togar2.pvp.feature.config.DefinedFeature;
6 | import io.github.togar2.pvp.feature.config.FeatureConfiguration;
7 | import io.github.togar2.pvp.utils.CombatVersion;
8 | import net.minestom.server.entity.Player;
9 | import net.minestom.server.entity.attribute.Attribute;
10 | import net.minestom.server.event.EventListener;
11 | import net.minestom.server.event.EventNode;
12 | import net.minestom.server.event.player.PlayerChangeHeldSlotEvent;
13 | import net.minestom.server.event.player.PlayerHandAnimationEvent;
14 | import net.minestom.server.event.trait.EntityInstanceEvent;
15 | import net.minestom.server.tag.Tag;
16 | import net.minestom.server.utils.MathUtils;
17 |
18 | /**
19 | * Vanilla implementation of {@link AttackCooldownFeature}
20 | */
21 | public class VanillaAttackCooldownFeature implements AttackCooldownFeature, RegistrableFeature {
22 | public static final DefinedFeature DEFINED = new DefinedFeature<>(
23 | FeatureType.ATTACK_COOLDOWN, VanillaAttackCooldownFeature::new,
24 | FeatureType.VERSION
25 | );
26 |
27 | public static final Tag LAST_ATTACKED_TICKS = Tag.Long("lastAttackedTicks");
28 |
29 | private final FeatureConfiguration configuration;
30 | private CombatVersion version;
31 |
32 | public VanillaAttackCooldownFeature(FeatureConfiguration configuration) {
33 | this.configuration = configuration;
34 | }
35 |
36 | @Override
37 | public void initDependencies() {
38 | this.version = configuration.get(FeatureType.VERSION);
39 | }
40 |
41 | @Override
42 | public void init(EventNode node) {
43 | node.addListener(EventListener.builder(PlayerHandAnimationEvent.class).handler(event ->
44 | resetCooldownProgress(event.getPlayer())).build());
45 |
46 | node.addListener(EventListener.builder(PlayerChangeHeldSlotEvent.class).handler(event -> {
47 | if (!event.getPlayer().getItemInMainHand()
48 | .isSimilar(event.getPlayer().getInventory().getItemStack(event.getNewSlot()))) {
49 | resetCooldownProgress(event.getPlayer());
50 | }
51 | }).build());
52 | }
53 |
54 | @Override
55 | public void resetCooldownProgress(Player player) {
56 | player.setTag(LAST_ATTACKED_TICKS, player.getAliveTicks());
57 | }
58 |
59 | @Override
60 | public double getAttackCooldownProgress(Player player) {
61 | if (version.legacy()) return 1.0;
62 |
63 | Long lastAttacked = player.getTag(LAST_ATTACKED_TICKS);
64 | if (lastAttacked == null) return 1.0;
65 |
66 | long timeSinceLastAttacked = player.getAliveTicks() - lastAttacked;
67 | return MathUtils.clamp(
68 | (timeSinceLastAttacked + 0.5) / getAttackCooldownProgressPerTick(player),
69 | 0, 1
70 | );
71 | }
72 |
73 | protected double getAttackCooldownProgressPerTick(Player player) {
74 | return (1 / player.getAttributeValue(Attribute.ATTACK_SPEED)) * 20;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/feature/cooldown/VanillaItemCooldownFeature.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.feature.cooldown;
2 |
3 | import io.github.togar2.pvp.feature.FeatureType;
4 | import io.github.togar2.pvp.feature.RegistrableFeature;
5 | import io.github.togar2.pvp.feature.config.DefinedFeature;
6 | import net.minestom.server.MinecraftServer;
7 | import net.minestom.server.entity.Player;
8 | import net.minestom.server.event.EventNode;
9 | import net.minestom.server.event.player.PlayerTickEvent;
10 | import net.minestom.server.event.player.PlayerUseItemEvent;
11 | import net.minestom.server.event.trait.EntityInstanceEvent;
12 | import net.minestom.server.item.Material;
13 | import net.minestom.server.network.packet.server.play.SetCooldownPacket;
14 | import net.minestom.server.tag.Tag;
15 |
16 | import java.util.HashMap;
17 | import java.util.Iterator;
18 | import java.util.Map;
19 |
20 | /**
21 | * Vanilla implementation of {@link ItemCooldownFeature}
22 | */
23 | public class VanillaItemCooldownFeature implements ItemCooldownFeature, RegistrableFeature {
24 | public static final DefinedFeature DEFINED = new DefinedFeature<>(
25 | FeatureType.ITEM_COOLDOWN, configuration -> new VanillaItemCooldownFeature(),
26 | VanillaItemCooldownFeature::initPlayer
27 | );
28 |
29 | public static final Tag