Drupal RPG Creator - Mano-A-Mano

Besides the obligatory dice game, almost every game-interested developer might have tried to create a role-playing game. One of my long-term-projects (translation: projects, which take forever and are never finished) is the Drupal RPG Creator. There is no other project, where I have as frequently thrown over everything, just to start over again.

Anyone who is familiar with the world of role-playing games, might at some point have stumbled upon one of the numerous RPG creation software solutions. These tools are more or less popular and can be found all over the internet. Some might even be entirely web-based. With the Drupal RPG Creator it will be possible to just click together a personal ready-to-use RPG some day. Right now it is just a set of (unpublished) modules and classes, which should provide an easily extensible framework for other modules. My focus for the RPG Creator are people, who like to create games themselves, but are not skilled programmers.

One of the constants of this project is the (default) battle script. Default, because you will be able to replace it with any custom script you want. Concerning the character values, I was inspired by Diablo. Also the script is actually round based. It assumes that two characters (attacker and defender) engage in a battle. Both characters have different values for attack strength, damage, armor, defense and life points. The damage dealt and the chance to hit their opponent (cth) is calculated based on these values.

/**
 * Attacker kills the defender. Yeah! This is the return value.
 */
define('RPGC_BATTLE_ATT_WINS', 1);

/**
 * Return value, if none of the characters could win a battle. Bah, lame!
 */
define('RPGC_BATTLE_DRAW', 0);

/**
 * Defender successfully defends himself, making his name count.
 */
define('RPGC_BATTLE_DEF_WINS', -1);

/**
 * Starting health of each character in the battle.
 * This is meant to be seen as a percentage value.
 */
define('RPGC_BATTLE_INIT_HEALTH', 100);

/**
 * Returns the results of a battle between two characters.
 * One of them is the attacker, the other one defends himself.
 */
function rpgc_solve_battle($attacker = NULL, $defender = NULL) {
  if (empty($attacker) || empty($defender)) {
    return FALSE;
  }

  $rounds = array();

  // calculate chance to hit (cth) for attacker and defender, this will be a percent rate
  $attacker->cth = (($attacker->attack - $defender->defense) / $attacker->attack) * 100;
  $defender->cth = (($defender->attack - $attacker->defense) / $defender->attack) * 100;

  // no negative values allowed, otherwise this fighting system won't work
  if ($attacker->cth < 0) $attacker->cth = 0;
  if ($defender->cth < 0) $defender->cth = 0;

  // if both cth equal zero, the fighters won't be able to hit each other, so let's just return
  if ($attacker->cth == 0 && $defender->cth == 0) {
    $result = RPGC_BATTLE_DRAW;
  }
  else {
    // calculate damage attacker and defender will be able to deal to each other
    $attacker->dmg_rate = (($attacker->damage - $defender->armor) / $attacker->damage) * 100;
    $defender->dmg_rate = (($defender->damage - $attacker->armor) / $defender->damage) * 100;

    // same as above
    if ($attacker->dmg_rate < 0) $attacker->dmg_rate = 0;
    if ($defender->dmg_rate < 0) $defender->dmg_rate = 0;

    // if both fighters can't deal any damage, it might be better to just break here
    if ($attacker->dmg_rate == 0 && $defender->dmg_rate == 0) {
      $result = RPGC_BATTLE_DRAW;
    }
    else {
      // to determin when the fight is over, both fighters get a fake health bar
      $attacker->health = $defender->health = RPGC_BATTLE_INIT_HEALTH;

      // now both fighters are prepared, let's begin the fighting
      while ($attacker->health > 0 && $defender->health > 0) {
        $round = array();

        // the attacker started the battle, so he begins
        $round['attacker_roll'] = rand(0, 100);

        // if the throw is smaller then the chance to hit, he hits the defender
        if ($round['attacker_roll'] <= $attacker->cth) {
          $round['attacker_damage'] = rand(0, $attacker->dmg_rate);
          $defender->health-= $round['attacker_damage'];
        }

        // if the defender is still alive, it's his turn now
        if ($attacker->dmg_rate > 0 && $defender->health > 0) {
          $round['defender_roll'] = rand(0, 100);
          if ($round['defender_roll'] <= $defender->cth) {
            $round['defender_damage'] = rand(0, $defender->dmg_rate);
            $attacker->health-= $round['defender_damage'];           
          }
        }

        $round['attacker_health'] = $attacker->health;
        $round['defender_health'] = $defender->health;

        $rounds[] = (object) $round;
      }

      // check who won this fight
      if ($attacker->health <= 0) {
        $result = RPGC_BATTLE_DEF_WINS;
      }
      else {
        $result = RPGC_BATTLE_ATT_WINS;
      }
    }
  }

  // Gather information for return.
  return array(
    'attacker' => $attacker,
    'defender' => $defender,
    'result'   => $result,
    'rounds'   => $rounds,
  );
}

I will let the comments speak for themselves, but I want to add a few words concerning the returned values. The function returns an array with four single values. attacker and defender are the updated character objects. result is an integer value, which defines the outcome of the encounter. Possible values are: Attacker wins, defender wins or draw.

rounds contains information about every single round of the battle, especially the damage dealt to each other. Developers can use these values to create a graphical and even animated version of the fight. This might come in handy, to create a more dynamic user experience.