Advertisement
Shammie

For DiceBot

Feb 24th, 2016
295
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Ruby 10.37 KB | None | 0 0
  1. #!/usr/bin/env ruby
  2. #
  3. # bones v0.03
  4. # by Jonathan Drain http://d20.jonnydigital.com/roleplaying-tools/dicebot
  5. # (run "bones-go.rb" first)
  6. #
  7. # NB: As a security measure, some IRC networks prevent IRC bots from joining
  8. # channels too quickly after connecting. Solve with this:
  9. # /msg bones @@@join #channel
  10.  
  11. require 'socket'
  12. require 'strscan'
  13. require 'dicebox'
  14.  
  15. module Bones
  16.   class Client # an "instance" of bones; generally only one
  17.     def initialize(nick, server, port, channels, admin, player_init_bonuses)
  18.       @running = true
  19.      
  20.       @nick = nick
  21.       @server = server # one only
  22.       @port = port
  23.       @channels = channels
  24.       @admin = admin
  25.       @player_init_bonuses = player_init_bonuses
  26.  
  27.       connect()
  28.       run()
  29.     end
  30.  
  31.     def connect
  32.       @connection = Connection.new(@server, @port)
  33.      
  34.       @connection.speak "NICK #{@nick}"
  35.       @connection.speak "USER #{@nick} bones * :Bones++ Dicebot: https://github.com/injate/BonesPlusPlus/"
  36.       # This needs some work, faking the client port (54520) right now.
  37.       # http://www.team-clanx.org/articles/socketbot-ident.html
  38.       @connection.speak "IDENT 54520, #{@port} : USERID : UNIX : #{@nick}"
  39.  
  40.       # TODO: fix join bug
  41.       join(@channels)
  42.     end
  43.  
  44.     def join(channels)
  45.       channels.each do |channel|
  46.         # join channel
  47.         @connection.speak "JOIN #{channel}"
  48.         puts "Joining #{channel}"
  49.       end
  50.     end
  51.    
  52.     def join_quietly(channels)
  53.       channels.each do |channel|
  54.         # join channel
  55.         @connection.speak("JOIN #{channel}", true)
  56.       end
  57.     end
  58.    
  59.     def run # go
  60.       # stay connected
  61.       # handle replies
  62.  
  63.       while @running
  64.         while @connection.disconnected? # never give up reconnect
  65.           sleep 10
  66.           connect()
  67.         end
  68.        
  69.         handle_msg (@connection.listen)
  70.       end
  71.     end
  72.    
  73.     def handle_msg(msg)
  74.       case msg
  75.         when nil
  76.           #nothing
  77.         when /END OF MESSAGE/ # For irc.gamesurge.net joining ASAP, the 1st join happens too early to work on some servers.
  78.           join_quietly(@channels)
  79.         when /^PING (.+)$/
  80.           @connection.speak("PONG #{$1}", true) # PING? PONG!
  81.           # TODO: Check if channels are joined before attempting redundant joins
  82.           join_quietly(@channels)
  83.         when /^:/ # msg
  84.           message = Message.new(msg)
  85.           respond(message)
  86.         else
  87.           puts "RAW>> #{msg}"
  88.           #nothing
  89.       end
  90.     end
  91.    
  92.     def respond(msg)
  93.       # msg :name, :hostname, :mode, :origin, :privmsg, :text
  94.       if msg.name =~ /#{@admin}/ && msg.text =~ /^#{@nick}, quit/i
  95.         quit(msg.text)
  96.       end
  97.      
  98.       if msg.text =~ /^#{@nick}(:|,*) ?(\S+)( (.*))?/i
  99.         prefix = @nick
  100.         command = $2.downcase
  101.         unless $4.nil? #optional args, downcase errors on nil
  102.           args = $4.downcase
  103.         end
  104.         # do command - switch statement or use a command handler class
  105.         c = command_handler(prefix, command, args)
  106.         reply(msg, c) if c
  107.       elsif msg.text =~ /^@@@join (#.*)/
  108.         join $1.to_s
  109.       elsif msg.text == "hay"  # from here any kind of random text responses can be added
  110.         reply(msg, "hay :v")
  111.       elsif msg.text == "Testing, Testing, 123"
  112.         reply(msg, "everyone has their own learning style")
  113.       elsif msg.text =~ /^(!|@)(\S+)( (.*))?/   # until here
  114.         prefix = $1
  115.         command = $2
  116.         args = $4
  117.         #do command
  118.         c = command_handler(prefix, command, args)
  119.         reply(msg, c) if c
  120.       elsif msg.text =~ /^(\d*#)?(\d+)d(\d+)/
  121.         # DICE HANDLER
  122.         dice = Dicebox::Dice.new(msg.text)
  123.         begin
  124.           d = dice.roll
  125.           if (d.length < 350)
  126.             reply(msg, d)
  127.           else
  128.             reply(msg, "I don't have enough dice to roll that!")
  129.           end
  130.         rescue Exception => e
  131.           puts "ERROR: " + e.to_s
  132.           reply(msg, "I don't understand...")
  133.         end
  134.       end
  135.     end
  136.    
  137.     def command_handler(prefix, command, args)
  138.       c = CommandHandler.new(prefix, command, args, @player_init_bonuses)
  139.       return c.handle
  140.     end
  141.  
  142.     def reply(msg, message) # reply to a pm or channel message
  143.       if msg.privmsg
  144.         @connection.speak "#{msg.mode} #{msg.name} :#{message}"
  145.       elsif msg.text =~ /^(\d*#)?(\d+)d(\d+)/       # keeps the name for rolls
  146.         @connection.speak "#{msg.mode} #{msg.origin} :#{msg.name}, #{message}"
  147.       else
  148.         @connection.speak "#{msg.mode} #{msg.origin} :#{message}"
  149.       end
  150.     end
  151.    
  152.     def pm(person, message)
  153.       @connection.speak "PRIVMSG #{person} :#{message}"
  154.     end
  155.    
  156.     def say(channel, message)
  157.       pm(channel, message) # they're functionally the same
  158.     end
  159.    
  160.     def notice(person, message)
  161.       @conection.speak "NOTICE #{person} :#{message}"
  162.     end
  163.  
  164.     def quit(message)
  165.       @connection.speak "QUIT :#{message}"
  166.       @connection.disconnect
  167.       @running = false;
  168.     end
  169.   end
  170.  
  171.   class Message
  172.     attr_accessor :name, :hostname, :mode, :origin, :privmsg, :text
  173.    
  174.     def initialize(msg)
  175.       parse(msg)
  176.     end
  177.    
  178.     def parse(msg)
  179.       # sample messages:
  180.       # :[email protected] PRIVMSG #bones :hi
  181.       # :[email protected] PRIVMSG bones :hi
  182.      
  183.       # filter out bold and colour
  184.       # feature suggested by KT
  185.       msg = msg.gsub(/\x02/, '') # bold
  186.       msg = msg.gsub(/\x03(\d)?(\d)?/, '') # colour
  187.  
  188.       case msg
  189.         when nil
  190.           puts "heard nil? wtf"
  191.         when /^:(\S+)!(\S+) (PRIVMSG|NOTICE) ((#?)\S+) :(.+)/
  192.           @name = $1
  193.           @hostname = $2
  194.           @mode = $3
  195.           @origin = $4
  196.           if ($5 == "#")
  197.             @privmsg = false
  198.           else
  199.             @privmsg = true
  200.           end
  201.           @text = $6.chomp
  202.           print()
  203.       end
  204.     end
  205.  
  206.     def print
  207.       puts "[#{@origin}|#{@mode}] <#{@name}> #{@text}"
  208.     end
  209.   end
  210.  
  211.   class Connection # a connection to an IRC server; only one so far
  212.     attr_reader :disconnected
  213.  
  214.     def initialize(server, port)
  215.       @server = server
  216.       @port = port
  217.       @disconnected = false
  218.       connect()
  219.     end
  220.    
  221.     def connect
  222.       # do some weird stuff with ports
  223.       @socket = TCPSocket.open(@server, @port)
  224.       puts "hammer connected!"
  225.       @disconnected = false
  226.     end
  227.  
  228.     def disconnected? # inadvertently disconnected
  229.       return @socket.closed? || @disconnected
  230.     end
  231.  
  232.     def disconnect
  233.       @socket.close
  234.     end
  235.  
  236.     def speak(msg,quietly = nil)
  237.       begin
  238.         if quietly != true
  239.           puts("spoke>> " + msg)
  240.         end
  241.         @socket.write(msg + "\n")
  242.       rescue Errno::ECONNRESET
  243.         @disconnected = true;
  244.       end
  245.     end
  246.  
  247.     def listen  # poll socket for lines. luckily, listen is sleepy
  248.       sockets = select([@socket], nil, nil, 1)
  249.       if sockets == nil
  250.         return nil
  251.       else
  252.         begin
  253.           s = sockets[0][0] # read from socket 1
  254.          
  255.           if s.eof?
  256.             @disconnected = true
  257.             return nil
  258.           end
  259.          
  260.           msg = s.gets
  261.          
  262.         rescue Errno::ECONNRESET
  263.           @disconnected = true
  264.           return nil
  265.         end
  266.       end
  267.     end
  268.   end
  269.  
  270.   class CommandHandler
  271.     def initialize(prefix, command, args, player_init_bonuses)
  272.       @prefix = prefix
  273.       @command = command
  274.       @args = args
  275.       @args.strip if @args
  276.       @player_init_bonuses = player_init_bonuses
  277.     end
  278.    
  279.     def handle
  280.       case @command
  281.     when "init", "initiative"
  282.       result = handle_init
  283.         when "chargen"
  284.           result = handle_chargen
  285.         when "rules", "rule"
  286.           result = handle_rules
  287.         when "help"
  288.           result = handle_help
  289.         else
  290.           result = nil
  291.         #end
  292.       end
  293.       return result
  294.     end
  295.    
  296.     def handle_chargen
  297.       set = []
  298.       6.times do
  299.         roll = []
  300.         4.times do
  301.           roll << rand(6)+1
  302.         end
  303.         roll = roll.sort
  304.         total = roll[1] + roll[2] + roll[3]
  305.         set << total
  306.       end
  307.      
  308.       if set.sort[5] < 13
  309.         return handle_chargen
  310.       end
  311.      
  312.       return set.sort.reverse.join(", ")
  313.     end
  314.    
  315.     def handle_rules
  316.       case @args
  317.         when "chargen", "pointsbuy", "pointbuy", "point buy", "points buy", "houserules", "house rules"
  318.           result = "Iron Heroes style pointbuy, 26 points. "
  319.           result += "Ability scores start at 10. Increments cost 1pt up to 15, 2pts up to 17, "
  320.           result += "and 4pts up to 18, before racial modifiers. "
  321.           result += "You may drop any one 10 to an 8 and spend the two points elsewhere. "
  322.           result += "You may have up to one flaw and two traits."
  323.         else
  324.           result = nil
  325.         #end
  326.       end
  327.       return result
  328.     end
  329.    
  330.     def handle_help
  331.       result = "Roll dice in the format '1d20+6'. Multiple sets as so: '2#1d20+6'. "
  332.       result += "Rolls can be followed with a comment as so: '1d20+6 attack roll'. "
  333.       result += "Separate multiple rolls with a semicolon, ';'. "
  334.       result += "Features: Can add and subtract multiple dice, shows original rolls, "
  335.       result += "high degree of randomness (uses a modified Mersenne Twister with a period of 2**19937-1)."
  336.       result += "Bugs: must specify the '1' in '1d20'."
  337.       return result
  338.     end
  339.  
  340.     def handle_join(client,channel)
  341.       client.join(channel)
  342.     end
  343.  
  344.     def handle_init()
  345.       players = @player_init_bonuses
  346.       playerRolls = Hash.new
  347.       playerFullRolls = Hash.new
  348.       results = '| '
  349.       resultsFull = '| '
  350.       players.each { |player,bonus|
  351.         init = Dicebox::Dice.new("1d10+"+bonus.to_s())
  352.         initroll = init.roll
  353.         playerFullRolls[player] = initroll
  354.         /^[^:]*: (-?\d+)/ =~ initroll
  355.         playerRolls[player] = Regexp.last_match(1).to_i(10)
  356.       }
  357.       playerRolls = playerRolls.sort {|a,b| a[0]<=>b[0]}
  358.       playerRolls = playerRolls.sort {|a,b| a[1]<=>b[1]}
  359.       playerRolls.each { |player,roll|
  360.         results += "#{player}: #{roll} | "
  361.         resultsFull += "#{player}: " + playerFullRolls[player] +" | "
  362.       }
  363.  
  364.       if @args =~ /details?$/i || @args =~ /full?$/i
  365.         # Show full rolls
  366.         return resultsFull
  367.       else
  368.         # Just show the final roll
  369.         return results
  370.       end
  371.     end
  372.   end
  373. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement