Skip to content

Commit 252e536

Browse files
committed
1D - Damage events and entity ownership
1 parent adb52f8 commit 252e536

6 files changed

Lines changed: 184 additions & 2 deletions

File tree

changelog.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11

22
# Changelog
33

4+
## 1D
5+
6+
Damage event
7+
8+
Changes:
9+
10+
- Added damage override to damage events
11+
- Added damage source and damager attributes to damage events
12+
- Added shooter attribute to projectile entities
13+
- Added owner attribute to tamed entities
14+
- Added is_tamed attribute to entities
15+
416
## 1C
517

618
API cleanup

docs/index.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ Base event proxy. Event payloads may expose additional fields depending on event
4949

5050
- `block`: [`Block`](#block) — The block involved in the event, if any.
5151
- `chunk`: [`Chunk`](#chunk) — The chunk involved in the event, if any.
52+
- `damager`: [`Entity`](#entity) or [`Block`](#block) — The damage source for damage-related events, if any.
53+
- `damage`: float — Raw damage for damage-related events.
54+
- `final_damage`: float — Final damage after modifiers for damage-related events.
55+
- `damage_cause`: [`EntityDamageEvent.DamageCause`](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/entity/EntityDamageEvent.DamageCause.html) — Damage cause, if available.
5256
- `entity`: [`Entity`](#entity) — The entity involved in the event, if any.
5357
- `inventory`: [`Inventory`](#inventory) — The inventory involved in the event, if any.
5458
- `item`: [`Item`](#item) — The item involved in the event, if any.
@@ -61,6 +65,8 @@ Base event proxy. Event payloads may expose additional fields depending on event
6165

6266
- `cancel()` — Cancel the event if it is cancellable. Returns an awaitable that resolves to `None`.
6367

68+
For damage-related events, returning a number from the handler overrides the event damage.
69+
6470
### Chat formatting
6571

6672
For `player_chat` handlers, returning a string will cancel the original chat event and broadcast the returned string instead. Returning `None` (or no return value) leaves chat unchanged.
@@ -138,6 +144,13 @@ Base entity proxy.
138144

139145
- `location`: [`Location`](#location) — Current location.
140146
- `type`: [`EntityType`](#entitytype) — Entity type.
147+
- `is_projectile`: bool — Whether this entity is a projectile.
148+
- `shooter`: [`Entity`](#entity) or [`Block`](#block) — Projectile shooter/source, if any.
149+
- `is_tamed`: bool — Whether this entity is tamed (if tameable).
150+
- `owner`: [`Player`](#player) — Owner (if tameable and owner is online).
151+
- `owner_uuid`: str — Owner UUID (if tameable).
152+
- `owner_name`: str — Owner name (if tameable and known).
153+
- `source`: [`Entity`](#entity) or [`Player`](#player) — Source for spawned entities (e.g., TNT, clouds), if available.
141154
- `uuid`: str — Unique id.
142155
- `world`: [`World`](#world) — Current world.
143156

releases/pyjavabridge-1C.jar

1.94 KB
Binary file not shown.

src/main/java/com/pyjavabridge/PyJavaBridgePlugin.java

Lines changed: 141 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
import org.bukkit.entity.EntityType;
1515
import org.bukkit.entity.LivingEntity;
1616
import org.bukkit.entity.Player;
17+
import org.bukkit.entity.Projectile;
18+
import org.bukkit.entity.Tameable;
19+
import org.bukkit.entity.AnimalTamer;
1720
import org.bukkit.event.Event;
1821
import org.bukkit.event.EventPriority;
1922
import org.bukkit.event.HandlerList;
@@ -24,13 +27,16 @@
2427
import org.bukkit.block.BlockState;
2528
import org.bukkit.event.inventory.InventoryClickEvent;
2629
import org.bukkit.event.entity.CreatureSpawnEvent;
30+
import org.bukkit.event.entity.EntityDamageEvent;
2731
import org.bukkit.plugin.EventExecutor;
2832
import org.bukkit.plugin.java.JavaPlugin;
2933
import org.bukkit.scheduler.BukkitTask;
3034
import org.bukkit.command.CommandMap;
3135
import org.bukkit.util.RayTraceResult;
3236
import org.bukkit.util.Vector;
3337
import org.bukkit.permissions.PermissionAttachment;
38+
import org.bukkit.projectiles.BlockProjectileSource;
39+
import org.bukkit.projectiles.ProjectileSource;
3440
import org.bukkit.attribute.Attribute;
3541
import org.bukkit.attribute.AttributeModifier;
3642
import org.bukkit.inventory.InventoryHolder;
@@ -825,7 +831,20 @@ private void handleEventResult(JsonObject message) {
825831
return;
826832
}
827833

828-
if (message.has("result") && !message.get("result").isJsonNull()) {
834+
if (!message.has("result") || message.get("result").isJsonNull()) {
835+
return;
836+
}
837+
838+
String resultType = message.has("result_type") ? message.get("result_type").getAsString() : null;
839+
if ("damage".equalsIgnoreCase(resultType)) {
840+
JsonElement result = message.get("result");
841+
if (result.isJsonPrimitive() && result.getAsJsonPrimitive().isNumber()) {
842+
pending.damageOverride = result.getAsDouble();
843+
}
844+
return;
845+
}
846+
847+
if (resultType == null || "chat".equalsIgnoreCase(resultType)) {
829848
pending.chatOverride = message.get("result").getAsString();
830849
}
831850
}
@@ -2142,6 +2161,8 @@ private JsonElement serialize(Object value, Set<Object> seen) {
21422161
fields.add("type", serialize(entity.getType(), seen));
21432162
fields.add("location", serialize(entity.getLocation(), seen));
21442163
fields.add("world", serialize(entity.getWorld(), seen));
2164+
fields.addProperty("is_projectile", entity instanceof Projectile);
2165+
addAttributionFields(entity, fields, seen);
21452166
}
21462167

21472168
if (value instanceof org.bukkit.World world) {
@@ -2545,6 +2566,12 @@ private JsonObject baseEventPayload(Event event, String eventName) {
25452566
tryAddPayload(payload, event, "player", "getPlayer", "getWhoClicked");
25462567
tryAddPayload(payload, event, "block", "getBlock", "getClickedBlock");
25472568
tryAddPayload(payload, event, "entity", "getEntity");
2569+
tryAddPayload(payload, event, "damager", "getDamager");
2570+
if (event instanceof EntityDamageEvent damageEvent) {
2571+
payload.addProperty("damage", damageEvent.getDamage());
2572+
payload.addProperty("final_damage", damageEvent.getFinalDamage());
2573+
payload.add("damage_cause", serialize(damageEvent.getCause()));
2574+
}
25482575
tryAddPayload(payload, event, "location", "getLocation");
25492576
tryAddPayload(payload, event, "world", "getWorld");
25502577
tryAddPayload(payload, event, "item", "getItem");
@@ -2601,6 +2628,9 @@ private boolean dispatchCancellableEvent(Event event, String eventName, JsonObje
26012628
Bukkit.getScheduler().runTask(plugin,
26022629
() -> Bukkit.getServer().broadcast(net.kyori.adventure.text.Component.text(message)));
26032630
}
2631+
if (pending.damageOverride != null && pending.event instanceof EntityDamageEvent damageEvent) {
2632+
damageEvent.setDamage(pending.damageOverride);
2633+
}
26042634
if (cancelRequested && cancelMode == CancelMode.EVENT) {
26052635
pending.cancellable.setCancelled(true);
26062636
}
@@ -2695,6 +2725,115 @@ private void tryAddPayload(JsonObject payload, Event event, String key, String..
26952725
}
26962726
}
26972727

2728+
private void addAttributionFields(org.bukkit.entity.Entity entity, JsonObject fields, Set<Object> seen) {
2729+
Object shooter = null;
2730+
if (entity instanceof Projectile projectile) {
2731+
shooter = projectile.getShooter();
2732+
} else {
2733+
shooter = tryInvokeNoArg(entity, "getShooter");
2734+
}
2735+
addAttribution("shooter", shooter, fields, seen);
2736+
2737+
Object source = tryInvokeNoArg(entity, "getSource");
2738+
addAttribution("source", source, fields, seen);
2739+
2740+
Object owner = null;
2741+
if (entity instanceof Tameable tameable) {
2742+
fields.addProperty("is_tamed", tameable.isTamed());
2743+
owner = tameable.getOwner();
2744+
}
2745+
if (owner == null) {
2746+
owner = tryInvokeNoArg(entity, "getOwner");
2747+
}
2748+
if (owner == null) {
2749+
owner = tryInvokeNoArg(entity, "getOwningPlayer");
2750+
}
2751+
if (owner == null) {
2752+
owner = tryInvokeNoArg(entity, "getOwningEntity");
2753+
}
2754+
if (owner == null) {
2755+
owner = tryInvokeNoArg(entity, "getSummoner");
2756+
}
2757+
addAttribution("owner", owner, fields, seen);
2758+
2759+
}
2760+
2761+
private Object tryInvokeNoArg(Object target, String methodName) {
2762+
try {
2763+
Method method = target.getClass().getMethod(methodName);
2764+
if (method.getParameterCount() != 0) {
2765+
return null;
2766+
}
2767+
return method.invoke(target);
2768+
} catch (Exception ignored) {
2769+
return null;
2770+
}
2771+
}
2772+
2773+
private void addAttribution(String key, Object source, JsonObject fields, Set<Object> seen) {
2774+
if (source == null) {
2775+
return;
2776+
}
2777+
if (fields.has(key)) {
2778+
return;
2779+
}
2780+
2781+
Object resolved = source;
2782+
if (source instanceof ProjectileSource projectileSource && !(source instanceof Entity)) {
2783+
if (projectileSource instanceof BlockProjectileSource blockSource) {
2784+
resolved = blockSource.getBlock();
2785+
}
2786+
}
2787+
2788+
if ("source".equals(key) || "shooter".equals(key)) {
2789+
if (resolved instanceof Entity sourceEntity) {
2790+
fields.add(key, serialize(sourceEntity, seen));
2791+
} else if (resolved instanceof org.bukkit.block.Block block) {
2792+
fields.add(key, serialize(block, seen));
2793+
}
2794+
return;
2795+
}
2796+
2797+
if (resolved instanceof Entity sourceEntity) {
2798+
fields.add(key, serialize(sourceEntity, seen));
2799+
fields.addProperty(key + "_uuid", sourceEntity.getUniqueId().toString());
2800+
if (sourceEntity instanceof Player player) {
2801+
fields.addProperty(key + "_name", player.getName());
2802+
} else {
2803+
Object name = tryInvokeNoArg(sourceEntity, "getName");
2804+
if (name instanceof String nameText && !nameText.isBlank()) {
2805+
fields.addProperty(key + "_name", nameText);
2806+
}
2807+
}
2808+
return;
2809+
}
2810+
2811+
if (resolved instanceof org.bukkit.block.Block block) {
2812+
fields.add(key, serialize(block, seen));
2813+
return;
2814+
}
2815+
2816+
if (resolved instanceof AnimalTamer tamer) {
2817+
fields.addProperty(key + "_uuid", tamer.getUniqueId().toString());
2818+
if (tamer.getName() != null) {
2819+
fields.addProperty(key + "_name", tamer.getName());
2820+
}
2821+
if (tamer instanceof Player player) {
2822+
fields.add(key, serialize(player, seen));
2823+
}
2824+
return;
2825+
}
2826+
2827+
if (resolved instanceof UUID uuid) {
2828+
fields.addProperty(key + "_uuid", uuid.toString());
2829+
return;
2830+
}
2831+
2832+
if (resolved instanceof String nameText) {
2833+
fields.addProperty(key + "_name", nameText);
2834+
}
2835+
}
2836+
26982837
private void send(JsonObject response) {
26992838
if (writer == null) {
27002839
return;
@@ -3332,6 +3471,7 @@ static class PendingEvent {
33323471
Event event;
33333472
int id;
33343473
String chatOverride;
3474+
Double damageOverride;
33353475
}
33363476

33373477
private Class<? extends Event> resolveEventClass(String eventName) throws ClassNotFoundException {

src/main/resources/python/bridge.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1960,11 +1960,17 @@ async def _dispatch_event(self, event_name: str, payload: Any):
19601960
if event_id is not None:
19611961
if handlers:
19621962
override_text = None
1963+
override_damage = None
1964+
is_damage_event = isinstance(payload, ProxyBase) and "damage" in payload._fields
19631965
for result in results:
19641966
if isinstance(result, str):
19651967
override_text = result
1968+
elif is_damage_event and isinstance(result, (int, float)) and not isinstance(result, bool):
1969+
override_damage = float(result)
19661970
if override_text is not None:
1967-
self.send({"type": "event_result", "id": event_id, "result": override_text})
1971+
self.send({"type": "event_result", "id": event_id, "result": override_text, "result_type": "chat"})
1972+
if override_damage is not None:
1973+
self.send({"type": "event_result", "id": event_id, "result": override_damage, "result_type": "damage"})
19681974
self.send({"type": "event_done", "id": event_id})
19691975

19701976
def _handle_reader_error(self, exc: Exception):

src/main/resources/python/bridge.pyi

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ class Event:
1818
def cancel(self) -> Awaitable[None]: ...
1919
player: "Player"
2020
entity: "Entity"
21+
damager: "Entity" | "Block" | None
22+
damage: float
23+
final_damage: float
24+
damage_cause: Any
2125
block: "Block"
2226
world: "World"
2327
location: "Location"
@@ -83,6 +87,13 @@ class Entity:
8387
type: "EntityType"
8488
location: "Location"
8589
world: "World"
90+
is_projectile: bool
91+
shooter: "Entity" | "Player" | "Block" | None
92+
is_tamed: bool
93+
owner: "Player" | None
94+
owner_uuid: str | None
95+
owner_name: str | None
96+
source: "Entity" | "Player" | None
8697
def teleport(self, location: "Location") -> Awaitable[None]: ...
8798
def remove(self) -> Awaitable[None]: ...
8899
def set_velocity(self, vector: "Vector") -> Awaitable[None]: ...

0 commit comments

Comments
 (0)