[solved] [0.17.43] coding - find all surfaces with players on it

Place to get help with not working mods / modding interface.
AlFara
Burner Inserter
Burner Inserter
Posts: 16
Joined: Sat May 25, 2019 4:14 pm
Contact:

[solved] [0.17.43] coding - find all surfaces with players on it

Post 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?
Last edited by AlFara on Tue May 28, 2019 9:28 pm, edited 1 time in total.
eduran
Filter Inserter
Filter Inserter
Posts: 344
Joined: Fri May 09, 2014 2:52 pm
Contact:

Re: [0.17.43] Question - coding - find all surfaces with players on it

Post 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
Choumiko
Smart Inserter
Smart Inserter
Posts: 1352
Joined: Fri Mar 21, 2014 10:51 pm
Contact:

Re: [0.17.43] Question - coding - find all surfaces with players on it

Post 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
User avatar
eradicator
Smart Inserter
Smart Inserter
Posts: 5207
Joined: Tue Jul 12, 2016 9:03 am
Contact:

Re: [0.17.43] Question - coding - find all surfaces with players on it

Post 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'}
Author of: Belt Planner, Hand Crank Generator, Screenshot Maker, /sudo and more.
Mod support languages: 日本語, Deutsch, English
My code in the post above is dedicated to the public domain under CC0.
AlFara
Burner Inserter
Burner Inserter
Posts: 16
Joined: Sat May 25, 2019 4:14 pm
Contact:

Re: [0.17.43] Question - coding - find all surfaces with players on it

Post 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.
Last edited by AlFara on Sun May 26, 2019 1:54 pm, edited 1 time in total.
User avatar
darkfrei
Smart Inserter
Smart Inserter
Posts: 2905
Joined: Thu Nov 20, 2014 11:11 pm
Contact:

Re: [0.17.43] Question - coding - find all surfaces with players on it

Post 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
User avatar
eradicator
Smart Inserter
Smart Inserter
Posts: 5207
Joined: Tue Jul 12, 2016 9:03 am
Contact:

Re: [0.17.43] Question - coding - find all surfaces with players on it

Post 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)
Author of: Belt Planner, Hand Crank Generator, Screenshot Maker, /sudo and more.
Mod support languages: 日本語, Deutsch, English
My code in the post above is dedicated to the public domain under CC0.
AlFara
Burner Inserter
Burner Inserter
Posts: 16
Joined: Sat May 25, 2019 4:14 pm
Contact:

Re: [0.17.43] Question - coding - find all surfaces with players on it

Post 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
User avatar
darkfrei
Smart Inserter
Smart Inserter
Posts: 2905
Joined: Thu Nov 20, 2014 11:11 pm
Contact:

Re: [0.17.43] Question - coding - find all surfaces with players on it

Post 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
User avatar
eradicator
Smart Inserter
Smart Inserter
Posts: 5207
Joined: Tue Jul 12, 2016 9:03 am
Contact:

Re: [0.17.43] Question - coding - find all surfaces with players on it

Post 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.
Author of: Belt Planner, Hand Crank Generator, Screenshot Maker, /sudo and more.
Mod support languages: 日本語, Deutsch, English
My code in the post above is dedicated to the public domain under CC0.
User avatar
darkfrei
Smart Inserter
Smart Inserter
Posts: 2905
Joined: Thu Nov 20, 2014 11:11 pm
Contact:

Re: [0.17.43] Question - coding - find all surfaces with players on it

Post 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.
AlFara
Burner Inserter
Burner Inserter
Posts: 16
Joined: Sat May 25, 2019 4:14 pm
Contact:

Re: [0.17.43] Question - coding - find all surfaces with players on it

Post 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
User avatar
steinio
Smart Inserter
Smart Inserter
Posts: 2638
Joined: Sat Mar 12, 2016 4:19 pm
Contact:

Re: [0.17.43] Question - coding - find all surfaces with players on it

Post by steinio »

How about an API request for surface.players_count?
Image

Transport Belt Repair Man

View unread Posts
eduran
Filter Inserter
Filter Inserter
Posts: 344
Joined: Fri May 09, 2014 2:52 pm
Contact:

Re: [0.17.43] Question - coding - find all surfaces with players on it

Post 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.
User avatar
eradicator
Smart Inserter
Smart Inserter
Posts: 5207
Joined: Tue Jul 12, 2016 9:03 am
Contact:

Re: [0.17.43] Question - coding - find all surfaces with players on it

Post 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.
Author of: Belt Planner, Hand Crank Generator, Screenshot Maker, /sudo and more.
Mod support languages: 日本語, Deutsch, English
My code in the post above is dedicated to the public domain under CC0.
AlFara
Burner Inserter
Burner Inserter
Posts: 16
Joined: Sat May 25, 2019 4:14 pm
Contact:

Re: [0.17.43] Question - coding - find all surfaces with players on it

Post 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?
User avatar
eradicator
Smart Inserter
Smart Inserter
Posts: 5207
Joined: Tue Jul 12, 2016 9:03 am
Contact:

Re: [0.17.43] Question - coding - find all surfaces with players on it

Post 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.
Author of: Belt Planner, Hand Crank Generator, Screenshot Maker, /sudo and more.
Mod support languages: 日本語, Deutsch, English
My code in the post above is dedicated to the public domain under CC0.
Qon
Smart Inserter
Smart Inserter
Posts: 2164
Joined: Thu Mar 17, 2016 6:27 am
Contact:

Re: [0.17.43] Question - coding - find all surfaces with players on it

Post 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.
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
eradicator
Smart Inserter
Smart Inserter
Posts: 5207
Joined: Tue Jul 12, 2016 9:03 am
Contact:

Re: [0.17.43] Question - coding - find all surfaces with players on it

Post 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.
Author of: Belt Planner, Hand Crank Generator, Screenshot Maker, /sudo and more.
Mod support languages: 日本語, Deutsch, English
My code in the post above is dedicated to the public domain under CC0.
eduran
Filter Inserter
Filter Inserter
Posts: 344
Joined: Fri May 09, 2014 2:52 pm
Contact:

Re: [0.17.43] Question - coding - find all surfaces with players on it

Post 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 :D)
+1 :D
Post Reply

Return to “Modding help”