mirror of
https://github.com/Refactorio/RedMew.git
synced 2024-12-12 10:04:40 +02:00
26e1c28dc0
* Init Factorio 2.0 update * add credits * fix test module * I know luackeck, I know * Fixes * Fix bad event.player_index handling * Hotfixes * Remove all filter inserters * Migrate removed items * Deprecating spidertron control and landfill features
1492 lines
42 KiB
Lua
1492 lines
42 KiB
Lua
local Gui = require 'utils.gui'
|
|
local Global = require 'utils.global'
|
|
local Event = require 'utils.event'
|
|
local Rank = require 'features.rank_system'
|
|
local Game = require 'utils.game'
|
|
local math = require 'utils.math'
|
|
local table = require 'utils.table'
|
|
local Core = require 'utils.core'
|
|
local Server = require 'features.server'
|
|
local Command = require 'utils.command'
|
|
local Color = require 'resources.color_presets'
|
|
local Ranks = require 'resources.ranks'
|
|
local Settings = require 'utils.redmew_settings'
|
|
|
|
local insert = table.insert
|
|
|
|
local default_poll_duration = 300 * 60 -- in ticks
|
|
local duration_max = 3600 -- in seconds
|
|
local duration_step = 60 -- in seconds
|
|
local ticks_in_hour = 60 * 60 * 60
|
|
|
|
local duration_slider_max = duration_max / duration_step
|
|
local tick_duration_step = duration_step * 60
|
|
local inv_tick_duration_step = 1 / tick_duration_step
|
|
|
|
local normal_color = Color.white
|
|
local focus_color = Color.dark_orange
|
|
|
|
local notify_name = 'notify_poll'
|
|
Settings.register(notify_name, Settings.types.boolean, true, 'poll.notify_caption_short')
|
|
|
|
local polls = {}
|
|
local polls_counter = {0}
|
|
local active_polls = {}
|
|
local no_notify_players = {}
|
|
local player_poll_index = {}
|
|
local player_create_poll_data = {}
|
|
|
|
Global.register(
|
|
{
|
|
polls = polls,
|
|
polls_counter = polls_counter,
|
|
active_polls = active_polls,
|
|
no_notify_players = no_notify_players,
|
|
player_poll_index = player_poll_index,
|
|
player_create_poll_data = player_create_poll_data
|
|
},
|
|
function(tbl)
|
|
polls = tbl.polls
|
|
polls_counter = tbl.polls_counter
|
|
active_polls = tbl.active_polls
|
|
no_notify_players = tbl.no_notify_players
|
|
player_poll_index = tbl.player_poll_index
|
|
player_create_poll_data = tbl.player_create_poll_data
|
|
end
|
|
)
|
|
|
|
local main_button_name = Gui.uid_name()
|
|
local main_frame_name = Gui.uid_name()
|
|
local create_poll_button_name = Gui.uid_name()
|
|
local notify_checkbox_name = Gui.uid_name()
|
|
|
|
local poll_view_back_name = Gui.uid_name()
|
|
local poll_view_forward_name = Gui.uid_name()
|
|
local poll_view_vote_name = Gui.uid_name()
|
|
local poll_view_edit_name = Gui.uid_name()
|
|
|
|
local create_poll_frame_name = Gui.uid_name()
|
|
local create_poll_duration_name = Gui.uid_name()
|
|
local create_poll_label_name = Gui.uid_name()
|
|
local create_poll_question_name = Gui.uid_name()
|
|
local create_poll_answer_name = Gui.uid_name()
|
|
local create_poll_add_answer_name = Gui.uid_name()
|
|
local create_poll_delete_answer_name = Gui.uid_name()
|
|
local create_poll_close_name = Gui.uid_name()
|
|
local create_poll_clear_name = Gui.uid_name()
|
|
local create_poll_edit_name = Gui.uid_name()
|
|
local create_poll_confirm_name = Gui.uid_name()
|
|
local create_poll_delete_name = Gui.uid_name()
|
|
|
|
local function poll_id()
|
|
local count = polls_counter[1] + 1
|
|
polls_counter[1] = count
|
|
return count
|
|
end
|
|
|
|
local function apply_direction_button_style(button)
|
|
local button_style = button.style
|
|
button_style.width = 24
|
|
button_style.height = 24
|
|
button_style.top_padding = 0
|
|
button_style.bottom_padding = 0
|
|
button_style.left_padding = 0
|
|
button_style.right_padding = 0
|
|
button_style.font = 'default-listbox'
|
|
end
|
|
|
|
local function apply_button_style(button)
|
|
local button_style = button.style
|
|
button_style.font = 'default-semibold'
|
|
button_style.height = 26
|
|
button_style.minimal_width = 26
|
|
button_style.top_padding = 0
|
|
button_style.bottom_padding = 0
|
|
button_style.left_padding = 2
|
|
button_style.right_padding = 2
|
|
button_style.font_color = {0, 0, 0}
|
|
end
|
|
|
|
local function get_active_and_remaning_time(poll)
|
|
local end_tick = poll.end_tick
|
|
if end_tick == -1 then
|
|
return true, 'Endless Poll.'
|
|
end
|
|
|
|
local ticks = end_tick - game.tick
|
|
if ticks < 0 then
|
|
return false, 'Poll Finished.'
|
|
else
|
|
local include_seconds = ticks < ticks_in_hour
|
|
return true, 'End Time: ' .. Core.format_time(ticks, include_seconds)
|
|
end
|
|
end
|
|
|
|
local function do_remaining_time(poll, remaining_time_label)
|
|
local is_active, message = get_active_and_remaning_time(poll)
|
|
remaining_time_label.caption = message
|
|
return is_active
|
|
end
|
|
|
|
local function has_poll_finished(poll)
|
|
local end_tick = poll.end_tick
|
|
if end_tick == -1 then
|
|
return false
|
|
end
|
|
|
|
local ticks = end_tick - game.tick
|
|
return ticks < 0
|
|
end
|
|
|
|
local function send_poll_result_to_discord(poll)
|
|
local _, remaning_time_message = get_active_and_remaning_time(poll)
|
|
local result = {'Poll #', poll.id, ' ', remaning_time_message, '\\n'}
|
|
|
|
local created_by_player = poll.created_by
|
|
if created_by_player and created_by_player.valid then
|
|
insert(result, ' Created by ')
|
|
insert(result, created_by_player.name)
|
|
end
|
|
|
|
local edited_by_players = poll.edited_by
|
|
if next(edited_by_players) then
|
|
insert(result, ' Edited by ')
|
|
for pi, _ in pairs(edited_by_players) do
|
|
local p = game.get_player(pi)
|
|
if p and p.valid then
|
|
insert(result, p.name)
|
|
insert(result, ', ')
|
|
end
|
|
end
|
|
table.remove(result)
|
|
end
|
|
|
|
insert(result, '\\n**Question: ')
|
|
insert(result, poll.question)
|
|
insert(result, '**\\n')
|
|
|
|
local answers = poll.answers
|
|
local answers_count = #answers
|
|
for i, a in pairs(answers) do
|
|
insert(result, '[')
|
|
insert(result, a.voted_count)
|
|
insert(result, '] - ')
|
|
insert(result, a.text)
|
|
if i ~= answers_count then
|
|
insert(result, '\\n')
|
|
end
|
|
end
|
|
|
|
local message = table.concat(result)
|
|
Server.to_discord_embed(message)
|
|
end
|
|
|
|
local function redraw_poll_viewer_content(data)
|
|
local poll_viewer_content = data.poll_viewer_content
|
|
local remaining_time_label = data.remaining_time_label
|
|
local poll_index = data.poll_index
|
|
local player = poll_viewer_content.gui.player
|
|
|
|
data.vote_buttons = nil
|
|
Gui.remove_data_recursively(poll_viewer_content)
|
|
poll_viewer_content.clear()
|
|
|
|
local poll = polls[poll_index]
|
|
if not poll then
|
|
return
|
|
end
|
|
|
|
local answers = poll.answers
|
|
local voters = poll.voters
|
|
|
|
local tooltips = {}
|
|
for _, a in pairs(answers) do
|
|
tooltips[a] = {}
|
|
end
|
|
|
|
for player_index, answer in pairs(voters) do
|
|
local p = game.get_player(player_index)
|
|
insert(tooltips[answer], p.name)
|
|
end
|
|
|
|
for a, t in pairs(tooltips) do
|
|
if #t == 0 then
|
|
tooltips[a] = ''
|
|
else
|
|
tooltips[a] = table.concat(t, ', ')
|
|
end
|
|
end
|
|
|
|
local created_by_player = poll.created_by
|
|
local created_by_text
|
|
if created_by_player and created_by_player.valid then
|
|
created_by_text = ' Created by ' .. created_by_player.name
|
|
else
|
|
created_by_text = ''
|
|
end
|
|
|
|
local top_flow = poll_viewer_content.add {type = 'flow', direction = 'vertical'}
|
|
top_flow.add {type = 'label', caption = table.concat {'Poll #', poll.id, created_by_text}}
|
|
|
|
local edited_by_players = poll.edited_by
|
|
if next(edited_by_players) then
|
|
local edit_names = {'Edited by '}
|
|
for pi, _ in pairs(edited_by_players) do
|
|
local p = game.get_player(pi)
|
|
if p and p.valid then
|
|
insert(edit_names, p.name)
|
|
insert(edit_names, ', ')
|
|
end
|
|
end
|
|
|
|
table.remove(edit_names)
|
|
local edit_text = table.concat(edit_names)
|
|
|
|
local top_flow_label = top_flow.add {type = 'label', caption = edit_text, tooltip = edit_text}
|
|
top_flow_label.style.single_line = false
|
|
top_flow_label.style.horizontally_stretchable = false
|
|
end
|
|
|
|
local poll_enabled = do_remaining_time(poll, remaining_time_label)
|
|
|
|
local question_flow = poll_viewer_content.add {type = 'table', column_count = 2}
|
|
|
|
local edit_rank = poll.edit_rank or Ranks.regular
|
|
if Rank.equal_or_greater_than(player.name, edit_rank) then
|
|
local edit_button =
|
|
question_flow.add {
|
|
type = 'sprite-button',
|
|
name = poll_view_edit_name,
|
|
sprite = 'utility/rename_icon',
|
|
tooltip = 'Edit Poll.'
|
|
}
|
|
|
|
local edit_button_style = edit_button.style
|
|
edit_button_style.width = 26
|
|
edit_button_style.height = 26
|
|
end
|
|
|
|
local question_label = question_flow.add {type = 'label', caption = poll.question}
|
|
local question_label_style = question_label.style
|
|
question_label_style.font_color = focus_color
|
|
question_label_style.single_line = false
|
|
question_label_style.font = 'default-listbox'
|
|
|
|
local grid = poll_viewer_content.add {type = 'table', column_count = 2}
|
|
|
|
local answer = voters[player.index]
|
|
local vote_buttons = {}
|
|
for i, a in pairs(answers) do
|
|
local vote_button_flow = grid.add {type = 'flow'}
|
|
local vote_button =
|
|
vote_button_flow.add {
|
|
type = 'button',
|
|
name = poll_view_vote_name,
|
|
caption = a.voted_count,
|
|
enabled = poll_enabled
|
|
}
|
|
|
|
local tooltip = tooltips[a]
|
|
if tooltip ~= '' then
|
|
vote_button.tooltip = tooltip
|
|
end
|
|
|
|
local vote_button_style = vote_button.style
|
|
vote_button_style.height = 24
|
|
vote_button_style.width = 26
|
|
vote_button_style.font = 'default-small'
|
|
vote_button_style.top_padding = 0
|
|
vote_button_style.bottom_padding = 0
|
|
vote_button_style.left_padding = 0
|
|
vote_button_style.right_padding = 0
|
|
|
|
if answer == a then
|
|
vote_button_style.font_color = focus_color
|
|
vote_button_style.disabled_font_color = focus_color
|
|
else
|
|
vote_button_style.font_color = normal_color
|
|
vote_button_style.disabled_font_color = normal_color
|
|
end
|
|
|
|
Gui.set_data(vote_button, {answer = a, data = data})
|
|
vote_buttons[i] = vote_button
|
|
|
|
local label = grid.add {type = 'label', caption = a.text}
|
|
label.style.single_line = false
|
|
end
|
|
|
|
data.vote_buttons = vote_buttons
|
|
end
|
|
|
|
local function update_poll_viewer(data)
|
|
local back_button = data.back_button
|
|
local forward_button = data.forward_button
|
|
local poll_index_label = data.poll_index_label
|
|
local poll_index = data.poll_index
|
|
|
|
if #polls == 0 then
|
|
poll_index = 0
|
|
else
|
|
poll_index = math.clamp(poll_index, 1, #polls)
|
|
end
|
|
|
|
data.poll_index = poll_index
|
|
|
|
if poll_index == 0 then
|
|
poll_index_label.caption = 'No Polls'
|
|
else
|
|
poll_index_label.caption = table.concat {'Poll ', poll_index, ' / ', #polls}
|
|
end
|
|
|
|
back_button.enabled = poll_index > 1
|
|
forward_button.enabled = poll_index < #polls
|
|
|
|
redraw_poll_viewer_content(data)
|
|
end
|
|
|
|
local function draw_main_frame(left, player)
|
|
local frame = left.add {type = 'frame', name = main_frame_name, caption = 'Polls', direction = 'vertical'}
|
|
frame.style.maximal_width = 360
|
|
|
|
local poll_viewer_top_flow = frame.add {type = 'table', column_count = 5}
|
|
poll_viewer_top_flow.style.horizontal_spacing = 0
|
|
|
|
local back_button = poll_viewer_top_flow.add {type = 'button', name = poll_view_back_name, caption = '◀'}
|
|
apply_direction_button_style(back_button)
|
|
|
|
local forward_button = poll_viewer_top_flow.add {type = 'button', name = poll_view_forward_name, caption = '▶'}
|
|
apply_direction_button_style(forward_button)
|
|
|
|
local poll_index_label = poll_viewer_top_flow.add {type = 'label'}
|
|
poll_index_label.style.left_padding = 8
|
|
|
|
local spacer = poll_viewer_top_flow.add {type = 'flow'}
|
|
spacer.style.horizontally_stretchable = true
|
|
|
|
local remaining_time_label = poll_viewer_top_flow.add {type = 'label'}
|
|
|
|
local poll_viewer_content = frame.add {type = 'scroll-pane'}
|
|
poll_viewer_content.style.maximal_height = 250
|
|
poll_viewer_content.style.width = 340
|
|
|
|
local poll_index = player_poll_index[player.index] or #polls
|
|
|
|
local data = {
|
|
back_button = back_button,
|
|
forward_button = forward_button,
|
|
poll_index_label = poll_index_label,
|
|
poll_viewer_content = poll_viewer_content,
|
|
remaining_time_label = remaining_time_label,
|
|
poll_index = poll_index,
|
|
notify_checkbox = nil
|
|
}
|
|
|
|
Gui.set_data(frame, data)
|
|
Gui.set_data(back_button, data)
|
|
Gui.set_data(forward_button, data)
|
|
|
|
update_poll_viewer(data)
|
|
|
|
local state = Settings.get(player.index, notify_name)
|
|
local notify_checkbox =
|
|
frame.add {
|
|
type = 'checkbox',
|
|
name = notify_checkbox_name,
|
|
state = state,
|
|
caption = {'poll.notify_caption'},
|
|
tooltip = {'poll.notify_tooltip'}
|
|
}
|
|
data.notify_checkbox = notify_checkbox
|
|
|
|
local bottom_flow = frame.add {type = 'flow', direction = 'horizontal'}
|
|
|
|
local left_flow = bottom_flow.add {type = 'flow'}
|
|
left_flow.style.horizontal_align = 'left'
|
|
left_flow.style.horizontally_stretchable = true
|
|
|
|
local close_button =
|
|
left_flow.add {
|
|
type = 'button',
|
|
name = main_button_name,
|
|
caption = {'common.close_button'},
|
|
style = 'back_button'
|
|
}
|
|
apply_button_style(close_button)
|
|
|
|
local right_flow = bottom_flow.add {type = 'flow'}
|
|
right_flow.style.horizontal_align = 'right'
|
|
|
|
if Rank.equal_or_greater_than(player.name, Ranks.regular) then
|
|
local create_poll_button =
|
|
right_flow.add {type = 'button', name = create_poll_button_name, caption = 'Create Poll'}
|
|
apply_button_style(create_poll_button)
|
|
else
|
|
local create_poll_button =
|
|
right_flow.add {
|
|
type = 'button',
|
|
caption = 'Create Poll',
|
|
enabled = false,
|
|
tooltip = 'Sorry, you need to be a regular to create polls.'
|
|
}
|
|
apply_button_style(create_poll_button)
|
|
end
|
|
end
|
|
|
|
local function remove_create_poll_frame(create_poll_frame, player_index)
|
|
local data = Gui.get_data(create_poll_frame)
|
|
|
|
data.edit_mode = nil
|
|
player_create_poll_data[player_index] = data
|
|
|
|
Gui.remove_data_recursively(create_poll_frame)
|
|
create_poll_frame.destroy()
|
|
end
|
|
|
|
local function remove_main_frame(main_frame, left, player)
|
|
local player_index = player.index
|
|
local data = Gui.get_data(main_frame)
|
|
player_poll_index[player_index] = data.poll_index
|
|
|
|
Gui.remove_data_recursively(main_frame)
|
|
main_frame.destroy()
|
|
|
|
local create_poll_frame = left[create_poll_frame_name]
|
|
if create_poll_frame and create_poll_frame.valid then
|
|
remove_create_poll_frame(create_poll_frame, player_index)
|
|
end
|
|
end
|
|
|
|
local function toggle(event)
|
|
local player = event.player
|
|
local left = Gui.get_left_flow(player)
|
|
local main_frame = left[main_frame_name]
|
|
|
|
if main_frame then
|
|
remove_main_frame(main_frame, left, event.player)
|
|
local main_button = Gui.get_top_element(player, main_button_name)
|
|
main_button.toggled = false
|
|
else
|
|
draw_main_frame(left, event.player)
|
|
end
|
|
end
|
|
|
|
local function update_duration(slider)
|
|
local slider_data = Gui.get_data(slider)
|
|
local label = slider_data.duration_label
|
|
local value = slider.slider_value
|
|
|
|
value = math.floor(value)
|
|
|
|
slider_data.data.duration = value * tick_duration_step
|
|
|
|
if value == 0 then
|
|
label.caption = 'Endless Poll.'
|
|
else
|
|
local seconds = value * duration_step
|
|
local include_seconds = seconds < 3600
|
|
label.caption = Core.format_time(seconds * 60, include_seconds)
|
|
end
|
|
end
|
|
|
|
local function redraw_create_poll_content(data)
|
|
local grid = data.grid
|
|
local answers = data.answers
|
|
|
|
Gui.remove_data_recursively(grid)
|
|
grid.clear()
|
|
|
|
grid.add {type = 'flow'}
|
|
grid.add {
|
|
type = 'label',
|
|
caption = 'Duration:',
|
|
tooltip = 'Pro tip: Use mouse wheel for more fine control.'
|
|
}
|
|
|
|
local duration_flow = grid.add {type = 'flow', direction = 'horizontal'}
|
|
local duration_slider =
|
|
duration_flow.add {
|
|
type = 'slider',
|
|
name = create_poll_duration_name,
|
|
minimum_value = 0,
|
|
maximum_value = duration_slider_max,
|
|
value = math.floor(data.duration * inv_tick_duration_step)
|
|
}
|
|
duration_slider.style.width = 80
|
|
|
|
data.duration_slider = duration_slider
|
|
|
|
local duration_label = duration_flow.add {type = 'label'}
|
|
|
|
Gui.set_data(duration_slider, {duration_label = duration_label, data = data})
|
|
|
|
update_duration(duration_slider)
|
|
|
|
grid.add {type = 'flow'}
|
|
local question_label =
|
|
grid.add({type = 'flow'}).add {type = 'label', name = create_poll_label_name, caption = 'Question:'}
|
|
|
|
local question_textfield =
|
|
grid.add({type = 'flow'}).add {type = 'textfield', name = create_poll_question_name, text = data.question}
|
|
question_textfield.style.width = 215
|
|
|
|
Gui.set_data(question_label, question_textfield)
|
|
Gui.set_data(question_textfield, data)
|
|
|
|
local edit_mode = data.edit_mode
|
|
for count, answer in pairs(answers) do
|
|
local delete_flow = grid.add {type = 'flow'}
|
|
|
|
local delete_button
|
|
if edit_mode or count ~= 1 then
|
|
delete_button =
|
|
delete_flow.add {
|
|
type = 'sprite-button',
|
|
name = create_poll_delete_answer_name,
|
|
sprite = 'utility/trash',
|
|
tooltip = 'Delete answer field.'
|
|
}
|
|
delete_button.style.height = 26
|
|
delete_button.style.width = 26
|
|
else
|
|
delete_flow.style.height = 26
|
|
delete_flow.style.width = 26
|
|
end
|
|
|
|
local label_flow = grid.add {type = 'flow'}
|
|
local label =
|
|
label_flow.add {
|
|
type = 'label',
|
|
name = create_poll_label_name,
|
|
caption = table.concat {'Answer #', count, ':'}
|
|
}
|
|
|
|
local textfield_flow = grid.add {type = 'flow'}
|
|
|
|
local textfield = textfield_flow.add {type = 'textfield', name = create_poll_answer_name, text = answer.text}
|
|
textfield.style.width = 215
|
|
Gui.set_data(textfield, {answers = answers, count = count})
|
|
|
|
if delete_button then
|
|
Gui.set_data(delete_button, {data = data, count = count})
|
|
end
|
|
|
|
Gui.set_data(label, textfield)
|
|
end
|
|
end
|
|
|
|
local function draw_create_poll_frame(parent, player, previous_data)
|
|
previous_data = previous_data or player_create_poll_data[player.index]
|
|
|
|
local edit_mode
|
|
local question
|
|
local answers
|
|
local duration
|
|
local title_text
|
|
local confirm_text
|
|
local confirm_name
|
|
if previous_data then
|
|
edit_mode = previous_data.edit_mode
|
|
|
|
question = previous_data.question
|
|
|
|
answers = {}
|
|
for i, a in pairs(previous_data.answers) do
|
|
answers[i] = {text = a.text, source = a}
|
|
end
|
|
|
|
duration = previous_data.duration
|
|
else
|
|
question = ''
|
|
answers = {{text = ''}, {text = ''}, {text = ''}}
|
|
duration = default_poll_duration
|
|
end
|
|
|
|
if edit_mode then
|
|
title_text = 'Edit Poll #' .. previous_data.id
|
|
confirm_text = 'Edit Poll'
|
|
confirm_name = create_poll_edit_name
|
|
else
|
|
title_text = 'New Poll'
|
|
confirm_text = 'Create Poll'
|
|
confirm_name = create_poll_confirm_name
|
|
end
|
|
|
|
local frame =
|
|
parent.add {type = 'frame', name = create_poll_frame_name, caption = title_text, direction = 'vertical'}
|
|
frame.style.maximal_width = 360
|
|
|
|
local scroll_pane = frame.add {type = 'scroll-pane', vertical_scroll_policy = 'always'}
|
|
scroll_pane.style.maximal_height = 250
|
|
scroll_pane.style.width = 340
|
|
|
|
local grid = scroll_pane.add {type = 'table', column_count = 3}
|
|
|
|
local data = {
|
|
frame = frame,
|
|
grid = grid,
|
|
question = question,
|
|
answers = answers,
|
|
duration = duration,
|
|
previous_data = previous_data,
|
|
edit_mode = edit_mode
|
|
}
|
|
|
|
Gui.set_data(frame, data)
|
|
|
|
redraw_create_poll_content(data)
|
|
|
|
local add_answer_button =
|
|
scroll_pane.add {
|
|
type = 'button',
|
|
name = create_poll_add_answer_name,
|
|
caption = 'Add Answer'
|
|
}
|
|
apply_button_style(add_answer_button)
|
|
Gui.set_data(add_answer_button, data)
|
|
|
|
local bottom_flow = frame.add {type = 'flow', direction = 'horizontal'}
|
|
|
|
local left_flow = bottom_flow.add {type = 'flow'}
|
|
left_flow.style.horizontal_align = 'left'
|
|
left_flow.style.horizontally_stretchable = true
|
|
|
|
local close_button =
|
|
left_flow.add {
|
|
type = 'button',
|
|
name = create_poll_close_name,
|
|
caption = {'common.close_button'},
|
|
style = 'back_button'
|
|
}
|
|
apply_button_style(close_button)
|
|
Gui.set_data(close_button, frame)
|
|
|
|
local clear_button = left_flow.add {type = 'button', name = create_poll_clear_name, caption = 'Clear'}
|
|
apply_button_style(clear_button)
|
|
Gui.set_data(clear_button, data)
|
|
|
|
local right_flow = bottom_flow.add {type = 'flow'}
|
|
right_flow.style.horizontal_align = 'right'
|
|
|
|
if edit_mode then
|
|
local delete_button =
|
|
right_flow.add {type = 'button', name = create_poll_delete_name, caption = {'common.delete'}}
|
|
apply_button_style(delete_button)
|
|
Gui.set_data(delete_button, data)
|
|
end
|
|
|
|
local confirm_button = right_flow.add {type = 'button', name = confirm_name, caption = confirm_text}
|
|
apply_button_style(confirm_button)
|
|
Gui.set_data(confirm_button, data)
|
|
end
|
|
|
|
local function show_new_poll(poll_data)
|
|
local message =
|
|
table.concat {poll_data.created_by.name, ' has created a new Poll #', poll_data.id, ': ', poll_data.question}
|
|
|
|
for _, p in pairs(game.connected_players) do
|
|
local left = Gui.get_left_flow(p)
|
|
local frame = left[main_frame_name]
|
|
if no_notify_players[p.index] then
|
|
if frame and frame.valid then
|
|
local data = Gui.get_data(frame)
|
|
update_poll_viewer(data)
|
|
end
|
|
else
|
|
p.print(message)
|
|
|
|
if frame and frame.valid then
|
|
local data = Gui.get_data(frame)
|
|
data.poll_index = #polls
|
|
update_poll_viewer(data)
|
|
else
|
|
player_poll_index[p.index] = nil
|
|
draw_main_frame(left, p)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function create_poll(event)
|
|
local player = event.player
|
|
local data = Gui.get_data(event.element)
|
|
|
|
local frame = data.frame
|
|
local question = data.question
|
|
|
|
if not question:find('%S') then
|
|
event.player.print('Sorry, the poll needs a question.')
|
|
return
|
|
end
|
|
|
|
local answers = {}
|
|
for _, a in pairs(data.answers) do
|
|
local text = a.text
|
|
if text:find('%S') then
|
|
local index = #answers + 1
|
|
answers[index] = {text = text, index = index, voted_count = 0}
|
|
end
|
|
end
|
|
|
|
if #answers < 1 then
|
|
player.print('Sorry, the poll needs at least one answer.')
|
|
return
|
|
end
|
|
|
|
player_create_poll_data[player.index] = nil
|
|
|
|
local tick = game.tick
|
|
local duration = data.duration
|
|
local end_tick
|
|
|
|
if duration == 0 then
|
|
end_tick = -1
|
|
else
|
|
end_tick = tick + duration
|
|
end
|
|
|
|
local poll_data = {
|
|
id = poll_id(),
|
|
question = question,
|
|
answers = answers,
|
|
voters = {},
|
|
start_tick = tick,
|
|
end_tick = end_tick,
|
|
duration = duration,
|
|
created_by = event.player,
|
|
edited_by = {},
|
|
edit_rank = nil
|
|
}
|
|
|
|
insert(polls, poll_data)
|
|
insert(active_polls, poll_data)
|
|
|
|
show_new_poll(poll_data)
|
|
send_poll_result_to_discord(poll_data)
|
|
|
|
Gui.remove_data_recursively(frame)
|
|
frame.destroy()
|
|
end
|
|
|
|
local function update_vote(voters, answer, direction)
|
|
local count = answer.voted_count + direction
|
|
answer.voted_count = count
|
|
|
|
local tooltip = {}
|
|
for pi, a in pairs(voters) do
|
|
if a == answer then
|
|
local player = game.get_player(pi)
|
|
insert(tooltip, player.name)
|
|
end
|
|
end
|
|
|
|
return tostring(count), table.concat(tooltip, ', ')
|
|
end
|
|
|
|
local function vote(event)
|
|
local player_index = event.player_index
|
|
local voted_button = event.element
|
|
local button_data = Gui.get_data(voted_button)
|
|
local answer = button_data.answer
|
|
|
|
local poll_index = button_data.data.poll_index
|
|
local poll = polls[poll_index]
|
|
|
|
local voters = poll.voters
|
|
|
|
local previous_vote_answer = voters[player_index]
|
|
if previous_vote_answer == answer then
|
|
return
|
|
end
|
|
|
|
local vote_index = answer.index
|
|
|
|
voters[player_index] = answer
|
|
|
|
local previous_vote_button_count
|
|
local previous_vote_button_tooltip
|
|
local previous_vote_index
|
|
if previous_vote_answer then
|
|
previous_vote_button_count, previous_vote_button_tooltip = update_vote(voters, previous_vote_answer, -1)
|
|
previous_vote_index = previous_vote_answer.index
|
|
end
|
|
|
|
local vote_button_count, vote_button_tooltip = update_vote(voters, answer, 1)
|
|
|
|
for _, p in pairs(game.connected_players) do
|
|
local frame = Gui.get_left_element(p, main_frame_name)
|
|
if frame and frame.valid then
|
|
local data = Gui.get_data(frame)
|
|
|
|
if data.poll_index == poll_index then
|
|
local vote_buttons = data.vote_buttons
|
|
if previous_vote_answer then
|
|
local vote_button = vote_buttons[previous_vote_index]
|
|
vote_button.caption = previous_vote_button_count
|
|
vote_button.tooltip = previous_vote_button_tooltip
|
|
|
|
if p.index == player_index then
|
|
local vote_button_style = vote_button.style
|
|
vote_button_style.font_color = normal_color
|
|
vote_button_style.disabled_font_color = normal_color
|
|
end
|
|
end
|
|
|
|
local vote_button = vote_buttons[vote_index]
|
|
vote_button.caption = vote_button_count
|
|
vote_button.tooltip = vote_button_tooltip
|
|
|
|
if p.index == player_index then
|
|
local vote_button_style = vote_button.style
|
|
vote_button_style.font_color = focus_color
|
|
vote_button_style.disabled_font_color = focus_color
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function player_created(event)
|
|
local player = game.get_player(event.player_index)
|
|
if not player or not player.valid then
|
|
return
|
|
end
|
|
|
|
Gui.add_top_element(player,
|
|
{
|
|
type = 'sprite-button',
|
|
name = main_button_name,
|
|
sprite = 'item/programmable-speaker',
|
|
tooltip = {'poll.tooltip'},
|
|
auto_toggle = true,
|
|
}
|
|
)
|
|
end
|
|
|
|
local function player_joined(event)
|
|
local player = game.get_player(event.player_index)
|
|
if not player or not player.valid then
|
|
return
|
|
end
|
|
|
|
local frame = Gui.get_left_element(player, main_frame_name)
|
|
if frame and frame.valid then
|
|
local data = Gui.get_data(frame)
|
|
update_poll_viewer(data)
|
|
end
|
|
end
|
|
|
|
local function tick()
|
|
for _, p in pairs(game.connected_players) do
|
|
local frame = Gui.get_left_element(p, main_frame_name)
|
|
if frame and frame.valid then
|
|
local data = Gui.get_data(frame)
|
|
local poll = polls[data.poll_index]
|
|
if poll then
|
|
local poll_enabled = do_remaining_time(poll, data.remaining_time_label)
|
|
|
|
if not poll_enabled then
|
|
for _, v in pairs(data.vote_buttons) do
|
|
v.enabled = poll_enabled
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
for i = #active_polls, 1, -1 do
|
|
local poll = active_polls[i]
|
|
if has_poll_finished(poll) then
|
|
table.remove(active_polls, i)
|
|
send_poll_result_to_discord(poll)
|
|
|
|
local message = table.concat {'Poll finished: Poll #', poll.id, ': ', poll.question}
|
|
for _, p in pairs(game.connected_players) do
|
|
if not no_notify_players[p.index] then
|
|
p.print(message)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
Event.add(defines.events.on_player_created, player_created)
|
|
Event.add(defines.events.on_player_joined_game, player_joined)
|
|
Event.on_nth_tick(60, tick)
|
|
|
|
Gui.on_click(main_button_name, toggle)
|
|
|
|
Gui.on_click(
|
|
create_poll_button_name,
|
|
function(event)
|
|
local player = event.player
|
|
local left = Gui.get_left_flow(player)
|
|
local frame = left[create_poll_frame_name]
|
|
if frame and frame.valid then
|
|
remove_create_poll_frame(frame, player.index)
|
|
else
|
|
draw_create_poll_frame(left, player)
|
|
end
|
|
end
|
|
)
|
|
|
|
Gui.on_click(
|
|
poll_view_edit_name,
|
|
function(event)
|
|
local player = event.player
|
|
local left = Gui.get_left_flow(player)
|
|
local frame = left[create_poll_frame_name]
|
|
|
|
if frame and frame.valid then
|
|
Gui.remove_data_recursively(frame)
|
|
frame.destroy()
|
|
end
|
|
|
|
local main_frame = left[main_frame_name]
|
|
local frame_data = Gui.get_data(main_frame)
|
|
local poll = polls[frame_data.poll_index]
|
|
|
|
poll.edit_mode = true
|
|
draw_create_poll_frame(left, player, poll)
|
|
end
|
|
)
|
|
|
|
Gui.on_value_changed(
|
|
create_poll_duration_name,
|
|
function(event)
|
|
update_duration(event.element)
|
|
end
|
|
)
|
|
|
|
Gui.on_click(
|
|
create_poll_delete_answer_name,
|
|
function(event)
|
|
local button_data = Gui.get_data(event.element)
|
|
local data = button_data.data
|
|
|
|
table.remove(data.answers, button_data.count)
|
|
redraw_create_poll_content(data)
|
|
end
|
|
)
|
|
|
|
Gui.on_click(
|
|
create_poll_label_name,
|
|
function(event)
|
|
local textfield = Gui.get_data(event.element)
|
|
textfield.focus()
|
|
end
|
|
)
|
|
|
|
Gui.on_text_changed(
|
|
create_poll_question_name,
|
|
function(event)
|
|
local textfield = event.element
|
|
local data = Gui.get_data(textfield)
|
|
|
|
data.question = textfield.text
|
|
end
|
|
)
|
|
|
|
Gui.on_text_changed(
|
|
create_poll_answer_name,
|
|
function(event)
|
|
local textfield = event.element
|
|
local data = Gui.get_data(textfield)
|
|
|
|
data.answers[data.count].text = textfield.text
|
|
end
|
|
)
|
|
|
|
Gui.on_click(
|
|
create_poll_add_answer_name,
|
|
function(event)
|
|
local data = Gui.get_data(event.element)
|
|
|
|
insert(data.answers, {text = ''})
|
|
redraw_create_poll_content(data)
|
|
end
|
|
)
|
|
|
|
Gui.on_click(
|
|
create_poll_close_name,
|
|
function(event)
|
|
local frame = Gui.get_data(event.element)
|
|
remove_create_poll_frame(frame, event.player_index)
|
|
end
|
|
)
|
|
|
|
Gui.on_click(
|
|
create_poll_clear_name,
|
|
function(event)
|
|
local data = Gui.get_data(event.element)
|
|
|
|
local slider = data.duration_slider
|
|
slider.slider_value = math.floor(default_poll_duration * inv_tick_duration_step)
|
|
update_duration(slider)
|
|
|
|
data.question = ''
|
|
|
|
local answers = data.answers
|
|
for i = 1, #answers do
|
|
answers[i].text = ''
|
|
end
|
|
|
|
redraw_create_poll_content(data)
|
|
end
|
|
)
|
|
|
|
Gui.on_click(create_poll_confirm_name, create_poll)
|
|
|
|
Gui.on_click(
|
|
create_poll_delete_name,
|
|
function(event)
|
|
local player = event.player
|
|
local data = Gui.get_data(event.element)
|
|
local frame = data.frame
|
|
local poll = data.previous_data
|
|
|
|
Gui.remove_data_recursively(frame)
|
|
frame.destroy()
|
|
|
|
player_create_poll_data[player.index] = nil
|
|
|
|
local removed_index
|
|
for i, p in pairs(polls) do
|
|
if p == poll then
|
|
table.remove(polls, i)
|
|
table.remove_element(active_polls, p)
|
|
removed_index = i
|
|
break
|
|
end
|
|
end
|
|
|
|
if not removed_index then
|
|
return
|
|
end
|
|
|
|
local message = table.concat {player.name, ' has deleted Poll #', poll.id, ': ', poll.question}
|
|
|
|
for _, p in pairs(game.connected_players) do
|
|
if not no_notify_players[p.index] then
|
|
p.print(message)
|
|
end
|
|
|
|
local main_frame = Gui.get_left_element(player, main_frame_name)
|
|
if main_frame and main_frame.valid then
|
|
local main_frame_data = Gui.get_data(main_frame)
|
|
local poll_index = main_frame_data.poll_index
|
|
|
|
if removed_index < poll_index then
|
|
main_frame_data.poll_index = poll_index - 1
|
|
end
|
|
|
|
update_poll_viewer(main_frame_data)
|
|
end
|
|
end
|
|
end
|
|
)
|
|
|
|
Gui.on_click(
|
|
create_poll_edit_name,
|
|
function(event)
|
|
local player = event.player
|
|
local data = Gui.get_data(event.element)
|
|
local frame = data.frame
|
|
local poll = data.previous_data
|
|
|
|
local new_question = data.question
|
|
if not new_question:find('%S') then
|
|
player.print('Sorry, the poll needs a question.')
|
|
return
|
|
end
|
|
|
|
local new_answer_set = {}
|
|
local new_answers = {}
|
|
for _, a in pairs(data.answers) do
|
|
if a.text:find('%S') then
|
|
local source = a.source
|
|
local index = #new_answers + 1
|
|
if source then
|
|
new_answer_set[source] = a
|
|
source.text = a.text
|
|
source.index = index
|
|
new_answers[index] = source
|
|
else
|
|
new_answers[index] = {text = a.text, index = index, voted_count = 0}
|
|
end
|
|
end
|
|
end
|
|
|
|
if not next(new_answers) then
|
|
player.print('Sorry, the poll needs at least one answer.')
|
|
return
|
|
end
|
|
|
|
Gui.remove_data_recursively(frame)
|
|
frame.destroy()
|
|
|
|
local player_index = player.index
|
|
|
|
player_create_poll_data[player_index] = nil
|
|
|
|
local old_answers = poll.answers
|
|
local voters = poll.voters
|
|
for _, a in pairs(old_answers) do
|
|
if not new_answer_set[a] then
|
|
for pi, a2 in pairs(voters) do
|
|
if a == a2 then
|
|
voters[pi] = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
poll.question = new_question
|
|
poll.answers = new_answers
|
|
poll.edited_by[player_index] = true
|
|
|
|
local start_tick = game.tick
|
|
local duration = data.duration
|
|
local end_tick
|
|
|
|
if duration == 0 then
|
|
end_tick = -1
|
|
else
|
|
end_tick = start_tick + duration
|
|
end
|
|
|
|
poll.start_tick = start_tick
|
|
poll.end_tick = end_tick
|
|
poll.duration = duration
|
|
|
|
local poll_index
|
|
for i, p in pairs(polls) do
|
|
if poll == p then
|
|
poll_index = i
|
|
break
|
|
end
|
|
end
|
|
|
|
if not poll_index then -- In case we edit a poll that has been deleted.
|
|
insert(polls, poll)
|
|
insert(active_polls, poll)
|
|
poll_index = #polls
|
|
elseif not table.contains(active_polls, poll) then -- In case we edit a poll that has finished.
|
|
insert(active_polls, poll)
|
|
end
|
|
|
|
local message = table.concat {player.name, ' has edited Poll #', poll.id, ': ', poll.question}
|
|
|
|
for _, p in pairs(game.connected_players) do
|
|
local left = Gui.get_left_flow(p)
|
|
local main_frame = left[main_frame_name]
|
|
|
|
if no_notify_players[p.index] then
|
|
if main_frame and main_frame.valid then
|
|
local main_frame_data = Gui.get_data(main_frame)
|
|
update_poll_viewer(main_frame_data)
|
|
end
|
|
else
|
|
p.print(message)
|
|
if main_frame and main_frame.valid then
|
|
local main_frame_data = Gui.get_data(main_frame)
|
|
main_frame_data.poll_index = poll_index
|
|
update_poll_viewer(main_frame_data)
|
|
else
|
|
draw_main_frame(left, p)
|
|
end
|
|
end
|
|
end
|
|
|
|
send_poll_result_to_discord(poll)
|
|
end
|
|
)
|
|
|
|
Gui.on_checked_state_changed(
|
|
notify_checkbox_name,
|
|
function(event)
|
|
local player_index = event.player_index
|
|
local checkbox = event.element
|
|
local state = checkbox.state
|
|
|
|
local no_notify
|
|
if state then
|
|
no_notify = nil
|
|
else
|
|
no_notify = true
|
|
end
|
|
|
|
no_notify_players[player_index] = no_notify
|
|
Settings.set(player_index, notify_name, state)
|
|
end
|
|
)
|
|
|
|
local function do_direction(event, sign)
|
|
local count
|
|
if event.shift then
|
|
count = #polls
|
|
else
|
|
local button = event.button
|
|
if button == defines.mouse_button_type.right then
|
|
count = 5
|
|
else
|
|
count = 1
|
|
end
|
|
end
|
|
|
|
count = count * sign
|
|
|
|
local data = Gui.get_data(event.element)
|
|
data.poll_index = data.poll_index + count
|
|
update_poll_viewer(data)
|
|
end
|
|
|
|
Gui.on_click(
|
|
poll_view_back_name,
|
|
function(event)
|
|
do_direction(event, -1)
|
|
end
|
|
)
|
|
|
|
Gui.on_click(
|
|
poll_view_forward_name,
|
|
function(event)
|
|
do_direction(event, 1)
|
|
end
|
|
)
|
|
|
|
Gui.on_click(poll_view_vote_name, vote)
|
|
|
|
Gui.allow_player_to_toggle_top_element_visibility(main_button_name)
|
|
|
|
Event.add(
|
|
Settings.events.on_setting_set,
|
|
function(event)
|
|
if event.setting_name ~= notify_name then
|
|
return
|
|
end
|
|
|
|
local player_index = event.player_index
|
|
local player = game.get_player(player_index)
|
|
if not player or not player.valid then
|
|
return
|
|
end
|
|
|
|
local state = event.new_value
|
|
local no_notify
|
|
if state then
|
|
no_notify = nil
|
|
else
|
|
no_notify = true
|
|
end
|
|
|
|
no_notify_players[player_index] = no_notify
|
|
|
|
local frame = Gui.get_left_element(player, main_frame_name)
|
|
if not frame then
|
|
return
|
|
end
|
|
|
|
local data = Gui.get_data(frame)
|
|
local checkbox = data.notify_checkbox
|
|
|
|
checkbox.state = state
|
|
end
|
|
)
|
|
|
|
local Class = {}
|
|
|
|
function Class.validate(data)
|
|
if type(data) ~= 'table' then
|
|
return false, 'argument must be of type table'
|
|
end
|
|
|
|
local question = data.question
|
|
if type(question) ~= 'string' or question == '' then
|
|
return false, 'field question must be a non empty string.'
|
|
end
|
|
|
|
local answers = data.answers
|
|
if type(answers) ~= 'table' then
|
|
return false, 'answers field must be an array.'
|
|
end
|
|
|
|
if #answers == 0 then
|
|
return false, 'answer array must contain at least one entry.'
|
|
end
|
|
|
|
for _, a in pairs(answers) do
|
|
if type(a) ~= 'string' or a == '' then
|
|
return false, 'answers must be a non empty string.'
|
|
end
|
|
end
|
|
|
|
local duration = data.duration
|
|
local duration_type = type(duration)
|
|
if duration_type == 'number' then
|
|
if duration < 0 then
|
|
return false, 'duration cannot be negative, set duration to 0 for endless poll.'
|
|
end
|
|
elseif duration_type ~= 'nil' then
|
|
return false, 'duration must be of type number or nil'
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
function Class.poll(data)
|
|
local suc, error = Class.validate(data)
|
|
if not suc then
|
|
return false, error
|
|
end
|
|
|
|
local answers = {}
|
|
for index, a in pairs(data.answers) do
|
|
if a ~= '' then
|
|
insert(answers, {text = a, index = index, voted_count = 0})
|
|
end
|
|
end
|
|
|
|
local duration = data.duration
|
|
if duration then
|
|
duration = duration * 60
|
|
else
|
|
duration = default_poll_duration
|
|
end
|
|
|
|
local start_tick = game.tick
|
|
local end_tick
|
|
if duration == 0 then
|
|
end_tick = -1
|
|
else
|
|
end_tick = start_tick + duration
|
|
end
|
|
|
|
local id = poll_id()
|
|
|
|
local poll_data = {
|
|
id = id,
|
|
question = data.question,
|
|
answers = answers,
|
|
voters = {},
|
|
start_tick = start_tick,
|
|
end_tick = end_tick,
|
|
duration = duration,
|
|
created_by = game.player or {name = '<server>', valid = true},
|
|
edited_by = {},
|
|
edit_rank = data.edit_rank or nil
|
|
}
|
|
|
|
insert(polls, poll_data)
|
|
insert(active_polls, poll_data)
|
|
|
|
show_new_poll(poll_data)
|
|
send_poll_result_to_discord(poll_data)
|
|
|
|
return true, id
|
|
end
|
|
|
|
function Class.poll_result(id)
|
|
if type(id) ~= 'number' then
|
|
return 'poll-id must be a number'
|
|
end
|
|
|
|
for _, poll_data in pairs(polls) do
|
|
if poll_data.id == id then
|
|
local result = {'Question: ', poll_data.question, ' Answers: '}
|
|
local answers = poll_data.answers
|
|
local answers_count = #answers
|
|
for i, a in pairs(answers) do
|
|
insert(result, '( [')
|
|
insert(result, a.voted_count)
|
|
insert(result, '] - ')
|
|
insert(result, a.text)
|
|
insert(result, ' )')
|
|
if i ~= answers_count then
|
|
insert(result, ', ')
|
|
end
|
|
end
|
|
|
|
return table.concat(result)
|
|
end
|
|
end
|
|
|
|
return table.concat {'poll #', id, ' not found'}
|
|
end
|
|
|
|
function Class.get_poll_data(id)
|
|
for _, poll_data in pairs(polls) do
|
|
if poll_data.id == id then
|
|
return poll_data
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
local function poll_command(args)
|
|
local param = args.poll
|
|
param = 'return ' .. param
|
|
|
|
local func, error = loadstring(param)
|
|
if not func then
|
|
Game.player_print(error)
|
|
return
|
|
end
|
|
|
|
local suc, result = Class.poll(func())
|
|
if not suc then
|
|
Game.player_print(result)
|
|
else
|
|
Game.player_print(table.concat {'Poll #', result, ' successfully created.'})
|
|
end
|
|
end
|
|
|
|
local function poll_result_command(args)
|
|
local id = tonumber(args.poll)
|
|
local result = Class.poll_result(id)
|
|
Game.player_print(result)
|
|
end
|
|
|
|
function Class.send_poll_result_to_discord(id)
|
|
if type(id) ~= 'number' then
|
|
Server.to_discord_embed('poll-id must be a number')
|
|
return
|
|
end
|
|
|
|
for _, poll_data in pairs(polls) do
|
|
if poll_data.id == id then
|
|
send_poll_result_to_discord(poll_data)
|
|
return
|
|
end
|
|
end
|
|
|
|
local message = table.concat {'poll #', id, ' not found'}
|
|
Server.to_discord_embed(message)
|
|
end
|
|
|
|
Command.add(
|
|
'poll',
|
|
{
|
|
description = {'command_description.poll'},
|
|
arguments = {'poll'},
|
|
required_rank = Ranks.admin,
|
|
allowed_by_server = true,
|
|
custom_help_text = {'command_custom_help.poll'},
|
|
log_command = true,
|
|
capture_excess_arguments = true
|
|
},
|
|
poll_command
|
|
)
|
|
|
|
Command.add(
|
|
'poll-result',
|
|
{
|
|
description = {'command_description.poll_result'},
|
|
arguments = {'poll'},
|
|
allowed_by_server = true
|
|
},
|
|
poll_result_command
|
|
)
|
|
|
|
return Class
|