Author Topic: A possible bug when creating an entity from another script  (Read 308 times)

Diarandor

  • Hero Member
  • *****
  • Posts: 666
  • Cats are cool! (ΦωΦ)
    • View Profile
A possible bug when creating an entity from another script
« on: February 11, 2017, 08:01:33 pm »
Hi @Christopho! I need some help. I have a problem with my rain script. I don't kow if this is a bug or something I did wrong. I think the problem started when I began refactoring my code, but I am not sure. My (unfinished) rain script is this one:

Code: Lua
  1. -- Rain manager script.
  2. --[[
  3. To add this script to your game, call from game_manager script:
  4.     require("scripts/weather/rain_manager")
  5.  
  6. The functions here defined are:
  7.     game:get_rain_type(world)
  8.     game:set_rain_type(world, rain_type)
  9.  
  10. Rain types: nil (no rain), "rain", "storm".
  11. --]]
  12.  
  13. -- This script requires the multi_event script:
  14. require("scripts/multi_events")
  15. local rain_manager = {}
  16.  
  17. local game_meta = sol.main.get_metatable("game")
  18. local map_meta = sol.main.get_metatable("map")
  19.  
  20.  
  21. -- Default settings. Change these for testing.
  22. local rain_enabled = true -- Do not change this property, unless you are testing.
  23. local lightning_enabled = false
  24. local rain_speed = 100 -- Default drop speed 100.
  25. local drop_max_distance = 300 -- Max possible distance for drop movements.
  26. local drop_delay = 10 -- Delay between drops, in milliseconds.
  27. local drop_sprite_id = "test/rain"
  28.  
  29. -- Initialize rain on maps when necessary.
  30. game_meta:register_event("on_map_changed", function()
  31.   if self ~= nil then
  32.     local map = self:get_map()
  33.     rain_manager:update_rain(map)
  34.   end
  35. end)
  36.  
  37. -- Get the raining state for a given world.
  38. function game_meta:get_rain_type(world)
  39.   local rain_type = self:get_value("rain_state_" .. world)
  40.   return rain_enabled and rain_type
  41. end
  42. -- Set the raining state for a given world.
  43. function game_meta:set_rain_type(world, rain_type)
  44.   -- Update savegame variable.
  45.   self:set_value("rain_state_" .. world, rain_type)
  46.   -- Check if rain is necessary: if we are in that world and rain is needed.  
  47.   local current_world = self:get_map():get_world()
  48.   local rain_needed = (current_world == world) and rain_enabled and rain_type
  49.   if (not rain_needed) then return end -- Do nothing if rain is not needed!
  50.   -- We need to start the rain in the current map.
  51.   local map = self:get_map()
  52.   rain_manager:start_rain(map)
  53. end
  54.  
  55.  
  56. -- Create rain if necessary when entering a new map.
  57. function rain_manager:update_rain(map)
  58.   -- Get rain state in this world.
  59.   local world = map:get_world()
  60.   local rain_type = map:get_game():get_rain_type(world)
  61.   -- Start rain if necessary.
  62.   if rain_type == "rain" then
  63.     self:start_rain(map)
  64.   elseif rain_type == "storm" then
  65.     self:start_storm(map)
  66.   end
  67. end
  68.  
  69. -- Define function to create splash effects.
  70. -- If no parameters x, y are given, the position is random.
  71. local function create_drop_splash(map, x, y)
  72.   local max_layer = map:get_max_layer()
  73.   local min_layer = map:get_min_layer()
  74.   local camera = map:get_camera()
  75.   local cx, cy, cw, ch = camera:get_bounding_box()
  76.   local drop_properties = {direction = 0, x = 0, y = 0, layer = max_layer,
  77.     width = 16, height = 16, sprite = drop_sprite_id}
  78.   -- Initialize parameters.
  79.   local x = x or cx + cw * math.random()
  80.   local y = y or cy + ch * math.random()
  81.   local layer = max_layer
  82.   while map:get_ground(x,y,layer) == "empty" and layer > min_layer do
  83.     layer = layer - 1 -- Draw the splash at the lower layer we can.
  84.   end
  85.   -- Do not draw splash over some bad grounds: "hole" and "lava".
  86.   local ground = map:get_ground(x, y, layer)
  87.   if ground ~= "hole" and ground ~= "lava" then
  88.     drop_properties.x = x
  89.     drop_properties.y = y
  90.     drop_properties.layer = layer
  91.     local drop_splash = map:create_custom_entity(drop_properties)
  92.     assert(drop_splash ~= nil)
  93.     local splash_sprite = drop_splash:get_sprite()
  94.     splash_sprite:set_animation("drop_splash")
  95.     splash_sprite:set_direction(0)
  96.     function splash_sprite:on_animation_finished() drop_splash:remove() end
  97.   end
  98. end
  99.  
  100. -- Define function to create drops.
  101. -- If no parameters x, y are given, the position is random.
  102. local function create_drop(map, x, y)
  103.   local max_layer = map:get_max_layer()
  104.   local min_layer = map:get_min_layer()
  105.   local camera = map:get_camera()
  106.   local cx, cy, cw, ch = camera:get_bounding_box()
  107.   local drop_properties = {direction = 0, x = 0, y = 0, layer = max_layer,
  108.     width = 16, height = 16, sprite = drop_sprite_id}
  109.   -- Initialize parameters.
  110.   drop_properties.x = x or cx + cw * math.random() + 30
  111.   drop_properties.y = y or cy + ch * math.random() - 100
  112.   drop_properties.layer = max_layer
  113.   local drop = map:create_custom_entity(drop_properties)
  114.   local m = sol.movement.create("straight")
  115.   m:set_angle(7 * math.pi / 5)
  116.   m:set_speed(rain_speed)
  117.   local random_max_distance = math.random(1, drop_max_distance)
  118.   m:set_max_distance(random_max_distance)
  119.   m:set_ignore_obstacles()
  120.   function m:on_finished() drop:remove() end
  121.   function m:on_obstacle_reached() drop:remove() end
  122.   function drop:on_removed()
  123.     local x, y = drop:get_position()
  124.     create_drop_splash(map, x, y)
  125.   end
  126.   m:start(drop)
  127. end
  128.  
  129.  
  130. -- Start rain in the current map.
  131. function rain_manager:start_rain(map)
  132.   local max_layer = map:get_max_layer()
  133.   local min_layer = map:get_min_layer()
  134.   local camera = map:get_camera()
  135.   local drop_properties = {direction = 0, x = 0, y = 0, layer = max_layer,
  136.     width = 16, height = 16, sprite = drop_sprite_id}
  137.   -- Initialize random seed for positions.
  138.   math.randomseed(os.time())
  139.  
  140.   -- Start timer to draw rain drops.
  141.   sol.timer.start(map, drop_delay, function()
  142.     -- Create drops on random positions.
  143.     create_drop(map)
  144.     -- Repeat loop.
  145.     return true
  146.   end)
  147. end
  148.  
  149. -- Stop rain in the current map.
  150. function rain_manager:stop_rain(map)
  151.  
  152. end
  153.  
  154. -- Return rain manager.
  155. return rain_manager
  156.  

The problem appears when leaving a map with rain to another (where there is no rain, but this is probably not important). The bug appears in the console (no crash) at lines 92-93, because the entity "drop_splash" does not exist in that moment. I know that this can be fixed with an extra condition "if drop_splash ~= nil then blablabla end", but I'd like to know why does this happen.

I had the same problem at lines 31-32, because the "self" variable does not exist sometimes, and the condition "if self ~= nil then" fixed this. Why do not these variables exist when these events are called? Is this a bug?
Thanks in advance for the help.

llamazing

  • Jr. Member
  • **
  • Posts: 58
    • View Profile
Re: A possible bug when creating an entity from another script
« Reply #1 on: February 11, 2017, 10:42:28 pm »
I don't know if this is related to the problems you are having, but on quick inspection of your code, I see two problems.

Lines 71 - 97
Code: Lua
  1. local function create_drop_splash(map, x, y) --line 71
  2.   --skip
  3.   local drop_properties = {direction = 0, x = 0, y = 0, layer = max_layer,
  4.     width = 16, height = 16, sprite = drop_sprite_id}
  5.   -- Initialize parameters.
  6.   local x = x or cx + cw * math.random() --these x & y are not used anywhere
  7.   local y = y or cy + ch * math.random()
  8.   local layer = max_layer
  9.   --skip
  10.   local ground = map:get_ground(x, y, layer) --these x & y override the previously defined locals
  11.   if ground ~= "hole" and ground ~= "lava" then
  12.     drop_properties.x = x --these x & y are defined by map:get_ground(x,y,layer)
  13.     drop_properties.y = y
  14.     drop_properties.layer = layer
  15.     --skip
  16.   end

EDIT: Disregard second problem
« Last Edit: February 11, 2017, 10:44:33 pm by llamazing »

Diarandor

  • Hero Member
  • *****
  • Posts: 666
  • Cats are cool! (ΦωΦ)
    • View Profile
Re: A possible bug when creating an entity from another script
« Reply #2 on: February 11, 2017, 11:04:57 pm »
I don't know if this is related to the problems you are having, but on quick inspection of your code, I see two problems.

Lines 71 - 97
Code: Lua
  1. local function create_drop_splash(map, x, y) --line 71
  2.   --skip
  3.   local drop_properties = {direction = 0, x = 0, y = 0, layer = max_layer,
  4.     width = 16, height = 16, sprite = drop_sprite_id}
  5.   -- Initialize parameters.
  6.   local x = x or cx + cw * math.random() --these x & y are not used anywhere
  7.   local y = y or cy + ch * math.random()
  8.   local layer = max_layer
  9.   --skip
  10.   local ground = map:get_ground(x, y, layer) --these x & y override the previously defined locals
  11.   if ground ~= "hole" and ground ~= "lava" then
  12.     drop_properties.x = x --these x & y are defined by map:get_ground(x,y,layer)
  13.     drop_properties.y = y
  14.     drop_properties.layer = layer
  15.     --skip
  16.   end

EDIT: Disregard second problem

I don't see any problem with these variables (x, y). I think the local variables of the inner scope are overriding the ones of the outer scope, so there should not be any problem.

Diarandor

  • Hero Member
  • *****
  • Posts: 666
  • Cats are cool! (ΦωΦ)
    • View Profile
Re: A possible bug when creating an entity from another script
« Reply #3 on: February 11, 2017, 11:07:36 pm »
Should I share my rain sprite files? I don't know if that may help.

Christopho

  • Administrator
  • Hero Member
  • *****
  • Posts: 1001
    • View Profile
Re: A possible bug when creating an entity from another script
« Reply #4 on: February 12, 2017, 08:11:30 am »
self only exists in methods (when using the colon syntax). When you call register_event(), pass a function that takes the game parameter and don't use self.

Diarandor

  • Hero Member
  • *****
  • Posts: 666
  • Cats are cool! (ΦωΦ)
    • View Profile
Re: A possible bug when creating an entity from another script
« Reply #5 on: February 12, 2017, 12:50:23 pm »
Thanks a lot Chris! That solved one of the problems. But I still get this error:
Code: [Select]
Error: In on_removed: [string "scripts/weather/rain_manager.lua"]:94: attempt to index local 'drop_splash' (a nil value)
The new version of the script is this:
Code: Lua
  1. -- Rain manager script.
  2. --[[
  3. To add this script to your game, call from game_manager script:
  4.     require("scripts/weather/rain_manager")
  5.  
  6. The functions here defined are:
  7.     game:get_raining(world)
  8.     game:set_raining(world, rain_type)
  9.  
  10. Rain types: nil (no rain), "rain", "storm".
  11. --]]
  12.  
  13. -- This script requires the multi_event script:
  14. require("scripts/multi_events")
  15. local rain_manager = {}
  16.  
  17. local game_meta = sol.main.get_metatable("game")
  18. local map_meta = sol.main.get_metatable("map")
  19.  
  20.  
  21. -- Default settings. Change these for testing.
  22. local rain_enabled = true -- Do not change this property, unless you are testing.
  23. local lightning_enabled = false
  24. local rain_speed = 100 -- Default drop speed 100.
  25. local drop_max_distance = 300 -- Max possible distance for drop movements.
  26. local drop_delay = 10 -- Delay between drops, in milliseconds.
  27. local drop_sprite_id = "test/rain"
  28.  
  29. -- Initialize rain on maps when necessary.
  30. game_meta:register_event("on_map_changed", function(game)
  31.     local map = game:get_map()
  32.     rain_manager:update_rain(map)
  33. end)
  34.  
  35. -- Get the raining state for a given world.
  36. function game_meta:get_rain_type(world)
  37.   local rain_type = nil
  38.   if world then
  39.     rain_type = self:get_value("rain_state_" .. world)
  40.   end
  41.   return rain_enabled and rain_type
  42. end
  43. -- Set the raining state for a given world.
  44. function game_meta:set_rain_type(world, rain_type)
  45.   -- Update savegame variable.
  46.   self:set_value("rain_state_" .. world, rain_type)
  47.   -- Check if rain is necessary: if we are in that world and rain is needed.  
  48.   local current_world = self:get_map():get_world()
  49.   local rain_needed = (current_world == world) and rain_enabled and rain_type
  50.   if (not rain_needed) then return end -- Do nothing if rain is not needed!
  51.   -- We need to start the rain in the current map.
  52.   local map = self:get_map()
  53.   rain_manager:start_rain(map)
  54. end
  55.  
  56.  
  57. -- Create rain if necessary when entering a new map.
  58. function rain_manager:update_rain(map)
  59.   -- Get rain state in this world.
  60.   local world = map:get_world()
  61.   local rain_type = map:get_game():get_rain_type(world)
  62.   -- Start rain if necessary.
  63.   if rain_type == "rain" then
  64.     self:start_rain(map)
  65.   elseif rain_type == "storm" then
  66.     self:start_storm(map)
  67.   end
  68. end
  69.  
  70. -- Define function to create splash effects.
  71. -- If no parameters x, y are given, the position is random.
  72. local function create_drop_splash(map, x, y)
  73.   local max_layer = map:get_max_layer()
  74.   local min_layer = map:get_min_layer()
  75.   local camera = map:get_camera()
  76.   local cx, cy, cw, ch = camera:get_bounding_box()
  77.   local drop_properties = {direction = 0, x = 0, y = 0, layer = max_layer,
  78.     width = 16, height = 16, sprite = drop_sprite_id}
  79.   -- Initialize parameters.
  80.   local x = x or cx + cw * math.random()
  81.   local y = y or cy + ch * math.random()
  82.   local layer = max_layer
  83.   while map:get_ground(x,y,layer) == "empty" and layer > min_layer do
  84.     layer = layer - 1 -- Draw the splash at the lower layer we can.
  85.   end
  86.   -- Do not draw splash over some bad grounds: "hole" and "lava".
  87.   local ground = map:get_ground(x, y, layer)
  88.   if ground ~= "hole" and ground ~= "lava" then
  89.     drop_properties.x = x
  90.     drop_properties.y = y
  91.     drop_properties.layer = layer
  92.     local drop_splash = map:create_custom_entity(drop_properties)
  93. --    if drop_splash ~= nil then
  94.       local splash_sprite = drop_splash:get_sprite()
  95.       splash_sprite:set_animation("drop_splash")
  96.       splash_sprite:set_direction(0)
  97.       function splash_sprite:on_animation_finished() drop_splash:remove() end
  98. --    end
  99.   end
  100. end
  101.  
  102. -- Define function to create drops.
  103. -- If no parameters x, y are given, the position is random.
  104. local function create_drop(map, x, y)
  105.   local max_layer = map:get_max_layer()
  106.   local min_layer = map:get_min_layer()
  107.   local camera = map:get_camera()
  108.   local cx, cy, cw, ch = camera:get_bounding_box()
  109.   local drop_properties = {direction = 0, x = 0, y = 0, layer = max_layer,
  110.     width = 16, height = 16, sprite = drop_sprite_id}
  111.   -- Initialize parameters.
  112.   drop_properties.x = x or cx + cw * math.random() + 30
  113.   drop_properties.y = y or cy + ch * math.random() - 100
  114.   drop_properties.layer = max_layer
  115.   local drop = map:create_custom_entity(drop_properties)
  116.   local m = sol.movement.create("straight")
  117.   m:set_angle(7 * math.pi / 5)
  118.   m:set_speed(rain_speed)
  119.   local random_max_distance = math.random(1, drop_max_distance)
  120.   m:set_max_distance(random_max_distance)
  121.   m:set_ignore_obstacles()
  122.   function m:on_finished() drop:remove() end
  123.   function m:on_obstacle_reached() drop:remove() end
  124.   function drop:on_removed()
  125.     local x, y = drop:get_position()
  126.     create_drop_splash(map, x, y)
  127.   end
  128.   m:start(drop)
  129. end
  130.  
  131.  
  132. -- Start rain in the current map.
  133. function rain_manager:start_rain(map)
  134.   local max_layer = map:get_max_layer()
  135.   local min_layer = map:get_min_layer()
  136.   local camera = map:get_camera()
  137.   local drop_properties = {direction = 0, x = 0, y = 0, layer = max_layer,
  138.     width = 16, height = 16, sprite = drop_sprite_id}
  139.   -- Initialize random seed for positions.
  140.   math.randomseed(os.time())
  141.  
  142.   -- Start timer to draw rain drops.
  143.   sol.timer.start(map, drop_delay, function()
  144.     -- Create drops on random positions.
  145.     create_drop(map)
  146.     -- Repeat loop.
  147.     return true
  148.   end)
  149. end
  150.  
  151. -- Stop rain in the current map.
  152. function rain_manager:stop_rain(map)
  153.  
  154. end
  155.  
  156. -- Return rain manager.
  157. return rain_manager
  158.  

My guess of what might be happening is that when the map is gonna change, the "drop" entities are removed so the event "drop:on_removed()" in line 124 is called, hence the function "create_drop_splash(map, x, y)" tries to create a drop_splash which I think is not created because the map is being destroyed, so that drop_splash becomes nil and the error appears... could this be the problem?
I will now try not to use the event "drop:on_removed()", which is probably a bad idea...

Diarandor

  • Hero Member
  • *****
  • Posts: 666
  • Cats are cool! (ΦωΦ)
    • View Profile
Re: A possible bug when creating an entity from another script
« Reply #6 on: February 12, 2017, 12:57:58 pm »
Yes, that solved my other problem. I guess that my guess was correct, but I could be wrong.

From now on I will be more careful with the "entity:on_removed" events, since I guess these are called by the engine too when leaving maps and, in these particular cases, new entities cannot be created by this function because the map is being removed too...