Class: Minecraft::Extensions

Inherits:
Object
  • Object
show all
Includes:
Commands
Defined in:
lib/minecraft/extensions.rb

Overview

An Extensions instance is meant to process pipes from a Server instance and manage custom functionality additional to default Notchian Minecraft behaviour.

Constant Summary

Constants included from Data

Data::DATA_VALUE_HASH, Data::ITEM_BUCKETS, Data::KITS, Data::TIME, Data::TIME_QUOTES

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Commands

#addtimer, #board, #cancelvote, #dawn, #day, #dehop, #deltimer, #disco, #disturb, #dnd, #dusk, #evening, #finished, #give, #help, #hop, #kickvote, #kickvotes, #kit, #kitlist, #list, #memo, #morning, #night, #nom, #om, #points, #printdnd, #printtime, #printtimer, #property, #roulette, #rules, #s, #shortcuts, #stop, #todo, #tp, #tpall, #uptime, #vote, #warptime, #welcome

Constructor Details

#initialize(server, opts) ⇒ Extensions

New Extensions instance.

Parameters:

  • server (IO)

    The standard input pipe of the server process.

  • opts (Slop)

    Command line options from Slop.



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/minecraft/extensions.rb', line 13

def initialize(server, opts)
  @ops = File.readlines("ops.txt").map { |s| s.chomp } if File.exists? "ops.txt"
  get_json :hops, []
  get_json :uptime
  get_json :timers
  get_json :shortcuts
  get_json :userlog
  get_json :userpoints
  get_json :userdnd, []
  get_json :memos
  get_json :todo_items, []
  @users = []
  @counter = 0
  @logon_time = {}
  @server = server
  @kickvotes = {}
  @last_kick_vote = nil
  load_server_properties

  @ic = Iconv.new("UTF-8//IGNORE", "UTF-8")

  opts.to_hash.each { |k, v| instance_variable_set("@#{k}", v) }
  @vote_expiration ||= 300
  @vote_threshold  ||= 5
  @rules           ||= "No rules specified."

  # Initialize the set of commands.
  @commands = {}
  commands = Minecraft::Commands.public_instance_methods
  @command_info = File.read(method(commands.first).source_location.first).split("\n")
  @enums = [ :ops ]
  commands.each do |sym|
    next if sym.to_s.end_with? "all"
    meth  = method(sym)
    src_b, src_e = get_comment_range(meth.source_location.last)

    @commands[sym] = {
      :help => "",
      :ops => :none,
      :params => meth.parameters
    }
    parse_comments(src_b, src_e, sym)
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(sym, *args) ⇒ Object

If a command method is called and is not specified, take in the arguments here and attempt to !give the player the item. Otherwise print an error.



368
369
370
371
372
373
374
375
376
# File 'lib/minecraft/extensions.rb', line 368

def method_missing(sym, *args)
  item, quantity = items_arg(1, [sym.to_s.downcase, args.last])
  item = resolve_item(item)
  if item and is_op? args.first
    give(args.first, item, quantity.to_s)
  else
    puts "#{item} is invalid."
  end
end

Instance Attribute Details

#welcome_messageObject

Returns the value of attribute welcome_message.



6
7
8
# File 'lib/minecraft/extensions.rb', line 6

def welcome_message
  @welcome_message
end

Instance Method Details

#calculate_uptime(user) ⇒ Integer

Calculate a users current uptime.

Parameters:

  • user (String)

    The specified user.

Returns:

  • (Integer)

    The uptime in seconds.



400
401
402
# File 'lib/minecraft/extensions.rb', line 400

def calculate_uptime(user)
  Time.now - (@logon_time[user] || 0)
end

#call_command(user, command, *args) ⇒ Object

Complicated method to decide the logic of calling a command. Checks if the command requires op privileges and whether an ‘all` version is available and has been requested.

Figures out the root portion of the command, checks if a validation method is available, if so it will be executed. Then checks if op privileges are required, any ‘all` version of the command requires ops as it affects all users. The corresponding method will be called, if an `all` command is used it will call the corresponding method if available or loop through the base command for each connected user.

Examples:

call_command("basicxman", "give", "cobblestone", "64")

Parameters:

  • user (String)

    The requesting user.

  • command (String)

    The requested command.

  • args

    Arguments for the command.



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/minecraft/extensions.rb', line 140

def call_command(user, command, *args)
  is_all = command.to_s.end_with? "all"
  root   = command.to_s.chomp("all").to_sym
  return send(root, user, *args) unless @commands.include? root

  # Any `all` suffixed command requires ops.
  if @commands[root][:ops] == :op or (is_all and @commands[root][:all])
    return unless validate_ops(user, command)
  elsif @commands[root][:ops] == :hop
    return unless validate_ops(user, command, false) or validate_hops(user, command)
  end

  if respond_to? "validate_" + root.to_s
    return unless send("validate_" + root.to_s, *args)
  end

  is_all = @commands[root][:all] if is_all
  rest_param = @commands[root][:params].count { |a| a.first == :rest }
  reg_params = @commands[root][:params].count { |a| a.last != :user }

  # Remove excess parameters.
  args = args[0...reg_params] if args.length > reg_params and rest_param == 0

  args = [user] + args unless @commands[root][:params].length == 0
  if is_all
    @server.puts "say #{user} #{@commands[root][:all]}"
    if respond_to? command
      send(command, *args)
    else
      @users.each { |u| send(root, u, *args[1..-1]) unless @userdnd.include? u.downcase }
    end
  else
    send(root, *args)
  end
rescue Exception => e
  validate_command_entry(rest_param, reg_params, user, command, *args)
  $stderr.puts "An error has occurred during a call command operation.\n#{e}"
  $stderr.puts e.backtrace
end

#check_join_part(line) ⇒ Object

Check if a console line has informed us about a player [dis]connecting.



352
353
354
355
356
357
358
359
360
361
362
363
364
# File 'lib/minecraft/extensions.rb', line 352

def check_join_part(line)
  user = line.split(" ").first
  if line.index "lost connection"
    log_time(user)
    return remove_user(user)
  elsif line.index "logged in"
    @users << user
    display_welcome_message(user)
    check_memos(user)
    @logon_time[user] = Time.now
    return true
  end
end

#check_kick_ban(line) ⇒ Object

Check if a console line has informed us about a ban or kick.



330
331
332
333
334
335
336
337
# File 'lib/minecraft/extensions.rb', line 330

def check_kick_ban(line)
  user = line.split(" ").last
  if line.index "Banning"
    return remove_user(user)
  elsif line.index "Kicking"
    return remove_user(user)
  end
end

#check_memos(user) ⇒ Object

Checks the available memos for a uesr who has just logged in, prints any that are found.

Examples:

check_memos("mike_n_7")

Parameters:

  • user (String)

    The user to check.



282
283
284
285
286
287
288
# File 'lib/minecraft/extensions.rb', line 282

def check_memos(user)
  user = user.downcase
  return unless @memos.has_key? user

  @memos[user].each { |m| say("Message from: #{m.first} - #{m.last}") }
  @memos[user] = []
end

#check_ops(line) ⇒ Object

Check if a console line has informed us about a [de-]op privilege change.



340
341
342
343
344
345
346
347
348
349
# File 'lib/minecraft/extensions.rb', line 340

def check_ops(line)
  user = line.split(" ").last
  if line.index "De-opping"
    @ops.reject! { |u| u == user.downcase }
    return true
  elsif line.index "Opping"
    @ops << user.downcase
    return true
  end
end

#check_saveObject

Checks if the server needs to be saved and prints the save-all command if so.



233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/minecraft/extensions.rb', line 233

def check_save
  if @savefreq.nil?
    freq = 30
  elsif @savefreq == "0"
    return
  else
    freq = @savefreq.to_i
  end
  if @counter % freq == 0
    @server.puts "save-all"
    save
  end
end

#colour(line) ⇒ Object

Colours a server side line



217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/minecraft/extensions.rb', line 217

def colour(line)
  return line if @no_colour
  line.gsub!(/^([0-9\-]{10}\s[0-9:]{8})/) { |m| "\033[0;37m#{$1}\033[0m" }
  if line.index "lost connection" or line.index "logged in"
    line.gsub!(/(\[INFO\]\s)(.*)/) { |m| "#{$1}\033[1;30m#{$2}\033[0m" }
  elsif line.index "[INFO] CONSOLE:"
    line.gsub!("CONSOLE:", "\033[1;36mCONSOLE:\033[0m")
  else
    line.gsub!(/(\[INFO\]\s+\<)(.*?)(\>)/) { |m| "#{$1}\033[1;34m#{$2}\033[0m#{$3}" }
    line.gsub!(/(\>\s+)(!.*?)$/) { |m| "#{$1}\033[1;33m#{$2}\033[0m" }
  end
  return line
end

#display_welcome_message(user) ⇒ Object

Display a welcome message to a recently connected user.



477
478
479
# File 'lib/minecraft/extensions.rb', line 477

def display_welcome_message(user)
  say("#{@welcome_message.gsub('%', user)}") unless @welcome_message.nil?
end

#format_uptime(time) ⇒ Float

Format an uptime for printing. Should not be used for logging.

Parameters:

  • time (Integer)

    Time difference in seconds.

Returns:

  • (Float)

    Returns the number of minutes rounded to two precision.



392
393
394
# File 'lib/minecraft/extensions.rb', line 392

def format_uptime(time)
  (time / 60.0).round(2)
end

#get_comment_range(line_number) ⇒ Object

Gets the line number bounds for the comments corresponding with a method on a given line number.



87
88
89
90
91
# File 'lib/minecraft/extensions.rb', line 87

def get_comment_range(line_number)
  src_e = line_number - 2
  src_b = (0..src_e - 1).to_a.reverse.detect(src_e - 1) { |n| not @command_info[n] =~ /^\s+#/ } + 1
  [src_b, src_e]
end

#get_json(var, blank = {}) ⇒ Object

Sets an instance variable with it’s corresponding data file or a blank hash.



94
95
96
97
98
99
100
101
102
# File 'lib/minecraft/extensions.rb', line 94

def get_json(var, blank = {})
  file = "#{var}.json"
  t = if File.exists? file
    JSON.parse(File.read(file))
  else
    blank
  end
  instance_variable_set("@#{var}", t)
end

#info_command(line) ⇒ Object

Removes the meta data (timestamp, INFO) from the line and then executes a series of checks on the line. Grabs the user and command from the line and then calls the call_command method.

Parameters:

  • line (String)

    The line from the console.



295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/minecraft/extensions.rb', line 295

def info_command(line)
  line.gsub! /^.*?\[INFO\]\s+/, ''
  return if meta_check(line)

  # :foo should use the shortcut 'foo'.
  line.gsub!(/^(\<.*?\>\s+):/) { |m| "#{$1}!s " }

  match_data = line.match /^\<(.*?)\>\s+!(.*?)$/
  return if match_data.nil?

  user = match_data[1]
  args = match_data[2].split(" ")
  call_command(user, args.slice!(0).to_sym, *args)
end

#invalid_command(command) ⇒ Object

An error message for invalid commands.



462
463
464
# File 'lib/minecraft/extensions.rb', line 462

def invalid_command(command)
  @server.puts "say #{command} is invalid."
end

#is_hop?(user) ⇒ Boolean

Check if a user has half op privileges.

Parameters:

  • user (String)

    The specified user.

Returns:

  • (Boolean)


437
438
439
# File 'lib/minecraft/extensions.rb', line 437

def is_hop?(user)
  @hops.include? user.downcase
end

#is_op?(user) ⇒ Boolean

Check if a user has op privileges.

Parameters:

  • user (String)

    The specified user.

Returns:

  • (Boolean)


429
430
431
# File 'lib/minecraft/extensions.rb', line 429

def is_op?(user)
  @ops.include? user.downcase
end

#load_server_propertiesObject

Load the server.properties file into a Ruby hash.



467
468
469
470
471
472
473
474
# File 'lib/minecraft/extensions.rb', line 467

def load_server_properties
  @server_properties = {}
  File.readlines("server.properties").each do |line|
    next if line[0] == "#"
    key, value = line.split("=")
    @server_properties[key] = value
  end
end

#log_time(user) ⇒ Object

Calculate and print the time spent by a recently disconnected user. Save the user uptime log.



380
381
382
383
384
385
386
# File 'lib/minecraft/extensions.rb', line 380

def log_time(user)
  time_spent = calculate_uptime(user)
  @userlog[user] ||= 0
  @userlog[user] += time_spent
  @server.puts "say #{user} spent #{format_uptime(time_spent)} minutes in the server, totalling to #{format_uptime(@userlog[user])}."
  save_file :userlog
end

#meta_check(line) ⇒ Object

Executes the meta checks (kick/ban, ops, join/disconnect) and returns true if any of them were used (and thus no further processing required).

Parameters:

  • line (String)

    The passed line from the console.



314
315
316
317
318
# File 'lib/minecraft/extensions.rb', line 314

def meta_check(line)
  return true if check_kick_ban(line)
  return true if check_ops(line)
  return true if check_join_part(line)
end

#parse_comments(src_b, src_e, sym) ⇒ Object

Parses the comments for a given command method between the two given line numbers. Places the results in the commands instance hash.

Examples:

parse_comments(6, 13, :disco)

Parameters:

  • src_b (Integer)

    Beginning comment line.

  • src_e (Integer)

    Ending comment line.

  • sym (Symbol)

    Method symbol.



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/minecraft/extensions.rb', line 66

def parse_comments(src_b, src_e, sym)
  help_done = false
  (src_b..src_e).each do |n|
    line = @command_info[n].strip[2..-1]
    if line.nil?
      help_done = true
      next
    elsif !help_done
      @commands[sym][:help] += " " unless @commands[sym][:help].empty?
      @commands[sym][:help] += line
    end

    if line.index("@note") == 0
      key, value = line[6..-1].split(": ")
      @commands[sym][key.to_sym] = @enums.include?(key.to_sym) ? value.to_sym : value
    end
  end
end

#periodicObject

Increments the counter and checks if any timers are needed to be executed.



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/minecraft/extensions.rb', line 249

def periodic
  @counter += 1
  check_save
  if @disco
    if @counter % 2 == 0
      @server.puts "time set 0"
    else
      @server.puts "time set 16000"
    end
  end

  if @counter % 10 == 0
    expire_kickvotes
    if @time_change and @time_change != 0
      @server.puts "time add #{@time_change}"
    end
  end

  @users.each do |user|
    next unless @timers.has_key? user
    @timers[user].each do |item, duration|
      next if duration.nil?
      @server.puts "give #{user} #{item} 64" if @counter % duration == 0
    end
  end
end

#process(line) ⇒ Object

Processes a line from the console.



207
208
209
210
211
212
213
214
# File 'lib/minecraft/extensions.rb', line 207

def process(line)
  line = @ic.iconv(line) if line.index "7"
  puts colour(line.dup)
  return info_command(line) if line.index "INFO"
rescue Exception => e
  $stderr.puts "An error has occurred during line processing.\n#{e}"
  $stderr.puts e.backtrace
end

#remove_user(user) ⇒ Boolean

Removes the specified user as from the connected players array.

Parameters:

  • user (String)

    The specified user.

Returns:

  • (Boolean)

    Always returns true.



324
325
326
327
# File 'lib/minecraft/extensions.rb', line 324

def remove_user(user)
  @users.reject! { |u| u.downcase == user.downcase }
  return true
end

#saveObject

Save instance variables to their respective JSON files.



105
106
107
108
109
110
111
112
113
# File 'lib/minecraft/extensions.rb', line 105

def save
  save_file :timers
  save_file :shortcuts
  save_file :hops
  save_file :userpoints
  save_file :userdnd
  save_file :memos
  save_file :todo_items
end

#save_file(var) ⇒ Object

Save an instance hash to it’s associated data file.

Examples:

save_file :timers

Parameters:

  • var (Symbol)


120
121
122
# File 'lib/minecraft/extensions.rb', line 120

def save_file(var)
  File.open("#{var}.json", "w") { |f| f.print instance_variable_get("@#{var}").to_json }
end

#say(message) ⇒ Object

Executes the say command with proper formatting (i.e. wrapping at sixty characters).

Examples:

say("The quick brown fox jumped over the lazy dog.")

Parameters:

  • message (String)

    The message to print properly.



410
411
412
413
414
415
416
417
418
419
420
421
422
423
# File 'lib/minecraft/extensions.rb', line 410

def say(message)
  temp_length, buf = 0, []
  message.split(" ").each do |word|
    temp_length += word.length
    if temp_length > 45
      @server.puts "say #{buf.join(" ")}"
      buf = [word]
      temp_length = word.length
    else
      buf << word
    end
  end
  @server.puts "say #{buf.join(" ")}" unless buf.empty?
end

#validate_command_entry(rest_param, reg_params, user, command, *args) ⇒ Object

After an exception is caught this method should be called to find and print errors with the arguments specified to the command.

Examples:

call_command("basicxman", "give")

Parameters:

  • user (String)

    The requesting user.

  • command (String)

    The requested command.

  • args

    Arguments for the command.



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/minecraft/extensions.rb', line 188

def validate_command_entry(rest_param, reg_params, user, command, *args)
  args.slice! 0 if args.first == user
  params = @commands[command.to_sym][:params][1..-1].map { |a| [a[0], a[1].to_s.gsub("_", " ")] }
  return unless args.length < reg_params

  return @server.puts "say Expected at least one argument." if rest_param == 1
  req_params = params.count { |a| a.first == :req }
  if args.length < req_params
    args.length.times { params.slice! 0 }
    if params.length == 1
      return @server.puts "say Expected the argument '#{params[0][1]}'"
    else
      temp = params.map { |a| "'#{a[1]}'" }
      return @server.pust "say Expected additional arguments, #{temp.join(", ")}"
    end
  end
end

#validate_hops(user, command, message = true) ⇒ Boolean

Check if a user has half op privileges and print a privilege error if not.

Parameters:

  • user (String)

    The specified user.

  • command (String)

    The command they tried to use.

Returns:

  • (Boolean)

    Returns true if the user is an op.



456
457
458
459
# File 'lib/minecraft/extensions.rb', line 456

def validate_hops(user, command, message = true)
  return true if is_hop? user.downcase
  @server.puts "say #{user} is not a half-op, cannot use !#{command}." if message
end

#validate_ops(user, command, message = true) ⇒ Boolean

Check if a user has op privileges and print a privilege error if not.

Parameters:

  • user (String)

    The specified user.

  • command (String)

    The command they tried to use.

Returns:

  • (Boolean)

    Returns true if the user is an op.



446
447
448
449
# File 'lib/minecraft/extensions.rb', line 446

def validate_ops(user, command, message = true)
  return true if is_op? user.downcase
  @server.puts "say #{user} is not an op, cannot use !#{command}." if message
end