Page 1 of 2
[solved] [0.17.43] coding - find all surfaces with players on it
Posted: Sat May 25, 2019 4:55 pm
by AlFara
-edit-
added solved; better way-check array of players for their surfaces instead.
previous short discussion happened on steam forums "steamcommunity.com/app/427520/discussions/1/1651045226224021692/"
As stated in the title, i'm looking for an option to
1) check all existing surfaces for active players and if it has at least 1 player on it, it gets added to a list
2) pick up a random surface from the list
3) save that surface to a variable
general ideas:
-number 3 is done done via "local surface = game.surfaces[index]" while index is a number.
-number 2 could be potentially done with sth like "local generator = game.create_random_generator()" followed by sth like "generator(1,n+0.99)" (n is the number of different surfaces with players on it, or more precisely, the size of the resulting array of number 1)
-number 1 is the problematic one, the idea looks like this:
Code: Select all
L = empty List
k = 1
while(game.surfaces[k] != nil){
if(curr_surface.has_active_player){
L.add = curr_surface (or do stuff on the current surface the player is on directly)
}
k += 1
}
current code:
Code: Select all
1: local surfaces = {};
2: for i, surface in ipairs(game.surfaces[i]) do
3: local entities = surface.find_entities_filtered{force = "player", type="character"};
4: if (entities ~= nil) then
5: local inner_surface = game.surfaces[i]
6: game.player.print(inner_surface.name)
7: --do_more_stuff_here
8: end
9: end
10: end
Problems:
4-never enters the IF, probably caused by "2"
2-fel stated on steam that "for i, surface in ipairs(game.surfaces) do" is empty and my line with surfaces['i'] doesn't work either.
does anyone knows how to solve that issue?
Re: [0.17.43] Question - coding - find all surfaces with players on it
Posted: Sat May 25, 2019 6:09 pm
by eduran
Use pairs() unstead if ipairs().
Why? See
https://lua-api.factorio.com/latest/LuaCustomTable.html and
https://lua-api.factorio.com/latest/Libraries.html
Code: Select all
for _, surface in pairs(game.surfaces) do
if surface.find_entities_filtered{force = "player", type="character"} then
game.player.print(surface)
--do_more_stuff_here
end
end
Re: [0.17.43] Question - coding - find all surfaces with players on it
Posted: Sat May 25, 2019 6:15 pm
by Choumiko
Code: Select all
local surfaces = {}
for i, player in pairs(game.players) do
if player.connected then--only online players
surfaces[player.surface.name] = player.surface
end
end
That's probably how i would do it. Get's all surfaces for online players (whether or not they have a character, they could be respawning, god controller, spectating.)
Edit: Searching for entities of type character might also get you non player characters from other mods that use them for some trickery or whatever
Re: [0.17.43] Question - coding - find all surfaces with players on it
Posted: Sat May 25, 2019 6:32 pm
by eradicator
As choumiko rightly pointed out, iterating surfaces is pointless because players are not guaranteed to have a character. Also a game can potentially have much more surfaces than players, so in a worst case scenario iterating players is also better.
Here's a function that directly gets you the surface. It's biased towards surfaces with more players. Not sure if that is desirable in your case.
Code: Select all
local function get_me_a_random_surface_with_players_on_it()
local surfaces = {}
for _,player in pairs(game.connected_players) do
surfaces[#surfaces+1] = player.surface
end
return surfaces[math.random(#surfaces)]
end
@eduran: btw, when surface scanning is an option the fasted method to detect "are there *any* of them at all" is surface.count_entities_filtered{limit=1,type='whatever'}
Re: [0.17.43] Question - coding - find all surfaces with players on it
Posted: Sat May 25, 2019 7:56 pm
by AlFara
thank you very much for the informations and the large amount of code provided by both of you^^
i'll try the code later today on when i got the time and report back (or edit this post) if it works or not.
edit: both worked, thy vm
"[...]non player characters from other mods[...]"
didn't think about that option at all.
"It's biased towards surfaces with more players. Not sure if that is desirable in your case."
shouldn't hurt because more players on a surface will create more clutter.
Re: [0.17.43] Question - coding - find all surfaces with players on it
Posted: Sat May 25, 2019 10:45 pm
by darkfrei
Code: Select all
function is_value_in_list (value, list)
for i, v in pairs (list) do
if value == v then
return true
end
end
return false
end
Code: Select all
function get_me_a_random_surface_name_with_players_on_it()
local surface_names_list = {}
for player_index, player in pairs(game.connected_players) do
if not is_value_in_list (player.surface.name, surface_names_list) then
table.insert (surface_names_list, player.surface.name)
end
end
return surface_names_list[math.random(#surface_names_list)]
end
Re: [0.17.43] Question - coding - find all surfaces with players on it
Posted: Sun May 26, 2019 7:58 am
by eradicator
AlFara wrote: Sat May 25, 2019 7:56 pm
"It's biased towards surfaces with more players. Not sure if that is desirable in your case."
shouldn't hurt because more players on a surface will create more clutter.
If you don't mind bias towards player count then there is a much faster solution: just select a random player.
Code: Select all
local random_surface = game.connected_players[math.random(#game.connected_players)].surface
The proper
unbiased version would be something like this:
Code: Select all
local function get_me_a_random_surface_with_players_on_it()
local indexes = {}
for _,player in pairs(game.connected_players) do
indexes[player.surface.index] = true
end
local surfaces = {}
for index,_ in pairs(indexes) do
surfaces[#surfaces+1] = index
end
return game.surfaces[surfaces[math.random(#surfaces)]]
end
Note that @darkfrei's version loops through *all* previous surfaces for *each* player, while this loops only once through players and then once through surfaces. (
https://en.wikipedia.org/wiki/Set_theory)
Re: [0.17.43] Question - coding - find all surfaces with players on it
Posted: Sun May 26, 2019 3:40 pm
by AlFara
Thanks for the updates from both of u. I'll mostly end up using both variants (bias wise) in different situations, depending on which one makes more sense for each case.
"just select a random player"
i didn't check that case for the previous code from yesterday but wouldn't it be generally nessesary to do something like this:
Code: Select all
if (#game.connected_players >= 1) then
local random_surface = game.connected_players[math.random(#game.connected_players)].surface
else
local random_surface = game.surfaces['nauvis']
end
so i don't pass a 0 to math.random when the world would run without players online (e.g. on servers or mb with some mods)? or is that case not possible at all?
less imporant fact: also, it appears that "find_entities_filtered{type = "character"}" doesn't consider characters in vehicles, so sth like the following code should do the trick (obviously extend the filter, "car" is just my testing value)
Code: Select all
local valid_vehicles = event_surface.find_entities_filtered{type = "car"}
for _, vehicle in pairs(valid_vehicles) do
if (vehicle.get_driver() ~= nil) then
game.print(vehicle.get_driver().position.x)
game.print(vehicle.get_driver().position.y)
end
end
for _, vehicle in pairs(valid_vehicles) do
if (vehicle.get_passenger() ~= nil) then
game.print(vehicle.get_passenger().position.x)
game.print(vehicle.get_passenger().position.y)
end
end
Re: [0.17.43] Question - coding - find all surfaces with players on it
Posted: Sun May 26, 2019 7:28 pm
by darkfrei
Here is another example of intersecting calculation, it works just as handler[x][y], it executes the code only if it was the new element y in the x.
Code: Select all
function make_handlers ()
local force_surface_intersection = {}
local handlers = {}
for player_index, player in pairs(game.connected_players) do
local force_name = player.force.name
if not force_surface_intersection[force_name] then
force_surface_intersection[force_name] = {}
end
local surface_name = player.surface.name
if not force_surface_intersection[force_name][surface_name] then
-- first intersection of force and surface
local force = game.forces[force_name]
local surface = game.surfaces[surface_name]
for chunk in surface.get_chunks() do
local position = {x = chunk.x, y = chunk.y}
table.insert (handlers, {force=force, position=position, surface=surface})
end
end
end
return handlers
end
This code is from mod
Black Map
Re: [0.17.43] Question - coding - find all surfaces with players on it
Posted: Mon May 27, 2019 7:59 am
by eradicator
AlFara wrote: Sun May 26, 2019 3:40 pm
so i don't pass a 0 to math.random when the world would run without players online (e.g. on servers or mb with some mods)? or is that case not possible at all?
game.connected_players can be empty, game.players ...i guess could theoretically be empty too, but that'd mean someone used commands to actively delete the player data of everyone. Far less likely. but yea, like the recent FFF said "insert 0 checks" :p.
AlFara wrote: Sun May 26, 2019 3:40 pm
less imporant fact: also, it appears that "find_entities_filtered{type = "character"}" doesn't consider characters in vehicles, so sth like the following code should do the trick (obviously extend the filter, "car" is just my testing value)
Calling find_entities on *the entire surface* is a very expensive operation and should be carefully used if you care about performance at all. But without you telling us *why* you need this "give me a random surface" i can't really give you any more advice as it's all too situational. Also do consider that cars - like characters, and everything else - are frequently used by mods to do things that you don't expect.
Re: [0.17.43] Question - coding - find all surfaces with players on it
Posted: Mon May 27, 2019 10:19 am
by darkfrei
So find all characters is much cheaper:
Code: Select all
local characters = {}
for player_index, player in pairs (game.players) do
if player.character then
table.insert (characters, player.character)
end
end
Some players can have no character at all.
Re: [0.17.43] Question - coding - find all surfaces with players on it
Posted: Mon May 27, 2019 2:56 pm
by AlFara
thanks for the updates, i'll take a look into it.
@eradicator - so you're saying i should rather do the checks force-wise (and mb exclude neutral and enemy)?
about your surface question: i need that surface because i need events to happen on that surface over time and those shall happen on that specific surface.
about the car thing: let's say i want to spawn and fire a projectile at the player. this will happen in 2 ways:
1) next to the player in a specific distance IF the player is not inside a vehicle/building:
Code: Select all
event_surface.create_entity{name = "explosive-artillery-projectile", target={target_player.position.x, target_player.position.y}, speed=1, position = {target_player.position.x+50, target_player.position.y+50}, force = game.forces.enemy}
2) similar to 1: next to a player inside a vehicle/building/whatever but with including current movement of that vehicle
Code: Select all
event_surface.create_entity{name = "explosive-artillery-projectile", target={projectile_vehicle_x_final, projectile_vehicle_y_final}, speed=modified_speed, position = {target_player.position.x+50, target_player.position.y+50}, force = game.forces.enemy}
the calculation of that is stuff is already finished and works properly, so that is fine.
that's why i need to find the cars/... with players inside (either as passenger or as driver). is there any more effective way to check it other than the following code?
Code: Select all
for curr_force, force in pairs (game.forces) do
if (curr_force.name ~= 'enemy' and curr_force.name ~= 'neutral)
event_surface.find_entities_filtered{force = curr_force.name, type = {"car", "locomotive", "cargo-wagon", "artillery-wagon"}}
end
end
Re: [0.17.43] Question - coding - find all surfaces with players on it
Posted: Mon May 27, 2019 3:12 pm
by steinio
How about an API request for surface.players_count?
Re: [0.17.43] Question - coding - find all surfaces with players on it
Posted: Mon May 27, 2019 3:41 pm
by eduran
AlFara wrote: Mon May 27, 2019 2:56 pm
is there any more effective way to check it other than the following code?
Select the target player in whichever way you like and check the
vehicle property of the player.
Re: [0.17.43] Question - coding - find all surfaces with players on it
Posted: Mon May 27, 2019 7:11 pm
by eradicator
AlFara wrote: Mon May 27, 2019 2:56 pm
@eradicator - so you're saying i should rather do the checks force-wise (and mb exclude neutral and enemy)?
about your surface question: i need that surface because i need events to happen on that surface over time and those shall happen on that specific surface.
I'm saying that the more details we know the better advice we can give. From the bits you posted so far it seems that you're approaching from the wrong direction. You want to do something to a player, yet you search through surfaces, cars, etc, instead of starting with the player. As @eduran already said you can simply read the vehicle a player is in from the LuaPlayer. The LuaPlayer also always has the current position, regardless of it the player is in a car or not.
Re: [0.17.43] Question - coding - find all surfaces with players on it
Posted: Mon May 27, 2019 11:45 pm
by AlFara
idk how i could miss the info provided by eduran when checking the documentation myself.
the car part now looks like this:
Code: Select all
local characters = {}
for player_index, player in pairs (game.connected_players) do
if (player.character) then
table.insert (characters, player.character)
end
end
--code above called once
[...]
--code below called multiple times (over time)
for _, character in pairs(characters) do
if (character.vehicle ~= nil) then
--do vehicle stuff (r/w vehicle values) here
end
end
i got rid off most other surface-requests now, except a few (nessesary) ones and this one case, where i tried to get a collision check running.
(basically: check if the coordinate (not tile) in a certain direction (located right next to the player) every tick for a while)
Code: Select all
--every tick:
if (timer < timer_limit) then
timer = timer +1
--transport belt for checking tile occupation by 'flat' entities and character for checking the
if (event_surface.can_place_entity({ name="transport-belt", position={target_player.position.x + x_change*0.1*intensity, target_player.position.y +
y_change*0.1*intensity}, force="player"}) or event_surface.can_place_entity({ name="character", position={target_player.position.x + x_change*0.5*intensity,
target_player.position.y + y_change*0.5*intensity}, force="player"})) then
target_player.teleport({
x = target_player.position.x + x_change*0.1*intensity,
y = target_player.position.y + y_change*0.1*intensity
})
end
end
the idea is to use
count_entites_filtered with "collision_mask" and "position" to get the big if-clause out of the way, is that the right approach or is there a better(cheaper) way to do that?
Re: [0.17.43] Question - coding - find all surfaces with players on it
Posted: Tue May 28, 2019 7:13 am
by eradicator
AlFara wrote: Mon May 27, 2019 11:45 pm
Code: Select all
--code below called multiple times (over time)
for _, character in pairs(characters) do
You're iterating over a static list of outdated character references without even checking them for .valid==true. By the time you get there the player might have died and gotten a new character, might have disconnected changed controller type, etcpp. You have to iterate over a more reliable source. game.players is always constant so it's a good place to start, but spreading stuff over several ticks is always difficult. Here's an attempt to find the next connected player each tick:
Code: Select all
script.on_event(defines.events.on_tick,function(event)
local player_index = global.last_player_index or 1
local surface = game.surfaces.nauvis --fallback value
local player
local start = player_index
while #game.connected_players > 0 do
player_index = player_index + 1
if player_index > #game.players then player_index = 1 end
player = game.players[player_index]
if player and player.connected and player.character then
surface = player.surface
break
end
if player_index == start then break end --more than one cycle per tick wouldn't do anything
end
global.last_player_index = player_index
--you now have a surface guaranteed
--and a player if there are any connected
end)
AlFara wrote: Mon May 27, 2019 11:45 pm
the idea is to use count_entites_filtered with "collision_mask" and "position" to get the big if-clause out of the way, is that the right approach or is there a better(cheaper) way to do that?
With a limited area/position find/count_entities_filtered should be just fine. It's just the "whole surface" scans you were doing before that i found too expensive. You haven't said what the condition is (and i'm not going to try and guess from code bits ^^), so i can't tell you if that's a good/better way.
Re: [0.17.43] Question - coding - find all surfaces with players on it
Posted: Tue May 28, 2019 11:28 am
by Qon
Making a great mod in secret without spoilers and then make a big reveal that will awe the community is a right reserved for those of us that have the knowledge and skill to make something by ourselves without help from others.
Those that need help and ask for it owe an explanation of what the mod does to those that have the knowledge and skill to help you. Trying to make us work for you without revealing the purpose is a cruel joke you play on us, the community. Some have fallen in your trap because they are very good people that want to help. They want to help regardless of the suffering they have to endure when trying to give you what you want without proper directions of what you actually want. I'm writing this with the hope that you are doing evil out of ignorance and not spite, to maybe make you realise the errors your way and find the path of righteous collaboration.
"But I don't want anyone to steal my idea!"
Ideas can't be stolen, only shared together. If you want the best for the community then you should share your idea with those more capable. If someone does try to make your idea then that is a compliment to you as the inventor of the original mod idea that you should take pride in. If you release a mod then someone can make a better mod after you have released it anyway, and you can download their source code to learn how to improve your own version. But rarely is someone else more passionate about your idea than yourself. We have our own mod ideas already that we want to make instead. You probably think too highly of your own idea if you think that people will abandon their own ideas to make your mod and release it under their own name before you can. And nothing can prevent you from being able to release your "true" version yourself.
Re: [0.17.43] Question - coding - find all surfaces with players on it
Posted: Tue May 28, 2019 12:08 pm
by eradicator
Qon wrote: Tue May 28, 2019 11:28 am
Making a great mod in secret without spoilers and then make a big reveal that will awe the community is a right reserved for those of us that have the knowledge and skill to make something by ourselves without help from others
Haha! Pretty much right. Except i think this particular @OP isn't doing it out of evilness. If the postcount is any indicator at all. I've seen people far more aggressively hiding their ideas. And they were mostly lousy coders ;). I don't help them because i want something back, i do it because i feel good when i can brag about my awesome l33t coding skillz® and tell other people they're wrong :p. (And then hope nobody who's
actually good at coding comes along and tells me how shitty my code is :D)
Qon wrote: Tue May 28, 2019 11:28 am
But rarely is someone else more passionate about your idea than yourself. We have our own mod ideas already that we want to make instead. You probably think too highly of your own idea if you think that people will abandon their own ideas to make your mod and release it under their own name before you can.
And i love hiding my own ideas too, which is why i can totally understand why other people do it. Look at the portal. There's two mods by me because the other stuff "isn't ready" yet :p. I doubt that anyone would copy my ideas even *if* they were awesome. Because - as you said - making an idea into a working mod is a huge amount of work that most people would rather invest into their own ideas. And in any case abandoned modding help threads are not the preferred fishing grounds of Idea Buccaneers™.
Back to topic. Harr.
Re: [0.17.43] Question - coding - find all surfaces with players on it
Posted: Tue May 28, 2019 12:15 pm
by eduran
Qon wrote: Tue May 28, 2019 11:28 am
Those that need help and ask for it owe an explanation of what the mod does to those that have the knowledge and skill to help you. Trying to make us work for you without revealing the purpose is a cruel joke you play on us, the community. Some have fallen in your trap because they are very good people that want to help. They want to help regardless of the suffering they have to endure when trying to give you what you want without proper directions of what you actually want. I'm writing this with the hope that you are doing evil out of ignorance and not spite, to maybe make you realise the errors your way and find the path of righteous collaboration.
I certainly did not fall into a trap and I don't feel entitled to know any details about what someone is trying to do (and frankly, don't much care either in most cases). Working on Factorio mods is a fun hobby for me and being able to share advice is part of that. I don't expect anything in return. As far as I am concerned: feel free to ask questions in the way that it was done here.
Edit:
eradicator wrote: Tue May 28, 2019 12:08 pm
I don't help them because i want something back, i do it because i feel good when i can brag about my awesome l33t coding skillz® and tell other people they're wrong :p. (And then hope nobody who's
actually good at coding comes along and tells me how shitty my code is
![Very Happy :D](./images/smilies/icon_e_biggrin.gif)
)
+1
![Very Happy :D](./images/smilies/icon_e_biggrin.gif)