├── .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 featureType, Constructor constructor, 31 | FeatureType... dependencies) { 32 | this(featureType, constructor, null, dependencies); 33 | } 34 | 35 | public DefinedFeature(FeatureType 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> COOLDOWN_END = Tag.Transient("cooldownEnd"); 30 | 31 | private static void initPlayer(Player player, boolean firstInit) { 32 | player.setTag(COOLDOWN_END, new HashMap<>()); 33 | } 34 | 35 | @Override 36 | public int getPriority() { 37 | // Needs to stop every item usage event 38 | return -5; 39 | } 40 | 41 | @Override 42 | public void init(EventNode node) { 43 | node.addListener(PlayerTickEvent.class, event -> { 44 | Player player = event.getPlayer(); 45 | Map cooldown = player.getTag(COOLDOWN_END); 46 | if (cooldown.isEmpty()) return; 47 | long time = System.currentTimeMillis(); 48 | 49 | Iterator> iterator = cooldown.entrySet().iterator(); 50 | 51 | while (iterator.hasNext()) { 52 | Map.Entry entry = iterator.next(); 53 | if (entry.getValue() <= time) { 54 | iterator.remove(); 55 | sendCooldownPacket(player, entry.getKey(), 0); 56 | } 57 | } 58 | }); 59 | 60 | node.addListener(PlayerUseItemEvent.class, event -> { 61 | if (hasCooldown(event.getPlayer(), event.getItemStack().material())) 62 | event.setCancelled(true); 63 | }); 64 | } 65 | 66 | @Override 67 | public boolean hasCooldown(Player player, Material material) { 68 | Map cooldown = player.getTag(COOLDOWN_END); 69 | return cooldown.containsKey(material) && cooldown.get(material) > System.currentTimeMillis(); 70 | } 71 | 72 | @Override 73 | public void setCooldown(Player player, Material material, int ticks) { 74 | Map cooldown = player.getTag(COOLDOWN_END); 75 | cooldown.put(material, System.currentTimeMillis() + (long) ticks * MinecraftServer.TICK_MS); 76 | sendCooldownPacket(player, material, ticks); 77 | } 78 | 79 | protected void sendCooldownPacket(Player player, Material material, int ticks) { 80 | player.getPlayerConnection().sendPacket(new SetCooldownPacket(material.key().asString(), ticks)); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/damage/DamageFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.damage; 2 | 3 | import io.github.togar2.pvp.feature.CombatFeature; 4 | 5 | /** 6 | * Combat feature which handles entities being damaged. 7 | */ 8 | public interface DamageFeature extends CombatFeature { 9 | DamageFeature NO_OP = new DamageFeature() {}; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/effect/EffectFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.effect; 2 | 3 | import io.github.togar2.pvp.entity.projectile.Arrow; 4 | import io.github.togar2.pvp.feature.CombatFeature; 5 | import net.minestom.server.entity.Entity; 6 | import net.minestom.server.entity.LivingEntity; 7 | import net.minestom.server.item.component.PotionContents; 8 | import net.minestom.server.potion.CustomPotionEffect; 9 | import net.minestom.server.potion.Potion; 10 | import net.minestom.server.potion.PotionType; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | import java.util.Collection; 14 | import java.util.List; 15 | 16 | /** 17 | * Combat feature which manages potion effects and their effects on entities. 18 | */ 19 | public interface EffectFeature extends CombatFeature { 20 | EffectFeature NO_OP = new EffectFeature() { 21 | @Override 22 | public int getPotionColor(PotionContents contents) { 23 | return 0; 24 | } 25 | 26 | @Override 27 | public List getAllPotions(PotionType potionType, Collection customEffects) { 28 | return List.of(); 29 | } 30 | 31 | @Override public void updatePotionVisibility(LivingEntity entity) {} 32 | @Override public void addArrowEffects(LivingEntity entity, Arrow arrow) {} 33 | @Override public void addSplashPotionEffects(LivingEntity entity, PotionContents potionContents, double proximity, 34 | @Nullable Entity source, @Nullable Entity attacker) {} 35 | }; 36 | 37 | int getPotionColor(PotionContents contents); 38 | 39 | default List getAllPotions(@Nullable PotionContents potionContents) { 40 | if (potionContents == null) return List.of(); 41 | return getAllPotions(potionContents.potion(), potionContents.customEffects()); 42 | } 43 | 44 | List getAllPotions(PotionType potionType, Collection customEffects); 45 | 46 | /** 47 | * Updates the potion visibility of an entity. This includes particles and invisibility status. 48 | * 49 | * @param entity the entity to update the potion visibility of 50 | */ 51 | void updatePotionVisibility(LivingEntity entity); 52 | 53 | /** 54 | * Applies the effects of a (tipped) arrow to an entity. 55 | * 56 | * @param entity the entity which was hit 57 | * @param arrow the arrow 58 | */ 59 | void addArrowEffects(LivingEntity entity, Arrow arrow); 60 | 61 | /** 62 | * Applies the effects of a splash potion to an entity. 63 | * The proximity is usually calculated following: {@code 1.0 - Math.sqrt(distanceSquared) / 4.0} 64 | * 65 | * @param entity the entity which was hit 66 | * @param potionContents the potion contents of the splash potion 67 | * @param proximity the proximity of the potion to the entity 68 | * @param source the direct source of the splash (usually the splash potion) 69 | * @param attacker the attacker of the splash (usually the thrower) 70 | */ 71 | void addSplashPotionEffects(LivingEntity entity, PotionContents potionContents, double proximity, 72 | @Nullable Entity source, @Nullable Entity attacker); 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/effect/PotionColorUtils.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.effect; 2 | 3 | import net.minestom.server.potion.Potion; 4 | 5 | import java.util.Collection; 6 | 7 | class PotionColorUtils { 8 | public static int getPotionColor(Collection effects) { 9 | int r = 0, g = 0, b = 0; 10 | int totalAmplifier = 0; 11 | 12 | for (Potion potion : effects) { 13 | if (potion.hasParticles()) { 14 | int color = potion.effect().registry().color(); 15 | int amplifier = potion.amplifier() + 1; 16 | r += amplifier * (color >> 16 & 0xFF); 17 | g += amplifier * (color >> 8 & 0xFF); 18 | b += amplifier * (color & 0xFF); 19 | totalAmplifier += amplifier; 20 | } 21 | } 22 | 23 | if (totalAmplifier == 0) { 24 | return -1; 25 | } else { 26 | return rgba(255, r / totalAmplifier, g / totalAmplifier, b / totalAmplifier); 27 | } 28 | } 29 | 30 | public static int rgba(int a, int r, int g, int b) { 31 | return (a << 24) | (r << 16) | (g << 8) | b; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/enchantment/EnchantmentFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.enchantment; 2 | 3 | import io.github.togar2.pvp.enchantment.EntityGroup; 4 | import io.github.togar2.pvp.feature.CombatFeature; 5 | import net.minestom.server.entity.Entity; 6 | import net.minestom.server.entity.EquipmentSlot; 7 | import net.minestom.server.entity.LivingEntity; 8 | import net.minestom.server.entity.damage.DamageType; 9 | import net.minestom.server.item.ItemStack; 10 | import net.minestom.server.item.enchant.Enchantment; 11 | import net.minestom.server.registry.DynamicRegistry; 12 | 13 | import java.util.Map; 14 | 15 | /** 16 | * Combat feature which manages enchantments. 17 | *

18 | * It contains methods which other features depend on, 19 | * such as methods for the protection amount of armor enchantments and the attack damage amount of weapon enchantments. 20 | */ 21 | public interface EnchantmentFeature extends CombatFeature { 22 | EnchantmentFeature NO_OP = new EnchantmentFeature() { 23 | @Override 24 | public int getEquipmentLevel(LivingEntity entity, DynamicRegistry.Key enchantment) { 25 | return 0; 26 | } 27 | 28 | @Override 29 | public Map.Entry pickRandom(LivingEntity entity, DynamicRegistry.Key enchantment) { 30 | return null; 31 | } 32 | 33 | @Override 34 | public int getProtectionAmount(LivingEntity entity, DamageType damageType) { 35 | return 0; 36 | } 37 | 38 | @Override 39 | public float getAttackDamage(ItemStack stack, EntityGroup group) { 40 | return 0; 41 | } 42 | 43 | @Override 44 | public double getExplosionKnockback(LivingEntity entity, double strength) { 45 | return strength; 46 | } 47 | 48 | @Override 49 | public int getFireDuration(LivingEntity entity, int duration) { 50 | return duration; 51 | } 52 | 53 | @Override 54 | public int getKnockback(LivingEntity entity) { 55 | return 0; 56 | } 57 | 58 | @Override 59 | public int getSweeping(LivingEntity entity) { 60 | return 0; 61 | } 62 | 63 | @Override 64 | public int getFireAspect(LivingEntity entity) { 65 | return 0; 66 | } 67 | 68 | @Override 69 | public boolean shouldUnbreakingPreventDamage(ItemStack stack) { 70 | return false; 71 | } 72 | 73 | @Override 74 | public void onUserDamaged(LivingEntity user, LivingEntity attacker) {} 75 | 76 | @Override 77 | public void onTargetDamaged(LivingEntity user, Entity target) {} 78 | }; 79 | 80 | /** 81 | * Gets the highest level of en enchantment on an entity's equipment. 82 | * 83 | * @param entity the entity for which to determine the equipment level 84 | * @param enchantment the enchantment for which to determine the equipment level 85 | * @return the equipment level 86 | */ 87 | int getEquipmentLevel(LivingEntity entity, DynamicRegistry.Key enchantment); 88 | 89 | /** 90 | * Picks a random equipment piece which has the specified enchantment. 91 | * 92 | * @param entity the entity to pick equipment from 93 | * @param enchantment the enchantment which the equipment should have 94 | * @return a map entry containing both the equipment slot and the item stack of the equipment piece 95 | */ 96 | Map.Entry pickRandom(LivingEntity entity, DynamicRegistry.Key enchantment); 97 | 98 | int getProtectionAmount(LivingEntity entity, DamageType damageType); 99 | 100 | float getAttackDamage(ItemStack stack, EntityGroup group); 101 | 102 | double getExplosionKnockback(LivingEntity entity, double strength); 103 | 104 | int getFireDuration(LivingEntity entity, int duration); 105 | 106 | int getKnockback(LivingEntity entity); 107 | 108 | int getSweeping(LivingEntity entity); 109 | 110 | int getFireAspect(LivingEntity entity); 111 | 112 | boolean shouldUnbreakingPreventDamage(ItemStack stack); 113 | 114 | /** 115 | * Handles an entity being damaged by an attacker. Usually applies thorns. 116 | * 117 | * @param user the entity being damaged 118 | * @param attacker the attacker 119 | */ 120 | void onUserDamaged(LivingEntity user, LivingEntity attacker); 121 | 122 | /** 123 | * Handles an entity damaging another entity. 124 | * Some enchantments could for example add extra effects to the target. 125 | * 126 | * @param user the attacker 127 | * @param target the target 128 | */ 129 | void onTargetDamaged(LivingEntity user, Entity target); 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/explosion/ExplosionFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.explosion; 2 | 3 | import io.github.togar2.pvp.feature.CombatFeature; 4 | import net.minestom.server.coordinate.Point; 5 | import net.minestom.server.entity.Entity; 6 | import net.minestom.server.entity.Player; 7 | import net.minestom.server.instance.ExplosionSupplier; 8 | import net.minestom.server.instance.Instance; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | /** 13 | * Combat feature which handles explosions. Contains a method to prime an explosive at a certain place. 14 | *

15 | * Important to note is that implementations of this feature might provide an {@link ExplosionSupplier}. 16 | * This explosion supplier should be registered to every (Minestom) instance which should allow explosions. 17 | * See {@link ExplosionFeature#getExplosionSupplier()}. 18 | */ 19 | public interface ExplosionFeature extends CombatFeature { 20 | ExplosionFeature NO_OP = new ExplosionFeature() { 21 | @Override 22 | public @Nullable ExplosionSupplier getExplosionSupplier() { 23 | return null; 24 | } 25 | 26 | @Override 27 | public void primeExplosive(Instance instance, Point blockPosition, @NotNull IgnitionCause cause, int fuse) {} 28 | }; 29 | 30 | @Nullable ExplosionSupplier getExplosionSupplier(); 31 | 32 | void primeExplosive(Instance instance, Point blockPosition, @NotNull IgnitionCause cause, int fuse); 33 | 34 | sealed interface IgnitionCause { 35 | @Nullable Entity causingEntity(); 36 | 37 | /** 38 | * Ignition cause when a player directly ignites an explosive. 39 | * 40 | * @param player the player which ignited the explosive 41 | */ 42 | record ByPlayer(Player player) implements IgnitionCause { 43 | @Override 44 | public @Nullable Player causingEntity() { 45 | return player; 46 | } 47 | } 48 | 49 | /** 50 | * Ignition cause when an explosion causes another explosive to ignite. 51 | * 52 | * @param causingEntity the entity which caused the original explosion 53 | */ 54 | record Explosion(Entity causingEntity) implements IgnitionCause {} 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/explosion/ExplosiveFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.explosion; 2 | 3 | import io.github.togar2.pvp.feature.CombatFeature; 4 | 5 | /** 6 | * Combat feature which handles placing and igniting explosives. 7 | * Supports tnt, end crystals and respawn anchors. 8 | */ 9 | public interface ExplosiveFeature extends CombatFeature { 10 | ExplosiveFeature NO_OP = new ExplosiveFeature() {}; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/explosion/VanillaExplosionFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.explosion; 2 | 3 | import io.github.togar2.pvp.entity.explosion.TntEntity; 4 | import io.github.togar2.pvp.events.ExplosivePrimeEvent; 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 net.kyori.adventure.sound.Sound; 9 | import net.minestom.server.coordinate.Point; 10 | import net.minestom.server.event.EventDispatcher; 11 | import net.minestom.server.instance.Instance; 12 | import net.minestom.server.sound.SoundEvent; 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | /** 16 | * Vanilla implementation of {@link ExplosionFeature} 17 | *

18 | * Provides an explosion supplier which can be registered to an instance, 19 | * see {@link VanillaExplosionFeature#getExplosionSupplier()}. 20 | */ 21 | public class VanillaExplosionFeature implements ExplosionFeature { 22 | public static final DefinedFeature DEFINED = new DefinedFeature<>( 23 | FeatureType.EXPLOSION, VanillaExplosionFeature::new, 24 | FeatureType.ENCHANTMENT 25 | ); 26 | 27 | private final FeatureConfiguration configuration; 28 | 29 | private VanillaExplosionSupplier explosionSupplier; 30 | 31 | public VanillaExplosionFeature(FeatureConfiguration configuration) { 32 | this.configuration = configuration; 33 | } 34 | 35 | @Override 36 | public void initDependencies() { 37 | this.explosionSupplier = new VanillaExplosionSupplier(this, configuration.get(FeatureType.ENCHANTMENT)); 38 | } 39 | 40 | @Override 41 | public VanillaExplosionSupplier getExplosionSupplier() { 42 | return explosionSupplier; 43 | } 44 | 45 | @Override 46 | public void primeExplosive(Instance instance, Point blockPosition, @NotNull IgnitionCause cause, int fuse) { 47 | ExplosivePrimeEvent event = new ExplosivePrimeEvent(instance, blockPosition, cause, fuse); 48 | EventDispatcher.callCancellable(event, () -> { 49 | TntEntity entity = new TntEntity(cause.causingEntity()); 50 | entity.setFuse(event.getFuse()); 51 | entity.setInstance(instance, blockPosition.add(0.5, 0, 0.5)); 52 | entity.getViewersAsAudience().playSound(Sound.sound( 53 | SoundEvent.ENTITY_TNT_PRIMED, Sound.Source.BLOCK, 54 | 1.0f, 1.0f 55 | ), entity); 56 | }); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/fall/FallFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.fall; 2 | 3 | import io.github.togar2.pvp.feature.CombatFeature; 4 | import net.minestom.server.entity.LivingEntity; 5 | 6 | /** 7 | * Combat feature which manages the fall distance and fall damage of entities. 8 | * It may also apply this damage when needed. 9 | */ 10 | public interface FallFeature extends CombatFeature { 11 | FallFeature NO_OP = new FallFeature() { 12 | @Override 13 | public int getFallDamage(LivingEntity entity, double fallDistance) { 14 | return 0; 15 | } 16 | 17 | @Override 18 | public double getFallDistance(LivingEntity entity) { 19 | return 0; 20 | } 21 | 22 | @Override 23 | public void resetFallDistance(LivingEntity entity) {} 24 | 25 | @Override 26 | public void setExtraFallParticles(LivingEntity entity, boolean extraFallParticles) {} 27 | }; 28 | 29 | int getFallDamage(LivingEntity entity, double fallDistance); 30 | 31 | double getFallDistance(LivingEntity entity); 32 | 33 | void resetFallDistance(LivingEntity entity); 34 | 35 | void setExtraFallParticles(LivingEntity entity, boolean extraFallParticles); 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/food/ChorusFruitUtil.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.food; 2 | 3 | import io.github.togar2.pvp.utils.ViewUtil; 4 | import net.kyori.adventure.sound.Sound; 5 | import net.minestom.server.MinecraftServer; 6 | import net.minestom.server.coordinate.Pos; 7 | import net.minestom.server.entity.Entity; 8 | import net.minestom.server.instance.Instance; 9 | import net.minestom.server.instance.block.Block; 10 | import net.minestom.server.sound.SoundEvent; 11 | import net.minestom.server.utils.MathUtils; 12 | import net.minestom.server.world.DimensionType; 13 | 14 | import java.util.concurrent.ThreadLocalRandom; 15 | 16 | public class ChorusFruitUtil { 17 | private static boolean randomTeleport(Entity entity, Pos to) { 18 | Instance instance = entity.getInstance(); 19 | assert instance != null; 20 | 21 | boolean success = false; 22 | int lowestY = to.blockY(); 23 | if (lowestY == 0) lowestY++; 24 | while (lowestY > MinecraftServer.getDimensionTypeRegistry().get(instance.getDimensionType()).minY()) { 25 | Block block = instance.getBlock(to.blockX(), lowestY - 1, to.blockZ()); 26 | if (!block.isAir() && !block.isLiquid()) { 27 | Block above = instance.getBlock(to.blockX(), lowestY, to.blockZ()); 28 | Block above2 = instance.getBlock(to.blockX(), lowestY + 1, to.blockZ()); 29 | if (above.isAir() && above2.isAir()) { 30 | success = true; 31 | break; 32 | } else { 33 | lowestY--; 34 | } 35 | } else { 36 | lowestY--; 37 | } 38 | } 39 | 40 | if (!success) return false; 41 | 42 | entity.teleport(to.withY(lowestY)); 43 | entity.triggerStatus((byte) 46); 44 | 45 | return true; 46 | } 47 | 48 | public static void tryChorusTeleport(Entity entity, float diameter) { 49 | ThreadLocalRandom random = ThreadLocalRandom.current(); 50 | Instance instance = entity.getInstance(); 51 | assert instance != null; 52 | float radius = diameter / 2.0f; 53 | 54 | Pos prevPosition = entity.getPosition(); 55 | double prevX = prevPosition.x(); 56 | double prevY = prevPosition.y(); 57 | double prevZ = prevPosition.z(); 58 | 59 | float pitch = prevPosition.pitch(); 60 | float yaw = prevPosition.yaw(); 61 | 62 | DimensionType dimensionType = MinecraftServer.getDimensionTypeRegistry().get(instance.getDimensionType()); 63 | assert dimensionType != null; 64 | 65 | // Max 16 tries 66 | for (int i = 0; i < 16; i++) { 67 | double x = prevX + (random.nextDouble() - 0.5) * radius; 68 | double y = MathUtils.clamp(prevY + (random.nextInt(16) - 8), 69 | dimensionType.minY(), dimensionType.minY() 70 | + dimensionType.logicalHeight() - 1); 71 | double z = prevZ + (random.nextDouble() - 0.5) * radius; 72 | 73 | if (entity.getVehicle() != null) { 74 | entity.getVehicle().removePassenger(entity); 75 | } 76 | 77 | if (randomTeleport(entity, new Pos(x, y, z, yaw, pitch))) { 78 | ViewUtil.packetGroup(entity).playSound(Sound.sound( 79 | SoundEvent.ITEM_CHORUS_FRUIT_TELEPORT, Sound.Source.PLAYER, 80 | 1.0f, 1.0f 81 | ), prevPosition); 82 | 83 | if (!entity.isSilent()) { 84 | entity.getViewersAsAudience().playSound(Sound.sound( 85 | SoundEvent.ITEM_CHORUS_FRUIT_TELEPORT, Sound.Source.PLAYER, 86 | 1.0f, 1.0f 87 | ), entity); 88 | } 89 | 90 | break; 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/food/ExhaustionFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.food; 2 | 3 | import io.github.togar2.pvp.feature.CombatFeature; 4 | import net.minestom.server.entity.Player; 5 | import net.minestom.server.entity.damage.DamageType; 6 | 7 | /** 8 | * Combat feature which manages player exhaustion and its influence on their food and saturation values. 9 | */ 10 | public interface ExhaustionFeature extends CombatFeature { 11 | ExhaustionFeature NO_OP = new ExhaustionFeature() { 12 | @Override 13 | public void addExhaustion(Player player, float exhaustion) {} 14 | 15 | @Override 16 | public void addAttackExhaustion(Player player) {} 17 | 18 | @Override 19 | public void addDamageExhaustion(Player player, DamageType type) {} 20 | 21 | @Override 22 | public void applyHungerEffect(Player player, int amplifier) {} 23 | }; 24 | 25 | void addExhaustion(Player player, float exhaustion); 26 | 27 | /** 28 | * Applies the exhaustion from an attack to a player. 29 | * 30 | * @param player the player to apply the attack exhaustion to 31 | */ 32 | void addAttackExhaustion(Player player); 33 | 34 | /** 35 | * Applies the exhaustion from taking damage to a player. 36 | * 37 | * @param player the player to apply the damage exhaustion to 38 | * @param type the damage type 39 | */ 40 | void addDamageExhaustion(Player player, DamageType type); 41 | 42 | /** 43 | * Applies effect of the hunger potion effect to a player. 44 | * If a player has the effect, this will be called on a regular basis. 45 | * 46 | * @param player the player to apply the effect to 47 | * @param amplifier the amplifier of the effect 48 | */ 49 | void applyHungerEffect(Player player, int amplifier); 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/food/FoodFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.food; 2 | 3 | import io.github.togar2.pvp.feature.CombatFeature; 4 | import net.minestom.server.entity.Player; 5 | import net.minestom.server.item.ItemStack; 6 | 7 | /** 8 | * Combat feature which manages player eating and their food and saturation values. 9 | */ 10 | public interface FoodFeature extends CombatFeature { 11 | FoodFeature NO_OP = new FoodFeature() { 12 | @Override 13 | public void addFood(Player player, int food, float saturation) {} 14 | 15 | @Override 16 | public void eat(Player player, int food, float saturationModifier) {} 17 | 18 | @Override 19 | public void eat(Player player, ItemStack stack) {} 20 | 21 | @Override 22 | public void applySaturationEffect(Player player, int amplifier) {} 23 | }; 24 | 25 | /** 26 | * Adds food to a player. 27 | * 28 | * @param player the player to add food to 29 | * @param food the food amount 30 | * @param saturation the saturation of the food 31 | */ 32 | void addFood(Player player, int food, float saturation); 33 | 34 | /** 35 | * Legacy method for a player to eat. Still used by the saturation effect. 36 | * 37 | * @param player the player which is eating 38 | * @param food the food amount 39 | * @param saturationModifier the saturation modifier which along with the food amount is used to calculate the saturation 40 | */ 41 | void eat(Player player, int food, float saturationModifier); 42 | 43 | /** 44 | * Modern method for a player to eat. Uses Minestoms registry values for the food amount and saturation. 45 | * 46 | * @param player the player which is eating 47 | * @param stack the item to eat 48 | */ 49 | void eat(Player player, ItemStack stack); 50 | 51 | /** 52 | * Applies effect of the saturation potion effect to a player. 53 | * If a player has the effect, this will be called on a regular basis. 54 | * 55 | * @param player the player to apply the effect to 56 | * @param amplifier the amplifier of the effect 57 | */ 58 | void applySaturationEffect(Player player, int amplifier); 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/food/RegenerationFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.food; 2 | 3 | import io.github.togar2.pvp.feature.CombatFeature; 4 | import net.minestom.server.entity.Player; 5 | 6 | /** 7 | * Combat features which handles natural regeneration and starvation. 8 | */ 9 | public interface RegenerationFeature extends CombatFeature { 10 | RegenerationFeature NO_OP = (player, health, exhaustion) -> {}; 11 | 12 | void regenerate(Player player, float health, float exhaustion); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/food/VanillaRegenerationFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.food; 2 | 3 | import io.github.togar2.pvp.events.PlayerRegenerateEvent; 4 | import io.github.togar2.pvp.feature.FeatureType; 5 | import io.github.togar2.pvp.feature.RegistrableFeature; 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.provider.DifficultyProvider; 9 | import io.github.togar2.pvp.utils.CombatVersion; 10 | import net.minestom.server.entity.Player; 11 | import net.minestom.server.entity.attribute.Attribute; 12 | import net.minestom.server.entity.damage.DamageType; 13 | import net.minestom.server.event.EventDispatcher; 14 | import net.minestom.server.event.EventNode; 15 | import net.minestom.server.event.player.PlayerTickEvent; 16 | import net.minestom.server.event.trait.EntityInstanceEvent; 17 | import net.minestom.server.tag.Tag; 18 | import net.minestom.server.world.Difficulty; 19 | 20 | /** 21 | * Vanilla implementation of {@link RegenerationFeature} 22 | */ 23 | public class VanillaRegenerationFeature implements RegenerationFeature, RegistrableFeature { 24 | public static final DefinedFeature DEFINED = new DefinedFeature<>( 25 | FeatureType.REGENERATION, VanillaRegenerationFeature::new, 26 | VanillaRegenerationFeature::initPlayer, 27 | FeatureType.EXHAUSTION, FeatureType.DIFFICULTY, FeatureType.VERSION 28 | ); 29 | 30 | public static final Tag STARVATION_TICKS = Tag.Integer("starvationTicks"); 31 | 32 | private final FeatureConfiguration configuration; 33 | 34 | private ExhaustionFeature exhaustionFeature; 35 | private DifficultyProvider difficultyFeature; 36 | private CombatVersion version; 37 | 38 | public VanillaRegenerationFeature(FeatureConfiguration configuration) { 39 | this.configuration = configuration; 40 | } 41 | 42 | @Override 43 | public void initDependencies() { 44 | this.exhaustionFeature = configuration.get(FeatureType.EXHAUSTION); 45 | this.difficultyFeature = configuration.get(FeatureType.DIFFICULTY); 46 | this.version = configuration.get(FeatureType.VERSION); 47 | } 48 | 49 | public static void initPlayer(Player player, boolean firstInit) { 50 | player.setTag(STARVATION_TICKS, 0); 51 | } 52 | 53 | @Override 54 | public void init(EventNode node) { 55 | node.addListener(PlayerTickEvent.class, event -> onTick(event.getPlayer())); 56 | } 57 | 58 | protected void onTick(Player player) { 59 | if (player.getGameMode().invulnerable()) return; 60 | Difficulty difficulty = difficultyFeature.getValue(player); 61 | 62 | int food = player.getFood(); 63 | float health = player.getHealth(); 64 | int starvationTicks = player.getTag(STARVATION_TICKS); 65 | 66 | if (version.modern() && player.getFoodSaturation() > 0 && health > 0 67 | && health < player.getAttributeValue(Attribute.MAX_HEALTH) && food >= 20) { 68 | starvationTicks++; 69 | if (starvationTicks >= 10) { 70 | float amount = Math.min(player.getFoodSaturation(), 6); 71 | regenerate(player, amount / 6, amount); 72 | starvationTicks = 0; 73 | } 74 | } else if (food >= 18 && health > 0 75 | && health < player.getAttributeValue(Attribute.MAX_HEALTH)) { 76 | starvationTicks++; 77 | if (starvationTicks >= 80) { 78 | regenerate(player, 1, version.legacy() ? 3 : 6); 79 | starvationTicks = 0; 80 | } 81 | } else if (food <= 0) { 82 | starvationTicks++; 83 | if (starvationTicks >= 80) { 84 | if (health > 10 || difficulty == Difficulty.HARD 85 | || ((health > 1) && (difficulty == Difficulty.NORMAL))) { 86 | player.damage(DamageType.STARVE, 1); 87 | } 88 | 89 | starvationTicks = 0; 90 | } 91 | } else { 92 | starvationTicks = 0; 93 | } 94 | 95 | player.setTag(STARVATION_TICKS, starvationTicks); 96 | } 97 | 98 | @Override 99 | public void regenerate(Player player, float health, float exhaustion) { 100 | PlayerRegenerateEvent event = new PlayerRegenerateEvent(player, health, exhaustion); 101 | EventDispatcher.callCancellable(event, () -> { 102 | player.setHealth(player.getHealth() + event.getAmount()); 103 | exhaustionFeature.addExhaustion(player, event.getExhaustion()); 104 | }); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/item/ItemDamageFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.item; 2 | 3 | import io.github.togar2.pvp.feature.CombatFeature; 4 | import net.minestom.server.entity.EquipmentSlot; 5 | import net.minestom.server.entity.LivingEntity; 6 | import net.minestom.server.entity.damage.DamageType; 7 | 8 | /** 9 | * Combat feature which handles damaging items (durability). 10 | */ 11 | public interface ItemDamageFeature extends CombatFeature { 12 | ItemDamageFeature NO_OP = new ItemDamageFeature() { 13 | @Override 14 | public void damageEquipment(LivingEntity entity, EquipmentSlot slot, int amount) {} 15 | 16 | @Override 17 | public void damageArmor(LivingEntity entity, DamageType damageType, float damage, EquipmentSlot... slots) {} 18 | }; 19 | 20 | void damageEquipment(LivingEntity entity, EquipmentSlot slot, int amount); 21 | 22 | void damageArmor(LivingEntity entity, DamageType damageType, float damage, EquipmentSlot... slots); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/item/VanillaItemDamageFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.item; 2 | 3 | import io.github.togar2.pvp.damage.DamageTypeInfo; 4 | import io.github.togar2.pvp.enums.ArmorMaterial; 5 | import io.github.togar2.pvp.events.EquipmentDamageEvent; 6 | import io.github.togar2.pvp.feature.FeatureType; 7 | import io.github.togar2.pvp.feature.config.DefinedFeature; 8 | import io.github.togar2.pvp.feature.config.FeatureConfiguration; 9 | import io.github.togar2.pvp.feature.enchantment.EnchantmentFeature; 10 | import net.minestom.server.MinecraftServer; 11 | import net.minestom.server.entity.EquipmentSlot; 12 | import net.minestom.server.entity.LivingEntity; 13 | import net.minestom.server.entity.damage.DamageType; 14 | import net.minestom.server.event.EventDispatcher; 15 | import net.minestom.server.item.ItemComponent; 16 | import net.minestom.server.item.ItemStack; 17 | 18 | import java.util.function.Consumer; 19 | import java.util.function.UnaryOperator; 20 | 21 | /** 22 | * Vanilla implementation of {@link ItemDamageFeature} 23 | */ 24 | public class VanillaItemDamageFeature implements ItemDamageFeature { 25 | public static final DefinedFeature DEFINED = new DefinedFeature<>( 26 | FeatureType.ITEM_DAMAGE, VanillaItemDamageFeature::new, 27 | FeatureType.ENCHANTMENT 28 | ); 29 | 30 | private final FeatureConfiguration configuration; 31 | 32 | private EnchantmentFeature enchantmentFeature; 33 | 34 | public VanillaItemDamageFeature(FeatureConfiguration configuration) { 35 | this.configuration = configuration; 36 | } 37 | 38 | @Override 39 | public void initDependencies() { 40 | this.enchantmentFeature = configuration.get(FeatureType.ENCHANTMENT); 41 | } 42 | 43 | protected ItemStack damage(ItemStack stack, int amount) { 44 | if (amount == 0 || stack.has(ItemComponent.UNBREAKABLE) || stack.get(ItemComponent.MAX_DAMAGE, 0) <= 0) 45 | return stack; 46 | 47 | int preventAmount = 0; 48 | int newAmount = amount; 49 | 50 | for (int i = 0; i < newAmount; i++) { 51 | if (enchantmentFeature.shouldUnbreakingPreventDamage(stack)) { 52 | preventAmount++; 53 | } 54 | } 55 | 56 | newAmount -= preventAmount; 57 | if (newAmount <= 0) return stack; 58 | 59 | int finalNewAmount = newAmount; 60 | return stack.with(ItemComponent.DAMAGE, (UnaryOperator) d -> d + finalNewAmount); 61 | } 62 | 63 | protected ItemStack damage(ItemStack stack, int amount, 64 | T entity, Consumer breakCallback) { 65 | if (amount == 0 || stack.get(ItemComponent.MAX_DAMAGE, 0) <= 0) 66 | return stack; 67 | 68 | ItemStack newStack = damage(stack, amount); 69 | if (newStack.get(ItemComponent.DAMAGE, 0) >= stack.get(ItemComponent.MAX_DAMAGE, 0)) { 70 | breakCallback.accept(entity); 71 | newStack = newStack.withAmount(i -> i - 1).with(ItemComponent.DAMAGE, 0); 72 | } 73 | 74 | return newStack; 75 | } 76 | 77 | @Override 78 | public void damageEquipment(LivingEntity entity, EquipmentSlot slot, int amount) { 79 | EquipmentDamageEvent equipmentDamageEvent = new EquipmentDamageEvent(entity, slot, amount); 80 | EventDispatcher.callCancellable(equipmentDamageEvent, () -> 81 | entity.setEquipment(slot, damage(entity.getEquipment(slot), amount, entity, 82 | e -> triggerEquipmentBreak(e, slot)))); 83 | } 84 | 85 | @Override 86 | public void damageArmor(LivingEntity entity, DamageType damageType, float damage, EquipmentSlot... slots) { 87 | if (damage <= 0) return; 88 | 89 | damage /= 4; 90 | if (damage < 1) { 91 | damage = 1; 92 | } 93 | 94 | for (EquipmentSlot slot : slots) { 95 | ItemStack stack = entity.getEquipment(slot); 96 | DamageTypeInfo info = DamageTypeInfo.of(MinecraftServer.getDamageTypeRegistry().getKey(damageType)); 97 | if (!(info.fire() && stack.material().key().value().toLowerCase().contains("netherite")) 98 | && ArmorMaterial.fromMaterial(stack.material()) != null) { 99 | damageEquipment(entity, slot, (int) damage); 100 | } 101 | } 102 | } 103 | 104 | private static void triggerEquipmentBreak(LivingEntity entity, EquipmentSlot slot) { 105 | entity.triggerStatus(getEquipmentBreakStatus(slot)); 106 | } 107 | 108 | private static byte getEquipmentBreakStatus(EquipmentSlot slot) { 109 | return switch (slot) { 110 | case OFF_HAND -> (byte) 48; 111 | case HELMET -> (byte) 49; 112 | case CHESTPLATE -> (byte) 50; 113 | case LEGGINGS -> (byte) 51; 114 | case BOOTS -> (byte) 52; 115 | default -> (byte) 47; 116 | }; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/knockback/KnockbackFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.knockback; 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 which handles different types of knockback. 9 | */ 10 | public interface KnockbackFeature extends CombatFeature { 11 | KnockbackFeature NO_OP = new KnockbackFeature() { 12 | @Override 13 | public boolean applyDamageKnockback(Damage damage, LivingEntity target) { 14 | return false; 15 | } 16 | 17 | @Override 18 | public boolean applyAttackKnockback(LivingEntity attacker, LivingEntity target, int knockback) { 19 | return false; 20 | } 21 | 22 | @Override 23 | public boolean applySweepingKnockback(LivingEntity attacker, LivingEntity target) { 24 | return false; 25 | } 26 | }; 27 | 28 | /** 29 | * Applies base knockback to the target entity. 30 | * 31 | * @param damage the damage that caused the knockback 32 | * @param target the entity that is receiving the knockback 33 | * @return true if the target entity was knocked back, false otherwise 34 | */ 35 | boolean applyDamageKnockback(Damage damage, LivingEntity target); 36 | 37 | /** 38 | * Applies an extra attack knockback to the target entity. 39 | * 40 | * @param attacker the attacker that caused the knockback 41 | * @param target the entity that is receiving the knockback 42 | * @return true if the target entity was knocked back, false otherwise 43 | */ 44 | boolean applyAttackKnockback(LivingEntity attacker, LivingEntity target, int knockback); 45 | 46 | /** 47 | * Applies sweeping knockback to the target entity. 48 | * 49 | * @param attacker the attacker that caused the knockback 50 | * @param target the entity that is receiving the knockback 51 | * @return true if the target entity was knocked back, false otherwise 52 | */ 53 | boolean applySweepingKnockback(LivingEntity attacker, LivingEntity target); 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/knockback/KnockbackSettings.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.knockback; 2 | 3 | import net.minestom.server.ServerFlag; 4 | 5 | /** 6 | * Class which contains settings for knockback. 7 | *

8 | * For further documentation, see the config of BukkitOldCombatMechanics 9 | *

10 | * (This class is also used for modern knockback) 11 | */ 12 | public record KnockbackSettings(double horizontal, double vertical, 13 | double verticalLimit, 14 | double extraHorizontal, double extraVertical) { 15 | public static final KnockbackSettings DEFAULT = builder().build(); 16 | 17 | public KnockbackSettings(double horizontal, double vertical, double verticalLimit, 18 | double extraHorizontal, double extraVertical) { 19 | double tps = ServerFlag.SERVER_TICKS_PER_SECOND; 20 | this.horizontal = horizontal * tps; 21 | this.vertical = vertical * tps; 22 | this.verticalLimit = verticalLimit * tps; 23 | this.extraHorizontal = extraHorizontal * tps; 24 | this.extraVertical = extraVertical * tps; 25 | } 26 | 27 | public static Builder builder() { 28 | return new Builder(); 29 | } 30 | 31 | public static class Builder { 32 | private double horizontal = 0.4, vertical = 0.4; 33 | private double verticalLimit = 0.4; 34 | private double extraHorizontal = 0.5, extraVertical = 0.1; 35 | 36 | public Builder horizontal(double horizontal) { 37 | this.horizontal = horizontal; 38 | return this; 39 | } 40 | 41 | public Builder vertical(double vertical) { 42 | this.vertical = vertical; 43 | return this; 44 | } 45 | 46 | public Builder verticalLimit(double verticalLimit) { 47 | this.verticalLimit = verticalLimit; 48 | return this; 49 | } 50 | 51 | public Builder extraHorizontal(double extraHorizontal) { 52 | this.extraHorizontal = extraHorizontal; 53 | return this; 54 | } 55 | 56 | public Builder extraVertical(double extraVertical) { 57 | this.extraVertical = extraVertical; 58 | return this; 59 | } 60 | 61 | public KnockbackSettings build() { 62 | return new KnockbackSettings(horizontal, vertical, verticalLimit, extraHorizontal, extraVertical); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/potion/PotionFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.potion; 2 | 3 | import io.github.togar2.pvp.feature.CombatFeature; 4 | 5 | /** 6 | * Combat feature which handles drinking and throwing potions. 7 | */ 8 | public interface PotionFeature extends CombatFeature { 9 | PotionFeature NO_OP = new PotionFeature() {}; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/projectile/BowFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.projectile; 2 | 3 | import io.github.togar2.pvp.feature.CombatFeature; 4 | 5 | /** 6 | * Combat feature which handles bow shooting. 7 | */ 8 | public interface BowFeature extends CombatFeature { 9 | BowFeature NO_OP = new BowFeature() {}; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/projectile/CrossbowFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.projectile; 2 | 3 | import io.github.togar2.pvp.feature.CombatFeature; 4 | 5 | /** 6 | * Combat feature which handles crossbow shooting. 7 | */ 8 | public interface CrossbowFeature extends CombatFeature { 9 | CrossbowFeature NO_OP = new CrossbowFeature() {}; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/projectile/FishingRodFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.projectile; 2 | 3 | import io.github.togar2.pvp.feature.CombatFeature; 4 | 5 | /** 6 | * Combat feature which handles throwing and retrieving fishing rods. 7 | */ 8 | public interface FishingRodFeature extends CombatFeature { 9 | FishingRodFeature NO_OP = new FishingRodFeature() {}; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/projectile/MiscProjectileFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.projectile; 2 | 3 | import io.github.togar2.pvp.feature.CombatFeature; 4 | 5 | /** 6 | * Combat feature which handles throwing snowballs, eggs and ender pearls. 7 | */ 8 | public interface MiscProjectileFeature extends CombatFeature { 9 | MiscProjectileFeature NO_OP = new MiscProjectileFeature() {}; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/projectile/ProjectileItemFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.projectile; 2 | 3 | import io.github.togar2.pvp.feature.CombatFeature; 4 | import net.minestom.server.entity.Player; 5 | import net.minestom.server.item.ItemStack; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | /** 9 | * Combat feature which determines which projectile to use for bow and crossbow shooting. 10 | */ 11 | public interface ProjectileItemFeature extends CombatFeature { 12 | ProjectileItemFeature NO_OP = new ProjectileItemFeature() { 13 | @Override 14 | public @Nullable ProjectileItem getBowProjectile(Player player) { 15 | return null; 16 | } 17 | 18 | @Override 19 | public @Nullable ProjectileItem getCrossbowProjectile(Player player) { 20 | return null; 21 | } 22 | }; 23 | 24 | @Nullable ProjectileItem getBowProjectile(Player player); 25 | 26 | @Nullable ProjectileItem getCrossbowProjectile(Player player); 27 | 28 | record ProjectileItem(int slot, ItemStack stack) {} 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/projectile/TridentFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.projectile; 2 | 3 | import io.github.togar2.pvp.feature.CombatFeature; 4 | import net.minestom.server.entity.Player; 5 | 6 | /** 7 | * Combat feature which handles using a trident. 8 | */ 9 | public interface TridentFeature extends CombatFeature { 10 | TridentFeature NO_OP = (player, level) -> {}; 11 | 12 | void applyRiptide(Player player, int level); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/projectile/VanillaMiscProjectileFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.projectile; 2 | 3 | import io.github.togar2.pvp.entity.projectile.*; 4 | import io.github.togar2.pvp.feature.FeatureType; 5 | import io.github.togar2.pvp.feature.RegistrableFeature; 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.cooldown.ItemCooldownFeature; 9 | import io.github.togar2.pvp.feature.fall.FallFeature; 10 | import io.github.togar2.pvp.utils.ViewUtil; 11 | import net.kyori.adventure.sound.Sound; 12 | import net.minestom.server.coordinate.Pos; 13 | import net.minestom.server.coordinate.Vec; 14 | import net.minestom.server.entity.GameMode; 15 | import net.minestom.server.entity.Player; 16 | import net.minestom.server.event.EventNode; 17 | import net.minestom.server.event.player.PlayerUseItemEvent; 18 | import net.minestom.server.event.trait.EntityInstanceEvent; 19 | import net.minestom.server.item.ItemStack; 20 | import net.minestom.server.item.Material; 21 | import net.minestom.server.sound.SoundEvent; 22 | 23 | import java.util.Objects; 24 | import java.util.concurrent.ThreadLocalRandom; 25 | 26 | /** 27 | * Vanilla implementation of {@link MiscProjectileFeature} 28 | */ 29 | public class VanillaMiscProjectileFeature implements MiscProjectileFeature, RegistrableFeature { 30 | public static final DefinedFeature DEFINED = new DefinedFeature<>( 31 | FeatureType.MISC_PROJECTILE, VanillaMiscProjectileFeature::new, 32 | FeatureType.ITEM_COOLDOWN, FeatureType.FALL 33 | ); 34 | 35 | private final FeatureConfiguration configuration; 36 | 37 | private ItemCooldownFeature itemCooldownFeature; 38 | private FallFeature fallFeature; 39 | 40 | public VanillaMiscProjectileFeature(FeatureConfiguration configuration) { 41 | this.configuration = configuration; 42 | } 43 | 44 | @Override 45 | public void initDependencies() { 46 | this.itemCooldownFeature = configuration.get(FeatureType.ITEM_COOLDOWN); 47 | this.fallFeature = configuration.get(FeatureType.FALL); 48 | } 49 | 50 | @Override 51 | public void init(EventNode node) { 52 | node.addListener(PlayerUseItemEvent.class, event -> { 53 | if (event.getItemStack().material() != Material.SNOWBALL 54 | && event.getItemStack().material() != Material.EGG 55 | && event.getItemStack().material() != Material.ENDER_PEARL) 56 | return; 57 | 58 | Player player = event.getPlayer(); 59 | ItemStack stack = event.getItemStack(); 60 | 61 | boolean snowball = stack.material() == Material.SNOWBALL; 62 | boolean enderpearl = stack.material() == Material.ENDER_PEARL; 63 | 64 | SoundEvent soundEvent; 65 | CustomEntityProjectile projectile; 66 | if (snowball) { 67 | soundEvent = SoundEvent.ENTITY_SNOWBALL_THROW; 68 | projectile = new Snowball(player); 69 | } else if (enderpearl) { 70 | soundEvent = SoundEvent.ENTITY_ENDER_PEARL_THROW; 71 | projectile = new ThrownEnderpearl(player, fallFeature); 72 | } else { 73 | soundEvent = SoundEvent.ENTITY_EGG_THROW; 74 | projectile = new ThrownEgg(player); 75 | } 76 | 77 | ((ItemHoldingProjectile) projectile).setItem(stack); 78 | 79 | ThreadLocalRandom random = ThreadLocalRandom.current(); 80 | ViewUtil.viewersAndSelf(player).playSound(Sound.sound( 81 | soundEvent, 82 | snowball || enderpearl ? Sound.Source.NEUTRAL : Sound.Source.PLAYER, 83 | 0.5f, 0.4f / (random.nextFloat() * 0.4f + 0.8f) 84 | ), player); 85 | 86 | if (enderpearl) { 87 | itemCooldownFeature.setCooldown(player, Material.ENDER_PEARL, 20); 88 | } 89 | 90 | Pos position = player.getPosition().add(0, player.getEyeHeight(), 0); 91 | projectile.shootFromRotation(position.pitch(), position.yaw(), 0, 1.5, 1.0); 92 | projectile.setInstance(Objects.requireNonNull(player.getInstance()), position.withView(projectile.getPosition())); 93 | 94 | Vec playerVel = player.getVelocity(); 95 | projectile.setVelocity(projectile.getVelocity().add(playerVel.x(), 96 | player.isOnGround() ? 0.0D : playerVel.y(), playerVel.z())); 97 | 98 | if (player.getGameMode() != GameMode.CREATIVE) { 99 | player.setItemInHand(event.getHand(), stack.withAmount(stack.amount() - 1)); 100 | } 101 | }); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/projectile/VanillaProjectileItemFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.projectile; 2 | 3 | import java.util.function.Predicate; 4 | 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | import io.github.togar2.pvp.entity.projectile.Arrow; 8 | import io.github.togar2.pvp.feature.FeatureType; 9 | import io.github.togar2.pvp.feature.config.DefinedFeature; 10 | import net.minestom.server.entity.GameMode; 11 | import net.minestom.server.entity.Player; 12 | import net.minestom.server.entity.PlayerHand; 13 | import net.minestom.server.item.ItemStack; 14 | import net.minestom.server.item.Material; 15 | import net.minestom.server.utils.inventory.PlayerInventoryUtils; 16 | 17 | /** 18 | * Vanilla implementation of {@link ProjectileItemFeature} 19 | */ 20 | public class VanillaProjectileItemFeature implements ProjectileItemFeature { 21 | public static final DefinedFeature DEFINED = new DefinedFeature<>( 22 | FeatureType.PROJECTILE_ITEM, configuration -> new VanillaProjectileItemFeature() 23 | ); 24 | 25 | public static final Predicate ARROW_PREDICATE = stack -> 26 | stack.material() == Material.ARROW 27 | || stack.material() == Material.SPECTRAL_ARROW 28 | || stack.material() == Material.TIPPED_ARROW; 29 | 30 | public static final Predicate ARROW_OR_FIREWORK_PREDICATE = ARROW_PREDICATE.or(stack -> 31 | stack.material() == Material.FIREWORK_ROCKET); 32 | 33 | @Override 34 | public @Nullable ProjectileItem getBowProjectile(Player player) { 35 | return getProjectile(player, ARROW_PREDICATE, ARROW_PREDICATE); 36 | } 37 | 38 | @Override 39 | public @Nullable ProjectileItem getCrossbowProjectile(Player player) { 40 | return getProjectile(player, ARROW_OR_FIREWORK_PREDICATE, ARROW_PREDICATE); 41 | } 42 | 43 | public static @Nullable ProjectileItem getProjectile(Player player, Predicate heldSupportedPredicate, 44 | Predicate allSupportedPredicate) { 45 | ProjectileItem held = getHeldItem(player, heldSupportedPredicate); 46 | if (held != null) return held; 47 | 48 | ItemStack[] itemStacks = player.getInventory().getItemStacks(); 49 | for (int i = 0; i < itemStacks.length; i++) { 50 | ItemStack stack = itemStacks[i]; 51 | if (stack == null || stack.isAir()) continue; 52 | if (allSupportedPredicate.test(stack)) return new ProjectileItem(i, stack); 53 | } 54 | 55 | if (player.getGameMode() == GameMode.CREATIVE) { 56 | return new ProjectileItem(-1, Arrow.DEFAULT_ARROW); 57 | } else { 58 | return null; 59 | } 60 | } 61 | 62 | private static @Nullable ProjectileItem getHeldItem(Player player, Predicate predicate) { 63 | ItemStack stack = player.getItemInHand(PlayerHand.OFF); 64 | if (predicate.test(stack)) return new ProjectileItem(PlayerInventoryUtils.OFFHAND_SLOT, stack); 65 | 66 | stack = player.getItemInHand(PlayerHand.MAIN); 67 | if (predicate.test(stack)) return new ProjectileItem(player.getHeldSlot(), stack); 68 | 69 | return null; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/provider/DifficultyProvider.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.provider; 2 | 3 | import io.github.togar2.pvp.feature.CombatFeature; 4 | import net.minestom.server.MinecraftServer; 5 | import net.minestom.server.entity.LivingEntity; 6 | import net.minestom.server.world.Difficulty; 7 | 8 | /** 9 | * Certain mechanics in vanilla are dependent on difficulty. 10 | * This combat feature can provide which difficulty should be used. 11 | */ 12 | public interface DifficultyProvider extends CombatFeature { 13 | DifficultyProvider DEFAULT = entity -> MinecraftServer.getDifficulty(); 14 | 15 | Difficulty getValue(LivingEntity entity); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/spectate/SpectateFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.spectate; 2 | 3 | import io.github.togar2.pvp.feature.CombatFeature; 4 | import net.minestom.server.entity.Entity; 5 | import net.minestom.server.entity.Player; 6 | 7 | /** 8 | * Combat feature which handles spectating in spectator mode. 9 | */ 10 | public interface SpectateFeature extends CombatFeature { 11 | SpectateFeature NO_OP = new SpectateFeature() { 12 | @Override 13 | public void makeSpectate(Player player, Entity target) {} 14 | 15 | @Override 16 | public void stopSpectating(Player player) {} 17 | }; 18 | 19 | void makeSpectate(Player player, Entity target); 20 | 21 | void stopSpectating(Player player); 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/spectate/VanillaSpectateFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.spectate; 2 | 3 | import io.github.togar2.pvp.events.PlayerSpectateEvent; 4 | import io.github.togar2.pvp.feature.FeatureType; 5 | import io.github.togar2.pvp.feature.RegistrableFeature; 6 | import io.github.togar2.pvp.feature.config.DefinedFeature; 7 | import net.minestom.server.entity.Entity; 8 | import net.minestom.server.entity.GameMode; 9 | import net.minestom.server.entity.LivingEntity; 10 | import net.minestom.server.entity.Player; 11 | import net.minestom.server.event.EventDispatcher; 12 | import net.minestom.server.event.EventNode; 13 | import net.minestom.server.event.entity.EntityAttackEvent; 14 | import net.minestom.server.event.player.PlayerTickEvent; 15 | import net.minestom.server.event.trait.EntityInstanceEvent; 16 | import net.minestom.server.tag.Tag; 17 | 18 | /** 19 | * Vanilla implementation of {@link SpectateFeature} 20 | */ 21 | public class VanillaSpectateFeature implements SpectateFeature, RegistrableFeature { 22 | public static final DefinedFeature DEFINED = new DefinedFeature<>( 23 | FeatureType.SPECTATE, configuration -> new VanillaSpectateFeature() 24 | ); 25 | 26 | public static final Tag SPECTATING = Tag.Transient("spectating"); 27 | 28 | @Override 29 | public int getPriority() { 30 | // Make sure events are called on this node before on VanillaAttackFeature 31 | // This seems to be the only way to have 'dependencies' without overcomplicating 32 | return -1; 33 | } 34 | 35 | @Override 36 | public void init(EventNode node) { 37 | node.addListener(EntityAttackEvent.class, event -> { 38 | if (event.getEntity() instanceof Player player && player.getGameMode() == GameMode.SPECTATOR) 39 | makeSpectate(player, event.getTarget()); 40 | }); 41 | 42 | node.addListener(PlayerTickEvent.class, event -> spectateTick(event.getPlayer())); 43 | } 44 | 45 | protected void spectateTick(Player player) { 46 | Entity spectating = player.getTag(SPECTATING); 47 | if (spectating == null || spectating == player) return; 48 | 49 | // This is to make sure other players don't see the player standing still while spectating 50 | // And when the player stops spectating, 51 | // they are at the entities position instead of their position before spectating 52 | player.teleport(spectating.getPosition()); 53 | 54 | if (player.getEntityMeta().isSneaking() || spectating.isRemoved() 55 | || (spectating instanceof LivingEntity livingSpectating && livingSpectating.isDead())) { 56 | stopSpectating(player); 57 | } 58 | } 59 | 60 | @Override 61 | public void makeSpectate(Player player, Entity target) { 62 | PlayerSpectateEvent playerSpectateEvent = new PlayerSpectateEvent(player, target); 63 | EventDispatcher.callCancellable(playerSpectateEvent, () -> { 64 | player.spectate(target); 65 | player.setTag(SPECTATING, target); 66 | }); 67 | } 68 | 69 | @Override 70 | public void stopSpectating(Player player) { 71 | player.stopSpectating(); 72 | player.removeTag(SPECTATING); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/state/PlayerStateFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.state; 2 | 3 | import io.github.togar2.pvp.feature.CombatFeature; 4 | import net.minestom.server.entity.LivingEntity; 5 | import net.minestom.server.instance.block.Block; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | /** 9 | * Combat feature which handles certain player states. For now this is only climbing. 10 | */ 11 | public interface PlayerStateFeature extends CombatFeature { 12 | PlayerStateFeature NO_OP = new PlayerStateFeature() { 13 | @Override 14 | public boolean isClimbing(LivingEntity entity) { 15 | return false; 16 | } 17 | 18 | @Override 19 | public Block getLastClimbedBlock(LivingEntity entity) { 20 | return Block.AIR; 21 | } 22 | }; 23 | 24 | boolean isClimbing(LivingEntity entity); 25 | 26 | @Nullable Block getLastClimbedBlock(LivingEntity entity); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/state/VanillaPlayerStateFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.state; 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.GameMode; 8 | import net.minestom.server.entity.LivingEntity; 9 | import net.minestom.server.entity.Player; 10 | import net.minestom.server.event.EventNode; 11 | import net.minestom.server.event.player.PlayerMoveEvent; 12 | import net.minestom.server.event.player.PlayerTickEvent; 13 | import net.minestom.server.event.trait.EntityInstanceEvent; 14 | import net.minestom.server.instance.block.Block; 15 | import net.minestom.server.tag.Tag; 16 | import org.jetbrains.annotations.Nullable; 17 | 18 | import java.util.Objects; 19 | 20 | /** 21 | * Vanilla implementation of {@link PlayerStateFeature} 22 | */ 23 | public class VanillaPlayerStateFeature implements PlayerStateFeature, RegistrableFeature { 24 | public static final DefinedFeature DEFINED = new DefinedFeature<>( 25 | FeatureType.PLAYER_STATE, configuration -> new VanillaPlayerStateFeature() 26 | ); 27 | 28 | public static final Tag LAST_CLIMBED_BLOCK = Tag.Transient("lastClimbedBlock"); 29 | 30 | @Override 31 | public void init(EventNode node) { 32 | node.addListener(PlayerTickEvent.class, event -> { 33 | Player player = event.getPlayer(); 34 | if (player.isOnGround() && player.hasTag(LAST_CLIMBED_BLOCK)) { 35 | // Make sure fall damage message still has the correct climbed block 36 | // Due to multithreading this can be triggered before the death message is computed 37 | player.scheduleNextTick(p -> p.removeTag(LAST_CLIMBED_BLOCK)); 38 | } 39 | }); 40 | 41 | node.addListener(PlayerMoveEvent.class, event -> { 42 | Player player = event.getPlayer(); 43 | if (isClimbing(player)) { 44 | player.setTag(LAST_CLIMBED_BLOCK, player.getInstance().getBlock(player.getPosition())); 45 | } 46 | }); 47 | } 48 | 49 | @Override 50 | public boolean isClimbing(LivingEntity entity) { 51 | if (entity instanceof Player player && player.getGameMode() == GameMode.SPECTATOR) return false; 52 | 53 | var tag = MinecraftServer.getTagManager().getTag(net.minestom.server.gamedata.tags.Tag.BasicType.BLOCKS, "minecraft:climbable"); 54 | assert tag != null; 55 | 56 | Block block = Objects.requireNonNull(entity.getInstance()).getBlock(entity.getPosition()); 57 | return tag.contains(block.key()); 58 | } 59 | 60 | @Override 61 | public @Nullable Block getLastClimbedBlock(LivingEntity entity) { 62 | return entity.getTag(LAST_CLIMBED_BLOCK); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/totem/TotemFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.totem; 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 which determines whether a totem protects a player and what happens afterward. 9 | */ 10 | public interface TotemFeature extends CombatFeature { 11 | TotemFeature NO_OP = (entity, type) -> false; 12 | 13 | /** 14 | * Returns whether the entity is protected. May also apply (visual) effects. 15 | * 16 | * @param entity the entity to check for 17 | * @param type the type of damage being done to the entity 18 | * @return whether the entity is protected by a totem 19 | */ 20 | boolean tryProtect(LivingEntity entity, DamageType type); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/totem/VanillaTotemFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.totem; 2 | 3 | import io.github.togar2.pvp.damage.DamageTypeInfo; 4 | import io.github.togar2.pvp.events.TotemUseEvent; 5 | import io.github.togar2.pvp.feature.FeatureType; 6 | import io.github.togar2.pvp.feature.config.DefinedFeature; 7 | import io.github.togar2.pvp.feature.food.VanillaFoodFeature; 8 | import net.minestom.server.MinecraftServer; 9 | import net.minestom.server.entity.LivingEntity; 10 | import net.minestom.server.entity.PlayerHand; 11 | import net.minestom.server.entity.damage.DamageType; 12 | import net.minestom.server.event.EventDispatcher; 13 | import net.minestom.server.item.ItemComponent; 14 | import net.minestom.server.item.ItemStack; 15 | import net.minestom.server.item.component.ConsumeEffect; 16 | import net.minestom.server.item.component.DeathProtection; 17 | 18 | import java.util.Random; 19 | import java.util.concurrent.ThreadLocalRandom; 20 | 21 | /** 22 | * Vanilla implementation of {@link TotemFeature} 23 | */ 24 | public class VanillaTotemFeature implements TotemFeature { 25 | public static final DefinedFeature DEFINED = new DefinedFeature<>( 26 | FeatureType.TOTEM, configuration -> new VanillaTotemFeature() 27 | ); 28 | 29 | @Override 30 | public boolean tryProtect(LivingEntity entity, DamageType type) { 31 | if (DamageTypeInfo.of(MinecraftServer.getDamageTypeRegistry().getKey(type)).outOfWorld()) return false; 32 | 33 | DeathProtection deathProtection = null; 34 | for (PlayerHand hand : PlayerHand.values()) { 35 | ItemStack stack = entity.getItemInHand(hand); 36 | if (stack.has(ItemComponent.DEATH_PROTECTION)) { 37 | TotemUseEvent totemUseEvent = new TotemUseEvent(entity, hand); 38 | EventDispatcher.call(totemUseEvent); 39 | 40 | if (totemUseEvent.isCancelled()) continue; 41 | 42 | deathProtection = stack.get(ItemComponent.DEATH_PROTECTION); 43 | entity.setItemInHand(hand, stack.withAmount(stack.amount() - 1)); 44 | break; 45 | } 46 | } 47 | 48 | if (deathProtection != null) { 49 | entity.setHealth(1.0f); 50 | 51 | Random random = ThreadLocalRandom.current(); 52 | for (ConsumeEffect deathEffect : deathProtection.deathEffects()) { 53 | VanillaFoodFeature.applyConsumeEffect(entity, deathEffect, random); 54 | } 55 | 56 | // Totem particles 57 | entity.triggerStatus((byte) 35); 58 | } 59 | 60 | return deathProtection != null; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/tracking/TrackingFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.tracking; 2 | 3 | import io.github.togar2.pvp.feature.CombatFeature; 4 | import net.kyori.adventure.text.Component; 5 | import net.minestom.server.entity.Entity; 6 | import net.minestom.server.entity.Player; 7 | import net.minestom.server.entity.damage.Damage; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | /** 11 | * Combat feature which is used for tracking all the damage to a player and their death message. 12 | */ 13 | public interface TrackingFeature extends CombatFeature { 14 | TrackingFeature NO_OP = new TrackingFeature() { 15 | @Override 16 | public void recordDamage(Player player, @Nullable Entity attacker, Damage damage) {} 17 | 18 | @Override 19 | public @Nullable Component getDeathMessage(Player player) { 20 | return null; 21 | } 22 | }; 23 | 24 | void recordDamage(Player player, @Nullable Entity attacker, Damage damage); 25 | 26 | @Nullable 27 | Component getDeathMessage(Player player); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/feature/tracking/VanillaDeathMessageFeature.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.feature.tracking; 2 | 3 | import io.github.togar2.pvp.damage.combat.CombatManager; 4 | import io.github.togar2.pvp.feature.FeatureType; 5 | import io.github.togar2.pvp.feature.RegistrableFeature; 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.fall.FallFeature; 9 | import io.github.togar2.pvp.feature.state.PlayerStateFeature; 10 | import net.kyori.adventure.text.Component; 11 | import net.minestom.server.entity.Entity; 12 | import net.minestom.server.entity.Player; 13 | import net.minestom.server.entity.damage.Damage; 14 | import net.minestom.server.event.EventNode; 15 | import net.minestom.server.event.player.PlayerDeathEvent; 16 | import net.minestom.server.event.player.PlayerSpawnEvent; 17 | import net.minestom.server.event.player.PlayerTickEvent; 18 | import net.minestom.server.event.trait.EntityInstanceEvent; 19 | import net.minestom.server.tag.Tag; 20 | import org.jetbrains.annotations.Nullable; 21 | 22 | /** 23 | * Vanilla implementation of {@link TrackingFeature} 24 | */ 25 | public class VanillaDeathMessageFeature implements TrackingFeature, RegistrableFeature { 26 | public static final DefinedFeature DEFINED = new DefinedFeature<>( 27 | FeatureType.TRACKING, VanillaDeathMessageFeature::new, 28 | VanillaDeathMessageFeature::initPlayer, 29 | FeatureType.FALL, FeatureType.PLAYER_STATE 30 | ); 31 | 32 | public static final Tag COMBAT_MANAGER = Tag.Transient("combatManager"); 33 | 34 | private final FeatureConfiguration configuration; 35 | 36 | private FallFeature fallFeature; 37 | private PlayerStateFeature playerStateFeature; 38 | 39 | public VanillaDeathMessageFeature(FeatureConfiguration configuration) { 40 | this.configuration = configuration; 41 | } 42 | 43 | @Override 44 | public void initDependencies() { 45 | this.fallFeature = configuration.get(FeatureType.FALL); 46 | this.playerStateFeature = configuration.get(FeatureType.PLAYER_STATE); 47 | } 48 | 49 | public static void initPlayer(Player player, boolean firstInit) { 50 | if (firstInit) player.setTag(COMBAT_MANAGER, new CombatManager(player)); 51 | } 52 | 53 | @Override 54 | public void init(EventNode node) { 55 | node.addListener(PlayerSpawnEvent.class, event -> event.getPlayer().getTag(COMBAT_MANAGER).reset()); 56 | 57 | node.addListener(PlayerTickEvent.class, event -> event.getPlayer().getTag(COMBAT_MANAGER).tick()); 58 | 59 | node.addListener(PlayerDeathEvent.class, event -> { 60 | Component message = getDeathMessage(event.getPlayer()); 61 | event.setChatMessage(message); 62 | event.setDeathText(message); 63 | }); 64 | } 65 | 66 | @Override 67 | public void recordDamage(Player player, @Nullable Entity attacker, Damage damage) { 68 | int id = attacker == null ? -1 : attacker.getEntityId(); 69 | player.getTag(COMBAT_MANAGER).recordDamage(id, damage, fallFeature, playerStateFeature); 70 | } 71 | 72 | @Override 73 | public @Nullable Component getDeathMessage(Player player) { 74 | return player.getTag(COMBAT_MANAGER).getDeathMessage(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/player/CombatPlayer.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.player; 2 | 3 | import net.minestom.server.ServerFlag; 4 | import net.minestom.server.coordinate.Pos; 5 | import net.minestom.server.coordinate.Vec; 6 | import net.minestom.server.entity.Player; 7 | import net.minestom.server.entity.attribute.Attribute; 8 | import net.minestom.server.entity.attribute.AttributeInstance; 9 | import net.minestom.server.event.Event; 10 | import net.minestom.server.event.EventNode; 11 | import net.minestom.server.event.player.PlayerMoveEvent; 12 | import net.minestom.server.potion.PotionEffect; 13 | import net.minestom.server.potion.TimedPotion; 14 | 15 | import java.util.function.Function; 16 | 17 | public interface CombatPlayer { 18 | // Minestom methods 19 | TimedPotion getEffect(PotionEffect effect); 20 | AttributeInstance getAttribute(Attribute attribute); 21 | boolean isSprinting(); 22 | Pos getPosition(); 23 | 24 | /** 25 | * Does not guarantee anything, the implementation uses Minestom physics logic which does not take into account many edge cases. 26 | * It is also quite performance intensive, not suitable for calling often. 27 | * @param ticks the amount of ticks to test for 28 | * @return true if the player will likely be on the ground in the given amount of ticks 29 | */ 30 | boolean isOnGroundAfterTicks(int ticks); 31 | 32 | default double getJumpVelocity() { 33 | return getAttribute(Attribute.JUMP_STRENGTH).getValue(); 34 | } 35 | 36 | default double getJumpBoostVelocityModifier() { 37 | TimedPotion effect = getEffect(PotionEffect.JUMP_BOOST); 38 | return effect != null ? 39 | (0.1 * (effect.potion().amplifier() + 1)) : 0.0; 40 | } 41 | 42 | default void jump() { 43 | int tps = ServerFlag.SERVER_TICKS_PER_SECOND; 44 | double yVel = getJumpVelocity() + getJumpBoostVelocityModifier(); 45 | setVelocityNoUpdate(velocity -> velocity.withY(Math.max(velocity.y(), yVel * tps))); 46 | if (isSprinting()) { 47 | double angle = getPosition().yaw() * (Math.PI / 180); 48 | setVelocityNoUpdate(velocity -> velocity.add(-Math.sin(angle) * 0.2 * tps, 0, Math.cos(angle) * 0.2 * tps)); 49 | } 50 | } 51 | 52 | default void afterSprintAttack() { 53 | setVelocityNoUpdate(velocity -> velocity.mul(0.6, 1, 0.6)); 54 | } 55 | 56 | void setVelocityNoUpdate(Function function); 57 | 58 | void sendImmediateVelocityUpdate(); 59 | 60 | static void init(EventNode node) { 61 | node.addListener(PlayerMoveEvent.class, event -> { 62 | Player player = event.getPlayer(); 63 | if (player.isOnGround() && !event.isOnGround() 64 | && event.getNewPosition().y() > player.getPosition().y() 65 | && player instanceof CombatPlayer combatPlayer) { 66 | combatPlayer.jump(); 67 | } 68 | }); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/potion/effect/AbsorptionPotionEffect.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.potion.effect; 2 | 3 | import io.github.togar2.pvp.utils.CombatVersion; 4 | import net.minestom.server.entity.LivingEntity; 5 | import net.minestom.server.entity.Player; 6 | import net.minestom.server.potion.PotionEffect; 7 | 8 | public class AbsorptionPotionEffect extends CombatPotionEffect { 9 | public AbsorptionPotionEffect() { 10 | super(PotionEffect.ABSORPTION); 11 | } 12 | 13 | @Override 14 | public void onApplied(LivingEntity entity, int amplifier, CombatVersion version) { 15 | if (entity instanceof Player player) { 16 | player.setAdditionalHearts(player.getAdditionalHearts() + (float) (4 * (amplifier + 1))); 17 | } 18 | 19 | super.onApplied(entity, amplifier, version); 20 | } 21 | 22 | @Override 23 | public void onRemoved(LivingEntity entity, int amplifier, CombatVersion version) { 24 | if (entity instanceof Player player) { 25 | player.setAdditionalHearts(Math.max(player.getAdditionalHearts() - (float) (4 * (amplifier + 1)), 0)); 26 | } 27 | 28 | super.onRemoved(entity, amplifier, version); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/potion/effect/CombatPotionEffects.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.potion.effect; 2 | 3 | import net.kyori.adventure.key.Key; 4 | import net.minestom.server.entity.attribute.Attribute; 5 | import net.minestom.server.entity.attribute.AttributeOperation; 6 | import net.minestom.server.particle.Particle; 7 | import net.minestom.server.potion.PotionEffect; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | public class CombatPotionEffects { 13 | private static final Map POTION_EFFECTS = new HashMap<>(); 14 | 15 | public static CombatPotionEffect get(PotionEffect potionEffect) { 16 | return POTION_EFFECTS.get(potionEffect); 17 | } 18 | 19 | public static void register(CombatPotionEffect... potionEffects) { 20 | for (CombatPotionEffect potionEffect : potionEffects) { 21 | POTION_EFFECTS.put(potionEffect.getPotionEffect(), potionEffect); 22 | } 23 | } 24 | 25 | public static void registerAll() { 26 | register( 27 | new CombatPotionEffect(PotionEffect.SPEED).addAttributeModifier(Attribute.MOVEMENT_SPEED, Key.key("minecraft:effect.speed"), 0.2, AttributeOperation.MULTIPLY_TOTAL), 28 | new CombatPotionEffect(PotionEffect.SLOWNESS).addAttributeModifier(Attribute.MOVEMENT_SPEED, Key.key("minecraft:effect.slowness"), -0.15, AttributeOperation.MULTIPLY_TOTAL), 29 | new CombatPotionEffect(PotionEffect.HASTE).addAttributeModifier(Attribute.ATTACK_SPEED, Key.key("minecraft:effect.haste"), 0.1, AttributeOperation.MULTIPLY_TOTAL), 30 | new CombatPotionEffect(PotionEffect.MINING_FATIGUE).addAttributeModifier(Attribute.ATTACK_SPEED, Key.key("minecraft:effect.mining_fatigue"), -0.1, AttributeOperation.MULTIPLY_TOTAL), 31 | new CombatPotionEffect(PotionEffect.STRENGTH).addAttributeModifier(Attribute.ATTACK_DAMAGE, Key.key("minecraft:effect.strength"), 3.0, AttributeOperation.ADD_VALUE).addLegacyAttributeModifier(Attribute.ATTACK_DAMAGE, Key.key("minecraft:effect.strength"), 1.3, AttributeOperation.MULTIPLY_TOTAL), 32 | new CombatPotionEffect(PotionEffect.INSTANT_HEALTH), 33 | new CombatPotionEffect(PotionEffect.INSTANT_DAMAGE), 34 | new CombatPotionEffect(PotionEffect.JUMP_BOOST).addAttributeModifier(Attribute.SAFE_FALL_DISTANCE, Key.key("minecraft:effect.jump_boost"), 1.0, AttributeOperation.ADD_VALUE), 35 | new CombatPotionEffect(PotionEffect.NAUSEA), 36 | new CombatPotionEffect(PotionEffect.REGENERATION), 37 | new CombatPotionEffect(PotionEffect.RESISTANCE), 38 | new CombatPotionEffect(PotionEffect.FIRE_RESISTANCE), 39 | new CombatPotionEffect(PotionEffect.WATER_BREATHING), 40 | new CombatPotionEffect(PotionEffect.INVISIBILITY), 41 | new CombatPotionEffect(PotionEffect.BLINDNESS), 42 | new CombatPotionEffect(PotionEffect.NIGHT_VISION), 43 | new CombatPotionEffect(PotionEffect.HUNGER), 44 | new CombatPotionEffect(PotionEffect.WEAKNESS).addAttributeModifier(Attribute.ATTACK_DAMAGE, Key.key("minecraft:effect.weakness"), -4.0, AttributeOperation.ADD_VALUE).addLegacyAttributeModifier(Attribute.ATTACK_DAMAGE, Key.key("minecraft:effect.weakness"), -0.5, AttributeOperation.ADD_VALUE), 45 | new CombatPotionEffect(PotionEffect.POISON), 46 | new CombatPotionEffect(PotionEffect.WITHER), 47 | new HealthBoostPotionEffect().addAttributeModifier(Attribute.MAX_HEALTH, Key.key("minecraft:effect.health_boost"), 4.0, AttributeOperation.ADD_VALUE), 48 | new AbsorptionPotionEffect(), 49 | new CombatPotionEffect(PotionEffect.SATURATION), 50 | new GlowingPotionEffect(), 51 | new CombatPotionEffect(PotionEffect.LEVITATION), 52 | new CombatPotionEffect(PotionEffect.LUCK).addAttributeModifier(Attribute.LUCK, Key.key("minecraft:effect.luck"), 1.0, AttributeOperation.ADD_VALUE), 53 | new CombatPotionEffect(PotionEffect.UNLUCK).addAttributeModifier(Attribute.LUCK, Key.key("minecraft:effect.unluck"), -1.0, AttributeOperation.ADD_VALUE), 54 | new CombatPotionEffect(PotionEffect.SLOW_FALLING), 55 | new CombatPotionEffect(PotionEffect.CONDUIT_POWER), 56 | new CombatPotionEffect(PotionEffect.DOLPHINS_GRACE), 57 | new CombatPotionEffect(PotionEffect.BAD_OMEN), 58 | new CombatPotionEffect(PotionEffect.HERO_OF_THE_VILLAGE), 59 | new CombatPotionEffect(PotionEffect.DARKNESS), 60 | new CombatPotionEffect(PotionEffect.TRIAL_OMEN, potion -> Particle.TRIAL_OMEN), 61 | new CombatPotionEffect(PotionEffect.RAID_OMEN, potion -> Particle.RAID_OMEN), 62 | new CombatPotionEffect(PotionEffect.WIND_CHARGED, potion -> Particle.SMALL_GUST), 63 | new CombatPotionEffect(PotionEffect.WEAVING), 64 | new CombatPotionEffect(PotionEffect.OOZING), 65 | new CombatPotionEffect(PotionEffect.INFESTED) 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/potion/effect/GlowingPotionEffect.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.potion.effect; 2 | 3 | import io.github.togar2.pvp.utils.CombatVersion; 4 | import net.minestom.server.entity.LivingEntity; 5 | import net.minestom.server.potion.PotionEffect; 6 | 7 | public class GlowingPotionEffect extends CombatPotionEffect { 8 | public GlowingPotionEffect() { 9 | super(PotionEffect.GLOWING); 10 | } 11 | 12 | @Override 13 | public void onApplied(LivingEntity entity, int amplifier, CombatVersion version) { 14 | entity.setGlowing(true); 15 | } 16 | 17 | @Override 18 | public void onRemoved(LivingEntity entity, int amplifier, CombatVersion version) { 19 | entity.setGlowing(false); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/potion/effect/HealthBoostPotionEffect.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.potion.effect; 2 | 3 | import io.github.togar2.pvp.utils.CombatVersion; 4 | import net.minestom.server.entity.LivingEntity; 5 | import net.minestom.server.entity.attribute.Attribute; 6 | import net.minestom.server.potion.PotionEffect; 7 | 8 | public class HealthBoostPotionEffect extends CombatPotionEffect { 9 | public HealthBoostPotionEffect() { 10 | super(PotionEffect.HEALTH_BOOST); 11 | } 12 | 13 | @Override 14 | public void onRemoved(LivingEntity entity, int amplifier, CombatVersion version) { 15 | super.onRemoved(entity, amplifier, version); 16 | 17 | if (entity.getHealth() > entity.getAttributeValue(Attribute.MAX_HEALTH)) { 18 | entity.setHealth((float) entity.getAttributeValue(Attribute.MAX_HEALTH)); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/potion/item/CombatPotionType.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.potion.item; 2 | 3 | import io.github.togar2.pvp.utils.CombatVersion; 4 | import net.minestom.server.potion.Potion; 5 | import net.minestom.server.potion.PotionType; 6 | 7 | import java.util.List; 8 | 9 | public class CombatPotionType { 10 | private final PotionType potionType; 11 | private final List effects; 12 | private List legacyEffects; 13 | 14 | public CombatPotionType(PotionType potionType, Potion... effects) { 15 | this.potionType = potionType; 16 | this.effects = List.of(effects); 17 | } 18 | 19 | public CombatPotionType legacy(Potion... effects) { 20 | legacyEffects = List.of(effects); 21 | return this; 22 | } 23 | 24 | public PotionType getPotionType() { 25 | return potionType; 26 | } 27 | 28 | public List getEffects(CombatVersion version) { 29 | if (legacyEffects == null) return effects; 30 | return version.legacy() ? legacyEffects : effects; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/utils/AccurateLatencyListener.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.utils; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.kyori.adventure.text.format.NamedTextColor; 5 | import net.minestom.server.entity.Player; 6 | import net.minestom.server.event.player.PlayerPacketOutEvent; 7 | import net.minestom.server.network.packet.client.common.ClientKeepAlivePacket; 8 | import net.minestom.server.network.packet.server.common.KeepAlivePacket; 9 | import net.minestom.server.tag.Tag; 10 | 11 | public class AccurateLatencyListener { 12 | private static final Component KICK_MESSAGE = Component.text("Bad Keep Alive packet", NamedTextColor.RED); 13 | 14 | private static final Tag SEND_TIME = Tag.Transient("keepalive_send_time"); 15 | 16 | public static void listener(ClientKeepAlivePacket packet, Player player) { 17 | final long packetId = packet.id(); 18 | if (packetId != player.getLastKeepAlive()) { 19 | player.kick(KICK_MESSAGE); 20 | return; 21 | } 22 | player.refreshAnswerKeepAlive(true); 23 | long sendTime = player.getTag(SEND_TIME); 24 | // Update latency 25 | final int latency = (int) (System.currentTimeMillis() - sendTime); 26 | player.refreshLatency(latency); 27 | } 28 | 29 | public static void onSend(PlayerPacketOutEvent event) { 30 | // This will get called right before writing the packet, so more accuracy 31 | if (event.getPacket() instanceof KeepAlivePacket) { 32 | event.getPlayer().setTag(SEND_TIME, System.currentTimeMillis()); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/utils/CombatVersion.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.utils; 2 | 3 | import io.github.togar2.pvp.feature.CombatFeature; 4 | 5 | public final class CombatVersion implements CombatFeature { 6 | public static CombatVersion MODERN = new CombatVersion(false); 7 | public static CombatVersion LEGACY = new CombatVersion(true); 8 | 9 | private final boolean legacy; 10 | 11 | CombatVersion(boolean legacy) { 12 | this.legacy = legacy; 13 | } 14 | 15 | public boolean modern() { 16 | return !legacy; 17 | } 18 | 19 | public boolean legacy() { 20 | return legacy; 21 | } 22 | 23 | public static CombatVersion fromLegacy(boolean legacy) { 24 | return legacy ? LEGACY : MODERN; 25 | } 26 | 27 | @Override 28 | public String toString() { 29 | return "CombatVersion[legacy=" + legacy + "]"; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/utils/EffectUtil.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.utils; 2 | 3 | import net.minestom.server.network.packet.server.play.WorldEventPacket; 4 | import net.minestom.server.worldevent.WorldEvent; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import net.minestom.server.coordinate.Pos; 8 | import net.minestom.server.instance.Instance; 9 | import net.minestom.server.utils.PacketSendingUtils; 10 | 11 | public class EffectUtil { 12 | public static void sendNearby(@NotNull Instance instance, @NotNull WorldEvent effect, 13 | int x, int y, int z, int data, double distance, boolean global) { 14 | WorldEventPacket packet = new WorldEventPacket(effect.id(), new Pos(x, y, z), data, global); 15 | 16 | double distanceSquared = distance * distance; 17 | PacketSendingUtils.sendGroupedPacket(instance.getPlayers(), packet, player -> { 18 | Pos position = player.getPosition(); 19 | double dx = x - position.x(); 20 | double dy = y - position.y(); 21 | double dz = z - position.z(); 22 | 23 | return dx * dx + dy * dy + dz * dz < distanceSquared; 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/utils/EntityUtil.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.utils; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.kyori.adventure.text.event.HoverEvent; 5 | import net.minestom.server.entity.Entity; 6 | import net.minestom.server.entity.ItemEntity; 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.item.ItemStack; 11 | import net.minestom.server.utils.time.TimeUnit; 12 | 13 | import java.lang.reflect.Field; 14 | import java.util.Objects; 15 | 16 | public class EntityUtil { 17 | public static void spawnItemAtLocation(Entity entity, ItemStack itemStack, double up) { 18 | if (itemStack.isAir()) return; 19 | 20 | ItemEntity item = new ItemEntity(itemStack); 21 | item.setPickupDelay(10, TimeUnit.SERVER_TICK); // Default 0.5 seconds 22 | item.setInstance(Objects.requireNonNull(entity.getInstance()), entity.getPosition().add(0, up, 0)); 23 | } 24 | 25 | public static Component getName(Entity entity) { 26 | HoverEvent hoverEvent = HoverEvent.showEntity(entity.getEntityType().key(), entity.getUuid()); 27 | if (entity.getCustomName() != null) { 28 | return entity.getCustomName().hoverEvent(hoverEvent); 29 | } else if (entity instanceof Player) { 30 | return ((Player) entity).getName().hoverEvent(hoverEvent); 31 | } else { 32 | // Use entity type without underscores and starting with capital letter 33 | String name = entity.getEntityType().key().value().replace('_', ' '); 34 | name = name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase(); 35 | return Component.text(name).hoverEvent(hoverEvent); 36 | } 37 | } 38 | 39 | public static void setLastDamage(LivingEntity livingEntity, Damage lastDamage) { 40 | // Use reflection to set lastDamage field 41 | try { 42 | Field field = LivingEntity.class.getDeclaredField("lastDamage"); 43 | field.setAccessible(true); 44 | field.set(livingEntity, lastDamage); 45 | } catch (NoSuchFieldException | IllegalAccessException e) { 46 | e.printStackTrace(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/utils/FluidUtil.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.utils; 2 | 3 | import it.unimi.dsi.fastutil.Pair; 4 | import net.minestom.server.coordinate.Pos; 5 | import net.minestom.server.entity.Player; 6 | import net.minestom.server.instance.Instance; 7 | import net.minestom.server.instance.block.Block; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | public class FluidUtil { 13 | public static int getLevel(Block block) { 14 | String levelStr = block.getProperty("level"); 15 | if (levelStr == null) return 8; 16 | int level = Integer.parseInt(levelStr); 17 | if (level >= 8) return 8; // Falling water 18 | return 8 - level; 19 | } 20 | 21 | public static double getHeight(Block block) { 22 | int level = getLevel(block); 23 | return switch (level) { 24 | case 1 -> 0.25; 25 | case 2 -> 0.375; 26 | case 3 -> 0.5; 27 | case 4 -> 0.625; 28 | case 5 -> 0.75; 29 | default -> 1; 30 | }; 31 | } 32 | 33 | public static boolean isTouchingWater(Player player, Block block, int blockY) { 34 | if (!block.compare(Block.WATER)) return false; 35 | if (player.getPosition().y() + player.getBoundingBox().height() < blockY) return false; 36 | if (player.getPosition().y() > (blockY + getHeight(block))) return false; 37 | return true; 38 | } 39 | 40 | public static boolean isTouchingWater(Player player) { 41 | Pos position = player.getPosition(); 42 | double x = position.x(); 43 | int blockX = position.blockX(); 44 | double z = position.z(); 45 | int blockZ = position.blockZ(); 46 | double y = position.y(); 47 | int blockY = position.blockY(); 48 | 49 | List> points = new ArrayList<>(); 50 | points.add(Pair.of(blockX, blockZ)); 51 | 52 | if (x - blockX > 0.7) { 53 | if (z - blockZ > 0.7) { 54 | points.add(Pair.of(blockX + 1, blockZ + 1)); 55 | } 56 | points.add(Pair.of(blockX + 1, blockZ)); 57 | } else if (x - blockX < 0.2) { 58 | if (z - blockZ < 0.2) { 59 | points.add(Pair.of(blockX - 1, blockZ - 1)); 60 | } 61 | points.add(Pair.of(blockX - 1, blockZ)); 62 | } 63 | if (z - blockZ > 0.7) { 64 | if (x - blockX < 0.2) { 65 | points.add(Pair.of(blockX - 1, blockZ + 1)); 66 | } 67 | points.add(Pair.of(blockX, blockZ + 1)); 68 | } else if (z - blockZ < 0.2) { 69 | if (x - blockX > 0.7) { 70 | points.add(Pair.of(blockX + 1, blockZ - 1)); 71 | } 72 | points.add(Pair.of(blockX, blockZ - 1)); 73 | } 74 | 75 | Instance instance = player.getInstance(); 76 | assert instance != null; 77 | 78 | for (Pair pair : points) { 79 | Block block = instance.getBlock(pair.first(), blockY, pair.second()); 80 | if (isTouchingWater(player, block, blockY)) return true; 81 | block = instance.getBlock(pair.first(), blockY + 1, pair.second()); 82 | if (isTouchingWater(player, block, blockY + 1)) return true; 83 | 84 | if (y - blockY >= 2 - player.getBoundingBox().height()) { 85 | block = instance.getBlock(pair.first(), blockY + 2, pair.second()); 86 | if (isTouchingWater(player, block, blockY + 2)) return true; 87 | } 88 | } 89 | 90 | return false; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/utils/ModifierId.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.utils; 2 | 3 | import net.kyori.adventure.key.Key; 4 | 5 | public class ModifierId { 6 | public static final Key ATTACK_DAMAGE_MODIFIER_ID = Key.key("minecraft:base_attack_damage"); 7 | public static final Key ATTACK_SPEED_MODIFIER_ID = Key.key("minecraft:base_attack_speed"); 8 | 9 | public static final Key[] ARMOR_MODIFIERS = new Key[]{ 10 | Key.key("minecraft:armor.boots"), 11 | Key.key("minecraft:armor.leggings"), 12 | Key.key("minecraft:armor.chestplate"), 13 | Key.key("minecraft:armor.helmet"), 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/utils/PotionFlags.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.utils; 2 | 3 | public class PotionFlags { 4 | public static byte create(boolean ambient, boolean particles, boolean icon) { 5 | byte flags = 0; 6 | if (ambient) { 7 | flags = (byte) (flags | 0x01); 8 | } 9 | if (particles) { 10 | flags = (byte) (flags | 0x02); 11 | } 12 | if (icon) { 13 | flags = (byte) (flags | 0x04); 14 | } 15 | return flags; 16 | } 17 | 18 | private static final byte DEFAULT_FLAGS = create(false, true, true); 19 | public static byte defaultFlags() { 20 | return DEFAULT_FLAGS; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/utils/ProjectileUtil.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.utils; 2 | 3 | import net.minestom.server.collision.BoundingBox; 4 | import net.minestom.server.collision.CollisionUtils; 5 | import net.minestom.server.collision.PhysicsResult; 6 | import net.minestom.server.coordinate.Pos; 7 | import net.minestom.server.coordinate.Vec; 8 | import net.minestom.server.instance.WorldBorder; 9 | import net.minestom.server.instance.block.Block; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | // Copied from Minestom, added singleCollision parameter and removed velocity update 14 | public class ProjectileUtil { 15 | public static @NotNull PhysicsResult simulateMovement(@NotNull Pos entityPosition, @NotNull Vec entityVelocityPerTick, 16 | @NotNull BoundingBox entityBoundingBox, @NotNull WorldBorder worldBorder, 17 | @NotNull Block.Getter blockGetter, boolean entityHasPhysics, 18 | @Nullable PhysicsResult previousPhysicsResult, 19 | boolean singleCollision) { 20 | final PhysicsResult physicsResult = entityHasPhysics ? 21 | CollisionUtils.handlePhysics(blockGetter, entityBoundingBox, entityPosition, entityVelocityPerTick, previousPhysicsResult, singleCollision) : 22 | CollisionUtils.blocklessCollision(entityPosition, entityVelocityPerTick); 23 | 24 | Pos newPosition = physicsResult.newPosition(); 25 | Vec newVelocity = physicsResult.newVelocity(); 26 | 27 | Pos positionWithinBorder = CollisionUtils.applyWorldBorder(worldBorder, entityPosition, newPosition); 28 | // Originally there was a call to update velocity here, but since projectiles handle it themselves it is not needed 29 | return new PhysicsResult(positionWithinBorder, newVelocity, physicsResult.isOnGround(), physicsResult.collisionX(), physicsResult.collisionY(), physicsResult.collisionZ(), 30 | physicsResult.originalDelta(), physicsResult.collisionPoints(), physicsResult.collisionShapes(), physicsResult.collisionShapePositions(), physicsResult.hasCollision(), physicsResult.res()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/io/github/togar2/pvp/utils/ViewUtil.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.utils; 2 | 3 | import net.kyori.adventure.audience.Audience; 4 | import net.minestom.server.adventure.audience.PacketGroupingAudience; 5 | import net.minestom.server.entity.Entity; 6 | 7 | import java.util.Collections; 8 | 9 | public class ViewUtil { 10 | public static Audience viewersAndSelf(Entity origin) { 11 | if (origin.getChunk() == null) return Audience.empty(); 12 | return origin.getChunk().getViewersAsAudience(); 13 | } 14 | 15 | // Only to be used for positioned sounds (PacketGroupingAudience has an overload for it, Audience does not) 16 | public static PacketGroupingAudience packetGroup(Entity origin) { 17 | if (origin.getChunk() == null) return PacketGroupingAudience.of(Collections.emptyList()); 18 | return ((PacketGroupingAudience) origin.getChunk().getViewersAsAudience()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/io/github/togar2/pvp/test/DemoGenerator.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.test; 2 | 3 | import net.minestom.server.coordinate.Point; 4 | import net.minestom.server.instance.block.Block; 5 | import net.minestom.server.instance.generator.GenerationUnit; 6 | import net.minestom.server.instance.generator.Generator; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import java.util.concurrent.ThreadLocalRandom; 10 | 11 | public class DemoGenerator implements Generator { 12 | @Override 13 | public void generate(@NotNull GenerationUnit unit) { 14 | Point start = unit.absoluteStart(); 15 | Point end = unit.absoluteEnd(); 16 | for (int x = start.blockX(); x < end.blockX(); x++) { 17 | for (int z = start.blockZ(); z < end.blockZ(); z++) { 18 | for (int y = 0; y < 60; y++) { 19 | if (ThreadLocalRandom.current().nextInt(10) == 1) { 20 | unit.modifier().setBlock(x, y, z, Block.GOLD_BLOCK); 21 | } else { 22 | unit.modifier().setBlock(x, y, z, Block.QUARTZ_BLOCK); 23 | } 24 | } 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/io/github/togar2/pvp/test/commands/ClearCommand.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.test.commands; 2 | 3 | import net.minestom.server.command.builder.Command; 4 | import net.minestom.server.entity.Player; 5 | 6 | public class ClearCommand extends Command { 7 | 8 | public ClearCommand() { 9 | super("clear"); 10 | 11 | setDefaultExecutor((sender, args) -> { 12 | if (sender instanceof Player player) { 13 | player.getInventory().clear(); 14 | } 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/io/github/togar2/pvp/test/commands/Commands.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.test.commands; 2 | 3 | import net.minestom.server.MinecraftServer; 4 | import net.minestom.server.command.CommandManager; 5 | 6 | public class Commands { 7 | public static void init() { 8 | CommandManager commandManager = MinecraftServer.getCommandManager(); 9 | 10 | commandManager.register(new GameModeCommand()); 11 | commandManager.register(new DamageCommand()); 12 | commandManager.register(new ClearCommand()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/io/github/togar2/pvp/test/commands/DamageCommand.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.test.commands; 2 | 3 | import net.minestom.server.command.builder.Command; 4 | import net.minestom.server.command.builder.arguments.ArgumentLiteral; 5 | import net.minestom.server.command.builder.arguments.ArgumentType; 6 | import net.minestom.server.command.builder.arguments.minecraft.ArgumentEntity; 7 | import net.minestom.server.command.builder.arguments.number.ArgumentFloat; 8 | import net.minestom.server.entity.Entity; 9 | import net.minestom.server.entity.LivingEntity; 10 | import net.minestom.server.entity.Player; 11 | import net.minestom.server.entity.damage.Damage; 12 | import net.minestom.server.entity.damage.DamageType; 13 | 14 | public class DamageCommand extends Command { 15 | 16 | public DamageCommand() { 17 | super("damage"); 18 | 19 | ArgumentLiteral nonGenArg = ArgumentType.Literal("nongen"); 20 | ArgumentEntity entityArg = ArgumentType.Entity("entity").singleEntity(true); 21 | ArgumentFloat amountArg = ArgumentType.Float("amount"); 22 | 23 | addSyntax((sender, args) -> { 24 | Entity entity = args.get(entityArg).findFirstEntity(sender); 25 | if (entity == null) { 26 | sender.sendMessage("Could not find an entity"); 27 | return; 28 | } 29 | if (!(entity instanceof LivingEntity)) { 30 | sender.sendMessage("Invalid entity"); 31 | return; 32 | } 33 | 34 | ((LivingEntity) entity).damage(DamageType.GENERIC, args.get(amountArg)); 35 | }, entityArg, amountArg); 36 | 37 | addSyntax((sender, args) -> { 38 | Entity entity = args.get(entityArg).findFirstEntity(sender); 39 | if (entity == null) { 40 | sender.sendMessage("Could not find an entity"); 41 | return; 42 | } 43 | if (!(entity instanceof LivingEntity)) { 44 | sender.sendMessage("Invalid entity"); 45 | return; 46 | } 47 | 48 | ((LivingEntity) entity).damage(new Damage( 49 | DamageType.PLAYER_ATTACK, 50 | ((Player) sender), ((Player) sender), 51 | null, args.get(amountArg) 52 | )); 53 | }, nonGenArg, entityArg, amountArg); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/io/github/togar2/pvp/test/commands/GameModeCommand.java: -------------------------------------------------------------------------------- 1 | package io.github.togar2.pvp.test.commands; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.kyori.adventure.text.format.NamedTextColor; 5 | import net.minestom.server.command.builder.Command; 6 | import net.minestom.server.command.builder.arguments.ArgumentEnum; 7 | import net.minestom.server.command.builder.arguments.ArgumentType; 8 | import net.minestom.server.command.builder.arguments.minecraft.ArgumentEntity; 9 | import net.minestom.server.entity.GameMode; 10 | import net.minestom.server.entity.Player; 11 | 12 | public class GameModeCommand extends Command { 13 | 14 | public GameModeCommand() { 15 | super("gamemode", "gm"); 16 | 17 | ArgumentEntity playerArgument = ArgumentType.Entity("player").onlyPlayers(true).singleEntity(true); 18 | ArgumentEnum mode = ArgumentType.Enum("gamemode", GameMode.class); 19 | mode.setFormat(ArgumentEnum.Format.LOWER_CASED); 20 | 21 | addSyntax((sender, args) -> { 22 | if (!(sender instanceof Player player)) return; 23 | player.setGameMode(args.get(mode)); 24 | }, mode); 25 | 26 | addSyntax((sender, args) -> { 27 | Player player = args.get(playerArgument).findFirstPlayer(sender); 28 | 29 | if (player == null) { 30 | sender.sendMessage(Component.text("That player does not exist.", NamedTextColor.RED)); 31 | } else { 32 | player.setGameMode(args.get(mode)); 33 | } 34 | }, mode, playerArgument); 35 | } 36 | } 37 | --------------------------------------------------------------------------------