bcdice 3.9.0 → 3.11.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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