├── settings.gradle.kts
├── gradle
├── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
└── libs.versions.toml
├── .gitignore
├── src
├── main
│ └── java
│ │ └── io
│ │ └── github
│ │ └── togar2
│ │ └── pvp
│ │ ├── entity
│ │ ├── projectile
│ │ │ ├── ItemHoldingProjectile.java
│ │ │ ├── ThrownEgg.java
│ │ │ ├── Snowball.java
│ │ │ ├── SpectralArrow.java
│ │ │ ├── ThrownEnderpearl.java
│ │ │ ├── Arrow.java
│ │ │ └── ThrownPotion.java
│ │ └── explosion
│ │ │ ├── CrystalEntity.java
│ │ │ └── TntEntity.java
│ │ ├── feature
│ │ ├── projectile
│ │ │ ├── BowFeature.java
│ │ │ ├── CrossbowFeature.java
│ │ │ ├── FishingRodFeature.java
│ │ │ ├── MiscProjectileFeature.java
│ │ │ ├── TridentFeature.java
│ │ │ ├── ProjectileItemFeature.java
│ │ │ ├── VanillaProjectileItemFeature.java
│ │ │ └── VanillaMiscProjectileFeature.java
│ │ ├── damage
│ │ │ └── DamageFeature.java
│ │ ├── potion
│ │ │ └── PotionFeature.java
│ │ ├── attributes
│ │ │ ├── EquipmentFeature.java
│ │ │ └── VanillaEquipmentFeature.java
│ │ ├── explosion
│ │ │ ├── ExplosiveFeature.java
│ │ │ ├── ExplosionFeature.java
│ │ │ └── VanillaExplosionFeature.java
│ │ ├── food
│ │ │ ├── RegenerationFeature.java
│ │ │ ├── ExhaustionFeature.java
│ │ │ ├── FoodFeature.java
│ │ │ ├── ChorusFruitUtil.java
│ │ │ └── VanillaRegenerationFeature.java
│ │ ├── armor
│ │ │ ├── ArmorFeature.java
│ │ │ └── VanillaArmorFeature.java
│ │ ├── CombatFeature.java
│ │ ├── provider
│ │ │ └── DifficultyProvider.java
│ │ ├── spectate
│ │ │ ├── SpectateFeature.java
│ │ │ └── VanillaSpectateFeature.java
│ │ ├── attack
│ │ │ ├── AttackFeature.java
│ │ │ ├── CriticalFeature.java
│ │ │ ├── SweepingFeature.java
│ │ │ ├── AttackValues.java
│ │ │ ├── VanillaCriticalFeature.java
│ │ │ └── VanillaSweepingFeature.java
│ │ ├── cooldown
│ │ │ ├── ItemCooldownFeature.java
│ │ │ ├── AttackCooldownFeature.java
│ │ │ ├── VanillaAttackCooldownFeature.java
│ │ │ └── VanillaItemCooldownFeature.java
│ │ ├── totem
│ │ │ ├── TotemFeature.java
│ │ │ └── VanillaTotemFeature.java
│ │ ├── state
│ │ │ ├── PlayerStateFeature.java
│ │ │ └── VanillaPlayerStateFeature.java
│ │ ├── item
│ │ │ ├── ItemDamageFeature.java
│ │ │ └── VanillaItemDamageFeature.java
│ │ ├── effect
│ │ │ ├── PotionColorUtils.java
│ │ │ └── EffectFeature.java
│ │ ├── tracking
│ │ │ ├── TrackingFeature.java
│ │ │ └── VanillaDeathMessageFeature.java
│ │ ├── block
│ │ │ ├── BlockFeature.java
│ │ │ └── LegacyBlockFeature.java
│ │ ├── fall
│ │ │ └── FallFeature.java
│ │ ├── CombatFeatureSet.java
│ │ ├── RegistrableFeature.java
│ │ ├── config
│ │ │ ├── CombatFeatureRegistry.java
│ │ │ └── DefinedFeature.java
│ │ ├── knockback
│ │ │ ├── KnockbackFeature.java
│ │ │ └── KnockbackSettings.java
│ │ └── enchantment
│ │ │ └── EnchantmentFeature.java
│ │ ├── utils
│ │ ├── ModifierId.java
│ │ ├── PotionFlags.java
│ │ ├── CombatVersion.java
│ │ ├── ViewUtil.java
│ │ ├── EffectUtil.java
│ │ ├── AccurateLatencyListener.java
│ │ ├── EntityUtil.java
│ │ ├── ProjectileUtil.java
│ │ └── FluidUtil.java
│ │ ├── potion
│ │ ├── effect
│ │ │ ├── GlowingPotionEffect.java
│ │ │ ├── HealthBoostPotionEffect.java
│ │ │ ├── AbsorptionPotionEffect.java
│ │ │ └── CombatPotionEffects.java
│ │ └── item
│ │ │ └── CombatPotionType.java
│ │ ├── enchantment
│ │ ├── enchantments
│ │ │ ├── ImpalingEnchantment.java
│ │ │ ├── ProtectionEnchantment.java
│ │ │ ├── ThornsEnchantment.java
│ │ │ └── DamageEnchantment.java
│ │ ├── EntityGroup.java
│ │ ├── CombatEnchantment.java
│ │ └── CombatEnchantments.java
│ │ ├── events
│ │ ├── PrepareAttackEvent.java
│ │ ├── AnchorChargeEvent.java
│ │ ├── TotemUseEvent.java
│ │ ├── AnchorExplodeEvent.java
│ │ ├── PlayerSpectateEvent.java
│ │ ├── CrystalPlaceEvent.java
│ │ ├── FishingBobberRetrieveEvent.java
│ │ ├── PickupEntityEvent.java
│ │ ├── PlayerExhaustEvent.java
│ │ ├── EquipmentDamageEvent.java
│ │ ├── PlayerRegenerateEvent.java
│ │ ├── ExplosivePrimeEvent.java
│ │ ├── DamageBlockEvent.java
│ │ ├── EntityPreDeathEvent.java
│ │ ├── ExplosionEvent.java
│ │ ├── PotionVisibilityEvent.java
│ │ ├── FinalDamageEvent.java
│ │ └── EntityKnockbackEvent.java
│ │ ├── damage
│ │ └── combat
│ │ │ └── CombatEntry.java
│ │ ├── enums
│ │ └── ToolMaterial.java
│ │ ├── player
│ │ └── CombatPlayer.java
│ │ └── MinestomPvP.java
└── test
│ └── java
│ └── io
│ └── github
│ └── togar2
│ └── pvp
│ └── test
│ ├── commands
│ ├── ClearCommand.java
│ ├── Commands.java
│ ├── GameModeCommand.java
│ └── DamageCommand.java
│ └── DemoGenerator.java
├── .github
└── workflows
│ ├── build.yml
│ └── publish.yml
└── gradlew.bat
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "MinestomPvP"
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TogAr2/MinestomPvP/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | minestom = "2025.10.31-1.21.10"
3 | fastutil = "8.5.12"
4 |
5 | [libraries]
6 | minestom = { group = "net.minestom", name = "minestom", version.ref = "minestom" }
7 | fastutil = { group = "it.unimi.dsi", name = "fastutil", version.ref = "fastutil" }
8 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | - pull_request
5 |
6 | jobs:
7 | build:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v4
11 |
12 | - name: Set up Java
13 | uses: actions/setup-java@v5
14 | with:
15 | distribution: temurin
16 | java-version: 25
17 |
18 | - name: Set up Gradle
19 | uses: gradle/actions/setup-gradle@v4
20 |
21 | - name: Add execute permissions for gradlew
22 | run: chmod +x gradlew
23 |
24 | - name: Build with Gradle
25 | run: ./gradlew build
26 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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/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/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 | on:
3 | push:
4 | branches:
5 | - 'master'
6 | jobs:
7 | publish:
8 | if: github.repository_owner == 'TogAr2'
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout Repository
12 | uses: actions/checkout@v5
13 | - name: Setup Java
14 | uses: actions/setup-java@v5
15 | with:
16 | distribution: graalvm
17 | java-version: 25
18 | - name: Setup Gradle
19 | uses: gradle/actions/setup-gradle@v4
20 |
21 | - name: Publish to Maven Central
22 | run: ./gradlew publishToMavenCentral
23 | env:
24 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
25 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
26 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }}
27 | ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }}
28 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/config/CombatFeatureRegistry.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.feature.config;
2 |
3 | import net.minestom.server.MinecraftServer;
4 | import net.minestom.server.entity.Player;
5 | import net.minestom.server.event.Event;
6 | import net.minestom.server.event.EventNode;
7 | import net.minestom.server.event.instance.AddEntityToInstanceEvent;
8 | import net.minestom.server.event.player.AsyncPlayerConfigurationEvent;
9 | import net.minestom.server.event.player.PlayerRespawnEvent;
10 | import net.minestom.server.event.player.PlayerSpawnEvent;
11 |
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | public class CombatFeatureRegistry {
16 | private static final EventNode initNode = EventNode.all("combat-feature-init");
17 | private static final List> features = new ArrayList<>();
18 |
19 | public static void init(DefinedFeature> feature) {
20 | if (!features.contains(feature)) {
21 | features.add(feature);
22 | if (feature.playerInit() != null) {
23 | initNode.addListener(AddEntityToInstanceEvent.class, event -> {
24 | var entity = event.getEntity();
25 | if (entity instanceof Player player)
26 | feature.playerInit().init(player, true);
27 | });
28 | initNode.addListener(PlayerSpawnEvent.class, event -> feature.playerInit().init(event.getPlayer(), false));
29 | initNode.addListener(PlayerRespawnEvent.class, event -> feature.playerInit().init(event.getPlayer(), false));
30 | }
31 | }
32 | }
33 |
34 | public static void init() {
35 | MinecraftServer.getGlobalEventHandler().addChild(initNode);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/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/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/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.RegistryKey;
12 |
13 | public class ProtectionEnchantment extends CombatEnchantment {
14 | private final Type type;
15 |
16 | public ProtectionEnchantment(RegistryKey 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/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/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/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/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/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/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/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/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/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() * 2 * Math.PI;
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/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/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/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.component.DataComponents;
6 | import net.minestom.server.entity.Entity;
7 | import net.minestom.server.entity.ItemEntity;
8 | import net.minestom.server.entity.LivingEntity;
9 | import net.minestom.server.entity.Player;
10 | import net.minestom.server.entity.damage.Damage;
11 | import net.minestom.server.item.ItemStack;
12 | import net.minestom.server.utils.time.TimeUnit;
13 |
14 | import java.lang.reflect.Field;
15 | import java.util.Objects;
16 |
17 | public class EntityUtil {
18 | public static void spawnItemAtLocation(Entity entity, ItemStack itemStack, double up) {
19 | if (itemStack.isAir()) return;
20 |
21 | ItemEntity item = new ItemEntity(itemStack);
22 | item.setPickupDelay(10, TimeUnit.SERVER_TICK); // Default 0.5 seconds
23 | item.setInstance(Objects.requireNonNull(entity.getInstance()), entity.getPosition().add(0, up, 0));
24 | }
25 |
26 | public static Component getName(Entity entity) {
27 | HoverEvent hoverEvent = HoverEvent.showEntity(entity.getEntityType().key(), entity.getUuid());
28 | var customName = entity.get(DataComponents.CUSTOM_NAME);
29 | if (customName != null) {
30 | return customName.hoverEvent(hoverEvent);
31 | } else if (entity instanceof Player) {
32 | return ((Player) entity).getName().hoverEvent(hoverEvent);
33 | } else {
34 | // Use entity type without underscores and starting with capital letter
35 | String name = entity.getEntityType().key().value().replace('_', ' ');
36 | name = name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase();
37 | return Component.text(name).hoverEvent(hoverEvent);
38 | }
39 | }
40 |
41 | public static void setLastDamage(LivingEntity livingEntity, Damage lastDamage) {
42 | // Use reflection to set lastDamage field
43 | try {
44 | Field field = LivingEntity.class.getDeclaredField("lastDamage");
45 | field.setAccessible(true);
46 | field.set(livingEntity, lastDamage);
47 | } catch (NoSuchFieldException | IllegalAccessException e) {
48 | e.printStackTrace();
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/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/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/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/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/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.RegistryKey;
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(RegistryKey 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/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.component.DataComponents;
10 | import net.minestom.server.entity.LivingEntity;
11 | import net.minestom.server.entity.PlayerHand;
12 | import net.minestom.server.entity.damage.DamageType;
13 | import net.minestom.server.event.EventDispatcher;
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(DataComponents.DEATH_PROTECTION)) {
37 | TotemUseEvent totemUseEvent = new TotemUseEvent(entity, hand);
38 | EventDispatcher.call(totemUseEvent);
39 |
40 | if (totemUseEvent.isCancelled()) continue;
41 |
42 | deathProtection = stack.get(DataComponents.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/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.RegistryKey;
13 |
14 | import java.util.HashMap;
15 | import java.util.Map;
16 | import java.util.Set;
17 |
18 | public class CombatEnchantment {
19 | private final RegistryKey enchantment;
20 | private final EquipmentSlot[] slotTypes;
21 |
22 | private final Set> dependencies;
23 |
24 | public CombatEnchantment(RegistryKey enchantment, EquipmentSlot... slotTypes) {
25 | this(enchantment, Set.of(), slotTypes);
26 | }
27 |
28 | public CombatEnchantment(RegistryKey enchantment,
29 | Set> dependencies, EquipmentSlot... slotTypes) {
30 | this.enchantment = enchantment;
31 | this.dependencies = dependencies;
32 | this.slotTypes = slotTypes;
33 | }
34 |
35 | public RegistryKey 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/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/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/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/feature/config/DefinedFeature.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.feature.config;
2 |
3 | import io.github.togar2.pvp.feature.CombatFeature;
4 | import io.github.togar2.pvp.feature.FeatureType;
5 | import net.minestom.server.entity.Player;
6 | import org.jetbrains.annotations.Nullable;
7 |
8 | import java.util.Set;
9 |
10 | /**
11 | * Represents a feature implementation which has been defined but not yet instantiated.
12 | * It contains the feature type, all the feature types that this feature depends on and its constructor.
13 | * The constructor always takes a {@link FeatureConfiguration} which the feature should store,
14 | * and which it can get the dependencies from once {@link CombatFeature#initDependencies()} is called.
15 | *
16 | * A defined feature can also contain a {@link PlayerInit},
17 | * which describes everything that should happen when a players state is reset (they join, they respawn, etc.).
18 | * This can for example be used to add certain tags to a player that a feature needs.
19 | * Since features might be instantiated multiple times, this player init is contained in the feature definition
20 | * and not every time when it is instantiated.
21 | *
22 | * @param the feature class
23 | */
24 | public class DefinedFeature {
25 | private final FeatureType> featureType;
26 | private final Set> dependencies;
27 | private final Constructor constructor;
28 | private final PlayerInit playerInit;
29 |
30 | public DefinedFeature(FeatureType super F> featureType, Constructor constructor,
31 | FeatureType>... dependencies) {
32 | this(featureType, constructor, null, dependencies);
33 | }
34 |
35 | public DefinedFeature(FeatureType super F> featureType, Constructor constructor,
36 | @Nullable PlayerInit playerInit, FeatureType>... dependencies) {
37 | this.featureType = featureType;
38 | this.dependencies = Set.of(dependencies);
39 | this.constructor = constructor;
40 | this.playerInit = playerInit;
41 | }
42 |
43 | public F construct(FeatureConfiguration configuration) {
44 | // Registers player init
45 | CombatFeatureRegistry.init(this);
46 | return constructor.construct(configuration);
47 | }
48 |
49 | public FeatureType> featureType() {
50 | return featureType;
51 | }
52 |
53 | public Set> dependencies() {
54 | return dependencies;
55 | }
56 |
57 | @Nullable PlayerInit playerInit() {
58 | return playerInit;
59 | }
60 |
61 | @FunctionalInterface
62 | public interface Constructor {
63 | F construct(FeatureConfiguration configuration);
64 | }
65 |
66 | @FunctionalInterface
67 | public interface PlayerInit {
68 | void init(Player player, boolean firstInit);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/java/io/github/togar2/pvp/feature/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.kyori.adventure.key.Key;
7 | import net.minestom.server.MinecraftServer;
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.EventNode;
12 | import net.minestom.server.event.player.PlayerMoveEvent;
13 | import net.minestom.server.event.player.PlayerTickEvent;
14 | import net.minestom.server.event.trait.EntityInstanceEvent;
15 | import net.minestom.server.instance.block.Block;
16 | import net.minestom.server.tag.Tag;
17 | import org.jetbrains.annotations.Nullable;
18 |
19 | import java.util.Objects;
20 |
21 | /**
22 | * Vanilla implementation of {@link PlayerStateFeature}
23 | */
24 | public class VanillaPlayerStateFeature implements PlayerStateFeature, RegistrableFeature {
25 | public static final DefinedFeature DEFINED = new DefinedFeature<>(
26 | FeatureType.PLAYER_STATE, configuration -> new VanillaPlayerStateFeature()
27 | );
28 |
29 | public static final Tag LAST_CLIMBED_BLOCK = Tag.Transient("lastClimbedBlock");
30 |
31 | @Override
32 | public void init(EventNode node) {
33 | node.addListener(PlayerTickEvent.class, event -> {
34 | Player player = event.getPlayer();
35 | if (player.isOnGround() && player.hasTag(LAST_CLIMBED_BLOCK)) {
36 | // Make sure fall damage message still has the correct climbed block
37 | // Due to multithreading this can be triggered before the death message is computed
38 | player.scheduleNextTick(p -> p.removeTag(LAST_CLIMBED_BLOCK));
39 | }
40 | });
41 |
42 | node.addListener(PlayerMoveEvent.class, event -> {
43 | Player player = event.getPlayer();
44 | if (isClimbing(player)) {
45 | player.setTag(LAST_CLIMBED_BLOCK, player.getInstance().getBlock(player.getPosition()));
46 | }
47 | });
48 | }
49 |
50 | @Override
51 | public boolean isClimbing(LivingEntity entity) {
52 | if (entity instanceof Player player && player.getGameMode() == GameMode.SPECTATOR) return false;
53 |
54 | var tag = MinecraftServer.process().blocks().getTag(Key.key("minecraft:climbable"));
55 | assert tag != null;
56 |
57 | Block block = Objects.requireNonNull(entity.getInstance()).getBlock(entity.getPosition());
58 | var key = block.asKey();
59 | assert key != null;
60 | return tag.contains(key);
61 | }
62 |
63 | @Override
64 | public @Nullable Block getLastClimbedBlock(LivingEntity entity) {
65 | return entity.getTag(LAST_CLIMBED_BLOCK);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/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/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/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/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/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/utils/FluidUtil.java:
--------------------------------------------------------------------------------
1 | package io.github.togar2.pvp.utils;
2 |
3 | import net.minestom.server.coordinate.Pos;
4 | import net.minestom.server.entity.Player;
5 | import net.minestom.server.instance.Instance;
6 | import net.minestom.server.instance.block.Block;
7 |
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | public class FluidUtil {
12 | public static int getLevel(Block block) {
13 | String levelStr = block.getProperty("level");
14 | if (levelStr == null) return 8;
15 | int level = Integer.parseInt(levelStr);
16 | if (level >= 8) return 8; // Falling water
17 | return 8 - level;
18 | }
19 |
20 | public static double getHeight(Block block) {
21 | int level = getLevel(block);
22 | return switch (level) {
23 | case 1 -> 0.25;
24 | case 2 -> 0.375;
25 | case 3 -> 0.5;
26 | case 4 -> 0.625;
27 | case 5 -> 0.75;
28 | default -> 1;
29 | };
30 | }
31 |
32 | public static boolean isTouchingWater(Player player, Block block, int blockY) {
33 | if (!block.compare(Block.WATER)) return false;
34 | if (player.getPosition().y() + player.getBoundingBox().height() < blockY) return false;
35 | if (player.getPosition().y() > (blockY + getHeight(block))) return false;
36 | return true;
37 | }
38 |
39 | record PairXZ(int x, int z) {}
40 |
41 | public static boolean isTouchingWater(Player player) {
42 | Pos position = player.getPosition();
43 | double x = position.x();
44 | int blockX = position.blockX();
45 | double z = position.z();
46 | int blockZ = position.blockZ();
47 | double y = position.y();
48 | int blockY = position.blockY();
49 |
50 | List points = new ArrayList<>();
51 | points.add(new PairXZ(blockX, blockZ));
52 |
53 | if (x - blockX > 0.7) {
54 | if (z - blockZ > 0.7) {
55 | points.add(new PairXZ(blockX + 1, blockZ + 1));
56 | }
57 | points.add(new PairXZ(blockX + 1, blockZ));
58 | } else if (x - blockX < 0.2) {
59 | if (z - blockZ < 0.2) {
60 | points.add(new PairXZ(blockX - 1, blockZ - 1));
61 | }
62 | points.add(new PairXZ(blockX - 1, blockZ));
63 | }
64 | if (z - blockZ > 0.7) {
65 | if (x - blockX < 0.2) {
66 | points.add(new PairXZ(blockX - 1, blockZ + 1));
67 | }
68 | points.add(new PairXZ(blockX, blockZ + 1));
69 | } else if (z - blockZ < 0.2) {
70 | if (x - blockX > 0.7) {
71 | points.add(new PairXZ(blockX + 1, blockZ - 1));
72 | }
73 | points.add(new PairXZ(blockX, blockZ - 1));
74 | }
75 |
76 | Instance instance = player.getInstance();
77 | assert instance != null;
78 |
79 | for (PairXZ pair : points) {
80 | Block block = instance.getBlock(pair.x(), blockY, pair.z());
81 | if (isTouchingWater(player, block, blockY)) return true;
82 | block = instance.getBlock(pair.x(), blockY + 1, pair.z());
83 | if (isTouchingWater(player, block, blockY + 1)) return true;
84 |
85 | if (y - blockY >= 2 - player.getBoundingBox().height()) {
86 | block = instance.getBlock(pair.x(), blockY + 2, pair.z());
87 | if (isTouchingWater(player, block, blockY + 2)) return true;
88 | }
89 | }
90 |
91 | return false;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 | set CLASSPATH=
74 |
75 |
76 | @rem Execute Gradle
77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
78 |
79 | :end
80 | @rem End local scope for the variables with windows NT shell
81 | if %ERRORLEVEL% equ 0 goto mainEnd
82 |
83 | :fail
84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
85 | rem the _cmd.exe /c_ return code!
86 | set EXIT_CODE=%ERRORLEVEL%
87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
89 | exit /b %EXIT_CODE%
90 |
91 | :mainEnd
92 | if "%OS%"=="Windows_NT" endlocal
93 |
94 | :omega
95 |
--------------------------------------------------------------------------------
/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