bcdice/BCDice

View on GitHub
lib/bcdice/game_system/RuinBreakers.rb

Summary

Maintainability
C
1 day
Test Coverage
A
95%
# frozen_string_literal: true

require 'bcdice/arithmetic_evaluator'
require 'bcdice/dice_table/table'
require 'bcdice/dice_table/range_table'

module BCDice
  module GameSystem
    class RuinBreakers < Base
      # ゲームシステムの識別子
      ID = 'RuinBreakers'

      # ゲームシステム名
      NAME = 'ルーインブレイカーズ'

      # ゲームシステム名の読みがな
      SORT_KEY = 'るういんふれいかあす'

      # ダイスボットの使い方
      HELP_MESSAGE = <<~MESSAGETEXT
        ■ 基本判定 (RBx@y#z)
          x:成功率、y:クリティカル値(省略可)、z:ファンブル値(省略可)
          1D100を振って、成功率に応じて成功/失敗/クリティカル/ファンブルの判定を行います。(P.60)
          クリティカル値を省略した場合は成功率の5分の1(切り捨て、最低1)
          ファンブル値を省略した場合は、成功率が99以下の場合は96、100以上の場合は99
          例) RB32, RB(45+20)/2, RB30@10, RB35+20#90, RB40-20+10@10#90

        ■ FPへのダメージ (FPDx)
          x:破滅ポイント
          ルーインブレイクロール失敗時やラウンド終了時に、残っている
          破滅ポイントに応じて発生するダメージのダイスロールを行います。(P.91,92)
          例) FPD23

        ■ FPの回復 (FPRx)
          x:破滅ポイント
          ルーインブレイク成功時に発生する、FPの回復量を決定するダイスロールを行います。(P.93)
          例) FPR29

        ■ 各種表
          ・ポジティブ感情表 (PE)
          ・ネガティブ感情表 (NE)
          ・デウス・エクス・マキナ表 (DXM)
          ・断罪チャート (JC)
          ・破滅のイヤな感じ表 (RDF)
          ・トラブルチャート/トラブル解決チャート (TC)
          ・ドタバタアクション表 (DA)
      MESSAGETEXT

      def eval_game_system_specific_command(command)
        case command
        when /^RB/
          check_roll(command)
        when /^FPD/
          roll_fp_damage(command)
        when /^FPR/
          roll_fp_recovery(command)
        else
          roll_tables(command, TABLES)
        end
      end

      private

      def check_roll(command)
        m = %r{^RB(-?\d+([+\-*/]\d+)*)(@(\d+))?(#(\d+))?$}.match(command)
        unless m
          return nil
        end

        success_rate = ArithmeticEvaluator.eval(m[1])
        critical_border = m[4]&.to_i || [success_rate / 5, 1].max
        fumble_border = m[6]&.to_i || (success_rate < 100 ? 96 : 99)

        total = @randomizer.roll_once(100)

        result = Result.new

        compare_result =
          if total >= fumble_border
            result.fumble = true
            result.failure = true
            'ファンブル'
          elsif total == 1 || total <= critical_border
            result.critical = true
            result.success = true
            'クリティカル'
          elsif total <= success_rate
            result.success = true
            '成功'
          else
            result.failure = true
            '失敗'
          end

        sequence = [
          "(1D100<=#{success_rate}@#{critical_border}##{fumble_border})",
          total,
          compare_result
        ]

        result.text = sequence.join(" > ")
        result
      end

      def roll_fp_damage(command)
        m = /^FPD(\d+)$/.match(command)
        unless m
          return nil
        end

        ruin_point = m[1].to_i
        ruin_point_tens, ruin_point_ones = ruin_point.divmod(10)

        dice_list = @randomizer.roll_barabara(1 + ruin_point_tens, 10)
        total = dice_list.sum()
        dice_str = dice_list.join(",")

        sequence = [
          "((1+#{ruin_point_tens})D10+#{ruin_point_ones})",
          "#{total}[#{dice_str}]+#{ruin_point_ones}",
          "#{total + ruin_point_ones}ダメージ"
        ]

        return sequence.join(" > ")
      end

      def roll_fp_recovery(command)
        m = /^FPR(\d+)$/.match(command)
        unless m
          return nil
        end

        ruin_point = m[1].to_i
        dice_count = ruin_point.fdiv(10).ceil

        dice_list = @randomizer.roll_barabara(dice_count, 10)
        total = dice_list.sum()
        dice_str = dice_list.join(",")

        sequence = [
          "(#{dice_count}D10)",
          "#{total}[#{dice_str}]",
          "#{total}回復"
        ]

        return sequence.join(" > ")
      end

      TABLES = {
        "PE" => DiceTable::RangeTable.new(
          "ポジティブ感情表",
          "1D100",
          [
            [1..5, "【希望】相手はまるで自分の過去、あるいは未来を見ているように感じる。"],
            [6..10, "【礼儀】相手に礼を尽くすべきだとあなたは考えている。"],
            [11..15, "【家族】相手とは家族のような関係となる。"],
            [16..20, "【恩人】相手から助けを受けたことがある。それは大事な思い出だ。"],
            [21..25, "【友人】相手とはなんとなくウマが合う。一緒にいると楽しい。"],
            [26..30, "【信用】相手は信用できる人物だと思う。"],
            [31..35, "【仲間】相手は同じ目的を持つ仲間だ。"],
            [36..40, "【庇護】相手のことを助けてあげたいと思っている。"],
            [41..45, "【尊敬】相手の行動、思考、思想などを尊敬している。"],
            [46..50, "【憧れ】相手の生き方、外見、能力などになんとなく憧れている。"],
            [51..55, "【好意】相手の主張、外見、生き方などに好意を抱いている。"],
            [56..60, "【忠義】相手に対して真摯に忠実でありたいと思っている。"],
            [61..65, "【目標】相手はあなたにとっての目標であり、理想の存在だ。"],
            [66..70, "【借り】相手から助けを受けた。それはいつか返すべき、借りだ。"],
            [71..75, "【貸し】相手には貸しがある。別に返してもらおうとは思っていない。"],
            [76..80, "【腐れ縁】相手は昔から何かというと縁がある。この縁は今も続いている。"],
            [81..85, "【相性】相手とはなんとなくうまくいく。相性がいいようだ。"],
            [86..90, "【有為】相手はあなたにとって益をもたらす人物だ、そう考えている。"],
            [91..95, "【秘密】相手の秘密を知っている。あるいはお互い秘密を共有している。"],
            [96..100, "【好敵手】相手のことを好敵手、ライバルだと思っている。"],
            # [101, "【任意】相手と相談の上で関係を設定すること。"],
          ]
        ),
        "NE" => DiceTable::RangeTable.new(
          "ネガティブ感情表",
          "1D100",
          [
            [1..5, "【同族嫌悪】1日に自分の忌むべき過去、あるいは自分自身を見ているように感じる。"],
            [6..10, "【侮蔑】相手を蔑む気持ちがある。どうにも、気に入らない。"],
            [11..15, "【反発】相手の主張や行動などに反発を感じる。相手を受け入れることに抵抗がある。"],
            [16..20, "【わだかまり】相手には言葉にしにくいもやもやとした感情を持っている。"],
            [21..25, "【隔たり】相手とはなんとなくウマが合わない。一緒にいても面白くない。"],
            [26..30, "【疑惑】相手は信用できない人物だと思っている。"],
            [31..35, "【裏切り】相手に裏切られたという気持ちがある。"],
            [36..40, "【妨害】相手のことを気に入らず、何かあれば、邪魔したいと思っている。"],
            [41..45, "【侮辱】相手の行動、思考、思想などを嫌悪している。"],
            [46..50, "【うらやみ】相手の生き方、外見、能力などをうらやんでいる。"],
            [51..55, "【害意】相手の主張、外見、生き方などを嫌い、害を与えたいと思っている。"],
            [56..60, "【不快】相手を不快な人間だと思っている。生理的に受け付けない。"],
            [61..65, "【反面】相手を反面教師としている。ああはなるまい、と。"],
            [66..70, "【詐欺】相手に騙されているように思う。何か嘘を吐かれているように思うのだ。"],
            [71..75, "【搾取】相手に自分の何かを奪われているような怒りを感じる。"],
            [76..80, "【悪縁】相手は昔から縁がある。この縁を絶ちきりたいと思っている。"],
            [81..85, "【相性】相手とはなんとなくうまくいかない。残念だが相性が悪い。"],
            [86..90, "【害悪】相手はあなたにとって害をもたらす、そう思っている。"],
            [91..95, "【怨恨】相手に恨みを持っている。この恨みを晴らす日は来るだろうか。"],
            [96..100, "【仇敵】相手のことを倒すべき相手と思っている。"],
            # [101, "【任意】相手と相談の上で関係を設定すること。"],
          ]
        ),
        "DXM" => DiceTable::RangeTable.new(
          "デウス・エクス・マキナ表",
          "1D10",
          [
            [1..2, "神降臨。エンディングフェイズに効果を発揮する。あなたの願いはかなう。願いの内容はGMと相談して決定すること。"],
            [3..4, "逃走。状況を無視してあなた以外のキャストはシーンから退場できる。"],
            [5..6, "命の雫。あなた以外のキャストのFPが3D10点だけ回復する。"],
            [7..8, "天変地異。巨大な嵐や地震、雷雨などが発生し、周囲は大混乱に陥る。トループやエキストラはシーン終了まで何も行なえない(戦闘不能として扱う)。"],
            [9..10, "不思議なことが起こった。あなたのFPが完全に回復する。"],
          ]
        ),
        "JC" => DiceTable::Table.new(
          "断罪チャート",
          "1D10",
          [
            "【国王/女王】国レベルの代表者が現われて、あなたの主張を支持してくれる。",
            "【王子/王女】王子や王女といった国で知らぬ者がないような存在が、あなたの主張を支持してくれる。",
            "【高位聖職者】高位の聖職者が、あなたの主張を支持してくれる。",
            "【有力貴族】有力貴族が、あなたの主張を支持してくれる。",
            "【有力市民】有力市民が、あなたの主張を支持してくれる。",
            "【豪商】豪商が、あなたの主張を支持してくれる。",
            "【現役学生たち】アカデミーの学生たちが、あなたの主張を支持してくれる。",
            "【OB、OGたち】アカデミーのOBやOGが、あなたの主張を支持してくれる。",
            "【多くの人々】名も知れぬ多くの人々が、あなたの主張を支持してくれる。",
            "【外国の王侯貴族】外国の代表者が現われて、あなたの主張を支持してくれる。",
            # "【任意】GMと相談して後ろ盾となる人物を決定する。",
          ]
        ),
        "RDF" => DiceTable::RangeTable.new(
          "破滅のイヤな感じ表",
          "1D100",
          [
            [1..5, "【水中で拘束】\n演出:水中で長い髪の毛が全身に絡みついて動きが重くなるような感覚。\nルーインブレイク成功:重い拘束から解き放たれたような快感。"],
            [6..10, "【鈍痛】\n演出:こめかみから長い釘を差し込まれているような感覚。\nルーインブレイク成功:痛みが消えてなくなる安堵感。"],
            [11..15, "【酸欠】\n演出:空気が薄くなり呼吸をしても息苦しさが消えない感覚。\nルーインブレイク成功:清浄な空気を吸った時の快感。"],
            [16..20, "【ヘッドロック】\n演出:頭を締め上げられているような感覚。\nルーインブレイク成功:痛みから逃れられた安心感。"],
            [21..25, "【悪寒】\n演出:背中が冷やりとして悪寒が全身を突き抜けるような感覚。\nルーインブレイク成功:悪寒が鎮まった平穏感。"],
            [26..30, "【熱病】\n演出:熱病で浮かされたように頭がぼうっとする感覚。\nルーインブレイク成功:落ち着きを取り戻した安息感。"],
            [31..35, "【高所恐怖】\n演出:目もくらむような断崖の際に立たされたような感覚。\nルーインブレイク成功:落下の恐怖から逃れた安堵感。"],
            [36..40, "【ガラスの破片】\n演出:砕けた散ったガラスの破片を踏み続けるような感覚。\nルーインブレイク成功:幻の痛みが消えていく安心感。"],
            [41..45, "【ジャリ感】\n演出:口の中に砂を詰め込まれたような感覚。\nルーインブレイク成功:口の中がすっきりしたような清浄感。"],
            [46..50, "【耳鳴り】\n演出:耳をふさいでも聞こえる耳鳴りが響き続けているような感覚。\nルーインブレイク成功:異音が消えた平安感。"],
            [51..55, "【孤独】\n演出:虚空にただひとり浮かんでいるような孤独な感覚。\nルーインブレイク成功:孤立から脱した安心感。"],
            [56..60, "【落下感】\n演出:高所から落ち続けているような感覚。\nルーインブレイク成功:地に足のついた安定感。"],
            [61..65, "【暗所恐怖】\n演出:明るいはずなのに周囲が真っ暗で何も見えない不安な感覚。\nルーインブレイク成功:周囲がハッキリ見える安息感。"],
            [66..70, "【擦過】\n演出:心の表面をザラザラとしたもので削られているような感覚。\nルーインブレイク成功:痛みから逃れられた安楽感。"],
            [71..75, "【幻聴】\n演出:周囲に人がいて、絶えず自分の悪口を囁きあっているような感覚。\nルーインブレイク成功:周囲への恐怖が消えた平穏感。"],
            [76..80, "【異臭】\n演出:不快な香りが漂ってくるような感覚。\nルーインブレイク成功:異臭を感じなくなった清浄感。"],
            [81..85, "【健忘感】\n演出:何かを忘れていて、それが何かは思い出せないような感覚。\nルーインブレイク成功:忘れごとを思い出せたときの開放感。"],
            [86..90, "【杞憂】\n演出:天が崩れていつ落ちてくるかわからない感覚。\nルーインブレイク成功:頭上がすっきりした痛快感。"],
            [91..95, "【背後恐怖】\n演出:背後に人が立っているような感覚。\nルーインブレイク成功:後方に憂いのない安心感。"],
            [96..100, "【夢中感】\n演出:夢の中にいるような不安な感覚。\nルーインブレイク成功:しっかりとした現実感。"],
            # [101, "【任意】\n演出:GMあるいはプレイヤーが演出を行なう。\nルーインブレイク成功:任意"],
          ]
        ),
        "TC" => DiceTable::Table.new(
          "トラブルチャート/トラブル解決チャート",
          "1D10",
          [
            "【暴れ馬/交通事故】\nトラブル:いきなり、暴れ馬がやってきて、キミは刎ねられた。\n解決:時間はかかったが、事故は処理された。",
            "【突然の崩落/地下遺跡へ移動】\nトラブル:周辺ごと地面が陥没し、地下へと導かれる。\n解決:崩落した先は謎の古代文明の遺跡であった。",
            "【暗殺者の襲撃】\nトラブル:凶刃がキャストを襲う。\n解決:何とか暗殺者の手を逃れ、キミは生還した。",
            "【拉致・誘拐】\nトラブル:突然、キミは黒覆面の男たちに馬車に押し込まれ、誘拐される。\n解決:何とかして、キミは誘拐組織の手を逃れた。",
            "【爆発!!】\nトラブル:爆発した!\n解決:奇跡的にキミは無傷だ、周囲には破壊されたガレキが転がっている。",
            "【行きずりの強盗】\nトラブル:訪れていた店やレストラン、銀行などが強盗に襲われる。\n解決:通りすがりのヒーローが強盗を倒した。あれはいったい。",
            "【テロリストの襲撃/撃退】\nトラブル:テロリストに襲われる。\n解決:テロリストは撃退された。",
            "【交通マヒ/移動変更】\nトラブル:直接、事故に行きあったわけではない事故によって起こった交通マヒによって身動きが取れない。\n解決:交通機関を変更して移動することになった。",
            "【軍・警察の封鎖/大捕物】\nトラブル:突如して軍や警察などの治安組織によって建物が封鎖されてしまった。\n解決:建物内にいる犯人を巡り、大捕物が始った。",
            "【任意】\nGMと相談してトラブルの内容を決めよう。",
          ]
        ),
        "DA" => DiceTable::Table.new(
          "ドタバタアクション表",
          "1D10",
          [
            "【フードファイト(野菜)】大根ソードで切りつけ、カボチャハンマーで殴り抜け",
            "【ホコリの雲】ドカッ、バキ、ボカッ。キュウ。",
            "【リビングルームストーム】飛び交うソーサー、ポットの中には煎れたばかりの紅茶(抽出温度28度)が入っているぞ。",
            "【廊下でランナウェイ】廊下を走っては行けません。",
            "【図書館バトル】敏腕司書が、図書館の静寂を乱す者を残らず静かにさせていく。",
            "【パーティーファイト】優雅に踊り、紳士淑女の助けを借りて悪漢を退治しよう。",
            "【フードファイト(肉と骨)】ヒトに眠る野性を解き放て。羊の骨が最古の武器として再発見される。",
            "【イスと机】イスは盾であり、武器であり悪漢をけん制し、拘束する。",
            "【洗濯物ファイト】シーツで敵の動きを止めて、石鹸で転ばせよう。",
            "【任意】GMと相談して、イメージをふくらませよう。",
          ]
        ),
      }.freeze

      register_prefix('RB', 'FP[DR]', TABLES.keys)
    end
  end
end