bcdice 3.9.0 → 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,7 +25,7 @@ module BCDice
25
25
  例2)KC6 → 目標値6の継続判定
26
26
  ・罠動作チェック+獲物表(P163): CTR
27
27
  罠ごとに1D12を振り、12が出た場合には生き物が罠を動作させ、その影響を受けている。
28
- ・各種表
28
+ ・各種表(基本ルールブック)
29
29
  ・大失敗表(P120): FT
30
30
  ・能力値ランダム決定表(P121): RST
31
31
  ・ランダム所要時間表(P122): RTT
@@ -38,6 +38,16 @@ module BCDice
38
38
  ・食材採集表(P157): GFT
39
39
  ・水採集表(P157): GWT
40
40
  ・白の魔石効果表(P186): WST
41
+ ・部位ダメージ関連の表(参照先ページはリプレイ&データブック「嚙神ノ宴」のもの)
42
+ ・人間部位表(P216): HPT
43
+ ・部位ダメージ段階表(P217): PDT
44
+ ・四足動物部位表(P225): QPT
45
+ ・無足動物部位表(P225): APT
46
+ ・二足動物部位表(P226): TPT
47
+ ・鳥部位表(P226): BPT
48
+ ・頭足動物部位表(P227): CPT
49
+ ・昆虫部位表(P227): IPT
50
+ ・蜘蛛部位表(P228): SPT
41
51
  MESSAGETEXT
42
52
 
43
53
  def eval_game_system_specific_command(command)
@@ -237,6 +247,128 @@ module BCDice
237
247
  '大型動物を召喚する',
238
248
  ]
239
249
  ),
250
+ 'HPT' => DiceTable::RangeTable.new(
251
+ '人間部位表',
252
+ '1D12',
253
+ [
254
+ [1..2, '右腕部'],
255
+ [3..4, '左腕部'],
256
+ [5..6, '右脚部'],
257
+ [7..8, '左脚部'],
258
+ [9..11, '胴部'],
259
+ [12, '頭部'],
260
+ ]
261
+ ),
262
+ 'PDT' => DiceTable::RangeTable.new(
263
+ '部位ダメージ段階表',
264
+ '1D12',
265
+ [
266
+ [1..6, '軽傷'],
267
+ [7..10, '重傷'],
268
+ [11, '破壊'],
269
+ [12, '喪失'],
270
+ ]
271
+ ),
272
+ 'QPT' => DiceTable::RangeTable.new(
273
+ '四足動物部位表',
274
+ '1D12',
275
+ [
276
+ [1..2, '異形'],
277
+ [3, '武器'],
278
+ [4, '右前脚部'],
279
+ [5, '左前脚部'],
280
+ [6, '右後脚部'],
281
+ [7, '左後脚部'],
282
+ [8..10, '胴部'],
283
+ [11..12, '頭部'],
284
+ ]
285
+ ),
286
+ 'APT' => DiceTable::RangeTable.new(
287
+ '無足動物部位表',
288
+ '1D12',
289
+ [
290
+ [1..3, '異形'],
291
+ [4..6, '武器'],
292
+ [7..10, '胴部'],
293
+ [11..12, '頭部'],
294
+ ]
295
+ ),
296
+ 'TPT' => DiceTable::RangeTable.new(
297
+ '二足動物部位表',
298
+ '1D12',
299
+ [
300
+ [1, '異形'],
301
+ [2, '武器'],
302
+ [3, '右腕部'],
303
+ [4, '左腕部'],
304
+ [5..6, '右脚部'],
305
+ [7..8, '左脚部'],
306
+ [9..11, '胴部'],
307
+ [12, '頭部'],
308
+ ]
309
+ ),
310
+ 'BPT' => DiceTable::RangeTable.new(
311
+ '鳥部位表',
312
+ '1D12',
313
+ [
314
+ [1, '異形'],
315
+ [2, '武器'],
316
+ [3..4, '右翼(右腕部)'],
317
+ [5..6, '左翼(左腕部)'],
318
+ [7, '右脚部'],
319
+ [8, '左脚部'],
320
+ [9..11, '胴部'],
321
+ [12, '頭部'],
322
+ ]
323
+ ),
324
+ 'CPT' => DiceTable::RangeTable.new(
325
+ '頭足動物部位表',
326
+ '1D12',
327
+ [
328
+ [1, '異形'],
329
+ [2, '武器'],
330
+ [3, '右腕部'],
331
+ [4, '左腕部'],
332
+ [5..7, '右脚部'],
333
+ [8..10, '左脚部'],
334
+ [11, '胴部'],
335
+ [12, '頭部'],
336
+ ]
337
+ ),
338
+ 'IPT' => DiceTable::RangeTable.new(
339
+ '昆虫部位表',
340
+ '1D12',
341
+ [
342
+ [1..2, '異形'],
343
+ [3, '武器'],
344
+ [4, '右前脚部'],
345
+ [5, '左前脚部'],
346
+ [6, '右中脚部'],
347
+ [7, '左中脚部'],
348
+ [8, '右後脚部'],
349
+ [9, '左後脚部'],
350
+ [10..11, '胴部'],
351
+ [12, '頭部'],
352
+ ]
353
+ ),
354
+ 'SPT' => DiceTable::RangeTable.new(
355
+ '蜘蛛部位表',
356
+ '1D12',
357
+ [
358
+ [1, '異形'],
359
+ [2, '武器'],
360
+ [3, '右第一脚部'],
361
+ [4, '左第一脚部'],
362
+ [5, '右第二脚部'],
363
+ [6, '左第二脚部'],
364
+ [7, '右第三脚部'],
365
+ [8, '左第三脚部'],
366
+ [9, '右第四脚部'],
367
+ [10, '左第四脚部'],
368
+ [11, '胴部'],
369
+ [12, '頭部'],
370
+ ]
371
+ ),
240
372
  }.freeze
241
373
 
242
374
  register_prefix('K[AC]', 'CTR', TABLES.keys)
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BCDice
4
+ module GameSystem
5
+ class MamonoScramble < Base
6
+ # ゲームシステムの識別子
7
+ ID = 'MamonoScramble'
8
+
9
+ # ゲームシステム名
10
+ NAME = 'マモノスクランブル'
11
+
12
+ # ゲームシステム名の読みがな
13
+ SORT_KEY = 'まものすくらんふる'
14
+
15
+ # ダイスボットの使い方
16
+ HELP_MESSAGE = <<~INFO_MESSAGE_TEXT
17
+ ・判定 xMS<=t
18
+  [判定]を行う。成否と[マリョク]の上昇量を表示する。
19
+  x: ダイス数
20
+  t: 能力値(目標値)
21
+
22
+ ・アクシデント表 ACC
23
+ INFO_MESSAGE_TEXT
24
+
25
+ def initialize(command)
26
+ super(command)
27
+
28
+ @sides_implicit_d = 12
29
+ @round_type = RoundType::CEIL
30
+ end
31
+
32
+ def eval_game_system_specific_command(command)
33
+ roll_ability(command) || roll_tables(command, TABLES)
34
+ end
35
+
36
+ private
37
+
38
+ def roll_ability(command)
39
+ parser = Command::Parser.new("MS", round_type: @round_type)
40
+ .has_prefix_number
41
+ .disable_modifier
42
+ .restrict_cmp_op_to(:<=)
43
+ parsed = parser.parse(command)
44
+ unless parsed
45
+ return nil
46
+ end
47
+
48
+ dice_list = @randomizer.roll_barabara(parsed.prefix_number, 12).sort
49
+ count_success = dice_list.count { |value| value <= parsed.target_number }
50
+ count_one = dice_list.count(1)
51
+ is_critical = count_one > 0
52
+ has_twelve = dice_list.include?(12)
53
+
54
+ maryoku =
55
+ if has_twelve && !is_critical
56
+ 0
57
+ else
58
+ count_success + count_one
59
+ end
60
+
61
+ sequence = [
62
+ "(#{parsed})",
63
+ "[#{dice_list.join(',')}]",
64
+ count_success > 0 ? "成功, [マリョク]が#{maryoku}上がる" : "失敗"
65
+ ]
66
+
67
+ return Result.new.tap do |r|
68
+ r.text = sequence.join(" > ")
69
+ r.condition = count_success > 0
70
+ r.critical = r.success? && is_critical
71
+ end
72
+ end
73
+
74
+ TABLES = {
75
+ "ACC" => DiceTable::Table.new(
76
+ "アクシデント表",
77
+ "1D12",
78
+ [
79
+ "思わぬ対立:[判定]で10〜12の出目を1個でも出した場合、【耐久値】を2点減らす。",
80
+ "都市の迷宮化:[判定]に【社会】を使用できない。",
81
+ "不穏な天気:特別な効果は発生しない。",
82
+ "突然の雷雨:エリアの[特性]に[雨]や[水たまり]などを足してもいい。",
83
+ "関係ない危機:[判定]に失敗したPCの【耐久値】を2点減らす。",
84
+ "からりと晴天:エリアの[特性]に[強い日光]や[日だまり]などを足してもいい。",
85
+ "謎のお祭り:[判定]で1〜3の出目を1個でも出した場合、【耐久値】を2点回復する。",
86
+ "すごい人ごみ:エリアの[特性]に[野次馬]や[観光客]などを足してもいい。",
87
+ "マリョク乱気流:[判定]に【異質】を使用できない。",
88
+ "魔術テロ事件:GMが1Dをロールする。出目が1〜3なら【身体】、出目が4〜6なら【異質】、出目が7〜9なら【社会】が[判定]で使えない。10〜12は何も起きない。",
89
+ "マリョク低気圧:[判定]に【身体】を使用できない。",
90
+ "平穏な時間:特別な効果は発生しない。",
91
+ ]
92
+ )
93
+ }.freeze
94
+
95
+ register_prefix('\d+MS', TABLES.keys)
96
+ end
97
+ end
98
+ end
@@ -59,7 +59,7 @@ module BCDice
59
59
  private
60
60
 
61
61
  def check_roll(command)
62
- m = /^(\d+)D6([+\-\d]*)>=(\d+)(\[(\d+)?(,(\d+))?\])?$/i.match(command)
62
+ m = /^(\d+)D6([+\-\d]*)>=(\?|\d+)(\[(\d+)?(,(\d+))?\])?$/i.match(command)
63
63
  unless m
64
64
  return nil
65
65
  end
@@ -80,6 +80,8 @@ module BCDice
80
80
  Result.fumble(translate("MonotoneMuseum.automatic_failure"))
81
81
  elsif dice_value >= critical
82
82
  Result.critical(translate("MonotoneMuseum.automatic_success"))
83
+ elsif target == 0
84
+ Result.success(nil)
83
85
  elsif total >= target
84
86
  Result.success(translate("success"))
85
87
  else
@@ -91,7 +93,7 @@ module BCDice
91
93
  "#{dice_value}[#{dice_str}]#{Format.modifier(modify_number)}",
92
94
  total.to_s,
93
95
  result.text,
94
- ]
96
+ ].compact
95
97
 
96
98
  result.text = sequence.join(" > ")
97
99
 
@@ -8,24 +8,26 @@ module BCDice
8
8
  SORT_KEY = "えすああるえすしやないせかいしゆのめいきゆうTRPG"
9
9
 
10
10
  HELP_MESSAGE = <<~MESSAGETEXT
11
- ■ 判定 (xSQ±y)
12
- xD6の判定。3つ以上振ったとき、出目の高い2つを表示します。クリティカル、ファンブルも計算します。
11
+ ■ 判定 (xSQ±y>=z)
12
+ xD6の判定。3つ以上振ったとき、出目の高い2つを表示します。絶対成功、絶対失敗も計算します。
13
+ 2つのサイコロを使用して出目に1があった場合は、FPの獲得も表示します。3つ以上使用した場合は表示しません。
13
14
  ±y: yに修正値を入力。±の計算に対応。省略可能。
15
+ z: 目標値。省略可能。
14
16
 
15
17
  ■ ダメージロール (xDR(C)(+)y)
16
- xD6のダメージロール。クリティカルの自動判定を行います。Cを付けるとクリティカルアップ状態で計算できます。+を付けるとクリティカル時のダイスが8個になります。
18
+ xD6のダメージロール。クリティカルヒットの自動判定を行います。Cを付けるとクリティカルアップ状態で計算できます。+を付けるとクリティカルヒット時のダイスが8個になります。
17
19
  x: xに振るダイス数を入力。
18
20
  y: yに耐性を入力。
19
21
  例) 5DR3 5DRC4 5DRC+4
20
22
 
21
23
  ■ 回復ロール (xHRy)
22
- xD6の回復ロール。クリティカルが発生しません。
24
+ xD6の回復ロール。クリティカルヒットが発生しません。
23
25
  x: xに振るダイス数を入力。
24
26
  y: yに耐性を入力。省略した場合3。
25
27
  例) 2HR 10HR2
26
28
 
27
- 採取ロール (TC±z,SC±z,GC±z)
28
- ちょっと(T)、そこそこ(S)、がっつり(G)採取採掘伐採を行う。
29
+ 採集ロール (TC±z,SC±z,GC±z)
30
+ 少しだけ(T)、そこそこ(S)、ガッツリ(G)採取採掘伐採を行います。
29
31
  z: zに追加でロールする回数を入力。省略可能。
30
32
  例) TC SC+1 GC-1
31
33
  MESSAGETEXT
@@ -40,27 +42,38 @@ module BCDice
40
42
 
41
43
  # 判定
42
44
  def roll_sq(command)
43
- m = /(\d+)SQ([+\-\d]+)?/i.match(command)
45
+ m = /(\d+)SQ([+\-\d]+)?(([>=]+)(\d+))?/i.match(command)
44
46
  return nil unless m
45
47
 
46
48
  dice_count = m[1].to_i
47
49
  modifier = ArithmeticEvaluator.eval(m[2])
50
+ target = m[5].nil? ? nil : m[5].to_i
48
51
 
49
52
  dice_list = @randomizer.roll_barabara(dice_count, 6)
50
53
  largest_two = dice_list.sort.reverse.take(2)
51
54
  total = largest_two.sum + modifier
55
+ num_1 = dice_list.count(1)
52
56
 
53
57
  additional_result =
54
58
  if largest_two == [6, 6]
55
- " クリティカル!"
59
+ Result.critical(" > 絶対成功!")
56
60
  elsif largest_two == [1, 1]
57
- " ファンブル!"
61
+ Result.fumble(" > 絶対失敗!")
62
+ elsif target && total >= target
63
+ Result.success(" > 成功")
64
+ elsif target && total < target
65
+ Result.failure(" > 失敗")
66
+ else
67
+ Result.new
58
68
  end
59
69
 
70
+ # ダイス数が2個の場合は1の出目の数だけ【FP】を獲得できる
71
+ fp_result = dice_count == 2 && num_1 >= 1 ? " (【FP】#{num_1}獲得)" : ""
72
+
60
73
  sequence = [
61
74
  "(#{command})",
62
75
  "[#{dice_list.join(',')}]#{Format.modifier(modifier)}",
63
- "#{total}[#{largest_two.join(',')}]#{additional_result}",
76
+ "#{total}[#{largest_two.join(',')}]#{additional_result.text}#{fp_result}",
64
77
  ]
65
78
 
66
79
  return sequence.join(" > ")
@@ -83,7 +96,7 @@ module BCDice
83
96
  critical_target = critical_up ? 1 : 2
84
97
 
85
98
  if dice_list.count(6) - dice_list.count(1) >= critical_target
86
- result += " クリティカル!\n"
99
+ result += " クリティカルヒット!\n"
87
100
  result += additional_damage_roll(increase_critical_dice, resist)
88
101
  end
89
102
 
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BCDice
4
+ module GameSystem
5
+ class RuneQuestRoleplayingInGlorantha < Base
6
+ # ゲームシステムの識別子
7
+ ID = 'RuneQuestRoleplayingInGlorantha'
8
+
9
+ # ゲームシステム名
10
+ NAME = 'RuneQuest:Roleplaying in Glorantha'
11
+
12
+ # ゲームシステム名の読みがな
13
+ SORT_KEY = 'るうんくえすと4'
14
+
15
+ # ダイスボットの使い方
16
+ HELP_MESSAGE = <<~MESSAGETEXT
17
+ ・判定コマンド クリティカル、スペシャル、ファンブルを含めた判定を行う。
18
+ RQG<=成功率
19
+
20
+ 例1:RQG<=80 (技能値80で判定)
21
+ 例2:RQG<=80+20 (技能値100で判定)
22
+
23
+ ・抵抗判定コマンド(能動-受動) クリティカル、スペシャル、ファンブルを含めた判定を行う。
24
+ RES(能動能力-受動能力)m増強値
25
+ 増強値は省略可能。
26
+
27
+ 例1:RES(9-11) (能動能力9 vs 受動能力11で判定)
28
+ 例2:RES(9-11)m20 (能動能力9 vs 受動能力11、+20%の増強が能動側に入る判定)
29
+ 例3:RES(9)m50 (能動能力と受動能力の差が9で、+50%の増強が能動側に入る判定)
30
+
31
+ ・抵抗判定コマンド(能動側のみ) クリティカル、スペシャル、ファンブルは含めず判定を行う。
32
+ RSA(能動能力)m増強値
33
+ 増強値は省略可能。
34
+
35
+ 例1:RSA(9) (能動能力9で判定)
36
+ 例2:RSA(9)m20 (能動能力9で判定、+20%の増強が能動側に入る判定)
37
+
38
+ MESSAGETEXT
39
+
40
+ register_prefix('RQG', 'RES', 'RSA')
41
+
42
+ def eval_game_system_specific_command(command)
43
+ case command
44
+ when /RQG/i
45
+ return do_ability_roll(command)
46
+ when /RES/i
47
+ return do_resistance_roll(command)
48
+ when /RSA/i
49
+ return do_resistance_active_characteristic_roll(command)
50
+ end
51
+ return nil
52
+ end
53
+
54
+ private
55
+
56
+ # 技能などの一般判定
57
+ def do_ability_roll(command)
58
+ m = %r{\A(RQG)(<=([+-/*\d]+))?$}.match(command)
59
+ unless m
60
+ return nil
61
+ end
62
+
63
+ roll_value = @randomizer.roll_once(100)
64
+ unless m[3]
65
+ # RQGのみ指定された場合は1d100を振ったのと同じ挙動
66
+ return "(1D100) > #{roll_value}"
67
+ end
68
+
69
+ ability_value = Arithmetic.eval(m[3], RoundType::ROUND)
70
+ result_prefix_str = "(1D100<=#{ability_value}) >"
71
+
72
+ if ability_value == 0
73
+ # 0%は判定なしで失敗
74
+ return Result.failure("#{result_prefix_str} 失敗")
75
+ end
76
+
77
+ result_str = "#{result_prefix_str} #{roll_value} >"
78
+
79
+ # 判定
80
+ get_roll_result(result_str, ability_value, roll_value)
81
+ end
82
+
83
+ # 抵抗判定
84
+ def do_resistance_roll(command)
85
+ m = %r{\A(RES)([+-/*\d]+)(M([+-/*\d]+))?$}.match(command)
86
+ unless m
87
+ return nil
88
+ end
89
+
90
+ unless m[2]
91
+ return nil
92
+ end
93
+
94
+ difference_value = Arithmetic.eval(m[2], RoundType::ROUND)
95
+ difference_value = -10 if difference_value < -10
96
+
97
+ resistance_velue = 50 + (difference_value * 5)
98
+ resistance_velue += Arithmetic.eval(m[4], RoundType::ROUND) if m[4]
99
+
100
+ roll_value = @randomizer.roll_once(100)
101
+ result_str = "(1D100<=#{resistance_velue}) > #{roll_value} >"
102
+
103
+ # 判定
104
+ get_roll_result(result_str, resistance_velue, roll_value)
105
+ end
106
+
107
+ # 能動側のみの対抗判定
108
+ def do_resistance_active_characteristic_roll(command)
109
+ m = %r{\A(RSA)(\d+)(M([+-/*\d]+))?$}.match(command)
110
+ unless m
111
+ return nil
112
+ end
113
+
114
+ unless m[2]
115
+ return nil
116
+ end
117
+
118
+ active_ability_value = m[2].to_i
119
+ if active_ability_value == 0
120
+ return "0は指定できません。"
121
+ end
122
+
123
+ modifiy_value = m[4] ? Arithmetic.eval(m[4], RoundType::ROUND) : 0
124
+ roll_value = @randomizer.roll_once(100)
125
+ active_value = active_ability_value * 5 + modifiy_value
126
+ result_prefix_str = "(1D100<=#{active_value}) > #{roll_value} >"
127
+
128
+ note_str = "クリティカル/スペシャル、ファンブルは未処理。必要なら確認すること。"
129
+
130
+ if roll_value >= 96
131
+ # 96-99は無条件で失敗
132
+ Result.failure("#{result_prefix_str} 失敗\n#{note_str}")
133
+ elsif roll_value <= 5 || roll_value <= modifiy_value
134
+ # 02-05あるいは修正値以下は無条件で成功
135
+ Result.success("#{result_prefix_str} 成功\n#{note_str}")
136
+ else
137
+ # 上記全てが当てはまらない時に突破可能な能力値を算出
138
+ "#{result_prefix_str} 相手側能力値#{active_ability_value + (50 + modifiy_value - roll_value) / 5}まで成功\n#{note_str}"
139
+ end
140
+ end
141
+
142
+ # 判定結果の取得
143
+ def get_roll_result(result_str, success_value, roll_value)
144
+ critical_value = (success_value.to_f / 20).round
145
+ special_value = (success_value.to_f / 5).round
146
+ funmble_value = ((100 - success_value.to_f) / 20).round
147
+
148
+ if (roll_value == 1) || (roll_value <= critical_value)
149
+ # クリティカル(01は必ずクリティカル)
150
+ Result.critical("#{result_str} クリティカル/スペシャル")
151
+ elsif (roll_value == 100) || (roll_value >= (100 - funmble_value + 1))
152
+ # ファンブル(00は必ずファンブル)
153
+ Result.fumble("#{result_str} ファンブル")
154
+ elsif roll_value <= special_value
155
+ # スペシャル
156
+ Result.success("#{result_str} スペシャル")
157
+ elsif (roll_value <= 95) && ((roll_value <= 5) || (roll_value <= success_value))
158
+ # 成功(02-05は必ず成功で、96-99は必ず失敗)
159
+ Result.success("#{result_str} 成功")
160
+ else
161
+ # 失敗
162
+ Result.failure("#{result_str} 失敗")
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end