/*
* Copyright 2015 Demigods RPG
* Copyright 2015 Alexander Chauncey
* Copyright 2015 Alex Bennett
*
* 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 com.demigodsrpg.ability;
import com.demigodsrpg.aspect.Aspect;
import com.demigodsrpg.util.ZoneUtil;
import com.demigodsrpg.util.misc.StringUtil2;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import javax.annotation.Nullable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class AbilityRegistry implements Listener {
// FIXME Do we really need two collections for the same data? This is expensive...
private static AbilityCasterProvider CASTER_PROVIDER;
private static CooldownHandler COOLDOWNS;
private boolean NO_COST_ASPECT_MODE;
private static final ConcurrentMap<String, AbilityMetaData> REGISTERED_COMMANDS = new ConcurrentHashMap<>();
private static final Multimap<String, AbilityMetaData> REGISTERED_ABILITIES = Multimaps.newListMultimap(new ConcurrentHashMap<>(), () -> new ArrayList<>(0));
public AbilityRegistry(AbilityCasterProvider casterProvider, CooldownHandler cooldowns, boolean noCostAspectMode) {
CASTER_PROVIDER = casterProvider;
COOLDOWNS = cooldowns;
NO_COST_ASPECT_MODE = noCostAspectMode;
}
@EventHandler(priority = EventPriority.LOWEST)
private void onEvent(PlayerInteractEvent event) {
switch (event.getAction()) {
case LEFT_CLICK_BLOCK:
case LEFT_CLICK_AIR:
return;
}
for (AbilityMetaData ability : REGISTERED_ABILITIES.get(event.getClass().getName())) {
try {
AbilityCaster model = CASTER_PROVIDER.fromPlayer(event.getPlayer());
if (processAbility1(model, ability)) {
Object rawResult = ability.getMethod().invoke(ability.getAspect(), event);
processAbility2(event.getPlayer(), model, ability, rawResult);
event.setCancelled(true);
return;
}
} catch (Exception oops) {
oops.printStackTrace();
}
}
}
@EventHandler(priority = EventPriority.LOWEST)
private void onEvent(EntityDamageByEntityEvent event) {
for (AbilityMetaData ability : REGISTERED_ABILITIES.get(event.getClass().getName())) {
try {
if (event.getDamager() instanceof Player) {
Player player = (Player) event.getDamager();
AbilityCaster model = CASTER_PROVIDER.fromPlayer(player);
if (processAbility1(model, ability)) {
Object rawResult = ability.getMethod().invoke(ability.getAspect(), event);
processAbility2(player, model, ability, rawResult);
}
}
} catch (Exception oops) {
oops.printStackTrace();
}
}
}
@EventHandler(priority = EventPriority.HIGHEST)
private void bindCommands(PlayerCommandPreprocessEvent event) {
String message = event.getMessage();
message = message.substring(1);
String[] args = message.split("\\s+");
Player player = event.getPlayer();
if (!ZoneUtil.inNoDGZone(event.getPlayer().getLocation())) {
// Process the command
try {
if (args.length == 2 && "info".equals(args[1])) {
if (abilityInfo(player, args[0].toLowerCase())) {
//CONSOLE.info(event.getPlayer().getName() + " used the command: /" + message);
event.setCancelled(true);
return;
}
}
if (bindAbility(player, args[0].toLowerCase())) {
//CONSOLE.info(event.getPlayer().getName() + " used the command: /" + message);
event.setCancelled(true);
}
} catch (Exception errored) {
// Not a command
errored.printStackTrace();
}
}
}
boolean abilityInfo(Player player, String command) {
for (AbilityMetaData ability : REGISTERED_ABILITIES.values()) {
if (ability.getCommand().equals(command)) {
player.sendMessage(StringUtil2.chatTitle(ability.getName()));
player.sendMessage(" - Aspect: " + ability.getAspect().getGroup().getColor() + ability.getAspect().name());
player.sendMessage(" - Type: " + StringUtil2.beautify(ability.getType().name()));
if (!ability.getType().equals(Ability.Type.PASSIVE)) {
player.sendMessage(" - Cost: " + ability.getCost());
}
if (ability.getCooldown() > 0) {
player.sendMessage(" - Cooldown (ms): " + ability.getCooldown());
}
player.sendMessage(ability.getInfo());
return true;
}
}
return false;
}
boolean bindAbility(Player player, String command) {
// Is this a correct command?
if (!REGISTERED_COMMANDS.keySet().contains(command)) {
return false;
}
// Can't bind to air.
if (player.getItemInHand() == null || Material.AIR.equals(player.getItemInHand().getType())) {
abilityInfo(player, command);
return true;
}
AbilityCaster model = CASTER_PROVIDER.fromPlayer(player);
Material material = player.getItemInHand().getType();
AbilityMetaData bound = model.getBound(material);
if (bound != null) {
if (!bound.getCommand().equals(command)) {
player.sendMessage(ChatColor.RED + "This item already has /" + bound.getCommand() + " bound to it.");
return true;
} else {
model.unbind(bound);
player.sendMessage(ChatColor.YELLOW + bound.getName() + " has been unbound.");
return true;
}
} else {
AbilityMetaData ability = fromCommand(command);
if (ability.getCommand().equals(command) && model.getAspects().contains(ability.getAspect().name()) && ability.getCommand().equals(command)) {
model.bind(ability, material);
player.sendMessage(ChatColor.YELLOW + ability.getName() + " has been bound to " + StringUtil2.beautify(material.name()) + ".");
return true;
}
}
return false;
}
boolean processAbility1(AbilityCaster model, AbilityMetaData ability) {
if (ZoneUtil.inNoDGZone(model.getLocation())) return false;
if (!ability.getType().equals(Ability.Type.PASSIVE)) {
if ((ability.getType().equals(Ability.Type.OFFENSIVE) || ability.getType().equals(Ability.Type.ULTIMATE)) && ZoneUtil.inNoPvpZone(model.getLocation())) {
return false;
}
if (model.getBound(ability) == null) {
return false;
}
if (!model.getOfflinePlayer().getPlayer().getItemInHand().getType().equals(model.getBound(ability))) {
return false;
}
double cost = ability.getCost();
if (!NO_COST_ASPECT_MODE && model.getFavor() < cost) {
model.getOfflinePlayer().getPlayer().sendMessage(ChatColor.YELLOW + ability.getName() + " requires more favor.");
return false;
}
if (COOLDOWNS.hasDelay(model, ability)) {
return false;
}
if (COOLDOWNS.hasCooldown(model, ability)) {
model.getOfflinePlayer().getPlayer().sendMessage(ChatColor.YELLOW + ability.getName() + " is on a cooldown.");
return false;
}
}
return true;
}
void processAbility2(Player player, AbilityCaster model, AbilityMetaData ability, @Nullable Object rawResult) {
// Check for result
if (rawResult == null) {
throw new NullPointerException("An ability (" + ability.getName() + ") returned null while casting.");
}
try {
// Process result
AbilityResult result = (AbilityResult) rawResult;
switch (result) {
case SUCCESS: {
break;
}
case NO_TARGET_FOUND: {
player.sendMessage(ChatColor.YELLOW + "No target found.");
return;
}
case OTHER_FAILURE: {
return;
}
}
// Process ability
double cost = ability.getCost();
long delay = ability.getDelay();
long cooldown = ability.getCooldown();
if (delay > 0) {
COOLDOWNS.delay(model, ability);
}
if (cooldown > 0) {
COOLDOWNS.cooldown(model, ability);
}
if (!NO_COST_ASPECT_MODE && cost > 0) {
model.setFavor(model.getFavor() - cost);
}
} catch (Exception ignored) {
}
}
public List<AbilityMetaData> getAbilities(Aspect aspect) {
Class<? extends Aspect> deityClass = aspect.getClass();
List<AbilityMetaData> abilityMetaDatas = new ArrayList<>();
for (Method method : deityClass.getMethods()) {
if (method.isAnnotationPresent(Ability.class)) {
Ability ability = method.getAnnotation(Ability.class);
abilityMetaDatas.add(new AbilityMetaData(aspect, method, ability));
}
}
return abilityMetaDatas;
}
public void registerAbilities(Aspect[] aspects) {
for (Aspect aspect : aspects) {
Class<? extends Aspect> deityClass = aspect.getClass();
for (Method method : deityClass.getMethods()) {
if (method.isAnnotationPresent(Ability.class)) {
Ability ability = method.getAnnotation(Ability.class);
register(aspect, method, ability);
}
}
}
}
@SuppressWarnings("unchecked")
void register(Aspect aspect, Method method, Ability ability) {
if (ability.placeholder()) return;
Class<?>[] paramaters = method.getParameterTypes();
try {
if (paramaters.length < 1) {
//CONSOLE.severe("An ability (" + ability.name() + ") tried to register without any parameters.");
return;
}
Class<? extends Event> eventClass = (Class<? extends Event>) paramaters[0];
AbilityMetaData data = new AbilityMetaData(aspect, method, ability);
REGISTERED_ABILITIES.put(eventClass.getName(), data);
if (!"".equals(data.getCommand())) {
REGISTERED_COMMANDS.put(data.getCommand(), data);
}
} catch (Exception oops) {
oops.printStackTrace();
}
}
public static AbilityMetaData fromCommand(String commandName) {
if (REGISTERED_COMMANDS.containsKey(commandName)) {
return REGISTERED_COMMANDS.get(commandName);
}
return null;
}
}