Package games.stendhal.server.maps.quests

Source Code of games.stendhal.server.maps.quests.ReverseArrow$GamblosSpeakerNPC

/* $Id: ReverseArrow.java,v 1.98 2011/04/02 15:44:18 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.maps.quests;

import games.stendhal.common.Direction;
import games.stendhal.common.grammar.Grammar;
import games.stendhal.server.core.engine.SingletonRepository;
import games.stendhal.server.core.engine.StendhalRPZone;
import games.stendhal.server.core.events.LoginListener;
import games.stendhal.server.core.events.TurnListener;
import games.stendhal.server.entity.RPEntity;
import games.stendhal.server.entity.item.StackableItem;
import games.stendhal.server.entity.item.token.Token;
import games.stendhal.server.entity.mapstuff.portal.OnePlayerRoomDoor;
import games.stendhal.server.entity.mapstuff.portal.Portal;
import games.stendhal.server.entity.mapstuff.sign.Sign;
import games.stendhal.server.entity.npc.ConversationPhrases;
import games.stendhal.server.entity.npc.ConversationStates;
import games.stendhal.server.entity.npc.SpeakerNPC;
import games.stendhal.server.entity.npc.action.SayTextWithPlayerNameAction;
import games.stendhal.server.entity.npc.condition.AndCondition;
import games.stendhal.server.entity.npc.condition.QuestCompletedCondition;
import games.stendhal.server.entity.npc.condition.QuestNotCompletedCondition;
import games.stendhal.server.entity.npc.condition.GreetingMatchesNameCondition;
import games.stendhal.server.entity.player.Player;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;

import marauroa.common.game.IRPZone;

import org.apache.log4j.Logger;

/**
* A quest where the player has to invert an arrow build out of stones by moving
* only up to 3 tokens.
*
* @author hendrik
*/

public class ReverseArrow extends AbstractQuest implements
    Token.TokenMoveListener<Token>, LoginListener {

  private static final Logger LOGGER = Logger.getLogger(ReverseArrow.class);

  // constants
  private static final String QUEST_SLOT = "reverse_arrow";

  private static final String ZONE_NAME = "int_ados_reverse_arrow";

  /** Time (in Seconds) to solve the puzzle. */
  private static final int TIME = 60;

  /** Possible number of moves to solve the puzzle. */
  private static final int MAX_MOVES = 3;

  /** Horizontal position of the upper left token at the beginning. */
  private static final int OFFSET_X = 15;

  /** Vertical position of the upper left token at the beginning. */
  private static final int OFFSET_Y = 10;

  // "static" data
  protected StendhalRPZone zone;

  protected SpeakerNPC npc;

  protected List<Token> tokens;

  private OnePlayerRoomDoor door;

  private StendhalRPZone entranceZone;

  // quest instance data
  private int moveCount;

  protected Player player;

  private Timer timer;

  /**
   * Checks the result.
   */
  protected class ReverseArrowCheck implements TurnListener {

    /**
     * Is the task solved?
     *
     * @return true on success, false on failure
     */
    private boolean checkBoard() {
      if (tokens == null) {
        return false;
      }

      // We check the complete arrow (and not just the three moved
      // tokens) here for two reasons:

      // 1. there are 6 permutions so the code would quite messy
      // 2. there may be a solution i did not recognize

      // This aproach has the side effect that the code does not
      // tell the solution :-)

      // sort the tokens according to their position
      Collections.sort(tokens, new Comparator<Token>() {
        public int compare(final Token t1, final Token t2) {
          int d = t1.getY() - t2.getY();
          if (d == 0) {
            d = t1.getX() - t2.getX();
          }
          return d;
        }
      });
      // * * 0 * *
      // * 1 2 3 *
      // 4 5 6 7 8

      // get the position of the topmost token
      final int topX = tokens.get(0).getX();
      final int topY = tokens.get(0).getY();

      // check first row
      for (int i = 1; i <= 3; i++) {
        final Token token = tokens.get(i);
        if ((token.getX() != topX - 1 + (i - 1))
            || (token.getY() != topY + 1)) {
          return false;
        }
      }

      // check second row
      for (int i = 4; i <= 8; i++) {
        final Token token = tokens.get(i);
        if ((token.getX() != topX - 2 + (i - 4))
            || (token.getY() != topY + 2)) {
          return false;
        }
      }

      return true;
    }

    /**
     * invoked shortly after the player did his/her third move.
     * @param currentTurn on which it is invoked
     */
    public void onTurnReached(final int currentTurn) {
      if (checkBoard() && (moveCount <= MAX_MOVES)) {
        if (player.isQuestCompleted(QUEST_SLOT)) {
          npc.say("Congratulations, you solved the quiz again. But unfortunately I don't have any further rewards for you.");
        } else {
          npc.say("Congratulations, you solved the quiz.");
          final StackableItem money = (StackableItem) SingletonRepository.getEntityManager().getItem(
                  "money");
          money.setQuantity(50);
          player.equipToInventoryOnly(money);
          player.addXP(100);
        }
        player.setQuest(QUEST_SLOT, "done");
      } else {
        if (!player.isQuestCompleted(QUEST_SLOT)) {
          player.setQuest(QUEST_SLOT, "failed");
        }
        npc.say("I am sorry. This does not look like an arrow pointing upwards to me.");
      }

      // teleport the player out after 2 seconds of delay
      SingletonRepository.getTurnNotifier().notifyInTurns(6,
          new FinishNotifier(true, player));
    }
  }

  /**
   * Teleports the player out.
   */
  protected class FinishNotifier implements TurnListener {
    private final boolean reset;

    private final Player finishPlayer;

    public FinishNotifier(final boolean reset, final Player player) {
      this.finishPlayer = player;
      this.reset = reset;
    }

    /**
     * invoked shortly after the player did his job.
     * @param currentTurn on which it is invoked
     */
    public void onTurnReached(final int currentTurn) {
      finish(reset, finishPlayer);
    }
  }

  /**
   * Tells the player the remaining time and teleports him out if the task is
   * not completed in time.
   */
  class Timer implements TurnListener {
    private final Player timerPlayer;

    /**
     * Starts a teleport-out-timer.
     *
     * @param player
     *            the player who started the timer
     */
    protected Timer(final Player player) {
      timerPlayer = player;
    }

    private int counter = TIME;

    public void onTurnReached(final int currentTurn) {
      // check that the player is still in game and stop the timer
      // in case the player is not playing anymore.
      // Note that "player" always refers to the current player
      // in order not to teleport the next player out too early,
      // we have to compare it to the player who started this timer
      if ((player == timerPlayer) && (player != null)) {
        final IRPZone playerZone = player.getZone();

        if (playerZone.equals(zone)) {
          if (counter > 0) {
            npc.say("You have " + counter + " seconds left.");
            counter = counter - 10;
            SingletonRepository.getTurnNotifier().notifyInTurns(10 * 3, this);
          } else {
            // teleport the player out
            npc.say("Sorry, your time is up.");
            SingletonRepository.getTurnNotifier().notifyInTurns(1,
                new FinishNotifier(true, player));
            // need to do this on the next turn
          }
        }
      }
    }
  }

  /**
   * A special door that only lets one player in at a time, and that notifies
   * this script on successful usage.
   */
  class NotifyingDoor extends OnePlayerRoomDoor {
    NotifyingDoor(final String clazz) {
      super(clazz);
    }

    @Override
    public boolean onUsed(final RPEntity user) {
      boolean success = super.onUsed(user);
     
      if (user instanceof Player) {
        start((Player) user);
      } else {
        LOGGER.error("user is no instance of Player but: " + user, new Throwable());
      }

      return success;
    }

    @Override
    public void onUsedBackwards(final RPEntity user) {
      super.onUsedBackwards(user);

      if (user instanceof Player) {
        finish(true, (Player) user);
      } else {
        LOGGER.error("user is no instance of Player but: " + user, new Throwable());
      }
    }
  }

  @Override
  public String getSlotName() {
    return QUEST_SLOT;
  }

  @Override
  public String getName() {
    return "ReverseArrow";
  }

  /**
   * Creates a token and adds it to the world.
   *
   * @param x
   *            x-position
   * @param y
   *            y-position
   */
  private void addTokenToWorld(final int x, final int y) {
    final Token token = (Token) SingletonRepository.getEntityManager().getItem("arrow game token");
    token.setPosition(x, y);
    token.setTokenMoveListener(this);
    zone.add(token, false);
    tokens.add(token);
  }

  /**
   * Adds the tokens to the game field.
   */
  private void addAllTokens() {
    // 0 1 2 3 4
    // * 5 6 7 *
    // * * 8 * *
    tokens = new LinkedList<Token>();
    for (int i = 0; i < 5; i++) {
      addTokenToWorld(OFFSET_X + i, OFFSET_Y);
    }
    for (int i = 1; i < 4; i++) {
      addTokenToWorld(OFFSET_X + i, OFFSET_Y + 1);
    }
    addTokenToWorld(OFFSET_X + 2, OFFSET_Y + 2);
  }

  /**
   * Removes all tokens (called after the player messed them up).
   */
  private void removeAllTokens() {
    if (tokens != null) {
      for (final Token token : tokens) {
        if (token != null) {
          zone.remove(token.getID());
        }
      }
    }
    tokens = null;
  }

  private void step_1() {
    zone = SingletonRepository.getRPWorld().getZone(ZONE_NAME);
    step1CreateNPC();
    step1CreateDoors();
  }

  private void step1CreateNPC() {
    npc = new GamblosSpeakerNPC("Gamblos");

    npc.setEntityClass("oldwizardnpc");
    npc.setPosition(20, 8);
    npc.setDirection(Direction.DOWN);
    npc.initHP(100);
    zone.add(npc);
  }

  private static final class GamblosSpeakerNPC extends SpeakerNPC {
    private GamblosSpeakerNPC(String name) {
      super(name);
    }

    @Override
    protected void createPath() {
      // NPC doesn't move
      setPath(null);
    }

    @Override
    protected void createDialog() {
      add(ConversationStates.IDLE,
          ConversationPhrases.GREETING_MESSAGES,
          new AndCondition(new GreetingMatchesNameCondition(getName()),
              new QuestCompletedCondition(QUEST_SLOT)),
          ConversationStates.ATTENDING,
          null,
          new SayTextWithPlayerNameAction("Hi again, [name]. I remember that you solved this problem already. You can do it again, of course."));
     
      add(ConversationStates.IDLE,
          ConversationPhrases.GREETING_MESSAGES,
          new AndCondition(new GreetingMatchesNameCondition(getName()),
              new QuestNotCompletedCondition(QUEST_SLOT)),
          ConversationStates.ATTENDING,
          "Hi, welcome to our small game. Your task is to let this arrow point upwards, by moving up to three tokens.",
          null);
     
      addHelp("You have to stand next to a token in order to move it.");
      addJob("I am the supervisor for this task.");
      addGoodbye("It was nice to meet you.");
      addQuest("Your task in this game is to reverse the direction of this arrow moving only 3 tokens within "
          + TIME + " seconds.");
    }
  }

  private void step1CreateDoors() {
    // 0_semos_mountain_n2 at (95,101)
    final String entranceZoneName = "0_semos_mountain_n2";
    entranceZone = SingletonRepository.getRPWorld().getZone(entranceZoneName);
    door = new NotifyingDoor("housedoor");
    door.setPosition(95, 101);
    door.setIdentifier(Integer.valueOf((0)));
    door.setDestination(ZONE_NAME, Integer.valueOf(0));
    entranceZone.add(door);

    door.open();

    final Portal exit = new Portal();
    exit.setPosition(17, 20);
    exit.setIdentifier(Integer.valueOf(0));
    exit.setDestination(entranceZoneName, Integer.valueOf(0));
    zone.add(exit);

    final Sign sign = new Sign();
    sign.setPosition(96, 102);
    sign.setText("If the door is closed, you will have to wait a short time until the last player finishes his task.");
    entranceZone.add(sign);
  }

  public void onLoggedIn(final Player player) {
    // need to do this on the next turn
    SingletonRepository.getTurnNotifier().notifyInTurns(1, new FinishNotifier(false, player));
  }

  /**
   * The player moved a token.
   *
   * @param player Player
   * @param token Token
   */
  public void onTokenMoved(final Player player, Token token) {
    //TODO only count if the token really changed its position
    moveCount++;

    if (moveCount < MAX_MOVES) {
      npc.say("This was your " + Grammar.ordered(moveCount) + " move.");
    } else if (moveCount == MAX_MOVES) {
      npc.say("This was your " + Grammar.ordered(moveCount)
          + " and final move. Let me check your work.");
      // notify in 2 seconds
      SingletonRepository.getTurnNotifier().notifyInTurns(6, new ReverseArrowCheck());
      if (timer != null) {
        SingletonRepository.getTurnNotifier().dontNotify(timer);
      }
    } else {
      npc.say("Sorry, you may only do " + MAX_MOVES + " moves");
    }
  }

  /**
   * A player entered the zone.
   *
   * @param player
   *            Player
   */
  public void start(final Player player) {
    final IRPZone playerZone = player.getZone();

    if (playerZone.equals(zone)) {
      this.player = player;
      removeAllTokens();
      addAllTokens();
      timer = new Timer(player);
      SingletonRepository.getTurnNotifier().notifyInTurns(0, timer);
      moveCount = 0;
    }
  }

  /**
   * Finishes the quest and teleports the player out.
   *
   * @param reset
   *            reset it for the next player (set to false on login)
   * @param player
   *            the player to teleport out
   */
  protected void finish(final boolean reset, final Player player) {
    if (player != null) {
      final IRPZone playerZone = player.getZone();

      if (playerZone.equals(zone)) {
        player.teleport(entranceZone, door.getX(), door.getY() + 1,
            Direction.DOWN, player);
      }
    }
    if (reset) {
      removeAllTokens();
      this.player = null;
      moveCount = 0;
      if (timer != null) {
        SingletonRepository.getTurnNotifier().dontNotify(timer);
      }
      door.open();
    }
  }

  @Override
  public void addToWorld() {
    super.addToWorld();
    fillQuestInfo(
        "Reverse Arrow",
        "Gamblos has a fun puzzle to solve.",
        false);

    SingletonRepository.getLoginNotifier().addListener(this);

    step_1();
  }
 
  @Override
  public List<String> getHistory(final Player player) {
      final List<String> res = new ArrayList<String>();
      if (!player.hasQuest(QUEST_SLOT)) {
        return res;
      }
      final String questState = player.getQuest(QUEST_SLOT);
      res.add("Gamblos challenged me with a puzzle.");
      if ("failed".equals(questState)) {
        res.add("I couldn't solve the puzzle.");
      }
      if (isCompleted(player)) {
        res.add("I solved the puzzle!");
      }
      return res;
  }
}
TOP

Related Classes of games.stendhal.server.maps.quests.ReverseArrow$GamblosSpeakerNPC

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.