package toadmess.explosives.events.handlers;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.TNTPrimed;
import org.bukkit.event.Event;
import org.bukkit.event.Event.Type;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitScheduler;
import toadmess.explosives.config.entity.EntityConf;
import toadmess.explosives.events.HEEvent;
import toadmess.explosives.events.HEWrappedTNTEvent;
import toadmess.explosives.events.Handler;
import toadmess.explosives.events.TippingPoint;
public class TNTTracker implements Handler {
/**
* The Bukkit scheduler task ID scheduled
*/
private Integer scheduledTaskID = null;
/**
* The HEEvents that need to be associated with the appropriate TNTPrimed entities.
*/
private LinkedList<HEEvent> triggerEventsToAssociate = new LinkedList<HEEvent>();
/**
* Maps already seen TNTPrimed entity's ID to the HEEvent that triggered it.
*/
private final Map<Integer, HEEvent> seenTNTPrimedEntities = new HashMap<Integer, HEEvent>();
private final Plugin plugin;
/**
* Some event handler that knows how to deal with any new events that we may create (e.g. CAN_CHANGE_TNT_FUSE).
*/
private Handler eventRouter;
public TNTTracker(final Plugin p, final Handler eventRouter) {
this.plugin = p;
this.eventRouter = eventRouter;
// TODO: Sort this out..
HEEvent.setTNTTracker(this);
}
@Override
public TippingPoint[] getTippingPointsHandled() {
return new TippingPoint[] {
TippingPoint.TNT_PRIME_BY_FIRE,
TippingPoint.TNT_PRIME_BY_PLAYER,
TippingPoint.TNT_PRIME_BY_REDSTONE,
TippingPoint.TNT_PRIME_BY_EXPLOSION,
TippingPoint.AN_EXPLOSION,
TippingPoint.AN_EXPLOSION_CANCELLED,
TippingPoint.CAN_CHANGE_TNT_FUSE,
};
}
@Override
public Type[] getBukkitEventsRequired() {
return new Type[] {
Event.Type.BLOCK_DAMAGE,
Event.Type.BLOCK_BURN,
Event.Type.BLOCK_PHYSICS,
Event.Type.ENTITY_EXPLODE
};
}
@Override
public boolean isNeededBy(final EntityConf thisConfig) {
return thisConfig.hasTNTFuseConfig() ||
thisConfig.hasTNTPrimeByHandConfig() || thisConfig.hasTNTPrimeByFireConfig() ||
thisConfig.hasTNTPrimeByRedstoneConfig() || thisConfig.hasTNTPrimeByExplosionConfig();
}
@Override
public void handle(final HEEvent ev) {
final EntityConf worldConf = ev.getApplicableConfig();
switch(ev.type) {
case TNT_PRIME_BY_FIRE:
case TNT_PRIME_BY_PLAYER:
case TNT_PRIME_BY_REDSTONE:
associateWithTNTPrimedEntity(ev, worldConf);
break;
case AN_EXPLOSION_CANCELLED:
cleanupIfPrimedTNT((EntityExplodeEvent) ev.event);
break;
case AN_EXPLOSION:
cleanupIfPrimedTNT((EntityExplodeEvent) ev.event);
// Some other TNT block may have been caught in this blast, so search for new primed TNTs
associateWithTNTPrimedEntity(ev, worldConf);
break;
}
}
/**
* @param entityID
* @return The HEEvent that originally triggered the priming of the TNT
*/
public HEEvent getTriggerFor(final TNTPrimed tnt) {
return seenTNTPrimedEntities.get(tnt.getEntityId());
}
private void cleanupIfPrimedTNT(final EntityExplodeEvent ev) {
final Entity entity = ev.getEntity();
if(ev.getEntity() instanceof TNTPrimed) {
seenTNTPrimedEntities.remove(entity.getEntityId());
}
}
// Called when any priming is detected or suspected.
// It will schedule a search, in the next few ticks, for any TNTPrimed entities in
// that world which have not yet been re-fused.
private void associateWithTNTPrimedEntity(final HEEvent triggerEvent, final EntityConf worldConf) {
this.triggerEventsToAssociate.add(triggerEvent);
final BukkitScheduler bs = this.plugin.getServer().getScheduler();
if(this.scheduledTaskID != null && bs.isQueued(this.scheduledTaskID)) {
// We've already scheduled a TNTPrimed search
return;
}
final int taskId = this.plugin.getServer().getScheduler().scheduleSyncDelayedTask(this.plugin, new Runnable() {
@Override
public void run() {
TNTTracker.this.scheduledTaskID = null;
// Find the worlds that we need to search
final Set<String> worldsToSearch = new HashSet<String>();
for(final HEEvent triggerEvent : TNTTracker.this.triggerEventsToAssociate) {
worldsToSearch.add(triggerEvent.getEventLocation().getWorld().getName());
}
// Go through each world at a time and look for TNTPrimed entities that have not been seen before.
for(final String worldName : worldsToSearch) {
final World worldToSearch = TNTTracker.this.plugin.getServer().getWorld(worldName);
// TODO: Find an efficient bukkit way to get the TNTPrimed entity near the destroyed TNT block.
for(final Entity e : worldToSearch.getEntities()) {
if(e instanceof TNTPrimed) {
if(!seenTNTPrimedEntities.containsKey(e.getEntityId())) {
// This TNTPrimed entity has not been seen before.
// Try and match it up with it's triggering event's config
final TNTPrimed tnt = (TNTPrimed) e;
final Location tntLocation = tnt.getLocation();
// Go through all of the triggering events (extremely likely to be events of the
// same kind of TippingPoint) and find the nearest TNTPrimed entity to those
// event's locations.
HEEvent bestMatchFound = null;
double bestMatchDistanceSq = Double.POSITIVE_INFINITY;
for(final HEEvent triggeringEvent : TNTTracker.this.triggerEventsToAssociate) {
double distance = triggeringEvent.getEventLocation().toVector().distanceSquared(tntLocation.toVector());
if(distance < bestMatchDistanceSq) {
bestMatchDistanceSq = distance;
bestMatchFound = triggeringEvent;
}
}
seenTNTPrimedEntities.put(tnt.getEntityId(), bestMatchFound);
if(bestMatchFound.type == TippingPoint.AN_EXPLOSION) {
// This TNT was primed by another nearby explosion, a useful tipping point
final HEEvent primeTntEvent;
primeTntEvent = routeNewEvent(TippingPoint.TNT_PRIME_BY_EXPLOSION, tnt, bestMatchFound);
// Make sure that our tracking hashtable contains a reference to the actual
// triggering event itself rather than the explosion event that caused the triggering event.
seenTNTPrimedEntities.put(tnt.getEntityId(), primeTntEvent);
}
if(tnt.isDead()) {
// The priming of the TNT was prevented by removing the entity,
// Do housekeeping on our hashtable of entity IDs
seenTNTPrimedEntities.remove(tnt.getEntityId());
} else {
// This is a TippingPoint for changing the TNT's fuse duration.
routeNewEvent(TippingPoint.CAN_CHANGE_TNT_FUSE, tnt, bestMatchFound);
}
}
}
}
}
TNTTracker.this.triggerEventsToAssociate.clear();
}
private HEEvent routeNewEvent(final TippingPoint tp, final TNTPrimed tnt, final HEEvent bestMatchFound) {
final HEEvent tntEvent = new HEWrappedTNTEvent(tp, tnt, bestMatchFound);
eventRouter.handle(tntEvent);
return tntEvent;
}
});
this.scheduledTaskID = taskId;
}
}