Package games.stendhal.server.entity.npc

Source Code of games.stendhal.server.entity.npc.SpeakerNPC

/* $Id: SpeakerNPC.java,v 1.177 2011/05/16 14:18:53 kymara Exp $ */
/***************************************************************************
*                   (C) Copyright 2003-2010 - Stendhal                    *
***************************************************************************
***************************************************************************
*                                                                         *
*   This program is free software; you can redistribute it and/or modify  *
*   it under the terms of the GNU General Public License as published by  *
*   the Free Software Foundation; either version 2 of the License, or     *
*   (at your option) any later version.                                   *
*                                                                         *
***************************************************************************/
package games.stendhal.server.entity.npc;

import games.stendhal.common.parser.ConversationParser;
import games.stendhal.common.parser.ExpressionMatcher;
import games.stendhal.common.parser.Sentence;
import games.stendhal.server.core.engine.SingletonRepository;
import games.stendhal.server.entity.Entity;
import games.stendhal.server.entity.RPEntity;
import games.stendhal.server.entity.item.Corpse;
import games.stendhal.server.entity.npc.action.NPCEmoteAction;
import games.stendhal.server.entity.npc.condition.EmoteCondition;
import games.stendhal.server.entity.npc.condition.GreetingMatchesNameCondition;
import games.stendhal.server.entity.npc.fsm.Engine;
import games.stendhal.server.entity.npc.fsm.Transition;
import games.stendhal.server.entity.player.Player;

import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

import org.apache.log4j.Logger;

/**
* This is a finite state machine that implements a chat system. See:
* http://en.wikipedia.org/wiki/Finite_state_machine In fact, it is a
* transducer.
* States are denoted by the enum ConversationStates. Input is the text
* that the player says to the SpeakerNPC. Output is the text that the
* SpeakerNPC answers.
*
* See examples to understand how it works.
*
* RULES:
*
* State IDLE is both the start state and the state that will end the
* conversation between the player and the SpeakerNPC.
*
* State ATTENDING is the state where only one player can talk to NPC
* and where the prior talk doesn't matter.
*
* State ANY is a wildcard and is used to jump from any state whenever
* the trigger is active. There are states that are reserved for
* special behaviours and quests.
*
* Example how it works: First we need to create a message to greet the player
* and attend it. We add a hi event:
*
* add(ConversationStates.IDLE, ConversationPhrases.GREETING_MESSAGES,
* ConversationStates.ATTENDING, "Welcome, player!", null)
*
* Once the NPC is in the IDLE state and hears the word "hi", it will say
* "Welcome player!" and move to ATTENDING.
*
* Now let's add some options when player is in ATTENDING_STATE, like job,
* offer, buy, sell, etc.
*
* add(ConversationStates.ATTENDING, ConversationPhrases.JOB_MESSAGES,
* ConversationStates.ATTENDING, "I work as a part time example showman", null)
*
* add(ConversationStates.ATTENDING_STATE, "offer",
* ConversationStates.ATTENDING_STATE, "I sell best quality swords", null)
*
* Ok, two new events: job and offer, they go from ATTENDING state to ATTENDING
* state, because after reacting to "job" or "offer", the NPC can directly react
* to one of these again.
*
* <pre>
* add(ConversationStates.ATTENDING, "buy", ConversationStates.BUY_PRICE_OFFERED, null, new ChatAction() {
*    public void fire(Player player, String text, SpeakerNPC npc) {
*        int i = text.indexOf(" ");
*        String item = text.substring(i + 1);
*        if (item.equals("sword")) {
*            npc.say(item + "costs 10 coins. Do you want to buy?");
*        } else {
*            npc.say("Sorry, I don't sell " + item + ".");
*            npc.setActualState(ConversationStates.ATTENDING);
*        }
*    }
* });
* </pre>
*
* Now the hard part. We listen to "buy", so we need to process the text, and
* for that we use the ChatAction class, we create a new class that will handle
* the event. Also see that we move to a new state, BUY_PRICE_OFFERED. The
* player is then replying to a question, so we only expect two possible
* replies: yes or no.
*
* add(ConversationStates.BUY_PRICE_OFFERED, ConversationPhrases.YES_MESSAGES,
* ConversationStates.ATTENDING, "Sorry, I changed my mind. I won't sell
* anything.", null); // See SellerBehaviour.java for a working example.
*
* Whatever the reply is, return to ATTENDING state so we can listen to new
* things.
*
* Finally we want to finish the conversation, so whatever state we are, we want
* to finish a conversation with "Bye!".
*
* add(ConversationStates.ANY, ConversationPhrases.GOODBYE_MESSAGES,
* ConversationStates.IDLE, "Bye!", null);
*
* We use the state ANY as a wildcard, so if the input text is "bye" the
* transition happens, no matter in which state the FSM really is, with the
* exception of the IDLE state.
*/
public class SpeakerNPC extends NPC {
  /** the logger instance. */
  private static final Logger logger = Logger.getLogger(SpeakerNPC.class);

  private final Engine engine = new Engine(this);

  /**
   * Determines how long a conversation can be paused before it will
   * terminated by the NPC. Defaults to 30 seconds at 300 ms / turn.
   */
  private long playerChatTimeout = 100;

  // Default wait message when NPC is busy
  private String waitMessage;

  // Default wait action when NPC is busy
  private ChatAction waitAction;

  // Default bye message when NPC stops chatting with the player
  private String goodbyeMessage;

  private ChatCondition initChatCondition;

  // Default initChat action when NPC starts chatting with the player
  private ChatAction initChatAction;

  /**
   * Stores which turn was the last one at which a player spoke to this NPC.
   * This is important to determine conversation timeout.
   */
  private long lastMessageTurn;

  /**
   * The player who is currently talking to the NPC, or null if the NPC is
   * currently not taking part in a conversation.
   */
  private RPEntity attending;

  /**
   * alternative image for website
   */
  private String alternativeImage;

  /**
   * Creates a new SpeakerNPC.
   *
   * @param name
   *            The NPC's name. Please note that names should be unique.
   */
  public SpeakerNPC(final String name) {
    baseSpeed = 0.2;
    createPath();

    lastMessageTurn = 0;

    setName(name);
    createDialog();
    put("title_type", "npc");

    setSize(1, 1);

    // set the default perception range for player chatting
    setPerceptionRange(5);
  }

  protected void createPath() {
    // sub classes can implement this method
  }

  protected void createDialog() {
    // sub classes can implement this method
  }

  /**
   * Is called when the NPC stops chatting with a player. Override it if
   * needed.
   * @param attending2 who has been talked to.
   */
  protected void onGoodbye(final RPEntity attending2) {
    // do nothing
  }

  /**
   * Gets all players that have recently (this turn?) talked and are standing
   * nearby the NPC. Nearby means that they are standing less than <i>range</i>
   * squares away horizontally and less than <i>range</i> squares away
   * vertically.
   *
   * @param npc
   * @param range
   * @return A list of nearby players who have recently talked.
   */
  private List<Player> getNearbyPlayersThatHaveSpoken(final NPC npc, final double range) {
    final int x = npc.getX();
    final int y = npc.getY();

    final List<Player> players = new LinkedList<Player>();

    for (final Player player : getZone().getPlayers()) {
      final int px = player.getX();
      final int py = player.getY();

      if (player.has("text")) {
        int dx = px - x;
        int dy = py - y;

        if (Math.abs(dx)<range && Math.abs(dy)<range) { // check rectangular area
//        if (dx*dx + dy*dy < range*range) { // optionally we could check a circular area
          players.add(player);
        }
      }
    }

    return players;
  }

  /**
   * Gets the player who is standing nearest to the NPC. Returns null if no
   * player is standing nearby. Nearby means that they are standing less than
   * <i>range</i> squares away horizontally and less than <i>range</i>
   * squares away vertically. Note, however, that the Euclidian distance is
   * used to compare which player is standing closest.
   *
   * @param range
   * @return The nearest player, or null if no player is standing on the same
   *         map.
   */
  private Player getNearestPlayer(final double range) {
    final int x = getX();
    final int y = getY();

    Player nearest = null;

    int squaredDistanceOfNearestPlayer = Integer.MAX_VALUE;

    for (final Player player : getZone().getPlayers()) {
      final int px = player.getX();
      final int py = player.getY();

      if ((Math.abs(px - x) < range) && (Math.abs(py - y) < range)) {
        final int squaredDistanceOfThisPlayer =
            (px - x) * (px - x) + (py - y) * (py - y);

        if (squaredDistanceOfThisPlayer < squaredDistanceOfNearestPlayer) {
          squaredDistanceOfNearestPlayer = squaredDistanceOfThisPlayer;
          nearest = player;
        }
      }
    }

    return nearest;
  }

  /**
   * The entity who is currently talking to the NPC, or null if the NPC is
   * currently not taking part in a conversation.
   *
   * @return RPEntity
   */
  public RPEntity getAttending() {
    return attending;
  }

  /**
   * Sets the rpentity to whom the NPC is currently listening. Note: You don't
   * need to use this for most NPCs.
   *
   * @param rpentity
   *            the entity with whom the NPC should be talking.
   */
  public void setAttending(final RPEntity rpentity) {
    attending = rpentity;
    lastMessageTurn = SingletonRepository.getRuleProcessor().getTurn();
    if (rpentity != null) {
      stop();
    } else {
      if (hasPath()) {
        setSpeed(getBaseSpeed());
      }
      setIdea(null);
    }
  }

  @Override
  public void onDead(final Entity killer, final boolean remove) {

    heal();
    notifyWorldAboutChanges();
  }

  @Override
  protected void dropItemsOn(final Corpse corpse) {
    //they cant die
    logger.error("SpeakerNpc " + getName());
  }

  /**
   * Sets the time a conversation can be paused before it will be terminated
   * by the NPC.
   *
   * @param playerChatTimeout
   *            the time, in turns
   */
  public void setPlayerChatTimeout(final long playerChatTimeout) {
    this.playerChatTimeout = playerChatTimeout;
  }

  @Override
  public void logic() {
    // do nothing, the logic is in preLogic because it needs to be
    // done at the beginning of the next turn. Otherwise the NPCs
    // respond to player in the chat log before the player says something.
  }

  public void preLogic() {

    if (this.getZone().getPlayerAndFriends().isEmpty() && !isTalking()) {
      return;
    }

    if (has("text")) {
      remove("text");
    }

    // if no player is talking to the NPC, the NPC can move around.
    if (!isTalking()) {
      // TODO: Reset this on FSM engine state change
      if (getAttending() != null) {
        setAttending(null);
      }
      if (hasPath()) {
        setSpeed(baseSpeed);
      }
      applyMovement();
    } else if (attending != null) {
      // If the player is too far away
      if ((attending.squaredDistance(this) > 8 * 8)
          || ((attending instanceof Player) && (((Player) attending).isDisconnected()))
      // or if the player fell asleep ;)
          || ((attending instanceof Player) && (SingletonRepository.getRuleProcessor().getTurn()
              - lastMessageTurn > playerChatTimeout))) {
        // we force him to say bye to NPC :)
        if (goodbyeMessage != null) {
          say(goodbyeMessage);
        }
        onGoodbye(attending);
        engine.setCurrentState(ConversationStates.IDLE);
        setAttending(null);
      }
    }

    // now look for nearest player only if there's an initChatAction
    if (!isTalking() && (initChatAction != null)) {
      final Player nearest = getNearestPlayer(7);

      if (nearest != null) {
        if ((initChatCondition == null)
            || initChatCondition.fire(nearest, null, this)) {
          // Note: The sentence parameter is left as null, so be
          // careful not to use it in the fire() handler.
          initChatAction.fire(nearest, null, new EventRaiser(this));
        }
      }
    }

    // and finally react on anybody talking to us
    final List<Player> speakers = getNearbyPlayersThatHaveSpoken(this, getPerceptionRange());
    for (final Player speaker : speakers) {
      tell(speaker, speaker.get("text"));
    }

    notifyWorldAboutChanges();
  }

  public boolean isTalking() {
    return engine.getCurrentState() != ConversationStates.IDLE;
  }

  @Override
  // you can override this if you don't want your NPC to turn around
  // in certain situations.
  public void say(final String text) {
    // turn towards player if necessary, then say it.
    say(text, true);
  }

  protected void say(final String text, final boolean turnToPlayer) {
    // be polite and face the player we are talking to
    if (turnToPlayer && (attending != null)) {
      faceToward(attending);
    }

    super.say(text);
  }

  /** Message when NPC is attending another player.
   * @param text to say to bothering player
   * @param action to perform
   */
  public void addWaitMessage(final String text, final ChatAction action) {
    waitMessage = text;
    waitAction = action;
  }


  public void addInitChatMessage(final ChatCondition condition, final ChatAction action) {
    initChatCondition = condition;
    initChatAction = action;
  }

  /**
   * Adds a new transition to the FSM.
   *
   * @param state
   *            the starting state of the FSM
   * @param trigger
   *            input for this transition
   * @param condition
   *            null or condition that has to return true for this transition
   *            to be considered
   * @param nextState
   *            the new state of the FSM
   * @param reply
   *            a simple text reply (may be null for no reply)
   * @param action
   *            a special action to be taken (may be null)
   */
  public void add(final ConversationStates state, final String trigger, final ChatCondition condition,
      final ConversationStates nextState, final String reply, final ChatAction action) {
    engine.add(state, trigger, condition, false, nextState, reply, action);
  }

  /**
   * Adds a new transition with explicit ExpressionMatcher to the FSM.
   *
   * @param state
   * @param trigger
   * @param matcher
   * @param condition
   * @param nextState
   * @param reply
   * @param action
   */
  public void addMatching(final ConversationStates state, final String trigger, final ExpressionMatcher matcher, final ChatCondition condition,
      final ConversationStates nextState, final String reply, final ChatAction action) {
    engine.addMatching(state, trigger, matcher, condition, false, nextState, reply, action);
  }

  /**
   * Adds a new set of transitions to the FSM.
   *
   * @param state
   *            the starting state of the FSM
   * @param triggerStrings
   *            a list of inputs for this transition
   * @param condition
   *            null or condition that has to return true for this transition
   *            to be considered
   * @param nextState
   *            the new state of the FSM
   * @param reply
   *            a simple text reply (may be null for no reply)
   * @param action
   *            a special action to be taken (may be null)
   */
  public void add(final ConversationStates state, final Collection<String> triggerStrings, final ChatCondition condition,
      final ConversationStates nextState, final String reply, final ChatAction action) {
    engine.add(state, triggerStrings, condition, false, nextState, reply, action);
  }

  /**
   * Adds a new set of transitions to the FSM.
   *
   * @param state
   *            the starting state of the FSM
   * @param triggerStrings
   *            a list of inputs for this transition
   * @param condition
   *            null or condition that has to return true for this transition
   *            to be considered
   * @param secondary
   *         flag to mark secondary transitions to be taken into account after preferred transitions
   * @param nextState
   *            the new state of the FSM
   * @param reply
   *            a simple text reply (may be null for no reply)
   * @param action
   *            a special action to be taken (may be null)
   */
  public void add(final ConversationStates state, final Collection<String> triggerStrings, final ChatCondition condition, boolean secondary,
      final ConversationStates nextState, final String reply, final ChatAction action) {
    engine.add(state, triggerStrings, condition, secondary, nextState, reply, action);
  }

  /**
   * Adds a new set of transitions to the FSM.
   *
   * @param states
   *            the starting states of the FSM
   * @param trigger
   *            input for this transition
   * @param condition
   *            null or condition that has to return true for this transition
   *            to be considered
   * @param nextState
   *            the new state of the FSM
   * @param reply
   *            a simple text reply (may be null for no reply)
   * @param action
   *            a special action to be taken (may be null)
   */
  public void add(final ConversationStates[] states, final String trigger, final ChatCondition condition,
      final ConversationStates nextState, final String reply, final ChatAction action) {
    for (final ConversationStates state : states) {
      add(state, trigger, condition, nextState, reply, action);
    }
  }

  /**
   * Adds a new set of transitions to the FSM.
   *
   * @param states
   *            the starting states of the FSM
   * @param triggerStrings
   *            a list of inputs for this transition
   * @param condition
   *            null or condition that has to return true for this transition
   *            to be considered
   * @param nextState
   *            the new state of the FSM
   * @param reply
   *            a simple text reply (may be null for no reply)
   * @param action
   *            a special action to be taken (may be null)
   */
  public void add(final ConversationStates[] states, final Collection<String> triggerStrings,
      final ChatCondition condition, final ConversationStates nextState, final String reply,
      final ChatAction action) {
    for (final ConversationStates state : states) {
      add(state, triggerStrings, condition, nextState, reply, action);
    }
  }


  public void add(final ConversationStates state, final Collection<String> triggerStrings, final ConversationStates nextState,
      final String reply, final ChatAction action) {
    add(state, triggerStrings, null, nextState, reply, action);
  }

  public void listenTo(final Player player, final String text) {
    tell(player, text);
  }

  /**
   * If the given player says something to this NPC, and the NPC is already
   * speaking to another player, tells the given player to wait.
   *
   * @param player
   *            The player who spoke to the player
   * @param text
   *            The text that the given player has said
   * @return true iff the NPC had to get rid of the player
   */
  private boolean getRidOfPlayerIfAlreadySpeaking(final Player player, final String text) {
    // If we are attending another player make this one wait.
    if (attending != null && !player.equals(attending)) {
      if (ConversationPhrases.GREETING_MESSAGES.contains(
          ConversationParser.parse(text).getTriggerExpression().getNormalized())) {
        logger.debug("Already attending a player");

        if (waitMessage != null) {
          say(waitMessage);
        }

        if (waitAction != null) {
          final Sentence sentence = ConversationParser.parse(text);
          // Note: sentence is currently not yet used in
          // the called handler functions.
          waitAction.fire(player, sentence, new EventRaiser(this));
        }
      }

      return true;
    }

    return false;
  }

  /** This function evolves the FSM.
   * @param player
   * @param text
   * @return true if step was successfully executed*/
  private boolean tell(final Player player, final String text) {
    if (getRidOfPlayerIfAlreadySpeaking(player, text)) {
      return true;
    }

    // If we are not attending a player, attend this one.
    if (engine.getCurrentState() == ConversationStates.IDLE) {
      logger.debug("Attending player " + player.getName());
      setAttending(player);
    }

    lastMessageTurn = SingletonRepository.getRuleProcessor().getTurn();

    return engine.step(player, text);
  }

  public void setCurrentState(final ConversationStates state) {
    if (state == ConversationStates.ATTENDING) {
          setIdea("attending");
        } else {
          setIdea("awaiting");
        }
    engine.setCurrentState(state);
  }

  /**
   * Add default greeting transition with optional recognition of the NPC name.
   */
  public void addGreeting() {
    addGreeting("Greetings! How may I help you?", null);
  }

  /**
   * Add greeting transition with name recognition.
   * @param text
   */
  public void addGreeting(final String text) {
    addGreeting(text, null);
  }

  /**
   * Add greeting transition with name recognition.
   * @param text
   * @param action
   */
  public void addGreeting(final String text, final ChatAction action) {
    add(ConversationStates.IDLE, ConversationPhrases.GREETING_MESSAGES,
        new GreetingMatchesNameCondition(getName()), true,
        ConversationStates.ATTENDING, text, action);

    addWaitMessage(null, new ChatAction() {
      public void fire(final Player player, final Sentence sentence, final EventRaiser npc) {
        npc.say("Please wait, " + player.getTitle()
            + "! I am still attending to "
            + npc.getAttending().getTitle() + ".");
      }
    });
  }

  /**
   * Makes this NPC say a text when it hears a certain trigger during a
   * conversation.
   *
   * @param trigger
   *            The text that causes the NPC to answer
   * @param text
   *            The answer
   */
  public void addReply(final String trigger, final String text) {
    add(ConversationStates.ATTENDING, trigger, null,
        ConversationStates.ATTENDING, text, null);
  }

  /**
   * @param triggerStrings
   * @param text
   */
  public void addReply(final Collection<String> triggerStrings, final String text) {
    add(ConversationStates.ATTENDING, triggerStrings,
        ConversationStates.ATTENDING, text, null);
  }

  /**
   * Makes NPC say a text and/or do an action when a trigger is said.
   *
   * @param trigger
   * @param text
   * @param action
   */
  public void addReply(final String trigger, final String text, final ChatAction action) {
    add(ConversationStates.ATTENDING, trigger, null,
        ConversationStates.ATTENDING, text, action);

    addWaitMessage(null, new ChatAction() {
      public void fire(final Player player, final Sentence sentence, final EventRaiser npc) {
        npc.say("Please wait, " + player.getTitle()
            + "! I am still attending to "
            + npc.getAttending().getTitle() + ".");
      }
    });
  }

  /**
   * Makes NPC say a text and/or do an action when a trigger is said.
   *
   * @param triggerStrings
   * @param text
   * @param action
   */
  public void addReply(final Collection<String> triggerStrings, final String text, final ChatAction action) {
    add(ConversationStates.ATTENDING, triggerStrings, null,
        ConversationStates.ATTENDING, text, action);

    addWaitMessage(null, new ChatAction() {
      public void fire(final Player player, final Sentence sentence, final EventRaiser npc) {
        npc.say("Please wait, " + player.getTitle()
            + "! I am still attending to "
            + npc.getAttending().getTitle() + ".");
      }
    });
  }

  public void addQuest(final String text) {
    add(ConversationStates.ATTENDING, ConversationPhrases.QUEST_MESSAGES,
        ConversationStates.ATTENDING, text, null);
  }

  public void addJob(final String jobDescription) {
    addReply(ConversationPhrases.JOB_MESSAGES, jobDescription);
  }

  public void addHelp(final String helpDescription) {
    addReply(ConversationPhrases.HELP_MESSAGES, helpDescription);
  }

  public void addOffer(final String offerDescription) {
    addReply(ConversationPhrases.OFFER_MESSAGES, offerDescription);
  }

  /**
   * make npc's emotion reply on player's emotion
   * @param playerAction - what player doing with npc
   * @param npcAction - npc's emotion reply on player's emotion
   */
  public void addEmotionReply(final String playerAction, final String npcAction) {
    add(ConversationStates.IDLE, Arrays.asList("!me "), new EmoteCondition(playerAction),
        ConversationStates.IDLE, null, new NPCEmoteAction(npcAction));
    add(ConversationStates.ATTENDING, Arrays.asList("!me "), new EmoteCondition(playerAction),
        ConversationStates.ATTENDING, null, new NPCEmoteAction(npcAction));
  }

  /**
   * make npc's emotion
   * @param triggerStrings - player's keywords for npc emotion
   * @param npcAction - npc's emotion
   */
  public void addEmotion(final Collection<String> triggerStrings, final String npcAction) {
    add(ConversationStates.IDLE, triggerStrings,
        ConversationStates.IDLE, null, new NPCEmoteAction(npcAction));
    add(ConversationStates.ATTENDING, triggerStrings,
        ConversationStates.ATTENDING, null, new NPCEmoteAction(npcAction));

  }

  /**
   * make npc's emotion
   * @param trigger - player's keywords for npc emotion
   * @param npcAction - npc's emotion
   */
  public void addEmotion(final String trigger, final String npcAction) {
    add(ConversationStates.IDLE, Arrays.asList(trigger),
        ConversationStates.IDLE, null, new NPCEmoteAction(npcAction));
    add(ConversationStates.ATTENDING, Arrays.asList(trigger),
        ConversationStates.ATTENDING, null, new NPCEmoteAction(npcAction));
  }

  /**
   * make npc's reply on player's emotion
   * @param playerAction - what player doing with npc
   * @param reply - npc's reply on player's emotion
   */
  public void addReplyOnEmotion(final String playerAction, final String reply) {
    add(ConversationStates.IDLE, Arrays.asList("!me "),new EmoteCondition(playerAction),
        ConversationStates.IDLE, reply, null);
    add(ConversationStates.ATTENDING, Arrays.asList("!me "),new EmoteCondition(playerAction),
        ConversationStates.ATTENDING, reply, null);
  }

  public void addGoodbye() {
    addGoodbye("Bye.");
  }

  public void addGoodbye(final String text) {
    goodbyeMessage = text;
    add(ConversationStates.ANY, ConversationPhrases.GOODBYE_MESSAGES,
        ConversationStates.IDLE, text, new ChatAction() {

          public void fire(final Player player, final Sentence sentence,
              final EventRaiser npc) {
            ((SpeakerNPC) npc.getEntity()).onGoodbye(player);
          }

          @Override
          public String toString() {
            return "SpeakerNPC.onGoodbye";
          }
        });
  }

  /**
   * Returns a copy of the transition table.
   *
   * @return list of transitions
   */
  public List<Transition> getTransitions() {
    return engine.getTransitions();
  }

  public Engine getEngine() {
    return engine;
  }

  @Override
  protected void handleObjectCollision() {
    stop();
  }

  @Override
  protected void handleSimpleCollision(final int nx, final int ny) {
    stop();
  }

  /**
   * gets an alternative image for example for the website
   *
   * @return name of alternative image or <code>null</code> in case the normal image should be used.
   */
  public String getAlternativeImage() {
    return alternativeImage;
  }

  /**
   * sets an alternative image for example for the website
   *
   * @param alternativeImage name of alternative image or <code>null</code> in case the normal image should be used.
   */
  public void setAlternativeImage(String alternativeImage) {
    this.alternativeImage = alternativeImage;
  }


}
TOP

Related Classes of games.stendhal.server.entity.npc.SpeakerNPC

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.