Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Script] [sorter] Rework of sorter to accept table arg #7027

Merged
merged 10 commits into from
Dec 18, 2024
331 changes: 172 additions & 159 deletions sorter.lic
Original file line number Diff line number Diff line change
Expand Up @@ -2,177 +2,190 @@
Documentation: https://elanthipedia.play.net/Lich_script_development#sorter
=end

custom_require.call(%w[common])

@settings = get_settings
@settings.sorter['ignore_categories'] ||= 'nil'

script.want_downstream = false
script.want_downstream_xml = true
hide_me

best_column_count = proc { |list|
num_columns = 1
loop do
items_per_column = (list.length / num_columns.to_f).ceil
total_width = 0
for column_num in 0...num_columns
max_width = 0
list[(column_num * items_per_column)...((column_num + 1) * items_per_column)].each { |item| max_width = [max_width, item.length].max }
total_width += max_width
end
total_width += (num_columns - 1) * 8
if total_width > @settings.sorter['width'].to_i - 8
num_columns -= 1
break
elsif num_columns >= list.length
break
end
num_columns += 1
custom_require.call(%w[common common-items])
require 'terminal-table'

class Sorter
def initialize(use_table)
@settings = get_settings
@settings.sorter['ignore_categories'] ||= 'nil'
@use_table = use_table
setup_downstream_hook
end
[num_columns, 1].max
}

begin
action = proc { |s|
if s =~ /^(?:[IO]n the .*? you see |You rummage .*? and see .*?(?:, | and )(?:a|an|some) |You take a moment to look for all the items in the area and see|(?:You are|He is|She is) wearing )/ && @settings.sorter['mute_old_inventory']
if s =~ /You take a moment to look for all the items in the area and see/ && [email protected]['sort_look_items_command']
s
elsif s =~ /You are wearing/ && [email protected]['sort_inv_command']
s
elsif s =~ /(?:He is|She is) wearing/ && [email protected]['sort_look_others']
s
def setup_downstream_hook
action = proc { |line| process_line(line) }
DownstreamHook.add('sorter', action)
end

def process_line(line)
if line =~ /^(You rummage .*? and see .*?|You rummage .*? but there is nothing in there\.)/
container_name, items = parse_rummage_output(line)
sorted_items = sort_items(items)
@use_table ? display_table(sorted_items, container_name) : display_default(sorted_items, container_name)
nil # Suppress the original line output
elsif line =~ /(?:In|On|Behind|Under) (?:an?|the) (.*?) you see (.*)\./
container_name = Regexp.last_match(1).strip
contents = Regexp.last_match(2)
items = DRC.list_to_array(contents)
sorted_items = sort_items(items)
@use_table ? display_table(sorted_items, container_name) : display_default(sorted_items, container_name)
nil # Suppress the original line output
elsif line =~ /^On (?:an?|the) (.*?), you see:/
container_name = $1.strip
shop_items = []
while (item_line = get) !~ /\[Type SHOP \[GOOD\] or click an item to see some details about it\.\]/
if item_line =~ /<d cmd='shop #\d+ on #\d+'>(.*?)<\/d>/
shop_items << $1.strip
end
end
sorted_items = sort_shop_items(shop_items)
display_shop_table(sorted_items, container_name)
nil
else
s
line # Allow non-matching lines to pass through
end
}
DownstreamHook.add('sorter', action)
while (line = get)
if line =~ /^([IO]n the .*?) (you see .*\.)/
container = Regexp.last_match(1)
contents = Regexp.last_match(2)
next if line =~ /In the/ && line =~ /On the/
elsif line =~ /^(You rummage .*?)( and see .*?\.)/
container = Regexp.last_match(1)
contents = Regexp.last_match(2)
next if line =~ /In the/ && line =~ /On the/
elsif line =~ /(You take a moment to look for all the items in the area)( and see .*?\.)/ && @settings.sorter['sort_look_items_command']
container = Regexp.last_match(1)
contents = Regexp.last_match(2)
next if line =~ /In the/ && line =~ /On the/
elsif line =~ /You are wearing (.*\.)/ && @settings.sorter['sort_inv_command']
container = 'Inventory'
contents = 'and see ' + Regexp.last_match(1)
next if line =~ /In the/ && line =~ /On the/
elsif line =~ /(?:He is|She is) wearing (.*\.)/ && @settings.sorter['sort_look_others']
container = 'Inventory'
contents = 'and see ' + Regexp.last_match(1)
next if line =~ /In the/ && line =~ /On the/
else
next
end

def parse_rummage_output(line)
if line =~ /^You rummage through (.*?) and see (.*)\./
container_name = $1
items = DRC.list_to_array($2)
return container_name, items
elsif line =~ /^You rummage through (.*?) but there is nothing in there\./
container_name = $1
return container_name, []
end
if contents =~ /(?:and|you) see (.*)\./
if (contents = DRC.list_to_array(Regexp.last_match(1)))
sorted_contents = {}
item_data = get_data('sorting').to_h
item_data.merge!(get_data('items').to_h.keep_if { |key, _value| key =~ /gem_nouns|scroll_nouns/ })

contents.each do |item|
item = item.sub(/^\s*?\b(?:a|an|some|and|the)\b\s/, '')
category_name = 'other'
item_data.each do |key, value|
if item =~ /pushBold|popBold/
category_name = 'NPC'
break
elsif DRC.get_noun(item) =~ /#{value.join('$|').concat('$')}/i || DRC.remove_flavor_text(item) =~ /(?:#{value.join('$|').concat('$')})/i
if key.to_s.sub(/_nouns|_types/, '') !~ /#{@settings.sorter['ignore_categories']}/i
category_name = key.to_s.sub(/_nouns|_types/, '')
break
end
end
end
sorted_contents[category_name] ||= {}
sorted_contents ||= {}
if sorted_contents[category_name][item].nil?
echo "Name: #{item.ljust(75)} Noun: #{DRC.get_noun(item).ljust(20)} Category: #{category_name}" if UserVars.sorter_debug == 'true'
sorted_contents[category_name][item] = {}
sorted_contents[category_name][item]['noun'] = DRC.get_noun(item)
# sorted_contents[category_name][item]['exist'] = item.id
sorted_contents[category_name][item]['count'] = 1
else
sorted_contents[category_name][item]['count'] += 1
end
end
output = if (@settings.sorter['width'].to_i > 0) && ($frontend == 'stormfront')
"<output class=\"mono\"/>\n"
else
''
end
sorted_contents = sorted_contents.sort { |a, b| a[0].split(/\s/).last <=> b[0].split(/\s/).last }
output.concat "#{container}:\n"
if @settings.sorter['width'].to_i > 0
for category_name, category_contents in sorted_contents
count = 0
category_contents.each_value { |value| count += value['count'] }
output.concat "#{monsterbold_start}#{category_name} (#{count}):#{monsterbold_end} \n"
category_contents = category_contents.sort { |a, b| a[0].split(/\s/).last <=> b[0].split(/\s/).last }
column_count = best_column_count.call(category_contents.collect { |a| a[0] })
row_count = (category_contents.length / column_count.to_f).ceil
column_count = (category_contents.length / row_count.to_f).ceil
column_widths = []
for column_num in 0...column_count
category_contents[(column_num * row_count)...((column_num + 1) * row_count)].each { |item| column_widths[column_num] = [column_widths[column_num].to_i, item.length].max }
end
for row_num in 0...row_count
output.concat ' '
for column_num in 0...column_count
item, item_info = category_contents[(column_num * row_count) + row_num]
str = if item_info['count'] > 1
"#{item} (#{item_info['count']})"
else
item.to_s
end
str = str.ljust(column_widths[column_num] + str.length + 12)
str = str.strip if column_num == column_count - 1
output.concat str
end
output.concat "\n"
end
output.concat "\n"
end
end

def sort_items(items)
sorted = {}
items.each do |item|
clean_item = item.sub(/^\s*?\b(?:a|an|some|and|the)\b\s/, '').chomp('.').strip
noun = DRC.get_noun(clean_item)
type = get_item_type(clean_item, noun)

if sorted[type]
if sorted[type][clean_item]
sorted[type][clean_item][:qty] += 1
else
for category_name, category_contents in sorted_contents
count = 0
category_contents.each_value { |value| count += value['count'] }
output.concat "#{monsterbold_start}#{category_name} (#{count}):#{monsterbold_end} "
category_contents = category_contents.sort { |a, b| a[0].split(/\s/).last <=> b[0].split(/\s/).last }
for item, item_info in category_contents
if item_info['count'] > 1
output.concat "#{item} (#{item_info['count']}), "
else
output.concat "#{item}, "
end
end
output.chop!
output.chop!
output.concat ".\n"
end
sorted[type][clean_item] = { noun: noun, qty: 1, full_description: item.chomp('.').strip }
end
if (@settings.sorter['width'].to_i > 0) && ($frontend == 'stormfront')
output.concat "<output class=\"\"/>\n"
else
sorted[type] = { clean_item => { noun: noun, qty: 1, full_description: item.chomp('.').strip } }
end
end
sorted
end

def sort_shop_items(items)
sorted = {}
items.each do |item|
if item =~ /(.*) for (.*?) (.*?) Kronars$/
full_description = $1.strip
price = $2
currency = $3
clean_item = full_description.sub(/^\s*?\b(?:a|an|some|and|the)\b\s/, '')
noun = DRC.get_noun(clean_item)
type = get_item_type(clean_item, noun)
sorted[type] ||= {}
sorted[type][clean_item] = {
noun: noun,
price: "#{price} #{currency}",
full_description: full_description
}
end
end
sorted
end

def get_item_type(item, noun)
item_data = get_data('sorting').to_h.merge(get_data('items').to_h)
category = 'Other'

item_data.each do |key, value|
if noun =~ /#{value.join('$|').concat('$')}/i || item =~ /(?:#{value.join('$|').concat('$')})/i
category = key.to_s.sub(/_nouns|_types/, '').capitalize
break
end
end

category
end

def display_table(sorted_items, container_name)
table = Terminal::Table.new do |t|
t.title = "Contents of #{container_name.strip}"
t.style = { border_x: "-", border_i: "+", border_y: "|" }

if sorted_items.empty?
t << [{ value: "This container is empty", alignment: :center, colspan: 2 }]
else
t.headings = ['Item', 'Qty.']
sorted_types = sorted_items.keys.sort_by { |type| type == "Other" ? [1, type] : [0, type] }

sorted_types.each do |type|
items = sorted_items[type]
t << :separator
t << [{ value: type.strip, alignment: :center, colspan: 2 }]
t << :separator
items.each do |data|
t << [data[:full_description].strip, data[:qty]]
end
end
output.gsub!(/<.*?>/, '') unless $frontend =~ /^(?:stormfront|profanity)$/
if defined?(_respond)
_respond output
else
$stdout.puts output
end
end

table_string = table.to_s.gsub('[[MONSTERBOLD]]', monsterbold_start).gsub('[[/MONSTERBOLD]]', monsterbold_end)
Lich::Messaging.mono(table_string)
end

def display_shop_table(sorted_items, container_name)
table = Terminal::Table.new do |t|
t.title = "Items for sale on #{container_name}"
t.headings = ['Item', 'Type', 'Price']
sorted_items.each do |type, items|
t << :separator
t << [{ value: type, alignment: :center, colspan: 3 }]
t << :separator
items.each do |data|
t << [data[:full_description], data[:noun], data[:price]]
end
else
echo 'fixme'
end
end

Lich::Messaging.mono(table.to_s)
end

def display_default(sorted_items, container_name)
output = "#{container_name}:\n"
sorted_items.sort.each do |category_name, category_contents|
count = category_contents.values.sum { |item| item[:qty] }
output << "#{monsterbold_start}#{category_name} (#{count}):#{monsterbold_end} "
category_contents.each do |_item, data|
output << if data[:qty] > 1
"#{data[:full_description]} (#{data[:qty]}), "
else
"#{data[:full_description]}, "
end
end
output.chomp!(', ')
output << ".\n"
end
Lich::Messaging.mono(output)
end

def cleanup_hook
DownstreamHook.remove('sorter')
end
end

use_table = script.vars.include?('table')
sorter_instance = Sorter.new(use_table)

begin
# Keep the script running in the background to monitor incoming lines.
while true; sleep(0.1); end
ensure
DownstreamHook.remove('sorter')
sorter_instance.cleanup_hook # Clean up when the script exits.
end
Loading