Question about the global table

Place to get help with not working mods / modding interface.
Post Reply
User avatar
spiro9
Inserter
Inserter
Posts: 23
Joined: Tue Jul 12, 2016 12:15 am
Contact:

Question about the global table

Post by spiro9 »

I'm working with a new mod project (with no prior Factorio modding experience) and I'm trying to set up something where a pseudo-infinite upgrade technology could possibly increment an integer value to track how many times that technology has been researched, assuming that's necessary.

I'm not 100% sure if that is needed, but basically what I want to do is check how many times that technology has been researched so I can call it from a function to determine, say, the maximum rotational speed of an inserter, based on settings defined in the mod's settings and the progress the player has made in-game.

If that doesn't need a call of the global table, then that's fine, though I would still like to know how I would make such a call for future reference.

If more information is needed, so as to provide better input, I'm willing to give more thorough explanation.

Thank you!
Hi hungry, I'm dad!

User avatar
darkfrei
Smart Inserter
Smart Inserter
Posts: 2903
Joined: Thu Nov 20, 2014 11:11 pm
Contact:

Re: Question about the global table

Post by darkfrei »

See code of https://mods.factorio.com/mod/RocketExplosions

Code: Select all

function get_tech_level (force, tech_name)
    local level = 0
    local techs = force.technologies
    
    local tech = techs[tech_name] or techs[tech_name..'-1']
    
    if tech and tech.researched then
        level = tech.level
        local i = 1
        local fl = true
        while fl do
            i=i+1
            local tech = techs[tech_name..'-'..i]
            if tech and tech.researched then
                --game.print ('ok: i: '..i)
                level=tech.level
            elseif tech then
                -- level 7 or not researched
                fl = false
                
                if not (techs[tech_name..'-'..(i+1)]) then
                    -- there is no level 8 at all
                    level=tech.level
                end
            else
                fl = false
            end
        end
    else
        --game.print ('not researched')
        return 0
    end
    return level
end

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

Re: Question about the global table

Post by Pi-C »

spiro9 wrote:
Mon Apr 25, 2022 2:29 am
I'm working with a new mod project (with no prior Factorio modding experience) and I'm trying to set up something where a pseudo-infinite upgrade technology could possibly increment an integer value to track how many times that technology has been researched, assuming that's necessary.
Why don't you define an infinite-tech prototype? You'll need to find a formula for the unit count (how many science packs are required per level) and set technology.max_level = "infinite".
I'm not 100% sure if that is needed, but basically what I want to do is check how many times that technology has been researched so I can call it from a function to determine, say, the maximum rotational speed of an inserter, based on settings defined in the mod's settings and the progress the player has made in-game.
Listen to on_research_finished:

Code: Select all

script.on_event(defines.events.on_research_finished, function(event)
	local tech = event.research
	local level = tech.level
end)
If that doesn't need a call of the global table, then that's fine, though I would still like to know how I would make such a call for future reference.
The global table is something that belongs to your mod. It will be accessible in script.on_init, script.on_configuration_changed, migration scripts, and during normal running of the game, but NOT in script.on_load.

You should put things in there that can't be easily restored when you load a game: e.g. who built a particular entity, or what was the last car a player was riding in. Also, you should think about how to structure your data before storing anything in the global table. Obviously, as your mod evolves your needs will change, but it's always harder to reorganize your data later on than doing it right immediately.

As an example, let's say you have a specific tech and want prevent players from using cheats to research it. So you could do this:

Code: Select all

script.on_init(function()
	global.forces = global.forces or {}
end)

script.on_event(defines.events.on_research_finished, function(event)
	local tech = event.research
	local cheated = event.by_script
	
	local level = tech.level
	local force = tech.force
	
	global.forces[force.name] = global.forces[force.name] or {}
	
	-- Add new level of researched tech to global table
	if not cheated then 
		global.forces[force.name][tech.name] = tech.level

	-- Cheat detected! Some level of the tech has been researched 
	-- honestly, reset the tech to that level.
	elseif global.forces[force.name][tech.name] then
		tech.level = global.forces[force.name][tech.name]
		
	-- Cheat detected! No level of the tech has ever been researched
	-- by that force, revert the research completely.
	else
		tech.researched = false
	end
	
end)
Hope that helps! :-)
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!

robot256
Filter Inserter
Filter Inserter
Posts: 596
Joined: Sun Mar 17, 2019 1:52 am
Contact:

Re: Question about the global table

Post by robot256 »

Pi-C wrote:The global table is something that belongs to your mod. It will be accessible in script.on_init, script.on_configuration_changed, migration scripts, and during normal running of the game, but NOT in script.on_load.
To clarify, `global` is read-only during `script.on_load`. It is to be used only to restore event subscription states and Lua metatables.

FuryoftheStars
Smart Inserter
Smart Inserter
Posts: 2530
Joined: Tue Apr 25, 2017 2:01 pm
Contact:

Re: Question about the global table

Post by FuryoftheStars »

Iirc, too (and somebody please correct me if I'm wrong), each mod's instance of "global" is for that mod only. So there's no worry about needing to use unique keys to avoid mod conflict (though that also means another mod can't access any values you store there).
My Mods: Classic Factorio Basic Oil Processing | Sulfur Production from Oils | Wood to Oil Processing | Infinite Resources - Normal Yield | Tree Saplings (Redux) | Alien Biomes Tweaked | Restrictions on Artificial Tiles

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

Re: Question about the global table

Post by Pi-C »

robot256 wrote:
Mon Apr 25, 2022 1:01 pm
To clarify, `global` is read-only during `script.on_load`. It is to be used only to restore event subscription states and Lua metatables.
Thanks, you're right! I've confused 'game' and 'global' here: 'game' can't be accessed at all in on_load, 'global' can only be read.

@ spiro9: Also 'global' can't be used outside of event handlers. If control.lua is loaded, 'global' will be empty, and if you make any changes to it, they will be overwritten once the file has been loaded completely. You definitely should read about the data lifecycle.
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!

User avatar
spiro9
Inserter
Inserter
Posts: 23
Joined: Tue Jul 12, 2016 12:15 am
Contact:

Re: Questions about the global table & tech calls

Post by spiro9 »

darkfrei wrote:
Mon Apr 25, 2022 9:29 am
See code of https://mods.factorio.com/mod/RocketExplosions

Code: Select all

function get_tech_level (force, tech_name)
    local level = 0
    local techs = force.technologies
    
    local tech = techs[tech_name] or techs[tech_name..'-1']
    
    if tech and tech.researched then
        level = tech.level
        local i = 1
        local fl = true
        while fl do
            i=i+1
            local tech = techs[tech_name..'-'..i]
            if tech and tech.researched then
                --game.print ('ok: i: '..i)
                level=tech.level
            elseif tech then
                -- level 7 or not researched
                fl = false
                
                if not (techs[tech_name..'-'..(i+1)]) then
                    -- there is no level 8 at all
                    level=tech.level
                end
            else
                fl = false
            end
        end
    else
        --game.print ('not researched')
        return 0
    end
    return level
end
If I'm calling this from, say, clicking on an inserter to change certain attributes on the fly, what would be the best way to do so and return the level value as something I can pass into a formula built from user-set mod settings?

So like, if I've got a single technology that has a maximum level set (based on a mod's settings, which I've already coded, see below code block), how would I work with that? Passing mod settings into variables should be easy enough, I figure.

Keep in mind I'm primarily referencing and recycling the code from the Inserter Configuration mod, which only seems to have calls for checking an inserter prototype, a functionality that isn't like what I'm trying to accomplish, which is simply stated, a more configurable take on what Bob's Adjustable Inserters does, since that mod has hard-coded limitations I'd like a work-around to. I note that in reading the code used there I see a lot of 'force' calls related to technology, similar to what you sent. I'm not sure how I'd grab that.

Code: Select all

-- Based on Bob's Adjustable Inserters technology format as reference.

data:extend(
{
  {
    type = "technology",
    name = "spiro9_inserter_reach",
    icon = "__spiros_inserters__/graphics/tech-icons/reach.png",
    icon_size = 128,
    effects =
    {
    },
    prerequisites =
    {
      "logistics",
    },
    unit =
    {
      count_formula = "((10*L)^2)/5",
      ingredients =
      {
        {"automation-science-pack", 1},
      },
      time = 5
    },
	max_level = settings.startup["s9:inserter-reach-techs"].value,
    order = "spiro9_inserter_reach_1",
	
	upgrade = true,
  },
}
)
The current relevant code block, if it helps.

Code: Select all

--[[ Commented out as reference to replace it with custom functionality.
function inserter_utils.get_max_range(inserter)
	local prototype = inserter
	if prototype.object_name == "LuaEntity" then
		prototype = inserter_utils.get_prototype(prototype)
	end

	local pickup_pos = math2d.position.tilepos(math2d.position.add(prototype.inserter_pickup_position, {0.5, 0.5}))
	local drop_pos = math2d.position.tilepos(math2d.position.add(prototype.inserter_drop_position, {0.5, 0.5}))
	return math.max(math.abs(pickup_pos.x), math.abs(pickup_pos.y), math.abs(drop_pos.x), math.abs(drop_pos.y))
end ]]

function inserter_utils.get_max_range(inserter)
	-- The original function appears to grab the maximum range from the inserter prototype. This is not desired behavior.
	-- Change this to follow the variables set by the user!
	
	-- function: max range = base + inserter reach setting + (reach incrementation setting * reach tech level), where base is 2
	local setting_irbase = settings.startup["s9:inserter-reach"].value
	local reach_increment_final = settings.startup["s9:inserter-reach-inc"].value * 
	return ( 2 + setting_irbase + reach_increment_final )
end
Hi hungry, I'm dad!

User avatar
spiro9
Inserter
Inserter
Posts: 23
Joined: Tue Jul 12, 2016 12:15 am
Contact:

Re: Question about the global table

Post by spiro9 »

Pi-C wrote:
Mon Apr 25, 2022 10:03 am
The global table is something that belongs to your mod. It will be accessible in script.on_init, script.on_configuration_changed, migration scripts, and during normal running of the game, but NOT in script.on_load.

You should put things in there that can't be easily restored when you load a game: e.g. who built a particular entity, or what was the last car a player was riding in. Also, you should think about how to structure your data before storing anything in the global table. Obviously, as your mod evolves your needs will change, but it's always harder to reorganize your data later on than doing it right immediately.

As an example, let's say you have a specific tech and want prevent players from using cheats to research it. So you could do this:

Code: Select all

script.on_init(function()
	global.forces = global.forces or {}
end)

script.on_event(defines.events.on_research_finished, function(event)
	local tech = event.research
	local cheated = event.by_script
	
	local level = tech.level
	local force = tech.force
	
	global.forces[force.name] = global.forces[force.name] or {}
	
	-- Add new level of researched tech to global table
	if not cheated then 
		global.forces[force.name][tech.name] = tech.level

	-- Cheat detected! Some level of the tech has been researched 
	-- honestly, reset the tech to that level.
	elseif global.forces[force.name][tech.name] then
		tech.level = global.forces[force.name][tech.name]
		
	-- Cheat detected! No level of the tech has ever been researched
	-- by that force, revert the research completely.
	else
		tech.researched = false
	end
	
end)
Hope that helps! :-)
I'm looking more into this and I think I've found my answer in the *game* table, for what it is I'm looking to do. The event that calls the function I'm rewriting is on_entity_settings_pasted, which has a player_index value.

What I need to know now is whether I can safely call something like local player_obj = game.players[player_index] or whether that might prevent saving like the docs warn about.
Hi hungry, I'm dad!

User avatar
spiro9
Inserter
Inserter
Posts: 23
Joined: Tue Jul 12, 2016 12:15 am
Contact:

Re: Question about the global table

Post by spiro9 »

darkfrei wrote:
Mon Apr 25, 2022 9:29 am
See code of https://mods.factorio.com/mod/RocketExplosions

Code: Select all

function get_tech_level (force, tech_name)
    local level = 0
    local techs = force.technologies
    
    local tech = techs[tech_name] or techs[tech_name..'-1']
    
    if tech and tech.researched then
        level = tech.level
        local i = 1
        local fl = true
        while fl do
            i=i+1
            local tech = techs[tech_name..'-'..i]
            if tech and tech.researched then
                --game.print ('ok: i: '..i)
                level=tech.level
            elseif tech then
                -- level 7 or not researched
                fl = false
                
                if not (techs[tech_name..'-'..(i+1)]) then
                    -- there is no level 8 at all
                    level=tech.level
                end
            else
                fl = false
            end
        end
    else
        --game.print ('not researched')
        return 0
    end
    return level
end
Is it okay if I recycle this code with credit? This is my current code, if that's not the case - perhaps you might know how to retool it to have the functionality I need.

Code: Select all

function inserter_utils.get_max_range(inserter, this_force)
	-- The original function appears to grab the maximum range from the inserter prototype. This is not desired behavior.
	-- Change this to follow the variables set by the user!
	
	-- Initialize a variable of the relevant technology!
	local reachtech = this_force.technologies["spiro9_inserter_reach"]
	
	-- function: max range = base + inserter reach setting + (reach incrementation setting * reach tech level), where base is 2
	local setting_irbase = settings.startup["s9:inserter-reach"].value
	local reach_increment_final = (settings.startup["s9:inserter-reach-inc"].value * reachtech.level)
	return ( 2 + setting_irbase + reach_increment_final )
end
Hi hungry, I'm dad!

User avatar
darkfrei
Smart Inserter
Smart Inserter
Posts: 2903
Joined: Thu Nov 20, 2014 11:11 pm
Contact:

Re: Question about the global table

Post by darkfrei »

spiro9 wrote:
Wed Apr 27, 2022 4:33 am


Is it okay if I recycle this code with credit? This is my current code, if that's not the case - perhaps you might know how to retool it to have the functionality I need.

Code: Select all

function inserter_utils.get_max_range(inserter, this_force)
	-- The original function appears to grab the maximum range from the inserter prototype. This is not desired behavior.
	-- Change this to follow the variables set by the user!
	
	-- Initialize a variable of the relevant technology!
	local reachtech = this_force.technologies["spiro9_inserter_reach"]
	
	-- function: max range = base + inserter reach setting + (reach incrementation setting * reach tech level), where base is 2
	local setting_irbase = settings.startup["s9:inserter-reach"].value
	local reach_increment_final = (settings.startup["s9:inserter-reach-inc"].value * reachtech.level)
	return ( 2 + setting_irbase + reach_increment_final )
end
Yes, you can make what you want without any limitations :)
The unlimited technology (1-7 was limited and from 8 is unlimited) level is not so easy as it must be.

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

Re: Question about the global table

Post by Pi-C »

spiro9 wrote:
Wed Apr 27, 2022 1:25 am
What I need to know now is whether I can safely call something like local player_obj = game.players[player_index] or whether that might prevent saving like the docs warn about.
It's OK to use that in a local definition. You can also use

Code: Select all

local entity = player.surface.create_entity{name = "car", position = player.position}
global.entities = global.entities or {}
global.entities[entity.unit_number] = entity
What's illegal is using an entity as an index:

Code: Select all

local entity = player.surface.create_entity{name = "car", position = player.position}
global.entities = global.entities or {}
global.entities[entity] = entity.unit_number
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!

Post Reply

Return to “Modding help”