Loot Question [Solved]

Place to get help with not working mods / modding interface.
TheSAguy
Smart Inserter
Smart Inserter
Posts: 1449
Joined: Mon Jan 13, 2014 6:17 pm
Contact:

Loot Question [Solved]

Post by TheSAguy »

Hi,

I'm trying to add loot (Small Alien Artifacts) to a table each time they are created.
I also need to remove them if they are picked up or somehow destroyed.

I'm trying the following, but it does not appear that they are "Built" when they are created:

Events:

Code: Select all

local build_events = {defines.events.on_built_entity, defines.events.on_robot_built_entity}
script.on_event(build_events, On_Built)

local pre_remove_events = {defines.events.on_pre_player_mined_item, defines.events.on_robot_pre_mined}
script.on_event(pre_remove_events, On_Remove)

local death_events = {defines.events.on_entity_died}
script.on_event(death_events, On_Death)
On Built:

Code: Select all

local function On_Built(event)

    local entity = event.created_entity


    --- Add Alien Artifacts to table
    if entity.valid and entity.name == "small-alien-artifact" then

        global.small_alien_artifact_created[entity.unit_number] = {
            artifact = entity,
            time = event.tick
        }

    end    

end

And then removing them from the table if they are picked up:
On Remove:

Code: Select all

local function On_Remove(event)

    local entity = event.entity


    --- Remove Alien Artifacts from table
    if entity.valid and entity.name == "small-alien-artifact" then

        if global.small_alien_artifact_created[entity.unit_number] then
            global.small_alien_artifact_created[entity.unit_number] = nil
        end

    end    



end
Same for death:

Code: Select all

local function On_Death(event)

    local entity = event.entity
    
    if entity.valid and entity.name == "small-alien-artifact" then

        if global.small_alien_artifact_created[entity.unit_number] then
            global.small_alien_artifact_created[entity.unit_number] = nil
        end

    end   
    
end
    

I briefly tried "Trigger" but I don't think my loot is setup as a "trigger"
How should I add and remove these Small Alien Artifact Loot from a table?

Thanks.
Last edited by TheSAguy on Wed Jun 28, 2023 8:20 pm, edited 3 times in total.
User avatar
DaveMcW
Smart Inserter
Smart Inserter
Posts: 3716
Joined: Tue May 13, 2014 11:06 am
Contact:

Re: Loot Question

Post by DaveMcW »

small-alien-artifact is not a vanilla item.

Please describe EXACTLY what a small-alien-artifact is. Preferably with the data.lua entries.
Pi-C
Smart Inserter
Smart Inserter
Posts: 1726
Joined: Sun Oct 14, 2018 8:13 am
Contact:

Re: Loot Question

Post by Pi-C »

TheSAguy wrote: Fri Jun 23, 2023 9:46 pm I'm trying to add loot (Small Alien Artifacts) to a table each time they are created.
I also need to remove them if they are picked up or somehow destroyed.
The artifacts are item-entities. You can add them to the loot of entity-with-health prototypes in the data stage: if they die, the item-entities will be created (depending on the probability you've set).

You don't need to register the item-entities in a table. Instead, listen to on_picked_up_item and act if event.item_stack.name == "small-alien-artifact".

You may want to look at my Maps mod for reference. The maps (item-entities) are defined in prototypes/maps.lua, in data-final-fixes.lua they are assigned as loot to tree prototypes, and in scripts/maps.lua I manipulate whatever loot destroyed trees have left and do things when a map has been picked up (functions MAPSTUFF.on_entity_died and MAPSTUFF.on_picked_up_item).
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!
TheSAguy
Smart Inserter
Smart Inserter
Posts: 1449
Joined: Mon Jan 13, 2014 6:17 pm
Contact:

Re: Loot Question

Post by TheSAguy »

@Pi-C. Okay, I'm trying to follow what you did, but it's a little complex for me.
@DaveMcW, Here is the Alien Artifact: So it's an Item only.

Code: Select all

local NEEnemies = require('common')('Natural_Evolution_Enemies')
local ICONPATH = NEEnemies.modRoot .. "/graphics/icons/"

if settings.startup["NE_Alien_Artifacts"].value == true then

    data:extend({{
        type = "item",
        name = "alien-artifact",
        icon = ICONPATH .. "alien-artifact.png",
        icon_size = 64,
        icons = {{
            icon = ICONPATH .. "alien-artifact.png",
            icon_size = 64
        }},
        subgroup = "raw-material",
        order = "g[alien-artifact]-a[pink]-a[small]",
        fuel_value = "250MJ",
        fuel_category = "chemical",
        fuel_emissions_multiplier_update = 0.00,
        stack_size = 500,
        default_request_amount = 10
    }})
end

I already have this is the "Loot" table of enemy units, so when they die, these guys are created.

Now all I'm trying to do it to add these dropped Alien Artifacts to a table when they are created and to remove them if they are picked up or something else happens to them. (Not sure if anything really else could happen to them).

I need the position and time it happend. Something to the effect below:

Code: Select all

        global.small_alien_artifact_created[entity.unit_number] = {
            artifact = entity,
            time = event.tick
        }

        writeDebug("Small Artifact Added (on death - built")
        writeDebug(table_size(global.small_alien_artifact_created) )
My issue is I don't know how to detect the loot dropping. It does not follow the conventional "Entity" stuff I'm a little more familiar with. I normally use the below:

Code: Select all

local build_events = {defines.events.on_built_entity, defines.events.on_robot_built_entity}
script.on_event(build_events, On_Built)

local pre_remove_events = {defines.events.on_pre_player_mined_item, defines.events.on_robot_pre_mined}
script.on_event(pre_remove_events, On_Remove)

local death_events = {defines.events.on_entity_died}
script.on_event(death_events, On_Death)
So again, just need to understand how I can capture the loot in a table and also remove if picked/up mined, destroyed.
Thanks.
User avatar
DaveMcW
Smart Inserter
Smart Inserter
Posts: 3716
Joined: Tue May 13, 2014 11:06 am
Contact:

Re: Loot Question

Post by DaveMcW »

Code: Select all

local function On_Death(event)
  if event.loot and event.loot.valid then
    for i = 1, #event.loot do
      if event.loot[i].valid_for_read and event.loot[i].name == "small-alien-artifact" then
        -- add to item table    
      end
    end     
  end   
end
TheSAguy
Smart Inserter
Smart Inserter
Posts: 1449
Joined: Mon Jan 13, 2014 6:17 pm
Contact:

Re: Loot Question

Post by TheSAguy »

Much apricated DaveMcW!
Pi-C, I'm still absorbing that wonderful code of yours.
TheSAguy
Smart Inserter
Smart Inserter
Posts: 1449
Joined: Mon Jan 13, 2014 6:17 pm
Contact:

Re: Loot Question

Post by TheSAguy »

Unfortunately, this does not seem to work exactly correct.
Each time the loot triggers, it's captured, but only once per loot activation and not for each loot created.

Sample
As you can see from the screen shot, 3 nests were destroyed. Artifacts dropped, but only 3 instances were recorded as being added to the table and not one for each Artifact created.

This is my code:

Code: Select all


local function On_Death(event)


    --- Add Alien Artifacts to table
    if event.loot and event.loot.valid then
       -- writeDebug("Loot Trigger")
        for i = 1, #event.loot do
            writeDebug("Loot Trigger - Number of Loot is:".. i)
            if event.loot[i].valid_for_read and event.loot[i].name == "small-alien-artifact" then
                global.small_alien_artifact_created[entity.unit_number] = {
                    artifact = entity,
                    time = event.tick,
                }
                writeDebug("Small Artifact Added")
                writeDebug(table_size(global.small_alien_artifact_created))  

            elseif event.loot[i].valid_for_read and event.loot[i].name == "alien-artifact" then   
                global.big_alien_artifact_created[entity.unit_number] = {
                    artifact = entity,
                    time = event.tick,
                    surface = entity.surface,
                    position = entity.position
                }
                --- Remove trees and rocks around artifacts
                Remove_Trees(entity)
                Remove_Rocks(entity)
                writeDebug("Big Artifact Added")
                writeDebug(table_size(global.big_alien_artifact_created))  
            end

        end
         
    end   

end
One more question, if I wanted to create something from the data in the table I created. What should that look like:
I want to create a worm where the artifact dropped.

Code: Select all

local function on_tick()


        if global.big_alien_artifact_created ~= nil then
           
            writeDebug("Number of Big Artifacts in table is: "..table_size(global.bigl_alien_artifact_created))  
            
            for k, Big_Artifacts in pairs(global.small_alien_artifact_created) do

                if Big_Artifacts.time and Big_Artifacts.time + (3600 * 1) < game.tick then 
            
                     
                    local entity = Big_Artifacts.artifact
                    local surface = Big_Artifacts.surface
                    local position = Big_Artifacts.position
                    
                    local worm_larva = surface.create_entity({
                        name = "ne-base-larva-worm",
                        position = position,
                        force = game.forces.enemy
                    })
                    
                    
                    Big_Artifacts.artifact.destroy()
                    Big_Artifacts.artifact = nil
                    Big_Artifacts.time = nil
                    Big_Artifacts.surface = nil
                    Big_Artifacts.position = nil
                end

            end

        end

    end
end
I'm able to build my worm where the Artifact was added, but I'm also having issues getting rid of the Artifact in the table once built.
I'd not removing the entry from the table with above.


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

Re: Loot Question

Post by DaveMcW »

Unfortunately there is no event for loot entities spawning on the ground. But we have 3 big hints:

Code: Select all

event.loot[i].name  -- the name of the loot item
event.entity.position  -- the center of the dead entity
event.entity.surface  -- the surface the loot is on
Using those hints, we can run surface.find_entities_filtered to find the loot entities.

Code: Select all

local loot_entities = event.entity.surface.find_entities_filtered{
  name = event.loot[i].name,
  position = event.entity.position, 
  radius = 10,
}
TheSAguy
Smart Inserter
Smart Inserter
Posts: 1449
Joined: Mon Jan 13, 2014 6:17 pm
Contact:

Re: Loot Question

Post by TheSAguy »

Yeah, I thought that might be the way to go.

Why is the following not removing/deleting the records in the table?

Code: Select all

                    Big_Artifacts.artifact.destroy()
                    Big_Artifacts.artifact = nil
                    Big_Artifacts.time = nil
                    Big_Artifacts.surface = nil
                    Big_Artifacts.position = nil
I thought that would remove the entry from the table, but it does not.
Thanks,
TheSAguy
Smart Inserter
Smart Inserter
Posts: 1449
Joined: Mon Jan 13, 2014 6:17 pm
Contact:

Re: Loot Question

Post by TheSAguy »

I really don't know what I'm doing wrong here, but I can't seem to find any of the Artifacts or the ground.
I tweaked this a 10 times and actually did find some at a point, but it was inconsistent.

If anyone could please point out my coding error below and help with adding the found items to a global table it would be much appreciated. Thanks.
I've attached the Mod if needed.

Code: Select all


 --- Look for Alien Artifacts from Loot Drops
    if event.loot and event.loot.valid then

        for i = 1, #event.loot do

            if event.loot[i].valid_for_read and event.loot[i].name == "small-alien-artifact" then
                writeDebug("Loot Trigger is Small Artifact")

                local loot_entities = {}
                local loot_entities = event.entity.surface
                                          .find_entities_filtered({
                    --name = event.loot[i].name,
                    position = event.entity.position,
                    type = "item-on-ground",
                    radius = 10
                })

                local loot_entities = event.entity.surface.find_entities_filtered{
                    name = event..name,
                    position = event.entity.position, 
                    radius = 10,
                  }

                writeDebug("Number of small artifacts found = " ..#loot_entities)

                if #loot_entities > 0 then

                    for i = 1, #loot_entities do
                        writeDebug("got here")

                        for i, Small_Artifacts in pairs(loot_entities) do

                            --- Add EACH small Artifict found to a Global Table
                            --[[
                                if Small_Artifacts and Small_Artifacts.valid then
                                
                                    global.small_alien_artifact_created[Small_Artifacts.unit_number] = {
                                        artifact = entity,
                                        time = event.tick,
                                        surface = entity.surface,
                                        position = event.entity.position
                                    }
                                    writeDebug("Small Artifact Added")
                                    writeDebug(table_size(global.small_alien_artifact_created))  
                                end
                                ]]

                        end
                    end

                end

            elseif event.loot[i].valid_for_read and event.loot[i].name == "alien-artifact" then
                writeDebug("Loot Trigger is Big Artifact")

                local position = event.entity.position
                local radius = 5
                local area = {
                    {position.x - radius, position.y - radius},
                    {position.x + radius, position.y + radius}
                }
                local big_loot_entities = {}
                local big_loot_entities =
                    surface.find_entities_filtered({
                        area = area,
                        name = "alien-artifact"
                    })

                writeDebug("Found big artifacts = " .. #big_loot_entities)

                if #big_loot_entities > 0 then

                    for i, Big_Artifacts in pairs(big_loot_entities) do

                        if Big_Artifacts and Big_Artifacts.valid then

                            --- Add EACH bIG Artifict found to a Global Table
                            global.big_alien_artifact_created[entity.unit_number] =
                                {
                                    artifact = entity,
                                    time = event.tick,
                                    surface = entity.surface,
                                    position = entity.position
                                }

                        end
                    end

                end

            end
        end
    end

Attachments
Natural_Evolution_Enemies_1.1.15.zip
(2.67 MiB) Downloaded 46 times
User avatar
DaveMcW
Smart Inserter
Smart Inserter
Posts: 3716
Joined: Tue May 13, 2014 11:06 am
Contact:

Re: Loot Question

Post by DaveMcW »

Maybe the item-on-ground is generated after on_entity_died triggers?

In that case you would need to add the parameters to a temporary table and run it the next tick.
Pi-C
Smart Inserter
Smart Inserter
Posts: 1726
Joined: Sun Oct 14, 2018 8:13 am
Contact:

Re: Loot Question

Post by Pi-C »

TheSAguy wrote: Tue Jun 27, 2023 4:47 pm

Code: Select all


 --- Look for Alien Artifacts from Loot Drops
    if event.loot and event.loot.valid then
        for i = 1, #event.loot do
        …
To begin with, the if-condition doesn't make sense because event.loot isn't optional, so it will always exist and always be valid. Basically, event.loot is a dynamically generated inventory that has just as many slots as necessary to accommodate all the loot -- if the killed entity didn't leave any loot behind, it will have 0 slots. So you could change this condition to

Code: Select all

    if #event.loot > 0 then

However, this is redundant as the for-loop will only run iff #event.loot > 0.

Code: Select all

        for i = 1, #event.loot do
            if event.loot[i].valid_for_read and event.loot[i].name == "small-alien-artifact" then
In this loop, you go over all slots in the loot-inventory. Each slot is a LuaItemStack, and its name is the name of the item prototype that is placed in this slot.

Code: Select all

                writeDebug("Loot Trigger is Small Artifact")

                local loot_entities = {}
                local loot_entities = event.entity.surface.find_entities_filtered({
                    position = event.entity.position,
                    type = "item-on-ground",
                    radius = 10
                })
Before, you've looked for items, now you've looked for real entities. However, this won't find anything because you've mixed up entity.type ("item-entity") and entity.name("item-on-ground"). This should actually find something:

Code: Select all

                writeDebug("Loot Trigger is Small Artifact")

                local loot_entities = {}
                local loot_entities = event.entity.surface.find_entities_filtered({
                    position = event.entity.position,
                    name = "item-on-ground",
                    radius = 10
                })
Entities of type item-on-ground are a kind of dummy or container; they have a property entity.stack, which describes what item it contains (name and amount). There may be anything in it, not just your artifacts, so this also won't work as expected:

Code: Select all

                writeDebug("Number of small artifacts found = " ..#loot_entities)

                if #loot_entities > 0 then

                    for i = 1, #loot_entities do
                        writeDebug("got here")

                        for i, Small_Artifacts in pairs(loot_entities) do

                            --- Add EACH small Artifict found to a Global Table
                            --[[
                                if Small_Artifacts and Small_Artifacts.valid then
                                
                                    global.small_alien_artifact_created[Small_Artifacts.unit_number] = {
                                        artifact = entity,
                                        time = event.tick,
                                        surface = entity.surface,
                                        position = event.entity.position
                                    }
                                    writeDebug("Small Artifact Added")
                                    writeDebug(table_size(global.small_alien_artifact_created))  
                                end
                                ]]
                        end
                    end

                end
Even worse: Although an item-entity is a LuaEntity, it doesn't have a unit_number, so your scheme for storing the artifacts won't work.

I'm not sure what exactly you want to achieve. My guess is that you want the artifacts to be automatically removed if they haven't been picked up or destroyed after a certain time. If that assumption is correct, it would make sense to store them in tables indexed by event.tick (or event.tick + TICKS_TO_EXPIRE). Then you could check in on_tick whether there a table for that tick exists and destroy all item-entities that are still valid (i.e. they have not been picked up yet).

I've also noticed that only occasionally (some) of the loot will be found in on_entity_died. DaveMcW seems to be right: It seems that all loot items will be item-on-ground by the time that on_post_entity_died triggers. So you should add defines.events.on_post_entity_died to death_events. When your event handler is entered for on_entity_died, check if the loot contains any of your artifacts and just create a new table (indexed by tick). On the next run, look for item-on-ground entities that contain your artifacts and insert them into the appropriate table. Your search radius is rather big (10 tiles), so you may find loot that has been left behind by another entity. But this really shouldn't matter if all you want to do is destroying uncollected loot after a certain time.

Here's my take on your On_Death function, plus the clean-up code for on_tick:

Code: Select all

local reverse_events = {}
for e_name, e in pairs(defines.events) do
  reverse_events[e] = e_name
end

LOOT_EXPIRE_TICKS = 60*30   -- 30 seconds, should probably be longer!
--------------------------------------------------------------------

local function On_Death(event)
writeDebug(string.format("Entered handler for event %s: %s", reverse_events[event.name], serpent.line(event)))

    local post_event = (event.name == defines.events.on_post_entity_died)

    -- Exists only in on_entity_died!
    local entity = event.entity

    -- on_post_entity_died provides surface_index
    local surface = post_event and game.surfaces[event.surface_index] or
                                    entity.surface

    -- Only relevant for on_entity_died!
    local force = entity and entity.force

    -- on_post_entity_died provides position
    local pos = post_event and event.position or entity.position


    local loot_expires_on_tick = event.tick + LOOT_EXPIRE_TICKS


    -- Code for on_post_entity_died
    if post_event then
      local tick = loot_expires_on_tick
      -- Tables have been created in on_entity_died if artifacts were found in loot!
      if global.small_alien_artifact_created[tick] or
          global.big_alien_artifact_created[tick] then

        -- Look for items on ground around position where entity died
        local loot_entities = surface.find_entities_filtered({
          position = pos,
          name = "item-on-ground",
          --~ type = "item-entity",
          radius = 10
        })
log("loot_entities: "..serpent.block(loot_entities))

        -- Look for the items on ground that contain artifacts
        for e, entity in pairs(loot_entities) do
          log(string.format("e: %s\titem in entity: %s (%s)",
                            e, entity.stack.name, entity.stack.count))
          if entity.stack.valid and entity.stack.valid_for_read then
            -- Add small artifacts to table
            if entity.stack.name == "small-alien-artifact" and
                global.small_alien_artifact_created[tick] then

              writeDebug("Loot Trigger is Small Artifact")
log("entity: "..serpent.line(entity and entity.valid and entity.name))
              table.insert(global.small_alien_artifact_created[tick], entity)

            -- Add big artifacts to table
            elseif entity.stack.name == "alien-artifact" and
                    global.big_alien_artifact_created[tick] then
              writeDebug("Loot Trigger is Big Artifact")
              table.insert(global.big_alien_artifact_created[tick], entity)
            end
          end
        end
        writeDebug(string.format("Added %s small and %s big artifacts!",
                    global.small_alien_artifact_created[tick] and
                      #global.small_alien_artifact_created[tick] or 0,
                    global.big_alien_artifact_created[tick] and
                      #global.big_alien_artifact_created[tick] or 0))

      end

      -- Nothing more to do for on_post_entity_died!
      return
    end

    --------------------------------------------------------------------
    -- Code for on_entity_died

    --- If you remove a rocket silo, the tech levle lowers again.
    if entity.valid and entity.type == "rocket-silo" and
        settings.startup["NE_Challenge_Mode"].value then

      …
      
    end

    --- Unit Launcher Mine Detinated
    if entity.valid and NELandmine(entity) == "landmine" then

      …
      
    end

    --- Look for Alien Artifacts from Loot Drops
    local loot = event.loot.get_contents()
for i_name, i_count in pairs(loot) do
  log(string.format("name: %s\tcount: %s", i_name, i_count))
end

    -- Create tables
    if loot["small-alien-artifact"] then
      global.small_alien_artifact_created[loot_expires_on_tick] = {}
    end
    if loot["alien-artifact"] then
      global.big_alien_artifact_created[loot_expires_on_tick] = {}
    end

    …
end



local function on_tick()
    if global.big_alien_artifact_created[game.tick] then
      local cnt = 0
      for e, entity in pairs(global.big_alien_artifact_created[game.tick]) do
        if entity.valid then
          cnt = cnt + 1
          entity.destroy()
        end
      end
      global.big_alien_artifact_created[game.tick] = nil
      log(string.format("Removed %s big artifacts!", cnt))
    end

    if global.small_alien_artifact_created[game.tick] then
      local cnt = 0
      for e, entity in pairs(global.small_alien_artifact_created[game.tick] or {}) do
        if entity.valid then
          entity.destroy()
        end
      end
      global.small_alien_artifact_created[game.tick] = nil
      log(string.format("Removed %s small artifacts!", cnt))
    end

    …
end
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!
TheSAguy
Smart Inserter
Smart Inserter
Posts: 1449
Joined: Mon Jan 13, 2014 6:17 pm
Contact:

Re: Loot Question

Post by TheSAguy »

Thanks so much Pi-C,
I would never have been able to get to this with my mucking around.

Yes, the timing is only for testing.
As for what I'm trying to accomplish... well, worms must come from somewhere...
Beofre:
Image
After:
Image

Better pick up those artifacts.
Post Reply

Return to “Modding help”