bcdice/BCDice

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

Summary

Maintainability
A
1 hr
Test Coverage
# frozen_string_literal: true

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

      # ゲームシステム名
      NAME = '光砕のリヴァルチャー'

      # ゲームシステム名の読みがな
      SORT_KEY = 'こうさいのりうあるちやあ'

      # ダイスボットの使い方
      HELP_MESSAGE = <<~HELP
        ■アタック判定( xAT, xATK, xATTACK )
        x: ダイス数(加算 + と除算 / を使用可能)
        例) 3AT, 4ATK, 5+6ATTACK, 15/2AT

        □アタック判定 目標値つき( xAT<=y, xATK<=y, xATTACK<=y )
        x: ダイス数(加算 + と除算 / を使用可能)
        y: 目標値( 1 以上 6 以下。加算 + を使用可能)
        例) 3AT<=4, 3AT<=2+1

        □アタック判定 目標値&追加ダメージつき( xAT<=y[>=a:+b], xATK<=y[>=a:+b], xATTACK<=y[z] )
        x: ダイス数(加算 + と除算 / を使用可能)
        y: 目標値( 1 以上 6 以下。加算 + を使用可能)
        z: 追加ダメージの規則(詳細は後述)(※複数同時に指定可能)

        ▽追加ダメージの規則 [a:+b]
        a: ヒット数が a なら
         =a (ヒット数が a に等しい)
         >=a (ヒット数が a 以上)
        b: ダメージを b 点追加

        例) 3AT<=4[>=2:+3] #ルールブックp056「グレングラントAR」
        例) 2AT<=4[=1:+5][>=2:+8] #ルールブックp067「ファーボル・ドラゴンブレス」
      HELP

      ATTACK_ROLL_REG = %r{^(\d+([+/]\d+)*)?AT(TACK|K)?(<=([1-6](\+\d)*))?((\[>?=\d+:\+\d+\])+)?}i.freeze
      register_prefix('\d+([+\/]\d+)*AT')

      def eval_game_system_specific_command(command)
        if (m = ATTACK_ROLL_REG.match(command))
          roll_attack(m[1], m[5], m[7])
        end
      end

      private

      def roll_attack(dice_count_expression, border_expression, additional_damage_rules)
        dice_count = Arithmetic.eval(dice_count_expression, RoundType::FLOOR)
        border = Arithmetic.eval(border_expression, RoundType::FLOOR).clamp(1, 6) if border_expression

        command = make_command_text(dice_count, border, additional_damage_rules)

        if dice_count <= 0
          return "#{command} > ダイス数が 0 です"
        elsif border.nil? && additional_damage_rules
          return "#{command} > 目標値が指定されていないため、追加ダメージを算出できません"
        end

        dices = @randomizer.roll_barabara(dice_count, 6).sort

        critical_hit_count = dices.count(1)
        hit_count = dices.count { |dice| dice <= border } + critical_hit_count if border
        damage = calc_damage(hit_count, additional_damage_rules)

        message_elements = []
        message_elements << command
        message_elements << dices.join(',')
        message_elements << "クリティカル #{critical_hit_count}" if critical_hit_count > 0
        message_elements << "ヒット数 #{hit_count}" if hit_count
        message_elements << "ダメージ #{damage}" if damage

        Result.new(message_elements.join(' > ')).tap do |r|
          r.condition = hit_count > 0 if hit_count
          r.critical = critical_hit_count > 0
        end
      end

      def make_command_text(dice_count, border, additional_damage_rules)
        command = "#{dice_count}attack"
        command += "<=#{border}" if border
        command += additional_damage_rules if additional_damage_rules

        "(#{command})"
      end

      def calc_damage(hit_count, additional_damage_rules)
        return nil unless additional_damage_rules

        damage = hit_count
        parse_additional_damage_rules(additional_damage_rules).each do |rule|
          if rule[:condition].call(hit_count)
            damage += rule[:additinal_damage]
          end
        end

        damage
      end

      def parse_additional_damage_rules(source)
        source.scan(/\[(>?=)(\d+):\+(\d+)\]/).map do |matched|
          {
            condition: make_additional_damage_condition(matched[0], matched[1].to_i),
            additinal_damage: matched[2].to_i,
          }
        end
      end

      def make_additional_damage_condition(comparer, comparing_target)
        case comparer
        when '='
          lambda { |hit_count| hit_count == comparing_target }
        when '>='
          lambda { |hit_count| hit_count >= comparing_target }
        end
      end
    end
  end
end