Skip to content

Commit

Permalink
Fix Hero System & add multi roll support
Browse files Browse the repository at this point in the history
This commit includes two changes. One minor and one major:
1. Resolve some issues with the hero system code that came from the
   original PR :/ . I shouldve caught this
2. Added support for multi roll logic. This allows for 4 unique dice
   rolls (limited by discord api) in a single command. Rolls are
separated by a forward slash / . This is a "beta feature" and some
complicated dice rolls may cause bugs.

example syntax: /roll 4d100 / 10d6 e6 k8 +4/ 3d10 k2/ ul 3d100

Resolves #174, Resolves #60
  • Loading branch information
Humblemonk committed Jun 23, 2024
1 parent ca73b3f commit 2841208
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 134 deletions.
242 changes: 125 additions & 117 deletions dice_maiden.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Dice bot for Discord
# Author: Humblemonk
# Version: 8.11.1
# Version: 9.0.0
# Copyright (c) 2017. All rights reserved.
# !/usr/bin/ruby
# If you wish to run a single instance of this bot, please follow the "Manual Install" section of the readme!
Expand All @@ -23,7 +23,7 @@

# open connection to sqlite db and set timeout to 10s if the database is busy
if @launch_option == 'lite'
# do nothing
puts 'Dice Maiden lite mode detected!'
else
require 'sqlite3'
$db = SQLite3::Database.new 'main.db'
Expand Down Expand Up @@ -51,136 +51,144 @@
inc_cmd = lambda do |event|
# Locking the thread to prevent messages going to the wrong server
mutex.lock
response_array = []
begin
@event_roll = event.options.values.join('')

@do_tally_shuffle = false
check_comment
@roll_request = @event_roll.dup

@input = alias_input_pass(@event_roll) # Do alias pass as soon as we get the message
@simple_output = false
@wng = false
@dh = false
@godbound = false
@ed = false
@hsn = false
@hsk = false
@hsh = false
@no_result = false

@private_roll = false
@reroll_check = 0
@reroll_indefinite_check = 0
@reroll_count = 0
@botch = 0

check_roll_modes
next if @ed && !replace_earthdawn(event)

@roll_set = nil
next unless roll_sets_valid(event)

# check for single dice rolls
@input.gsub!(%r{(?<!\d)(^|[+*/-]\s?)d(\d+)}, '\11d\2') if @input.match?(%r{(?<!\d)(^|[+*/-]\s?)d(\d+)})

@roll = @input
@check = @prefix + @roll
@test_status = ''
# check user
check_user_or_nick(event)
# check for empty roll
if @event_roll.empty?
event.respond(content: "#{@user} roll is empty! Please type a complete dice roll message")
next
end
# check for modifiers that should apply to everything
check_universal_modifiers

# Check for dn
dnum = @input.scan(/dn\s?(\d+)/).first.join.to_i if @input.match?(/^(1dn)\d+/i)

# Check for correct input
if @roll.match?(/\dd\d/i)
event.channel.start_typing
next if check_roll(event) == true

# Check for wrath roll
check_wrath
# Grab dice roll, create roll, grab results
if @roll_set.nil?
next if do_roll(event) == true
else
@roll_set_results = ''
@error_check_roll_set = ''
roll_count = 0
@roll_set_total = 0
error_encountered = false
while roll_count < @roll_set.to_i
if do_roll(event) == true
error_encountered = true
break
inc_event_roll = event.options.values.join('')
rolls_array = inc_event_roll.split(%r{\s*/\s*}).take(4)
rolls_array.each do |event_roll|
@do_tally_shuffle = false
check_comment(event_roll)
@roll_request = event_roll.dup

@input = alias_input_pass(event_roll) # Do alias pass as soon as we get the message
@simple_output = false
@wng = false
@dh = false
@godbound = false
@ed = false
@hsn = false
@hsk = false
@hsh = false
@no_result = false

@private_roll = false
@reroll_check = 0
@reroll_indefinite_check = 0
@reroll_count = 0
@botch = 0

check_roll_modes
next if @ed && !replace_earthdawn(event)

@roll_set = nil
next unless roll_sets_valid(event)

# check for single dice rolls
@input.gsub!(%r{(?<!\d)(^|[+*/-]\s?)d(\d+)}, '\11d\2') if @input.match?(%r{(?<!\d)(^|[+*/-]\s?)d(\d+)})

@roll = @input
@check = @prefix + @roll
@test_status = ''
# check user
check_user_or_nick(event)
# check for empty roll
if event_roll.empty?
event.respond(content: "#{@user} roll is empty! Please type a complete dice roll message")
next
end
# check for modifiers that should apply to everything
check_universal_modifiers

# Check for dn
@dnum = @input.scan(/dn\s?(\d+)/).first.join.to_i if @input.match?(/^(1dn)\d+/i)

# Check for correct input
if @roll.match?(/\dd\d/i)
event.channel.start_typing
next if check_roll(event) == true

# Check for wrath roll
check_wrath
# Grab dice roll, create roll, grab results
if @roll_set.nil?
next if do_roll(event) == true
else
@roll_set_results = ''
@error_check_roll_set = ''
roll_count = 0
@roll_set_total = 0
error_encountered = false
while roll_count < @roll_set.to_i
if do_roll(event) == true
error_encountered = true
break
end
@tally = alias_output_pass(@tally)
if @simple_output == true
@roll_set_results << "#{@dice_result}\n"
else
@error_check_roll_set << "#{@dice_result}\n"
@roll_set_results << "`#{@tally}` #{@dice_result}\n"
end
roll_count += 1
end
@tally = alias_output_pass(@tally)
if @simple_output == true
@roll_set_results << "#{@dice_result}\n"
next if error_encountered

log_roll(event) if @launch_option == 'debug'
if @comment.to_s.empty? || @comment.to_s.nil?
event.respond(content: "#{@user} Request: `[#{@roll_request.strip}]` Rolls:\n#{@roll_set_results}Results Total: `#{@roll_set_total}`")
else
@error_check_roll_set << "#{@dice_result}\n"
@roll_set_results << "`#{@tally}` #{@dice_result}\n"
event.respond(content: "#{@user} Rolls:\n#{@roll_set_results}Results Total: `#{@roll_set_total}`\nReason: `#{@comment}`")
end
roll_count += 1
next
end
next if error_encountered

log_roll(event) if @launch_option == 'debug'
if @comment.to_s.empty? || @comment.to_s.nil?
event.respond(content: "#{@user} Request: `[#{@roll_request.strip}]` Rolls:\n#{@roll_set_results}Results Total: `#{@roll_set_total}`")
else
event.respond(content: "#{@user} Rolls:\n#{@roll_set_results}Results Total: `#{@roll_set_total}`\nReason: `#{@comment}`")
end
next
end
# Output aliasing
@tally = alias_output_pass(@tally)

# Output aliasing
@tally = alias_output_pass(@tally)
# Does calculation for Hero System stuff, if necessary
hero_system_math if @hsn || @hsk

# Does calculation for Hero System stuff, if necessary
hero_system_math if @hsn || @hsk
# Grab event user name, server name and timestamp for roll and log it
log_roll(event) if @launch_option == 'debug'

# Grab event user name, server name and timestamp for roll and log it
log_roll(event) if @launch_option == 'debug'
@has_comment = !@comment.to_s.empty? && !@comment.to_s.nil?

# Print dice result to Discord channel
@has_comment = !@comment.to_s.empty? && !@comment.to_s.nil?
if check_wrath == true
respond_wrath(event, dnum)
elsif @private_roll
event.respond(content: build_response, ephemeral: true)
else
event.respond(content: build_response)
check_fury(event)
response_array.push(build_response)
end
end
next if check_donate(event) == true
next if check_help(event) == true
next if check_bot_info(event) == true
next if check_purge(event) == false
rescue StandardError => e ## The worst that should happen is that we catch the error and return its message.
e.message = 'NIL MESSAGE!' if e.message.nil?
# Simplify roll and send it again if we error out due to character limit
if (e.message.include? 'Message over the character limit') || (e.message.include? 'Invalid Form Body')
if @roll_set.nil?
event.respond(content: "#{@user} Roll #{@dice_result} Reason: `Simplified roll due to character limit`")
next if check_donate(event) == true
next if check_help(event) == true
next if check_bot_info(event) == true
next if check_purge(event) == false
rescue StandardError => e ## The worst that should happen is that we catch the error and return its message.
e.message = 'NIL MESSAGE!' if e.message.nil?
# Simplify roll and send it again if we error out due to character limit
if (e.message.include? 'Message over the character limit') || (e.message.include? 'Invalid Form Body')
if @roll_set.nil?
event.respond(content: "#{@user} Roll #{@dice_result} Reason: `Simplified roll due to character limit`")
else
event.respond(content: "#{@user} Rolls:\n#{@error_check_roll_set}Reason: `Simplified roll due to character limit`")
end
elsif (e.message.include? "undefined method `join' for nil:NilClass") || (e.message.include? "The bot doesn't have the required permission to do this!") || (e.message.include? '500: Internal Server Error') || (e.message.include? '500 Internal Server Error')
time = Time.now.getutc
File.open('dice_rolls.log', 'a') { |f| f.puts "#{time} ERROR: #{e.message}" }
else
event.respond(content: "#{@user} Rolls:\n#{@error_check_roll_set}Reason: `Simplified roll due to character limit`")
event.respond(content: ('Unexpected exception thrown! (' + e.message + ")\n\nPlease drop us a message in the #support channel on the dice maiden server, or create an issue on Github."))
end
elsif (e.message.include? "undefined method `join' for nil:NilClass") || (e.message.include? "The bot doesn't have the required permission to do this!") || (e.message.include? '500: Internal Server Error') || (e.message.include? '500 Internal Server Error')
time = Time.now.getutc
File.open('dice_rolls.log', 'a') { |f| f.puts "#{time} ERROR: #{e.message}" }
else
event.respond(content: ('Unexpected exception thrown! (' + e.message + ")\n\nPlease drop us a message in the #support channel on the dice maiden server, or create an issue on Github."))
end
end
# Print dice results to Discord channel
# reduce noisy errors by checking if response array is empty due to responding earlier
if response_array.empty?
# do nothing
elsif check_wrath == true
respond_wrath(event, @dnum)
elsif @private_roll
event.respond(content: response_array.join("\n").to_s, ephemeral: true)
else
event.respond(content: response_array.join("\n").to_s)
check_fury(event)
end
mutex.unlock
end

Expand Down
5 changes: 5 additions & 0 deletions doc/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 9.0.0 -2024-06-23
### Added
- Added support for rolling up to 4 unique dice rolls per command
- Fixed some bugs with Hero system hsk roll

## 8.11.1 -2024-06-21
### Added
- Fixed help rolls
Expand Down
34 changes: 17 additions & 17 deletions src/dice_maiden_logic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ def alias_input_pass(input)
[/\battack\b/i, 'DnD attack roll', /\b(attack)\b/i, '1d20'], # DnD attack roll
[/\bskill\b/i, 'DnD skill check', /\b(skill)\b/i, '1d20'], # DnD skill check
[/\bsave\b/i, 'DnD saving throw', /\b(save)\b/i, '1d20'], # DnD saving throw
[/\b\d+hsn\b/i, 'Hero System Normal', /\b(\d+)hsn\b/i, 'hsn \\1d6 nr'], # Hero System 5e Normal Damage
[/\b\d+hsk\d*\b/i, 'Hero System Killing', /\b(\d+)hsk(\d*)\b/i, 'hsk\\2 \\1d6 nr'], # Hero System 5e Killing Damage
[/\b\d+hsh\b/i, 'Hero System to Hit', /\b(\d+)hsh\b/i, 'hsh 11+\\1 -3d6 nr'] # Hero System 5e to Hit
[/\b\d+hsn\b/i, 'Hero System Normal', /\b(\d+)hsn\b/i, 'hsn nr \\1d6'], # Hero System 5e Normal Damage
[/\b\d+hsk\d*\b/i, 'Hero System Killing', /\b(\d+)hsk(\d*)\b/i, 'nr hsk\\2 \\1d6'], # Hero System 5e Killing Damage
[/\b\d+hsh\b/i, 'Hero System to Hit', /\b(\d+)hsh\b/i, 'hsh nr 11+\\1 -3d6'] # Hero System 5e to Hit
]

@alias_types = []
Expand Down Expand Up @@ -84,16 +84,16 @@ def check_user_or_nick(event)
end
end

def check_comment
def check_comment(event_roll)
@comment = ''
if @event_roll.include?('!')
@comment = @event_roll.partition('!').last.lstrip
if event_roll.include?('!')
@comment = event_roll.partition('!').last.lstrip
# remove @ user ids from comments to prevent abuse
@comment.gsub!(/<@!\d+>/, '')
@do_tally_shuffle = true if @comment.include? 'unsort'
@event_roll = @event_roll[/(^.*)!/]
@event_roll.slice! @comment
@event_roll.slice! '!'
event_roll = event_roll[/(^.*)!/]
event_roll.slice! @comment
event_roll.slice! '!'
end
end

Expand Down Expand Up @@ -569,11 +569,11 @@ def check_roll_modes
when /\s?(hsn)\s/i
@hsn = true
@input.sub!('hsn', '')
when /\s?(hsk)\s/i
when /\s?(hsk)\s?/i
@hsk = true
if @input.match(/hsk\d+/i)
multiplier_string = @input.scan(/(hsk)\d+/i)
@hsk_multiplier_modifier = multiplier_string.scan(/\d+/).to_i
multiplier_string = @input.scan(/hsk\d+/i).join.to_s
@hsk_multiplier_modifier = multiplier_string.scan(/\d+/).join.to_i
@input.sub!(/hsk\d+/i, '')
else
@hsk_multiplier_modifier = 0
Expand Down Expand Up @@ -622,13 +622,13 @@ def hero_system_math
end

if @hsk
@hsk_body = @dice_result.scan(/\d+/).to_i
@hsk_body = @dice_result.scan(/\d+/)
@hsk_stun_roll = DiceBag::Roll.new('1d6').result.total
@hsk_multiplier = @hsk_stun_roll - 1 + @hsk_multiplier_modifier
if @hsk_multiplier.zero?
@hsk_multiplier = 1
end
@hsk_stun = @hsk_body * @hsk_multiplier
@hsk_stun = @hsk_body.join.to_i * @hsk_multiplier
end
end

Expand All @@ -651,15 +651,15 @@ def build_response
response += " #{@dice_result}" unless @no_result

if @hsn
response += " Body: #{@hsn_body}, Stun:#{@hsn_stun}"
response += " Body: `#{@hsn_body}`, Stun: `#{@hsn_stun}`"
end

if @hsk
response += " Body: #{@hsk_body}, Stun Multiplier: #{@hsk_multiplier}, Stun: #{@hsk_stun}"
response += " Body: `#{@hsk_body}`, Stun Multiplier: `#{@hsk_multiplier}`, Stun: `#{@hsk_stun}`"
end

if @hsh
response += " Hits DCV #{@dice_result.scan(/\d+/)}"
response += " Hits DCV `#{@dice_result.scan(/\d+/)}`"
end

response += " Reason: `#{@comment}`" if @has_comment
Expand Down

0 comments on commit 2841208

Please sign in to comment.