Carrying custom entities

Started by Diarandor, July 27, 2015, 05:07:47 AM

Previous topic - Next topic
Error: In on_map_changed: [string "scripts/game_manager.lua"]:41: attempt to index field 'save_between_maps' (a nil value)

I'm not sure where to include the save_between_maps.lua script I guess. I tried to load it at the beginning of my game_manager like custom_interactions.lua and collision_test_manager.lua, but I get the above error
sol.main.load_file("scripts/save_between_maps.lua")(game)

September 23, 2015, 03:18:51 AM #46 Last Edit: September 23, 2015, 03:20:51 AM by Diarandor
It seems that I forgot this piece of code. Put it in the game manager to solve that error:

game.save_between_maps = require("scripts/save_between_maps")
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."


Error: In on_map_changed: [string "scripts/save_between_maps.lua"]:99: bad argument #1 to 'pairs' (table expected, got nil)

September 23, 2015, 03:31:52 AM #48 Last Edit: September 23, 2015, 03:34:03 AM by Diarandor
Oops. Just after the line "game.save_between_maps = require("scripts/save_between_maps")" add this one:

game.independent_entities = {}


I use that list to store entities that save their position when left in some map (it can be used with the "metal ball", although that can be used for other types of entities too).
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

That works! I can now move between maps carrying the custom entity.

Anything else to test?

Yes, there are lots of things to test and features to add, like moving portable-custom entities on platforms, allowing them pushing buttons (not the ones of the engine, but custom entities for buttons), etc. I have scripts for all of this.

Now we will deal first with the customization, and second with what I call "independent entities". All is easy to use (after we implement it in your game manager script).
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

September 23, 2015, 03:56:22 AM #51 Last Edit: September 23, 2015, 03:58:47 AM by Diarandor
This is an easy example for a script to create a custom portable entity more specialized. In this example, we change the default bouncing sound and other default parameters. In this way, we can have different portable custom entities with differences in their behaviours, etc.


local entity = ...
sol.main.load_file("entities/generic_portable")(entity)

-- This function is called after the on_created function of the generic_portable entity.
function entity:on_custom_created()
  self.sound = "other_sound" -- Change the default bouncing sound.
  self.can_push_buttons = true
  self.moved_on_platform = false
  -- OTHER CHANGES HERE...
end

-- DEFINE OTHER FUNCTIONS OR WHATEVER


Note that I used the custom "event entity:on_custom_created()", this is not defined by the engine. I created it to avoid overriding the one in generic_portable.lua. This custom event is called only when defined (it is not nil) in the generic_portable script. There are other events defined there that I have used in other of my scripts, like on_custom_position_changed(), etc, and we can define more custom events in generic_portable.lua if necessary.

The point of all of this is that we have a base script for all of these portable entities.

EDIT: you can find more examples like this (some of them more complicated) in my other repository:
https://github.com/Diarandor/repository/tree/master/data/entities
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

If you put a portable entity in the map, you can carry it to other map and come back with it. But a new one will be created in the initial map. Sometimes it is desirable to avoid duplicating entities like this. For instance, if the entitiy is something unique: a key item like the "metal ball", a key that can be carried (I will use these in my game if I finish it someday), etc.

If you want to create an entity that is unique, it must be created dynamically from the map script. (You can use the generic_portable.lua script or a more specialized one.) Second, just after creating it you must add an identifier (usually a string) stored in the variable "entity.unique_id = ...". An example of this, in the map script, write:


function map:on_started()
  local game = map:get_game()
  -- Create independent entity if it does not exist!!!
  local unique_id = "write_this_entity_identifier_here"
  if not game.save_between_maps:entity_exists(game, unique_id) then
    local entity = map:create_custom_entity({x = 208, y = 197, layer = 0, direction = 0, sprite = "things/key", model = "generic_portable.lua"})
    entity.unique_id = unique_id
  end
end


The parameter "entity.unique_id" is used by the script save_between_maps.lua to detect if the entity exists in other map (or is carried by the hero), to avoid duplicating it. Of course, you can use it as a savegame variable related to the entity, as a second use.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

September 23, 2015, 04:25:45 AM #53 Last Edit: September 23, 2015, 04:40:55 AM by Diarandor
If you want in addition that entity is not destroyed when left in some map and it preserves the position, then use

entity.is_independent = true

The script save_between_maps.lua saves and loads the position automatically. That's why I call them independent entities.

All independent entities are unique, so you must always add

entity.unique_id = "whatever....."

(if you forget this you will get an error). Of course, to make this work correctly, these must be created dynamically using the "unique_id" variable.

This feature can be used with any kind of map entity (not only carryable custom entities).

In case you use this feature for some generic_portable entity with an specialized script, you can write "entity.is_independent = true" directly inside the custom event "on_custom_created()".

EDIT: when you test all of these, I will explain how to save/recover more extra information for entities carried from one map to another, or also for independent entities left behind in some map.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

@wrightmat
have you tested these last scripts?
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

Sorry, took a break from Solarus for a bit :)

This mostly works. The only issue I see is when I create an independent entity. The first time that I move the entity and return to the map, it's where I left it (as expected). But if I pick it up and throw it somewhere else a second time, it has returned to the initial location (as set by map:on_started) when I return.

September 29, 2015, 03:14:53 AM #56 Last Edit: September 29, 2015, 04:39:27 AM by Diarandor
Yes, you are right. There is some kind of problem that does not let to save the position of independent entities. It happens sometimes, and only if the entity is left in the map where it has been created (it does not happen in other maps). Thanks for notifying this problem! I will try to fix it.

EDIT: oops, I was getting an error because I forgot to set the unique identifier of the portable entities I used, which produced my problem. Now I cannot reproduce any problem.

EDIT 2: I figured out what produced your problem. I usually set the property "entity.is_independent = true" in the script of the specialized portable entity, when needed, so it is always set when the entity is created. But in your case, you are setting the property only in the map script. Hence, when the independent entity is created again (but this time it is created dynamically from the save_between_maps.lua script), that property was not set. I have improved the save_between_maps.lua script a bit, adding that property when an independent entity is created from that script, which should solve your problem.

So now, just update the save_between_maps.lua script with the new one in my repository, and then check if your problem was solved:
https://github.com/Diarandor/portable_entities
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

Another problem that I have not told you about is the following:

For independent entities, when they are being thrown we cannot save their position properly if the hero leaves the map, because the entity is still falling. One solution would have been to freeze the hero until the entity has fallen, but I refuse to do that because it would worsen the gameplay. Instead of doing that, what I did is just to disable all the enabled teletransporters for 1050 miliseconds during the falling animation (which lasts 1 second, but we wait a bit more just to be sure that the entity has fallen to the ground). This is done in the function "save_between_maps:disable_teletransporters(map)" of the save_between_maps.lua script, which is called from the generic_portable.lua script in the function "entity:throw()". Probably you did not realize yet that you cannot leave the map during the falling animation of an "independent" entity.

The problem with this is that, when the teletransporters are disabled, they are not drawn in the map (it seems that they disappear for 1 second). This is not a problem if the teletransporter has no sprite (which is the case of transitions between different maps). But if you create a normal teletransporter in the map with some sprite, you will notice that the sprite disappears for a second.

This can be solved either by using a different solution (tell me if you have some idea) like freezing the hero, or simply by not using sprites on teletransporters. (If it were possible to get the sprite of teletransporters we could create a sprite temporarily and destroy it after 1 second, but this is not possible since the engine does not allow to get the sprite of teletransporters.)

"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

Have you tried the new script save_between_maps.lua?
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

I think the old script works better - now the entity always comes back on the original map, and doesn't stay on a new map. I'm going to try placing the independent attribute in the custom entity script and using the old save_between_maps