Package net.aufdemrand.denizen.scripts.triggers.core

Source Code of net.aufdemrand.denizen.scripts.triggers.core.ChatTrigger$ChatContext

package net.aufdemrand.denizen.scripts.triggers.core;

import net.aufdemrand.denizen.Settings;
import net.aufdemrand.denizen.objects.*;
import net.aufdemrand.denizen.npc.traits.ChatbotTrait;
import net.aufdemrand.denizen.npc.traits.TriggerTrait;
import net.aufdemrand.denizen.scripts.commands.core.DetermineCommand;
import net.aufdemrand.denizen.scripts.containers.core.InteractScriptContainer;
import net.aufdemrand.denizen.scripts.containers.core.InteractScriptHelper;
import net.aufdemrand.denizen.scripts.triggers.AbstractTrigger;
import net.aufdemrand.denizen.tags.TagManager;
import net.aufdemrand.denizen.utilities.DenizenAPI;
import net.aufdemrand.denizen.utilities.Utilities;
import net.aufdemrand.denizen.utilities.entity.Rotation;
import net.aufdemrand.denizen.utilities.debugging.dB;

import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.event.player.PlayerChatEvent;

import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@SuppressWarnings("deprecation")
public class ChatTrigger extends AbstractTrigger implements Listener {

    final static Pattern triggerPattern = Pattern.compile("/([^/]*)/");

    @Override
    public void onEnable() {
        Bukkit.getServer().getPluginManager().registerEvents(this, DenizenAPI.getCurrentInstance());
    }

    // Technically defined in TriggerTrait, but placing here instead.
    // <--[action]
    // @Actions
    // chat
    //
    // @Triggers when a player chats to the NPC.
    //
    // @Context
    // <context.message> returns the triggering message
    // <context.keyword> returns the keyword matched by a RegEx trigger
    //
    // @Determine
    // "CANCELLED" to stop the player from chatting.
    // Element(String) to change the message.
    //
    // -->
    public ChatContext process(Player player, String message) {

        // Check if there is an NPC within range of a player to chat to.
        dNPC npc = Utilities.getClosestNPC_ChatTrigger(player.getLocation(), 25);
        dPlayer denizenPlayer = dPlayer.mirrorBukkitPlayer(player);

        // No NPC? Nothing else to do here.
        if (npc == null) return new ChatContext(false);

        // If the NPC doesn't have triggers, or the triggers are not enabled, then
        // just return false.
        if (!npc.getCitizen().hasTrait(TriggerTrait.class)) return new ChatContext(false);
        if (!npc.getCitizen().getTrait(TriggerTrait.class).isEnabled(name)) return new ChatContext(false);

        // Check range
        if (npc.getTriggerTrait().getRadius(name) < npc.getLocation().distance(player.getLocation()))
            return new ChatContext(false);

        // The Denizen config can require some other criteria for a successful chat-with-npc.
        // Should we check 'line of sight'? Players cannot talk to NPCs through walls
        // if enabled. Should the Player chat only when looking at the NPC? This may
        // reduce accidental chats with NPCs.


        if (Settings.chatMustSeeNPC())
            if (!player.hasLineOfSight(npc.getEntity())) return new ChatContext(false);

        if (Settings.chatMustLookAtNPC())
            if (!Rotation.isFacingEntity(player, npc.getEntity(), 45)) return new ChatContext(false);

        Boolean ret = false;

        // Denizen should be good to interact with. Let's get the script.
        InteractScriptContainer script = npc.getInteractScript(denizenPlayer, ChatTrigger.class);

        Map<String, dObject> context = new HashMap<String, dObject>();
        context.put("message", new Element(message));

        //
        // Fire the Actions!
        //

        // If engaged or not cool, calls On Unavailable, if cool, calls On Chat
        // If available (not engaged, and cool) sets cool down and returns true.
        TriggerTrait.TriggerContext trigger = npc.getTriggerTrait()
                .trigger(ChatTrigger.this, denizenPlayer, context);

        // Return false if determine cancelled
        if (trigger.hasDetermination()) {
            if (trigger.getDetermination().equalsIgnoreCase("cancelled")) {
                // Mark as handled, the event will cancel.
                return new ChatContext(true);
            }
        }

        // Return false if trigger was unable to fire
        if (!trigger.wasTriggered()) {
            // If the NPC is not interact-able, Settings may allow the chat to filter
            // through. Check the Settings if this is enabled.
            if (Settings.chatGloballyIfUninteractable()) {
                dB.echoDebug(script, ChatColor.YELLOW + "Resuming. " + ChatColor.WHITE
                        + "The NPC is currently cooling down or engaged.");
                return new ChatContext(false);

            } else
                ret = true;
        }

        // Debugger
        dB.report(script, name, aH.debugObj("Player", player.getName())
                + aH.debugObj("NPC", npc.toString())
                + aH.debugObj("Radius(Max)", npc.getLocation().distance(player.getLocation())
                + "(" + npc.getTriggerTrait().getRadius(name) + ")")
                + aH.debugObj("Trigger text", message)
                + aH.debugObj("LOS", String.valueOf(player.hasLineOfSight(npc.getEntity())))
                + aH.debugObj("Facing", String.valueOf(Rotation.isFacingEntity(player, npc.getEntity(), 45))));

        // Change the text if it's in the determination
        if (trigger.hasDetermination()) {
            message = trigger.getDetermination();
        }

        if (script == null) return new ChatContext(message, false);

        // Check if the NPC has Chat Triggers for this step.
        if (!script.containsTriggerInStep(
                InteractScriptHelper.getCurrentStep(denizenPlayer,
                        script.getName()),  ChatTrigger.class)) {

            // If this is a Chatbot, make it chat anything it wants if
            // it has no chat triggers for this step
            if (npc.getCitizen().hasTrait(ChatbotTrait.class)) {
                Utilities.talkToNPC(message, denizenPlayer, npc, Settings.chatToNpcOverhearingRange());
                npc.getCitizen().getTrait(ChatbotTrait.class).chatTo(player, message);
                return new ChatContext(false);
            }

            // No chat trigger for this step.. do we chat globally, or to the NPC?
            else if (!Settings.chatGloballyIfNoChatTriggers()) {
                dB.echoDebug(script, player.getName() + " says to "
                        + npc.getNicknameTrait().getNickname() + ", " + message);
                return new ChatContext(false);
            }

            else return new ChatContext(message, ret);
        }


        // Parse the script and match Triggers.. if found, cancel the text! The
        // parser will take care of everything else.
        String id = null;
        boolean matched = false;
        String replacementText = null;
        String regexId = null;
        String regexMessage = null;

        // Use TreeMap to sort chat triggers alphabetically
        TreeMap<String, String> idMap = new TreeMap<String, String>();
        idMap.putAll(script.getIdMapFor(ChatTrigger.class, denizenPlayer));

        if (!idMap.isEmpty()) {
            // Iterate through the different id entries in the step's chat trigger
            for (Map.Entry<String, String> entry : idMap.entrySet()) {

                // Check if the chat trigger specified in the specified id's 'trigger:' key
                // matches the text the player has said
                String triggerText = TagManager.tag(denizenPlayer, npc, entry.getValue());
                Matcher matcher = triggerPattern.matcher(triggerText);
                while (matcher.find ()) {
                    if (!script.checkSpecificTriggerScriptRequirementsFor(ChatTrigger.class,
                            denizenPlayer, npc, entry.getKey())) continue;
                    String keyword = TagManager.tag(denizenPlayer, npc, matcher.group().replace("/", ""));
                    String[] split = keyword.split("\\\\\\+REPLACE:", 2);
                    String replace = null;
                    if (split.length == 2) {
                        keyword = split[0];
                        replace = split[1];
                    }
                    // Check if the trigger is REGEX, but only if we don't have a REGEX
                    // match already (thus using alphabetical priority for triggers)
                    if(regexId == null && isKeywordRegex(keyword)) {
                        Pattern    pattern = Pattern.compile(keyword.substring(6));
                        Matcher m = pattern.matcher(message);
                        if (m.find()) {
                            // REGEX matches are left for last, so save it in case non-REGEX
                            // matches don't exist
                            regexId = entry.getKey();
                            regexMessage = triggerText.replace(matcher.group(), m.group());
                            dB.log("entry value: " + triggerText + "  keyword: " + keyword + "  m.group: " + m.group() + "  matcher.group: " + matcher.group());
                            context.put("keyword", new Element(m.group()));
                            if (replace != null)
                                regexMessage = replace;
                        }
                    }
                    else if (isKeywordStrict(keyword)) {
                        if (message.toUpperCase().equalsIgnoreCase(keyword.toUpperCase()))
                        {
                            // Trigger matches
                            id = entry.getKey();
                            replacementText = triggerText.replace("/", "");
                            matched = true;
                            if (replace != null)
                                replacementText = replace;
                        }
                    }
                    else if (message.toUpperCase().contains(keyword.toUpperCase()))
                    {
                        // Trigger matches
                        id = entry.getKey();
                        replacementText = triggerText.replace("/", "");
                        matched = true;
                        if (replace != null)
                            replacementText = replace;
                    }
                }
                if (matched) break;
            }
        }

        if (!matched && regexId != null) {
            id = regexId;
            replacementText = regexMessage;
        }

        // If there was a match, the id of the match should have been returned.
        if (id != null) {
            Utilities.talkToNPC(replacementText, denizenPlayer, npc, Settings.chatToNpcOverhearingRange());
            parse(npc, denizenPlayer, script, id, context);
            return new ChatContext(true);
        }
        else {
            // If this is a Chatbot, make it chat anything it wants if
            // none of its chat triggers worked
            if (npc.getCitizen().hasTrait(ChatbotTrait.class)) {
                Utilities.talkToNPC(message, denizenPlayer, npc, Settings.chatToNpcOverhearingRange());
                npc.getCitizen().getTrait(ChatbotTrait.class).chatTo(player, message);
                return new ChatContext(true);
            }
            else if (!Settings.chatGloballyIfFailedChatTriggers ()) {
                Utilities.talkToNPC(message, denizenPlayer, npc, Settings.chatToNpcOverhearingRange());
                return new ChatContext(true);
            }
            // No matching chat triggers, and the config.yml says we
            // should just ignore the interaction...
        }
        return new ChatContext(message, ret);
    }


    @EventHandler
    public void asyncChatTrigger(final AsyncPlayerChatEvent event) {
        if (event.isCancelled()) return;

        // Return if "Use asynchronous event" is false in config file
        if (!Settings.chatAsynchronous()) return;

        Callable<ChatContext> call = new Callable<ChatContext>() {
            public ChatContext call() {
                return process(event.getPlayer(), event.getMessage());
            }
        };

        Boolean cancelled = false;

        try {
            // Determine if the chat should be cancelled
            cancelled = event.isAsynchronous()
                    ? Bukkit.getScheduler().callSyncMethod(DenizenAPI.getCurrentInstance(), call).get().wasTriggered()
                    : call.call().wasTriggered();

            // Handle any changes with the ChatContext message
            if (call.call().hasChanges())
                event.setMessage(call.call().getChanges());

        } catch (InterruptedException e) {
            // This is normal -- probably.
            // dB.echoError(e);
        } catch (ExecutionException e) {
            dB.echoError(e);
        } catch (Exception e) {
            dB.echoError(e);
        }


        if (cancelled)
            event.setCancelled(true);
    }

    @EventHandler
    public void syncChatTrigger(final PlayerChatEvent event) {
        if (event.isCancelled()) return;

        // Return if "Use asynchronous event" is true in config file
        if (Settings.chatAsynchronous()) return;

        ChatContext chat = process(event.getPlayer(), event.getMessage());

        if (chat.wasTriggered())
            event.setCancelled(true);


        if (chat.hasChanges())
            event.setMessage(chat.getChanges());
    }

    private boolean isKeywordRegex (String keyWord) {
        return keyWord.toUpperCase().startsWith("REGEX:");
    }

    private boolean isKeywordStrict (String keyWord) {
        return keyWord.toUpperCase().startsWith("STRICT:");
    }


    /**
     * Contains whether the chat trigger successfully 'triggered' and any context that was
     * available while triggering or attempting to trigger.
     *
     */
    public class ChatContext {

        public ChatContext(boolean triggered) {
            this.triggered = triggered;
        }

        public ChatContext(String changed_text, boolean triggered) {
            this.changed_text = changed_text;
            this.triggered = triggered;
        }

        String changed_text; boolean triggered;

        public boolean hasChanges() {
            return changed_text != null;
        }

        public String getChanges() {
            return changed_text != null ? changed_text : DetermineCommand.DETERMINE_NONE;
        }

        public Boolean wasTriggered() {
            return triggered;
        }

    }
}
TOP

Related Classes of net.aufdemrand.denizen.scripts.triggers.core.ChatTrigger$ChatContext

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.