Entity Events v1 ()

* Entity Events v1.

First up is an event fired after a living entity is damaged. All vanilla living entities except armor stands work with this event.

* Events related to killing of entities, changing world, player respawn/copyFrom

Death related:
an entity killing something and an entity being killed by an adversary

Player related:
After respawn, copy to

* Adversary stuff isn't needed

* checkstyle again

* Call AFTER_KILLED_OTHER on a ServerPlayerEntity upon death.

* Add event which is fired when a player first joined a server and add functional interface annotations

* Update build.gradle

* mispelled

* Some renames, test events, drop damage event

Damage events need further consideration in future

* Comments and null check

* Update for mappings

* Warning comment

* Remove first join event and teleport helper

First join does not work atm and teleport is in dimensions pr

* Module dependencies

* Javadoc tweaks

* The serverening
This commit is contained in:
i509VCB 2020-12-30 10:43:22 -06:00 committed by GitHub
parent d9daf26edb
commit 79b23bee5b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 568 additions and 0 deletions

View file

@ -0,0 +1,10 @@
archivesBaseName = "fabric-entity-events-v1"
version = getSubprojectVersion(project, "1.0.0")
moduleDependencies(project, [
'fabric-api-base'
])
dependencies {
testmodCompile project(path: ':fabric-command-api-v1', configuration: 'dev')
}

View file

@ -0,0 +1,55 @@
/*
* 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.api.entity.event.v1;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.server.world.ServerWorld;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
/**
* Events related to entities in combat.
*/
public final class ServerEntityCombatEvents {
/**
* An event that is called after an entity is directly responsible for killing another entity.
*
* @see Entity#onKilledOther(ServerWorld, LivingEntity)
*/
public static final Event<AfterKilledOtherEntity> AFTER_KILLED_OTHER_ENTITY = EventFactory.createArrayBacked(AfterKilledOtherEntity.class, callbacks -> (world, entity, killedEntity) -> {
for (AfterKilledOtherEntity callback : callbacks) {
callback.afterKilledOtherEntity(world, entity, killedEntity);
}
});
@FunctionalInterface
public interface AfterKilledOtherEntity {
/**
* Called after an entity has killed another entity.
*
* @param world the world
* @param entity the entity
* @param killedEntity the entity which was killed by the {@code entity}
*/
void afterKilledOtherEntity(ServerWorld world, Entity entity, LivingEntity killedEntity);
}
private ServerEntityCombatEvents() {
}
}

View file

@ -0,0 +1,92 @@
/*
* 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.api.entity.event.v1;
import net.minecraft.entity.Entity;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
/**
* Events related to an entity being moved to another world.
*
* @apiNote For a {@link ServerPlayerEntity}, please use {@link ServerEntityWorldChangeEvents#AFTER_PLAYER_CHANGE_WORLD}.
*/
public final class ServerEntityWorldChangeEvents {
/**
* An event which is called after an entity has been moved to a different world.
*
* <p>All entities are copied to the destination and the old entity removed.
* This event does not apply to the {@link ServerPlayerEntity} since players are physically moved to the new world instead of being copied over.
*
* <p>A mod may use this event for reference cleanup if it is tracking an entity's current world.
*
* @see ServerEntityWorldChangeEvents#AFTER_PLAYER_CHANGE_WORLD
*/
public static final Event<AfterEntityChange> AFTER_ENTITY_CHANGE_WORLD = EventFactory.createArrayBacked(AfterEntityChange.class, callbacks -> (originalEntity, newEntity, origin, destination) -> {
for (AfterEntityChange callback : callbacks) {
callback.afterChangeWorld(originalEntity, newEntity, origin, destination);
}
});
/**
* An event which is called after a player has been moved to a different world.
*
* <p>This is similar to {@link ServerEntityWorldChangeEvents#AFTER_ENTITY_CHANGE_WORLD} but is only called for players.
* This is because the player is physically moved to the new world instead of being recreated at the destination.
*
* @see ServerEntityWorldChangeEvents#AFTER_ENTITY_CHANGE_WORLD
*/
public static final Event<AfterPlayerChange> AFTER_PLAYER_CHANGE_WORLD = EventFactory.createArrayBacked(AfterPlayerChange.class, callbacks -> (player, origin, destination) -> {
for (AfterPlayerChange callback : callbacks) {
callback.afterChangeWorld(player, origin, destination);
}
});
@FunctionalInterface
public interface AfterEntityChange {
/**
* Called after an entity has been recreated at the destination when being moved to a different world.
*
* <p>Note this event is not called if the entity is a {@link ServerPlayerEntity}.
* {@link AfterPlayerChange} should be used to track when a player has changed worlds.
*
* @param originalEntity the original entity
* @param newEntity the new entity at the destination
* @param origin the world the original entity is in
* @param destination the destination world the new entity is in
*/
void afterChangeWorld(Entity originalEntity, Entity newEntity, ServerWorld origin, ServerWorld destination);
}
@FunctionalInterface
public interface AfterPlayerChange {
/**
* Called after a player has been moved to different world.
*
* @param player the player
* @param origin the original world the player was in
* @param destination the new world the player was moved to
*/
void afterChangeWorld(ServerPlayerEntity player, ServerWorld origin, ServerWorld destination);
}
private ServerEntityWorldChangeEvents() {
}
}

View file

@ -0,0 +1,74 @@
/*
* 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.api.entity.event.v1;
import net.minecraft.server.network.ServerPlayerEntity;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
public final class ServerPlayerEvents {
/**
* An event that is called when the data from an old player is copied to a new player.
*
* <p>This event is typically called before a player is completely respawned.
* Mods may use this event to copy old player data to a new player.
*/
public static final Event<ServerPlayerEvents.CopyFrom> COPY_FROM = EventFactory.createArrayBacked(ServerPlayerEvents.CopyFrom.class, callbacks -> (oldPlayer, newPlayer, alive) -> {
for (CopyFrom callback : callbacks) {
callback.copyFromPlayer(oldPlayer, newPlayer, alive);
}
});
/**
* An event that is called after a player has been respawned.
*
* <p>Mods may use this event for reference clean up on the old player.
*/
public static final Event<ServerPlayerEvents.AfterRespawn> AFTER_RESPAWN = EventFactory.createArrayBacked(ServerPlayerEvents.AfterRespawn.class, callbacks -> (oldPlayer, newPlayer, alive) -> {
for (AfterRespawn callback : callbacks) {
callback.afterRespawn(oldPlayer, newPlayer, alive);
}
});
@FunctionalInterface
public interface CopyFrom {
/**
* Called when player data is copied to a new player.
*
* @param oldPlayer the old player
* @param newPlayer the new player
* @param alive whether the old player is still alive
*/
void copyFromPlayer(ServerPlayerEntity oldPlayer, ServerPlayerEntity newPlayer, boolean alive);
}
@FunctionalInterface
public interface AfterRespawn {
/**
* Called after player a has been respawned.
*
* @param oldPlayer the old player
* @param newPlayer the new player
* @param alive whether the old player is still alive
*/
void afterRespawn(ServerPlayerEntity oldPlayer, ServerPlayerEntity newPlayer, boolean alive);
}
private ServerPlayerEvents() {
}
}

View file

@ -0,0 +1,40 @@
/*
* 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.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.entity.Entity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.world.World;
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents;
@Mixin(Entity.class)
abstract class EntityMixin {
@Shadow
public World world;
@Inject(method = "moveToWorld", at = @At(value = "RETURN", ordinal = 1))
private void afterWorldChanged(ServerWorld destination, CallbackInfoReturnable<Entity> cir) {
ServerEntityWorldChangeEvents.AFTER_ENTITY_CHANGE_WORLD.invoker().afterChangeWorld((Entity) (Object) this, cir.getReturnValue(), (ServerWorld) this.world, (ServerWorld) cir.getReturnValue().world);
}
}

View file

@ -0,0 +1,38 @@
/*
* 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.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.server.world.ServerWorld;
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityCombatEvents;
@Mixin(LivingEntity.class)
abstract class LivingEntityMixin extends EntityMixin {
@Inject(method = "onDeath", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;onKilledOther(Lnet/minecraft/server/world/ServerWorld;Lnet/minecraft/entity/LivingEntity;)V", shift = At.Shift.AFTER), locals = LocalCapture.CAPTURE_FAILEXCEPTION)
private void onEntityKilledOther(DamageSource source, CallbackInfo ci, Entity attacker) {
ServerEntityCombatEvents.AFTER_KILLED_OTHER_ENTITY.invoker().afterKilledOtherEntity(((ServerWorld) this.world), attacker, (LivingEntity) (Object) this);
}
}

View file

@ -0,0 +1,35 @@
/*
* 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.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.server.PlayerManager;
import net.minecraft.server.network.ServerPlayerEntity;
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
@Mixin(PlayerManager.class)
abstract class PlayerManagerMixin {
@Inject(method = "respawnPlayer", at = @At("TAIL"))
private void afterRespawn(ServerPlayerEntity oldPlayer, boolean alive, CallbackInfoReturnable<ServerPlayerEntity> cir) {
ServerPlayerEvents.AFTER_RESPAWN.invoker().afterRespawn(oldPlayer, cir.getReturnValue(), alive);
}
}

View file

@ -0,0 +1,68 @@
/*
* 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.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.entity.Entity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityCombatEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
@Mixin(ServerPlayerEntity.class)
abstract class ServerPlayerEntityMixin extends LivingEntityMixin {
@Shadow
public abstract ServerWorld getServerWorld();
/**
* Minecraft by default does not call Entity#onKilledOther for a ServerPlayerEntity being killed.
* This is a Mojang bug.
* This is implements the method call on the server player entity and then calls the corresponding event.
*/
@Inject(method = "onDeath", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerPlayerEntity;getPrimeAdversary()Lnet/minecraft/entity/LivingEntity;"))
private void callOnKillForPlayer(DamageSource source, CallbackInfo ci) {
final Entity attacker = source.getAttacker();
// If the damage source that killed the player was an entity, then fire the event.
if (attacker != null) {
attacker.onKilledOther(this.getServerWorld(), (ServerPlayerEntity) (Object) this);
ServerEntityCombatEvents.AFTER_KILLED_OTHER_ENTITY.invoker().afterKilledOtherEntity(this.getServerWorld(), attacker, (ServerPlayerEntity) (Object) this);
}
}
/**
* This is called by both "moveToWorld" and "teleport".
* So this is suitable to handle the after event from both call sites.
*/
@Inject(method = "worldChanged(Lnet/minecraft/server/world/ServerWorld;)V", at = @At("TAIL"))
private void afterWorldChanged(ServerWorld origin, CallbackInfo ci) {
ServerEntityWorldChangeEvents.AFTER_PLAYER_CHANGE_WORLD.invoker().afterChangeWorld((ServerPlayerEntity) (Object) this, origin, this.getServerWorld());
}
@Inject(method = "copyFrom", at = @At("TAIL"))
private void onCopyFrom(ServerPlayerEntity oldPlayer, boolean alive, CallbackInfo ci) {
ServerPlayerEvents.COPY_FROM.invoker().copyFromPlayer(oldPlayer, (ServerPlayerEntity) (Object) this, alive);
}
}

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 java.util.Set;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Coerce;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import net.minecraft.entity.Entity;
import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.command.TeleportCommand;
import net.minecraft.server.world.ServerWorld;
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents;
@Mixin(TeleportCommand.class)
abstract class TeleportCommandMixin {
/**
* We need to fire the change world event for entities that are teleported using the `/teleport` command.
*/
@Inject(method = "teleport", at = @At(value = "FIELD", target = "Lnet/minecraft/entity/Entity;removed:Z", opcode = Opcodes.PUTFIELD), locals = LocalCapture.CAPTURE_FAILEXCEPTION)
private static void afterEntityTeleportedToWorld(ServerCommandSource source, Entity originalEntity, ServerWorld destination, double x, double y, double z, Set<PlayerPositionLookS2CPacket.Flag> movementFlags, float yaw, float pitch, @Coerce Object facingLocation, CallbackInfo ci, float clampedYaw, float clampedPitch, Entity newEntity) {
ServerEntityWorldChangeEvents.AFTER_ENTITY_CHANGE_WORLD.invoker().afterChangeWorld(originalEntity, newEntity, ((ServerWorld) originalEntity.world), destination);
}
}

Binary file not shown.

After

(image error) Size: 1.5 KiB

View file

@ -0,0 +1,15 @@
{
"required": true,
"package": "net.fabricmc.fabric.mixin.entity.event",
"compatibilityLevel": "JAVA_8",
"mixins": [
"EntityMixin",
"LivingEntityMixin",
"PlayerManagerMixin",
"ServerPlayerEntityMixin",
"TeleportCommandMixin"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -0,0 +1,25 @@
{
"schemaVersion": 1,
"id": "fabric-entity-events-v1",
"name": "Fabric Entity Events (v1)",
"version": "${version}",
"environment": "*",
"license": "Apache-2.0",
"icon": "assets/fabric-entity-events-v1/icon.png",
"contact": {
"homepage": "https://fabricmc.net",
"irc": "irc://irc.esper.net:6667/fabric",
"issues": "https://github.com/FabricMC/fabric/issues",
"sources": "https://github.com/FabricMC/fabric"
},
"authors": [
"FabricMC"
],
"depends": {
"fabricloader": ">=0.8.2"
},
"description": "Events to hook into entities.",
"mixins": [
"fabric-entity-events-v1.mixins.json"
]
}

View file

@ -0,0 +1,52 @@
/*
* 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.test.entity.event;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityCombatEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
public final class EntityEventTests implements ModInitializer {
private static final Logger LOGGER = LogManager.getLogger(EntityEventTests.class);
@Override
public void onInitialize() {
ServerEntityCombatEvents.AFTER_KILLED_OTHER_ENTITY.register((world, entity, killed) -> {
LOGGER.info("Entity Killed: {}", killed);
});
ServerEntityWorldChangeEvents.AFTER_PLAYER_CHANGE_WORLD.register((player, origin, destination) -> {
LOGGER.info("Moved player {}: [{} -> {}]", player, origin.getRegistryKey().getValue(), destination.getRegistryKey().getValue());
});
ServerEntityWorldChangeEvents.AFTER_ENTITY_CHANGE_WORLD.register((originalEntity, newEntity, origin, destination) -> {
LOGGER.info("Moved entity {} -> {}: [({} -> {}]", originalEntity, newEntity, origin.getRegistryKey().getValue(), destination.getRegistryKey().getValue());
});
ServerPlayerEvents.COPY_FROM.register((oldPlayer, newPlayer, alive) -> {
LOGGER.info("Copied data for {} from {} to {}", oldPlayer.getGameProfile().getName(), oldPlayer, newPlayer);
});
ServerPlayerEvents.AFTER_RESPAWN.register((oldPlayer, newPlayer, alive) -> {
LOGGER.info("Respawned {}, [{}, {}]", oldPlayer.getGameProfile().getName(), oldPlayer.getServerWorld().getRegistryKey().getValue(), newPlayer.getServerWorld().getRegistryKey().getValue());
});
}
}

View file

@ -0,0 +1,17 @@
{
"schemaVersion": 1,
"id": "fabric-entity-events-v1-testmod",
"name": "Fabric Entity Events (v1) Test Mod",
"version": "1.0.0",
"environment": "*",
"license": "Apache-2.0",
"depends": {
"fabric-entity-events-v1": "*",
"fabric-command-api-v1":"*"
},
"entrypoints": {
"main": [
"net.fabricmc.fabric.test.entity.event.EntityEventTests"
]
}
}

View file

@ -22,6 +22,7 @@ include 'fabric-containers-v0'
include 'fabric-content-registries-v0'
include 'fabric-crash-report-info-v1'
include 'fabric-dimensions-v1'
include 'fabric-entity-events-v1'
include 'fabric-events-interaction-v0'
include 'fabric-events-lifecycle-v0'
include 'fabric-game-rule-api-v1'