This commit is contained in:
phidias authored and GitHub committed 2026-11-03 14:09:56 +09:00
1 parent 48f86959d9
commit c64a6201d9
5 files changed
+384 -4

No files matched your search

+1
View File
@@ -79,6 +79,7 @@ add(require("modules/exp_scenario/gui/player_bonus"))
add(require("modules/exp_scenario/gui/player_list"))
add(require("modules/exp_scenario/gui/player_stats"))
add(require("modules/exp_scenario/gui/production_stats"))
add(require("modules/exp_scenario/gui/calculator"))
add(require("modules/exp_scenario/gui/quick_actions"))
add(require("modules/exp_scenario/gui/readme"))
add(require("modules/exp_scenario/gui/research_milestones"))
+327
View File
@@ -0,0 +1,327 @@
--[[-- Gui - Calculator
Add a calculator to do quick calculation.
]]
local Gui = require("modules/exp_gui")
local Roles = require("modules/exp_roles")
--- @class ExpGui_Calculator.elements
local Elements = {}
local calc_function = setmetatable({
ln = math.log,
log = function(x, b) return math.log(x, b or 10) end,
round = function(x) return x >= 0 and math.floor(x + 0.5) or math.ceil(x - 0.5) end,
trunc = function(x) return (math.modf(x)) end,
sign = function(x) return x > 0 and 1 or (x < 0 and -1 or 0) end,
pi = function(_) return math.pi end,
}, {
__index = math
})
local calc_function_operation = {
["+"] = {1, function(a, b) return a + b end},
["-"] = {1, function(a, b) return a - b end},
["*"] = {2, function(a, b) return a * b end},
["/"] = {2, function(a, b) if b == 0 then error({"", {"exp-gui_calculator.error"}, {"exp-gui_calculator.div_zero"}}, 0) end return a / b end},
["%"] = {2, function(a, b) if b == 0 then error({"", {"exp-gui_calculator.error"}, {"exp-gui_calculator.mod_zero"}}, 0) end return a % b end},
}
local calc_si_suffix = {
k = 1e3,
m = 1e6,
g = 1e9,
t = 1e12,
p = 1e15
}
local calc_number_base = {
x = {value = 16, digits = "%x"},
b = {value = 2, digits = "[01]"},
o = {value = 8, digits = "[0-7]"},
}
--- Input textfield
Elements.calculator_input_textfield = Gui.define("calculator/input_textfield")
:draw{
type = "textfield",
name = "calculator_input_textfield",
style = "console_input_textfield",
}
:style{
width = 320,
}
--- Output label
Elements.calculator_result_label = Gui.define("calculator/result_label")
:draw{
type = "label",
name = "calculator_result_label",
style = "heading_2_label",
}
:style{
horizontal_align = "right"
}
--- Empty new line label
Elements.calculator_label_1 = Gui.define("calculator/label_1")
:draw{
type = "label",
name = "calculator_label_1",
style = "heading_2_label",
}
--- Container added to the screen
Elements.container = Gui.define("exp-gui_calculatorulator/container")
:draw(function(def, parent)
local screen_frame = Gui.elements.screen_frame(parent, nil, true)
local button_flow = Gui.elements.screen_frame.get_button_flow(screen_frame)
Elements.calculator_input_textfield(screen_frame)
Elements.calculator_result_label(screen_frame)
Elements.calculator_label_1(screen_frame)
return Gui.elements.screen_frame.get_root_element(screen_frame)
end)
--- Add a button to create the container
Gui.toolbar.create_button{
name = "open_calculator",
sprite = "entity/arithmetic-combinator",
tooltip = { "exp-gui_calculator.tooltip" },
visible = function(player, element)
return Roles.player_has_permission(player, "exp-gui_calculator")
end
}:on_click(function(def, player, element, event)
Elements.container(player.gui.screen)
end)
--- Parse Value
function Elements.calculator.parse(expression)
local cursor = 1
local function skip_space()
while expression:sub(cursor, cursor) == " " do
cursor = cursor + 1
end
end
local function factorial(n)
if n < 0 then
return error({"", {"exp-gui_calculator.error"}, {"exp-gui_calculator.factorial_neg", n}}, 0)
elseif n > 170 then
return error({"", {"exp-gui_calculator.error"}, {"exp-gui_calculator.factorial_large", n}}, 0)
end
if n ~= math.floor(n) then
return error({"", {"exp-gui_calculator.error"}, {"exp-gui_calculator.factorial_int", n}}, 0)
end
local result = 1
for i = 2, n do
result = result * i
end
return result
end
local parse_expr
local function parse_atom()
skip_space()
local character = expression:sub(cursor, cursor)
if character == "|" then
cursor = cursor + 1
local value = parse_expr(1)
skip_space()
if expression:sub(cursor, cursor) ~= "|" then
return error({"", {"exp-gui_calculator.error"}, {"exp-gui_calculator.expected_bar"}}, 0)
end
cursor = cursor + 1
return math.abs(value)
elseif character == "(" then
cursor = cursor + 1
local value = parse_expr(1)
skip_space()
if expression:sub(cursor, cursor) ~= ")" then
return error({"", {"exp-gui_calculator.error"}, {"exp-gui_calculator.expected_paren"}}, 0)
end
cursor = cursor + 1
return value
elseif character == "" then
return error({"", {"exp-gui_calculator.error"}, {"exp-gui_calculator.unexpected_end"}}, 0)
elseif character:match("%a") then
skip_space()
local start = cursor
while expression:sub(cursor, cursor):match("%w") do
cursor = cursor + 1
end
local name = expression:sub(start, cursor - 1)
local fn = calc_function[name]
skip_space()
if expression:sub(cursor, cursor) == "(" then
if not fn then
return error({"", {"exp-gui_calculator.error"}, {"exp-gui_calculator.unknown_fn"}}, 0)
end
cursor = cursor + 1
local argument = parse_expr(1)
skip_space()
if expression:sub(cursor, cursor) ~= ")" then
return error({"", {"exp-gui_calculator.error"}, {"exp-gui_calculator.expected_paren"}}, 0)
end
cursor = cursor + 1
return fn(argument)
end
if fn then
return error({"", {"exp-gui_calculator.error"}, {"exp-gui_calculator.expected_open", name}}, 0)
end
return error({"", {"exp-gui_calculator.error"}, {"exp-gui_calculator.unknown_id", name}}, 0)
else
skip_space()
if expression:sub(cursor, cursor) == "0" then
local base = calc_number_base[expression:sub(cursor + 1, cursor + 1):lower()]
if base then
local literal_start = cursor
cursor = cursor + 2
local digit_start = cursor
while expression:sub(cursor, cursor):match(base.digits) do
cursor = cursor + 1
end
if cursor == digit_start or expression:sub(cursor, cursor):match("%w") then
while expression:sub(cursor, cursor):match("%w") do
cursor = cursor + 1
end
return error({"", {"exp-gui_calculator.error"}, {"exp-gui_calculator.invalid_num", expression:sub(literal_start, cursor - 1)}}, 0)
end
return tonumber(expression:sub(digit_start, cursor - 1), base.value)
end
end
local literal = expression:match("^(%d+%.?%d*[eE][%+%-]?%d+)", cursor) or expression:match("^(%d+%.?%d*)", cursor)
if not literal then
return error({"", {"exp-gui_calculator.error"}, {"exp-gui_calculator.expected_num"}}, 0)
end
cursor = cursor + literal:len()
local num = tonumber(literal)
if not num then
return error({"", {"exp-gui_calculator.error"}, {"exp-gui_calculator.invalid_num", literal}}, 0)
end
return num
end
end
local function parse_factor()
skip_space()
if expression:sub(cursor, cursor) == "-" then
cursor = cursor + 1
return -parse_factor()
elseif expression:sub(cursor, cursor) == "+" then
cursor = cursor + 1
return parse_factor()
end
local base = parse_atom()
skip_space()
local suffix = calc_si_suffix[expression:sub(cursor, cursor)]
if suffix then
cursor = cursor + 1
base = base * suffix
end
skip_space()
if expression:sub(cursor, cursor) == "!" then
cursor = cursor + 1
base = factorial(base)
end
skip_space()
if expression:sub(cursor, cursor + 1) == "**" then
cursor = cursor + 2
return base ^ parse_factor()
elseif expression:sub(cursor, cursor) == "^" then
cursor = cursor + 1
return base ^ parse_factor()
end
return base
end
parse_expr = function(min_prec)
local lhs = parse_factor()
while true do
skip_space()
local entry = calc_function_operation[expression:sub(cursor, cursor)]
if not entry or entry[1] < min_prec then
break
end
cursor = cursor + 1
lhs = entry[2](lhs, parse_expr(entry[1] + 1))
end
return lhs
end
local ok, result = pcall(function()
local value = parse_expr(1)
skip_space()
if cursor <= #expression then
error({"", {"exp-gui_calculator.error"}, {"exp-gui_calculator.trailing", expression:sub(cursor, expression:len())}}, 0)
end
return value
end)
return ok and result or (type(result) == "table" and result or {"exp-gui_calculator.error", tostring(result)})
end
--- Value change
function Elements.calculator.change(event)
Elements.calculator_result_label.caption = event.text == "" and "" or main.parse(event.text:lower())
end
local e = defines.events
return {
elements = Elements,
events = {
[e.on_gui_text_changed] = Elements.calculator.parse,
},
}
+18
View File
@@ -258,6 +258,24 @@ tooltip-amount=Amount of items to insert into the __1__ slots
invalid=Autofill set to maximum amount: __1__ __2__ for __3__
inserted=Inserted __1__ __2__ into __3__
[exp-gui_calculator]
tooltip=Calculator
error=[ERROR]
factorial_neg=Factorial of a negative number __1__
factorial_large=Factorial too large __1__
factorial_int=Factorial must be an integer __1__
expected_bar=Expected closing bar
expected_num=Expected number
expected_open=Expected opening parenthesis __1__
expected_paren=Expected closing parenthesis
unexpected_end=Unexpected end of input
unknown_fn=Unknown function __1__
unknown_id=Unknown identifier __1__
invalid_num=Invalid number __1__
div_zero=Division by zero
mod_zero=Modulo by zero
trailing=Unexpected trailing input __1__
[exp-gui_module-inserter]
tooltip-main=Module Inserter
caption-main=Modules
+19 -2
View File
@@ -260,6 +260,24 @@ tooltip-amount=自動填入 __1__ 的數量
invalid=自動填入最大值 __1__ __2__ 給 __3__
inserted=自動填入 __1__ __2__ 到 __3__
[exp-gui_calculator]
tooltip=計算機
error=[錯誤]
factorial_neg=負數的階乘 __1__
factorial_large=階乘太大 __1__
factorial_int=階乘必須是整數 __1__
expected_bar=缺少右側垂直線
expected_num=缺少數字
expected_open=缺少左括號 __1__
expected_paren=缺少右括號
unexpected_end=意外的輸入結尾
unknown_fn=未知函數 __1__
unknown_id=未知識別項 __1__
invalid_num=無效的數字 __1__
div_zero=除以零
mod_zero=對零取餘數
trailing=意外的尾隨輸入 __1__
[exp-gui_module-inserter]
tooltip-main=模組
caption-main=Modules
@@ -603,7 +621,6 @@ chat-jailed=__1__ 因被多次拆除受保護物體而被禁止行動。請等
[exp_report-jail]
chat-jailed=__1__ 因被多次舉報而被禁止行動。請等候管理員作出下一步處理。
[exp-gui_player-list]
main-tooltip = Player List
open-action-bar = Options
@@ -620,4 +637,4 @@ afk-time = __1__% of total map time\nLast moved __2__ ago
open-map = __1__ __2__\n__3__\nClick to open map
[exp_spectate]
follow-label = Following __1__.\nClick here or press escape to stop following.
follow-label = Following __1__.\nClick here or press escape to stop following.
+19 -2
View File
@@ -260,6 +260,24 @@ tooltip-amount=自動填入 __1__ 的數量
invalid=自動填入最大值 __1__ __2__ 給 __3__
inserted=自動填入 __1__ __2__ 到 __3__
[exp-gui_calculator]
tooltip=計算機
error=[錯誤]
factorial_neg=負數的階乘 __1__
factorial_large=階乘太大 __1__
factorial_int=階乘必須是整數 __1__
expected_bar=缺少右側垂直線
expected_num=缺少數字
expected_open=缺少左括號 __1__
expected_paren=缺少右括號
unexpected_end=意外的輸入結尾
unknown_fn=未知函數 __1__
unknown_id=未知識別項 __1__
invalid_num=無效的數字 __1__
div_zero=除以零
mod_zero=對零取餘數
trailing=意外的尾隨輸入 __1__
[exp-gui_module-inserter]
tooltip-main=模組
caption-main=Modules
@@ -603,7 +621,6 @@ chat-jailed=__1__ 因被多次拆除受保護物體而被禁止行動。請等
[exp_report-jail]
chat-jailed=__1__ 因被多次舉報而被禁止行動。請等候管理員作出下一步處理。
[exp-gui_player-list]
main-tooltip = Player List
open-action-bar = Options
@@ -620,4 +637,4 @@ afk-time = __1__% of total map time\nLast moved __2__ ago
open-map = __1__ __2__\n__3__\nClick to open map
[exp_spectate]
follow-label = Following __1__.\nClick here or press escape to stop following.
follow-label = Following __1__.\nClick here or press escape to stop following.