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

Place to get help with not working mods / modding interface.
User avatar
SLywnow
Inserter
Inserter
Posts: 30
Joined: Sat Apr 25, 2020 7:37 pm
Contact:

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

Post 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.

asdff45
Long Handed Inserter
Long Handed Inserter
Posts: 65
Joined: Sun Aug 07, 2016 1:23 pm
Contact:

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

Post 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.

Yoyobuae
Filter Inserter
Filter Inserter
Posts: 504
Joined: Fri Nov 04, 2016 11:04 pm
Contact:

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

Post 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).

User avatar
SLywnow
Inserter
Inserter
Posts: 30
Joined: Sat Apr 25, 2020 7:37 pm
Contact:

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

Post 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

Yoyobuae
Filter Inserter
Filter Inserter
Posts: 504
Joined: Fri Nov 04, 2016 11:04 pm
Contact:

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

Post 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?

User avatar
SLywnow
Inserter
Inserter
Posts: 30
Joined: Sat Apr 25, 2020 7:37 pm
Contact:

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

Post 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",
}

User avatar
SLywnow
Inserter
Inserter
Posts: 30
Joined: Sat Apr 25, 2020 7:37 pm
Contact:

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

Post 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

Yoyobuae
Filter Inserter
Filter Inserter
Posts: 504
Joined: Fri Nov 04, 2016 11:04 pm
Contact:

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

Post 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.

User avatar
SLywnow
Inserter
Inserter
Posts: 30
Joined: Sat Apr 25, 2020 7:37 pm
Contact:

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

Post 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

Yoyobuae
Filter Inserter
Filter Inserter
Posts: 504
Joined: Fri Nov 04, 2016 11:04 pm
Contact:

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

Post 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

Pi-C
Smart Inserter
Smart Inserter
Posts: 1720
Joined: Sun Oct 14, 2018 8:13 am
Contact:

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

Post 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?
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!

Qon
Smart Inserter
Smart Inserter
Posts: 2164
Joined: Thu Mar 17, 2016 6:27 am
Contact:

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

Post 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!
My mods: Capsule Ammo | HandyHands - Automatic handcrafting | ChunkyChunks - Configurable Gridlines
Some other creations: Combinassembly Language GitHub w instructions and link to run it in your browser | 0~drain Laser

Yoyobuae
Filter Inserter
Filter Inserter
Posts: 504
Joined: Fri Nov 04, 2016 11:04 pm
Contact:

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

Post 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.

User avatar
SLywnow
Inserter
Inserter
Posts: 30
Joined: Sat Apr 25, 2020 7:37 pm
Contact:

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

Post 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?

Qon
Smart Inserter
Smart Inserter
Posts: 2164
Joined: Thu Mar 17, 2016 6:27 am
Contact:

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

Post 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.
My mods: Capsule Ammo | HandyHands - Automatic handcrafting | ChunkyChunks - Configurable Gridlines
Some other creations: Combinassembly Language GitHub w instructions and link to run it in your browser | 0~drain Laser

User avatar
SLywnow
Inserter
Inserter
Posts: 30
Joined: Sat Apr 25, 2020 7:37 pm
Contact:

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

Post 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?

Pi-C
Smart Inserter
Smart Inserter
Posts: 1720
Joined: Sun Oct 14, 2018 8:13 am
Contact:

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

Post 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. :-)
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!

User avatar
SLywnow
Inserter
Inserter
Posts: 30
Joined: Sat Apr 25, 2020 7:37 pm
Contact:

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

Post 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

Qon
Smart Inserter
Smart Inserter
Posts: 2164
Joined: Thu Mar 17, 2016 6:27 am
Contact:

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

Post 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.
My mods: Capsule Ammo | HandyHands - Automatic handcrafting | ChunkyChunks - Configurable Gridlines
Some other creations: Combinassembly Language GitHub w instructions and link to run it in your browser | 0~drain Laser

Qon
Smart Inserter
Smart Inserter
Posts: 2164
Joined: Thu Mar 17, 2016 6:27 am
Contact:

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

Post 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.
My mods: Capsule Ammo | HandyHands - Automatic handcrafting | ChunkyChunks - Configurable Gridlines
Some other creations: Combinassembly Language GitHub w instructions and link to run it in your browser | 0~drain Laser

Post Reply

Return to β€œModding help”