merged gui

This commit is contained in:
monica
2023-01-07 12:04:08 -05:00
committed by Monica Moniot
parent 2e021267e4
commit 1821dc1ccc
18 changed files with 2308 additions and 1 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 raiguard
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+195
View File
@@ -0,0 +1,195 @@
local train_util = require("__flib__.train")
local constants = require("scripts.gui.constants")
local util = require("scripts.gui.util")
local actions = {}
local function toggle_fab(elem, sprite, state)
if state then
elem.style = "flib_selected_frame_action_button"
elem.sprite = sprite .. "_black"
else
elem.style = "frame_action_button"
elem.sprite = sprite .. "_white"
end
end
function actions.close(Gui)
Gui:close()
end
function actions.recenter(Gui)
Gui.refs.window.force_auto_center()
end
function actions.toggle_auto_refresh(Gui)
Gui.state.auto_refresh = not Gui.state.auto_refresh
toggle_fab(Gui.refs.titlebar.refresh_button, "ltnm_refresh", Gui.state.auto_refresh)
end
function actions.toggle_pinned(Gui)
Gui.state.pinned = not Gui.state.pinned
toggle_fab(Gui.refs.titlebar.pin_button, "ltnm_pin", Gui.state.pinned)
if Gui.state.pinned then
Gui.state.pinning = true
Gui.player.opened = nil
Gui.state.pinning = false
else
Gui.player.opened = Gui.refs.window
Gui.refs.window.force_auto_center()
end
end
function actions.update_text_search_query(Gui, _, e)
local query = e.text
-- Input sanitization
for pattern, replacement in pairs(constants.input_sanitizers) do
query = string.gsub(query, pattern, replacement)
end
Gui.state.search_query = query
if Gui.state.search_job then
on_tick_n.remove(Gui.state.search_job)
end
if #query == 0 then
Gui:schedule_update()
else
Gui.state.search_job = on_tick_n.add(
game.tick + 30,
{ gui = "main", action = "update", player_index = Gui.player.index }
)
end
end
function actions.update_network_id_query(Gui)
Gui.state.network_id = tonumber(Gui.refs.toolbar.network_id_field.text) or -1
Gui:schedule_update()
end
function actions.open_train_gui(Gui, msg)
local train_id = msg.train_id
local train_data = Gui.state.ltn_data.trains[train_id]
if not train_data or not train_data.train.valid then
util.error_flying_text(Gui.player, { "message.ltnm-error-train-is-invalid" })
return
end
train_util.open_gui(Gui.player.index, train_data.train)
end
function actions.open_station_gui(Gui, msg, e)
local station_id = msg.station_id
local station_data = Gui.state.ltn_data.stations[station_id]
if not station_data or not station_data.entity.valid then
util.error_flying_text(Gui.player, { "message.ltnm-error-station-is-invalid" })
return
end
--- @type LuaPlayer
local player = Gui.player
if e.shift then
player.zoom_to_world(station_data.entity.position, 1)
rendering.draw_circle({
color = constants.colors.red.tbl,
target = station_data.entity.position,
surface = station_data.entity.surface,
radius = 0.5,
filled = false,
width = 5,
time_to_live = 60 * 3,
players = { player },
})
if not Gui.state.pinned then
Gui:close()
end
elseif e.control and remote.interfaces["ltn-combinator"] then
if not remote.call("ltn-combinator", "open_ltn_combinator", e.player_index, station_data.lamp_control, true) then
util.error_flying_text(player, { "message.ltnm-error-ltn-combinator-not-found" })
end
else
player.opened = station_data.entity
end
end
function actions.toggle_sort(Gui, msg, e)
local tab = msg.tab
local column = msg.column
local sorts = Gui.state.sorts[tab]
local active_column = sorts._active
if active_column == column then
sorts[column] = e.element.state
else
sorts._active = column
e.element.state = sorts[column]
local widths = Gui.widths[tab]
local old_checkbox = Gui.refs[tab].toolbar[active_column .. "_checkbox"]
old_checkbox.style = "ltnm_sort_checkbox"
if widths[active_column .. "_checkbox_stretchy"] then
old_checkbox.style.horizontally_stretchable = true
else
old_checkbox.style.width = widths[active_column]
end
e.element.style = "ltnm_selected_sort_checkbox"
if widths[column .. "_checkbox_stretchy"] then
e.element.style.horizontally_stretchable = true
else
e.element.style.width = widths[column]
end
end
Gui:schedule_update()
end
function actions.update(Gui)
Gui:schedule_update()
end
function actions.change_tab(Gui, msg)
Gui.state.active_tab = msg.tab
Gui:schedule_update()
end
function actions.change_surface(Gui, _, e)
local selected_index = e.element.selected_index
local selected_surface_index = Gui.state.ltn_data.surfaces.selected_to_index[selected_index]
if selected_surface_index then
Gui.state.surface = selected_surface_index
Gui:schedule_update()
end
end
function actions.clear_history(Gui)
global.flags.deleted_history = true
Gui:schedule_update()
end
function actions.delete_alert(Gui, msg)
global.active_data.alerts_to_delete[msg.alert_id] = true
Gui:schedule_update()
end
function actions.delete_all_alerts(Gui)
global.flags.deleted_all_alerts = true
Gui:schedule_update()
end
function actions.focus_search(Gui)
if not Gui.pinned then
Gui.refs.toolbar.text_search_field.select_all()
Gui.refs.toolbar.text_search_field.focus()
end
end
return actions
+227
View File
@@ -0,0 +1,227 @@
local gui = require("__flib__.gui")
local misc = require("__flib__.misc")
local constants = require("constants")
local util = require("scripts.util")
local templates = require("templates")
local alerts_tab = {}
function alerts_tab.build(widths)
return {
tab = {
type = "tab",
caption = { "gui.ltnm-alerts" },
ref = { "alerts", "tab" },
actions = {
on_click = { gui = "main", action = "change_tab", tab = "alerts" },
},
},
content = {
type = "frame",
style = "ltnm_main_content_frame",
direction = "vertical",
ref = { "alerts", "content_frame" },
{
type = "frame",
style = "ltnm_table_toolbar_frame",
style_mods = { right_padding = 4 },
templates.sort_checkbox(widths, "alerts", "time", true, nil, true),
templates.sort_checkbox(widths, "alerts", "train_id", false),
templates.sort_checkbox(widths, "alerts", "route", false),
templates.sort_checkbox(widths, "alerts", "network_id", false),
templates.sort_checkbox(nil, "alerts", "type", false),
{
type = "sprite-button",
style = "tool_button_red",
sprite = "utility/trash",
tooltip = { "gui.ltnm-delete-all-alerts" },
ref = { "alerts", "delete_all_button" },
actions = {
on_click = { gui = "main", action = "delete_all_alerts" },
},
},
},
{ type = "scroll-pane", style = "ltnm_table_scroll_pane", ref = { "alerts", "scroll_pane" } },
{
type = "flow",
style = "ltnm_warning_flow",
visible = false,
ref = { "alerts", "warning_flow" },
{
type = "label",
style = "ltnm_semibold_label",
caption = { "gui.ltnm-no-alerts" },
ref = { "alerts", "warning_label" },
},
},
},
}
end
function alerts_tab.update(self)
local dictionaries = self.player_table.dictionaries
local state = self.state
local refs = self.refs.alerts
local widths = self.widths
local search_query = state.search_query
local search_network_id = state.network_id
local search_surface = state.surface
local ltn_alerts = state.ltn_data.alerts
local alerts_to_delete = global.active_data.alerts_to_delete
local scroll_pane = refs.scroll_pane
local children = scroll_pane.children
local sorts = state.sorts[state.active_tab]
local active_sort = sorts._active
local sorted_alerts = state.ltn_data.sorted_alerts[active_sort]
local table_index = 0
-- False = ascending (arrow down), True = descending (arrow up)
local start, finish, step
if sorts[active_sort] then
start = #sorted_alerts
finish = 1
step = -1
else
start = 1
finish = #sorted_alerts
step = 1
end
if not global.flags.deleted_all_alerts then
for sorted_index = start, finish, step do
local alert_id = sorted_alerts[sorted_index]
local alerts_entry = ltn_alerts[alert_id]
if
(search_surface == -1 or (alerts_entry.train.surface_index == search_surface))
and bit32.btest(alerts_entry.train.network_id, search_network_id)
and (#search_query == 0 or string.find(
alerts_entry.search_strings[self.player.index] or "",
string.lower(search_query)
))
and not alerts_to_delete[alert_id]
then
table_index = table_index + 1
local row = children[table_index]
local color = table_index % 2 == 0 and "dark" or "light"
if not row then
row = gui.add(scroll_pane, {
type = "frame",
style = "ltnm_table_row_frame_" .. color,
{ type = "label", style_mods = { width = widths.alerts.time } },
{
type = "label",
style = "ltnm_clickable_semibold_label",
style_mods = { width = widths.alerts.train_id, horizontal_align = "center" },
tooltip = { "gui.ltnm-open-train-gui" },
},
{
type = "flow",
style_mods = { vertical_spacing = 0 },
direction = "vertical",
{
type = "label",
style = "ltnm_clickable_semibold_label",
style_mods = { width = widths.alerts.route },
tooltip = constants.open_station_gui_tooltip,
},
{
type = "label",
style = "ltnm_clickable_semibold_label",
style_mods = { width = widths.alerts.route },
tooltip = constants.open_station_gui_tooltip,
},
},
{ type = "label", style_mods = { width = widths.alerts.network_id, horizontal_align = "center" } },
{ type = "label", style_mods = { width = widths.alerts.type } },
{
type = "frame",
name = "contents_frame",
style = "ltnm_small_slot_table_frame_" .. color,
style_mods = { width = widths.alerts.contents },
{ type = "table", name = "contents_table", style = "slot_table", column_count = 4 },
},
{
type = "sprite-button",
style = "tool_button_red",
sprite = "utility/trash",
tooltip = { "gui.ltnm-delete-alert" },
},
})
end
gui.update(row, {
{ elem_mods = { caption = misc.ticks_to_timestring(alerts_entry.time) } },
{
elem_mods = { caption = alerts_entry.train_id },
actions = {
on_click = { gui = "main", action = "open_train_gui", train_id = alerts_entry.train_id },
},
},
{
{
elem_mods = { caption = alerts_entry.train.from },
actions = {
on_click = { gui = "main", action = "open_station_gui", station_id = alerts_entry.train.from_id },
},
},
{
elem_mods = {
caption = "[color=" .. constants.colors.caption.str .. "]->[/color] " .. alerts_entry.train.to,
},
actions = {
on_click = { gui = "main", action = "open_station_gui", station_id = alerts_entry.train.to_id },
},
},
},
{ elem_mods = { caption = util.signed_int32(alerts_entry.train.network_id) } },
{
elem_mods = {
caption = { "gui.ltnm-alert-" .. string.gsub(alerts_entry.type, "_", "-") },
tooltip = { "gui.ltnm-alert-" .. string.gsub(alerts_entry.type, "_", "-") .. "-description" },
},
},
{},
{
actions = {
on_click = { gui = "main", action = "delete_alert", alert_id = alert_id },
},
},
})
util.slot_table_update(row.contents_frame.contents_table, {
{ color = "green", entries = alerts_entry.planned_shipment or {}, translations = dictionaries.materials },
{ color = "red", entries = alerts_entry.actual_shipment or {}, translations = dictionaries.materials },
{ color = "red", entries = alerts_entry.unscheduled_load or {}, translations = dictionaries.materials },
{ color = "red", entries = alerts_entry.remaining_load or {}, translations = dictionaries.materials },
})
end
end
end
for child_index = table_index + 1, #children do
children[child_index].destroy()
end
if table_index == 0 then
refs.warning_flow.visible = true
scroll_pane.visible = false
refs.content_frame.style = "ltnm_main_warning_frame"
refs.delete_all_button.enabled = false
else
refs.warning_flow.visible = false
scroll_pane.visible = true
refs.content_frame.style = "ltnm_main_content_frame"
refs.delete_all_button.enabled = true
end
end
return alerts_tab
+152
View File
@@ -0,0 +1,152 @@
local constants = {}
constants.colors = {
caption = {
str = "255, 230, 192",
tbl = { 255, 230, 192 },
},
green = {
str = "69, 255, 69",
tbl = { 69, 255, 69 },
},
info = {
str = "128, 206, 240",
tbl = { 128, 206, 240 },
},
red = {
str = "255, 69, 69",
tbl = { 255, 69, 69 },
},
station_circle = {
str = "255, 50, 50, 190",
tbl = { 255, 50, 50, 190 },
},
yellow = {
str = "255, 240, 69",
tbl = { 255, 240, 69 },
},
white = {
str = "255, 255, 255",
tbl = { 255, 255, 255 },
},
}
-- dictionary locale identifier -> dictionary of hardcoded GUI sizes
constants.gui = {
en = {
trains = {
train_id = 90,
status = 378,
composition = 200,
depot = 149,
shipment = 36 * 6,
shipment_columns = 6,
},
stations = {
name = 238,
status = 53,
network_id = 84,
provided_requested = 36 * 6,
provided_requested_columns = 6,
shipments = 36 * 5,
shipments_columns = 5,
control_signals = 36 * 7,
control_signals_columns = 7,
},
depots = {
name = 200,
network_id = 84,
status = 200,
trains = 200,
},
history = {
train_id = 60,
route = 357,
depot = 160,
network_id = 84,
runtime = 68,
finished = 68,
shipment = (36 * 6),
shipment_checkbox_stretchy = true,
},
alerts = {
time = 68,
train_id = 60,
route = 326,
network_id = 84,
type = 230,
type_checkbox_stretchy = true,
contents = 36 * 6,
},
},
}
constants.gui_content_frame_height = 744
constants.gui_inventory_table_height = 40 * 18
constants.gui_translations = {
delivering_to = { "gui.ltnm-delivering-to" },
fetching_from = { "gui.ltnm-fetching-from" },
loading_at = { "gui.ltnm-loading-at" },
not_available = { "gui.ltnm-not-available" },
parked_at_depot_with_residue = { "gui.ltnm-parked-at-depot-with-residue" },
parked_at_depot = { "gui.ltnm-parked-at-depot" },
returning_to_depot = { "gui.ltnm-returning-to-depot" },
unloading_at = { "gui.ltnm-unloading-at" },
}
constants.input_sanitizers = {
["%%"] = "%%%%",
["%("] = "%%(",
["%)"] = "%%)",
["%.^[%*]"] = "%%.",
["%+"] = "%%+",
["%-"] = "%%-",
["^[%.]%*"] = "%%*",
["%?"] = "%%?",
["%["] = "%%[",
["%]"] = "%%]",
["%^"] = "%%^",
["%$"] = "%%$",
}
constants.ltn_control_signals = {
["ltn-depot"] = true,
["ltn-depot-priority"] = true,
-- excluded because it's shown as a separate column
-- ["ltn-network-id"] = true,
["ltn-min-train-length"] = true,
["ltn-max-train-length"] = true,
["ltn-max-trains"] = true,
["ltn-provider-threshold"] = true,
["ltn-provider-stack-threshold"] = true,
["ltn-provider-priority"] = true,
["ltn-locked-slots"] = true,
["ltn-requester-threshold"] = true,
["ltn-requester-stack-threshold"] = true,
["ltn-requester-priority"] = true,
["ltn-disable-warnings"] = true,
}
constants.ltn_event_names = {
on_stops_updated = true,
on_dispatcher_updated = true,
-- on_delivery_pickup_complete = true,
on_delivery_completed = true,
on_delivery_failed = true,
-- on_dispatcher_no_train_found = true,
on_provider_missing_cargo = true,
on_provider_unscheduled_cargo = true,
on_requester_remaining_cargo = true,
on_requester_unscheduled_cargo = true,
}
if script then
constants.open_station_gui_tooltip = {
"",
{ "gui.ltnm-open-station-gui" },
remote.interfaces["ltn-combinator"] and { "", "\n", { "gui.ltnm-open-ltn-combinator-gui" } } or nil,
}
end
return constants
+144
View File
@@ -0,0 +1,144 @@
local gui = require("__flib__.gui")
local templates = require("templates")
local depots_tab = {}
function depots_tab.build(widths)
return {
tab = {
type = "tab",
caption = { "gui.ltnm-depots" },
ref = { "depots", "tab" },
actions = {
on_click = { gui = "main", action = "change_tab", tab = "depots" },
},
},
content = {
type = "frame",
style = "ltnm_main_content_frame",
direction = "vertical",
ref = { "depots", "content_frame" },
{
type = "frame",
style = "ltnm_table_toolbar_frame",
style_mods = { right_padding = 4 },
templates.sort_checkbox(widths, "depots", "name", true, nil, true),
templates.sort_checkbox(widths, "depots", "network_id", false),
templates.sort_checkbox(widths, "depots", "status", false, { "gui.ltnm-status-description" }),
templates.sort_checkbox(widths, "depots", "trains", false),
},
{ type = "scroll-pane", style = "ltnm_table_scroll_pane", ref = { "depots", "scroll_pane" } },
{
type = "flow",
style = "ltnm_warning_flow",
visible = false,
ref = { "depots", "warning_flow" },
{
type = "label",
style = "ltnm_semibold_label",
caption = { "gui.ltnm-no-depots" },
ref = { "depots", "warning_label" },
},
},
},
}
end
function depots_tab.update(self)
local state = self.state
local refs = self.refs.depots
local widths = self.widths.depots
local search_query = state.search_query
local search_network_id = state.network_id
local search_surface = state.surface
local ltn_depots = state.ltn_data.depots
local scroll_pane = refs.scroll_pane
local children = scroll_pane.children
local sorts = state.sorts.depots
local active_sort = sorts._active
local sorted_depots = state.ltn_data.sorted_depots[active_sort]
local table_index = 0
-- False = ascending (arrow down), True = descending (arrow up)
local start, finish, step
if sorts[active_sort] then
start = #sorted_depots
finish = 1
step = -1
else
start = 1
finish = #sorted_depots
step = 1
end
for sorted_index = start, finish, step do
local depot_name = sorted_depots[sorted_index]
local depot_data = ltn_depots[depot_name]
if
(search_surface == -1 or depot_data.surfaces[search_surface])
and bit32.btest(depot_data.network_id, search_network_id)
and (#search_query == 0 or string.find(depot_data.search_string, string.lower(search_query)))
then
table_index = table_index + 1
local row = children[table_index]
local color = table_index % 2 == 0 and "dark" or "light"
if not row then
row = gui.add(scroll_pane, {
type = "frame",
style = "ltnm_table_row_frame_" .. color,
{ type = "label", style_mods = { width = widths.name } },
{ type = "label", style_mods = { width = widths.network_id, horizontal_align = "center" } },
{ type = "flow", name = "statuses_flow", style_mods = { width = widths.status } },
{ type = "label", style_mods = { width = widths.trains } },
})
end
gui.update(row, {
{ elem_mods = { caption = depot_name } },
{ elem_mods = { caption = depot_data.network_id } },
{},
{ elem_mods = { caption = depot_data.trains_string } },
})
local statuses_flow = row.statuses_flow
local statuses_children = statuses_flow.children
local status_index = 0
for color, count in pairs(depot_data.statuses) do
status_index = status_index + 1
local status_flow = statuses_children[status_index]
if not status_flow then
status_flow = gui.add(statuses_flow, templates.status_indicator())
end
gui.update(status_flow, {
{ elem_mods = { sprite = "flib_indicator_" .. color } },
{ elem_mods = { caption = count } },
})
end
for child_index = status_index + 1, #statuses_children do
statuses_children[child_index].destroy()
end
end
end
for child_index = table_index + 1, #children do
children[child_index].destroy()
end
if table_index == 0 then
refs.warning_flow.visible = true
scroll_pane.visible = false
refs.content_frame.style = "ltnm_main_warning_frame"
else
refs.warning_flow.visible = false
scroll_pane.visible = true
refs.content_frame.style = "ltnm_main_content_frame"
end
end
return depots_tab
+208
View File
@@ -0,0 +1,208 @@
local gui = require("__flib__.gui")
local misc = require("__flib__.misc")
local constants = require("constants")
local util = require("scripts.util")
local templates = require("templates")
local history_tab = {}
function history_tab.build(widths)
return {
tab = {
type = "tab",
caption = { "gui.ltnm-history" },
ref = { "history", "tab" },
actions = {
on_click = { gui = "main", action = "change_tab", tab = "history" },
},
},
content = {
type = "frame",
style = "ltnm_main_content_frame",
direction = "vertical",
ref = { "history", "content_frame" },
{
type = "frame",
style = "ltnm_table_toolbar_frame",
style_mods = { right_padding = 4 },
templates.sort_checkbox(widths, "history", "train_id", false),
templates.sort_checkbox(widths, "history", "route", false),
templates.sort_checkbox(widths, "history", "depot", false),
templates.sort_checkbox(widths, "history", "network_id", false),
templates.sort_checkbox(widths, "history", "runtime", false),
templates.sort_checkbox(widths, "history", "finished", true, nil, true),
templates.sort_checkbox(nil, "history", "shipment", false),
{
type = "sprite-button",
style = "tool_button_red",
sprite = "utility/trash",
tooltip = { "gui.ltnm-clear-history" },
ref = { "history", "clear_button" },
actions = {
on_click = { gui = "main", action = "clear_history" },
},
},
},
{ type = "scroll-pane", style = "ltnm_table_scroll_pane", ref = { "history", "scroll_pane" } },
{
type = "flow",
style = "ltnm_warning_flow",
visible = false,
ref = { "history", "warning_flow" },
{
type = "label",
style = "ltnm_semibold_label",
caption = { "gui.ltnm-no-history" },
ref = { "history", "warning_label" },
},
},
},
}
end
function history_tab.update(self)
local dictionaries = self.player_table.dictionaries
local state = self.state
local refs = self.refs.history
local widths = self.widths
local search_query = state.search_query
local search_network_id = state.network_id
local search_surface = state.surface
local ltn_history = state.ltn_data.history
local scroll_pane = refs.scroll_pane
local children = scroll_pane.children
local sorts = state.sorts[state.active_tab]
local active_sort = sorts._active
local sorted_history = state.ltn_data.sorted_history[active_sort]
local table_index = 0
-- False = ascending (arrow down), True = descending (arrow up)
local start, finish, step
if sorts[active_sort] then
start = #sorted_history
finish = 1
step = -1
else
start = 1
finish = #sorted_history
step = 1
end
if not global.flags.deleted_history then
for sorted_index = start, finish, step do
local history_id = sorted_history[sorted_index]
local history_entry = ltn_history[history_id]
if
(search_surface == -1 or (history_entry.surface_index == search_surface))
and bit32.btest(history_entry.network_id, search_network_id)
and (
#search_query == 0 or string.find(history_entry.search_strings[self.player.index], string.lower(search_query))
)
then
table_index = table_index + 1
local row = children[table_index]
local color = table_index % 2 == 0 and "dark" or "light"
if not row then
row = gui.add(scroll_pane, {
type = "frame",
style = "ltnm_table_row_frame_" .. color,
{
type = "label",
style = "ltnm_clickable_semibold_label",
style_mods = { width = widths.history.train_id, horizontal_align = "center" },
tooltip = constants.open_station_gui_tooltip,
},
{
type = "flow",
style_mods = { vertical_spacing = 0 },
direction = "vertical",
{
type = "label",
style = "ltnm_clickable_semibold_label",
style_mods = { width = widths.history.route },
tooltip = constants.open_station_gui_tooltip,
},
{
type = "label",
style = "ltnm_clickable_semibold_label",
style_mods = { width = widths.history.route },
tooltip = constants.open_station_gui_tooltip,
},
},
{ type = "label", style_mods = { width = widths.history.depot } },
{ type = "label", style_mods = { width = widths.history.network_id, horizontal_align = "center" } },
{ type = "label", style_mods = { width = widths.history.finished, horizontal_align = "center" } },
{ type = "label", style_mods = { width = widths.history.runtime, horizontal_align = "center" } },
{
type = "frame",
name = "shipment_frame",
style = "ltnm_small_slot_table_frame_" .. color,
style_mods = { width = widths.history.shipment },
{ type = "table", name = "shipment_table", style = "slot_table", column_count = 4 },
},
})
end
gui.update(row, {
{
elem_mods = { caption = history_entry.train_id },
actions = {
on_click = { gui = "main", action = "open_train_gui", train_id = history_entry.train_id },
},
},
{
{
elem_mods = { caption = history_entry.from },
actions = {
on_click = { gui = "main", action = "open_station_gui", station_id = history_entry.from_id },
},
},
{
elem_mods = {
caption = "[color=" .. constants.colors.caption.str .. "]->[/color] " .. history_entry.to,
},
actions = {
on_click = { gui = "main", action = "open_station_gui", station_id = history_entry.to_id },
},
},
},
{ elem_mods = { caption = history_entry.depot } },
{ elem_mods = { caption = util.signed_int32(history_entry.network_id) } },
{ elem_mods = { caption = misc.ticks_to_timestring(history_entry.runtime) } },
{ elem_mods = { caption = misc.ticks_to_timestring(history_entry.finished) } },
})
util.slot_table_update(
row.shipment_frame.shipment_table,
{ { color = "default", entries = history_entry.shipment, translations = dictionaries.materials } }
)
end
end
end
for child_index = table_index + 1, #children do
children[child_index].destroy()
end
if table_index == 0 then
refs.warning_flow.visible = true
scroll_pane.visible = false
refs.content_frame.style = "ltnm_main_warning_frame"
refs.clear_button.enabled = false
else
refs.warning_flow.visible = false
scroll_pane.visible = true
refs.content_frame.style = "ltnm_main_content_frame"
refs.clear_button.enabled = true
end
end
return history_tab
+87
View File
@@ -0,0 +1,87 @@
local misc = require("__flib__.misc")
local templates = require("templates")
local inventory_tab = {}
function inventory_tab.build()
return {
tab = {
type = "tab",
caption = { "gui.ltnm-inventory" },
ref = { "inventory", "tab" },
actions = {
on_click = { gui = "main", action = "change_tab", tab = "inventory" },
},
},
content = {
type = "flow",
style_mods = { horizontal_spacing = 12 },
direction = "horizontal",
ref = { "inventory", "content_frame" },
templates.inventory_slot_table("provided", 12),
templates.inventory_slot_table("in_transit", 8),
templates.inventory_slot_table("requested", 6),
},
}
end
local function update_table(self, name, color)
local translations = self.player_table.dictionaries.materials
local state = self.state
local refs = self.refs.inventory
local search_query = state.search_query
local search_network_id = state.network_id
local search_surface = state.surface
local ltn_inventory = state.ltn_data.inventory[name][search_surface]
local i = 0
local table = refs[name].table
local children = table.children
for name, count_by_network_id in pairs(ltn_inventory or {}) do
if
bit32.btest(count_by_network_id.combined_id, search_network_id)
and string.find(string.lower(translations[name]), string.lower(search_query))
then
local running_count = 0
for network_id, count in pairs(count_by_network_id) do
if network_id ~= "combined_id" and bit32.btest(network_id, search_network_id) then
running_count = running_count + count
end
end
if running_count > 0 then
i = i + 1
local button = children[i]
if not button then
button = table.add({ type = "sprite-button", style = "flib_slot_button_" .. color, enabled = false })
end
button.sprite = string.gsub(name, ",", "/")
button.number = running_count
button.tooltip = "[img="
.. string.gsub(name, ",", "/")
.. "] [font=default-semibold]"
.. translations[name]
.. "[/font]\n"
.. misc.delineate_number(running_count)
end
end
end
for j = i + 1, #children do
children[j].destroy()
end
end
function inventory_tab.update(self)
update_table(self, "provided", "green")
update_table(self, "in_transit", "blue")
update_table(self, "requested", "red")
end
return inventory_tab
+270
View File
@@ -0,0 +1,270 @@
local gui = require("__flib__.gui-lite")
local constants = require("scripts.gui.constants")
--local actions = require("scripts.gui.actions")
local templates = require("scripts.gui.templates")
local trains_tab = require("scripts.gui.trains")
--local depots_tab = require("scripts.gui.depots")
--local stations_tab = require("scripts.gui.stations")
--local inventory_tab = require("scripts.gui.inventory")
--local history_tab = require("scripts.gui.history")
--local alerts_tab = require("scripts.gui.alerts")
function Index:dispatch(msg, e)
-- "Transform" the action based on criteria
if msg.transform == "handle_refresh_click" then
if e.shift then
msg.action = "toggle_auto_refresh"
else
self.state.ltn_data = global.data
self.do_update = true
end
elseif msg.transform == "handle_titlebar_click" then
if e.button == defines.mouse_button_type.middle then
msg.action = "recenter"
end
end
-- Dispatch the associated action
if msg.action then
local func = self.actions[msg.action]
if func then
func(self, msg, e)
else
log("Attempted to call action `" .. msg.action .. "` for which there is no handler yet.")
end
end
-- Update if necessary
if self.do_update then
self:update()
self.do_update = false
end
end
function Index:schedule_update()
self.do_update = true
end
local manager = {}
function manager.build(player, player_data)
local widths = constants.gui["en"]
---@type table<string, LuaGuiElement>
local refs = {}
local _, window = gui.add(player.gui.screen, {
{
name = "manager_window",
type = "frame",
direction = "vertical",
visible = false,
handler = manager_handle.close,
children = {
{
name = "manager_titlebar",
type = "flow",
style = "flib_titlebar_flow",
handler = manager_handle.titlebar_click,
children = {
{ type = "label", style = "frame_title", caption = { "mod-name.LtnManager" }, ignored_by_interaction = true },
{ type = "empty-widget", style = "flib_titlebar_drag_handle", ignored_by_interaction = true },
{
name = "manager_dispatcher_status_label",
type = "label",
style = "bold_label",
style_mods = { font_color = constants.colors.red.tbl, left_margin = -4, top_margin = 1 },
caption = { "gui.ltnm-dispatcher-disabled" },
tooltip = { "gui.ltnm-dispatcher-disabled-description" },
visible = not settings.global["cybersyn-enable-planner"].value,
},
templates.frame_action_button("manager_pin_button", "ltnm_pin", { "gui.ltnm-keep-open" }, manager_handle.pin),--on_gui_clicked
templates.frame_action_button("manager_refresh_button", "ltnm_refresh", { "gui.ltnm-refresh-tooltip" }, manager_handle.refresh_click),--on_gui_clicked
templates.frame_action_button(nil, "utility/close", { "gui.close-instruction" }, manager_handle.close),--on_gui_clicked
},
},
{
type = "frame",
style = "inside_deep_frame",
direction = "vertical",
children = {
{
type = "frame",
style = "ltnm_main_toolbar_frame",
children = {
{ type = "label", style = "subheader_caption_label", caption = { "gui.ltnm-search-label" } },
{
name = "manager_text_search_field",
type = "textfield",
clear_and_focus_on_right_click = true,
handler = manager_handle.update_text_search_query, --on_gui_text_changed
},
{ type = "empty-widget", style = "flib_horizontal_pusher" },
{ type = "label", style = "caption_label", caption = { "gui.ltnm-network-id-label" } },
{
name = "manager_network_id_field",
type = "textfield",
style_mods = { width = 120 },
numeric = true,
allow_negative = true,
clear_and_focus_on_right_click = true,
text = "-1",
handler = manager_handle.update_network_id_query, --on_gui_text_changed
},
{ type = "label", style = "caption_label", caption = { "gui.ltnm-surface-label" } },
{
name = "manager_surface_dropdown",
type = "drop-down",
handler = manager_handle.change_surface, --on_gui_selection_state_changed
},
},
},
{
name = "manager_tabbed_pane",
type = "tabbed-pane",
style = "ltnm_tabbed_pane",
children = {
trains_tab.build(widths, refs),
},
},
},
},
},
},
}, refs)
refs.manager_titlebar.drag_target = window
window.force_auto_center()
end
--- @param player LuaPlayer
--- @param refs table<string, LuaGuiElement>
function manager.destroy(player, refs)
refs.manager_window.destroy()
player.set_shortcut_toggled("ltnm-toggle-gui", false)
player.set_shortcut_available("ltnm-toggle-gui", false)
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.open(player, player_data, refs)
refs.manager_window.bring_to_front()
refs.manager_window.visible = true
player_data.visible = true
if not player_data.pinning then
player.opened = refs.manager_window
end
player.set_shortcut_toggled("ltnm-toggle-gui", true)
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.close(player, player_data, refs)
if player_data.pinning then
return
end
refs.manager_window.visible = false
player_data.visible = false
if player.opened == refs.manager_window then
player.opened = nil
end
player.set_shortcut_toggled("ltnm-toggle-gui", false)
end
manager.handle = {}
--- @param e GuiEventData
function manager.wrapper(e, handler)
local player = game.get_player(e.player_index)
if not player then return end
local player_data = global.manager.players[e.player_index]
handler(player, player_data, refs)
end
local function toggle_fab(elem, sprite, state)
if state then
elem.style = "flib_selected_frame_action_button"
elem.sprite = sprite .. "_black"
else
elem.style = "frame_action_button"
elem.sprite = sprite .. "_white"
end
end
manager.handle.close = manager.close
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.handle.recenter(player, player_data, refs)
refs.window.force_auto_center()
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.handle.toggle_auto_refresh(player, player_data, refs)
player_data.auto_refresh = not player_data.auto_refresh
toggle_fab(refs.manager_refresh_button, "ltnm_refresh", player_data.auto_refresh)
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.handle.toggle_pinned(player, player_data, refs)
player_data.pinned = not player_data.pinned
toggle_fab(refs.manager_pin_button, "ltnm_pin", player_data.pinned)
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.handle.update_text_search_query(player, player_data, refs)
local query = e.text
-- Input sanitization
for pattern, replacement in pairs(constants.input_sanitizers) do
query = string.gsub(query, pattern, replacement)
end
Gui.state.search_query = query
if Gui.state.search_job then
on_tick_n.remove(Gui.state.search_job)
end
if #query == 0 then
Gui:schedule_update()
else
Gui.state.search_job = on_tick_n.add(
game.tick + 30,
{ gui = "main", action = "update", player_index = Gui.player.index }
)
end
end
--- @param player LuaPlayer
--- @param player_data PlayerData
--- @param refs table<string, LuaGuiElement>
function manager.handle.update_network_id_query(player, player_data, refs)
Gui.state.network_id = tonumber(Gui.refs.toolbar.network_id_field.text) or -1
Gui:schedule_update()
end
return manager
+174
View File
@@ -0,0 +1,174 @@
local gui = require("__flib__.gui")
local constants = require("constants")
local util = require("scripts.util")
local templates = require("templates")
local stations_tab = {}
function stations_tab.build(widths)
return {
tab = {
type = "tab",
caption = { "gui.ltnm-stations" },
ref = { "stations", "tab" },
actions = {
on_click = { gui = "main", action = "change_tab", tab = "stations" },
},
},
content = {
type = "frame",
style = "ltnm_main_content_frame",
direction = "vertical",
ref = { "stations", "content_frame" },
{
type = "frame",
style = "ltnm_table_toolbar_frame",
templates.sort_checkbox(widths, "stations", "name", true),
templates.sort_checkbox(widths, "stations", "status", false, { "gui.ltnm-status-description" }),
templates.sort_checkbox(widths, "stations", "network_id", false),
templates.sort_checkbox(
widths,
"stations",
"provided_requested",
false,
{ "gui.ltnm-provided-requested-description" }
),
templates.sort_checkbox(widths, "stations", "shipments", false, { "gui.ltnm-shipments-description" }),
templates.sort_checkbox(widths, "stations", "control_signals", false),
},
{ type = "scroll-pane", style = "ltnm_table_scroll_pane", ref = { "stations", "scroll_pane" } },
{
type = "flow",
style = "ltnm_warning_flow",
visible = false,
ref = { "stations", "warning_flow" },
{
type = "label",
style = "ltnm_semibold_label",
caption = { "gui.ltnm-no-stations" },
ref = { "stations", "warning_label" },
},
},
},
}
end
function stations_tab.update(self)
local dictionaries = self.player_table.dictionaries
local state = self.state
local refs = self.refs.stations
local widths = self.widths.stations
local search_query = state.search_query
local search_network_id = state.network_id
local search_surface = state.surface
local ltn_stations = state.ltn_data.stations
local scroll_pane = refs.scroll_pane
local children = scroll_pane.children
local sorts = state.sorts.stations
local active_sort = sorts._active
local sorted_stations = state.ltn_data.sorted_stations[active_sort]
local table_index = 0
-- False = ascending (arrow down), True = descending (arrow up)
local start, finish, step
if sorts[active_sort] then
start = #sorted_stations
finish = 1
step = -1
else
start = 1
finish = #sorted_stations
step = 1
end
for sorted_index = start, finish, step do
local station_id = sorted_stations[sorted_index]
local station_data = ltn_stations[station_id]
if station_data.entity.valid then
if
(search_surface == -1 or station_data.entity.surface.index == search_surface)
and bit32.btest(station_data.network_id, search_network_id)
and (
#search_query == 0 or string.find(station_data.search_strings[self.player.index], string.lower(search_query))
)
then
table_index = table_index + 1
local row = children[table_index]
local color = table_index % 2 == 0 and "dark" or "light"
if not row then
row = gui.add(scroll_pane, {
type = "frame",
style = "ltnm_table_row_frame_" .. color,
{
type = "label",
style = "ltnm_clickable_semibold_label",
style_mods = { width = widths.name },
tooltip = constants.open_station_gui_tooltip,
},
templates.status_indicator(widths.status, true),
{ type = "label", style_mods = { width = widths.network_id, horizontal_align = "center" } },
templates.small_slot_table(widths, color, "provided_requested"),
templates.small_slot_table(widths, color, "shipments"),
templates.small_slot_table(widths, color, "control_signals"),
})
end
gui.update(row, {
{
elem_mods = { caption = station_data.name },
actions = {
on_click = { gui = "main", action = "open_station_gui", station_id = station_id },
},
},
{
{ elem_mods = { sprite = "flib_indicator_" .. station_data.status.color } },
{ elem_mods = { caption = station_data.status.count } },
},
{ elem_mods = { caption = station_data.network_id } },
})
util.slot_table_update(row.provided_requested_frame.provided_requested_table, {
{ color = "green", entries = station_data.provided, translations = dictionaries.materials },
{ color = "red", entries = station_data.requested, translations = dictionaries.materials },
})
util.slot_table_update(row.shipments_frame.shipments_table, {
{ color = "green", entries = station_data.inbound, translations = dictionaries.materials },
{ color = "blue", entries = station_data.outbound, translations = dictionaries.materials },
})
util.slot_table_update(row.control_signals_frame.control_signals_table, {
{
color = "default",
entries = station_data.control_signals,
translations = dictionaries.virtual_signals,
type = "virtual-signal",
},
})
end
end
end
for child_index = table_index + 1, #children do
children[child_index].destroy()
end
if table_index == 0 then
refs.warning_flow.visible = true
scroll_pane.visible = false
refs.content_frame.style = "ltnm_main_warning_frame"
else
refs.warning_flow.visible = false
scroll_pane.visible = true
refs.content_frame.style = "ltnm_main_content_frame"
end
end
return stations_tab
+100
View File
@@ -0,0 +1,100 @@
local constants = require("constants")
local templates = {}
--- Creates a frame action button, automatically accounting for inverted sprites.
--- @param name string?
--- @param sprite string?
--- @param tooltip LocalisedString?
--- @param handler GuiElemHandler?
--- @param tags Tags?
function templates.frame_action_button(name, sprite, tooltip, handler, tags)
return {
type = "sprite-button",
name = name,
style = "frame_action_button",
sprite = sprite .. "_white",
hovered_sprite = sprite .. "_black",
clicked_sprite = sprite .. "_black",
mouse_button_filter = { "left" },
tooltip = tooltip,
handler = handler,
tags = tags,
}
end
--- Creates a full-sized scrollable slot table for the inventory tab.
--- @param name string
--- @param columns uint
function templates.inventory_slot_table(name, columns)
return {
type = "flow",
direction = "vertical",
{ type = "label", style = "bold_label", caption = { "gui.ltnm-" .. string.gsub(name, "_", "-") } },
{
type = "frame",
style = "deep_frame_in_shallow_frame",
style_mods = { height = constants.gui_inventory_table_height },
ref = { "inventory", name, "frame" },
{
type = "scroll-pane",
style = "ltnm_slot_table_scroll_pane",
style_mods = { width = 40 * columns + 12, minimal_height = constants.gui_inventory_table_height },
vertical_scroll_policy = "auto-and-reserve-space",
-- vertical_scroll_policy = "always",
ref = { "inventory", name, "scroll_pane" },
{ type = "table", style = "slot_table", column_count = columns, ref = { "inventory", name, "table" } },
},
},
}
end
--- Creates a small non-scrollable slot table.
--- @param widths table
--- @param color string
--- @param name string
function templates.small_slot_table(widths, color, name)
return {
type = "frame",
name = name .. "_frame",
style = "ltnm_small_slot_table_frame_" .. color,
style_mods = { width = widths[name] },
{ type = "table", name = name .. "_table", style = "slot_table", column_count = widths[name .. "_columns"] },
}
end
--- Creates a column header with a sort toggle.
--- @param widths table
--- @param tab string
--- @param column string
--- @param selected boolean
--- @param tooltip LocalisedString
function templates.sort_checkbox(widths, tab, column, selected, tooltip, state)
if state == nil then
state = false
end
return {
type = "checkbox",
style = selected and "ltnm_selected_sort_checkbox" or "ltnm_sort_checkbox",
style_mods = { width = widths and widths[tab][column] or nil, horizontally_stretchable = not widths },
caption = { "gui.ltnm-" .. string.gsub(column, "_", "-") },
tooltip = tooltip,
state = state,
ref = { tab, "toolbar", column .. "_checkbox" },
actions = {
on_checked_state_changed = { gui = "main", tab = tab, action = "toggle_sort", column = column },
},
}
end
function templates.status_indicator(width, center)
return {
type = "flow",
style = "flib_indicator_flow",
style_mods = { horizontal_align = center and "center" or nil, width = width },
{ type = "sprite", style = "flib_indicator" },
{ type = "label" },
}
end
return templates
+151
View File
@@ -0,0 +1,151 @@
local gui = require("__flib__.gui-lite")
local constants = require("constants")
local util = require("scripts.gui.util")
local templates = require("templates")
local trains_tab = {}
function trains_tab.build(map_data, player_id, player_data)
local widths = constants.gui["en"]
local search_query = player_data.search_query
local search_network_flag = player_data.network_flag
local search_network = player_data.network
local trains_sorted = player_data.trains_sorted
---@type GuiElemDef
local train_list = {}
--if not sorted_trains then
-- sorted_trains = {}
-- ids = {}
-- for id, train in pairs(map_data) do
-- local i = #ids + 1
-- ids[i] = id
-- sorted_trains[i] = train
-- end
-- dual_sort(ids, sorted_trains)
--end
if #trains_sorted == 0 then
train_list[1] = {
type = "label",
style = "ltnm_semibold_label",
caption = { "gui.ltnm-no-trains" },
ref = { "trains", "warning_label" },
}
else
local start, finish, step
if player_data.trains_ascending then
start = #trains_sorted
finish = 1
step = -1
else
start = 1
finish = #trains_sorted
step = 1
end
local gui_idx = 1
for idx = start, finish, step do
local train_id = trains_sorted[idx]
local train = map_data.trains[train_id]
if
true
then
local color = gui_idx % 2 == 0 and "dark" or "light"
train_list[gui_idx] = {
type = "frame",
style = "ltnm_table_row_frame_" .. color,
children = {
{
type = "frame",
style = "ltnm_table_inset_frame_" .. color,
children = {
type = "minimap",
style = "ltnm_train_minimap",
{ type = "label", style = "ltnm_minimap_label" },
{
type = "button",
style = "ltnm_train_minimap_button",
tooltip = { "gui.ltnm-open-train-gui" },
elem_mods = { entity = get_any_train_entity(train.entity) },
actions = {
on_click = { gui = "main", action = "open_train_gui", train_id = train_id },
},
},
},
},
{
type = "label",
style_mods = { width = widths.trains.composition },
elem_mods = { caption = train.composition },
},
{
type = "label", style_mods = { width = widths.trains.depot },
elem_mods = { caption = train.depot },
},
{
type = "frame",
name = "shipment_frame",
style = "ltnm_small_slot_table_frame_" .. color,
style_mods = { width = widths.trains.shipment },
children = {
{
type = "table",
name = "shipment_table",
style = "slot_table",
column_count = widths.trains.shipment_columns,
children = util.slot_table_build(train.manifest, "default"),
},
},
},
},
}
gui_idx = gui_idx + 1
end
end
end
return {
tab = {
type = "tab",
caption = #trains_sorted == 0 and { "gui.ltnm-trains" } or { "gui.ltnm-trains", #train_list },
badge_text = misc.delineate_number(#ltn_data.sorted_trains.composition),
ref = { "trains", "tab" },
actions = {
on_click = { gui = "main", action = "change_tab", tab = "trains" },
},
},
content = {
type = "frame",
style = "ltnm_main_content_frame",
direction = "vertical",
ref = { "trains", "content_frame" },
children = {
{
type = "frame",
style = "ltnm_table_toolbar_frame",
templates.sort_checkbox(widths, "trains", "train_id", true),
templates.sort_checkbox(widths, "trains", "status", false),
templates.sort_checkbox(widths, "trains", "composition", false, { "gui.ltnm-composition-description" }),
templates.sort_checkbox(widths, "trains", "depot", false),
templates.sort_checkbox(widths, "trains", "shipment", false),
},
{ type = "scroll-pane", style = "ltnm_table_scroll_pane", ref = { "trains", "scroll_pane" } },
{
type = "flow",
style = "ltnm_warning_flow",
visible = false,
ref = { "trains", "warning_flow" },
children = train_list,
},
},
},
}
end
return trains_tab
+90
View File
@@ -0,0 +1,90 @@
local gui = require("__flib__.gui-lite")
local format = require("__flib__.format")
local util = {}
--- Create a flying text at the player's cursor with an error sound.
--- @param player LuaPlayer
--- @param message LocalisedString
function util.error_flying_text(player, message)
player.create_local_flying_text({ create_at_cursor = true, text = message })
player.play_sound({ path = "utility/cannot_build" })
end
function util.gui_list(parent, iterator, test, build, update, ...)
local children = parent.children
local i = 0
for k, v in table.unpack(iterator) do
local passed = test(v, k, i, ...)
if passed then
i = i + 1
local child = children[i]
if not child then
gui.build(parent, { build(...) })
child = parent.children[i]
end
gui.update(child, update(v, k, i, ...))
end
end
for j = i + 1, #children do
children[j].destroy()
end
end
--- Updates a slot table based on the passed criteria.
--- @param manifest Manifest
--- @param color string
--- @return GuiElemDef[]
function util.slot_table_build(manifest, color)
local children = {}
local i = 1
for _, item in pairs(manifest) do
local name = item.name
local sprite
if item.type then
sprite = item.type .. "/" .. name
else
sprite = string.gsub(name, ",", "/")
end
if game.is_valid_sprite_path(sprite) then
children[i] = {
type = "sprite-button",
enabled = false,
style = "ltnm_small_slot_button_" .. color,
sprite = sprite,
tooltip = {
"",
"[img=" .. sprite .. "]",
{ "item-name." .. name },
"\n"..format.number(count),
},
}
i = i + 1
end
end
return children
end
function util.sorted_iterator(arr, src_tbl, sort_state)
local step = sort_state and 1 or -1
local i = sort_state and 1 or #arr
return function()
local j = i + step
if arr[j] then
i = j
local arr_value = arr[j]
return arr_value, src_tbl[arr_value]
end
end,
arr
end
local MAX_INT = 2147483648 -- math.pow(2, 31)
function util.signed_int32(val)
return (val >= MAX_INT and val - (2 * MAX_INT)) or val
end
return util