Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 62 additions & 15 deletions features/redmew_qol.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,14 @@ local function pick_name()
end

local loader_frame_name = Gui.uid_name()
local loader_machine_frame_name = Gui.uid_name()
local loader_button_player = Gui.uid_name()
local loader_button_machine = Gui.uid_name()

-- Exposed for tests
Public._loader_frame_name = loader_frame_name
Public._loader_machine_frame_name = loader_machine_frame_name

local loaders = {
['loader'] = true,
['fast-loader'] = true,
Expand Down Expand Up @@ -92,10 +97,11 @@ local function any_loader_enabled(recipes)
end

local function draw_loader_frame(parent, entity)
local frame = parent[loader_frame_name]
local frame_name = entity and loader_machine_frame_name or loader_frame_name
local frame = parent[frame_name]
local player = entity or safe_get_player(parent.player_index)
local recipes = player.force.recipes
if not player or not any_loader_enabled(recipes) then
local recipes = player and player.force.recipes
if not recipes or not any_loader_enabled(recipes) then
if frame and frame.valid then
Gui.destroy(frame)
end
Expand All @@ -107,7 +113,7 @@ local function draw_loader_frame(parent, entity)
else
frame = parent.add {
type = 'frame',
name = loader_frame_name,
name = frame_name,
anchor = {
gui = defines.relative_gui_type[entity and 'assembling_machine_select_recipe_gui' or 'controller_gui'],
position = defines.relative_gui_position.right
Expand Down Expand Up @@ -321,6 +327,9 @@ local features = {
[defines.events.on_research_finished] = 'on_research_finished',
[defines.events.on_gui_opened] = 'on_gui_opened',
[defines.events.on_gui_closed] = 'on_gui_closed',
[defines.events.on_player_created] = 'on_player_created',
[defines.events.on_player_joined_game] = 'on_player_created',
[defines.events.on_player_changed_force] = 'on_player_created',
},
handlers = {
on_built = Token.register(snap_loader),
Expand All @@ -330,10 +339,18 @@ local features = {
return
end
e.research.force.recipes[recipe].enabled = true
for _, p in pairs(game.players) do
if p.opened_gui_type == defines.gui_type.controller then
draw_loader_frame(p.gui.relative)
end
for _, p in pairs(e.research.force.players) do
draw_loader_frame(p.gui.relative)
end
end),
-- The frame anchored to the controller GUI is kept in place permanently.
-- Creating it inside on_gui_opened instead would delay its appearance in
-- multiplayer by the latency round trip, as script events do not run in
-- the client's latency-hidden state.
on_player_created = Token.register(function(e)
local p = safe_get_player(e.player_index)
if p then
draw_loader_frame(p.gui.relative)
end
end),
on_gui_opened = Token.register(function(e)
Expand All @@ -353,12 +370,27 @@ local features = {
if not p then
return
end
local frame = p.gui.relative[loader_frame_name]
local frame = p.gui.relative[loader_machine_frame_name]
if frame and frame.valid then
Gui.destroy(frame)
end
end)
}
},
on_register = function()
for _, p in pairs(game.players) do
draw_loader_frame(p.gui.relative)
end
end,
on_unregister = function()
for _, p in pairs(game.players) do
for _, name in pairs({ loader_frame_name, loader_machine_frame_name }) do
local frame = p.gui.relative[name]
if frame and frame.valid then
Gui.destroy(frame)
end
end
end
end,
},
{
name = 'save_bots',
Expand All @@ -379,6 +411,9 @@ local function register_feature(feature)
for event_id, handler_id in pairs(feature.events) do
Event.add_removable(event_id, feature.handlers[handler_id])
end
if feature.on_register and game then
feature.on_register()
end
return true
end

Expand All @@ -387,6 +422,9 @@ local function unregister_feature(feature)
Event.remove_removable(event_id, feature.handlers[handler_id])
end
enabled[feature.name] = false
if feature.on_unregister and game then
feature.on_unregister()
end
return true
end

Expand All @@ -409,7 +447,8 @@ end
Gui.on_click(loader_button_player, function(event)
local player = event.player
local recipe = event.element.elem_value
if not player.force.recipes[recipe].enabled then
local force_recipe = recipe and player.force.recipes[recipe]
if not (force_recipe and force_recipe.enabled) then
return
end
local count = (event.button == defines.mouse_button_type.left) and (event.shift and 4294967295 or 1) or (event.button == defines.mouse_button_type.right) and 5 or nil
Expand All @@ -420,7 +459,8 @@ end)

Gui.on_click(loader_button_machine, function(event)
local recipe = event.element.elem_value
if not event.player.force.recipes[recipe].enabled then
local force_recipe = recipe and event.player.force.recipes[recipe]
if not (force_recipe and force_recipe.enabled) then
return
end
local entity = Gui.get_data(event.element)
Expand All @@ -439,6 +479,11 @@ local loader_check_token = Token.register(function()
end
end
end
if enabled.loaders then
for _, p in pairs(game.players) do
draw_loader_frame(p.gui.relative)
end
end
end)

Event.on_init(function()
Expand All @@ -452,9 +497,11 @@ Event.on_configuration_changed(function()
Task.set_timeout_in_ticks(1, loader_check_token)
end
for _, p in pairs(game.players) do
local frame = p.gui.relative[loader_frame_name]
if frame then
Gui.destroy(frame)
for _, name in pairs({ loader_frame_name, loader_machine_frame_name }) do
local frame = p.gui.relative[name]
if frame then
Gui.destroy(frame)
end
end
end
end)
Expand Down
156 changes: 156 additions & 0 deletions features/redmew_qol_tests.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
local Declare = require 'utils.test.declare'
local EventFactory = require 'utils.test.event_factory'
local Assert = require 'utils.test.assert'
local RedmewQol = require 'features.redmew_qol'

local loader_recipes = { 'loader', 'fast-loader', 'express-loader', 'turbo-loader' }

local function controller_frame(player)
return player.gui.relative[RedmewQol._loader_frame_name]
end

local function machine_frame(player)
return player.gui.relative[RedmewQol._loader_machine_frame_name]
end

local function raise_controller_gui_event(event_name, player)
EventFactory.raise({
name = event_name,
tick = game.tick,
player_index = player.index,
gui_type = defines.gui_type.controller
})
end

local function count_loader_buttons(element)
local count = 0
for _, child in pairs(element.children) do
if child.type == 'choose-elem-button' then
count = count + 1
else
count = count + count_loader_buttons(child)
end
end
return count
end

Declare.module({'features', 'redmew qol', 'loaders gui'}, function()
local old_feature_enabled
local old_recipe_enabled

Declare.module_startup(function(context)
local force = context.player.force
old_feature_enabled = RedmewQol.get_loaders()
old_recipe_enabled = force.recipes['loader'].enabled

force.recipes['loader'].enabled = true
if not old_feature_enabled then
RedmewQol.set_loaders(true)
end
end)

Declare.module_teardown(function(context)
context.player.force.recipes['loader'].enabled = old_recipe_enabled
if not old_feature_enabled then
RedmewQol.set_loaders(false)
else
-- redraw with the restored recipe state
raise_controller_gui_event(defines.events.on_gui_opened, context.player)
end
end)

Declare.test('frame is drawn for all players when the feature is enabled', function(context)
local player = context.player

RedmewQol.set_loaders(false)
Assert.is_nil(controller_frame(player), 'frame should be removed when the feature is disabled')

RedmewQol.set_loaders(true)
Assert.valid(controller_frame(player), 'frame should be drawn when the feature is enabled')
end)

Declare.test('frame persists when the crafting menu is closed', function(context)
local player = context.player

raise_controller_gui_event(defines.events.on_gui_opened, player)
Assert.valid(controller_frame(player), 'frame should exist after opening the crafting menu')

raise_controller_gui_event(defines.events.on_gui_closed, player)
Assert.valid(controller_frame(player), 'frame should persist after closing the crafting menu')
end)

Declare.test('frame is removed when no loader recipe is enabled', function(context)
local player = context.player
local recipes = player.force.recipes

local old_states = {}
for _, name in pairs(loader_recipes) do
local recipe = recipes[name]
if recipe then
old_states[name] = recipe.enabled
recipe.enabled = false
end
end

raise_controller_gui_event(defines.events.on_gui_opened, player)
local removed = controller_frame(player) == nil

for name, state in pairs(old_states) do
recipes[name].enabled = state
end
raise_controller_gui_event(defines.events.on_gui_opened, player)

Assert.is_true(removed, 'frame should be removed when no loader recipe is enabled')
Assert.valid(controller_frame(player), 'frame should be drawn again when a loader recipe is enabled')
end)

Declare.test('frame lists one button per enabled loader recipe', function(context)
local player = context.player

raise_controller_gui_event(defines.events.on_gui_opened, player)

local expected = 0
for _, name in pairs(loader_recipes) do
local recipe = player.force.recipes[name]
if recipe and recipe.enabled then
expected = expected + 1
end
end

Assert.equal(expected, count_loader_buttons(controller_frame(player)), 'frame should have one button per enabled loader recipe')
end)

Declare.test('machine frame is separate and removed when its gui is closed', function(context)
local player = context.player
local surface = player.surface
local position = surface.find_non_colliding_position('assembling-machine-1', player.position, 32, 1)
local machine = surface.create_entity({ name = 'assembling-machine-1', position = position, force = player.force })

raise_controller_gui_event(defines.events.on_gui_opened, player)

EventFactory.raise({
name = defines.events.on_gui_opened,
tick = game.tick,
player_index = player.index,
gui_type = defines.gui_type.entity,
entity = machine
})
local machine_frame_drawn = machine_frame(player) ~= nil

EventFactory.raise({
name = defines.events.on_gui_closed,
tick = game.tick,
player_index = player.index,
gui_type = defines.gui_type.entity,
entity = machine
})
local machine_frame_removed = machine_frame(player) == nil
local controller_frame_kept = controller_frame(player) ~= nil

machine.destroy()

Assert.is_true(machine_frame_drawn, 'machine frame should be drawn when a machine gui is opened')
Assert.is_true(machine_frame_removed, 'machine frame should be removed when the machine gui is closed')
Assert.is_true(controller_frame_kept, 'controller frame should not be affected by the machine gui closing')
end)
end)
Loading