Author Topic: Script to save the state of hundreds of objects  (Read 91 times)

alexgleason

  • Jr. Member
  • **
  • Posts: 72
  • Vegan on a Desert Island
    • View Profile
    • Vegan on a Desert Island
Script to save the state of hundreds of objects
« on: October 17, 2018, 08:49:55 am »
Video of this in action: https://vimeo.com/295527145

This is basically solving the problem of Banjo Kazooie's music notes (or Donkey Kong 64's bananas) - how do you save each individual banana/music note separately so that once you picked it up once, it is gone forever?

This script is unfinished, and it's only in a single map for now, but I wanted to share my progress. Later I'll clean it up and abstract it into a proper separate lua module.

My game has hundreds of pieces of trash on the map. When you load the map, the "beach_trash" savegame value looks like this:

Code: [Select]
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
After vacuuming 2 pieces of trash:

Code: [Select]
1111111111111111111111100111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
After vacuuming 6 pieces:

Code: [Select]
1111111111111110011111100111111111111111111111111111111111111111110011111111111111101111111111111111111111111111111111111111111111111111111111111111111111111111111110111111111111111111111
Essentially, when the map loads, it finds all the trash entities, loops through them, and gives them an ID based on the order they're returned from map:get_entities(). So the first piece of trash returned from get_entities() would have an ID of 1 and so on. A "state" table then keeps track of every piece of trash. The trash's ID is used as a key for the state table, and the value is 1 if existing, and 0 if removed. The state table is converted into the above binary string when saved. Here's the full code:

EDIT: Better version in the post below. Also, someone pointed out that this is basically a bitmap. Pretty much - it's like removing all the gaps between the entities and then creating a bitmap over that.

Code: Lua
  1. -- ♡ Copying is an act of love. Please copy and share.
  2.  
  3. local map = ...
  4. local game = map:get_game()
  5. local trash_state = {}
  6.  
  7. -- Takes in an entity state table and converts it to a string for save
  8. local function state_tostring(state)
  9.   local string = ""
  10.   for i, v in ipairs(state) do
  11.     string = string .. tostring(v)
  12.   end
  13.   return string
  14. end
  15.  
  16. -- Takes a state string and returns a table
  17. local function state_fromstring(str)
  18.   if str == nil then return {} end
  19.   local state = {}
  20.   for i=1, #str do
  21.     local n = tonumber(str:sub(i, i))
  22.     state[i] = n
  23.   end
  24.   return state
  25. end
  26.  
  27. -- Get all trash entities for a map
  28. local function get_trash_entities()
  29.   local trash_entities = {}
  30.   local custom_entities = map:get_entities_by_type("custom_entity")
  31.   for entity in custom_entities do
  32.     if entity:get_model() == "trash" then
  33.       table.insert(trash_entities, entity)
  34.     end
  35.   end
  36.   return trash_entities
  37. end
  38.  
  39. -- Assign ID's to all trash entities.
  40. -- Add on_removed event.
  41. local function process_trash()
  42.   trash_state = state_fromstring(game:get_value("beach_trash")) -- load from save
  43.   local entities = get_trash_entities()
  44.   for i, entity in ipairs(entities) do
  45.     entity.id = i -- Assign id to each piece of trash
  46.     if not trash_state[entity.id] then
  47.       trash_state[entity.id] = 1
  48.     end
  49.     function entity:on_removed() -- Update the trash state table
  50.       trash_state[self.id] = 0
  51.       local state_string = state_tostring(trash_state)
  52.       game:set_value("beach_trash", state_string) -- Save the trash state
  53.     end
  54.     if trash_state[entity.id] == 0 then
  55.       entity:remove()
  56.     end
  57.   end
  58. end
  59.  
  60.  
  61. function map:on_started()
  62.   process_trash()
  63. end
  64.  
  65. -- Remove the on_removed event, otherwise trash_state will be set to all zeros when the map is left
  66. function map:on_finished()
  67.   for i, entity in ipairs(get_trash_entities()) do
  68.     entity.on_removed = nil
  69.   end
  70. end
  71.  
« Last Edit: October 18, 2018, 04:19:00 pm by alexgleason »
RIP Aaron Swartz

alexgleason

  • Jr. Member
  • **
  • Posts: 72
  • Vegan on a Desert Island
    • View Profile
    • Vegan on a Desert Island
Re: Script to save the state of hundreds of objects
« Reply #1 on: October 17, 2018, 10:00:07 pm »
I created a far superior version. It's encapsulated entirely within the custom entity script itself:

Code: Lua
  1. -- ♡ Copying is an act of love. Please copy and share.
  2.  
  3. local entity = ...
  4. local game = entity:get_game()
  5. local map = entity:get_map()
  6. local hero = game:get_hero()
  7.  
  8. -- Name of the savegame variable for this map and model
  9. local valname = string.format("entitystate__%s__%s", map:get_id(), entity:get_model())
  10.  
  11. -- Takes in a trash_state and converts it to a string for
  12. -- storage into a savegame variable
  13. local function state_tostring(state)
  14.   local string = ""
  15.   for i, v in ipairs(state) do
  16.     string = string .. tostring(v)
  17.   end
  18.   return string
  19. end
  20.  
  21. -- Takes a state string and returns a table
  22. local function state_fromstring(str)
  23.   if str == nil then return {} end
  24.   local state = {}
  25.   for i=1, #str do
  26.     local n = tonumber(str:sub(i, i))
  27.     state[i] = n
  28.   end
  29.   return state
  30. end
  31.  
  32. -- Event called when the custom entity is initialized.
  33. function entity:on_created()
  34.  
  35.   -- Initialize the properties of your custom entity here,
  36.   -- like the sprite, the size, and whether it can traverse other
  37.   -- entities and be traversed by them.
  38.   entity:set_traversable_by("hero", false)
  39.  
  40.   -- Initialize some variables in the map
  41.   if map._trash_count == nil then map._trash_count = 1 end
  42.   if map._trash_state == nil then
  43.     map._trash_state = state_fromstring(game:get_value(valname))
  44.   end
  45.  
  46.   -- Add an ID to this entity by the order it was created
  47.   self._id = map._trash_count
  48.   map._trash_count = map._trash_count + 1 -- increment counter
  49.  
  50.   -- Initialize the trash state
  51.   if map._trash_state[self._id] == nil then
  52.     map._trash_state[self._id] = 1
  53.   end
  54.  
  55.   -- Remove this if it was already sucked up
  56.   if map._trash_state[self._id] == 0 then
  57.     self:remove()
  58.   end
  59. end
  60.  
  61. -- Like remove(), but not called when the map ends
  62. function entity:destroy()
  63.   if self.on_destroyed ~= nil then
  64.     self:on_destroyed()
  65.   end
  66.   self:remove()
  67. end
  68.  
  69. -- Update the trash state table and save
  70. function entity:on_destroyed()
  71.   map._trash_state[self._id] = 0
  72.   local state_string = state_tostring(map._trash_state)
  73.   print(state_string)
  74.   game:set_value(valname, state_string) -- Save the trash state
  75. end
  76.  

This custom entity can now be placed in any map, and calling entity:destroy() will cause it to save the state of that entity for that map.

Best of all, it requires not a single loop over the map entities!
RIP Aaron Swartz