bcdice/BCDice

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

Summary

Maintainability
D
1 day
Test Coverage
# frozen_string_literal: true

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

      # ゲームシステム名
      NAME = 'ガンダム・センチネルRPG'

      # ゲームシステム名の読みがな
      SORT_KEY = 'かんたむせんちねる'

      # ダイスボットの使い方
      HELP_MESSAGE = <<~INFO_MESSAGE_TEXT
        ・基本戦闘(BB, BBM)
         BB[+修正][>回避値]で基本戦闘を判定します。回避値を指定すると、命中・回避も表示します。
         BBM[+修正][>回避値]でモブ用の基本戦闘を判定します。クリティカルを判定します。回避値を指定すると、命中・回避も表示します。

         例)BB BBM BB+5>14 BBM+5>15

        ・一般技能(GS)
         GS[+修正][>目標値]で一般技能を判定します。目標値を指定しない場合は、目標値10で判定します。

         例)GS GS+5 GS+5>10


        ・各種表
         敵MSクリティカルヒットチャート (ECHC)
         PC用脱出判定チャート      (PEJC[+m] m:修正)
         艦船追加ダメージ決定チャート  (ASDC)
         対空砲結果チャート       (AARC[+m]=t m:修正, t:対空防御力)
         リハビリ判定チャート      (RTJC[+m] m:修正)
         二次被害判定チャート      (SDDC)
      INFO_MESSAGE_TEXT

      def initialize(command)
        super(command)

        @round_type = RoundType::CEIL
        @d66_sort_type = D66SortType::NO_SORT
      end

      def eval_game_system_specific_command(command)
        roll_basic_battle(command) ||
          roll_general_skill(command) ||
          roll_anti_aircraft_gun_result_chart(command) ||
          roll_escape_chart(command) ||
          roll_rehabilitation_chart(command) ||
          roll_tables(command, TABLES)
      end

      private

      # 基本戦闘ロール
      def roll_basic_battle(command)
        m = /^BB(M)?([-+][-+\d]+)?(>([-+\d]+))?/.match(command)
        return nil unless m

        mob = m[1]
        modify = ArithmeticEvaluator.eval(m[2])
        have_modify = false
        have_modify = true if m[2]
        avoid = ArithmeticEvaluator.eval(m[4])
        have_avoid = false
        have_avoid = true if m[4]

        d60 = @randomizer.roll_once(6)
        d06 = @randomizer.roll_once(6)
        total_d = d60 * 10 + d06
        d60 += (d06 + modify - 1).div(6)
        d06 = (d06 + modify - 1).modulo(6) + 1
        total = d60 * 10 + d06
        total = 11 if total < 11

        success = false
        failure = false
        critical = false

        modify_label = nil
        if have_modify
          if modify >= 0
            modify_label = "#{total_d}+#{modify}"
          else
            modify_label = "#{total_d}#{modify}"
          end
        end

        critical_label = nil
        if mob && (total >= 66)
          critical_label = "クリティカル"
          critical = true
        end

        result = nil
        if have_avoid
          if total > avoid
            result = "命中(+" + count_success(total, avoid).to_s + ")"
            success = true
          else
            result = "回避"
            failure = true
          end
        end

        sequence = [
          "(#{command})",
          modify_label,
          total,
          result,
          critical_label,
        ].compact

        Result.new(sequence.join(" > ")).tap do |r|
          r.success = success
          r.failure = failure
          r.critical = critical
        end
      end

      def count_success(dice, avoid)
        d60 = dice.div(10)
        d06 = dice.modulo(10)
        a60 = avoid.div(10)
        a06 = avoid.modulo(10)

        return ((d60 * 6 + d06) - (a60 * 6 + a06))
      end

      # 一般技能ロール
      def roll_general_skill(command)
        m = /^GS([-+][-+\d]+)?(>([-+\d]+))?/.match(command)
        return nil unless m

        modify = ArithmeticEvaluator.eval(m[1])
        have_modify = false
        have_modify = true if m[1]
        target = ArithmeticEvaluator.eval(m[3])
        target = 10 unless m[3]

        success = false
        failure = false

        dice = @randomizer.roll_sum(2, 6)

        modify_label = nil
        if have_modify
          if modify >= 0
            modify_label = "#{dice}+#{modify}"
          else
            modify_label = "#{dice}#{modify}"
          end
        end
        total = dice + modify
        if total > target
          result = "成功"
          success = true
        else
          result = "失敗"
          failure = true
        end

        sequence = [
          "(#{command})",
          modify_label,
          total,
          result,
        ].compact

        Result.new(sequence.join(" > ")).tap do |r|
          r.success = success
          r.failure = failure
        end
      end

      # 各種表

      # 対空砲結果チャート
      GUN_RESULT_CHART = [
        ["D", "H", "H", "H", 10, 8, 6, 5, 4, 2, 1, "-", "-"],
        ["D", "H", "H", "H", 12, 10, 9, 8, 6, 5, 3, 2, "-"],
        ["D", "D", "H", "H", "H", 12, 10, 9, 7, 6, 4, 3, 1],
        ["D", "D", "H", "H", "H", 14, 13, 12, 10, 8, 6, 5, 3],
        ["D", "D", "D", "H", "H", "H", 14, 13, 11, 9, 7, 6, 4],
        ["D", "D", "D", "H", "H", "H", "H", 16, 14, 12, 11, 8, 6],
      ].freeze

      def roll_anti_aircraft_gun_result_chart(command)
        parser = Command::Parser.new("AARC", round_type: @round_type).restrict_cmp_op_to(:==)
        parsed = parser.parse(command)

        return nil unless parsed

        target = parsed.target_number.clamp(1, 6)
        dice = @randomizer.roll_sum(2, 6)
        total = (dice + parsed.modify_number).clamp(1, 13)

        cmd =
          if parsed.modify_number != 0
            "(#{dice}#{Format.modifier(parsed.modify_number)}=#{total})"
          else
            total.to_s
          end
        result = GUN_RESULT_CHART[target - 1][total - 1]

        Result.new().tap do |r|
          r.text = "対空砲結果チャート(#{cmd}vs#{target}) > 結果「#{result}」"
          r.condition = result.is_a?(Integer)
        end
      end

      # PC用脱出判定チャート
      ESCAPE_CHART = [
        '*',
        '*',
        '無傷で脱出',
        '無傷で脱出',
        '無傷で脱出',
        '軽傷で脱出「1D6ダメージ。」',
        '中傷で脱出「2D6ダメージ。」',
        '重傷で脱出「3D6ダメージ。」',
        '重体で脱出「1D3の耐久力が残る。」',
        '戦死「二階級特進。」',
        '戦死「二階級特進。」',
        '戦死「二階級特進。」',
        '戦死「二階級特進。」',
      ].freeze

      def roll_escape_chart(command)
        parser = Command::Parser.new("PEJC", round_type: @round_type).restrict_cmp_op_to(nil)
        parsed = parser.parse(command)
        return nil unless parsed

        dice = @randomizer.roll_sum(2, 6)
        total = (dice + parsed.modify_number).clamp(2, 12)

        cmd =
          if parsed.modify_number != 0
            "#{dice}#{Format.modifier(parsed.modify_number)}=#{total}"
          else
            total.to_s
          end
        result = ESCAPE_CHART[total]

        Result.new("PC用脱出判定チャート(#{cmd}) > #{result}")
      end

      REHABILITATION_CHART = [
        '*',
        '*',
        'なし',
        '1ヶ月',
        '2ヶ月',
        '3ヶ月',
        '4ヶ月',
        '5ヶ月',
        '6ヶ月',
        '10ヶ月',
        '1年',
        '1年6ヶ月',
        '1年と、もう一度このチャートで振った結果分を足した期間',
      ].freeze

      def roll_rehabilitation_chart(command)
        parser = Command::Parser.new("RTJC", round_type: @round_type).restrict_cmp_op_to(nil)
        parsed = parser.parse(command)
        return nil unless parsed

        dice = @randomizer.roll_sum(2, 6)
        total = (dice + parsed.modify_number).clamp(2, 12)

        cmd =
          if parsed.modify_number != 0
            "#{dice}#{Format.modifier(parsed.modify_number)}=#{total}"
          else
            total.to_s
          end
        result = REHABILITATION_CHART[total]

        Result.new("リハビリ判定チャート(#{cmd}) > #{result}")
      end

      TABLES = {
        'ECHC' => DiceTable::Table.new(
          '敵MSクリティカルヒットチャート',
          '2D6',
          [
            'コックピット直撃:目標MSは残骸となる。',
            '腕破損:同時に携帯武器も失う。携帯武装の交換も行えない。直ちにモラル判定を-4で行う。',
            '射撃武装破損:目標MSはその時点で使用しているナンバーの若い武装を1つ失う。全ての武装を失った場合、モラル判定を行う。',
            '頭部直撃:目標MSはメインカメラを失い、以後射撃、格闘の命中判定に-6の修正を受ける。頭部に装備されている武装も失われる。',
            'パイロット気絶:目標MSは回復するまで行動不能。',
            '目標MSへのダメージ2倍。',
            '目標MSへのダメージ2倍。',
            '目標MSへのダメージ3倍。',
            '脚破損:目標MSは、以後の回避値に-6の修正を受ける。',
            'コントロール不能:目標MSは1D6ラウンドの間、行動不能。',
            '熱核ジェネレーター直撃:目標MSは直ちに爆発(耐久力0)する。',
          ]
        ),
        'ASDC' => DiceTable::Table.new(
          '艦船追加ダメージ決定チャート',
          '2D6',
          [
            'ブリッジ損傷「複数ある艦は、総てのブリッジが損傷すると以後の対空防御は修正を+5する。」',
            'カタパルト損傷「複数ある艦は、総てのカタパルトが損傷すると、MSの発着艦ができなくなる。」',
            '追加ダメージ「追加2D6×2ダメージ。」',
            '主砲大破「主砲1門を失う。」',
            '副砲大破「副砲1門を失う。」',
            '追加ダメージ「追加2D6ダメージ。」',
            '追加ダメージ「追加2D6ダメージ。」',
            '追加ダメージ「追加2D6ダメージ。」',
            '1ターン行動不能「1ターンはその艦は何も行動ができない。」',
            '航行不能「その艦はそのヘックスから動けなくなる。」',
            'エンジン誘爆「1D6×10%の耐久力を失う。」',
          ]
        ),
        'SDDC' => DiceTable::D66GridTable.new(
          '二次被害判定チャート',
          [
            [
              '奇蹟的に無傷「不発!?今回のダメージは0。」',
              'メインカメラ破損「以後、射撃、格闘の命中判定に-3の修正を受ける。」',
              'コクピット破損「以後の追加ダメージ判定に-1の修正を受ける。」',
              '右腕損傷「携帯していた武装も失う。また右腕での武器の使用はできなくなる。」',
              '左腕損傷「携帯していた武装も失う。また左腕での武器の使用はできなくなる。」',
              '気絶「気絶判定の余地無く、必ず気絶する。」',
            ],
            [
              '気絶「気絶判定を-6の修正で行う。」',
              '気絶「気絶判定を-4の修正で行う。」',
              '気絶「気絶判定を-2の修正で行う。」',
              '気絶「気絶判定を行う。」',
              '予備弾倉破損「携帯している予備弾倉かEパックを1つ失う。」',
              'サブカメラ破損「以後、射撃、格闘の命中判定に-1の修正を受ける。」',
            ],
            [
              '固定武装破損「固定されている武装を1つ失う。」',
              '予備武装破損「携帯している以外の武装を1つ失う。」',
              '頭部破損「メインカメラも失い、以後、射撃、格闘の命中判定に-3の修正を受ける。」',
              '右脚破損「以後、回避値が1D3低下する。」',
              '左脚破損「以後、回避値が1D3低下する。」',
              '操縦機構破損「以後、すべての行動は消費行動ポイントを1ポイント余分に消費する。」',
            ],
            [
              '軽傷「パイロットは1D6のダメージを受ける。また気絶判定を行う。」',
              '中傷「パイロットは2D6のダメージを受ける。また気絶判定を-6修正で行う。」',
              '重傷「パイロットは3D6のダメージを受ける。また気絶判定を-9修正で行う。」',
              '操縦伝達部破損「以後すべての射撃、格闘の命中判定と回避値に-1の修正を受ける。」',
              'センサー破損「イニシアティブ決定に-1の修正を受ける。」',
              '脱出機構破損「脱出判定に+3の修正を受ける。」',
            ],
            [
              '熱核ジェネレーター損傷「行動の「追加移動」が行えなくなる。」',
              '右腕の携帯武装破損「右腕に持っていた武装を1つ失う。」',
              '左腕の携帯武装破損「左腕に持っていた武装を1つ失う。」',
              'サブスラスター破損「回避値が1低下する。」',
              'プロペラントタンク破損「プロペラントタンクを1つ失う。」',
              'バックパック破損「推進剤3D6ポイント失う。」',
            ],
            [
              'メインスラスター破損「回避値が1D6低下する。」',
              '動力パイプ破損「以後、行動ポイント決定のダイスに-1の修正を受ける。」',
              '動力伝達機構破損「以後、行動ポイント決定のサイコロに-1D3の修正を受ける。」',
              'サブスラスター破損「旋回が120度までしかできなくなる。」',
              'メインスラスター破損「旋回が60度までしかできなくなる。」',
              '熱核ジェネレーター直撃「そのMSは爆発する。PCは直ちに脱出判定を行う。」',
            ]
          ]
        ),
      }.freeze

      register_prefix('BBM?', 'GS', 'AARC', 'PEJC', 'RTJC', TABLES.keys)
    end
  end
end