[Guide] Editing entity UI

Place to post guides, observations, things related to modding that are not mods themselves.
Post Reply
MegaMech
Burner Inserter
Burner Inserter
Posts: 12
Joined: Tue Mar 22, 2016 10:02 pm
Contact:

[Guide] Editing entity UI

Post by MegaMech »

One might think that something as simple as adding a button to a building UI wouldn't be too hard. It's actually somewhat complicated if you don't know which approach to use. I created this guide to help new modders figure out the UI a bit quicker.

In Factorio all entity UI's are read-only. If you want to modify a UI you have to completely recreate it and its logic. However, another option exists where you leave the UI alone and add your own UI around it. That's the approach discussed here and it's much easier.

The code is not laid out in order. If you're new to programming the events go at the bottom. The UI function goes at the top and I put the destroy function under that.

Control.lua
First we need to hook the gui_opened event and check that it's the right entity. In-this case: ammo-turret.

Code: Select all

script.on_event(defines.events.on_gui_opened,function(event)
  if event.entity == nil then return end
  if event.entity.type == 'ammo-turret' then
    local player = game.players[event.player_index] --Find the player that opened the UI
    turret1_ui(player,event.entity) -- Run our "create gui" method
  end
  end)
  
Next we use the gui_closed event to check if the UI has been closed

Code: Select all

  script.on_event(defines.events.on_gui_closed,function(event)
  if event.entity == nil then return end
  if event.entity.type == 'ammo-turret' then
    local player = game.players[event.player_index] --Find the player that closed the UI
    turret1_ui_hide(player) -- Run our "destroy gui" method
  end
  end)
This is how we delete/remove/destroy the UI.

Code: Select all

	function turret1_ui_hide(ply)
	if ply == nil then return end
		local player_gui = ply.gui.center
		if not player_gui.turret_wrap then --If for some reason this method is called but the UI does not exist, we return to avoid throwing a nil pointer except (Shout-out to since deceased Java language).
		return  
		end
		
		player_gui.turret_wrap.destroy() --Removes the UI
	end
This is how we check if an element is being interacted with:

Code: Select all

script.on_event(defines.events.on_gui_click, function(event)
  if event.element.name == "button_upgrade1" then --If they interacted with our button
	local player = game.players[event.player_index]
	local turret = player.opened
    if player.get_main_inventory().get_item_count("coin") >= 1000 then -- and If they have enough coins
		player.remove_item{name="coin", count=1000} --remove the coins
		--Then do stuff here
		return
	end
  end
  -- Add in more elements here by copying the above and pasting it here changing element.name to whatever your elements are named.
end
Now lets create the gui

Code: Select all

function turret1_ui(ply, ent)
		local player_gui = ply.gui.center -- Get the center GUI of player
		if player_gui.turret_wrap ~= nil then -- If the UI already exists then return cause we don't want multiple.
			
			return
		end
		
		
		player_gui.add({ type="frame", name="turret_wrap", direction="horizontal", style="turretgui"}) --add a frame
		player_gui.turret_wrap.add({ type="frame", name="button_f", direction="vertical"})
		player_gui.turret_wrap.add({ type="flow", name="textFlow", direction="vertical", style="textlabel_flow"}) -add a flow for proper layout

		if ent.name == "turret1" then -- If you have multiple entities you can use this a bunch of times to create a dynamic UI between entities. Or if you're only doing this once you can create all the UI you need
			player_gui.turret_wrap.button_f.add({ type="sprite-button", tooltip="Upgrade to Cobalt Turret 2k\n\n"..stats1, name="button_upgrade1", sprite="upgradebuttonimg", clicked_sprite="upgradebuttonimg_click", style="turretgui_button"}) -- Button name="" corresponds to our method that detects if the button was pressed.
			player_gui.turret_wrap.textFlow.add({ type="label", name="label1a", caption="Health: 400"})
			player_gui.turret_wrap.textFlow.add({ type="label", name="label1b", caption="Range: 18"})
			player_gui.turret_wrap.textFlow.add({ type="label", name="label1c", caption="Shooting Speed: 7.5/s"})
			player_gui.turret_wrap.textFlow.add({ type="label", name="label1d", caption="Damage bonus: 0%"})
		end
end
Finally, styling. This one can be confusing to figure out. Game restarts are *required* to reflect data.lua changes
data.lua: require("prototypes.style")
prototypes/style.lua:

Every array here requires type and parent. Everything else is optional. Note that parent isn't really the parent. It's just the element you're applying it to. If you want to style a button then the parent is button. If you want to style a frame then the parent is frame. Normally you can guess the type by adding "_style" as a suffix to the UI element you're editing (ex. frame_style). I believe width and height are alternate names to minimum_width and minimum_height. Don't forget to restart the game after each edit. Other guides will have more advanced styling but most of them are outdated making it difficult to figure out whats relevant today.
See style specification: https://wiki.factorio.com/Types/StyleSpecification

Code: Select all

gui = data.raw["gui-style"].default --A mod can only call data.raw["gui-style"].default once.


	gui["turretgui"] = { --This 'turretgui' name reflects the style in control.lua (style="turretgui"
  type = "frame_style",
  parent = "frame",
  left_margin = 850,
  bottom_margin = 95,
  minimum_height = 250
}
	gui["turretgui_button"] = {
  type = "button_style",
  parent = "button",
  width = 64,
  height = 64,
  top_margin = 11
}
	gui["textlabel_flow"] = { --Note that vertical_flow_style and its parent "vertical_flow"
  type = "vertical_flow_style",
  parent = "vertical_flow",
  left_margin = 10
}
data.lua

All of the sprite-button guides are outdated. This is how to add a sprite to a sprite-button

Code: Select all

data:extend({
	{
		type = "sprite",
		name = "upgradebuttonimg", --Name this whatever you want
		width = "64",
		height = "64",
		filename = "__Amod__/resource/upgrade.png"
	},
	{
		type = "sprite",
		name = "upgradebuttonimg_click", -- If you add _click and _hover to them it makes it easier to distinguish between them.
		width = "64",
		height = "64",
		filename = "__Amod__/resource/upgrade_click.png"
	}
})
You'll notice on our button that clicked_sprite= our sprite above with the suffix "_click". You can also use hover_sprite="" to change the sprite-button image on hover. Look up the API for other visual changes that can be applied to the button.

Code: Select all

player_gui.turret_wrap.button_f.add({ type="sprite-button", tooltip="Upgrade to Cobalt Turret 2k\n\n"..stats1, name="button_upgrade1", sprite="upgradebuttonimg", clicked_sprite="upgradebuttonimg_click", style="turretgui_button"})
And that's it. I hope it helps a few people! Good luck.
Last edited by MegaMech on Thu Dec 19, 2019 4:08 pm, edited 1 time in total.

Honktown
Smart Inserter
Smart Inserter
Posts: 1026
Joined: Thu Oct 03, 2019 7:10 am
Contact:

Re: [Guide] Editing entity UI

Post by Honktown »

This actually will help me. Thanks. Glad I randomly was looking at discussions.

I both want to make a UI for an idea I have and fix a bug with an existing mod. I might report back on the bug (the mod isn't clearing / deleting old gui objects properly, I think, and I don't know if that'd entail a lot to fix).
I have mods! I guess!
Link

MegaMech
Burner Inserter
Burner Inserter
Posts: 12
Joined: Tue Mar 22, 2016 10:02 pm
Contact:

Re: [Guide] Editing entity UI

Post by MegaMech »

Glad it helped!
The problem is likely related to improper switching in an event. Or an event being called when you don't want it so improper filtering of it. I dislike doing GUI stuff in games. The documentation is normally lacking, the coding is tedious and complicated for something quite simple in other coding formats like web design. (I can't believe I just called web design simple)

Post Reply

Return to “Modding discussion”