Package games.stendhal.tools

Source Code of games.stendhal.tools.BalanceRPGame

/* $Id: BalanceRPGame.java,v 1.62 2011/06/21 15:15:59 madmetzger 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.tools;

import games.stendhal.server.core.config.CreatureGroupsXMLLoader;
import games.stendhal.server.core.engine.RPClassGenerator;
import games.stendhal.server.core.engine.SingletonRepository;
import games.stendhal.server.core.rule.EntityManager;
import games.stendhal.server.core.rule.defaultruleset.DefaultCreature;
import games.stendhal.server.entity.creature.Creature;
import games.stendhal.server.entity.item.Item;
import games.stendhal.server.entity.player.Player;

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

import marauroa.common.Pair;

/*
* Running:
*   java -cp $CLASSPATH java -cp games/stendhal/tools/BalanceRPGame
*     calculates balanced atk and def values to all creatures up to HIGHEST_LEVEL
*     (defined in the code below)
*   java -cp $CLASSPATH java -cp games/stendhal/tools/BalanceRPGame creature
*     calculates the values only for the specified creature
*
*   CLASSPATH should be (with appropriate changes for OS and versions), assuming
*     the compiled .class for this file is under "bin":
*    .:bin:libs/marauroa.jar:libs/log4j.jar:libs/mysql-connector-java-5.1.5-bin.jar:build/lib/stendhal-stendhal-server-0.71.jar
*/
/** * NOTE: AWFUL CODE FOLLOWS. YOU ARE NOT SUPPOSED TO READ THIS ;P ** */

public class BalanceRPGame {
  /**
   * A Simple (dumb) optimizer to adjust creature stats.
   */
  private static class Optimizer {
    Creature creature;
   
    public Optimizer(final Creature creature) {
      this.creature = creature;
    }
   
    public void step(final int leftHP, final int rounds) {
      float stepSize = leftHP / (float) player.getBaseHP();
      stepSize = Math.signum(stepSize) * Math.min(Math.abs(stepSize), 0.5f);
     
      final int oldAtk = creature.getAtk();
      int newAtk = Math.max(1, Math.round(creature.getAtk()
          + stepSize * creature.getAtk()));
      // Always ensure trying to get out of dead area
      if ((leftHP < 0) && (newAtk == oldAtk)) {
        newAtk--;
      }
     
      final int level = creature.getLevel();
      final int oldDef = creature.getDef();
      int newDef = oldDef;
      final double preferred = preferredDuration(level);
      if (!isWithinDurationRange(preferred, rounds)) {
        // Don't grow it the monster is already stronger than the player
        if ((leftHP > 0) || (preferred < rounds)) {
        newDef = Math.max(1, (int) (creature.getDef()
            + preferred - rounds + 0.5));
        }
      } else {
        newDef =   Math.max(1, (int) (creature.getDef()
          + 5 * stepSize * creature.getDef() + 0.5f));
      }
      // Don't change too fast
      if (newDef > 1.1 * oldDef) {
        newDef = Math.max((int) (1.1 * oldDef), oldDef + 1);
      } else if (newDef < 0.9 * oldDef) {
        newDef = Math.max(1, Math.min((int) (0.9 * oldDef), oldDef - 1));
      }
 
      creature.setAtk(newAtk);
      creature.setDef(newDef);
    }
  }
 
  private static final int ROUNDS = 100;
  private static final int HIGHEST_LEVEL = 500;
  private static final double DEFAULT_DURATION_THRESHOLD = 0.2;
  private static double durationThreshold;
  private static Player player;

  public static void main(final String[] args) throws Exception {
    new RPClassGenerator().createRPClasses();
    final CreatureGroupsXMLLoader loader = new CreatureGroupsXMLLoader("/data/conf/creatures.xml");
    final List<DefaultCreature> creatures = loader.load();

    Collections.sort(creatures, new Comparator<DefaultCreature>() {

      public int compare(final DefaultCreature o1, final DefaultCreature o2) {
        return o1.getLevel() - o2.getLevel();
      }
    });

    final int[] atkLevels = new int[HIGHEST_LEVEL + 1];
    final int[] defLevels = new int[HIGHEST_LEVEL + 1];

    for (int level = 0; level < atkLevels.length; level++) {
      // help newbies a bit, so don't start at real stats, but a bit lower
      atkLevels[level] = (int) Math.round(Math.log(level + 4) * - 10);
      defLevels[level] = (int) Math.round(Math.log(level + 4) * 20
          + level - 26);
    }

    final EntityManager em = SingletonRepository.getEntityManager();

    final Item shield = em.getItem("wooden shield");

    final Item armor = em.getItem("dress");

    final Item helmet = em.getItem("leather helmet");

    final Item legs = em.getItem("leather legs");

    final Item boots = em.getItem("leather boots");

    player = Player.createZeroLevelPlayer("Tester", null);

    player.equipToInventoryOnly(shield);
    player.equipToInventoryOnly(armor);
    player.equipToInventoryOnly(helmet);
    player.equipToInventoryOnly(legs);
    player.equipToInventoryOnly(boots);

    final boolean found = false;

    for (final DefaultCreature creature : creatures) {
      final int level = creature.getLevel();
     
      if (args.length > 0) {
        if (!args[0].equals(creature.getCreatureName()) && !found) {
          continue;
        }
      }
      if (creature.getLevel() > HIGHEST_LEVEL) {
        continue;
      }

      final Creature target = creature.getCreature();
      final Optimizer optimizer = new Optimizer(target);
     
      player.setLevel(level);
      player.setBaseHP(100 + 10 * level);
      player.setAtk(atkLevels[level]);
      player.setDef(defLevels[level]);

      equip(player, level);

      System.out.println("Player(" + level + ") vs "
          + creature.getCreatureName());

      durationThreshold = DEFAULT_DURATION_THRESHOLD;
     
      boolean balanced = false;
      int tries = 0;
      while (!balanced) {
        final Pair<Integer, Integer> results = combat(player, target,
            ROUNDS);
        final int meanTurns = results.first();
        final int meanLeftHP = results.second();

        final int proposedXPValue = (int) ((2 * creature.getLevel() + 1) * (meanTurns / 2.0));
        creature.setLevel(creature.getLevel(), proposedXPValue);
       
        System.out.println("Target ATK: "
            + target.getAtk()
              + "/DEF: "
              + target.getDef()
              + "/HP: "
              + target.getBaseHP()
              + "\t Turns: "
              + meanTurns
              + "\tLeft HP:"
              + meanLeftHP);
         
        if (isCorrectResult(level, meanTurns, meanLeftHP / (double) player.getBaseHP())) {
          balanced = true;
        } else {   
          optimizer.step(meanLeftHP, meanTurns);

          System.out.println("New ATK: " + target.getAtk()
              + "/DEF: " + target.getDef() + "/HP: "
              + target.getBaseHP());
        }
       
        // relax convergence criteria for pathological cases
        tries++;
        if (tries % 200 == 0) {
          durationThreshold *= 1.1;
          System.out.println(target.getName() + ": changed threshold to " + durationThreshold);
        }
      }

      boolean changed = false;

      if (creature.getAtk() != target.getAtk()) {
        changed = true;
      }

      if (creature.getDef() != target.getDef()) {
        changed = true;
      }

      if (creature.getHP() != target.getBaseHP()) {
        changed = true;
      }

      System.out.print("BALANCED: ");
      final StringBuilder stringBuilder = new StringBuilder();
      stringBuilder.append(creature.getCreatureName());
      stringBuilder.append("(");
      stringBuilder.append(creature.getLevel());
      stringBuilder.append(")\t");
      if (changed) {
        stringBuilder.append("*\t");
      } else {
        stringBuilder.append(" \t");
      }
      stringBuilder.append("ATK: ");
      stringBuilder.append(target.getAtk());
      stringBuilder.append("\t\tDEF: ");
      stringBuilder.append(target.getDef());
      stringBuilder.append("\t\tHP: ");
      stringBuilder.append(target.getBaseHP());
      stringBuilder.append("\t\tXP: ");
      stringBuilder.append(creature.getXP());
      System.out.println(stringBuilder.toString());
    }
  }

  private static Pair<Integer, Integer> combat(final Player player, final Creature target) {
    target.setHP(target.getBaseHP());
    player.setHP(player.getBaseHP());

    boolean combatFinishedWinPlayer = false;
    int turns = 0;
    int healAmount = 0;
    int healRate = 0;
   
    String healer = target.getAIProfile("heal");
    if (healer != null) {
      final String[] healingAttributes = healer.split(",");
      healAmount = Integer.parseInt(healingAttributes[0]);
      healRate = Integer.parseInt(healingAttributes[1]);
    }

    while (!combatFinishedWinPlayer) {
      turns++;
      if ((healAmount != 0) && (turns % healRate == 0)) {
        final int newHP = target.getHP() + healAmount;
        target.setHP(Math.min(target.getBaseHP(), newHP));
      }

      if ((turns % 5 == 0) && player.canHit(target)) {
        int damage = player.damageDone(target, player.getItemAtk(), player.getDamageType());
        if (damage < 0) {
          damage = 0;
        }

        target.setHP(target.getHP() - damage);
       
        if (target.getHP() <= 0) {
          combatFinishedWinPlayer = true;
          break;
        }
      }

      if ((turns % target.getAttackRate() == 0) && target.canHit(player)) {
        int damage = target.damageDone(player, target.getItemAtk(), player.getDamageType());
        if (damage < 0) {
          damage = 0;
        }
        player.setHP(player.getHP() - damage);
        target.handleLifesteal(target, target.getWeapons(), damage);
       
        if (player.getHP() <= 0) {
          combatFinishedWinPlayer = true;
          break;
        }
      }
    }

    return new Pair<Integer, Integer>(turns, player.getHP());
  }

  private static Pair<Integer, Integer> combat(final Player player, final Creature target,
      final int rounds) {
    int meanTurns = 0;
    int meanLeftHP = 0;

    for (int i = 0; i < rounds; i++) {
      final Pair<Integer, Integer> results = combat(player, target);
      meanTurns += results.first();
      meanLeftHP += results.second();
    }

    meanTurns = (int) (meanTurns / (rounds * 1.0));
    meanLeftHP = (int) (meanLeftHP / (rounds * 1.0));

    return new Pair<Integer, Integer>(meanTurns, meanLeftHP);
  }
 
  private static void equip(final Player p, final int level) {
    p.getWeapons().get(0).put("atk", 7 + level * 2 / 6);
    if (level == 0) {
      p.getShield().put("def", 0);
    } else {
      p.getShield().put("def", 12 + level / 8);
    }
    p.getArmor().put("def", 1 + level / 4);
    p.getHelmet().put("def", 1 + level / 7);
    p.getLegs().put("def", 1 + level / 7);
    p.getBoots().put("def", 1 + level / 10);
  }
 
  private static boolean isCorrectResult(final int level,
      final int meanTurns, final double relativeLeftHP) {
    if (!isWithinDurationRange(preferredDuration(level), meanTurns)) {
      return false;
    }

    if ((relativeLeftHP > 0.1) || (relativeLeftHP < 0)) {
      return false;
    }

    return true;
  }

  private static boolean isWithinDurationRange(final double preferred,
      final double real) {
    return (real < (1.0 + durationThreshold) * preferred)
      && (real > (1.0 - durationThreshold) * preferred);
  }

  private static double preferredDuration(final int level) {
    return 150 + level;
  }
}
TOP

Related Classes of games.stendhal.tools.BalanceRPGame

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.