How to refund a building

Place to post guides, observations, things related to modding that are not mods themselves.
Post Reply
User avatar
DaveMcW
Smart Inserter
Smart Inserter
Posts: 3699
Joined: Tue May 13, 2014 11:06 am
Contact:

How to refund a building

Post by DaveMcW »

My mod needs to prevent placing buildings with complex conditions, and I don't want to use "layer-11" collision mask. So I wrote this function to refund a building when my mod determines it was illegally placed.

Has any other mod implemented this feature so I can compare?

Does anyone see any bugs or edge cases I missed?

Code: Select all

function refund_entity(entity, build_event)
  if entity.prototype.items_to_place_this then
    -- Find the item used to place the entity
    local item_count = 0
    local item_name = nil
    if build_event and build_event.stack and build_event.stack.valid_for_read then
      item_name = build_event.stack.name
    end
    for _, item in pairs(entity.prototype.items_to_place_this) do
      if item_name == item.name then
        item_count = item.count
        break
      end
    end
    if item_count == 0 then
      local item = entity.prototype.items_to_place_this[1]
      if item then
        item_name = item.name
        item_count = item.count
      end
    end
    local health = entity.health / entity.prototype.max_health

    -- Return item to player inventory
    local player = nil
    if build_event and build_event.player_index then
      player = game.players[build_event.player_index]
    end
    if item_count > 0 and player then
      local result = player.insert{name=item_name, count=item_count, health=health}
      item_count = item_count - result
    end

    -- Return item to ground
    if item_count > 0 then
      entity.surface.spill_item_stack(entity.position, {name=item_name, count=item_count, health=health}, false, entity.force, false)
    end
  end

  -- Destroy entity
  entity.destroy{raise_destroy = true}
end
Last edited by DaveMcW on Tue May 21, 2019 1:55 am, edited 2 times in total.

Qon
Smart Inserter
Smart Inserter
Posts: 2091
Joined: Thu Mar 17, 2016 6:27 am
Contact:

Re: How to refund a building

Post by Qon »

Code: Select all

  -- Find the item used to place the entity
  local item_count = 0
  local item_name = nil
  if build_event and build_event.stack and build_event.stack.valid_for_read then
    item_name = build_event.stack.name
  end
  for _, item in pairs(entity.prototype.items_to_place_this) do
    if item.name == stack_name then
      item_count = item.count
      break
    end
  end
  if item_count == 0 then
    local item = entity.prototype.items_to_place_this[1]
    if item then
      item_name = item.name
      item_count = item.count
    end
  end
If there's a mod that gives you an item that transform into many different entities others when placed depending on location/what the entity is placed on your mod might want to allow it depending on what the result is after the modded entity transforms. Pumps Everywhere might do this, though it might be the game itself that transforms the entity to inland pumps, haven't read the source. Offshore pumps aren't allowed on ground, inland pumps are. But the entity transforms to the one that is allowed on that spot. But if the game transforms it (it is just the same entity all the time in this case, name change is just cosmetic) another mod might do the transformation itself and then it might happen after your code checks it even if entities transformed by the engine might possibly (?) run before your event code.

Code: Select all

  -- Return item to player inventory
  if item_count > 0 and build_event and build_event.player_index then
    local player = game.players[build_event.player_index]
    local result = player.insert{name = item_name, count = item_count}
    item_count = item_count - result
  end

  -- Return item to ground
  if item_count > 0 then
    entity.surface.spill_item_stack(entity.position, {name = item_name, count = item_count}, false, entity.force, false)
  end
You never checked the entitys health (or ammo count, durability) and always give back or spill an item that is fresh. Your mod can be used to repair entities for free.

What if your mod denies entity-on-ground? Don't know if it does, but if, what happens when items are denied and then spilled by your code and then end up on the ground? When spilled, you lose information on which player spilled it (I see no player reference). So either the items might spill illegaly and stay where you don't allow it or when fixed it might end up in an infinite loop where your mod denies its own spilling. So the item lands on the ground. The mod denies it, puts it back in players in inventory. But it doesn't fit, so it spills on the ground. Now your mod detects it being illegaly on the ground and puts it back into the players inventory. But it spills out again.

Or what if an inserter puts an item on the ground? Then no player was 'responsible' and there's no player reference. If you just don't call the function on item-entity objects then this becomes a non-problem of course.
Last edited by Qon on Thu May 16, 2019 4:50 pm, edited 1 time in total.

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

Re: How to refund a building

Post by DaveMcW »

Qon wrote:
Thu May 16, 2019 9:20 am
If there's a mod that gives you an item that transform into many different entities others when placed depending on location/what the entity is placed on your mod might want to allow it depending on what the result is after the modded entity transforms. Pumps Everywhere might do this, though it might be the game itself that transforms the entity to inland pumps, haven't read the source. Offshore pumps aren't allowed on ground, inland pumps are. But the entity transforms to the one that is allowed on that spot. But if the game transforms it another mod might do the transformation itself and then it might happen after your code checks it even if entities transformed by the engine might possibly (?) run before your event code.
Good point. I added script_raised_destroy to help clean up entities transformed before my mod. I already listen to script_raised_built to handle entities transformed after my mod.

Qon wrote:
Thu May 16, 2019 9:20 am
You never checked the entitys health (or ammo count, durability) and always give back or spill an item that is fresh. Your mod can be used to repair entities for free.
Ok, I added a health check.

Qon wrote:
Thu May 16, 2019 9:20 am
If you just don't call the function on item-entity objects then this becomes a non-problem of course.
That is the plan. :D

Thanks for the feedback!

Qon
Smart Inserter
Smart Inserter
Posts: 2091
Joined: Thu Mar 17, 2016 6:27 am
Contact:

Re: How to refund a building

Post by Qon »

Glad it helped!

And I think even Wube forgot about remembering the ammo count in an earlier version. At least I remember trying to make a design that abused that bug in 0.15~ around when I made my first 0~ drain laser and when they changed how damaged items could stack together. I continuously extracted ammo from turrets while they were firing to merge half used ammo with stacks of full ammo to replenish the ammo and get infinite gun turret ammo. But maybe I tried and failed because it wasn't bugged, can't remember :roll:

User avatar
Therax
Filter Inserter
Filter Inserter
Posts: 470
Joined: Sun May 21, 2017 6:28 pm
Contact:

Re: How to refund a building

Post by Therax »

Here's mine:

Code: Select all

local function abort_build(event)
  local entity = event.created_entity
  show_error(entity)
  if event.player_index then
    local player = game.players[event.player_index]
    player.mine_entity(entity, true)
  else
    entity.order_deconstruction(entity.force)
  end
end
It relies on the entity being mineable, but I only ever refund buildings actually added by my mod, so this is a fairly easy precondition to satisfy.
Miniloader — UPS-friendly 1x1 loaders
Bulk Rail Loaders — Rapid train loading and unloading
Beltlayer & Pipelayer — Route items and fluids freely underground

Post Reply

Return to “Modding discussion”