Page 1 of 3

How to get current surface and all entities with some name?

Posted: Tue Aug 18, 2020 8:42 pm
by SLywnow
I try to create a little power production mod, based on rocket launch.
I already create items and entities, but has problem with script.
In another topic (viewtopic.php?t=87835) DaveMcW told me what I needed. But I can't figure out which event to call it in.

Re: How to get current surface and all entities with some name?

Posted: Tue Aug 18, 2020 10:05 pm
by asdff45
It depends, on when you want to call that function.
Here is the list of all available events: https://lua-api.factorio.com/latest/events.html

For further help, please provide, what you want to achive and when you want to run that code.

Re: How to get current surface and all entities with some name?

Posted: Tue Aug 18, 2020 11:11 pm
by Yoyobuae
SLywnow wrote:
Tue Aug 18, 2020 8:42 pm
I try to create a little power production mod, based on rocket launch.
I already create items and entities, but has problem with script.
In another topic (viewtopic.php?t=87835) DaveMcW told me what I needed. But I can't figure out which event to call it in.
Then I think the event you want is this: https://lua-api.factorio.com/latest/eve ... t_launched

When a rocket is launched you then inspect what the rocket is carrying:

Code: Select all

function on_rocket_launched (event)
	local rocket = event.rocket
	if rocket then
		local inventory = rocket.get_inventory(defines.inventory.rocket)
		if inventory then
			local count = inventory.get_item_count("name-of-some-item")
			local surface = rocket.surface
			# Do something here with count of launched item and surface
		end
	end
end
script.on_event(defines.events.on_rocket_launched, on_rocket_launched)
Inside that function you will want to update your global count of items launched and somehow update the entities you want to update (probably using the function DaveMcW gave you).

Re: How to get current surface and all entities with some name?

Posted: Tue Aug 18, 2020 11:25 pm
by SLywnow
Yoyobuae wrote:
Tue Aug 18, 2020 11:11 pm
Then I think the event you want is this
No, i already done it. Just this code:

Code: Select all

script.on_event(defines.events.on_tick,function(event)
  for _, player in pairs(game.players) do change_output(player.surface,"player","ds-energy-loader") end
end)

function change_output(surface, force, entity_name)
  local entities = surface.find_entities_filtered{force=force, name=entity_name}
  for _, entity in pairs(entities) do
	if global.dysonsphere == nil then 
		global.dysonsphere = 0
	end 
	entity.power_production = global.dysonsphere / #entities * 100000 / 60
  end
end
But now i has new problem
(I launched 3 object to space)
Image
And:
Image

Re: How to get current surface and all entities with some name?

Posted: Tue Aug 18, 2020 11:45 pm
by Yoyobuae
SLywnow wrote:
Tue Aug 18, 2020 11:25 pm
Then I think the event you want is this
No, i already done it. Just this code:

Code: Select all

script.on_event(defines.events.on_tick,function(event)
  for _, player in pairs(game.players) do change_output(player.surface,"player","ds-energy-loader") end
end)

function change_output(surface, force, entity_name)
  local entities = surface.find_entities_filtered{force=force, name=entity_name}
  for _, entity in pairs(entities) do
	if global.dysonsphere == nil then 
		global.dysonsphere = 0
	end 
	entity.power_production = global.dysonsphere / #entities * 100000 / 60
  end
end
Do keep in mind that the on_tick event runs 60 times per second and you are executing the change_output on every tick. This has the potential to make the game very slow if there are many ds-energy-loader entities in the map.
SLywnow wrote:
Tue Aug 18, 2020 11:25 pm
But now i has new problem
(I launched 3 object to space)
I don't see where your code tracks the number of objects launched into space. Can you post that part?

Re: How to get current surface and all entities with some name?

Posted: Tue Aug 18, 2020 11:55 pm
by SLywnow
Yoyobuae wrote:
Tue Aug 18, 2020 11:45 pm
Do keep in mind that the on_tick event runs 60 times per second and you are executing the change_output on every tick. This has the potential to make the game very slow if there are many ds-energy-loader entities in the map.
Given that entity is decreasing its energy production, if there is another such entity on the surface, this is unlikely.
However, if there is a way to optimize this, i want to know how
Yoyobuae wrote:
Tue Aug 18, 2020 11:45 pm
I don't see where your code tracks the number of objects launched into space. Can you post that part?
That's simple part

Code: Select all

script.on_event(defines.events.on_rocket_launched,function(event)
  rocketLaunched(event)
end)

function rocketLaunched(event)
  local rocket_inventory = event.rocket.get_inventory(1)
  if rocket_inventory.get_item_count("dyson-sphere-satellite") == 0 then
    return
  end
  if global.dysonsphere == nil then
   global.dysonsphere = 0
  end
  global.dysonsphere = global.dysonsphere+1
  game.players[1].print("launched, count "..global.dysonsphere)
end
I think i has problem with entity:

Code: Select all

{
	type = "electric-energy-interface",
	name = "ds-energy-loader",
	icon = "__base__/graphics/icons/accumulator.png",
	icon_size = 64,
	flags = {"placeable-neutral", "placeable-player", "player-creation"},
	minable = {hardness = 0.2, mining_time = 0.5, result = "ds-energy-loader"},
	max_health = 250,
	energy_source = {
		type = "electric",
		usage_priority = "tertiary",
		--input_flow_limit = "0MW",
		--output_flow_limit = "1000GW",
	},
	allow_copy_paste = true,
	collision_box = {{-0.9, -0.9}, {0.9, 0.9}},
    	selection_box = {{-1, -1}, {1, 1}},
	collision_mask = {},
	picture = accumulator_picture( {r=0.6, g=1, b=1, a=1} ),
	energy_production = "1ZW",
	energy_usage = "0kW",
}

Re: How to get current surface and all entities with some name?

Posted: Wed Aug 19, 2020 12:50 am
by SLywnow
Okay, i found problem
I should have added a buffer. Now all works!
It's time to move to cinema4d to do sprites...

Code: Select all

{
	type = "electric-energy-interface",
	name = "ds-energy-loader",
	icon = "__base__/graphics/icons/accumulator.png",
	icon_size = 64,
	flags = {"placeable-neutral", "placeable-player", "player-creation"},
	minable = {hardness = 0.2, mining_time = 0.5, result = "ds-energy-loader"},
	max_health = 250,
	energy_source = {
		type = "electric",
		usage_priority = "tertiary",
		buffer_capacity = "10GJ",
		render_no_power_icon=true,
		--input_flow_limit = "0MW",
		--output_flow_limit = "1000GW",
	},
	allow_copy_paste = true,
	collision_box = {{-0.9, -0.9}, {0.9, 0.9}},
    selection_box = {{-1, -1}, {1, 1}},
	collision_mask = {},
	picture = accumulator_picture( {r=0.6, g=1, b=1, a=1} ),
	energy_production = "0kW",
	energy_usage = "0kW",
	--corpse = "medium-remnants",
    --subgroup = "other",
}
And a little change in control.lua:

Code: Select all

local ticktime=0
script.on_event(defines.events.on_tick,function(event)
	if ticktime < 60 then
		ticktime = ticktime+1
	else 
		for _, player in pairs(game.players) do change_output(player.surface,"player","ds-energy-loader") end
		ticktime=0
	end
end)

function change_output(surface, force, entity_name)
  local entities = surface.find_entities_filtered{force=force, name=entity_name}
  for _, entity in pairs(entities) do
	if global.dysonsphere == nil then 
		global.dysonsphere = 0
	end 
	--game.players[1].print("worked! "..global.dysonsphere)
	entity.power_production = global.dysonsphere / #entities * 100000 / 60
	entity.electric_buffer_size = global.dysonsphere / #entities * 100000 / 60
  end
end

Re: How to get current surface and all entities with some name?

Posted: Wed Aug 19, 2020 2:07 am
by Yoyobuae
SLywnow wrote:
Tue Aug 18, 2020 11:55 pm
Yoyobuae wrote:
Tue Aug 18, 2020 11:45 pm
Do keep in mind that the on_tick event runs 60 times per second and you are executing the change_output on every tick. This has the potential to make the game very slow if there are many ds-energy-loader entities in the map.
Given that entity is decreasing its energy production, if there is another such entity on the surface, this is unlikely.
However, if there is a way to optimize this, i want to know how
There are two situations where your ds-energy-loader need to be updated:
  • A rocket is launched with dyson-sphere-satellite
  • A new ds-energy-loader is placed
The first case can be handled within the on_rocket_launched event. Just need to go thru all the existing ds-energy-loaders and update their output (kinda like you already do inside the on_tick event).

The second case can be handled with the events on_robot_built_entity and on_built_entity. On those events you can call the change_output for the ds-energy-loader that was just placed.

Re: How to get current surface and all entities with some name?

Posted: Wed Aug 19, 2020 2:18 am
by SLywnow
Yoyobuae wrote:
Wed Aug 19, 2020 2:07 am
The second case can be handled with the events on_robot_built_entity and on_built_entity. On those events you can call the change_output for the ds-energy-loader that was just placed.
In that case i need check destroying events too, but yes, i think i use that, thanks

Re: How to get current surface and all entities with some name?

Posted: Wed Aug 19, 2020 2:29 am
by Yoyobuae
SLywnow wrote:
Wed Aug 19, 2020 2:18 am
In that case i need check destroying events too, but yes, i think i use that, thanks
True, the events you'll need to check are:
  • on_pre_player_mined_item
  • on_robot_pre_mined
  • on_entity_died

Re: How to get current surface and all entities with some name?

Posted: Wed Aug 19, 2020 7:32 am
by Pi-C
Yoyobuae wrote:
Wed Aug 19, 2020 2:29 am
SLywnow wrote:
Wed Aug 19, 2020 2:18 am
In that case i need check destroying events too, but yes, i think i use that, thanks
True, the events you'll need to check are:
  • on_pre_player_mined_item
  • on_robot_pre_mined
  • on_entity_died
Shouldn't there also be a check for script_raised_built and script_raised_destroy?

Re: How to get current surface and all entities with some name?

Posted: Wed Aug 19, 2020 8:34 am
by Qon
Don't use

Code: Select all

local entities = surface.find_entities_filtered{force=force, name=entity_name}
at all. The only valid use is for initialisation if the entities are placed before your mod is placed. But your entities are part of the mod so avoid it completely.
You are using the events, and they will tell you about what was placed or removed. Save those in the global table.
surface.find_entities() is really slow and only becomes slower the more you explore the map. And if you run it every time an entity is placed or removed then that's comparativly as bad as doing it on every tick.

If I run

Code: Select all

/c script.on_event(defines.events.on_tick, function() local a = game.players[1].surface.find_entities_filtered{name = 'burner-inserter'} end)
then I drop from 60 UPS to 7 UPS, with no factory at all!

Re: How to get current surface and all entities with some name?

Posted: Wed Aug 19, 2020 2:59 pm
by Yoyobuae
Pi-C wrote:
Wed Aug 19, 2020 7:32 am
Yoyobuae wrote:
Wed Aug 19, 2020 2:29 am
True, the events you'll need to check are:
  • on_pre_player_mined_item
  • on_robot_pre_mined
  • on_entity_died
Shouldn't there also be a check for script_raised_built and script_raised_destroy?
Yeah, those too.

Re: How to get current surface and all entities with some name?

Posted: Wed Aug 19, 2020 3:10 pm
by SLywnow
Qon wrote:
Wed Aug 19, 2020 8:34 am
Don't use

Code: Select all

local entities = surface.find_entities_filtered{force=force, name=entity_name}
at all.
Okaaay, and what i must use to find all my entities and change values?

Re: How to get current surface and all entities with some name?

Posted: Wed Aug 19, 2020 3:53 pm
by Qon
SLywnow wrote:
Wed Aug 19, 2020 3:10 pm
Okaaay, and what i must use to find all my entities and change values?
You don't have to "find all the entities" at once. Each event will tell you what is added and what is removed. Maintain a data structure in global with references to entities or entity indices of relevant entities (or just the number of them if that's enough) and use that data structure instead.
Most trivially written like something like this:

Code: Select all

script.on_init(function(event)
    global.things_I_care_about = {}
    global.things_I_care_about_count = 0
end)

function add_entity(event)
    global.things_I_care_about[event.entity] = true
    global.things_I_care_about_count = global.things_I_care_about_count + 1
end

function remove_entity(event)
    if global.things_I_care_about[event.entity] ~= nil then
        global.things_I_care_about[event.entity] = nil
        global.things_I_care_about_count = global.things_I_care_about_count - 1
    end
end

script.on_event(defines.events.on_something_added,                     add_entity)
script.on_event(defines.events.on_another_event_where_something_added, add_entity)

script.on_event(defines.events.on_something_removed,                remove_entity)

script.on_event(defines.events.on_event_where_I_go_through_my_items, function(event)
    -- If you just need count then 
    -- global.things_I_care_about_count
    -- is the same as 
    -- table_size(global.things_I_care_about) 
    -- but is faster since it's already calculated

    for entity, _ in pairs(global.things_I_care_about) do
        if entity.valid then 
            do_stuff(entity)
        else
            global.things_I_care_about[entity] = nil
            global.things_I_care_about_count = global.things_I_care_about_count - 1
        end
    end
end)

That way you don't have to rebuild the entire list from scratch tens of times per second where you go through the entire surface and every entity on it and filtering out the handful of things you actually care about. That's orders of magnitude slower.

The event names are ficticious and it's somewhat pseudocodey here, but the principle is to increment and decrement the data structure that keeps track of what things you want to keep track of and use that instead of doing all the things.

Edit: I read DaveMcW's response and your previous thread. Added code for keeping track of count.

Re: How to get current surface and all entities with some name?

Posted: Thu Aug 20, 2020 2:21 am
by SLywnow
Qon wrote:
Wed Aug 19, 2020 3:53 pm
I try to make this, and... it's work, partially...

If i copy-paste your code, the game says that the boolean type doesn't have a valid (well, it was to be expected), so I store it like this:

Code: Select all

global.generators[entity] = entity
But in that case the array is not being updated, I tried to understand how arrays work in lua, but when i read that they can contain different types and can be accessed by object, my brain, used to c++, js and c#, finally broke down.
How to make this?

Re: How to get current surface and all entities with some name?

Posted: Thu Aug 20, 2020 7:28 am
by Pi-C
SLywnow wrote:
Thu Aug 20, 2020 2:21 am
Qon wrote:
Wed Aug 19, 2020 3:53 pm
If i copy-paste your code, the game says that the boolean type doesn't have a valid
Say, this looks suspicious!

Code: Select all

for entity, _ in pairs(global.things_I_care_about) do
        if entity.valid then 
            do_stuff(entity)
	…
        end
Do you really use the complete entity as table index? That would mean your table looks like

Code: Select all

global = {
	things_I_care_about = {
		entity1 = true,
		entity2 = true,
		…
	}
}
What I'd have expected is

Code: Select all

global = {
	things_I_care_about = {
		[1] = {entity, true}
		[2] = {entity, true}
		…
	}
}
Take that with a grain of salt, though -- I'm still decaffeinated. :-)

Re: How to get current surface and all entities with some name?

Posted: Thu Aug 20, 2020 7:39 am
by SLywnow
Pi-C wrote:
Thu Aug 20, 2020 7:28 am
Say, this looks suspicious!
Thank you for your clarification

Now i can find my fail

I tried to use this

Code: Select all

for _, entity in pairs(global.things_I_care_about) do
Instead of this

Code: Select all

for entity, _ in pairs(global.things_I_care_about) do
Before that, I thought that the first code was just a template for calling "for", but now I understand what it means and how.
It will be necessary to remember that relying on your many years of experience in coding in a very unfamiliar language like lua is a bad idea. :lol: Although I should have understood that when I was studying Python

Re: How to get current surface and all entities with some name?

Posted: Thu Aug 20, 2020 8:38 am
by Qon
SLywnow wrote:
Thu Aug 20, 2020 2:21 am
I try to make this, and... it's work, partially...

If i copy-paste your code, the game says that the boolean type doesn't have a valid (well, it was to be expected), so I store it like this:

Code: Select all

global.generators[entity] = entity
SLywnow wrote:
Thu Aug 20, 2020 7:39 am
Now i can find my fail

I tried to use this

Code: Select all

for _, entity in pairs(global.things_I_care_about) do
Instead of this

Code: Select all

for entity, _ in pairs(global.things_I_care_about) do
Before that, I thought that the first code was just a template for calling "for", but now I understand what it means and how.
So when you copy and pasted you also modified it to read the "true", and tried to use it as an entity?

I think you get it now but just to show

Code: Select all

local xpression = 5
local my_table = {
	blubb = 3,
	['key with space'] = 9,
	[xpression] = 11
}
my_table[1026] = 34
my_table[1025] = 33
my_table[1] = 99 
for k, v in pairs(my_table) do
	game.print(game.table_to_json({key = k, value = v}))
end
--[[ pairs in factorio prints out integer keys 1-1024 in order, all other keys in insertion order. ]]--
will print out

Code: Select all

{"key":1,"value":99}
{"key":5,"value":11}
{"key":"blubb","value":3}
{"key":"key with space","value":9}
{"key":1026,"value":34}
{"key":1025,"value":33}
_ is just a variable name that you don't read.

Re: How to get current surface and all entities with some name?

Posted: Thu Aug 20, 2020 8:49 am
by Qon
Pi-C wrote:
Thu Aug 20, 2020 7:28 am
Do you really use the complete entity as table index?
[/code]
What I'd have expected is

Code: Select all

global = {
	things_I_care_about = {
		[1] = {entity, true}
		[2] = {entity, true}
		…
	}
}
Take that with a grain of salt, though -- I'm still decaffeinated. :-)
What you expected gives you a list-like table.
What I used was a map-like table.
"entire entity", not really, it's a reference, or just a typed number.
Edit: I would have used entity.index as key if there was something like that. I thought there was, but apparently not. It would not be a list since they keys are non-contiguous. But it would be values that can be printed to console.

Lookups in maps are O(1) which means instant while lookups in lists are O(n) if you don't know which index they are at (which you don't), which is slow.
If you are doing deletions somewhat often you want a map, since those have O(1) time deletions and lists have O(n) deletion time. If you are looping through it then both work but a list is better.But not in Lua. In Lua, lists are tables, which are key-value maps anyways. So they are basically maps with integer keys and not continous memory with pointer arithmetic access. So it doesn't matter here and you can use them as maps always basically.