Add mob conversion event ()

* Add mob conversion event

* spacing

* use a smarter mixin strategy

* add pig and villager lightning triggers

* tyop

Co-authored-by: haykam821 <24855774+haykam821@users.noreply.github.com>

* handle tadpole -> frog conversion

- update javadoc to clarify the handled cases
- document keepEquipment parameter
- improve mixin handler names

---------

Co-authored-by: haykam821 <24855774+haykam821@users.noreply.github.com>
This commit is contained in:
Syst3ms 2024-01-15 14:39:14 +01:00 committed by GitHub
parent a462da68c6
commit 44c0f8c656
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 213 additions and 3 deletions
fabric-entity-events-v1/src
main
testmod/java/net/fabricmc/fabric/test/entity/event

View file

@ -18,6 +18,7 @@ package net.fabricmc.fabric.api.entity.event.v1;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.mob.MobEntity;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
@ -73,6 +74,21 @@ public final class ServerLivingEntityEvents {
}
});
/**
* An event that is called when a mob has been converted to another type.
*
* <p>When this event is called, the old instance has not yet been discarded, and the new one has not yet been spawned.
* Mods may use this event to copy some of the old entity's data to the converted one.</p>
*
* <p>This event only handles cases where the entity type changes, requiring a new instance. Notably it does not
* cover mooshrooms changing color from lightning, creepers getting charged, or wolves being tamed.</p>
*/
public static final Event<MobConversion> MOB_CONVERSION = EventFactory.createArrayBacked(MobConversion.class, callbacks -> (previous, converted, keepEquipment) -> {
for (MobConversion callback : callbacks) {
callback.onConversion(previous, converted, keepEquipment);
}
});
@FunctionalInterface
public interface AllowDamage {
/**
@ -112,6 +128,18 @@ public final class ServerLivingEntityEvents {
void afterDeath(LivingEntity entity, DamageSource damageSource);
}
@FunctionalInterface
public interface MobConversion {
/**
* Called when a mob is converted to another type.
*
* @param previous the previous entity instance
* @param converted the new instance for the converted entity
* @param keepEquipment whether the converted entity should keep the previous one's equipment, like armor
*/
void onConversion(MobEntity previous, MobEntity converted, boolean keepEquipment);
}
private ServerLivingEntityEvents() {
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.entity.event;
import com.llamalad7.mixinextras.sugar.Local;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import net.minecraft.entity.Entity;
import net.minecraft.entity.mob.MobEntity;
import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents;
@Mixin(MobEntity.class)
public class MobEntityMixin {
@ModifyArg(method = "convertTo", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;spawnEntity(Lnet/minecraft/entity/Entity;)Z"))
private Entity afterEntityConverted(Entity converted, @Local(argsOnly = true) boolean keepEquipment) {
ServerLivingEntityEvents.MOB_CONVERSION.invoker().onConversion((MobEntity) (Object) this, (MobEntity) converted, keepEquipment);
return converted;
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.entity.event;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.mob.MobEntity;
import net.minecraft.entity.passive.AnimalEntity;
import net.minecraft.entity.passive.PigEntity;
import net.minecraft.world.World;
import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents;
@Mixin(PigEntity.class)
abstract class PigEntityMixin extends AnimalEntity {
protected PigEntityMixin(EntityType<? extends AnimalEntity> entityType, World world) {
super(entityType, world);
}
@ModifyArg(
method = "onStruckByLightning",
at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ServerWorld;spawnEntity(Lnet/minecraft/entity/Entity;)Z")
)
private Entity afterPiglinConversion(Entity converted) {
ServerLivingEntityEvents.MOB_CONVERSION.invoker().onConversion(this, (MobEntity) converted, false);
return converted;
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.entity.event;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.mob.MobEntity;
import net.minecraft.entity.passive.FishEntity;
import net.minecraft.entity.passive.TadpoleEntity;
import net.minecraft.world.World;
import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents;
@Mixin(TadpoleEntity.class)
abstract class TadpoleEntityMixin extends FishEntity {
TadpoleEntityMixin(EntityType<? extends FishEntity> entityType, World world) {
super(entityType, world);
}
@ModifyArg(
method = "growUp",
at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ServerWorld;spawnEntityAndPassengers(Lnet/minecraft/entity/Entity;)V")
)
private Entity afterGrowingUpToFrog(Entity converted) {
ServerLivingEntityEvents.MOB_CONVERSION.invoker().onConversion(this, (MobEntity) converted, false);
return converted;
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.entity.event;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.mob.MobEntity;
import net.minecraft.entity.passive.MerchantEntity;
import net.minecraft.entity.passive.VillagerEntity;
import net.minecraft.world.World;
import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents;
@Mixin(VillagerEntity.class)
abstract class VillagerEntityMixin extends MerchantEntity {
VillagerEntityMixin(EntityType<? extends MerchantEntity> entityType, World world) {
super(entityType, world);
}
@ModifyArg(
method = "onStruckByLightning",
at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ServerWorld;spawnEntityAndPassengers(Lnet/minecraft/entity/Entity;)V")
)
private Entity afterWitchConversion(Entity converted) {
ServerLivingEntityEvents.MOB_CONVERSION.invoker().onConversion(this, (MobEntity) converted, false);
return converted;
}
}

View file

@ -3,13 +3,17 @@
"package": "net.fabricmc.fabric.mixin.entity.event",
"compatibilityLevel": "JAVA_17",
"mixins": [
"elytra.LivingEntityMixin",
"elytra.PlayerEntityMixin",
"EntityMixin",
"LivingEntityMixin",
"MobEntityMixin",
"PigEntityMixin",
"PlayerEntityMixin",
"PlayerManagerMixin",
"ServerPlayerEntityMixin"
"ServerPlayerEntityMixin",
"TadpoleEntityMixin",
"VillagerEntityMixin",
"elytra.LivingEntityMixin",
"elytra.PlayerEntityMixin"
],
"injectors": {
"defaultRequire": 1,

View file

@ -114,6 +114,10 @@ public final class EntityEventTests implements ModInitializer {
LOGGER.info("{} died due to {} damage source", entity.getName().getString(), source.getName());
});
ServerLivingEntityEvents.MOB_CONVERSION.register((previous, converted, keepEquipment) -> {
LOGGER.info("{} is being converted to {} [{}]", previous.getName().getString(), converted.getName().getString(), keepEquipment);
});
EntitySleepEvents.ALLOW_SLEEPING.register((player, sleepingPos) -> {
// Can't sleep if holds blue wool
if (player.getStackInHand(Hand.MAIN_HAND).isOf(Items.BLUE_WOOL)) {