339 lines
15 KiB
Lua
339 lines
15 KiB
Lua
|
require('mod-gui')
|
||
|
local Entity = require("stdlib/entity/entity")
|
||
|
local Surface = require("stdlib/surface")
|
||
|
local microcontroller = require('microcontroller')
|
||
|
require('stdlib/string')
|
||
|
require('constants')
|
||
|
require('help')
|
||
|
require('stdlib/area/tile')
|
||
|
|
||
|
function get_player_data(player_index)
|
||
|
if global.player_data == nil then
|
||
|
global.player_data = {}
|
||
|
end
|
||
|
local player_data = global.player_data[player_index] or {}
|
||
|
return player_data
|
||
|
end
|
||
|
|
||
|
function set_player_data(player_index, data)
|
||
|
if global.player_data == nil then
|
||
|
global.player_data = {}
|
||
|
end
|
||
|
global.player_data[player_index] = data
|
||
|
end
|
||
|
|
||
|
-- Handle MicroController OPEN event.
|
||
|
script.on_event("microcontroller-open", function(event)
|
||
|
local player = game.players[event.player_index]
|
||
|
local entity = player.selected
|
||
|
if entity and entity.name == "microcontroller" then
|
||
|
entity.operable = false
|
||
|
local player_data = get_player_data(event.player_index)
|
||
|
player_data.open_microcontroller = entity
|
||
|
set_player_data(event.player_index, player_data)
|
||
|
microcontrollerGui(player, entity)
|
||
|
end
|
||
|
end)
|
||
|
|
||
|
-- Handle Entity built event.
|
||
|
script.on_event({defines.events.on_built_entity, defines.events.on_robot_built_entity}, function(event)
|
||
|
local entity = event.created_entity
|
||
|
if entity.name and entity.name == "microcontroller" then
|
||
|
-- Init and insert new microcontroller to global state.
|
||
|
microcontroller.init(entity, {})
|
||
|
local didFind = false
|
||
|
for i, mc in ipairs(global.microcontrollers) do
|
||
|
if mc == entity then
|
||
|
didFind = true
|
||
|
end
|
||
|
end
|
||
|
if not didFind then
|
||
|
table.insert(global.microcontrollers, entity)
|
||
|
end
|
||
|
end
|
||
|
if entity.name and (entity.name == "microcontroller-ram" or entity.name == "microcontroller") then
|
||
|
local adjacent = {{x = entity.position.x, y = entity.position.y + 1}, {x = entity.position.x, y = entity.position.y - 1}}
|
||
|
local inverse = {2, 1, 3, 4}
|
||
|
-- Iterate over adjacent positions.
|
||
|
for index, pos in ipairs(adjacent) do
|
||
|
-- Check if there is a MicroController adjacent.
|
||
|
local mc = entity.surface.find_entity("microcontroller", pos)
|
||
|
if mc then
|
||
|
local offset = mc.position.x - pos.x
|
||
|
if offset >= 0 and index == 1 then
|
||
|
index = 1
|
||
|
elseif offset >= 0 and index == 2 then
|
||
|
index = 2
|
||
|
elseif offset < 0 and index == 1 then
|
||
|
index = 3
|
||
|
else
|
||
|
index = 4
|
||
|
end
|
||
|
microcontroller.attach_module(mc, entity, index)
|
||
|
if entity.name == "microcontroller" then
|
||
|
microcontroller.attach_module(entity, mc, inverse[index])
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end)
|
||
|
|
||
|
-- Handle MicroController GUI Close event.
|
||
|
script.on_event("microcontroller-close", function(event)
|
||
|
local player = game.players[event.player_index]
|
||
|
local player_data = get_player_data(event.player_index)
|
||
|
if player_data.open_microcontroller_gui then
|
||
|
microcontroller.update_program_text(player_data.open_microcontroller, player_data.open_microcontroller_gui.outer.inner['program-input'].text)
|
||
|
player_data.open_microcontroller_gui.destroy()
|
||
|
player_data.open_microcontroller_gui = nil
|
||
|
end
|
||
|
if player.gui.center['mc-docs'] then
|
||
|
player.gui.center['mc-docs'].destroy()
|
||
|
end
|
||
|
set_player_data(event.player_index, player_data)
|
||
|
end)
|
||
|
|
||
|
-- Handle GUI text changed event.
|
||
|
script.on_event(defines.events.on_gui_text_changed, function(event)
|
||
|
local player_data = get_player_data(event.player_index)
|
||
|
local element = event.element
|
||
|
if element.name and element.name == "program-input" then
|
||
|
local lines = string.split(element.text, '\n', false)
|
||
|
if #lines > MC_LINES then
|
||
|
for i = 1, #lines - MC_LINES do
|
||
|
table.remove(lines, #lines)
|
||
|
end
|
||
|
element.text = table.concat(lines, '\n')
|
||
|
end
|
||
|
microcontroller.update_program_text(player_data.open_microcontroller, element.text)
|
||
|
end
|
||
|
set_player_data(event.player_index, player_data)
|
||
|
end)
|
||
|
|
||
|
-- Handle GUI button presses.
|
||
|
script.on_event(defines.events.on_gui_click, function(event)
|
||
|
local player = game.players[event.player_index]
|
||
|
local player_data = get_player_data(event.player_index)
|
||
|
local element = event.element
|
||
|
-- RUN button pressed:
|
||
|
if element.name and element.name == "run-program" then
|
||
|
local mc_state = Entity.get_data(player_data.open_microcontroller)
|
||
|
element.parent.parent.error_message.caption = ""
|
||
|
microcontroller.compile(player_data.open_microcontroller, mc_state)
|
||
|
microcontroller.run(player_data.open_microcontroller, mc_state)
|
||
|
-- HALT button pressed:
|
||
|
elseif element.name and element.name == "halt-program" then
|
||
|
local mc_state = Entity.get_data(player_data.open_microcontroller)
|
||
|
microcontroller.halt(player_data.open_microcontroller, mc_state)
|
||
|
-- STEP button pressed:
|
||
|
elseif element.name and element.name == "step-program" then
|
||
|
local mc_state = Entity.get_data(player_data.open_microcontroller)
|
||
|
element.parent.parent.error_message.caption = ""
|
||
|
microcontroller.compile(player_data.open_microcontroller, mc_state)
|
||
|
microcontroller.step(player_data.open_microcontroller, mc_state)
|
||
|
-- CLOSE button pressed:
|
||
|
elseif element.name and element.name == "close-microcontroller-window" then
|
||
|
microcontroller.update_program_text(player_data.open_microcontroller, player_data.open_microcontroller_gui.outer.inner['program-input'].text)
|
||
|
player_data.open_microcontroller_gui.destroy();
|
||
|
player_data.open_microcontroller_gui = nil
|
||
|
if player.gui.center['mc-docs'] then
|
||
|
player.gui.center['mc-docs'].destroy()
|
||
|
end
|
||
|
-- COPY button pressed:
|
||
|
elseif element.name and element.name == "copy-program" then
|
||
|
player_data.program_clipboard = player.gui.left.microcontroller.outer.inner['program-input'].text
|
||
|
-- PASTE button pressed:
|
||
|
elseif element.name and element.name == "paste-program" and player_data.program_clipboard then
|
||
|
player_data.open_microcontroller_gui.outer.inner['program-input'].text = player_data.program_clipboard
|
||
|
-- SHOW/HIDE DOCS button pressed:
|
||
|
elseif element.name and element.name == "mc-help-button" then
|
||
|
if player.gui.center['mc-docs'] then
|
||
|
player.gui.center['mc-docs'].destroy()
|
||
|
else
|
||
|
local helptext = player.gui.center.add{type = "text-box", name = "mc-docs", text = HELP_TEXT}
|
||
|
helptext.read_only = true
|
||
|
helptext.style.font = "default-mono"
|
||
|
helptext.style.minimal_height = 310
|
||
|
helptext.style.maximal_height = 310
|
||
|
end
|
||
|
end
|
||
|
set_player_data(event.player_index, player_data)
|
||
|
end)
|
||
|
|
||
|
-- Handle MicroController ERROR event.
|
||
|
script.on_event(microcontroller.event_error, function(event)
|
||
|
local mc = event.entity
|
||
|
local mc_state = event.state
|
||
|
for _, player in pairs(game.players) do
|
||
|
local player_data = get_player_data(player.index)
|
||
|
if player_data.open_microcontroller_gui then
|
||
|
-- Display error message.
|
||
|
player_data.open_microcontroller_gui.outer.error_message.caption = event.message
|
||
|
end
|
||
|
end
|
||
|
end)
|
||
|
|
||
|
-- Handle MicroController HALT event.
|
||
|
script.on_event(microcontroller.event_halt, function(event)
|
||
|
local mc = event.entity
|
||
|
local mc_state = event.state
|
||
|
for _, player in pairs(game.players) do
|
||
|
local player_data = get_player_data(player.index)
|
||
|
if player.opened == player_data.open_microcontroller and player_data.open_microcontroller then
|
||
|
local state = Entity.get_data(player_data.open_microcontroller)
|
||
|
player.opened = nil
|
||
|
Entity.set_data(player_data.open_microcontroller, state)
|
||
|
end
|
||
|
end
|
||
|
end)
|
||
|
|
||
|
function signalToSpritePath(signal)
|
||
|
if signal.type == "virtual" then
|
||
|
return "virtual-signal/" .. signal.name
|
||
|
else
|
||
|
return signal.type .. '/' .. signal.name
|
||
|
end
|
||
|
end
|
||
|
|
||
|
script.on_event(defines.events.on_tick, function(event)
|
||
|
-- Ensure we have a table to store microcontrollers in the global state.
|
||
|
if not global.microcontrollers then
|
||
|
global.microcontrollers = {}
|
||
|
end
|
||
|
-- Iterate through all stored microcontrollers.
|
||
|
for i = #global.microcontrollers, 1, -1 do
|
||
|
local mc = global.microcontrollers[i]
|
||
|
if mc.valid then
|
||
|
local mc_state = Entity.get_data(mc)
|
||
|
if mc_state then
|
||
|
-- Enable/Disable the run/step button.
|
||
|
if mc_state.gui_run_button and mc_state.gui_run_button.valid then
|
||
|
if microcontroller.is_running(mc) then
|
||
|
mc_state.gui_run_button.enabled = false
|
||
|
mc_state.gui_step_button.enabled = false
|
||
|
else
|
||
|
mc_state.gui_run_button.enabled = true
|
||
|
mc_state.gui_step_button.enabled = true
|
||
|
end
|
||
|
end
|
||
|
-- Make text read-only while running
|
||
|
if mc_state.gui_program_input and mc_state.gui_program_input.valid then
|
||
|
mc_state.gui_program_input.read_only = microcontroller.is_running(mc)
|
||
|
end
|
||
|
-- Update the program lines in the GUI.
|
||
|
if mc_state.gui_line_numbers and mc_state.gui_line_numbers.valid then
|
||
|
updateLines(mc_state.gui_line_numbers, mc_state)
|
||
|
end
|
||
|
-- Update the inspector GUI.
|
||
|
if mc_state.inspector and mc_state.inspector.valid then
|
||
|
for i = 1, 4 do
|
||
|
mc_state.inspector['mem'..i..'-inspect'].sprite = signalToSpritePath( mc_state.memory[i].signal)
|
||
|
mc_state.inspector['mem'..i..'-inspect'].number = mc_state.memory[i].count
|
||
|
end
|
||
|
end
|
||
|
-- Check adjacent modules still exists.
|
||
|
if mc_state.adjacent_modules ~= nil then
|
||
|
for i = 4, 1, -1 do
|
||
|
local module = mc_state.adjacent_modules[i]
|
||
|
if module and not module.valid then
|
||
|
mc_state.adjacent_modules[i] = nil
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
-- Tick the microcontroller.
|
||
|
microcontroller.tick(mc, mc_state)
|
||
|
end
|
||
|
else
|
||
|
-- Microcontroller no longer exists, remove from global state.
|
||
|
for player_index, player in pairs(game.players) do
|
||
|
local player_data = get_player_data(player_index)
|
||
|
if player_data.open_microcontroller_gui and player_data.open_microcontroller_gui.valid then
|
||
|
player_data.open_microcontroller_gui.destroy()
|
||
|
player_data.open_microcontroller_gui = nil
|
||
|
end
|
||
|
if player.gui.center['mc-docs'] then
|
||
|
player.gui.center['mc-docs'].destroy()
|
||
|
end
|
||
|
set_player_data(player_index, player_data)
|
||
|
end
|
||
|
table.remove(global.microcontrollers, i)
|
||
|
end
|
||
|
end
|
||
|
end)
|
||
|
|
||
|
function updateLines(element, state)
|
||
|
local lines = {}
|
||
|
for i = 1, MC_LINES do
|
||
|
local line = tostring(i)
|
||
|
if i < 10 then line = "0"..i end
|
||
|
if i == state.error_line then
|
||
|
line = '!'..line
|
||
|
elseif i == state.program_counter then
|
||
|
line = '>'..line
|
||
|
else
|
||
|
line = ' '..line
|
||
|
end
|
||
|
table.insert(lines, line)
|
||
|
end
|
||
|
element.text = table.concat( lines, "\n" )
|
||
|
end
|
||
|
|
||
|
function microcontrollerGui( player, entity )
|
||
|
local leftGui = player.gui.left
|
||
|
if leftGui.microcontroller then
|
||
|
leftGui.microcontroller.destroy()
|
||
|
end
|
||
|
local state = Entity.get_data(entity)
|
||
|
local player_data = get_player_data(player.index)
|
||
|
|
||
|
local gWindow = leftGui.add{type = "frame", name = "microcontroller", caption = "Microcontroller"}
|
||
|
player_data.open_microcontroller_gui = gWindow
|
||
|
local outerflow = gWindow.add{type = "flow", name = "outer", direction = "vertical"}
|
||
|
|
||
|
local buttons_row = outerflow.add{type = "flow", name = "buttons_row", direction = "horizontal"}
|
||
|
state.gui_run_button = buttons_row.add{type = "sprite-button", name = "run-program", sprite = "microcontroller-play-sprite"}
|
||
|
state.gui_step_button = buttons_row.add{type = "sprite-button", name = "step-program", sprite = "microcontroller-next-sprite"}
|
||
|
state.gui_halt_button = buttons_row.add{type = "sprite-button", name = "halt-program", sprite = "microcontroller-stop-sprite"}
|
||
|
state.gui_copy_button = buttons_row.add{type = "sprite-button", name = "copy-program", sprite = "microcontroller-copy-sprite"}
|
||
|
state.gui_paste_button = buttons_row.add{type = "sprite-button", name = "paste-program", sprite = "microcontroller-paste-sprite"}
|
||
|
state.help_button = buttons_row.add{type = "button", name = "mc-help-button", caption = "?"}
|
||
|
state.gui_exit_button = buttons_row.add{type = "sprite-button", name = "close-microcontroller-window", sprite = "microcontroller-exit-sprite"}
|
||
|
|
||
|
local flow = outerflow.add{type = "flow", name = "inner", direction = "horizontal"}
|
||
|
state.gui_line_numbers = flow.add{type = "text-box", style = "notice_textbox", ignored_by_interaction = true}
|
||
|
state.gui_line_numbers.style.font = "default-mono"
|
||
|
state.gui_line_numbers.style.font_color = {r = 0.9, g = 0.9, b = 0.975}
|
||
|
updateLines(state.gui_line_numbers, state)
|
||
|
|
||
|
local textbox = flow.add{type = "text-box", name = "program-input", style = "notice_textbox"}
|
||
|
textbox.text = state.program_text
|
||
|
textbox.word_wrap = false
|
||
|
textbox.style.minimal_width = 260
|
||
|
textbox.style.maximal_width = 260
|
||
|
textbox.style.minimal_height = 590
|
||
|
textbox.style.maximal_height = 590
|
||
|
textbox.style.font = "default-mono"
|
||
|
state.gui_program_input = textbox
|
||
|
|
||
|
if microcontroller.is_running(entity) then
|
||
|
state.gui_run_button.enabled = false
|
||
|
else
|
||
|
state.gui_run_button.enabled = true
|
||
|
end
|
||
|
|
||
|
outerflow.add{type = "label", name = "error_message", caption = "", style = "bold_red_label"}
|
||
|
|
||
|
local inspectorflow = outerflow.add{type = "frame", direction = "horizontal"}
|
||
|
inspectorflow.style.horizontally_stretchable = true
|
||
|
inspectorflow.style.left_padding = 64
|
||
|
inspectorflow.style.right_padding = 64
|
||
|
for i = 1, 4 do
|
||
|
local sb = inspectorflow.add{type = "sprite-button", name = "mem"..i.."-inspect", tooltip = "mem"..i}
|
||
|
sb.style.horizontally_stretchable = true
|
||
|
sb.style.align = "right"
|
||
|
end
|
||
|
state.inspector = inspectorflow
|
||
|
|
||
|
Entity.set_data(entity, state)
|
||
|
set_player_data(player.index, player_data)
|
||
|
end
|