Deadlock989 / IndustrialRevolution

Industrial Revolution 3 is an overhaul mod for Factorio.
51 stars 0 forks source link

Remote interface for Inspiration #445

Closed StephenBarnes closed 3 months ago

StephenBarnes commented 4 months ago

Describe the issue I wanted to ask for a remote interface for IR3: Inspiration, because I'm working on a modpack and I want to add/change unlocks for the technologies. I think the way to do this is to add a remote interface to set entries in your mod's globals.technologies table.

I've briefly tested some code that does this, though it might not be the best design. In your control.lua:

remote.add_interface("ir3-inspiration", {
    ["set_technologies"] = function(tech_changes)
        for tech_name,data in pairs(tech_changes) do
            g.technologies[tech_name] = data
        end
        reset_inspiration_data()
    end,
    ["get_technology"] = function(name)
        return g.technologies[name]
    end,
})

Calling it:

script.on_init(function()
    local woodTech = remote.call("ir3-inspiration", "get_technology", "ir-basic-wood")
    woodTech.IR_schematic_count_needed = 2
    remote.call("ir3-inspiration", "set_technologies", {["ir-basic-wood"] = woodTech})
end)

This doesn't update the amount shown in the tech tree or item descriptions. So mods that use the remote interface will have to also edit/create the item and tech prototypes in the data stage.

Factorio version, IR version N/A

Other mods installed N/A

Deadlock989 commented 4 months ago

I'm happy to do this in principle, but there is an issue with the way you have approached it above. Mutable data stored in global variables must be stored in the special global variable, or it will cause desyncs in multiplayer. The g global in Inspirations was only intended to be a table of constants, which is deterministic. It will potentially cause desync issues if it is changed during runtime.

I will take a look at this later in the week. Options are rewriting Inspirations slightly to store the inspiration tech data in global, or by using some property or other of the prototype to store the necessary values instead (which might kill two birds with one stone but off the top of my head, I'm not sure there are any candidate properties).

StephenBarnes commented 4 months ago

The Inspiration mod already sort of puts the information (item and amount needed for inspiration) inside the technology.unit.ingredients. So one option is, in control stage, look through all tech prototypes for units with names like "ir-inspiration-copper-ingot" and then look for an item or fluid named "copper-ingot". Then in the data stage, other mods could create new items with names like "ir-inspiration-plutonium" and set them as the unit for a tech, and in the control stage your Inspiration mod would see that and trigger an inspiration when an item called "plutonium" is obtained.

Or you could have a global DIR.technology_inspirations defined in data, then allow other mods to change that global later in the data stage. Then in data-updates you look through that and create icons and items. You would have to send that info into the control stage somehow, for which you could internally use the approach in the previous paragraph.


Also, somewhat related: I was thinking about my IR3 playthrough and I realized that at some point in the early game I saw the transfer plate explanation in the Manifesto, and thought they were neat, but I never ended up actually using them, possibly because there's an initial hurdle of having to try them out and see how they work. Maybe other people had the same issue. One way to fix this is by "tutorialization" using these Inspirations. For instance, maybe move the transfer plate recipe from the logistics tech to the steam mechanisms tech, and then make the logistics tech unlock with an inspiration the first time you use a transfer plate to move items.

I wrote some code to implement this, though messy:

local function onScriptEvent(event)
    if event.effect_id == "ir-transfer-plate" then
        global.lastTransferPlateTick = game.tick
    end
end

local function triggerInspiration(force)
    force.technologies["logistics"].researched = true
    -- Do the same stuff as IR3 Inspiration mod.
    force.print(
        {
            "gui.schematic-inspiration",
            "[img=technology/logistics]",
            game.technology_prototypes.logistics.localised_name
        },
        {sound = defines.print_sound.never})
    force.play_sound({path = "inspiration-chime"})
    for _,player in pairs(force.players) do
        if player and player.valid then
            remote.call("ir-utils", "fading-flying-text-player", player, "[img=inspiration]")
        end
    end
end

local function onPlayerMainInventoryChanged(event)
    if game.tick == global.lastTransferPlateTick then
        local player = game.players[event.player_index]
        if player ~= nil and player.valid and player.force ~= nil and player.force.valid then
            if (player.force.technologies["ir-basic-research"].researched
                and not player.force.technologies["logistics"].researched) then
                triggerInspiration(player.force)
            end
        end
    end
end

local function register()
    script.on_event(defines.events.on_script_trigger_effect, onScriptEvent)
    script.on_event(defines.events.on_player_main_inventory_changed, onPlayerMainInventoryChanged)
end

This code works, surprisingly. It would be cleaner if IR3 triggered a script event when a transfer plate moves items, and also if the Inspiration mod had a remote interface to trigger a specific tech inspiration.

Deadlock989 commented 3 months ago

The Inspiration mod already sort of puts the information (item and amount needed for inspiration) inside the technology.unit.ingredients. So one option is, in control stage, look through all tech prototypes for units with names like "ir-inspiration-copper-ingot" and then look for an item or fluid named "copper-ingot". Then in the data stage, other mods could create new items with names like "ir-inspiration-plutonium" and set them as the unit for a tech, and in the control stage your Inspiration mod would see that and trigger an inspiration when an item called "plutonium" is obtained.

This is along the lines of what I was thinking. The tech "units" (i.e. the tool item prototypes, essentially hidden/uncraftable science packs) have names in the format schematic-[techname] ("schematic" because the mod's very first incarnation allowed for finding "blueprint" schematics for technologies as loot, which automatically unlocked a tech when you pick them up - this is what the blueprint icon is for, and this functionality is still available but not currently used by Inspirations). However looking at the current control.lua, the entire thing is based on the assumption that the relevant properties can all be read out of the g global constants, which are used in both data and control stages. The control script would need to be almost entirely rewritten to pull that data out of technology prototypes instead. I only mention this because - full disclosure - I'm thoroughly demotivated around this whole thing, because Factorio 1.2/2.0 is coming in about 3 months with its trigger technologies, which will provide a game engine mechanism for what is essentially the same idea (I had the "trigger" idea and the prototyped mod all done some time before that blog post). When that comes out, I will need to make a decision about IR3's future in general, based on factors such as coolness of new toys versus any wrecking ball changes in the API, plus how much time I have available.

Or you could have a global DIR.technology_inspirations defined in data, then allow other mods to change that global later in the data stage. Then in data-updates you look through that and create icons and items. You would have to send that info into the control stage somehow, for which you could internally use the approach in the previous paragraph.

This already happens, except that the g prototype data is currently reloaded at the start of data-updates, it probably doesn't have to be (so that it behaved the same way as the main IR3 mod's DIR table whereby other mods get a pop at making changes).

Yet another approach would be reverse interface calls - for example the Informatron mod, and IR3's Manifesto functions, do this (see control-remote.lua). On mod init and configuration change, you poll all other mods to see if they provide an interface named according to a convention, and if so, call them to update your data when you (re)initialise your own mod. This is finicky, but it has the advantage of being desync-safe, and it wouldn't need as thorough a re-write at my end. Another way to do it might be via events. But probably the better (but most hassle-y) way would be to simply rewrite it all to consult the tech prototypes.

Also, somewhat related: I was thinking about my IR3 playthrough and I realized that at some point in the early game I saw the transfer plate explanation in the Manifesto, and thought they were neat, but I never ended up actually using them, possibly because there's an initial hurdle of having to try them out and see how they work. Maybe other people had the same issue. One way to fix this is by "tutorialization" using these Inspirations. For instance, maybe move the transfer plate recipe from the logistics tech to the steam mechanisms tech, and then make the logistics tech unlock with an inspiration the first time you use a transfer plate to move items. ... This code works, surprisingly. It would be cleaner if IR3 triggered a script event when a transfer plate moves items, and also if the Inspiration mod had a remote interface to trigger a specific tech inspiration.

I'm opposed to forcing players to use them to unlock further techs, so this isn't something I would like to see put into Inspirations itself. The main IR3 mod already has a framework set up for adding custom events (there is currently only one, on_forestry_chunk_updated) so I'm not opposed to adding a new event for transfer plate triggers. It would also be easy to add an interface in Inspirations for trigger_inspiration.

Deadlock989 commented 3 months ago

I have added a custom event to the next version of IR3, on_transfer_plate_or_hotkey, which fires when a transfer plate or the transfer hotkey move items from one entity to another (they both call the same internal function). Hotkey usage can be distinguished from transfer plates via one of the properties returned.

-- on_transfer_plate_or_hotkey
-- Raised when a transfer plate or hotkey moves items between player/vehicle and container
-- Not raised when no items are moved (if you want the triggers regardless of items moved, you can use on_script_trigger_effect with effect_id "ir-transfer-plate"
-- for transfer plate triggers, and script.on_event("ir-entity-adjustment") with a selected container item for the hotkey)
-- Returns a table of event data with the following key/value pairs:
-- player_index (uint) - the index of the player controlling the character/vehicle that triggered the plate or pressed the hotkey
-- identity (string) - identifier for trigger (IR3 uses "transfer-plate" and "hotkey")
-- silent (bool) - true if the flying notifications were silenced, otherwise false
-- transfers - an array of dictionaries, each containing the following key/value pairs:
---- item_name (string) - the id of the item being transferred
---- amount (uint) - the number of items transferred
---- from (LuaObject) - the source of the items, either a LuaPlayer, a LuaEntity representing a vehicle, or a LuaEntity representing a container
---- to (LuaObject) - the destination for the items, either a LuaPlayer, a LuaEntity representing a vehicle, or a LuaEntity representing a container

(edited 20/7/2022)

I will take a look at the other stuff again next week.

StephenBarnes commented 3 months ago

Thanks for adding the custom event! I didn't know they were going to add this in the new version. In that case it's probably not worth rewriting half your mod just for that. I haven't fleshed out my modpack's progression fully (it's sort of IR3 plus Freight Forwarding) but if I end up doing inspirations for new things like plutonium, and if I finish in less than 3 months, I'll just make my own on-inventory-changed handler to trigger that inspiration.

Deadlock989 commented 3 months ago

I have added a couple of basic remote interfaces to Inspiration as well (trigger_technology and reset_inspiration), see control.lua.

The main mod and Inspiration will be uploaded later today (20/7/2022).