Restrictions on Artificial Tiles


BETA A realism mod that adds some logical restrictions to "artificial" tiles (stone paths, concrete, etc) on what they can be placed under/on (trees, resources, cliffs, worms, etc). Configurable. Includes 100% removal of decoratives functionality ("Clean Concrete").

Tweaks
11 months ago
1.1
112
Environment
Owner:
FuryoftheStars
Source:
N/A
Homepage:
N/A
License:
GNU GPLv3
Created:
1 year, 26 days ago
Latest Version:
0.8.4 (11 months ago)
Factorio version:
1.1
Downloaded by:
112 users

Main Features / Description

This is a mod for adding a level of realism to placement of "artificial" tiles, which in this mod are tiles like stone path, concrete, refined concrete, and the hazard variants. It will also include various other mod added tiles, such as ones that have any of the words "road", "asphalt", "gravel", or "floor" in their names. Landfill is explicitly excluded.

These "artificial" tiles will not be placeable on/under trees (except dead), cliffs, biter nests, worms, miners & pumpjacks, resources, or rocks. Trees, biter nests, and worms have an additional (small, like less than a tile) radius around them to prevent placing of the tiles too close. The reason for this is because of the tile transitions. The tiles this mod is classifying as artificial actually overlap some with their neighboring tiles, so without this extra radius, they would, visually, look no different than if this mod didn't even affect them. Canonically, I'm also looking at it as the trees have roots, the nests have those tendril looking things growing out the sides, and the worms have these holes that are a bit bigger than their bodies.

These same entities from above likewise cannot be placed on/under any tiles classified as "artificial" with the following exceptions:

  • In an effort to prevent using artificial tiles as a hard stop/defense against biter spread, but still give it an element of that realism, biter nests and worms can spawn on artificial tiles, but have a chance at failure. The failure chance is based on the entity (small worms have the worst chance; behemoths have the best; nests are in the middle) and the average of the walking speed modifier on all tiles within their area of influence (the small radius mentioned above). The idea here being that the biters need to "break through" the artificial tiles to the ground beneath to create the nests/worms, and the nests/worms break apart any other tiles in their way as they grow/emerge. I use the walking speed modifier on the tiles because it's the best stat I have access to on the tiles (that I can think of) to determine "hardness" without creating a hard-coded table that would require updating for every mod that creates a new tile that isn't already covered.
  • The other exception here are rocks: you can place rocks (where enabled by other mods like Dectorio or in /editor) as decoratives on the tiles.

Mod Interaction & Use on Existing Saves

(Mod support is in another section further down.)

In otherwise vanilla gameplay on brand new maps, this mod should have no issues.

Interactions with other mods, however, has the potential of not going as planned (but not game/save breaking). Mods have the ability to create and destroy entities at will (ignoring build restrictions), but the default behavior of the commands used is to not raise an event telling other mods that this has been done. As such, mods that, say, create trees as part of a tree growth/spreading mod, or create biter nests/worms in place of vanilla generation, have the potential of ignoring these new restrictions, placing these entities on the artificial tiles, and not alerting this mod to what they did.

Aside from other mods, introducing this mod into an existing save will not make now invalid combinations (like concrete on resources) automatically resolve themselves.

The "fix" for this lead me to include a "scan" functionality that will look for and correct these. On game save load where this mod is introduced into an existing save or any startup settings have changed that affect the list of entities or tiles affected, and periodically afterwards, it will run a scan of all game surfaces a few chunks at a time (but only 1 scan at a time) and correct anything it finds. This does mean that on existing saves it can take a bit to scan through and correct everything.

It defaults to destroying any artificial tiles rather than the entity in any cases of conflict (so, sorry, not a free nest/worm/cliff remover), with the exception of trees, which it will mine and spill their results onto the ground around where the tree was. Rocks are just ignored as there's no real way to differentiate between a rock that was there first and one that was placed after for decoration (or whatever).

It's also possible, either through when the entity is created in the data stage or the name the mod author gives their entity/tile, for this mod to just miss identify the entity/tile and either apply restrictions to it when it shouldn't, or doesn't when it should. (Modders, please check out the Modding Support section below for how to correct this from your end.)


Settings

Startup

The startup settings are mostly centered around individually controlling the entity types that will conflict with artificial tiles:

  • Trees
  • Cliffs
  • Biter Nests
  • Biter Worms
  • Miners (& pumpjacks)
  • Resources
  • Rocks

  • There's also an option to disable the "Clean Concrete" feature

Map

There are also a few Map settings that you can use to control the scan functionality (and remember: mod map settings can be changed at any time, not just at map creation):

  • Enable/disable the periodic scan function (a scan will still occur on save game load if it's the first time this mod is being used with it or if any startup settings change that affects the list of entities or tiles it works with)
  • Periodic scan frequency (defaults to 10 minutes; only 1 scan will run at a time, so if the timer runs out and the previous scan is ongoing, it will wait until it completes before starting another)
  • Chunks to process per tick of the scan (both periodic and game load scans). I've set this to default to 5, but you may want to adjust this based on your preference and hardware. If you want to minimize UPS impact (and/or it's having a negative impact on your UPS), then you'll want to decrease this (and this supports decimal values, too, primarily for the purpose of having multiple ticks between each chunk processed). If you think your hardware can handle it, or you just want it to hurry up and get the job done (game load), you can increase this.

Known Issues & Future Plans

Issues
  • As mentioned in the Mod Interaction & Use on Existing Saves section:

    Adding this mod to an existing save can take a while to fully scan all surfaces and make any needed corrections, depending on the number of chunks and the Chunks to check per tick during scan map setting.

    It's possible for mods to create or destroy entities in a way that doesn't alert this mod of its happening. If enabled, a periodic scan will find and, if necessary, correct these.

    It's possible for this mod to misidentify mod added entities or tiles and thus apply restrictions incorrectly to them. It's ideal for the other mod author to make some simple adjustments on their end to correct for this.

  • As I haven't played mods like Space Exploration, I'm unsure what affect it'll have with certain surfaces that use artificial tiles in its generation (like in the spaceship). Provided there isn't the need to also place any of the affected entity types, everything should be fine.

Future
  • Collecting feedback for improvement and bug squashing before declaring this as version 1.0 and removing the BETA from the description.

Below this point is more technical / behind the scenes stuff


Modding Support

This mod works through the use of a collision layer generated via collision_mask_util.get_first_unused_layer() and several other special considerations, and attempts to automatically register all entity and tile types it should affect with these (see the Entity & Tile Auto-Registration section below for the criteria used). As such, I've included a few options for authors of other mods to register their entities and tiles with this mod (in case they're not picked up automatically or you just want them to be affected, too), or to even mark their entities and tiles for exclusion from this mod's handling, without having to recreate (or reverse) the wheel.

  • To exclude your entity or tile from getting auto-registered in this mod, simply add the property RestrictionsOnArtificialTiles_DoNotRegister to your prototype and set it to true.
  • Tree entities also get a special "placer" entity created for them: a fake offshore-pump to make use of the dual collision boxes and masks. This allows for enforcing the same restrictions on placing of the trees around artificial tiles as it does with placing the tiles around the trees. If for some reason you need to exclude your tree from getting this placer entity created for it (like the tree isn't placeable by the player anyway), you can add the property RestrictionsOnArtificialTiles_ExcludeFromPlacer to the tree prototype and set it to true.
  • To include your entity or tile with this mod's registration if it doesn't pick it up automatically, call one of the below functions from your mod in the data stage from data-updates.lua or data-final-fixes.lua (in order for it to work from data.lua, your mod would need to load after mine) and pass it the respective entity type or tile prototype. Note that none of the below functions actually validate the type of prototype you pass to it. This allows for some flexibility, but can also cause errors.

    RestrictionsOnArtificialTiles.Register_tile(tile) - Adds this mod's custom collision mask to the tile, as well as the "resource-layer" collision mask if the startup setting to affect resources is enabled.

    RestrictionsOnArtificialTiles.Register_tree(tree) - Checks this mod's setting for trees and, if enabled, adds this mod's custom collision mask to the tree as well as creating a special "placer" entity (explained above).

    RestrictionsOnArtificialTiles.Register_miner(mining_drill) - Checks this mod's setting for miners and, if enabled, adds this mod's custom collision mask to the mining drill.

    RestrictionsOnArtificialTiles.Register_worm(worm, fail_factor) - (fail_factor is expected to be a number) This mod's collision mask is not added to worms. Instead, this function checks this mod's setting for worms and, if enabled, creates a custom named (based on the worm's name) worm-hole entity for use in control stage. It also creates a dummy item that is then added to a dummy tech to pass the fail_factor for the worm to the control stage. fail_factor will be explained below in another section.

    RestrictionsOnArtificialTiles.Register_spawner(spawner, fail_factor) - (fail_factor is expected to be a number) This mod's collision mask is not added to nests. Instead, this function checks this mod's setting for nests and, if enabled, creates a custom named (based on the nest's name) spawner-influence entity for use in control stage. It also creates a dummy item that is then added to a dummy tech to pass the fail_factor for the nest to the control stage. fail_factor will be explained below in another section.

    RestrictionsOnArtificialTiles.Register_cliff(cliff) - Checks this mod's setting for cliffs and, if enabled, adds this mod's custom collision mask to the cliff.

    RestrictionsOnArtificialTiles.Register_rock(rock) - Check this mod's setting for rocks and, if enabled, adding this mod's custom collision mask to the rock as well as creating a custom "placer" entity to bypass the collision mask restrictions for the purposes of placing. Also makes use of the RestrictionsOnArtificialTiles_ExcludeFromPlacer property that can be set to true on the prototype to exclude it from placer generation, just like trees.

    RestrictionsOnArtificialTiles.Register_other(prototype, setting) - (setting is expected to be a string) Use this function for any other prototype. It will only add this mod's custom collision mask to the prototype. You can also optionally specify a setting so that your passed prototype will only get registered if the respective mod setting (from this mod) is enabled: "trees", "miners", "resources", "worms", "spawners", "cliffs", and "rocks".

    Example usage of the above functions (code excerpt taken from data-final-fixes.lua in my mod Tree Saplings (Redux)):

    -- Support for "Restrictions on Artificial Tiles" mod
    if mods["RestrictionsOnArtificialTiles"] then
        RestrictionsOnArtificialTiles.Register_other(data.raw["assembling-machine"]["planter"], "trees")
        RestrictionsOnArtificialTiles.Register_tree(data.raw["simple-entity"]["sapling-placer"])
    end
    

    This makes it so the "planter" entity gets this mod's collision mask added to it, but only when the respective "trees" setting is enabled.
    It also registers the "sapling-placer" entity as if it were a tree: if the respective setting for tress is enabled, it gets the collision mask added, and a "placer" from this mod for the dual collision box and masks.


Entity & Tile Auto-Registration

Below are the auto-registration criteria for this mod:

  • Any tiles that do not have the word landfill in its name and has any one of the following: a decorative_removal_probability property > 0, the word artificial in the order property, or any of the words concrete, path, road, asphalt, gravel, or floor in its name.
  • Any entities of the type "tree" that do not have dead- or dry- in their names.
  • Any entities of the type "mining-drill".
  • Any entities of the type "turret" and have the word worm in their name will be further checked as follows:
    If the word small is found, then it is registered with a fail_factor of 1.
    If the word medium is found, then it is registered with a fail_factor of 2/3.
    If the word big is found, then it is registered with a fail_factor of 1/3.
    If the word behemoth is found, then it is registered with a fail_factor of 0.
  • Any entities of the type "unit-spawner" with the word spawner and either biter or spitter in the name are registered with a fail_factor of 0.5.
  • Any entities of the type "cliff".
  • Any entities of the type "simple-entity" with the word rock in the name and has the minable property set.

fail_factor for Nests & Worms

To calculate the failure chance for spawning on artificial tiles of a biter nest or worm, I basically average together the walking_speed_modifier of all tiles that touch the nest's spawner-influence or worm's worm-hole entity, and use it in the following formula (in place of "avg"): (avg - 1) + ((avg - 1) * fail_factor)

That gives us this nifty little table (row 1 is fail_factor values, while column 1 is tile walking_speed_modifier values):

10 1.5 1 0.667 0.5 0.333 0 -0.5 -0.99 -1
1.0 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000
1.05 0.550 0.125 0.100 0.083 0.075 0.067 0.050 0.025 0.001 0.000
1.1 1.100 0.250 0.200 0.167 0.150 0.133 0.100 0.050 0.001 0.000
1.2 2.200 0.500 0.400 0.333 0.300 0.267 0.200 0.100 0.002 0.000
1.3 3.300 0.750 0.600 0.500 0.450 0.400 0.300 0.150 0.003 0.000
1.4 4.400 1.000 0.800 0.667 0.600 0.533 0.400 0.200 0.004 0.000
1.5 5.500 1.250 1.000 0.833 0.750 0.667 0.500 0.250 0.005 0.000
2.0 11.000 2.500 2.000 1.667 1.500 1.333 1.000 0.500 0.010 0.000

I then take a random number (math.random()), and if it's less than or equal to the result from that formula, then the nest or worm "fails" to spawn.

The low end of fail_factor is clamped to -1. This means that no matter the average tile's walking_speed_modifier, there's always a small chance at failure. On the opposite end of the spectrum, however, there's definitely average tile walking_speed_modifier values that will guarantee failure. A part of me wants to change the underlying formula at some point so this won't happen.