/*******************************************************************************
* 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.traitcontainer.interfaces;
import static de.tobiyas.racesandclasses.translation.languages.Keys.trait_cooldown;
import java.util.ArrayList;
import java.util.Date;
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.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Biome;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.inventory.ItemStack;
import de.tobiyas.racesandclasses.RacesAndClasses;
import de.tobiyas.racesandclasses.APIs.CooldownApi;
import de.tobiyas.racesandclasses.APIs.LanguageAPI;
import de.tobiyas.racesandclasses.datacontainer.traitholdercontainer.AbstractTraitHolder;
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.events.traittrigger.PostTraitTriggerEvent;
import de.tobiyas.racesandclasses.eventprocessing.events.traittrigger.PreTraitTriggerEvent;
import de.tobiyas.racesandclasses.listeners.generallisteners.PlayerLastDamageListener;
import de.tobiyas.racesandclasses.playermanagement.player.RaCPlayer;
import de.tobiyas.racesandclasses.traitcontainer.interfaces.annotations.configuration.TraitConfigurationField;
import de.tobiyas.racesandclasses.traitcontainer.interfaces.annotations.configuration.TraitConfigurationNeeded;
import de.tobiyas.racesandclasses.traitcontainer.interfaces.markerinterfaces.Trait;
import de.tobiyas.racesandclasses.traitcontainer.interfaces.markerinterfaces.TraitRestriction;
import de.tobiyas.racesandclasses.traitcontainer.interfaces.markerinterfaces.TraitWithRestrictions;
import de.tobiyas.racesandclasses.traitcontainer.interfaces.skills.SkillLevelConfig;
import de.tobiyas.racesandclasses.traitcontainer.modifiers.ModifierFactory;
import de.tobiyas.racesandclasses.traitcontainer.modifiers.TraitSituationModifier;
import de.tobiyas.racesandclasses.util.traitutil.TraitConfigParser;
import de.tobiyas.racesandclasses.util.traitutil.TraitConfiguration;
import de.tobiyas.racesandclasses.util.traitutil.TraitConfigurationFailedException;
import de.tobiyas.racesandclasses.util.traitutil.TraitVisible;
import de.tobiyas.racesandclasses.vollotile.ParticleEffects;
public abstract class AbstractBasicTrait implements Trait, TraitWithRestrictions, Listener {
/**
* The Path to modifiers depending on the levels.
*/
public static final String MODIFIERS_PATH = "modifiers";
/**
* On Cast Particles
*/
public static final String ON_USE_PARTICLES_PATH = "onUseParticles";
/**
* The path to
*/
public static final String REPLACES_OTHER_TRAITS_PATH = "replacesOtherTraits";
/**
* The Global Skill tree name.
*/
public static final String GLOBAL_SKILL_TREE_NAME = "GLOBAL";
/**
* The plugin to call stuff on.
*/
protected static final RacesAndClasses plugin = RacesAndClasses.getPlugin();
/**
* the minimum level to use this trait
*/
protected int minimumLevel = 1;
/**
* the maximum level to use this trait
*/
protected int maximumLevel = 90000000;
/**
* The Set of biomes restricted.
*/
protected final Set<Biome> biomes = new HashSet<>();
/**
* The set of Items NEEDED to be weared.s
*/
protected final Set<Material> wearing = new HashSet<>();
/**
* Tells if the Trait only works in the water
*/
protected boolean onlyInWater = false;
/**
* Tells if the Trait only works on land
*/
protected boolean onlyOnLand = false;
/**
* Tells if the Trait only works in lava
*/
protected boolean onlyInLava = false;
/**
* Tells if the Trait only works on Snow
*/
protected boolean onlyOnSnow = false;
/**
* Tells if the Trait can only trigger in Night.
*/
protected boolean onlyInNight = false;
/**
* Tells if the Trait can only trigger on Day.
*/
protected boolean onlyOnDay = false;
/**
* The Time the Trait has cooldown. (in Seconds)
*/
protected int cooldownTime = 0;
/**
* The DisplayName to show.
*/
protected String displayName;
/**
* The Elevation the player has to be above
*/
protected int aboveElevation = Integer.MIN_VALUE;
/**
* The Elevation the player has to be below
*/
protected int belowElevation = Integer.MAX_VALUE;
/**
* Tells if the Trait can only be triggered in the Rain.
*/
protected boolean onlyInRain = false;
/**
* Tells if the Trait may only be used after player has been damaged.
* <br>Time in seconds.
* <br> onlyAfterDamage <= 0 = no check.
*/
protected int onlyAfterDamaged = -1;
/**
* Tells if the Trait may only be used after player has NOT been damaged.
* <br>Time in seconds.
* <br> onlyAfterNotDamaged <= 0 = no check.
*/
protected int onlyAfterNotDamaged = -1;
/**
* The maximal uplink time to show.
*/
protected long minUplinkShowTime = 3;
/**
* This disables the Cooldown notice.
*/
protected boolean disableCooldownNotice = false;
/**
* Tells the Trait can be activated only on certain blocks.
*/
protected final List<Material> onlyOnBlocks = new LinkedList<>();
/**
* Tells the Trait can NOT activated be activated on certain blocks.
*/
protected final List<Material> notOnBlocks = new LinkedList<>();
/**
* Tells the Trait can be activated while the player sneaks.
*/
protected boolean onlyWhileSneaking = false;
/**
* Tells the Trait can be activated while the player does NOT sneaks.
*/
protected boolean onlyWhileNotSneaking = false;
/**
* The needed Permission for the skill
*/
protected String neededPermission = "";
/**
* The Description of the Trait.
*/
protected String traitDiscription = "";
/**
* If the Trait is visible.
*/
protected boolean visible = true;
/**
* The holders of the Trait.
*/
protected final Set<AbstractTraitHolder> holders = new HashSet<>();
/**
* The ConfigTotal of the Trait
*/
protected TraitConfiguration currentConfig;
/**
* The Particle Effect to show on cast
*/
protected ParticleEffects onUseParticles = ParticleEffects.SPELL;
/**
* The map of Uplink that can be notified.
*/
protected final HashMap<String,Long> uplinkNotifyList = new HashMap<>();
/**
* Only on specific worlds.
*/
protected final Set<World> onlyInWorlds = new HashSet<>();
/**
* The Modifiers to use.
*/
protected final Set<TraitSituationModifier> modifiers = new HashSet<>();
/**
* The Skill level config.
*/
protected final SkillLevelConfig skillConfig = new SkillLevelConfig();
/**
* The max level for the Skill.
*/
protected int skillTreeMaxLevel = 1;
/**
* If the Skill is a permanent skill or if it needs to be skilled.
*/
protected boolean permanentSkill = true;
/**
* The slot for the SkillTree.
*/
protected int skillTreeSlot = -1;
/**
* Traits that are excluded for this Trait in the Skill-Tree.
*/
protected List<String> excludeOtherTraits = new ArrayList<>();
/**
* Traits this trait replaces.
*/
protected List<String> replacesOtherTraits = new ArrayList<>();
/**
* The Name of the SkillTree.
*/
protected String skillTreeName = GLOBAL_SKILL_TREE_NAME;
/**
* The symbol for the SkillTree.
*/
protected ItemStack skillTreeSymbol = new ItemStack(Material.BOOK);
@Override
public void addTraitHolder(AbstractTraitHolder abstractTraitHolder) {
this.holders.add(abstractTraitHolder);
}
@Override
public Set<AbstractTraitHolder> getTraitHolders() {
return holders;
}
@SuppressWarnings("deprecation")
@TraitConfigurationNeeded(fields = {
@TraitConfigurationField(fieldName = MIN_LEVEL_PATH, classToExpect = Integer.class, optional = true),
@TraitConfigurationField(fieldName = MAX_LEVEL_PATH, classToExpect = Integer.class, optional = true),
@TraitConfigurationField(fieldName = BIOME_PATH, classToExpect = List.class, optional = true),
@TraitConfigurationField(fieldName = ONLY_ON_BLOCK_PATH, classToExpect = List.class, optional = true),
@TraitConfigurationField(fieldName = ONLY_IN_WATER_PATH, classToExpect = Boolean.class, optional = true),
@TraitConfigurationField(fieldName = ONLY_ON_LAND_PATH, classToExpect = Boolean.class, optional = true),
@TraitConfigurationField(fieldName = ONLY_IN_LAVA_PATH, classToExpect = Boolean.class, optional = true),
@TraitConfigurationField(fieldName = ONLY_ON_SNOW, classToExpect = Boolean.class, optional = true),
@TraitConfigurationField(fieldName = ONLY_ON_DAY_PATH, classToExpect = Boolean.class, optional = true),
@TraitConfigurationField(fieldName = ONLY_IN_NIGHT_PATH, classToExpect = Boolean.class, optional = true),
@TraitConfigurationField(fieldName = ONLY_IN_RAIN_PATH, classToExpect = Boolean.class, optional = true),
@TraitConfigurationField(fieldName = ONLY_AFTER_DAMAGED_PATH, classToExpect = Integer.class, optional = true),
@TraitConfigurationField(fieldName = ONLY_AFTER_NOT_DAMAGED_PATH, classToExpect = Integer.class, optional = true),
@TraitConfigurationField(fieldName = ONLY_WHILE_SNEAKING_PATH, classToExpect = Boolean.class, optional = true),
@TraitConfigurationField(fieldName = ONLY_WHILE_NOT_SNEAKING_PATH, classToExpect = Boolean.class, optional = true),
@TraitConfigurationField(fieldName = COOLDOWN_TIME_PATH, classToExpect = Integer.class, optional = true),
@TraitConfigurationField(fieldName = ABOVE_ELEVATION_PATH, classToExpect = Integer.class, optional = true),
@TraitConfigurationField(fieldName = BELOW_ELEVATION_PATH, classToExpect = Integer.class, optional = true),
@TraitConfigurationField(fieldName = DISPLAY_NAME_PATH, classToExpect = String.class, optional = true),
@TraitConfigurationField(fieldName = DESCRIPTION_PATH, classToExpect = String.class, optional = true),
@TraitConfigurationField(fieldName = NEEDED_PERMISSION_PATH, classToExpect = String.class, optional = true),
@TraitConfigurationField(fieldName = MIN_UPLINK_SHOW_PATH, classToExpect = Integer.class, optional = true),
@TraitConfigurationField(fieldName = DISABLE_COOLDOWN_NOTICE_PATH, classToExpect = Boolean.class, optional = true),
@TraitConfigurationField(fieldName = MODIFIERS_PATH, classToExpect = List.class, optional = true),
@TraitConfigurationField(fieldName = ONLY_IN_WORLDS_PATH, classToExpect = List.class, optional = true),
@TraitConfigurationField(fieldName = ON_USE_PARTICLES_PATH, classToExpect = String.class, optional = true),
@TraitConfigurationField(fieldName = VISIBLE_PATH, classToExpect = Boolean.class, optional = true),
@TraitConfigurationField(fieldName = REPLACES_OTHER_TRAITS_PATH, classToExpect = List.class, optional = true),
@TraitConfigurationField(fieldName = SKILL_LEVELS_CONFIG_PATH, classToExpect = List.class, optional = true),
@TraitConfigurationField(fieldName = "skillLevelsPre", classToExpect = List.class, optional = true), //Deprication!
@TraitConfigurationField(fieldName = SKILL_TREE_PERMANENT_TRAIT_PATH, classToExpect = Boolean.class, optional = true),
@TraitConfigurationField(fieldName = SKILL_TREE_SLOT_PATH, classToExpect = Integer.class, optional = true),
@TraitConfigurationField(fieldName = SKILL_TREE_MATERIAL_PATH, classToExpect = Material.class, optional = true),
@TraitConfigurationField(fieldName = SKILL_TREE_DAMAGE_PATH, classToExpect = Integer.class, optional = true),
@TraitConfigurationField(fieldName = SKILL_TREE_MAX_LEVEL_PATH, classToExpect = Integer.class, optional = true),
@TraitConfigurationField(fieldName = SKILL_TREE_EXCLUDE_OTHERS_PATH, classToExpect = List.class, optional = true),
@TraitConfigurationField(fieldName = SKILL_TREE_NAME, classToExpect = String.class, optional = true),
})
@Override
public void setConfiguration(TraitConfiguration configMap) throws TraitConfigurationFailedException {
this.currentConfig = configMap;
this.visible = configMap.getAsBool(VISIBLE_PATH, true);
if(!TraitVisible.isVisible(this)) this.visible = false;
this.neededPermission = configMap.getAsString(NEEDED_PERMISSION_PATH, "");
this.cooldownTime = configMap.getAsInt(COOLDOWN_TIME_PATH, 0);
this.traitDiscription = configMap.getAsString(DESCRIPTION_PATH, "");
this.minimumLevel = configMap.getAsInt(MIN_LEVEL_PATH, -1);
this.maximumLevel = configMap.getAsInt(MAX_LEVEL_PATH, 90000000);
this.onUseParticles = configMap.getAsParticle(ON_USE_PARTICLES_PATH, ParticleEffects.SPELL);
this.onlyAfterDamaged = configMap.getAsInt(ONLY_AFTER_DAMAGED_PATH, -1);
this.onlyAfterNotDamaged = configMap.getAsInt(ONLY_AFTER_NOT_DAMAGED_PATH,-1);
this.aboveElevation = configMap.getAsInt(ABOVE_ELEVATION_PATH, Integer.MIN_VALUE);
this.belowElevation = configMap.getAsInt(BELOW_ELEVATION_PATH, Integer.MAX_VALUE);
this.skillTreeSlot = configMap.getAsInt(SKILL_TREE_SLOT_PATH, -1);
this.skillTreeMaxLevel = configMap.getAsInt(SKILL_TREE_MAX_LEVEL_PATH, 1);
//Backwards compability:
if(configMap.containsKey("skillLevelsPre")) this.skillConfig.parse(configMap.getAsStringList("skillLevelsPre"));
else this.skillConfig.parse(configMap.getAsStringList(SKILL_LEVELS_CONFIG_PATH));
this.permanentSkill = configMap.getAsBool(SKILL_TREE_PERMANENT_TRAIT_PATH, true);
this.onlyInWater = configMap.getAsBool(ONLY_IN_WATER_PATH, false);
this.onlyInLava = configMap.getAsBool(ONLY_IN_LAVA_PATH, false);
this.onlyInRain = configMap.getAsBool(ONLY_IN_RAIN_PATH, false);
this.onlyOnSnow = configMap.getAsBool(ONLY_ON_SNOW, false);
this.onlyOnLand = configMap.getAsBool(ONLY_ON_LAND_PATH, false);
this.onlyOnDay = configMap.getAsBool(ONLY_ON_DAY_PATH, false);
this.onlyInNight = configMap.getAsBool(ONLY_IN_NIGHT_PATH, false);
this.displayName = configMap.getAsString(DISPLAY_NAME_PATH, null);
this.onlyWhileSneaking = configMap.getAsBool(ONLY_WHILE_SNEAKING_PATH, false);
this.onlyWhileNotSneaking = configMap.getAsBool(ONLY_WHILE_NOT_SNEAKING_PATH, false);
this.minUplinkShowTime = configMap.getAsInt(MIN_UPLINK_SHOW_PATH, 3);
this.disableCooldownNotice = configMap.getAsBool(DISABLE_COOLDOWN_NOTICE_PATH, false);
//Read the Slot Item:
Material skillTreeMat = configMap.getAsMaterial(SKILL_TREE_MATERIAL_PATH, Material.BOOK);
short skillTreeDamage = (short)configMap.getAsInt(SKILL_TREE_DAMAGE_PATH, 0);
this.skillTreeName = configMap.getAsString(SKILL_TREE_NAME, GLOBAL_SKILL_TREE_NAME);
this.skillTreeSymbol = new ItemStack(skillTreeMat, 1, skillTreeDamage);
//Read replacements:
this.replacesOtherTraits.clear();
this.replacesOtherTraits.addAll(configMap.getAsStringList(REPLACES_OTHER_TRAITS_PATH));
//Read replacements:
this.excludeOtherTraits.clear();
this.excludeOtherTraits.addAll(configMap.getAsStringList(SKILL_TREE_EXCLUDE_OTHERS_PATH));
//Reads the biomes for the trait if present
if(configMap.containsKey(TraitWithRestrictions.BIOME_PATH)){
try{
List<String> stringBiomes = configMap.getAsStringList(TraitWithRestrictions.BIOME_PATH);
this.biomes.clear();
for(String biome : stringBiomes){
biome = biome.toUpperCase();
try{
Biome biom = Biome.valueOf(biome);
if(biome != null) biomes.add(biom);
}catch(Throwable exp){}
}
}catch(Exception exp){}
}
//Reads the blocks for the trait if present
if(configMap.containsKey(TraitWithRestrictions.ONLY_ON_BLOCK_PATH)){
try{
@SuppressWarnings("unchecked")
List<String> stringBlocks = (List<String>) configMap.get(TraitWithRestrictions.ONLY_ON_BLOCK_PATH);
this.onlyOnBlocks.clear();
for(String block : stringBlocks){
block = block.toUpperCase();
Material type = null;
//try String parsing
try{
type = Material.valueOf(block);
}catch(IllegalArgumentException exp){}
//try id parsing
if(type == null){
try{
int id = Integer.parseInt(block);
type = Material.getMaterial(id);
}catch(NumberFormatException exp){}
}
if(type != null){
onlyOnBlocks.add(type);
}
}
}catch(Exception exp){}
}
//Reads the blocks for the trait if present
if(configMap.containsKey(TraitWithRestrictions.NOT_ON_BLOCK_PATH)){
try{
@SuppressWarnings("unchecked")
List<String> stringBlocks = (List<String>) configMap.get(TraitWithRestrictions.NOT_ON_BLOCK_PATH);
this.notOnBlocks.clear();
for(String block : stringBlocks){
block = block.toUpperCase();
Material type = null;
//try String parsing
try{
type = Material.valueOf(block);
}catch(IllegalArgumentException exp){}
//try id parsing
if(type == null){
try{
int id = Integer.parseInt(block);
type = Material.getMaterial(id);
}catch(NumberFormatException exp){}
}
if(type != null){
notOnBlocks.add(type);
}
}
}catch(Exception exp){}
}
//Reads the Armor for the trait to wear
if(configMap.containsKey(TraitWithRestrictions.WEARING_PATH)){
try{
@SuppressWarnings("unchecked")
List<String> stringBlocks = (List<String>) configMap.get(TraitWithRestrictions.WEARING_PATH);
this.wearing.clear();
for(String armor : stringBlocks){
armor = armor.toUpperCase();
Material type = Material.valueOf(armor);
if(type != null){
wearing.add(type);
}
}
}catch(Exception exp){}
}
//read modifiers Path.
if(configMap.containsKey(MODIFIERS_PATH)){
List<String> mods = configMap.getAsStringList(MODIFIERS_PATH);
if(!mods.isEmpty()){
for(String toParse : mods){
TraitSituationModifier mod = ModifierFactory.generate(toParse);
//Give a debug message for wrongly entered mod.
if(mod == null){
String holders = "";
for(AbstractTraitHolder holder : getTraitHolders()) holders += " " + holder.getDisplayName();
String message = "Modifier: '" + toParse + "' of Trait: '" + getDisplayName()
+ "' from Holders: '" + holders + "' could not be parsed!";
plugin.logWarning(message);
}
if(mod != null) modifiers.add(mod);
}
}
}
//Read if only on World:
if(configMap.containsKey(ONLY_IN_WORLDS_PATH)){
this.onlyInWorlds.clear();
for(String worldName : configMap.getAsStringList(ONLY_IN_WORLDS_PATH)){
World world = Bukkit.getWorld(worldName);
if(world != null) onlyInWorlds.add(world);
}
}
}
/**
* Modifies the Value passed to the Level Mod.
*
* @param player to check
* @param value to modify
* @param the value to modify
*
* @return the modified value
*/
protected double modifyToPlayer(RaCPlayer player, double value, String toModify){
if(player == null) return value;
if(modifiers.isEmpty()) return value;
for(TraitSituationModifier mod : modifiers){
if(mod.canBeApplied(toModify, player)) value = mod.apply(player, value, this);
}
return value;
}
/**
* Modifies the Value passed to the Level Mod.
*
* @param player to check
* @param value to modify
* @param toModify the value to modify.
*
* @return the modified value
*/
protected int modifyToPlayer(RaCPlayer player, int value, String toModify){
if(player == null) return value;
if(modifiers.isEmpty()) return value;
for(TraitSituationModifier mod : modifiers){
if(mod.canBeApplied(toModify, player)) value = mod.apply(player, value, this);
}
return value;
}
/**
* Modifies the Value passed to the Level Mod.
*
* @param player to check
* @param value to modify
*
* @return the modified value
*/
protected double getModValue(RaCPlayer player, String toModify){
if(player == null) return 1;
if(modifiers.isEmpty()) return 1;
double value = 1;
for(TraitSituationModifier mod : modifiers){
if(mod.canBeApplied(toModify, player)) value = mod.apply(player, value, this);
}
return value;
}
@Override
public TraitConfiguration getCurrentconfig(){
return currentConfig;
}
@Override
public void deInit(){
HandlerList.unregisterAll(this);
}
/**
* {@inheritDoc}
*
* <br>
* <br>To override, use {@link #getAdditionalOptionalConfigFields()}.
* <br>This adds the optional Fields to the one added here.
*/
@Override
public final List<String> getOptionalConfigFields(){
List<TraitConfigurationField> configFields = TraitConfigParser.getAllTraitConfigFieldsOfTrait(this);
List<String> optionalFields = new LinkedList<String>();
for(TraitConfigurationField field : configFields){
if(field.optional() == true){
optionalFields.add(field.fieldName());
}
}
return optionalFields;
}
@Override
public final String getPrettyConfiguration() {
if("".equals(traitDiscription)) return getPrettyConfigIntern();
return ChatColor.translateAlternateColorCodes('&', traitDiscription);
}
/**
* Returns the Pretty config.
* @return pretty name
*/
protected abstract String getPrettyConfigIntern();
@Override
public TraitRestriction checkRestrictions(EventWrapper wrapper) {
RaCPlayer player = wrapper.getPlayer();
if(player == null) return TraitRestriction.Unknown;
//players not online will most likely fail everything.
if(!player.isOnline()) return TraitRestriction.Unknown;
String playerName = player.getName();
//check for Level-Range
int playerLevel = player.getLevelManager().getCurrentLevel();
if(playerLevel < minimumLevel) return TraitRestriction.MinimumLevel;
if(playerLevel > maximumLevel) return TraitRestriction.MaximumLevel;
//Check if is skilled in tree:
if(!permanentSkill && plugin.getConfigManager().getGeneralConfig().isConfig_useSkillSystem()){
if(player.getSkillTreeManager().getLevel(this) <= 0) return TraitRestriction.NotSkilled;
}
//Save some generic Block infos.
Location playerLocation = player.getPlayer().getLocation();
boolean isOutOfWorld = playerLocation.getY() < 0 || playerLocation.getY() > 256;
Block feetBlock = isOutOfWorld ? null : playerLocation.getBlock();
Block locBlock = isOutOfWorld ? null : feetBlock.getRelative(BlockFace.DOWN);
Material feetType = feetBlock == null ? Material.AIR : feetBlock.getType();
Material belowFeetType = locBlock == null ? Material.AIR : locBlock.getType();
//check for allowed Biomes
Biome currentBiome = locBlock == null ? Biome.SKY : locBlock.getBiome();
if(!biomes.isEmpty() && !biomes.contains(currentBiome)) return TraitRestriction.Biomes;
//Check if on correct world:
if(!onlyInWorlds.isEmpty()){
if(!onlyInWorlds.contains(player.getWorld())){
triggerButHasRestriction(TraitRestriction.OnlyInWorld, wrapper);
return TraitRestriction.OnlyInWorld;
}
}
//Check if player is in water
if(!isOutOfWorld && onlyInWater){
if(feetType != Material.WATER && feetType != Material.STATIONARY_WATER){
triggerButHasRestriction(TraitRestriction.OnlyInWater, wrapper);
return TraitRestriction.OnlyInWater;
}
}
//Sneaking
if(onlyWhileSneaking){
if(!player.getPlayer().isSneaking()) {
triggerButHasRestriction(TraitRestriction.OnlyWhileSneaking, wrapper);
return TraitRestriction.OnlyWhileSneaking;
}
}
//Not sneaking
if(onlyWhileNotSneaking){
if(player.getPlayer().isSneaking()) {
triggerButHasRestriction(TraitRestriction.OnlyWhileNotSneaking, wrapper);
return TraitRestriction.OnlyWhileNotSneaking;
}
}
//check if player is on land
if(!isOutOfWorld && onlyOnLand){
if(feetType == Material.WATER || feetType == Material.STATIONARY_WATER){
triggerButHasRestriction(TraitRestriction.OnlyOnLand, wrapper);
return TraitRestriction.OnlyOnLand;
}
}
//check permission
if(!neededPermission.isEmpty()){
if(!plugin.getPermissionManager().checkPermissionsSilent(wrapper.getPlayer().getPlayer(), neededPermission)){
triggerButHasRestriction(TraitRestriction.NeededPermission, wrapper);
return TraitRestriction.NeededPermission;
}
}
//check if player is in lava
if(!isOutOfWorld && onlyInLava){
if(!(feetType == Material.LAVA || feetType == Material.STATIONARY_LAVA)){
triggerButHasRestriction(TraitRestriction.OnlyInLava, wrapper);
return TraitRestriction.OnlyInLava;
}
}
//check if player is on Snow
if(!isOutOfWorld && onlyOnSnow){
if(!(feetType == Material.SNOW || feetType == Material.SNOW_BLOCK
|| belowFeetType == Material.SNOW || belowFeetType == Material.SNOW_BLOCK)){
triggerButHasRestriction(TraitRestriction.OnlyOnSnow, wrapper);
return TraitRestriction.OnlyOnSnow;
}
}
//check if player is in Rain
if(!isOutOfWorld && onlyInRain){
if(!wrapper.getWorld().hasStorm()) return TraitRestriction.OnlyInRain;
int ownY = feetBlock.getY();
int highestY = feetBlock.getWorld().getHighestBlockYAt(feetBlock.getX(), feetBlock.getZ());
//This means having a roof over oneself
if(ownY != highestY) {
triggerButHasRestriction(TraitRestriction.OnlyInRain, wrapper);
return TraitRestriction.OnlyInRain;
}
}
//checking for wearing
if(!wearing.isEmpty()){
boolean found = false;
for(Material mat : wearing){
found = false;
for(ItemStack item : player.getPlayer().getInventory().getArmorContents()){
if(item == null) continue;
if(mat == item.getType()){
found = true;
break;
}
}
if(!found) {
triggerButHasRestriction(TraitRestriction.Wearing, wrapper);
return TraitRestriction.Wearing;
}
}
}
//check above elevation
if(!isOutOfWorld && aboveElevation != Integer.MIN_VALUE){
if(feetBlock.getY() <= aboveElevation) {
triggerButHasRestriction(TraitRestriction.AboveLevitation, wrapper);
return TraitRestriction.AboveLevitation;
}
}
//check below elevation
if(!isOutOfWorld && belowElevation != Integer.MAX_VALUE){
if(feetBlock.getY() >= belowElevation) {
triggerButHasRestriction(TraitRestriction.BelowLevitation, wrapper);
return TraitRestriction.BelowLevitation;
}
}
//check onlyAfterDamaged
if(onlyAfterDamaged > 0){
int lastDamage = PlayerLastDamageListener.getTimePassedSinceLastDamageInSeconds(playerName);
if(lastDamage > onlyAfterDamaged) {
triggerButHasRestriction(TraitRestriction.OnlyAfterDamage, wrapper);
return TraitRestriction.OnlyAfterDamage;
}
}
//check onlyAfterDamaged
if(onlyAfterNotDamaged > 0){
int lastDamage = PlayerLastDamageListener.getTimePassedSinceLastDamageInSeconds(playerName);
if(onlyAfterNotDamaged > lastDamage) {
triggerButHasRestriction(TraitRestriction.OnlyAfterNotDamage, wrapper);
return TraitRestriction.OnlyAfterNotDamage;
}
}
//check blocks on
if(!isOutOfWorld && !onlyOnBlocks.isEmpty()){
if(!onlyOnBlocks.contains(belowFeetType)) {
triggerButHasRestriction(TraitRestriction.OnlyOnBlock, wrapper);
return TraitRestriction.OnlyOnBlock;
}
}
//check blocks on
if(!isOutOfWorld && !notOnBlocks.isEmpty()){
if(notOnBlocks.contains(belowFeetType)) {
triggerButHasRestriction(TraitRestriction.NotOnBlock, wrapper);
return TraitRestriction.NotOnBlock;
}
}
//check cooldown
//only check if the Trait really HAS cooldown!
if(cooldownTime > 0){
String cooldownName = getCooldownName();
int playerUplinkTime = CooldownApi.getCooldownOfPlayer(playerName, cooldownName);
if(playerUplinkTime > 0){
if(!triggerButHasUplink(wrapper)){
triggerButHasRestriction(TraitRestriction.Cooldown, wrapper);
if(notifyTriggeredUplinkTime(wrapper)){
//if notices are disabled, we don't need to do anything here.
if(disableCooldownNotice) return TraitRestriction.Cooldown;
//we still check to not spam players.
long lastNotified = uplinkNotifyList.containsKey(playerName)
? uplinkNotifyList.get(playerName)
: 0;
long maxTime = minUplinkShowTime * 1000;
if(new Date().after(new Date(lastNotified + maxTime))){
LanguageAPI.sendTranslatedMessage(player.getPlayer(), trait_cooldown,
"seconds", String.valueOf(playerUplinkTime),
"name", getDisplayName());
uplinkNotifyList.put(playerName, new Date().getTime());
}
}
}
return TraitRestriction.Cooldown;
}
}
//Only check if we really need. Otherwise we would use resources we don't need
if(onlyOnDay || onlyInNight){
//Daytime check
//We need to add a 6 hour offset, since MC seems to be starting at 6am.
int hour = ((int) (wrapper.getWorld().getTime() / 1000l) + 6) % 24;
boolean isNight = hour > 18 || hour < 6;
boolean isDay = hour > 6 && hour < 18;
//Check day
if(onlyOnDay && isNight && !onlyInNight){
triggerButHasRestriction(TraitRestriction.OnlyOnDay, wrapper);
return TraitRestriction.OnlyOnDay;
}
//Check night
if(onlyInNight && isDay && !onlyOnDay){
triggerButHasRestriction(TraitRestriction.OnlyInNight, wrapper);
return TraitRestriction.OnlyOnDay;
}
}
//check for further restrictions.
TraitRestriction furtherRestriction = checkForFurtherRestrictions(wrapper);
if(furtherRestriction != null && furtherRestriction != TraitRestriction.None){
triggerButHasRestriction(furtherRestriction, wrapper);
return furtherRestriction;
}
//Check if someone else does not want to trigger:
PreTraitTriggerEvent event = new PreTraitTriggerEvent(player.getPlayer(), this, player.getWorld());
Bukkit.getPluginManager().callEvent(event);
if(event.isCancelled()) return event.getRestriction();
return TraitRestriction.None;
}
/**
* This is ment to be overritten.
* This is called AFTER all other restrictions are Checked.
* If any TraitRestriction is passed, the Trait may NOT be used.
* If null is returned, no restrictions is met.
*
* @param wrapper to evaluate.
*
* @return a {@link TraitRestriction} if present or null if not.
*/
protected TraitRestriction checkForFurtherRestrictions(EventWrapper wrapper){
return null;
}
@Override
public boolean isStackable(){
return true;
}
@Override
public int getMaxUplinkTime() {
return cooldownTime;
}
@Override
public boolean triggerButHasUplink(EventWrapper wrapper) {
return false;
}
@Override
public String toString(){
return getDisplayName();
}
/**
* Can and should be overriden.
*/
@Override
public boolean notifyTriggeredUplinkTime(EventWrapper wrapper) {
return true;
}
@Override
public String getDisplayName() {
return ChatColor.translateAlternateColorCodes('&', displayName == null ? getName() : displayName);
}
@Override
public boolean isInLevelRange(int level) {
if(level < minimumLevel) return false;
if(level > maximumLevel) return false;
return true;
}
@Override
public boolean isVisible(){
return visible;
}
@Override
public void triggerButHasRestriction(TraitRestriction restriction, EventWrapper wrapper){
}
/**
* This is ment to be overriden!
*/
@Override
public boolean isBindable() {
return false;
}
@Override
public String getCooldownName() {
return "trait." + getDisplayName();
}
@Override
public int getMaxLevel() {
return maximumLevel;
}
@Override
public int getMinLevel() {
return minimumLevel;
}
/**
* This is indicating that a player uses the bound
* @param using the player that is using this Trait.
*/
@Override
public final TraitResults triggerOnBind(RaCPlayer using){
if(!isBindable()) return TraitResults.False();
if(!using.isOnline()) return TraitResults.False();
EventWrapper wrapper = EventWrapperFactory.buildOnlyWithplayer(using);
if(this.checkRestrictions(wrapper) != TraitRestriction.None) {
return TraitResults.False();
}
//Finally trigger!
TraitResults result = bindCastIntern(using);
//If triggered -> Fire trigger event!
if(result.isTriggered()){
PostTraitTriggerEvent event = new PostTraitTriggerEvent(wrapper, this);
Bukkit.getPluginManager().callEvent(event);
}
return result;
}
/**
* Ment to be overriten.
* Here Preconditions are already checked!
*
* @param player to cast.
*/
protected TraitResults bindCastIntern(RaCPlayer player){
return TraitResults.False();
}
/**
* Returns if the Player passed is contained in this trait.
*
* @param player to check
* @return true if contained.
*/
public boolean hasPlayer(RaCPlayer player){
return TraitHolderCombinder.checkContainer(player, this);
}
@Override
public int compareTo(Trait other) {
return getDisplayName().compareTo(other.getDisplayName());
}
@Override
public int getSkillPointCost(int level) {
return skillConfig.getPointsForLevel(level);
}
@Override
public int getSkillMinLevel(int level) {
return skillConfig.getMinLevel(level);
}
@Override
public String getSkillTreeName() {
return skillTreeName;
}
@Override
public boolean isPermanentSkill() {
return permanentSkill;
}
@Override
public int getSkillTreePlace(){
return skillTreeSlot;
}
@Override
public ItemStack getSkillTreeSymbol(){
return skillTreeSymbol.clone();
}
@Override
public int getSkillMaxLevel() {
return skillTreeMaxLevel;
}
@Override
public List<String> getSkillTreePrequisits(int level) {
return skillConfig.getTraitPreForLevel(level);
}
@Override
public List<String> getExcludesOtherTraits() {
return excludeOtherTraits;
}
@Override
public List<String> getReplacesOtherTraits() {
return replacesOtherTraits;
}
}