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, Data::WOOL_COLOURS

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, #history, #hop, #kickvote, #kickvotes, #kit, #kitlist, #last, #list, #memo, #morning, #night, #nom, #om, #points, #printdnd, #printtime, #printtimer, #property, #roulette, #rules, #s, #shortcuts, #stop, #todo, #tp, #tpall, #uptime, #validate_kit, #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
57
# 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 :useruptime
  get_json :timers
  get_json :usershortcuts
  get_json :userlog
  get_json :userpoints
  get_json :userdnd, []
  get_json :memos
  get_json :todo_items, []
  get_json :command_history
  @users = []
  @counter = 0
  @logon_time = {}
  @server = server
  @userkickvotes = {}
  @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(/$/)
  @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 => "",
      :params => meth.parameters
    }
    parse_comments(src_b, src_e, sym)
    @commands.delete sym if @commands[sym][:ops].nil?
  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.



398
399
400
401
402
403
404
405
406
# File 'lib/minecraft/extensions.rb', line 398

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.



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

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.



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/minecraft/extensions.rb', line 162

def call_command(user, command, *args)
  process_history_addition(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].nil? 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.



382
383
384
385
386
387
388
389
390
391
392
393
394
# File 'lib/minecraft/extensions.rb', line 382

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.



360
361
362
363
364
365
366
367
# File 'lib/minecraft/extensions.rb', line 360

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.



305
306
307
308
309
310
311
# File 'lib/minecraft/extensions.rb', line 305

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.



370
371
372
373
374
375
376
377
378
379
# File 'lib/minecraft/extensions.rb', line 370

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.



256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/minecraft/extensions.rb', line 256

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



240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/minecraft/extensions.rb', line 240

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.



507
508
509
# File 'lib/minecraft/extensions.rb', line 507

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.



422
423
424
# File 'lib/minecraft/extensions.rb', line 422

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.



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

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.



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

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.



318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
# File 'lib/minecraft/extensions.rb', line 318

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(" ")

  if args.length == 0
    return call_command(user, :last)
  elsif args.first == "!" * args.first.length
    return call_command(user, :last, args.first.length + 1)
  end

  call_command(user, args.slice!(0).to_sym, *args)
end

#invalid_command(command) ⇒ Object

An error message for invalid commands.



492
493
494
# File 'lib/minecraft/extensions.rb', line 492

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)


467
468
469
# File 'lib/minecraft/extensions.rb', line 467

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)


459
460
461
# File 'lib/minecraft/extensions.rb', line 459

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

#load_server_propertiesObject

Load the server.properties file into a Ruby hash.



497
498
499
500
501
502
503
504
# File 'lib/minecraft/extensions.rb', line 497

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.



410
411
412
413
414
415
416
# File 'lib/minecraft/extensions.rb', line 410

def log_time(user)
  time_spent = calculate_uptime(user)
  @userlog[user] ||= 0
  @userlog[user] += time_spent
  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.



344
345
346
347
348
# File 'lib/minecraft/extensions.rb', line 344

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.



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

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.



272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/minecraft/extensions.rb', line 272

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.



230
231
232
233
234
235
236
237
# File 'lib/minecraft/extensions.rb', line 230

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

#process_history_addition(user, command, args) ⇒ Object

Process command history addition.

Examples:

process_history_addition("basicxman", "give", "cobblestone")
process_history_addition("basicxman", "history")

Parameters:

  • user (String)

    The user executing the command.

  • command (String)

    The command to potetentially add to history.

  • args (Array)

    Command arguments.



134
135
136
137
138
139
140
141
142
143
144
# File 'lib/minecraft/extensions.rb', line 134

def process_history_addition(user, command, args)
  blacklist = %w( last history s )
  return if blacklist.include? command.to_s

  history = [command] + args
  u = user.downcase

  @command_history[u] ||= []
  return if @command_history[u].last == history
  @command_history[u] << history
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.



354
355
356
357
# File 'lib/minecraft/extensions.rb', line 354

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

#saveObject

Save instance variables to their respective JSON files.



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

def save
  save_file :timers
  save_file :usershortcuts
  save_file :hops
  save_file :userpoints
  save_file :userdnd
  save_file :memos
  save_file :todo_items
  save_file :command_history
end

#save_file(var) ⇒ Object

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

Examples:

save_file :timers

Parameters:

  • var (Symbol)


122
123
124
# File 'lib/minecraft/extensions.rb', line 122

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.



440
441
442
443
444
445
446
447
448
449
450
451
452
453
# File 'lib/minecraft/extensions.rb', line 440

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.



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/minecraft/extensions.rb', line 211

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 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 say("Expected the argument '#{params[0][1]}'")
    else
      temp = params.map { |a| "'#{a[1]}'" }
      return 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.



486
487
488
489
# File 'lib/minecraft/extensions.rb', line 486

def validate_hops(user, command, message = true)
  return true if is_hop? user.downcase
  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.



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

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