/******************************************************************************* * Copyright 2014 Tobias Welther * * 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 de.tobiyas.racesandclasses.eventprocessing; import static de.tobiyas.racesandclasses.translation.languages.Keys.arrow_change; import static de.tobiyas.racesandclasses.translation.languages.Keys.cooldown_is_ready_again; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.event.Event; import org.bukkit.event.EventPriority; import org.bukkit.event.player.PlayerInteractEvent; import de.tobiyas.racesandclasses.RacesAndClasses; import de.tobiyas.racesandclasses.APIs.LanguageAPI; import de.tobiyas.racesandclasses.APIs.MessageScheduleApi; import de.tobiyas.racesandclasses.datacontainer.arrow.ArrowManager; import de.tobiyas.racesandclasses.datacontainer.traitholdercontainer.TraitHolderCombinder; import de.tobiyas.racesandclasses.eventprocessing.eventresolvage.EventWrapper; import de.tobiyas.racesandclasses.eventprocessing.eventresolvage.EventWrapperFactory; import de.tobiyas.racesandclasses.eventprocessing.eventresolvage.PlayerAction; import de.tobiyas.racesandclasses.eventprocessing.events.traittrigger.PostTraitTriggerEvent; import de.tobiyas.racesandclasses.listeners.interneventproxy.Listener_Proxy; import de.tobiyas.racesandclasses.playermanagement.player.RaCPlayer; import de.tobiyas.racesandclasses.playermanagement.playerdisplay.scoreboard.PlayerRaCScoreboardManager.SBCategory; import de.tobiyas.racesandclasses.traitcontainer.TraitStore; import de.tobiyas.racesandclasses.traitcontainer.container.TraitsList; import de.tobiyas.racesandclasses.traitcontainer.interfaces.TraitResults; import de.tobiyas.racesandclasses.traitcontainer.interfaces.annotations.bypasses.ByPassWorldDisabledCheck; import de.tobiyas.racesandclasses.traitcontainer.interfaces.annotations.bypasses.BypassHolderCheck; import de.tobiyas.racesandclasses.traitcontainer.interfaces.annotations.bypasses.StaticTrait; import de.tobiyas.racesandclasses.traitcontainer.interfaces.markerinterfaces.MagicSpellTrait; import de.tobiyas.racesandclasses.traitcontainer.interfaces.markerinterfaces.Trait; import de.tobiyas.racesandclasses.traitcontainer.interfaces.markerinterfaces.TraitRestriction; import de.tobiyas.racesandclasses.traitcontainer.interfaces.markerinterfaces.TraitWithCost; import de.tobiyas.racesandclasses.traitcontainer.interfaces.markerinterfaces.TraitWithRestrictions; import de.tobiyas.racesandclasses.traitcontainer.traits.arrows.AbstractArrow; import de.tobiyas.racesandclasses.util.friend.EnemyChecker.FriendDetectEvent; import de.tobiyas.racesandclasses.util.traitutil.TraitBypassCheck; public class TraitEventManager{ private RacesAndClasses plugin; private static long timings = 0; private static long calls = 0; private static TraitEventManager manager; private HashMap<Class<?>, Set<Trait>> traitList; private HashMap<Integer, Long> eventIDs; private int errorsPerMin = 0; private boolean spamPreventionActive = false; private List<String> registeredEventsAsName = new LinkedList<String>(); /** * Creates the whole Trait Event system */ public TraitEventManager(){ plugin = RacesAndClasses.getPlugin(); TraitsList.initStaticTraits(); TraitsList.initInternalTraits(); TraitStore.importFromFileSystem(); manager = this; traitList = new HashMap<Class<?>, Set<Trait>>(); eventIDs = new HashMap<Integer, Long>(); new DoubleEventRemover(this); Bukkit.getScheduler().runTaskTimer(plugin, new Runnable() { @Override public void run() { errorsPerMin = 0; } }, 20 * 60, 20 * 60); } /** * Inits the system by registering all needed stuff */ public void init(){ createStaticTraits(); } /** * Creates all Traits that are present for ALL players. */ private void createStaticTraits(){ TraitStore.buildTraitWithoutHolderByName("DeathCheckerTrait").generalInit(); TraitStore.buildTraitWithoutHolderByName("STDAxeDamageTrait").generalInit(); TraitStore.buildTraitWithoutHolderByName("ArmorTrait").generalInit(); } /** * Fires a synchronous Event intern * * @param event that was fired * @return if the Event was progressed by the plugin */ private boolean fireEventIntern(Event event){ if(event instanceof FriendDetectEvent) return false; calls ++; boolean changedSomething = false; if(eventIDs.containsKey(event.hashCode())){ return false; }else{ eventIDs.put(event.hashCode(), System.currentTimeMillis()); } EventWrapper eventWrapper = EventWrapperFactory.buildFromEvent(event); if(eventWrapper == null) return false; //we can't process this event. boolean disabledOnWorld = checkDisabledPerWorld(eventWrapper.getWorld()); Set<Trait> traitsToCheck = new HashSet<Trait>(); for(Class<?> clazz : traitList.keySet()){ if(clazz.isAssignableFrom(event.getClass())){ traitsToCheck.addAll(traitList.get(clazz)); } } for(Trait trait: traitsToCheck){ long timeBefore = System.currentTimeMillis(); if(trait == null) continue; try{ //first check if the Trait has a DisabledWorldBypass if(disabledOnWorld){ if(!trait.getClass().isAnnotationPresent(ByPassWorldDisabledCheck.class)){ continue; } } //Player player = trait.getReleventPlayer(event); //TODO check RaCPlayer player = eventWrapper.getPlayer(); //Check if Static Trait -> Always interested! //Check if Player has Trait. //If BypassHolderCheck annotation is present, it should be checked. if(!(trait instanceof StaticTrait)){ if(player == null){ continue; }else{ if(!(trait.getClass().isAnnotationPresent(BypassHolderCheck.class))){ if(!TraitHolderCombinder.getReducedTraitsOfPlayer(player).contains(trait)){ continue; } } } } boolean hasBypassForEvent = TraitBypassCheck.hasBypass(trait.getClass(), event.getClass()); //check if the Spell is changed. if(trait instanceof MagicSpellTrait && event instanceof PlayerInteractEvent && !hasBypassForEvent){ //only let the current magic spell continue for interaction events TraitWithCost magicTrait = (TraitWithCost) trait; if(player.getSpellManager().getCurrentSpell() != magicTrait){ continue; } } //check if the Arrow needs changed if(trait instanceof AbstractArrow && eventWrapper.getPlayerAction() == PlayerAction.CHANGE_ARROW && player.getArrowManager().hasAnyArrow() && player.getArrowManager().getCurrentArrow() == trait && !hasBypassForEvent){ //not we have a sure Arrow switch. boolean foreward = !player.getPlayer().isSneaking(); AbstractArrow newArrow = foreward ? player.getArrowManager().nextArrow() : player.getArrowManager().previousArrow(); if(newArrow != null && newArrow != trait){ if(!plugin.getConfigManager().getGeneralConfig().isConfig_enable_permanent_scoreboard()){ player.getScoreboardManager().updateSelectAndShow(SBCategory.Arrows); } LanguageAPI.sendTranslatedMessage(player.getPlayer(), arrow_change, "trait_name", newArrow.getDisplayName()); } continue; } //Check if arrow is currently equiped: if(trait instanceof AbstractArrow && !hasBypassForEvent) { ArrowManager arrowManager = player.getArrowManager(); if(!arrowManager.hasAnyArrow() || arrowManager.getCurrentArrow() != trait) continue; } //Check restrictions before calling. if(player != null && trait instanceof TraitWithRestrictions && !hasBypassForEvent){ TraitRestriction restriction = ((TraitWithRestrictions) trait).checkRestrictions(eventWrapper); if(restriction != TraitRestriction.None) continue; } //Trait is not interested in the event if(!hasBypassForEvent && !trait.canBeTriggered(eventWrapper)){ continue; } if(trait instanceof TraitWithCost && !hasBypassForEvent){ TraitWithCost costTrait = (TraitWithCost) trait; if(!player.getSpellManager().canCastSpell(costTrait)){ costTrait.triggerButDoesNotHaveEnoghCostType(eventWrapper); continue; } } plugin.getStatistics().traitTriggered(trait); //Statistic gathering TraitResults result = trait.trigger(eventWrapper); if(result.isTriggered()){ changedSomething = true; //fire event to event system. To let others see. RacesAndClasses.getPlugin().fireEventToBukkit(new PostTraitTriggerEvent(eventWrapper, trait)); if(trait instanceof TraitWithRestrictions && player != null && result.isSetCooldownOnPositiveTrigger()){ TraitWithRestrictions restrictionTrait = (TraitWithRestrictions) trait; String playerName = player.getName(); String cooldownName = restrictionTrait.getCooldownName(); int uplinkTraitTime = restrictionTrait.getMaxUplinkTime(); if(uplinkTraitTime > 0){ plugin.getCooldownManager().setCooldown(playerName, cooldownName, uplinkTraitTime); MessageScheduleApi.scheduleTranslateMessageToPlayer(player.getName(), uplinkTraitTime, cooldown_is_ready_again, "trait_name", trait.getDisplayName()); } } } }catch(Exception e){ //spam prevention is active. if(spamPreventionActive) return changedSomething; errorsPerMin++; //more than 20 Errors / min = spam prevention. if(errorsPerMin > 20){ spamPreventionActive = true; plugin.log("Getting too many Errors for Trait Events! " + "Spam preventions is suppressing them all to save your log from beeing flooded!"); } String holderName = trait.getTraitHolders()==null ? "static" : trait.getTraitHolders().toString(); plugin.getDebugLogger().logError("Error while executing trait: " + trait.getName() + " of holders: " + holderName + " event was: " + event.getEventName() + " Error was: " + e.getLocalizedMessage()); plugin.getDebugLogger().logStackTrace(e); }finally{ plugin.getStatistics().eventTime(trait.getName(), System.currentTimeMillis() - timeBefore); } } plugin.getStatistics().eventTriggered(); return changedSomething; } /** * Checks if the world is on the disabled list. * * True if it is, false if not. * * @param event * @return */ private boolean checkDisabledPerWorld(World world) { List<String> worldsDisabledOn = plugin.getConfigManager().getGeneralConfig().getConfig_worldsDisabled(); String worldName = world == null ? "" : world.getName(); for(String disabledWorldName : worldsDisabledOn){ if(disabledWorldName.equalsIgnoreCase(worldName)){ return true; } } return false; } public void cleanEventList(){ LinkedList<Integer> toRemove = new LinkedList<Integer>(); long currentTime = System.currentTimeMillis(); for(Integer inT : eventIDs.keySet()){ long oldVal = eventIDs.get(inT); if((currentTime - oldVal) > 500){ toRemove.add(inT); } } for(Integer inT : toRemove){ eventIDs.remove(inT); } } private void registerTraitIntern(Trait trait, Set<Class<? extends Event>> events, int priority){ //TODO register priority for(Class<? extends Event> clazz : events){ Set<Trait> traits = traitList.get(clazz); if(traits == null){ traits = new HashSet<Trait>(); traitList.put(clazz, traits); EventPriority eventPriority = EventPriority.NORMAL; try{ eventPriority = EventPriority.values()[priority]; }catch(IndexOutOfBoundsException exp){ } try{ plugin.getServer().getPluginManager().registerEvent(clazz, new Listener_Proxy(), eventPriority, new Simple_event_executor(), plugin); registeredEventsAsName.add(clazz.getCanonicalName()); }catch(Exception exp){ plugin.log("Could not register Event: " + clazz.getCanonicalName() + " of trait: " + trait.getName() + ". Exception: " + exp.getLocalizedMessage()); plugin.getDebugLogger().logStackTrace(exp); } } traits.add(trait); } } private void unregisterTraitIntern(Trait trait){ traitList.remove(trait); } public static TraitEventManager getInstance(){ return manager; } public static boolean fireEvent(Event event){ try{ long time = System.currentTimeMillis(); boolean result = getInstance().fireEventIntern(event); timings += System.currentTimeMillis() - time; return result; }catch(Exception e){ RacesAndClasses.getPlugin().getDebugLogger().logStackTrace(e); return false; } } public static long timingResults(){ long time = new Long(timings); timings = 0; return time; } public static long getCalls(){ long tempCalls = new Long(calls); calls = 0; return tempCalls; } public static void registerTrait(Trait trait, Set<Class<? extends Event>> events, int priority){ getInstance().registerTraitIntern(trait, events, priority); } public void unregisterTrait(Trait trait){ getInstance().unregisterTraitIntern(trait); } /** * @return the registeredEventsCount */ public List<String> getRegisteredEventsAsName() { return registeredEventsAsName; } }