Package games.stendhal.server.core.engine

Source Code of games.stendhal.server.core.engine.StendhalRPRuleProcessor

/* $Id: StendhalRPRuleProcessor.java,v 1.87 2011/05/29 19:54:09 nhnb Exp $ */
/***************************************************************************
*                      (C) Copyright 2003 - Marauroa                      *
***************************************************************************
***************************************************************************
*                                                                         *
*   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.core.engine;

import games.stendhal.common.Debug;
import games.stendhal.common.NotificationType;
import games.stendhal.common.filter.FilterCriteria;
import games.stendhal.server.actions.CommandCenter;
import games.stendhal.server.actions.admin.AdministrationAction;
import games.stendhal.server.core.account.AccountCreator;
import games.stendhal.server.core.account.CharacterCreator;
import games.stendhal.server.core.engine.db.StendhalWebsiteDAO;
import games.stendhal.server.core.engine.dbcommand.SetOnlineStatusCommand;
import games.stendhal.server.core.engine.transformer.PlayerTransformer;
import games.stendhal.server.core.events.TutorialNotifier;
import games.stendhal.server.core.rp.StendhalRPAction;
import games.stendhal.server.core.scripting.ScriptRunner;
import games.stendhal.server.entity.Entity;
import games.stendhal.server.entity.RPEntity;
import games.stendhal.server.entity.npc.NPCList;
import games.stendhal.server.entity.player.Player;
import games.stendhal.server.events.PlayerLoggedOnEvent;
import games.stendhal.server.events.PlayerLoggedOutEvent;
import games.stendhal.server.extension.StendhalServerExtension;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import marauroa.common.Configuration;
import marauroa.common.Pair;
import marauroa.common.game.AccountResult;
import marauroa.common.game.CharacterResult;
import marauroa.common.game.IRPZone;
import marauroa.common.game.RPAction;
import marauroa.common.game.RPObject;
import marauroa.common.io.UnicodeSupportingInputStreamReader;
import marauroa.server.db.command.DBCommand;
import marauroa.server.db.command.DBCommandQueue;
import marauroa.server.game.Statistics;
import marauroa.server.game.db.DAORegister;
import marauroa.server.game.rp.IRPRuleProcessor;
import marauroa.server.game.rp.RPServerManager;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;


/**
* adds game rules for Stendhal to the marauroa environment.
*
* @author hendrik
*/
public class StendhalRPRuleProcessor implements IRPRuleProcessor {

  /** only log the first exception while reading welcome URL. */
  private static boolean firstWelcomeException = true;
  /** the logger instance. */
  private static final Logger logger = Logger.getLogger(StendhalRPRuleProcessor.class);
  /** list of super admins read from admins.list. */
  private static Map<String, String> adminNames;
  /** welcome message unless overwritten by an URL */
  private static String welcomeMessage = "Welcome to Stendhal. Need help? #http://stendhalgame.org/wiki/AskForHelp - please report problems, suggestions and bugs. Remember to keep your password completely secret, never tell to another friend, player, or admin.";

  /** The Singleton instance. */
  protected static StendhalRPRuleProcessor instance;

  private RPServerManager rpman;

  /** a list of online players */
  protected PlayerList onlinePlayers;
  private final List<Player> playersRmText;

  /**
   * A list of RPEntities that were killed in the current turn, together with
   * the Entity that killed it.
   */
  private final List<Pair<RPEntity, Entity>> entityToKill;

  /** a list of zone that should be removed (like vaults) */
  private final List<StendhalRPZone> zonesToRemove = new LinkedList<StendhalRPZone>();

  /**
   * creates a new StendhalRPRuleProcessor
   */
  protected StendhalRPRuleProcessor() {
    onlinePlayers = new PlayerList();
    playersRmText = new LinkedList<Player>();
    entityToKill = new LinkedList<Pair<RPEntity, Entity>>();
  }

  private void init() {
    String[] params = {};
    new GameEvent("server system", "startup", params).raise();
  }

  /**
   * gets the singleton instance of StendhalRPRuleProcessor
   *
   * @return StendhalRPRuleProcessor
   */
  public static StendhalRPRuleProcessor get() {
    synchronized(StendhalRPRuleProcessor.class) {
      if (instance == null) {
        StendhalRPRuleProcessor instance = new StendhalRPRuleProcessor();
        instance.init();
        StendhalRPRuleProcessor.instance = instance;
      }
    }
    return instance;
  }

  /**
   * gets a list of online players
   *
   * @return list of online players
   */
  public PlayerList getOnlinePlayers() {
    return onlinePlayers;
  }

  public void setContext(final RPServerManager rpman) {
    try {
      /*
       * Print version information.
       */
      String preRelease = "";
      if (Debug.PRE_RELEASE_VERSION != null) {
        preRelease = " - " + preRelease;
      }
      logger.info("Running Stendhal server VERSION " + Debug.VERSION + preRelease);

      this.rpman = rpman;
      StendhalRPAction.initialize(rpman);

      /* Initialize quests */
      SingletonRepository.getStendhalQuestSystem().init();

      new ScriptRunner();

      final Configuration config = Configuration.getConfiguration();
      try {
        final String[] extensionsToLoad = config.get("server_extension").split(",");
        for (final String element : extensionsToLoad) {
          String extension = null;
          try {
            extension = element;
            if (extension.length() > 0) {
              StendhalServerExtension.getInstance(config.get(extension)).init();
            }
          } catch (final RuntimeException ex) {
            logger.error("Error while loading extension: " + extension, ex);
          }
        }
      } catch (final RuntimeException ep) {
        logger.info("No server extensions configured in ini file.");
      }

      /*
       * Remove online info from database.
       */
      DAORegister.get().get(StendhalWebsiteDAO.class).clearOnlineStatus();
    } catch (final Exception e) {
      logger.error("cannot set Context. exiting", e);
      System.exit(-1);
    }
  }

  public boolean checkGameVersion(final String game, final String version) {
    try {
      if (!game.equals(Configuration.getConfiguration().get("server_typeGame", "stendhal"))) {
        logger.warn("Client for game " + game + " is trying to login to server for game "
            + Configuration.getConfiguration().get("server_typeGame", "stendhal")
            + ", as defined in server configuration file (usually server.ini) with key server_typeGame (defaults to \"stendhal\" if not present).");
        return false;
      }
      if (Debug.VERSION.compareTo(version) > 0) {
        logger.warn("Client version: " + version);
      }
      return true;
    } catch (IOException e) {
      logger.error(e, e);
      return false;
    }
  }

  /**
   * Kills an RPEntity.
   *
   * @param entity
   * @param killer
   */
  public void killRPEntity(final RPEntity entity, final Entity killer) {
    entityToKill.add(new Pair<RPEntity, Entity>(entity, killer));
  }

  /**
   * Checks whether the given RPEntity has been killed this turn.
   *
   * @param entity
   *            The entity to check.
   * @return pair of killed, killer if the specified entity did logout while dying, <code>null</code> otherwise.
   */
  private Pair<RPEntity, Entity> removedKilled(final RPEntity entity) {
    Iterator<Pair<RPEntity, Entity>> itr = entityToKill.iterator();
    while (itr.hasNext()) {
      Pair<RPEntity, Entity> entry = itr.next();
      if (entity.equals(entry.first())) {
        itr.remove();
        return entry;
      }
    }
    return null;
  }

  public void removePlayerText(final Player player) {
    playersRmText.add(player);
  }

  /**
   * Finds an online player with a specific name.
   *
   * @param name
   *            The player's name
   * @return The player, or null if no player with the given name is currently
   *         online.
   */
  public Player getPlayer(final String name) {
    return onlinePlayers.getOnlinePlayer(name);
  }

  public boolean onActionAdd(final RPObject caster, final RPAction action, final List<RPAction> actionList) {
    return true;
  }

  public void execute(final RPObject caster, final RPAction action) {
    CommandCenter.execute(caster, action);
  }

  public int getTurn() {
    return rpman.getTurn();
  }

  /** Notify it when a new turn happens. */
  public synchronized void beginTurn() {
    final long start = System.nanoTime();

    try {
      destroyObsoleteZones();
    } catch (final Exception e) {
      logger.error("error in beginTurn", e);
    }

    try {
      logNumberOfPlayersOnline();
    } catch (final Exception e) {
      logger.error("error in beginTurn", e);
    }

    try {
      handleKilledEntities();
    } catch (final Exception e) {
      logger.error("error in beginTurn", e);
    }

    try {
      executePlayerLogic();
    } catch (final Exception e) {
      logger.error("error in beginTurn", e);
    }

    try {
      executeNPCsPreLogic();
    } catch (final Exception e) {
      logger.error("error in beginTurn", e);
    }

    try {
      handlePlayersRmTexts();
    } catch (final Exception e) {
      logger.error("error in beginTurn", e);
    }
    logger.debug("Begin turn: " + (System.nanoTime() - start) / 1000000.0);
  }

  private void destroyObsoleteZones() {
    StendhalRPWorld world = SingletonRepository.getRPWorld();
    for (StendhalRPZone zone : zonesToRemove) {
      zone.onRemoved();
      world.removeZone(zone);
    }
    zonesToRemove.clear();

  }

  protected void logNumberOfPlayersOnline() {
    // We keep the number of players logged.
    Statistics.getStatistics().set("Players logged", getOnlinePlayers().size());
  }

  protected void handlePlayersRmTexts() {
    for (final Player player : playersRmText) {
      if (player.has("text")) {
        player.remove("text");
        player.notifyWorldAboutChanges();
      }
    }
    playersRmText.clear();
  }

  protected void executeNPCsPreLogic() {
    // SpeakerNPC logic
    final NPCList npcList = SingletonRepository.getNPCList();
    final Set<String> npcs = npcList.getNPCs();
    for (final String npc : npcs) {
      npcList.get(npc).preLogic();
    }
  }

  protected void executePlayerLogic() {
    getOnlinePlayers().forAllPlayersExecute(new Task<Player>() {
      public void execute(final Player player) {
        try {
          player.logic();
        } catch (final Exception e) {
          logger.error("Error in player logic", e);
        }
      }
    });
  }

  protected void handleKilledEntities() {
    /*
     * This is here because there is a split between last hit and the moment
     * a entity die so that the last hit is visible on client.
     */
    // In order for the last hit to be visible dead happens at two
    // steps.
    for (final Pair<RPEntity, Entity> entry : entityToKill) {
      try {
        entry.first().onDead(entry.second());
      } catch (final Exception e) {
        logger.error("error while handling killed entities", e);
      }
    }
    entityToKill.clear();
  }


  public synchronized void endTurn() {
    final int currentTurn = getTurn();
    try {

      SingletonRepository.getTurnNotifier().logic(currentTurn);

      for (final IRPZone zoneI : SingletonRepository.getRPWorld()) {
        final StendhalRPZone zone = (StendhalRPZone) zoneI;
        zone.logic();
      }

      // run registered object's logic method for this turn

    } catch (final Exception e) {
      logger.error("error in endTurn", e);
    }
  }

  /**
   * reads the admins from admins.list.
   *
   * @param player
   *            Player to check for super admin status.
   */
  private static void readAdminsFromFile(final Player player) {
    synchronized(StendhalRPRuleProcessor.class) {
      if (adminNames == null) {
        Map<String, String> tempAdminNames = new HashMap<String, String>();

        String adminFilename = "data/conf/admins.txt";

        try {
          InputStream is = player.getClass().getClassLoader().getResourceAsStream(
              adminFilename);

          if (is == null) {
            logger.info(adminFilename + " does not exist.");
            return;
          }

          final BufferedReader in = new BufferedReader(
              new UnicodeSupportingInputStreamReader(is));
          try {
            String line;
            while ((line = in.readLine()) != null) {
              String[] tokens = line.split("=");
              if (tokens.length >= 2) {
                tempAdminNames.put(tokens[0].trim(), tokens[1].trim());
              } else {
                tempAdminNames.put(tokens[0].trim(), Integer.toString(AdministrationAction.REQUIRED_ADMIN_LEVEL_FOR_SUPER));
              }
            }
          } catch (final Exception e) {
            logger.error("Error loading admin names from: "+ adminFilename, e);
          } finally {
            in.close();
          }

          adminNames = tempAdminNames;
        } catch (final IOException e) {
          logger.error("Error loading admin names from: " + adminFilename, e);
        }
      }
    }

    if (adminNames.get(player.getName()) != null) {
      player.setAdminLevel(Integer.parseInt(adminNames.get(player.getName())));
    }
  }

  public synchronized boolean onInit(final RPObject object) {
    try {
      if (object == null) {
        logger.error("onInit: object = null", new Throwable());
        return false;
      }

      if (object instanceof Player) {
        Player player = (Player) object;

        playersRmText.add(player);

        // place the player and his pets into the world
        PlayerTransformer.placePlayerIntoWorldOnLogin(object, player);
        PlayerTransformer.placeSheepAndPetIntoWorld(player);
        player.notifyWorldAboutChanges();
        StendhalRPAction.transferContent(player);

        getOnlinePlayers().add(player);

        if (!player.isGhost()) {
          notifyOnlineStatus(true, player);
          DBCommand command = new SetOnlineStatusCommand(player.getName(), true);
          DBCommandQueue.get().enqueue(command);
        }
        updatePlayerNameListForPlayersOnLogin(player);
        String[] params = {};

        new GameEvent(player.getName(), "login", params).raise();
        SingletonRepository.getLoginNotifier().onPlayerLoggedIn(player);
        TutorialNotifier.login(player);

        readAdminsFromFile(player);
        welcome(player);
        return true;
      } else {
        logger.error("onInit: object is not an instance of Player: " + object, new Throwable());
        return false;
      }
    } catch (final Exception e) {
      final String id;
      if (object != null) {
        id = object.get("#db_id");
      } else {
         id = "<object is null>";
      }
      logger.error("There has been a severe problem loading player " + id, e);
      return false;
    }
  }
  /**
   * send a welcome message to the player which can be configured in
   * marauroa.ini file as "server_welcome". If the value is an http:// address,
   * the first line of that address is read and used as the message
   *
   * @param player
   *            Player
   */
  static void welcome(final Player player) {
    String msg = welcomeMessage;
    try {
      final Configuration config = Configuration.getConfiguration();
      if (config.has("server_welcome")) {
        msg = config.get("server_welcome");
        if (msg.startsWith("http://")) {
          final URL url = new URL(msg);
          HttpURLConnection.setFollowRedirects(false);
          final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                    try {
                        final BufferedReader br = new BufferedReader(
              new InputStreamReader(connection.getInputStream()));
                        try {
                            msg = br.readLine();
                        } finally {
                            br.close();
                        }
                    } finally {
                connection.disconnect();
                    }
        }
      }
    } catch (final IOException e) {
      if (firstWelcomeException) {
        logger.warn("Can't read server_welcome from marauroa.ini", e);
        firstWelcomeException = false;
      }
    }
    if (msg != null) {
      player.sendPrivateText(msg);
    }
  }

  public synchronized boolean onExit(final RPObject object) {
    if (object instanceof Player) {
      try {
        final Player player = (Player) object;

        Pair<RPEntity, Entity> entry = removedKilled(player);
        if (entry != null) {
          logger.info(object.get("name") + " logged out shortly before death: Killing it now :)");
          entry.first().onDead(entry.second());
        }

        if (!player.isGhost()) {
          notifyOnlineStatus(false, player);
        }
        updatePlayerNameListForPlayersOnLogout(player);

        Player.destroy(player);
        getOnlinePlayers().remove(player);

        DBCommand command = new SetOnlineStatusCommand(player.getName(), false);
        DBCommandQueue.get().enqueue(command);

        String[] params = {};
        new GameEvent(player.getName(), "logout", params).raise();
        logger.debug("removed player " + player);

        return true;
      } catch (final Exception e) {
        logger.error("error in onExit", e);
        return true;
      }
    } else {
      logger.error("object is no Player, but: " + object, new Throwable());
      return true;
    }
  }

  public synchronized void onTimeout(final RPObject object) {
    onExit(object);
  }

  public AccountResult createAccount(final String username, final String password, final String email) {
    final AccountCreator creator = new AccountCreator(username, password, email);
    return creator.create();
  }

  public CharacterResult createCharacter(final String username, final String character, final RPObject template) {
    final CharacterCreator creator = new CharacterCreator(username, character, template);
    return creator.create();
  }

  public RPServerManager getRPManager() {
    return rpman;
  }

  /**
   * Tell this message all players.
   *
   * @param notificationType type of notification message, usually NotificationType.PRIVMSG
   * @param message
   *            Message to tell all players
   */
  public void tellAllPlayers(NotificationType notificationType, final String message) {
    onlinePlayers.tellAllOnlinePlayers(notificationType, message);
  }

  /**
   * Sends a message to all supporters.
   *
   * @param message Support message
   */
  public void sendMessageToSupporters(final String message) {
    getOnlinePlayers().forFilteredPlayersExecute(

    new Task<Player>() {

      public void execute(final Player player) {
        player.sendPrivateText(NotificationType.SUPPORT, message);
        player.notifyWorldAboutChanges();
      }

    },

    new FilterCriteria<Player>() {

      public boolean passes(final Player p) {
        return p.getAdminLevel() >= AdministrationAction.REQUIRED_ADMIN_LEVEL_FOR_SUPPORT;
      }

    });

    logger.log(Level.toLevel(System.getProperty("stendhal.support.loglevel"), Level.DEBUG), message);
  }

  /**
   * Sends a message to all supporters.
   *
   * @param source
   *            a player or script name
   * @param message
   *            Support message
   */
  public void sendMessageToSupporters(final String source, final String message) {
    final String text = source + " asks for support to ADMIN: " + message;
    sendMessageToSupporters(text);
  }

  public static int getAmountOfOnlinePlayers() {
    return SingletonRepository.getRuleProcessor().onlinePlayers.size();
  }

  /**
   * Notifies buddies about going online/offline.
   *
   * @param isOnline did the player login?
   * @param playerToNotifyAbout name of the player
   */
  public void notifyOnlineStatus(final boolean isOnline, final Player playerToNotifyAbout) {
    if (instance != null) {
      if (isOnline) {
        SingletonRepository.getRuleProcessor().getOnlinePlayers().forAllPlayersExecute(new Task<Player>() {
          public void execute(final Player player) {
            player.notifyOnline(playerToNotifyAbout.getName());
          }
        });

      } else {
        SingletonRepository.getRuleProcessor().getOnlinePlayers().forAllPlayersExecute(new Task<Player>() {
          public void execute(final Player player) {
            player.notifyOffline(playerToNotifyAbout.getName());
          }
        });
      }
    }
  }

  /**
   * Update all player's lists of online player names on login of a new player
   *
   * @param playerToNotifyAbout
   */
  private void updatePlayerNameListForPlayersOnLogin(final Player playerToNotifyAbout) {
    SingletonRepository.getRuleProcessor().getOnlinePlayers().forAllPlayersExecute(new Task<Player>() {
      public void execute(final Player player) {
        if(playerToNotifyAbout.isGhost()) {
          playerToNotifyAbout.addEvent(new PlayerLoggedOnEvent(player.getName()));
          if (player.isGhost()) {
            player.addEvent(new PlayerLoggedOnEvent(playerToNotifyAbout.getName()));
          }
        } else {
          player.addEvent(new PlayerLoggedOnEvent(playerToNotifyAbout.getName()));
          if (!player.isGhost()) {
            playerToNotifyAbout.addEvent(new PlayerLoggedOnEvent(player.getName()));
          }
        }
      }
    });
  }

  /**
   * Update all player's lists of online player names on login of a new player
   *
   * @param playerToNotifyAbout
   */
  private void updatePlayerNameListForPlayersOnLogout(final Player playerToNotifyAbout) {
    SingletonRepository.getRuleProcessor().getOnlinePlayers().forAllPlayersExecute(new Task<Player>() {
      public void execute(final Player player) {
        player.addEvent(new PlayerLoggedOutEvent(playerToNotifyAbout.getName()));
      }
    });
  }

  /**
   * Removes a zone (like a personalized vault).
   *
   * @param zone StendhalRPZone to remove
   */
  public void removeZone(final StendhalRPZone zone) {
    zonesToRemove.add(zone);
  }

  /**
   * Sets the welcome message.
   *
   * @param msg welcome message
   */
  public static void setWelcomeMessage(String msg) {
    StendhalRPRuleProcessor.welcomeMessage = msg;
  }

  /**
   * gets the content type for the requested resource
   *
   * @param resource name of resource
   * @return mime content/type or <code>null</code>
   */
  public String getMimeTypeForResource(String resource) {
    if (resource.endsWith(".tmx")) {
      return "text/xml";
    } else if (resource.endsWith(".tmx")) {
      return "audio/ogg";
    } else if (resource.endsWith(".png")) {
      return "image/png";
    }
    return null;
  }

  /**
   * gets an input stream to the requested resource
   *
   * @param resource name of resource
   * @return InputStream or <code>null</code>
   */
  public InputStream getResource(String resource) {
    if (resource.startsWith("/tiled") || resource.startsWith("/data")) {
      return StendhalRPRuleProcessor.class.getClassLoader().getResourceAsStream(resource.substring(1));
    }
    if (resource.startsWith("/tileset")) {
      return StendhalRPRuleProcessor.class.getClassLoader().getResourceAsStream("tiled" + resource);
    }
    return null;
  }
}
TOP

Related Classes of games.stendhal.server.core.engine.StendhalRPRuleProcessor

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.