/*
* This file is part of Sponge, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.spongepowered.mod.event;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.inject.Singleton;
import net.minecraftforge.event.CommandEvent;
import net.minecraftforge.event.ServerChatEvent;
import net.minecraftforge.event.entity.EntityEvent;
import net.minecraftforge.event.entity.EntityJoinWorldEvent;
import net.minecraftforge.event.entity.EntityMountEvent;
import net.minecraftforge.event.entity.EntityStruckByLightningEvent;
import net.minecraftforge.event.entity.EntityTravelToDimensionEvent;
import net.minecraftforge.event.entity.item.ItemExpireEvent;
import net.minecraftforge.event.entity.item.ItemTossEvent;
import net.minecraftforge.event.entity.living.EnderTeleportEvent;
import net.minecraftforge.event.entity.living.LivingAttackEvent;
import net.minecraftforge.event.entity.living.LivingDeathEvent;
import net.minecraftforge.event.entity.living.LivingDropsEvent;
import net.minecraftforge.event.entity.living.LivingEntityUseItemEvent;
import net.minecraftforge.event.entity.living.LivingEvent;
import net.minecraftforge.event.entity.living.LivingExperienceDropEvent;
import net.minecraftforge.event.entity.living.LivingHealEvent;
import net.minecraftforge.event.entity.living.LivingHurtEvent;
import net.minecraftforge.event.entity.living.LivingSpawnEvent;
import net.minecraftforge.event.entity.living.ZombieEvent;
import net.minecraftforge.event.entity.minecart.MinecartCollisionEvent;
import net.minecraftforge.event.entity.minecart.MinecartInteractEvent;
import net.minecraftforge.event.entity.player.ArrowLooseEvent;
import net.minecraftforge.event.entity.player.ArrowNockEvent;
import net.minecraftforge.event.entity.player.AttackEntityEvent;
import net.minecraftforge.event.entity.player.BonemealEvent;
import net.minecraftforge.event.entity.player.EntityItemPickupEvent;
import net.minecraftforge.event.entity.player.FillBucketEvent;
import net.minecraftforge.event.entity.player.PlayerDestroyItemEvent;
import net.minecraftforge.event.entity.player.PlayerDropsEvent;
import net.minecraftforge.event.entity.player.PlayerFlyableFallEvent;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.event.entity.player.PlayerPickupXpEvent;
import net.minecraftforge.event.entity.player.UseHoeEvent;
import net.minecraftforge.event.world.BlockEvent;
import net.minecraftforge.event.world.ChunkEvent;
import net.minecraftforge.event.world.ExplosionEvent;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.fml.common.ModContainer;
import net.minecraftforge.fml.common.eventhandler.EventPriority;
import net.minecraftforge.fml.common.eventhandler.IEventListener;
import net.minecraftforge.fml.common.gameevent.PlayerEvent;
import org.apache.logging.log4j.Logger;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.event.Cancellable;
import org.spongepowered.api.event.Event;
import org.spongepowered.api.event.Order;
import org.spongepowered.api.event.action.LightningEvent;
import org.spongepowered.api.event.block.ChangeBlockEvent;
import org.spongepowered.api.event.block.InteractBlockEvent;
import org.spongepowered.api.event.block.NotifyNeighborBlockEvent;
import org.spongepowered.api.event.command.SendCommandEvent;
import org.spongepowered.api.event.entity.ChangeEntityExperienceEvent;
import org.spongepowered.api.event.entity.CollideEntityEvent;
import org.spongepowered.api.event.entity.ConstructEntityEvent;
import org.spongepowered.api.event.entity.DestructEntityEvent;
import org.spongepowered.api.event.entity.HarvestEntityEvent;
import org.spongepowered.api.event.entity.HealEntityEvent;
import org.spongepowered.api.event.entity.InteractEntityEvent;
import org.spongepowered.api.event.entity.MoveEntityEvent;
import org.spongepowered.api.event.entity.RideEntityEvent;
import org.spongepowered.api.event.entity.SpawnEntityEvent;
import org.spongepowered.api.event.item.inventory.ChangeInventoryEvent;
import org.spongepowered.api.event.item.inventory.DropItemEvent;
import org.spongepowered.api.event.item.inventory.UseItemStackEvent;
import org.spongepowered.api.event.message.MessageChannelEvent;
import org.spongepowered.api.event.network.ClientConnectionEvent;
import org.spongepowered.api.event.world.LoadWorldEvent;
import org.spongepowered.api.event.world.UnloadWorldEvent;
import org.spongepowered.api.event.world.chunk.LoadChunkEvent;
import org.spongepowered.api.plugin.PluginManager;
import org.spongepowered.common.SpongeImpl;
import org.spongepowered.common.event.RegisteredListener;
import org.spongepowered.common.event.SpongeEventManager;
import org.spongepowered.common.event.tracking.CauseTracker;
import org.spongepowered.mod.SpongeMod;
import org.spongepowered.mod.interfaces.IMixinASMEventHandler;
import org.spongepowered.mod.interfaces.IMixinEvent;
import org.spongepowered.mod.interfaces.IMixinLoadController;
import java.util.List;
import javax.inject.Inject;
@Singleton
public class SpongeModEventManager extends SpongeEventManager {
@SuppressWarnings("unused") private final ImmutableBiMap<EventPriority, Order> priorityMappings =
new ImmutableBiMap.Builder<EventPriority, Order>()
.put(EventPriority.HIGHEST, Order.FIRST)
.put(EventPriority.HIGH, Order.EARLY)
.put(EventPriority.NORMAL, Order.DEFAULT)
.put(EventPriority.LOW, Order.LATE)
.put(EventPriority.LOWEST, Order.LAST)
.build();
@SuppressWarnings({"unchecked", "rawtypes"})
Class<? extends Event>[] useItemStack = new Class[] {UseItemStackEvent.Start.class, UseItemStackEvent.Tick.class, UseItemStackEvent.Stop.class, UseItemStackEvent.Finish.class, UseItemStackEvent.Replace.class, UseItemStackEvent.Reset.class};
@SuppressWarnings({"unchecked", "rawtypes"})
Class<? extends Event>[] interactEntity = new Class[] {InteractEntityEvent.Primary.MainHand.class, InteractEntityEvent.Primary.OffHand.class, InteractEntityEvent.Secondary.MainHand.class, InteractEntityEvent.Secondary.OffHand.class};
@SuppressWarnings({"unchecked", "rawtypes"})
Class<? extends Event>[] interactBlock = new Class[] {InteractBlockEvent.Primary.MainHand.class, InteractBlockEvent.Primary.OffHand.class, InteractBlockEvent.Secondary.MainHand.class, InteractBlockEvent.Secondary.OffHand.class};
@SuppressWarnings({"unchecked", "rawtypes"})
Class<? extends Event>[] spawnEntityEvent = new Class[] {SpawnEntityEvent.ChunkLoad.class, SpawnEntityEvent.Spawner.class};
@SuppressWarnings("unchecked")
public final ImmutableMultimap<Class<? extends net.minecraftforge.fml.common.eventhandler.Event>, Class<? extends Event>>
forgeToSpongeEventMapping =
new ImmutableMultimap.Builder<Class<? extends net.minecraftforge.fml.common.eventhandler.Event>, Class<? extends Event>>()
.put(ItemExpireEvent.class, DestructEntityEvent.Death.class)
.put(ItemTossEvent.class, DropItemEvent.Dispense.class)
.put(EnderTeleportEvent.class, MoveEntityEvent.Teleport.Portal.class)
.put(LivingAttackEvent.class, org.spongepowered.api.event.entity.AttackEntityEvent.class)
.put(LivingDeathEvent.class, DestructEntityEvent.Death.class)
.putAll(LivingDropsEvent.class, DropItemEvent.Destruct.class, DropItemEvent.Custom.class)
.putAll(LivingEntityUseItemEvent.class, this.useItemStack)
.put(LivingEvent.LivingJumpEvent.class, MoveEntityEvent.class)
.put(LivingExperienceDropEvent.class, HarvestEntityEvent.TargetPlayer.class)
.put(LivingHealEvent.class, HealEntityEvent.class)
.put(LivingHurtEvent.class, org.spongepowered.api.event.entity.AttackEntityEvent.class)
.putAll(LivingSpawnEvent.class, SpawnEntityEvent.Spawner.class)
.putAll(ZombieEvent.class, SpawnEntityEvent.ChunkLoad.class, SpawnEntityEvent.Spawner.class)
.put(MinecartCollisionEvent.class, CollideEntityEvent.Impact.class)
.putAll(MinecartInteractEvent.class, this.interactEntity)
.put(ArrowLooseEvent.class, SpawnEntityEvent.Spawner.class)
.putAll(ArrowNockEvent.class, this.useItemStack)
.put(AttackEntityEvent.class, org.spongepowered.api.event.entity.AttackEntityEvent.class)
.putAll(BonemealEvent.class, this.interactBlock)
.putAll(BonemealEvent.class, this.useItemStack)
.putAll(EntityItemPickupEvent.class, ChangeInventoryEvent.Pickup.class, DestructEntityEvent.class)
.putAll(FillBucketEvent.class, this.interactBlock)
.putAll(FillBucketEvent.class, this.useItemStack)
.putAll(PlayerDestroyItemEvent.class, DestructEntityEvent.class, DropItemEvent.Destruct.class)
.putAll(PlayerDropsEvent.class, DropItemEvent.Dispense.class, DropItemEvent.Destruct.class, DestructEntityEvent.Death.class)
.putAll(net.minecraftforge.event.entity.player.PlayerEvent.HarvestCheck.class, ChangeBlockEvent.Modify.class, ChangeBlockEvent.Post.class)
.put(PlayerFlyableFallEvent.class, MoveEntityEvent.class)
.putAll(PlayerInteractEvent.EntityInteract.class, InteractEntityEvent.Secondary.MainHand.class, InteractEntityEvent.Secondary.OffHand.class)
.putAll(PlayerInteractEvent.EntityInteractSpecific.class, InteractEntityEvent.Secondary.MainHand.class, InteractEntityEvent.Secondary.OffHand.class)
.putAll(PlayerInteractEvent.RightClickBlock.class, InteractBlockEvent.Secondary.MainHand.class, InteractBlockEvent.Secondary.OffHand.class)
.putAll(PlayerInteractEvent.RightClickItem.class, InteractBlockEvent.Secondary.MainHand.class, InteractBlockEvent.Secondary.OffHand.class)
.putAll(PlayerInteractEvent.LeftClickBlock.class, InteractBlockEvent.Primary.MainHand.class, InteractBlockEvent.Primary.OffHand.class)
.putAll(PlayerInteractEvent.LeftClickEmpty.class, InteractBlockEvent.Primary.MainHand.class, InteractBlockEvent.Primary.OffHand.class)
.putAll(PlayerPickupXpEvent.class, ChangeEntityExperienceEvent.class, DestructEntityEvent.class)
.putAll(UseHoeEvent.class, this.interactBlock)
.putAll(UseHoeEvent.class, this.useItemStack)
.putAll(EntityEvent.EntityConstructing.class, ConstructEntityEvent.Pre.class, ConstructEntityEvent.Post.class)
.putAll(EntityEvent.EntityConstructing.class, this.spawnEntityEvent)
.put(EntityEvent.EnteringChunk.class, MoveEntityEvent.class)
.putAll(EntityJoinWorldEvent.class, this.spawnEntityEvent)
.putAll(EntityMountEvent.class, RideEntityEvent.Dismount.class, RideEntityEvent.Mount.class)
.putAll(EntityStruckByLightningEvent.class, LightningEvent.Pre.class, LightningEvent.Strike.class, LightningEvent.Post.class)
.put(EntityTravelToDimensionEvent.class, MoveEntityEvent.Teleport.Portal.class)
// These are handled in MixinPlayerInteractionManager#onTryHarvestBlock
//.putAll(BlockEvent.HarvestDropsEvent.class, SpawnEntityEvent.class, DropItemEvent.class, ChangeBlockEvent.class)
//.putAll(BlockEvent.BreakEvent.class, ChangeBlockEvent.Break.class)
.putAll(BlockEvent.PlaceEvent.class, ChangeBlockEvent.Place.class, ChangeBlockEvent.Modify.class, ChangeBlockEvent.Post.class)
.putAll(BlockEvent.MultiPlaceEvent.class, ChangeBlockEvent.Place.class)
.put(BlockEvent.NeighborNotifyEvent.class, NotifyNeighborBlockEvent.class)
.put(ChunkEvent.Load.class, LoadChunkEvent.class)
.put(ExplosionEvent.Start.class, org.spongepowered.api.event.world.ExplosionEvent.Pre.class)
.put(ExplosionEvent.Detonate.class, org.spongepowered.api.event.world.ExplosionEvent.Detonate.class)
.put(WorldEvent.Load.class, LoadWorldEvent.class)
.put(WorldEvent.Unload.class, UnloadWorldEvent.class)
.put(CommandEvent.class, SendCommandEvent.class)
.put(ServerChatEvent.class, MessageChannelEvent.Chat.class)
.putAll(PlayerEvent.ItemPickupEvent.class, DestructEntityEvent.class, ChangeInventoryEvent.Pickup.class)
.putAll(PlayerEvent.PlayerLoggedInEvent.class, ClientConnectionEvent.Auth.class, ClientConnectionEvent.Login.class, ClientConnectionEvent.Join.class)
.put(PlayerEvent.PlayerLoggedOutEvent.class, ClientConnectionEvent.Disconnect.class)
.put(PlayerEvent.PlayerChangedDimensionEvent.class, MoveEntityEvent.Teleport.Portal.class)
.build();
@Inject
public SpongeModEventManager(Logger logger, PluginManager pluginManager) {
super(logger, pluginManager);
}
// Uses Forge mixins
public boolean post(Event spongeEvent, net.minecraftforge.fml.common.eventhandler.Event forgeEvent, IEventListener[] listeners) {
checkNotNull(forgeEvent, "forgeEvent");
if (spongeEvent == null) { // Fired by Forge
spongeEvent = ((IMixinEvent) forgeEvent).createSpongeEvent();
}
RegisteredListener.Cache listenerCache = getHandlerCache(spongeEvent);
// Fire events to plugins before modifications
for (Order order : Order.values()) {
post(spongeEvent, listenerCache.getListenersByOrder(order), true, false);
}
// If there are no forge listeners for event, skip sync
// If plugin cancelled event before modifications, ignore mods
if (listeners.length > 0 && !forgeEvent.isCanceled()) {
for (IEventListener listener : listeners) {
try {
if (listener instanceof IMixinASMEventHandler) {
IMixinASMEventHandler modListener = (IMixinASMEventHandler) listener;
modListener.getTimingsHandler().startTimingIfSync();
listener.invoke(forgeEvent);
modListener.getTimingsHandler().stopTimingIfSync();
} else {
listener.invoke(forgeEvent);
}
} catch (Throwable throwable) {
this.logger.error("Encountered an exception while processing a Forge event listener", throwable);
}
}
}
// Fire events to plugins after modifications (default)
for (Order order : Order.values()) {
post(spongeEvent, listenerCache.getListenersByOrder(order), false, false);
}
if (spongeEvent instanceof Cancellable && spongeEvent != forgeEvent) {
if (forgeEvent.isCancelable() && ((Cancellable) spongeEvent).isCancelled()) {
forgeEvent.setCanceled(true);
}
}
return forgeEvent.isCancelable() && forgeEvent.isCanceled();
}
// Uses SpongeForgeEventFactory (required for any events shared in SpongeCommon)
public boolean post(Event spongeEvent, Class<? extends net.minecraftforge.fml.common.eventhandler.Event> clazz) {
RegisteredListener.Cache listenerCache = getHandlerCache(spongeEvent);
SpongeForgeEventFactory.handlePrefireLogic(spongeEvent);
// Fire events to plugins before modifications
for (Order order : Order.values()) {
post(spongeEvent, listenerCache.getListenersByOrder(order), true, false);
}
boolean cancelled = false;
if (spongeEvent instanceof Cancellable) {
cancelled = ((Cancellable) spongeEvent).isCancelled();
}
if (!cancelled) {
spongeEvent = SpongeForgeEventFactory.callForgeEvent(spongeEvent, clazz);
}
// Fire events to plugins after modifications (default)
for (Order order : Order.values()) {
post(spongeEvent, listenerCache.getListenersByOrder(order), false, false);
}
return spongeEvent instanceof Cancellable && ((Cancellable) spongeEvent).isCancelled();
}
@SuppressWarnings("unchecked")
protected boolean post(Event event, List<RegisteredListener<?>> listeners, boolean beforeModifications, boolean forced) {
ModContainer oldContainer = ((IMixinLoadController) SpongeMod.instance.getController()).getActiveModContainer();
for (@SuppressWarnings("rawtypes")
RegisteredListener listener : listeners) {
((IMixinLoadController) SpongeMod.instance.getController()).setActiveModContainer((ModContainer) listener.getPlugin());
try {
if (forced || (!listener.isBeforeModifications() && !beforeModifications)
|| (listener.isBeforeModifications() && beforeModifications)) {
listener.getTimingsHandler().startTimingIfSync();
CauseTracker.getInstance().getCurrentContext().activeContainer(listener.getPlugin());
listener.handle(event);
}
} catch (Throwable e) {
this.logger.error("Could not pass {} to {}", event.getClass().getSimpleName(), listener.getPlugin(), e);
} finally {
listener.getTimingsHandler().stopTimingIfSync();
CauseTracker.getInstance().getCurrentContext().activeContainer(null);
}
}
((IMixinLoadController) SpongeMod.instance.getController()).setActiveModContainer(oldContainer);
return event instanceof Cancellable && ((Cancellable) event).isCancelled();
}
@Override
public boolean post(Event event) {
return this.post(event, false);
}
@Override
public boolean post(Event spongeEvent, boolean allowClientThread) {
if (!allowClientThread & Sponge.getGame().getPlatform().getExecutionType().isClient()) {
return false;
}
if (spongeEvent.getClass().getInterfaces().length > 0) {
Class<? extends net.minecraftforge.fml.common.eventhandler.Event> clazz = SpongeForgeEventFactory.getForgeEventClass(spongeEvent.getClass());
if (clazz != null) {
return post(spongeEvent, clazz);
}
}
// no checking for modifications required
return post(spongeEvent, getHandlerCache(spongeEvent).getListeners(), false, true);
}
}