Package games.stendhal.server.maps.quests.maze

Source Code of games.stendhal.server.maps.quests.maze.MazeGenerator$ReturnTeleporter

/* $Id: MazeGenerator.java,v 1.13 2011/04/02 15:44:20 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.maze;

import games.stendhal.common.MathHelper;
import games.stendhal.common.Rand;
import games.stendhal.common.grammar.Grammar;
import games.stendhal.server.core.engine.SingletonRepository;
import games.stendhal.server.core.engine.Spot;
import games.stendhal.server.core.engine.StendhalRPZone;
import games.stendhal.server.core.engine.dbcommand.WriteHallOfFamePointsCommand;
import games.stendhal.server.core.events.MovementListener;
import games.stendhal.server.entity.ActiveEntity;
import games.stendhal.server.entity.item.Corpse;
import games.stendhal.server.entity.item.Item;
import games.stendhal.server.entity.mapstuff.portal.Portal;
import games.stendhal.server.entity.mapstuff.portal.Teleporter;
import games.stendhal.server.entity.mapstuff.sound.BackgroundMusicSource;
import games.stendhal.server.entity.npc.action.IncrementQuestAction;
import games.stendhal.server.entity.npc.action.SetQuestAction;
import games.stendhal.server.entity.player.Player;
import games.stendhal.server.util.TimeUtil;
import games.stendhal.tools.tiled.LayerDefinition;
import games.stendhal.tools.tiled.StendhalMapStructure;

import java.awt.Point;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;

import marauroa.common.game.RPObject;
import marauroa.server.db.command.DBCommandQueue;

import org.apache.log4j.Logger;

/**
* A random maze zone.
*/
public class MazeGenerator {
  /**
   * Time in minutes how long the player can spend in the maze
   * to earn <code>DEFAULT_REWARD_POINTS</code>. Shorter times
   * get a higher reward, and longer times lower.
   */
  private static final int DEFAULT_SOLVING_TIME = 5;
  /**
   * Amount of points for solving the maze in <code>DEFAULT_SOLVING_TIME</code>.
   */
  private static final int DEFAULT_REWARD_POINTS = 100;
  private static final int REWARD_XP = 30;
  private static final Logger logger = Logger.getLogger(MazeGenerator.class);

  private static final int WALL_THICKNESS = 2;
  private static final String[] prizes = {
    "summon scroll",
    "home scroll",
    "ados city scroll",
    "nalwor city scroll",
    "kirdneh city scroll",
    "kalavan city scroll",
    "empty scroll"
  };

  /** The music track to be played in the maze */
  private static final String MUSIC_TRACK = "medieval_harp";
  /** Volume of the music */
  private static final int MUSIC_VOLUME = 80;

  private final String name;
  private final int width, height;
  private Point startPosition;
  private List<Point> corners = null;

  private final StendhalMapStructure mapStructure;
  private StendhalRPZone  zone = null;

  /** The name of the zone where to return a leaving player */
  private String returnZoneName;
  /** The coordinates where to return a leaving player */
  private int returnX, returnY;

  /** The time when the player was sent to the maze. */
  private long timeStamp;
  private MazeSign sign;

  private Teleporter portal;

  /**
   * Create a maze.
   *
   * @param name Name of the maze to be used as the zone name
   * @param width Width of the generated zone
   * @param height Height of the generated zone
   */
  public MazeGenerator(String name, int width, int height) {
    this.name = name;
    this.width = width;
    this.height = height;

    mapStructure = generateMapStructure(width, height);
  }

  /**
   * Get the location where to teleport a player.
   *
   * @return the intended starting location of the maze
   */
  public Point getStartPosition() {
    if (startPosition == null) {
      startPosition = Rand.rand(getCorners());
    }

    return startPosition;
  }

  /**
   * Set the location where a player logging out or returning
   * via a portal should be placed.
   *
   * @param zoneName Name of the return zone
   * @param x X coordinate
   * @param y Y coordinate
   */
  public void setReturnLocation(String zoneName, int x, int y) {
    returnZoneName= zoneName;
    returnX = x;
    returnY = y;
  }

  /**
   * Set the sign for hall of fame.
   * @param sign sign
   */
  public void setSign(MazeSign sign) {
    this.sign = sign;
  }

  /**
   * Get the zone generated by this maze instance.
   *
   * @return The generated zone
   */
  public StendhalRPZone getZone() {
    if (zone == null) {
      zone = generateZone();
    }
    return zone;
  }

  /**
   * Start timing how long the player takest to solve the maze.
   */
  public void startTiming() {
    timeStamp = System.currentTimeMillis();
  }

  private StendhalMapStructure generateMapStructure(int width, int height) {
    LayerDefinition floor = new LayerDefinition(width, height);
    floor.setName("0_floor");
    floor.build();

    LayerDefinition terrain = new LayerDefinition(width, height);
    terrain.setName("1_terrain");

    LayerDefinition object = new LayerDefinition(width, height);
    object.setName("2_object");

    LayerDefinition roof = new LayerDefinition(width, height);
    roof.setName("3_roof");

    LayerDefinition collision = new LayerDefinition(width, height);
    collision.setName("collision");

    LayerDefinition protection = new LayerDefinition(width, height);
    protection.setName("protection");

    StendhalMapStructure map = new StendhalMapStructure(width, height);

    map.addLayer(floor);
    map.addLayer(terrain);
    map.addLayer(object);
    map.addLayer(roof);
    map.addLayer(collision);
    map.addLayer(protection);

    generateCollisions(collision);

    // solves client caching, but makes other trouble
    //String md5 = Hash.toHexString(Hash.hash(collision.exposeRaw()));
    //name += "_" + md5;

    MazePainter painter = new MazePainter();
    painter.paint(map);
    Point pos = getPortalPosition();
    painter.paintPortal(map, pos.x, pos.y);

    return map;
  }

  private void generateCollisions(LayerDefinition layer) {
    layer.build();

    // create a grid
    for (int i = 0; i < width; i++) {
      for (int j = 0; j < height; j++) {
        if (!((i % (WALL_THICKNESS + 1) == WALL_THICKNESS) && (j % (WALL_THICKNESS + 1) == WALL_THICKNESS))) {
          setCollide(layer, i, j, true);
        }
      }
    }

    burrowCave(getStartPosition(), layer);
    widenCorners(layer);
  }

  /**
   * Make the tunnels. The actual maze generation algorithm.
   *
   * @param point Starting point for the tunnels
   * @param layer Collision layer
   */
  private void burrowCave(Point point, LayerDefinition layer) {
    LinkedList<Point> branchPoints = new LinkedList<Point>();
    HashSet<Point> visited = new HashSet<Point>();
    branchPoints.add(point);
    List<Point> neighbours = getUnvisitedNeighbours(point, visited);

    do {
      visited.add(point);
      if (neighbours.size() > 0) {
        Point next = Rand.rand(neighbours);
        branchPoints.add(next);

        // Knock down the wall between
        int diffx = next.x - point.x;
        if (diffx != 0) {
          diffx /= Math.abs(diffx);
        }
        int diffy = next.y - point.y;
        if (diffy != 0) {
          diffy /= Math.abs(diffy);
        }

        for (int i = 1; i <= WALL_THICKNESS; i++) {
          setCollide(layer, point.x + i * diffx, point.y + i * diffy, false);
        }

        point = next;
      } else {
        branchPoints.remove(point);
        if (branchPoints.size() > 0) {
          // branch from the beginning to make nice and long tunnels
          point = branchPoints.getFirst();
        } else {
          point = null;
        }
      }

      neighbours = getUnvisitedNeighbours(point, visited);
    } while (point != null);
  }

  private List<Point> getUnvisitedNeighbours(Point point, HashSet<Point> visited) {
    if (point == null) {
      return null;
    }

    LinkedList<Point> neighbours = new LinkedList<Point>();

    Point left = new Point(point.x - (WALL_THICKNESS +1), point.y);
    if ((left.x > 0) && !visited.contains(left)) {
      neighbours.add(left);
    }

    Point right = new Point(point.x + (WALL_THICKNESS + 1), point.y);
    if ((right.x < width) && !visited.contains(right)) {
      neighbours.add(right);
    }

    Point up = new Point(point.x, point.y - (WALL_THICKNESS +1));
    if ((up.y > 0) && !visited.contains(up)) {
      neighbours.add(up);
    }

    Point down = new Point(point.x, point.y + (WALL_THICKNESS +1));
    if ((down.y < height) && !visited.contains(down)) {
      neighbours.add(down);
    }

    return neighbours;
  }

  private void widenCorners(LayerDefinition layer) {
    // top left corner
    setCollide(layer, WALL_THICKNESS - 1, WALL_THICKNESS - 1, false);
    setCollide(layer, WALL_THICKNESS - 1, WALL_THICKNESS, false);
    setCollide(layer, WALL_THICKNESS, WALL_THICKNESS - 1, false);
    // top right corner
    setCollide(layer, width - width % (WALL_THICKNESS + 1) - 1, WALL_THICKNESS - 1, false);
    setCollide(layer, width - width % (WALL_THICKNESS + 1), WALL_THICKNESS - 1, false);
    setCollide(layer, width - width % (WALL_THICKNESS + 1), WALL_THICKNESS, false);
    // bottom left corner
    setCollide(layer, WALL_THICKNESS - 1, height - height % (WALL_THICKNESS + 1) - 1, false);
    setCollide(layer, WALL_THICKNESS - 1, height - height % (WALL_THICKNESS + 1), false);
    setCollide(layer, WALL_THICKNESS, height - height % (WALL_THICKNESS + 1), false);
    // bottom right corner
    setCollide(layer, width - width % (WALL_THICKNESS + 1) - 1, height - height % (WALL_THICKNESS + 1), false);
    setCollide(layer, width - width % (WALL_THICKNESS + 1), height - height % (WALL_THICKNESS + 1), false);
    setCollide(layer, width - width % (WALL_THICKNESS + 1), height - height % (WALL_THICKNESS + 1) - 1, false);
  }

  private List<Point> getCorners() {
    if (corners == null) {
      corners = new LinkedList<Point>();
      // Order is important. Opposite corners should not be next to each other
      corners.add(new Point(WALL_THICKNESS, WALL_THICKNESS));
      corners.add(new Point(width - width % (WALL_THICKNESS + 1) - 1, WALL_THICKNESS));
      corners.add(new Point(width - width % (WALL_THICKNESS + 1) - 1,
          height - height % (WALL_THICKNESS + 1) - 1));
      corners.add(new Point(WALL_THICKNESS, height - height % (WALL_THICKNESS + 1) - 1));
    }

    return corners;
  }

  private Point getPortalPosition() {
    // opposite corner to start
    Point start = getStartPosition();
    Point pos = (Point) getCorners().get((getCorners().indexOf(start) + 2) % 4).clone();
    // shift a bit to put the portal deeper in the corner
    pos.x += (start.x > pos.x) ? -1 : 1;
    pos.y += (start.y > pos.y) ? -1 : 1;

    return pos;
  }

  private void setCollide(LayerDefinition layer, int x, int y, boolean collide) {
    layer.set(x, y, collide ? 1 : 0);
  }

  private StendhalRPZone generateZone() {
    mapStructure.build();

    final StendhalRPZone zone = new StendhalRPZone(name, width, height);

    try {
      zone.addTilesets(name + ".tilesets", mapStructure.getTilesets());
      zone.addLayer(name + ".0_floor", mapStructure.getLayer("0_floor"));
      zone.addLayer(name + ".1_terrain", mapStructure.getLayer("1_terrain"));
      zone.addLayer(name + ".2_object", mapStructure.getLayer("2_object"));
      zone.addLayer(name + ".3_roof", mapStructure.getLayer("3_roof"));

      zone.addCollisionLayer(name + ".collision",
          mapStructure.getLayer("collision"));
      zone.addProtectionLayer(name + ".protection",
          mapStructure.getLayer("protection"));
    } catch (IOException e) {
      logger.error(e);
    }

    // Create the return portal
    portal = new ReturnTeleporter(new Spot(SingletonRepository.getRPWorld().getZone(returnZoneName), returnX, returnY));
    Point pos = getPortalPosition();
    portal.setPosition(pos.x, pos.y);
    zone.add(portal);

    // disable double click move and teleport in
    zone.setMoveToAllowed(false);
    zone.disallowIn();

    // Add some scrolls
    addPrizes(zone);

    /*
     * Add music to the otherwise quiet zone. The radius is set
     * to be a bit more than the max distance, so at start the
     * music will be really quiet.
     */
    BackgroundMusicSource music = new BackgroundMusicSource(MUSIC_TRACK,
        (int) Math.sqrt(width * width + height * height) + 1, MUSIC_VOLUME);
    // set it at the the exit portal so that it's louder when the player is near
    music.setPosition(pos.x, pos.y);
    zone.add(music);

    zone.addMovementListener(new MazeMovementListener());
    return zone;
  }

  /**
   * Drop random prizes to the side corners.
   *
   * @param zone the maze zone to drop the items
   */
  private void addPrizes(StendhalRPZone zone) {
    int idx = getCorners().indexOf(getStartPosition());

    for (int i = 1; i <= 3; i += 2) {
      Item prize = SingletonRepository.getEntityManager().getItem(Rand.rand(prizes));
      Point location = getCorners().get((idx + i) % 4);
      prize.setPosition(location.x, location.y);

      zone.add(prize, true);
    }
  }

  /**
   * A listener to destroy the zone when players have left and to return the
   * player to the right place in case she logged out.
   */
  private final class MazeMovementListener implements MovementListener {
    private final Rectangle2D area;

    public MazeMovementListener() {
      area = new Rectangle2D.Double(0, 0, width, height);
    }

    public Rectangle2D getArea() {
      return area;
    }

    public void onEntered(final ActiveEntity entity, final StendhalRPZone zone, final int newX,
        final int newY) {
      // ignore
    }

    public void onExited(final ActiveEntity entity, final StendhalRPZone zone, final int oldX,
        final int oldY) {
      if (!(entity instanceof Player)) {
        return;
      }
      if (zone.getPlayers().size() == 1) {
        // since we are about to destroy the arena, change the player zoneid to house so that
        // if they are relogging, they can enter back to the house (not the default zone of PlayerRPClass).
        // If they are out or walking out the portal it works as before.
        entity.put("zoneid", returnZoneName);
        entity.put("x", returnX);
        entity.put("y", returnY);
        // Tell corpses they're going to be removed (from pets or creatures
        // from summon scrolls). This is for stopping the rotting timers
        for (RPObject obj : zone) {
          if (obj instanceof Corpse) {
            ((Corpse) obj).onRemoved(zone);
          }
        }
        SingletonRepository.getRPWorld().removeZone(zone);
      }
    }

    public void onMoved(final ActiveEntity entity, final StendhalRPZone zone, final int oldX,
        final int oldY, final int newX, final int newY) {
      // ignore
    }
  }

  protected void rewardPlayer(Player player) {
    long timediff = System.currentTimeMillis() - timeStamp;
    double normalized = timediff / (double) (DEFAULT_SOLVING_TIME * MathHelper.MILLISECONDS_IN_ONE_MINUTE);
    // theoretical maximum e * DEFAULT_REWARD_POINTS
    int points = (int) (DEFAULT_REWARD_POINTS * Math.exp(1 - normalized));
    // Give at least one xp for persistent but hopelessly slow players
    points = Math.max(points, 1);

    DBCommandQueue.get().enqueue(new WriteHallOfFamePointsCommand(player.getName(), "M", points, true));
    new SetQuestAction("maze", 0, "done").fire(player, null, null);
    new IncrementQuestAction("maze", 2, 1).fire(player, null, null);
    player.sendPrivateText("You used " + TimeUtil.timeUntil((int) (timediff / 1000), true)
        + " to solve the maze. That was worth " + Grammar.quantityplnoun(points, "point") + ".");
    SingletonRepository.getAchievementNotifier().onFinishQuest(player);
    player.addXP(REWARD_XP);
  }

  private class ReturnTeleporter extends Teleporter {
    public ReturnTeleporter(Spot spot) {
      super(spot);
    }

    @Override
    protected boolean usePortal(final Player player) {
      boolean success = super.usePortal(player);
      if (success) {
        rewardPlayer(player);
        sign.updatePlayers();
      }
      return success;
    }
  }

  /**
   * Access the portal from MazeTest.
   *
   * @return the exit portal
   */
  public Portal getPortal() {
    return portal;
  }
}
TOP

Related Classes of games.stendhal.server.maps.quests.maze.MazeGenerator$ReturnTeleporter

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.