From 6b77627d60c6b82045506749c40c671658075e23 Mon Sep 17 00:00:00 2001 From: jglrxavpok Date: Mon, 27 Apr 2020 20:33:08 +0200 Subject: [PATCH 1/2] Implemented damage types and void below world --- src/main/java/fr/themode/demo/Main.java | 2 + src/main/java/fr/themode/demo/PlayerInit.java | 5 +- .../demo/commands/GamemodeCommand.java | 73 +++++++++++++++++++ .../themode/demo/commands/HealthCommand.java | 2 +- .../net/minestom/server/entity/Entity.java | 19 ++++- .../net/minestom/server/entity/GameMode.java | 10 ++- .../minestom/server/entity/LivingEntity.java | 23 +++++- .../net/minestom/server/entity/Player.java | 26 +++++-- .../server/entity/damage/DamageType.java | 19 +++++ .../net/minestom/server/instance/Chunk.java | 21 +++++- .../minestom/server/instance/Instance.java | 8 ++ .../server/instance/InstanceContainer.java | 7 ++ .../server/instance/SharedInstance.java | 6 ++ 13 files changed, 205 insertions(+), 16 deletions(-) create mode 100644 src/main/java/fr/themode/demo/commands/GamemodeCommand.java create mode 100644 src/main/java/net/minestom/server/entity/damage/DamageType.java diff --git a/src/main/java/fr/themode/demo/Main.java b/src/main/java/fr/themode/demo/Main.java index c8a9197d9..89fcebc7b 100644 --- a/src/main/java/fr/themode/demo/Main.java +++ b/src/main/java/fr/themode/demo/Main.java @@ -2,6 +2,7 @@ package fr.themode.demo; import fr.themode.demo.blocks.StoneBlock; import fr.themode.demo.blocks.UpdatableBlockDemo; +import fr.themode.demo.commands.GamemodeCommand; import fr.themode.demo.commands.HealthCommand; import fr.themode.demo.commands.SimpleCommand; import net.minestom.server.MinecraftServer; @@ -32,6 +33,7 @@ public class Main { CommandManager commandManager = MinecraftServer.getCommandManager(); commandManager.register(new HealthCommand()); commandManager.register(new SimpleCommand()); + commandManager.register(new GamemodeCommand()); RecipeManager recipeManager = MinecraftServer.getRecipeManager(); ShapelessRecipe shapelessRecipe = new ShapelessRecipe("test", "groupname") { diff --git a/src/main/java/fr/themode/demo/PlayerInit.java b/src/main/java/fr/themode/demo/PlayerInit.java index 85ad99661..3413862b4 100644 --- a/src/main/java/fr/themode/demo/PlayerInit.java +++ b/src/main/java/fr/themode/demo/PlayerInit.java @@ -7,6 +7,7 @@ import net.minestom.server.MinecraftServer; import net.minestom.server.benchmark.BenchmarkManager; import net.minestom.server.benchmark.ThreadResult; import net.minestom.server.entity.*; +import net.minestom.server.entity.damage.DamageType; import net.minestom.server.event.*; import net.minestom.server.instance.InstanceContainer; import net.minestom.server.inventory.Inventory; @@ -97,7 +98,7 @@ public class PlayerInit { Entity entity = event.getTarget(); if (entity instanceof EntityCreature) { EntityCreature creature = (EntityCreature) entity; - creature.damage(-1); + creature.damage(DamageType.fromPlayer(player), -1); Vector velocity = player.getPosition().clone().getDirection().multiply(6); velocity.setY(4f); entity.setVelocity(velocity, 150); @@ -107,7 +108,7 @@ public class PlayerInit { Vector velocity = player.getPosition().clone().getDirection().multiply(4); velocity.setY(3.5f); target.setVelocity(velocity, 150); - target.damage(5); + target.damage(DamageType.fromPlayer(player), 5); player.sendMessage("ATTACK"); } }); diff --git a/src/main/java/fr/themode/demo/commands/GamemodeCommand.java b/src/main/java/fr/themode/demo/commands/GamemodeCommand.java new file mode 100644 index 000000000..8955947b8 --- /dev/null +++ b/src/main/java/fr/themode/demo/commands/GamemodeCommand.java @@ -0,0 +1,73 @@ +package fr.themode.demo.commands; + +import fr.themode.command.Arguments; +import fr.themode.command.Command; +import fr.themode.command.arguments.Argument; +import fr.themode.command.arguments.ArgumentType; +import net.minestom.server.entity.GameMode; +import net.minestom.server.entity.Player; + +import java.util.Optional; + +/** + * Command that make a player change gamemode + */ +public class GamemodeCommand extends Command { + public GamemodeCommand() { + super("gamemode", "g", "gm"); + + setCondition(this::isAllowed); + + setDefaultExecutor(this::usage); + + Argument player = ArgumentType.String("player"); + + GameMode[] gameModes = GameMode.values(); + String[] names = new String[gameModes.length]; + for (int i = 0; i < gameModes.length; i++) { + names[i] = gameModes[i].name().toLowerCase(); + } + Argument mode = ArgumentType.Word("mode").from(names); + + addCallback(this::gameModeCallback, mode); + + addSyntax(this::executeOnSelf, mode); + addSyntax(this::executeOnOther, player, mode); + } + + private void usage(Player player, Arguments arguments) { + player.sendMessage("Usage: /gamemode [player] "); + } + + private void executeOnSelf(Player player, Arguments arguments) { + String gamemodeName = arguments.getWord("mode"); + GameMode mode = GameMode.valueOf(gamemodeName.toUpperCase()); + assert mode != null; // mode is not supposed to be null, because gamemodeName will be valid + player.setGameMode(mode); + player.sendMessage("You are now playing in "+gamemodeName); + + System.out.println("hello"); + } + + private void executeOnOther(Player player, Arguments arguments) { + String gamemodeName = arguments.getWord("mode"); + String targetName = arguments.getString("player"); + GameMode mode = GameMode.valueOf(gamemodeName.toUpperCase()); + assert mode != null; // mode is not supposed to be null, because gamemodeName will be valid + Optional target = player.getInstance().getPlayers().stream().filter(p -> p.getUsername().equals(targetName)).findFirst(); + if(target.isPresent()) { + target.get().setGameMode(mode); + target.get().sendMessage("You are now playing in "+gamemodeName); + } else { + player.sendMessage("'"+targetName+"' is not a valid player name."); + } + } + + private void gameModeCallback(Player player, String gamemode, int error) { + player.sendMessage("'"+gamemode+"' is not a valid gamemode!"); + } + + private boolean isAllowed(Player player) { + return true; // TODO: permissions + } +} diff --git a/src/main/java/fr/themode/demo/commands/HealthCommand.java b/src/main/java/fr/themode/demo/commands/HealthCommand.java index 2a5504fa5..52840ef72 100644 --- a/src/main/java/fr/themode/demo/commands/HealthCommand.java +++ b/src/main/java/fr/themode/demo/commands/HealthCommand.java @@ -28,7 +28,7 @@ public class HealthCommand extends Command { } private boolean condition(Player player) { - // Your custom condition, called no matter the syntax used + // TODO: Your custom condition, called no matter the syntax used boolean hasPerm = true; if (!hasPerm) { player.sendMessage("You do not have permission !"); diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index a5acaf697..dd5caddbe 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -245,6 +245,7 @@ public abstract class Entity implements Viewable, DataContainer { if (!isOnGround()) { float strength = gravityDragPerTick * gravityTickCounter; + boolean foundBlock = false; int firstBlock = 0; for (int y = (int) position.getY(); y > 0; y--) { BlockPosition blockPosition = new BlockPosition(position.getX(), y, position.getZ()); @@ -253,6 +254,7 @@ public abstract class Entity implements Viewable, DataContainer { // System.out.println("id: " + blockId); if (blockId != 0) { firstBlock = y; + foundBlock = true; //System.out.println("first block: " + y); break; } @@ -260,7 +262,10 @@ public abstract class Entity implements Viewable, DataContainer { float newY = position.getY() - strength; //System.out.println("REFRESH Y " + newY); - newY = Math.max(newY, firstBlock); + // allow entities to go below the world + if(foundBlock) { + newY = Math.max(newY, firstBlock); + } refreshPosition(position.getX(), newY, position.getZ()); gravityTickCounter++; if (isOnGround()) { // Round Y axis when gravity movement is done @@ -276,6 +281,8 @@ public abstract class Entity implements Viewable, DataContainer { } } + handleVoid(); + // Call the abstract update method update(); @@ -291,6 +298,16 @@ public abstract class Entity implements Viewable, DataContainer { } } + /** + * How does this entity handle being in the void? + */ + protected void handleVoid() { + // Kill if in void + if(getInstance().isInVoid(this.position)) { + remove(); + } + } + public void setEventCallback(Class eventClass, Callback callback) { this.eventCallbacks.put(eventClass, callback); } diff --git a/src/main/java/net/minestom/server/entity/GameMode.java b/src/main/java/net/minestom/server/entity/GameMode.java index 04878efc5..709da33cd 100644 --- a/src/main/java/net/minestom/server/entity/GameMode.java +++ b/src/main/java/net/minestom/server/entity/GameMode.java @@ -2,13 +2,15 @@ package net.minestom.server.entity; public enum GameMode { - SURVIVAL((byte) 0), CREATIVE((byte) 1), ADVENTURE((byte) 2), SPECTATOR((byte) 3); + SURVIVAL((byte) 0, true), CREATIVE((byte) 1, false), ADVENTURE((byte) 2, true), SPECTATOR((byte) 3, false); private byte id; private boolean hardcore; + private boolean canTakeDamage; - GameMode(byte id) { + GameMode(byte id, boolean canTakeDamage) { this.id = id; + this.canTakeDamage = canTakeDamage; } public void setHardcore(boolean hardcore) { @@ -22,4 +24,8 @@ public enum GameMode { public boolean isHardcore() { return hardcore; } + + public boolean canTakeDamage() { + return canTakeDamage; + } } diff --git a/src/main/java/net/minestom/server/entity/LivingEntity.java b/src/main/java/net/minestom/server/entity/LivingEntity.java index a1a569eda..0cb52eb10 100644 --- a/src/main/java/net/minestom/server/entity/LivingEntity.java +++ b/src/main/java/net/minestom/server/entity/LivingEntity.java @@ -1,6 +1,7 @@ package net.minestom.server.entity; import net.minestom.server.collision.BoundingBox; +import net.minestom.server.entity.damage.DamageType; import net.minestom.server.entity.property.Attribute; import net.minestom.server.event.DeathEvent; import net.minestom.server.event.PickupItemEvent; @@ -103,7 +104,10 @@ public abstract class LivingEntity extends Entity { callEvent(DeathEvent.class, deathEvent); } - public void damage(float value) { + public void damage(DamageType type, float value) { + if(isImmune(type)) { + return; + } EntityAnimationPacket entityAnimationPacket = new EntityAnimationPacket(); entityAnimationPacket.entityId = getEntityId(); entityAnimationPacket.animation = EntityAnimationPacket.Animation.TAKE_DAMAGE; @@ -111,6 +115,15 @@ public abstract class LivingEntity extends Entity { setHealth(getHealth() - value); } + /** + * Is this entity immune to the given type of damage? + * @param type the type of damage + * @return true iff this entity is immune to the given type of damage + */ + public boolean isImmune(DamageType type) { + return false; + } + public float getHealth() { return health; } @@ -195,4 +208,12 @@ public abstract class LivingEntity extends Entity { setAttribute(attribute, attribute.getDefaultValue()); } } + + @Override + protected void handleVoid() { + // Kill if in void + if(getInstance().isInVoid(this.position)) { + damage(DamageType.VOID, 10f); + } + } } diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index faa19000d..c7d92d16c 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -6,6 +6,7 @@ import net.minestom.server.MinecraftServer; import net.minestom.server.bossbar.BossBar; import net.minestom.server.chat.Chat; import net.minestom.server.collision.BoundingBox; +import net.minestom.server.entity.damage.DamageType; import net.minestom.server.entity.property.Attribute; import net.minestom.server.entity.vehicle.PlayerVehicleInformation; import net.minestom.server.event.*; @@ -406,11 +407,11 @@ public class Player extends LivingEntity { } @Override - public void damage(float value) { - if (getGameMode() == GameMode.CREATIVE) - return; - - super.damage(value); + public boolean isImmune(DamageType type) { + if(getGameMode().canTakeDamage()) { + return type != DamageType.VOID; + } + return super.isImmune(type); } @Override @@ -450,6 +451,11 @@ public class Player extends LivingEntity { return !itemDropEvent.isCancelled(); } + public Position getRespawnPoint() { + // TODO: Custom + return new Position(0f, 70f, 0f); + } + public void respawn() { if (!isDead()) return; @@ -460,7 +466,7 @@ public class Player extends LivingEntity { respawnPacket.gameMode = getGameMode(); respawnPacket.levelType = getLevelType(); getPlayerConnection().sendPacket(respawnPacket); - PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(getPosition()); + PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(getRespawnPoint()); callEvent(PlayerRespawnEvent.class, respawnEvent); refreshIsDead(false); @@ -604,6 +610,14 @@ public class Player extends LivingEntity { return gameMode; } + /** + * Returns true iff this player is in creative. Used for code readability + * @return + */ + public boolean isCreative() { + return gameMode == GameMode.CREATIVE; + } + public void setDimension(Dimension dimension) { if (dimension == null) throw new IllegalArgumentException("Dimension cannot be null!"); diff --git a/src/main/java/net/minestom/server/entity/damage/DamageType.java b/src/main/java/net/minestom/server/entity/damage/DamageType.java new file mode 100644 index 000000000..645346282 --- /dev/null +++ b/src/main/java/net/minestom/server/entity/damage/DamageType.java @@ -0,0 +1,19 @@ +package net.minestom.server.entity.damage; + +import net.minestom.server.entity.Player; + +/** + * Represents a type of damage + */ +public class DamageType { + + // TODO + + public static final DamageType VOID = new DamageType(); + public static final DamageType PLAYER = new DamageType(); + + public static DamageType fromPlayer(Player player) { + // TODO + return PLAYER; + } +} diff --git a/src/main/java/net/minestom/server/instance/Chunk.java b/src/main/java/net/minestom/server/instance/Chunk.java index 24114c8a6..d402ca3a0 100644 --- a/src/main/java/net/minestom/server/instance/Chunk.java +++ b/src/main/java/net/minestom/server/instance/Chunk.java @@ -140,17 +140,29 @@ public class Chunk implements Viewable { } public short getBlockId(int x, int y, int z) { - short id = blocksId[getBlockIndex(x, y, z)]; + int index = getBlockIndex(x, y, z); + if(index < 0 || index >= blocksId.length) { + return 0; // TODO: custom invalid block + } + short id = blocksId[index]; return id; } public short getCustomBlockId(int x, int y, int z) { - short id = customBlocksId[getBlockIndex(x, y, z)]; + int index = getBlockIndex(x, y, z); + if(index < 0 || index >= blocksId.length) { + return 0; // TODO: custom invalid block + } + short id = customBlocksId[index]; return id; } public CustomBlock getCustomBlock(int x, int y, int z) { - short id = customBlocksId[getBlockIndex(x, y, z)]; + int index = getBlockIndex(x, y, z); + if(index < 0 || index >= blocksId.length) { + return null; // TODO: custom invalid block + } + short id = customBlocksId[index]; return id != 0 ? BLOCK_MANAGER.getCustomBlock(id) : null; } @@ -161,6 +173,9 @@ public class Chunk implements Viewable { protected void refreshBlockValue(int x, int y, int z, short blockId, short customId) { int blockIndex = getBlockIndex(x, y, z); + if(blockIndex < 0 || blockIndex >= blocksId.length) { + return; + } this.blocksId[blockIndex] = blockId; this.customBlocksId[blockIndex] = customId; diff --git a/src/main/java/net/minestom/server/instance/Instance.java b/src/main/java/net/minestom/server/instance/Instance.java index 4b7d72550..b05256bc2 100644 --- a/src/main/java/net/minestom/server/instance/Instance.java +++ b/src/main/java/net/minestom/server/instance/Instance.java @@ -88,6 +88,14 @@ public abstract class Instance implements BlockModifier, DataContainer { public abstract boolean hasEnabledAutoChunkLoad(); + /** + * Determines whether a position in the void. If true, entities should take damage and die. + * Always returning false allow entities to survive in the void + * @param position the position in the world + * @return true iif position is inside the void + */ + public abstract boolean isInVoid(Position position); + // protected void sendChunkUpdate(Collection players, Chunk chunk) { ByteBuf chunkData = chunk.getFullDataPacket(); diff --git a/src/main/java/net/minestom/server/instance/InstanceContainer.java b/src/main/java/net/minestom/server/instance/InstanceContainer.java index 646f1fb79..99b53ce6e 100644 --- a/src/main/java/net/minestom/server/instance/InstanceContainer.java +++ b/src/main/java/net/minestom/server/instance/InstanceContainer.java @@ -16,6 +16,7 @@ import net.minestom.server.particle.Particle; import net.minestom.server.particle.ParticleCreator; import net.minestom.server.utils.BlockPosition; import net.minestom.server.utils.ChunkUtils; +import net.minestom.server.utils.Position; import net.minestom.server.utils.SerializerUtils; import java.io.File; @@ -330,6 +331,12 @@ public class InstanceContainer extends Instance { return autoChunkLoad; } + @Override + public boolean isInVoid(Position position) { + // TODO: customizable + return position.getY() < -64; + } + protected void addSharedInstance(SharedInstance sharedInstance) { this.sharedInstances.add(sharedInstance); } diff --git a/src/main/java/net/minestom/server/instance/SharedInstance.java b/src/main/java/net/minestom/server/instance/SharedInstance.java index 1b3eb4acf..ea8156d4e 100644 --- a/src/main/java/net/minestom/server/instance/SharedInstance.java +++ b/src/main/java/net/minestom/server/instance/SharedInstance.java @@ -5,6 +5,7 @@ import net.minestom.server.entity.Player; import net.minestom.server.instance.batch.BlockBatch; import net.minestom.server.instance.batch.ChunkBatch; import net.minestom.server.utils.BlockPosition; +import net.minestom.server.utils.Position; import java.io.File; import java.util.Collection; @@ -133,6 +134,11 @@ public class SharedInstance extends Instance { return instanceContainer.hasEnabledAutoChunkLoad(); } + @Override + public boolean isInVoid(Position position) { + return instanceContainer.isInVoid(position); + } + @Override public void setBlock(int x, int y, int z, short blockId, Data data) { instanceContainer.setBlock(x, y, z, blockId, data); From 7e3bc22bc4055e48689f76107e12a578077f076d Mon Sep 17 00:00:00 2001 From: jglrxavpok Date: Mon, 27 Apr 2020 21:12:42 +0200 Subject: [PATCH 2/2] Chat messages when player dies and cleaner damage types --- .../net/minestom/server/entity/Player.java | 41 ++++++++++ .../server/entity/damage/DamageType.java | 31 +++++-- .../server/entity/damage/EntityDamage.java | 20 +++++ .../entity/damage/EntityProjectileDamage.java | 26 ++++++ .../packet/server/ServerPacketIdentifier.java | 2 +- .../packet/server/play/CombatEventPacket.java | 80 +++++++++++++++++++ 6 files changed, 194 insertions(+), 6 deletions(-) create mode 100644 src/main/java/net/minestom/server/entity/damage/EntityDamage.java create mode 100644 src/main/java/net/minestom/server/entity/damage/EntityProjectileDamage.java create mode 100644 src/main/java/net/minestom/server/network/packet/server/play/CombatEventPacket.java diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index c7d92d16c..d6ad73afb 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -1,5 +1,6 @@ package net.minestom.server.entity; +import club.thectm.minecraft.text.TextBuilder; import club.thectm.minecraft.text.TextObject; import com.google.gson.JsonObject; import net.minestom.server.MinecraftServer; @@ -76,6 +77,11 @@ public class Player extends LivingEntity { private Team team; private BelowNameScoreboard belowNameScoreboard; + /** + * Last damage source to hit this player, used to display the death message. + */ + private DamageType lastDamageSource; + // Abilities private boolean invulnerable; private boolean flying; @@ -109,6 +115,14 @@ public class Player extends LivingEntity { setCanPickupItem(true); // By default } + @Override + public void damage(DamageType type, float value) { + if(!isImmune(type)) { + lastDamageSource = type; + } + super.damage(type, value); + } + @Override public void update() { @@ -229,6 +243,33 @@ public class Player extends LivingEntity { } + @Override + public void kill() { + if(!isDead()) { + // send death message to player + TextObject deathMessage; + if(lastDamageSource != null) { + deathMessage = lastDamageSource.buildDeathMessage(); + } else { // may happen if killed by the server without applying damage + deathMessage = TextBuilder.of("Killed by poor programming.").build(); + } + CombatEventPacket deathPacket = CombatEventPacket.death(this, Optional.empty(), deathMessage); + playerConnection.sendPacket(deathPacket); + + // send death message to chat + TextObject chatMessage; + if(lastDamageSource != null) { + chatMessage = lastDamageSource.buildChatMessage(this); + } else { // may happen if killed by the server without applying damage + chatMessage = TextBuilder.of(getUsername()+" was killed by poor programming.").build(); + } + MinecraftServer.getConnectionManager().getOnlinePlayers().forEach(player -> { + player.sendMessage(chatMessage); + }); + } + super.kill(); + } + @Override public void spawn() { diff --git a/src/main/java/net/minestom/server/entity/damage/DamageType.java b/src/main/java/net/minestom/server/entity/damage/DamageType.java index 645346282..9b0342600 100644 --- a/src/main/java/net/minestom/server/entity/damage/DamageType.java +++ b/src/main/java/net/minestom/server/entity/damage/DamageType.java @@ -1,5 +1,8 @@ package net.minestom.server.entity.damage; +import club.thectm.minecraft.text.TextBuilder; +import club.thectm.minecraft.text.TextObject; +import net.minestom.server.entity.Entity; import net.minestom.server.entity.Player; /** @@ -7,13 +10,31 @@ import net.minestom.server.entity.Player; */ public class DamageType { - // TODO + public static final DamageType VOID = new DamageType("void"); + public static final DamageType GRAVITY = new DamageType("gravity"); + private final String identifier; - public static final DamageType VOID = new DamageType(); - public static final DamageType PLAYER = new DamageType(); + public DamageType(String identifier) { + this.identifier = identifier; + } + + public String getIdentifier() { + return identifier; + } + + public TextObject buildChatMessage(Player killed) { + return TextBuilder.of(killed.getUsername()+" was killed by damage of type "+identifier).build(); + } + + public TextObject buildDeathMessage() { + return TextBuilder.of("Killed by damage of type "+identifier).build(); + } public static DamageType fromPlayer(Player player) { - // TODO - return PLAYER; + return new EntityDamage(player); + } + + public static DamageType fromProjectile(Entity shooter, Entity projectile) { + return new EntityProjectileDamage(shooter, projectile); } } diff --git a/src/main/java/net/minestom/server/entity/damage/EntityDamage.java b/src/main/java/net/minestom/server/entity/damage/EntityDamage.java new file mode 100644 index 000000000..76e033630 --- /dev/null +++ b/src/main/java/net/minestom/server/entity/damage/EntityDamage.java @@ -0,0 +1,20 @@ +package net.minestom.server.entity.damage; + +import net.minestom.server.entity.Entity; + +/** + * Represents damage inflicted by an entity + */ +public class EntityDamage extends DamageType { + + private final Entity source; + + public EntityDamage(Entity source) { + super("entity_source"); + this.source = source; + } + + public Entity getSource() { + return source; + } +} diff --git a/src/main/java/net/minestom/server/entity/damage/EntityProjectileDamage.java b/src/main/java/net/minestom/server/entity/damage/EntityProjectileDamage.java new file mode 100644 index 000000000..1d9c2adb7 --- /dev/null +++ b/src/main/java/net/minestom/server/entity/damage/EntityProjectileDamage.java @@ -0,0 +1,26 @@ +package net.minestom.server.entity.damage; + +import net.minestom.server.entity.Entity; + +/** + * Represents damage inflicted by an entity, via a projectile + */ +public class EntityProjectileDamage extends DamageType { + + private Entity shooter; + private final Entity projectile; + + public EntityProjectileDamage(Entity shooter, Entity projectile) { + super("projectile_source"); + this.shooter = shooter; + this.projectile = projectile; + } + + public Entity getProjectile() { + return projectile; + } + + public Entity getShooter() { + return shooter; + } +} diff --git a/src/main/java/net/minestom/server/network/packet/server/ServerPacketIdentifier.java b/src/main/java/net/minestom/server/network/packet/server/ServerPacketIdentifier.java index cd954437b..5861115cf 100644 --- a/src/main/java/net/minestom/server/network/packet/server/ServerPacketIdentifier.java +++ b/src/main/java/net/minestom/server/network/packet/server/ServerPacketIdentifier.java @@ -53,7 +53,7 @@ public class ServerPacketIdentifier { public static final int OPEN_SIGN_EDITOR = 0x30; public static final int CRAFT_RECIPE_RESPONSE = 0x31; public static final int PLAYER_ABILITIES = 0x32; - public static final int COMBAT_EVENT = 0x33; // Do not seem to be used by the client + public static final int COMBAT_EVENT = 0x33; public static final int PLAYER_INFO = 0x34; public static final int FACE_PLAYER = 0x35; public static final int PLAYER_POSITION_AND_LOOK = 0x36; diff --git a/src/main/java/net/minestom/server/network/packet/server/play/CombatEventPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/CombatEventPacket.java new file mode 100644 index 000000000..9deac0451 --- /dev/null +++ b/src/main/java/net/minestom/server/network/packet/server/play/CombatEventPacket.java @@ -0,0 +1,80 @@ +package net.minestom.server.network.packet.server.play; + +import club.thectm.minecraft.text.TextObject; +import net.minestom.server.chat.Chat; +import net.minestom.server.entity.Entity; +import net.minestom.server.entity.Player; +import net.minestom.server.network.packet.PacketWriter; +import net.minestom.server.network.packet.server.ServerPacket; +import net.minestom.server.network.packet.server.ServerPacketIdentifier; + +import java.util.Optional; + +/** + * Packet sent during combat to a player. + * Only death is supported for the moment (other events are ignored anyway as of 1.15.2) + */ +public class CombatEventPacket implements ServerPacket { + + private EventType type; + private int duration; + private int opponent; + private int playerID; + private TextObject deathMessage; + + private CombatEventPacket() {} + + public static CombatEventPacket enter() { + CombatEventPacket packet = new CombatEventPacket(); + packet.type = EventType.ENTER_COMBAT; + return packet; + } + + public static CombatEventPacket end(int durationInTicks, Optional opponent) { + CombatEventPacket packet = new CombatEventPacket(); + packet.type = EventType.END_COMBAT; + packet.duration = durationInTicks; + packet.opponent = opponent.map(Entity::getEntityId).orElse(-1); + return packet; + } + + public static CombatEventPacket death(Player player, Optional killer, TextObject message) { + CombatEventPacket packet = new CombatEventPacket(); + packet.type = EventType.DEATH; + packet.playerID = player.getEntityId(); + packet.opponent = killer.map(Entity::getEntityId).orElse(-1); + packet.deathMessage = message; + return packet; + } + + @Override + public void write(PacketWriter writer) { + writer.writeVarInt(type.ordinal()); + switch (type) { + case ENTER_COMBAT: + // nothing to add + break; + + case END_COMBAT: + writer.writeVarInt(duration); + writer.writeInt(opponent); + break; + + case DEATH: + writer.writeVarInt(playerID); + writer.writeInt(opponent); + writer.writeSizedString(deathMessage.toJson().toString()); + break; + } + } + + @Override + public int getId() { + return ServerPacketIdentifier.COMBAT_EVENT; + } + + public enum EventType { + ENTER_COMBAT, END_COMBAT, // both ignored by Notchian client + DEATH, + } +}