Drupal RPG Creator - Mann gegen Mann

Neben dem obligatorischen Würfelspiel wird sich wohl jeder einigermaßen Spiele-interessierte Programmierer einmal an einem Rollenspiel versuchen. Eines meiner Langzeitprojekte (lat.: Projekte die ewig dauern und nie fertig werden) ist der Drupal RPG Creator. Bei keinem anderen Projekt habe ich so oft alles wieder über den Haufen geworfen und von vorne angefangen.

Wer sich ein wenig in der Rollenspielwelt auskennt, der wird sicher schon einmal über einen der zahlreichen RPG Creator gestolpert sein, die durch das Netz geistern und sich größerer oder kleinerer Popularität erfreuen. Mit dem Drupal RPG Creator soll es irgendwann, einmal in ferner Zukunft, möglich sein, sich sein persönliches Rollenspiel zusammen zu klicken. Der RPGC ist momentan eine Reihe von (unveröffentlichten) Drupalmodulen, die ein leicht erweiterbares Grundgerüst für ein Rollenspiel bieten. Als Fokus für den Creator habe ich hier vor allem Spielinteressierte im Auge, die nicht oder nur wenig programmieren können.

Eine der Konstanten im Spiel ist das (default) Battleskript. Default deswegen, da man problemlos sein eigenes Skript wird verwenden können. Ich habe mich hier im weitesten Sinne an den Charakterwerten von Diablo orientiert, das System funktioniert allerdings nach bewährter Rundenlogik. Zwei Charaktere (Angreifer und Verteidiger) treffen aufeinander. Beide verfügen über Angriffskraft, Schaden, Rüstung, Verteidigung und Lebensenergie. Anhand dieser Werte wird der Schaden berechnet, den sich die beiden jeweils zufügen können, sowie ihre jeweilige Chance zu treffen (cth).

/**
 * 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,
  );
}

Ich lasse die Kommentare im Skript für sich selbst sprechen, einige Worte möchte ich allerdings noch zu den Rückgabewerten verlieren. Die Funktion gibt ein Array mit vier Einzelwerten zurück. attacker und defender sind dabei die veränderten Objekte der beiden Charaktere. result ist ein Integer-Wert, der den Kampfausgang (Angreifer gewinnt, Verteidiger gewinnt, Unentschieden) definiert.

In den rounds sind alle erzielten oder nicht erzielten Treffer der beiden Charaktere gespeichert, sowie der jeweils hinzugefügte Schaden. Diese kann man zum Beispiel verwenden, um dem User einen animierten Kampf anzuzeigen und so ein etwas dynamischeres Spielerlebnis zu bieten.

Noch keine Kommentare vorhanden.