素人が RPG の開発に挑戦してみる 4

大幅な方針転換。戦闘ルールを自前で用意することは諦め、既存のシステムから借用することにした。先ほど本棚の置くから引っ張り出してきたグループ SNE のソードワールドは、個人的に非常に馴染み深いシステムだ。ルールは十分に把握しており、今回の目的にはうってつけである。

ところでそのソードワールドだが、今週末にはなんと 20 年ぶりの全面改定となる「ソードワールド 2.0」が発売される。舞台はフォーセリアから新世界ラクシアへと移り、うさぎ人間だのロボメイドだのデビルマンだのと新要素もてんこ盛りだそうな。真剣に追跡していたわけではないので、実は状況がさっぱり把握できていないのだがな。知人のメールで初めてその存在を明確に認識したのは今月の頭の話だし。それでも懐かしさの余り、リプレイと一緒に速攻で予約注文してしまったよ。


しかし残念ながら今手元にあるのは改定前の完全版であった。2.0 が手元に届いたら追従しよう。何やら当初の目的を見失いつつあるが、そんなのはいつものことである。サンプルキャラクター、ダッカード君、出撃。敵は君自身だ。

名 前:ダッカード・サンプル II
器用度:16(+2)
敏捷度:12(+2)
知 力:13(+2)
筋 力:14(+2)
生命力:18(+3)/ 抵抗力 5
精神力:14(+2)/ 抵抗力 4
技 能:ファイター 2、レンジャー 1
武 器:ブロードソード 14
 盾 :スモールシールド
 鎧 :リングメイル 7

うわあ、懐かしい。高校生の頃は毎週サイコロ振ってたっけなあ。


戦闘の処理手順は下記の通り。

  1. まずは行動順判定。敏捷度を比較する。補正はなし。敏捷度が同じ場合は同時処理となり、先手の攻撃が致命傷を与えても後手の攻撃は実行される。
  2. 続いて命中判定。キャラクター同士が戦う場合、「攻撃に使用する技能 + 器用度ボーナス + 2D」の値が相手の「防御に使用する技能 + 敏捷度ボーナス + 2D」を上回るか、2D が 12、つまり 6 ゾロであれば命中となる。ただし相手が回避で 6 ゾロを出した場合はそちらが優先される。
  3. 命中したらダメージ決定。武器の必要筋力と 2D6 でレーティング表からダメージを決定する。ファイター技能なら 10 以上出し続ける限りダメージが加算されてゆく。最後に筋力ボーナスと技能レベルを足して打撃力が決定される。防御側は鎧の必要筋力と 2D6 でダメージ軽減値を算出し、技能レベルを足した合計値を打撃力から引いたものが最終的なダメージとなる。
RATING_SHEET = {
   7 => [nil, nil, nil, 0, 1, 1, 2, 3, 4, 4 ,5, 5, 6],
  14 => [nil, nil, nil, 1, 2, 3, 4, 4, 4, 5, 6 ,7 ,8],
}

class Judge
  def resolve(a, b)
    offense, defense = [a, b].sort{|a, b| b.nimble <=> a.nimble}
    turn = 1.0
    loop do
      puts "#{sprintf('%2d', turn)} ターン" if turn % 1 == 0
      turn += 0.5
      print "  #{offense.name} の攻撃: "
      damage = offense.attack(defense)
      begin
        unless damage
          puts "攻撃は外れた"
          next
        end
        puts "#{damage} のダメージを与えた"
        if turn % 1 == 0 || offense.nimble != defense.nimble
          puts "  #{a.name} は倒れた" unless a.active?
          puts "  #{b.name} は倒れた" unless b.active?
          next if offense.active? && defense.active?
          break
        end
      ensure
        # 攻守交代
        offense, defense = defense, offense
      end
    end
  end
end

class Character
  attr_accessor :name, :life, :deft, :nimble, :power
  attr_accessor :weapon, :shield, :armor
  attr_accessor :fighter, :thief, :ranger

  def roll_2d6
    (rand(6) + 1) + (rand(6) + 1)
  end

  def attack(d)
    spots = roll_2d6
    return nil if spots == 2
    unless spots == 12
      return nil if d.dodged?(bonus(@deft) + spots)
    end
    d.damage(power)
  end

  def dodged?(a)
    spots = roll_2d6
    return true if spots == 12
    return false if spots == 2
    a <= bonus(@nimble) + spots + (@shield ? 1 : 0)
  end

  def damage(p)
    return nil unless p
    d = p - prevent
    d = (d > 0 ? d : 0)
    @life -= d
    d
  end

  def power
    result = nil
    loop do
      spots = roll_2d6
      unless spots == 2
        result ||= 0
        result += RATING_SHEET[@weapon][spots]
      end
      break if spots < 10
    end
    result += @fighter + bonus(@power) if result
    result
  end

  def prevent
    spots = roll_2d6
    return 0 if spots == 2
    RATING_SHEET[@armor][spots] + @fighter
  end

  def active?
    @life > 0
  end

  def bonus(p)
    p / 6
  end
end

a = Character.new
a.name = 'デッカード A'
a.life = 18
a.deft = 16
a.nimble = 12
a.power = 14
a.weapon = 14
a.shield = true
a.armor = 7
a.fighter = 2

b = Character.new
b.name = 'デッカード B'
b.life = 18
b.deft = 16
b.nimble = 12
b.power = 14
b.weapon = 14
b.shield = true
b.armor = 7
b.fighter = 2

Judge.new.resolve(a,b)

シーフ技能とレンジャー技能は無視。生存判定無視。結果はこちら。いい感じに相打ち。

第 1 ターン
  デッカード A の攻撃: 1 のダメージを与えた
  デッカード B の攻撃: 攻撃は外れた
第 2 ターン
  デッカード A の攻撃: 6 のダメージを与えた
  デッカード B の攻撃: 4 のダメージを与えた
第 3 ターン
  デッカード A の攻撃: 攻撃は外れた
  デッカード B の攻撃: 攻撃は外れた
第 4 ターン
  デッカード A の攻撃: 攻撃は外れた
  デッカード B の攻撃: 2 のダメージを与えた
第 5 ターン
  デッカード A の攻撃: 9 のダメージを与えた
  デッカード B の攻撃: 11 のダメージを与えた
第 6 ターン
  デッカード A の攻撃: 1 のダメージを与えた
  デッカード B の攻撃: 0 のダメージを与えた
第 7 ターン
  デッカード A の攻撃: 3 のダメージを与えた
  デッカード B の攻撃: 3 のダメージを与えた
  デッカード A は倒れた
  デッカード B は倒れた

もちろん何度も繰り返せば、A が勝つことも B が勝つこともある。やれやれ。お疲れ。

2008/04/14 追記

バグ発見。攻撃側と防御側が 6 ゾロ同士なら防御が優先されるので、こっちが正解。ちょっと美しくないかも。

  def attack(d)
    spots = roll_2d6
    return nil if spots == 2
    return nil if d.dodged?(bonus(@deft), spots)
    d.damage(power)
  end

  def dodged?(a, a_spots)
    spots = roll_2d6
    return true if spots == 12
    return false if a_spots == 12
    return false if spots == 2
    a + a_spots <= bonus(@nimble) + spots + (@shield ? 1 : 0)
  end


ソード・ワールド2.0 ルールブック I (富士見ドラゴン・ブック)

ソード・ワールド2.0 ルールブック I (富士見ドラゴン・ブック)