Have a script place a blueprint

Place to get help with not working mods / modding interface.
Post Reply
User avatar
TroZ
Inserter
Inserter
Posts: 21
Joined: Mon Sep 16, 2013 12:34 am
Contact:

Have a script place a blueprint

Post by TroZ »

Hi,

I'm starting work on a mod that is trying to turn Factorio into a game more like OpenTTD. The idea is that the mod would automatically build miners on ore deposits and place production factories around the map. Your job is to create a rail system to connect the mines to production area, such as a smelter, and then take the smelted plate to a factory that needs it, eventually producing science and research, and launching a rocket (like normal). Once you researched a technology, it would unlock new factory types that use that research. For example, initially smelters would be stone furnaces and would need ore and coal, later you will find steel furnaces, and eventually electric furnaces. I also want to add a currency, so that you have to buy the item you load into your trains, but sell them to the factories that use them, and you would also have to buy the rail and trains that you used, but that can come later after I work out the building of the factories by the mod. Something like the FACTRAIN mod / scenario, but without a fixed map, it would use the randomly generated terrain Factorio normally generates.

So, I've just started. What I would like is if I could just save a bunch of blueprint strings to files in the mod directory, but I don't think a mod can load files (someone let me know if that is incorrect!). I currently have a single blueprint string and I'm trying to have the mod place it down automatically in random chunks. I have figured out how to place a single entity down using surface.create_entity() (I added a listener to on_chunk_generated and can place a chest in each chunk as it is generated). However I'm having trouble trying to place a blueprint.

For a blueprint, I noticed that there is a function LuaItemStack.import_stack(). I was hoping to use that to import a blueprint string, and then call LuaItemStack.build_blueprint() to actually create the blueprint in the world. I'm actually not sure if it is possible to have that create the full blueprint, or if that only places ghosts, but I'm not able to get this approach to work at all. I'm calling surface.create_entity to create an item-on-ground of a blueprint. However, when I then try to call .stack.import_stack() on that created blueprint, I get a return value of 1, which indicates failure.

Am I doing this correctly? I get a failure whether I pass the full blueprint string or the string already gunziped.
Is the correct approach, or would I be better off having a json library and decoding the blueprint myself and attempting to place all the items myself (which seems complicated to me if I have to connect wires and set directions of items and states of requester chests).

Thanks for any help you can provide!

User avatar
eradicator
Smart Inserter
Smart Inserter
Posts: 5206
Joined: Tue Jul 12, 2016 9:03 am
Contact:

Re: Have a script place a blueprint

Post by eradicator »

Import_stack will probably only accept the same data format that is produced by export_stack, which for blueprints is a plain lua table i believe blueprint string.

Not sure what/who told you about "importing files" but it is of course possible to "require" any arbitary .lua file within the mods reach (i.e. contained in any mod folder). So having a seperate file per blueprint is perfectly feasible. Just make it directly return the table.

Code: Select all

local mining_base_type_one = require 'blueprints.mining_base_type_one'
LuaItemStack.import_stack(mining_base_type_one)
and in the blueprints subfolder you have mining_base_type_one.lua containing:
[code]
return {
--blueprint table goes here
}
[/code]

Code: Select all

return "blueprint string goes here"
EDIT: Corrected mistaken assumption that stack im-/exporting uses plain tables.
Last edited by eradicator on Mon May 28, 2018 8:35 pm, edited 1 time in total.

User avatar
TroZ
Inserter
Inserter
Posts: 21
Joined: Mon Sep 16, 2013 12:34 am
Contact:

Re: Have a script place a blueprint

Post by TroZ »

Thanks for your help, but I now get an error control.lua:101: bad argument #1 to 'import_stack' (string expected, got table).


As for 'importing files', I would like if I would be able to literally just have a bunch of text files with blueprint strings in them that the modification could load. I'm assuming that isn't possible as I see the Factorio API has a write_file call, but I don't see anything about reading files. I do realize that the lua api supports file handling, but I assumed that was disabled for security reasons (otherwise why would the game have it's own write file call if you could just use the standard lua file handling?).


So, since I've determined that import-stack doesn't take a table, does anyone know the correct format of the string it is expecting? It doesn't seem to be a blueprint string, either as exported from the game, nor gunziped, as I have tried both already and neither worked.

User avatar
eradicator
Smart Inserter
Smart Inserter
Posts: 5206
Joined: Tue Jul 12, 2016 9:03 am
Contact:

Re: Have a script place a blueprint

Post by eradicator »

Bleh. So i went ahead and tested it just for you and it works exactly like i said it does.
eradicator wrote:Import_stack will probably only accept the same data format that is produced by export_stack [...]
Only my assumption about what that data would be was wrong.

Code: Select all

/c A = game.player.cursor_stack.export_stack() --export the bp your holding to global variable A
/c game.player.cursor_stack.import_stack(A) --import stored bp to a bp on your cursor
Example:

Code: Select all

/c game.player.cursor_stack.import_stack("0eNp1jsEOgjAQRP9lzsWAKJL+ijGm4Go2gS1pi5GQ/ru0XLx4m5nsvJ0V3TDT5FgC9ArurXjo6wrPLzFDysIyETQ40AgFMWNyPlih4jk7MT0hKrA86ANdxZsCSeDAtIOyWe4yjx257eAPQmGyfmtZST83UlEezgrLLmLC5gX6Z7DCm5zPlaa9HKu2bsr6FOMXT8RGPw==")
TroZ wrote:As for 'importing files', I would like if I would be able to literally just have a bunch of text files with blueprint strings in them that the modification could
A .lua file is a text file. You only need to enclose the string in quotes and prepend with return so require gives you the string, like i already stated above. Think of "require" as any other function call, and the lua file being the function. And yes, you can not read arbitrary files becaues that would desync multiplayer games, require is the only way.

User avatar
TroZ
Inserter
Inserter
Posts: 21
Joined: Mon Sep 16, 2013 12:34 am
Contact:

Re: Have a script place a blueprint

Post by TroZ »

Well, thank you for your assistance!

I'm not sure what I did wrong initially, but the string I was originally using as a blueprint string apparently wasn't valid. I'm not sure where it went wrong, but exporting a new string and using that worked.


I still have some issue to work out but with your help I'm making progress. I should be placing land under the blueprint if the location happens to be over water. I also should be removing trees and rocks (I'm calling .destroy() on any entities in the area covered by the blueprint), but I did notice that some trees were marked for removal, so maybe I have to wait a frame after attempting to remove trees before placing the blueprint (or maybe I have to double check that that code is actually being run). The last part is converting the placed ghosts to actual entities, and that appears to just require calling .revive() on all the the ghost entities. I know the area the blueprint covers, so getting all the ghost entities in that area shouldn't be an issue.


I'll look into using require for loading a file with blueprints into the script. Perhaps I can do something like having the mod have a 'create blueprints' mode that starts the game with just a chest that you can put blueprints in, that the mod will then write out to a file in the correct format of a lua file that the mod could then load (after you move it from the script-output directory into the mod directory, replacing the default one the mod comes with).

Thanks again for your help!

User avatar
eradicator
Smart Inserter
Smart Inserter
Posts: 5206
Joined: Tue Jul 12, 2016 9:03 am
Contact:

Re: Have a script place a blueprint

Post by eradicator »

Hm. Destroy() should world instantly. Can you show your code? Maybe you're just somehow failing to actually call destroy() on some of the trees. Especially if you're saying that it works on some trees, and doesn't work on some other trees.

About placing land... some entities might not be placeable on land, not sure if you need mod compatibility. In vanilla the offshore pump might or might not require adequate water when used with build_blueprint.

Also now you're talking about loading user generated blueprints which is a completely different level of problem. Even if the users move the generated lua files to the correct mod directory, they'd still have to add the file names for those to control.lua (or equivalent), because there's no way to list the content of a directory. And also now that your users are making manual changes to the mod the mod won't be hash-identitcal for different users anymore and thus unusable in multiplayer. It *might* be possible to get around that by storing a blueprint-book string in a user-level mod setting, but i'm not sure if mod settings have a string-length limit.

What are you trying to accomplish though that requires user generated blueprints as part of the mod :D.

(Also your use of double linebreaks after each paragraph really makes me cringe :P)

User avatar
TroZ
Inserter
Inserter
Posts: 21
Joined: Mon Sep 16, 2013 12:34 am
Contact:

Re: Have a script place a blueprint

Post by TroZ »

Yeah, destroy does work, assuming you are using the correct coordinates in your find_entities call, which i wasn't.

I also realize that offshore pumps or similar entities will be an issue for me currently, but there should be something I can look at in their prototype that lest me know that they need to be placed on water, so I can adjust the tile under them as necessary. I just haven't gone that far yet.

I don't think the user generated blueprints will be that much trouble. I can pre-load the chest with the current blueprints the mod has (and use a modified chest that has a lot more slots), and then generate one file containing a table of all the blueprints that are in the chest when the player destroys the chest (I can tell when a specific entity is destroyed, correct?). The player would then just replace the file in the mod with the new one. Of course, for multiplayer, everyone would need to be using the same file. The point of user editable blueprints would be to support different mods, such as Bob's mods, or perhaps you like to train steam to outposts to provide power rather than run power lines or transporting coal.

Speaking of multiplayer, I'm noticing that calling math.random seems to be producing different results, even when I'm setting the seed. I'm concerned this will cause desyncs in multiplayer (although I have a long way to go before actual testing in multiplayer). LuaRandomGenerator documentation seems to discourage it's use and suggests using math.random. I'm using the same map seed, and I'm seeding math.randomseed with the map's random seed plus the chunk location, but I seem to be getting the blueprints placed in different places on different runs.

My mod so far: https://pastebin.com/9uNf7hFA

User avatar
eradicator
Smart Inserter
Smart Inserter
Posts: 5206
Joined: Tue Jul 12, 2016 9:03 am
Contact:

Re: Have a script place a blueprint

Post by eradicator »

TroZ wrote:I also realize that offshore pumps or similar entities will be an issue for me currently, but there should be something I can look at in their prototype that lest me know that they need to be placed on water, so I can adjust the tile under them as necessary. I just haven't gone that far yet.
I can't find anything in the API doc about that. So it's probably not exposed and you'd need to request it first. LuaSurface.create_entity() doesn't care for such limitations btw and just spawns the pump anywhere you want.
TroZ wrote:I don't think the user generated blueprints will be that much trouble. [...] The player would then just replace the file in the mod with the new one.
Eer. Depending on your target audience fiddling in mod internals can be a quite ludicrously high barrier. I guess as long as it's not required that's fine. But even as a modder myself (i.e. someone with ample knowledge of how to replace a file ;)) i'd find it quite annoying if i had to download a text file from somewhere and replace some other file in my mod, and then restart the game, every time i want to join a multiplayer game. And i don't even play on different servers that much. So i don't even run into the problem of how to distribute the file in the first place. Certainly precludes your mod from running on any sort of public server.
TroZ wrote:Speaking of multiplayer, I'm noticing that calling math.random seems to be producing different results, even when I'm setting the seed. I'm concerned this will cause desyncs in multiplayer
math.random is multiplayer safe. And if you find any situation where it's not that would be a bug in the engine that would get fixed quite fast i believe ;).

User avatar
Ranakastrasz
Smart Inserter
Smart Inserter
Posts: 2124
Joined: Thu Jun 12, 2014 3:05 am
Contact:

Re: Have a script place a blueprint

Post by Ranakastrasz »

https://mods.factorio.com/mods/justaran ... blueprints

Can't say much, but this looks related.
My Mods:
Modular Armor Revamp - V16
Large Chests - V16
Agent Orange - V16
Flare - V16
Easy Refineries - V16

User avatar
DaveMcW
Smart Inserter
Smart Inserter
Posts: 3700
Joined: Tue May 13, 2014 11:06 am
Contact:

Re: Have a script place a blueprint

Post by DaveMcW »

That mod is a fancy way to call LuaItemStack.build_blueprint(), which was mentioned in the first post.

MostlyNumbers
Inserter
Inserter
Posts: 26
Joined: Fri Apr 13, 2018 10:07 pm
Contact:

Re: Have a script place a blueprint

Post by MostlyNumbers »

TroZ wrote:
I also realize that offshore pumps or similar entities will be an issue for me currently, but there should be something I can look at in their prototype that lest me know that they need to be placed on water, so I can adjust the tile under them as necessary. I just haven't gone that far yet.
I can't find anything in the API doc about that. So it's probably not exposed and you'd need to request it first. LuaSurface.create_entity() doesn't care for such limitations btw and just spawns the pump anywhere you want.
Collision_mask = {"ground-tile"} tells you it needs to go over water.
The more complex issue with offshore pumps is the unique, adjacent_tile_collision_test = {"water-tile"}

User avatar
eradicator
Smart Inserter
Smart Inserter
Posts: 5206
Joined: Tue Jul 12, 2016 9:03 am
Contact:

Re: Have a script place a blueprint

Post by eradicator »

Btw there's a thread with a similar topic. It turns out that using create_entity is pretty straight forward. I've made a small script that force builds any blueprint string:

Code: Select all

    /c
    --[[FUNCTION DEFINITION]]
    local function build_blueprint_from_string(bp_string,surface,offset,force)
      local bp_entity = surface.create_entity{name='item-on-ground',position=offset,stack='blueprint'}
      bp_entity.stack.import_stack(bp_string)
      local bp_entities = bp_entity.stack.get_blueprint_entities()
      bp_entity.destroy()
      for _,entity in pairs(util.table.deepcopy(bp_entities)) do
       entity.position = {entity.position.x + offset.x, entity.position.y + offset.y}
       entity.force = force
       surface.create_entity(entity)
       end
      end
       
    --[[FUNCTION CALL]]   
    local bp_string = "0eNqVlu9qg0AQxN9lP1/A2zuN8VVKKZoc5SA5RS+lIfju1QRKabbpzifx389ldgbnSt3xHIYxpkzNleK+TxM1L1ea4ntqj+u1fBkCNRRzOJGh1J7Wszy2aRr6MW+6cMw0G4rpED6psfOroZByzDHcSbeTy1s6n7owLg/8xTA09NPyWp/Wry6oTWnoshzcQj/EMezv93g2D1DWQ50a6vRQr4Z6PdSKUC9ASz2U1ZNWauj3oAJlq6b4J5QaNo2VMDsYwxLGFvAS7f9LtBamsoLK6BpF5axDMbJyevu7Z9PoDc+i/pLfbYXOxgoo7H9ZNzgAsm5wAAoJw7D/FfZn2P6FAoq6X5SNUfPLqqHeV3iWS5Cp0awCw1Uo5kRjoDEMnAkxWrxDu8Ov2FdSdSjQ7qCBwgnRQBntDhooGhhxM86Dpnn4HS5V9FZbmx8t19BHGKf76PWWbe2qwvl5/gIqEqz8"
    --[[use a surface suitable for your usecase...]]
    local first_available_surface
    for _,surface in pairs(game.surfaces) do first_available_surface = surface break end
    --[[offset from the surface center, i.e. position on the surface]]
    local offset = {x = 10, y = 10}
    local force = 'player'
    build_blueprint_from_string(bp_string,first_available_surface,offset,force)

Post Reply

Return to “Modding help”