From c64a6201d98f775897a74a2eb389eb2c96202095 Mon Sep 17 00:00:00 2001 From: PHIDIAS Date: Tue, 3 Nov 2026 14:09:56 +0900 Subject: [PATCH] transfer --- exp_scenario/module/control.lua | 1 + exp_scenario/module/gui/calculator.lua | 327 +++++++++++++++++++++++++ exp_scenario/module/locale/en.cfg | 18 ++ exp_scenario/module/locale/zh-CN.cfg | 21 +- exp_scenario/module/locale/zh-TW.cfg | 21 +- 5 files changed, 384 insertions(+), 4 deletions(-) create mode 100644 exp_scenario/module/gui/calculator.lua diff --git a/exp_scenario/module/control.lua b/exp_scenario/module/control.lua index 6e6f8e83..8743bbac 100644 --- a/exp_scenario/module/control.lua +++ b/exp_scenario/module/control.lua @@ -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")) diff --git a/exp_scenario/module/gui/calculator.lua b/exp_scenario/module/gui/calculator.lua new file mode 100644 index 00000000..9f34645e --- /dev/null +++ b/exp_scenario/module/gui/calculator.lua @@ -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, + }, +} diff --git a/exp_scenario/module/locale/en.cfg b/exp_scenario/module/locale/en.cfg index 037646a8..2e55bca0 100644 --- a/exp_scenario/module/locale/en.cfg +++ b/exp_scenario/module/locale/en.cfg @@ -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 diff --git a/exp_scenario/module/locale/zh-CN.cfg b/exp_scenario/module/locale/zh-CN.cfg index 3fdd5f2e..9c630ad8 100644 --- a/exp_scenario/module/locale/zh-CN.cfg +++ b/exp_scenario/module/locale/zh-CN.cfg @@ -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. \ No newline at end of file +follow-label = Following __1__.\nClick here or press escape to stop following. diff --git a/exp_scenario/module/locale/zh-TW.cfg b/exp_scenario/module/locale/zh-TW.cfg index 3fdd5f2e..9c630ad8 100644 --- a/exp_scenario/module/locale/zh-TW.cfg +++ b/exp_scenario/module/locale/zh-TW.cfg @@ -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. \ No newline at end of file +follow-label = Following __1__.\nClick here or press escape to stop following.