Skip to content

Commit 34ed767

Browse files
authored
Fix effect particles showing on entities (#6088)
* Fix particle effects showing on entities * Map the new 1.21.0 effects * Move EffectType to better location * Implement review comments * Clear up some comment wording
1 parent f0815a9 commit 34ed767

File tree

6 files changed

+155
-52
lines changed

6 files changed

+155
-52
lines changed

core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
2+
* Copyright (c) 2019-2026 GeyserMC. http://geysermc.org
33
*
44
* Permission is hereby granted, free of charge, to any person obtaining a copy
55
* of this software and associated documentation files (the "Software"), to deal
@@ -37,9 +37,12 @@
3737
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerId;
3838
import org.cloudburstmc.protocol.bedrock.packet.MobArmorEquipmentPacket;
3939
import org.cloudburstmc.protocol.bedrock.packet.MobEquipmentPacket;
40+
import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket;
4041
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
42+
import org.geysermc.geyser.GeyserImpl;
4143
import org.geysermc.geyser.entity.EntityDefinition;
4244
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
45+
import org.geysermc.geyser.level.EffectType;
4346
import org.geysermc.geyser.entity.type.living.animal.HappyGhastEntity;
4447
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
4548
import org.geysermc.geyser.entity.vehicle.HappyGhastVehicleComponent;
@@ -68,7 +71,6 @@
6871
import org.geysermc.mcprotocollib.protocol.data.game.item.component.Equippable;
6972
import org.geysermc.mcprotocollib.protocol.data.game.level.particle.ColorParticleData;
7073
import org.geysermc.mcprotocollib.protocol.data.game.level.particle.Particle;
71-
import org.geysermc.mcprotocollib.protocol.data.game.level.particle.ParticleType;
7274

7375
import java.util.ArrayList;
7476
import java.util.Collections;
@@ -244,32 +246,51 @@ public void setHealth(FloatEntityMetadata entityMetadata) {
244246
// TODO: support all particle types
245247
public void setParticles(ObjectEntityMetadata<List<Particle>> entityMetadata) {
246248
List<Particle> particles = entityMetadata.getValue();
247-
float r = 0f;
248-
float g = 0f;
249-
float b = 0f;
250249

251250
int count = 0;
251+
long visibleEffects = 0L;
252252
for (Particle particle : particles) {
253-
if (particle.getType() != ParticleType.ENTITY_EFFECT) {
253+
EffectType effectType = null;
254+
255+
// Some particles are unique types on java, so we have to map them manually
256+
switch (particle.getType()) {
257+
case ENTITY_EFFECT -> effectType = EffectType.fromColor(((ColorParticleData) particle.getData()).getColor());
258+
case INFESTED -> effectType = EffectType.INFESTED;
259+
case ITEM_SLIME -> effectType = EffectType.OOZING;
260+
case ITEM_COBWEB -> effectType = EffectType.WEAVING;
261+
case SMALL_GUST -> effectType = EffectType.WIND_CHARGED;
262+
case TRIAL_OMEN -> effectType = EffectType.TRIAL_OMEN;
263+
case RAID_OMEN -> effectType = EffectType.RAID_OMEN;
264+
}
265+
266+
// If we couldn't map it, skip it
267+
if (effectType == null) {
268+
GeyserImpl.getInstance().getLogger().debug("Could not map particle " + particle.getType() + " to an effect for entity " + this.entityId);
254269
continue;
255270
}
256271

257-
int color = ((ColorParticleData) particle.getData()).getColor();
258-
r += ((color >> 16) & 0xFF) / 255f;
259-
g += ((color >> 8) & 0xFF) / 255f;
260-
b += ((color) & 0xFF) / 255f;
272+
int bedrockEffectId = effectType.getBedrockId();
273+
int ambient = 0; // We don't get this passed from java so assume false. BDS does the same.
274+
int effectBits = (bedrockEffectId & 0x3F) << 1 | ambient;
275+
276+
// Add the new effect to the bitfield, not sure why they are 7 not 8 but Mojank I guess
277+
visibleEffects = (visibleEffects << 7) | effectBits;
278+
261279
count++;
280+
281+
// Bedrock only supports showing 8 effects at a time
282+
if (count >= 8) {
283+
break;
284+
}
262285
}
263286

264-
int result = 0;
265-
if (count > 0) {
266-
r = r / count * 255f;
267-
g = g / count * 255f;
268-
b = b / count * 255f;
269-
result = (int) r << 16 | (int) g << 8 | (int) b;
287+
// If we got particles but none could be mapped to an effect, don't update
288+
// Not sure if this is the correct behavior, but seems reasonable
289+
if (count == 0 && !particles.isEmpty()) {
290+
return;
270291
}
271292

272-
dirtyMetadata.put(EntityDataTypes.EFFECT_COLOR, result);
293+
dirtyMetadata.put(EntityDataTypes.VISIBLE_MOB_EFFECTS, visibleEffects);
273294
}
274295

275296
public @Nullable Vector3i setBedPosition(EntityMetadata<Optional<Vector3i>, ?> entityMetadata) {
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Copyright (c) 2026 GeyserMC. http://geysermc.org
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
* THE SOFTWARE.
21+
*
22+
* @author GeyserMC
23+
* @link https://github.com/GeyserMC/Geyser
24+
*/
25+
26+
package org.geysermc.geyser.level;
27+
28+
import lombok.Getter;
29+
import org.geysermc.mcprotocollib.protocol.data.game.entity.Effect;
30+
31+
@Getter
32+
public enum EffectType {
33+
NONE(0, null, 0x000000),
34+
35+
SPEED(1, Effect.SPEED, 0x33EBFF),
36+
SLOWNESS(2, Effect.SLOWNESS, 0x8BAFE0),
37+
HASTE(3, Effect.HASTE, 0xD9C043),
38+
MINING_FATIGUE(4, Effect.MINING_FATIGUE, 0x4A4217),
39+
STRENGTH(5, Effect.STRENGTH, 0xFFC700),
40+
INSTANT_HEALTH(6, Effect.INSTANT_HEALTH, 0xF82423),
41+
INSTANT_DAMAGE(7, Effect.INSTANT_DAMAGE, 0xA9656A),
42+
JUMP_BOOST(8, Effect.JUMP_BOOST, 0xFDFF84),
43+
NAUSEA(9, Effect.NAUSEA, 0x551D4A),
44+
REGENERATION(10, Effect.REGENERATION, 0xCD5CAB),
45+
RESISTANCE(11, Effect.RESISTANCE, 0x9146F0),
46+
FIRE_RESISTANCE(12, Effect.FIRE_RESISTANCE, 0xFF9900),
47+
WATER_BREATHING(13, Effect.WATER_BREATHING, 0x98DAC0),
48+
INVISIBILITY(14, Effect.INVISIBILITY, 0xF6F6F6),
49+
BLINDNESS(15, Effect.BLINDNESS, 0x1F1F23),
50+
NIGHT_VISION(16, Effect.NIGHT_VISION, 0xC2FF66),
51+
HUNGER(17, Effect.HUNGER, 0x587653),
52+
WEAKNESS(18, Effect.WEAKNESS, 0x484D48),
53+
POISON(19, Effect.POISON, 0x87A363),
54+
WITHER(20, Effect.WITHER, 0x736156),
55+
HEALTH_BOOST(21, Effect.HEALTH_BOOST, 0xF87D23),
56+
ABSORPTION(22, Effect.ABSORPTION, 0x2552A5),
57+
SATURATION(23, Effect.SATURATION, 0xF82423),
58+
LEVITATION(24, Effect.LEVITATION, 0xCEFFFF),
59+
FATAL_POISON(25, null, 0x4E9331), // Bedrock-exclusive effect, maybe useful later if we map to closest color
60+
CONDUIT_POWER(26, Effect.CONDUIT_POWER, 0x1DC2D1),
61+
SLOW_FALLING(27, Effect.SLOW_FALLING, 0xF3CFB9),
62+
BAD_OMEN(28, Effect.BAD_OMEN, 0x0B6138),
63+
HERO_OF_THE_VILLAGE(29, Effect.HERO_OF_THE_VILLAGE, 0x44FF44),
64+
DARKNESS(30, Effect.DARKNESS, 0x292721),
65+
TRIAL_OMEN(31, Effect.TRIAL_OMEN, 0x16A6A6),
66+
WIND_CHARGED(32, Effect.WIND_CHARGED, 0xBDC9FF),
67+
WEAVING(33, Effect.WEAVING, 0x78695A),
68+
OOZING(34, Effect.OOZING, 0x99FFA3),
69+
INFESTED(35, Effect.INFESTED, 0x8C9B8C),
70+
RAID_OMEN(36, Effect.RAID_OMEN, 0xDE4058),
71+
BREATH_OF_THE_NAUTILUS(37, Effect.BREATH_OF_THE_NAUTILUS, 0x00FFEE),
72+
73+
// All Java-exclusive effects as of 1.16.2
74+
GLOWING(0, Effect.GLOWING, 0x94A061),
75+
LUCK(0, Effect.LUCK, 0x59C106),
76+
BAD_LUCK(0, Effect.UNLUCK, 0xC0A44D),
77+
DOLPHINS_GRACE(0, Effect.DOLPHINS_GRACE, 0x88A3BE);
78+
79+
private final int bedrockId;
80+
private final Effect javaEffect;
81+
private final int color;
82+
83+
EffectType(int bedrockId, Effect javaEffect, int color) {
84+
this.bedrockId = bedrockId;
85+
this.javaEffect = javaEffect;
86+
this.color = color;
87+
}
88+
89+
public static EffectType fromJavaEffect(Effect effect) {
90+
for (EffectType type : values()) {
91+
if (type.getJavaEffect() == effect) {
92+
return type;
93+
}
94+
}
95+
return EffectType.NONE;
96+
}
97+
98+
public static EffectType fromColor(int color) {
99+
color = color & 0xFFFFFF; // Ignore alpha channel
100+
for (EffectType type : values()) {
101+
if (type.getColor() == color) {
102+
return type;
103+
}
104+
}
105+
return EffectType.NONE;
106+
}
107+
}

core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaRemoveMobEffectTranslator.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
2+
* Copyright (c) 2019-2026 GeyserMC. http://geysermc.org
33
*
44
* Permission is hereby granted, free of charge, to any person obtaining a copy
55
* of this software and associated documentation files (the "Software"), to deal
@@ -25,14 +25,14 @@
2525

2626
package org.geysermc.geyser.translator.protocol.java.entity;
2727

28+
import org.geysermc.geyser.level.EffectType;
2829
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.ClientboundRemoveMobEffectPacket;
2930
import org.cloudburstmc.protocol.bedrock.packet.MobEffectPacket;
3031
import org.geysermc.geyser.entity.type.Entity;
3132
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
3233
import org.geysermc.geyser.session.GeyserSession;
3334
import org.geysermc.geyser.translator.protocol.PacketTranslator;
3435
import org.geysermc.geyser.translator.protocol.Translator;
35-
import org.geysermc.geyser.util.EntityUtils;
3636

3737
@Translator(packet = ClientboundRemoveMobEffectPacket.class)
3838
public class JavaRemoveMobEffectTranslator extends PacketTranslator<ClientboundRemoveMobEffectPacket> {
@@ -53,7 +53,7 @@ public void translate(GeyserSession session, ClientboundRemoveMobEffectPacket pa
5353
MobEffectPacket mobEffectPacket = new MobEffectPacket();
5454
mobEffectPacket.setEvent(MobEffectPacket.Event.REMOVE);
5555
mobEffectPacket.setRuntimeEntityId(entity.getGeyserId());
56-
mobEffectPacket.setEffectId(EntityUtils.toBedrockEffectId(packet.getEffect()));
56+
mobEffectPacket.setEffectId(EffectType.fromJavaEffect(packet.getEffect()).getBedrockId());
5757
session.sendUpstreamPacket(mobEffectPacket);
5858
}
5959
}

core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaUpdateMobEffectTranslator.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
2+
* Copyright (c) 2019-2026 GeyserMC. http://geysermc.org
33
*
44
* Permission is hereby granted, free of charge, to any person obtaining a copy
55
* of this software and associated documentation files (the "Software"), to deal
@@ -30,12 +30,12 @@
3030
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
3131
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
3232
import org.geysermc.geyser.entity.type.Entity;
33+
import org.geysermc.geyser.level.EffectType;
3334
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
3435
import org.geysermc.geyser.session.GeyserSession;
3536
import org.geysermc.geyser.session.cache.EntityEffectCache;
3637
import org.geysermc.geyser.translator.protocol.PacketTranslator;
3738
import org.geysermc.geyser.translator.protocol.Translator;
38-
import org.geysermc.geyser.util.EntityUtils;
3939
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.ClientboundUpdateMobEffectPacket;
4040

4141
import java.util.Collections;
@@ -71,7 +71,7 @@ public void translate(GeyserSession session, ClientboundUpdateMobEffectPacket pa
7171
mobEffectPacket.setRuntimeEntityId(entity.getGeyserId());
7272
mobEffectPacket.setParticles(packet.isShowParticles());
7373
mobEffectPacket.setAmbient(packet.isAmbient());
74-
mobEffectPacket.setEffectId(EntityUtils.toBedrockEffectId(packet.getEffect()));
74+
mobEffectPacket.setEffectId(EffectType.fromJavaEffect(packet.getEffect()).getBedrockId());
7575
session.sendUpstreamPacket(mobEffectPacket);
7676

7777
// Bedrock expects some attributes to be updated in the same tick as the effect causing them

core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
2+
* Copyright (c) 2019-2026 GeyserMC. http://geysermc.org
33
*
44
* Permission is hereby granted, free of charge, to any person obtaining a copy
55
* of this software and associated documentation files (the "Software"), to deal
@@ -35,6 +35,7 @@
3535
import org.cloudburstmc.protocol.bedrock.packet.PlayerActionPacket;
3636
import org.cloudburstmc.protocol.bedrock.packet.StopSoundPacket;
3737
import org.geysermc.geyser.entity.type.Entity;
38+
import org.geysermc.geyser.level.EffectType;
3839
import org.geysermc.geyser.level.BedrockDimension;
3940
import org.geysermc.geyser.level.JavaDimension;
4041
import org.geysermc.geyser.session.GeyserSession;
@@ -72,7 +73,7 @@ public static void switchDimension(GeyserSession session, JavaDimension javaDime
7273
MobEffectPacket mobEffectPacket = new MobEffectPacket();
7374
mobEffectPacket.setEvent(MobEffectPacket.Event.REMOVE);
7475
mobEffectPacket.setRuntimeEntityId(player.getGeyserId());
75-
mobEffectPacket.setEffectId(EntityUtils.toBedrockEffectId(effect));
76+
mobEffectPacket.setEffectId(EffectType.fromJavaEffect(effect).getBedrockId());
7677
session.sendUpstreamPacket(mobEffectPacket);
7778
}
7879
// Effects are re-sent from server

core/src/main/java/org/geysermc/geyser/util/EntityUtils.java

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2024 GeyserMC. http://geysermc.org
2+
* Copyright (c) 2024-2026 GeyserMC. http://geysermc.org
33
*
44
* Permission is hereby granted, free of charge, to any person obtaining a copy
55
* of this software and associated documentation files (the "Software"), to deal
@@ -74,32 +74,6 @@ public static String[] getAllEffectIdentifiers() {
7474
return identifiers;
7575
}
7676

77-
/**
78-
* Convert Java edition effect IDs to Bedrock edition
79-
*
80-
* @param effect Effect to convert
81-
* @return The numeric ID for the Bedrock edition effect
82-
*/
83-
public static int toBedrockEffectId(Effect effect) {
84-
return switch (effect) {
85-
case GLOWING, LUCK, UNLUCK, DOLPHINS_GRACE -> 0; // All Java-exclusive effects as of 1.16.2
86-
case LEVITATION -> 24;
87-
case CONDUIT_POWER -> 26;
88-
case SLOW_FALLING -> 27;
89-
case BAD_OMEN -> 28;
90-
case HERO_OF_THE_VILLAGE -> 29;
91-
case DARKNESS -> 30;
92-
case TRIAL_OMEN -> 31;
93-
case WIND_CHARGED -> 32;
94-
case WEAVING -> 33;
95-
case OOZING -> 34;
96-
case INFESTED -> 35;
97-
case RAID_OMEN -> 36;
98-
case BREATH_OF_THE_NAUTILUS -> 37;
99-
default -> effect.ordinal() + 1;
100-
};
101-
}
102-
10377
private static float getMountedHeightOffset(Entity mount) {
10478
if (mount instanceof BoatEntity boat && boat.getVariant() != BoatEntity.BoatVariant.BAMBOO) {
10579
return -0.1f;

0 commit comments

Comments
 (0)