Creating a respawn point on map changed?

Started by Max, February 27, 2018, 04:23:51 PM

Previous topic - Next topic
March 02, 2018, 10:24:00 PM #15 Last Edit: March 02, 2018, 10:26:51 PM by Diarandor
1) Again, don't create a destination just to change the position of the hero, it is unnecessary; just use "hero:set_position(x, y, layer)" after the teleportation.
2) You shouldn't use destinations in your scripts, but instead make your script independent from them. To teleport the hero to another map, you dont need a destination (there may be no destination, and the hero would still be teleported to the upper-left corner of the map). If you force your script to use a default destination and forget to put at least 1 destination in all your maps (which is annoying and easy to forget), your game would have bugs.
3) I can send you my test project (the one with the switching system of the 3 heroes) so that you can study the scripts and take what you need (but I warn you that the code might be a bit unclean and/or complicated). If I can switch from one hero to another in different maps and save their positions and states (which is harder than what you wanna do), then you should be able to respawn wherever you want without problems. You can write me a personal message to give me your mail to send you my testing project.
4) I recommend that you create your game over menu directly in that blank map you are using (in other words, use a special map and its associated script for the game over menu). It's a bit of work, but in that way you will have your map restarted for sure after respawning. (In my test project I use a map for that.)
5) Don't use sensors or other entities. You do not need them, and your maps will get dirty. Do it with elegant code and without using entities, to keep things simple. That is the easiest way (which is not easy in any case).
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

Quote from: Diarandor on March 02, 2018, 10:24:00 PM
5) Don't use sensors or other entities. You do not need them, and your maps will get dirty. Do it with elegant code and without using entities, to keep things simple. That is the easiest way (which is not easy in any case).

I know and I agree, I'd much rather do it with code- but more than that, I'd rather it worked, haha. I had forgotten about entity:set_position(), that's a useful function and should work exactly as I want! Much better than creating a destination, I just had forgotten about that method. However, I'm still having problems I don't understand.

Here's my code for my blank map. This code seems pretty straightforward, but all that's being called is hero:teleport(). Everything after that isn't called.

Code (lua) Select

function map:on_started()
  hero:teleport(game:get_value("respawn_map"))
  hero:set_position(game:get_value("respawn_x"), game:get_value("respawn_y"), game:get_value("respawn_layer"))
  print(game:get_value("respawn_x")) --for testing
  sol.audio.play_sound("secret") --for testing
  hero:set_direction(game:get_value("respawn_direction"))
end


I also tried writing this in a separate script and just having the script for the blank map requiring it. I'm at a loss here.

If you teleport the hero to a different map, the remaining code will not be called in the next map but in the current map (or maybe it is not even called). Use the "magical" multievents script and some map event to change the position in the respawning map.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

Something like the following should work
Code (lua) Select

--game_manager.lua
local map_meta = sol.main.get_metatable("map")
map_meta:register_event("on_started", function()
  local hero = game:get_hero()
  if game:get_value("currently_gameover") then
    hero:set_position( --move to respawn location
      game:get_value("respawn_x"),
      game:get_value("respawn_y"),
      game:get_value("respawn_layer")
    )
    hero:set_direction(game:get_value("respawn_direction"))
  else
    --save location in respawn savegame data
    local x, y, layer = hero:get_position()
    game:set_value("respawn_x", x) game:set_value("respawn_y", y) game:set_value("respawn_layer", layer)
    game:set_value("respawn_direction", hero:get_direction())
    game:set_value("respawn_map", self:get_id() )
  end
end)

-----

--blank map script
function map:on_started() --overrides the map_meta:on_started function, which is what you want
  hero:teleport(game:get_value("respawn_map"))
end

March 04, 2018, 08:57:45 PM #19 Last Edit: May 03, 2018, 09:11:23 PM by Max
ALRIGHT GUYS!
It works. There's a little bit of hacky stuff I had to do, which I will explain as I post what my code is. So the basic idea is this, for anyone who wants to join the conversation at the conclusion to implement a system like this:

STEP 1. Each time the player enters a map, the game will do one of two things. If the player has entered the map during normal gameplay, the game_manager script will put the player's location (x, y, layer, and map) and facing direction into savegame values. If, however, you enter the map during the respawning process, you will be moved to the saved location using hero:change_position(). It is of note that this has to be the map:on_opening_transition_finished() event, NOT map:on_started(). Part of the opening transition is moving the player actually onto the map, if you do map:on_started, the respawn location that gets saved is halfway onto the map from the edge if you entered by the side of the map. The player will be frozen halfway onto the map, although they can leave the map to reset, but anyway. You've gotta store the location data from on_opening_transition_finished.

STEP 2. Upon a gameover state, the game_manager script sets the hero's life to whatever you want (I'm using 80% full, most Zelda games do 3-4 hearts), then teleports the player to a blank map used only for respawning. Game:start() is never called, notably, because that would reset the player to the last saved DESTINATION, and often the player enters a map without using a destination, which was the whole point of this system.

STEP 3. This blank map has overriding behavior for map:on_opening_transition_finished(), because we don't want this location to be saved as a respawn point or the engine to try and move the hero to the restart position. The script for the respawn map creates a black surface (I made a menu called black screen for this) to hide what's about to happen. Then it teleports the hero to the map saved back in step one.
-HOWEVER, since we didn't specify a destination, you'll be taken to some destination on the map that's the default one, or else put at 0,0, either way, this is undesirable. So once you've been teleported there, the game_manager script will kick back in. This time, instead of saving your location (since we're in the respawning process), the game will use hero:change_position() to move the hero to the position we saved. The black screen hiding what's going on is for this purpose. Otherwise, we'd see the hero end up at some other location then be immediately moved to the saved one.
-Seeing the hero moved first to a wrong position, then the right one, could be avoided by having the hero:change_position() be called from a map:on_started() meta event instead of map:on_opening_transition_finished(). However, I just didn't want to go back and change all the syntax of everything I'd coded that already used that event, so I used the hacky method of just throwing up a black surface to hide everything. Then I take the surface away once everything is in place.




So all in all, the ingredients you'll need to recreate a system like this are:
1. game_manager script
2. a blank room
3. a menu that's a black surface to hide the untidy parts.


Game manager script:
Code (lua) Select

--Set Respawn point whenver map changes

local map_meta = sol.main.get_metatable("map")
map_meta:register_event("on_opening_transition_finished", function()

  if game:get_value("gameovering") == true then --if we're in the respawning process, move the hero to the saved position
    game:set_value("gameovering", false)
    local hero = game:get_hero()
    hero:set_position(game:get_value("respawn_x"), game:get_value("respawn_y"), game:get_value("respawn_layer"))
    hero:set_direction(game:get_value("respawn_direction"))
    require("scripts/menus/respawn_screen")
    sol.menu.stop(respawn_screen) --take away the black curtain that hides the messiness.

  else --if it's normal gameplay, not the respawning process.
    local map = game:get_map()
    game:set_value("respawn_map", map:get_id() ) --savegame value "respawn map" is this new map's ID
--    print(map:get_id().." respawn saved")
    local hero = game:get_hero()
    local x, y, layer = hero:get_position()
    game:set_value("respawn_x", x) game:set_value("respawn_y", y) game:set_value("respawn_layer", layer)
    game:set_value("respawn_direction", hero:get_direction())

  end
end)




--Game Over

function game:on_game_over_started()
  local hero = game:get_hero()
  hero:set_animation("dead")
  sol.audio.play_sound("hero_dying")
  sol.timer.start(game, 1500, game_over_stuff)
end


local function game_over_stuff()
    -- some of this is just because my game has elixers, which function like the potions from Zelda gameboy games. They'll heal you
    --when you die if you have them in your inventory.

    local elixer = game:get_item("elixer")
    local amount_elixer = elixer:get_amount()
    local hero = game:get_hero()

    if amount_elixer > 0 then --you can ignore this case if you don't have a magic potion thing system
      game:set_life(game:get_value("elixer_restoration_level"))
      hero:set_animation("walking")
      elixer:remove_amount(1)
      game:stop_game_over()
    else
      game:start_dialog("_game.game_over", function(answer)
        --save and continue
        if answer == 2 then
          game:save()
        --contine without saving
        elseif answer == 3 then
        --quit
        elseif answer == 4 then
          sol.main.exit()
        end

    game:set_value("gameovering", true) --because we're in the respawning process
    game:set_life(game:get_max_life() * .8) --this can be set to whatever you want, but you can't leave the player with no health!
    hero:set_invincible(true, 1500) --this is just a few frames of invincibility for when you're respawned.
    hero:teleport("respawn_map") --this teleports the hero to our blank map
    game:stop_game_over()

      end) --end gameover dialog choice
    end --end "if elixers" condition
end --end gameover stuff function


Script for the blank room:
Code (lua) Select

local map = ...
local game = map:get_game()
require("scripts/menus/respawn_screen")

-- Event called at initialization time, as soon as this map becomes is loaded.
function map:on_started()
    sol.menu.start(game, respawn_screen)
    hero:teleport(game:get_value("respawn_map"))
end

--This empty function is here to override the map:on_opening_transition_finished() metatable multievent.
--It keeps the game_manager script from saving a respawn location here, or trying to send the hero to the respawn location when
--the player arrives here.
function map:on_opening_transition_finished()
end



Then I've got a little menu to hide the messiness of sending the player to a map, THEN moving them to their starting location.
Code (lua) Select

respawn_screen = {}

local black_screen = sol.surface.create()
black_screen:fill_color({0,0,0})

function respawn_screen:on_draw(dst_surface)
  black_screen:draw(dst_surface)

end




So, that works! Thanks for all the help, Diarandor and llamazing!


EDIT: For anyone wishing to replicate this- spiral staircase teleports don't play nicely with this! If you're respawned back to a map you entered via spiral staircase teleport, the engine actually starts you in a wall with these and shows the animation of the hero coming up the stairs. If you're respawned using this script, you'll be started in the wall, but the engine won't know you haven't arrived there by staircase, and you'll just be stuck in the wall. Watch out!

Nice work! When will you release a demo? :P
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

I'm hoping to get it out this week. You guys have been super helpful with some of the major things I've been wanting to get done first- this respawn system and a quest log. I've got maybe half a dozen little things (like, you can't read this paper on the desk in this house, this puzzle is slightly too hard) that I can fix in an hour, then there's a tavern in one town I need to finish, then it's one last playtest and last-minute balancing before I send it out.

I'm eager to get it out because I don't have it backed up anywhere currently and it's making me nervous. Anyone have advice on a best place to host a file? My google drive is pretty full of music/video production stuff, and it seems like github is most people around here's choice. Is that free?

March 04, 2018, 10:48:26 PM #22 Last Edit: March 04, 2018, 10:51:57 PM by Diarandor
Advice: make several videos on youtube before releasing it with your art. You can keep them hidden if you want. Those could be a partial (but nice) proof of your autorship. In any case, I am not a lawyer, but it is unlikely that someone would steal your art (one can use my own art instead, which is free and hence legal for commercial use). When you publish it, put the licenses of each file somewhere in the data folder (it is usually done in a file "license.txt").

EDIT: github is free (as in free beer, but not as in freedom, I think) and a nice place to put the data of your demo. You can use your email and other devices for backup copies, just in case your computer breaks.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

GitHub works well for when you want to collaborate with others on a project or track changes. It is free for public repositories (that anyone can access).

Hey! So I thought this script was working out all great and fine, but I've encountered an issue. If you use any of the "spiral staircase" modifiers to your teleports, you can get stuck in a wall if you respawn.

The problem is this- the spiral staircase things work like this I think: they change the behavior of your teleport, so that instead of going right to the destination, the put you 16px to the right or left of your destination (in a wall), then the engine automatically moves the hero into the destination then down 16px while playing an animation to simulate the staircase thing.

The problem comes if you die in the room you entered by staircase. Since that's your respawn map, this script will send you back to wherever you entered the map. However, if you entered via spiral staircase, you technically entered the map 16px to the right or left of where the stairs (and your destination entity) are. So you'll respawn trapped in the wall, since the respawn script doesn't know if you came into the room via staircase.


So I'm wondering, is there any way to let the script know that you entered the map via spiral staircase?

It should be possible. Spiral staircase teleporters should notify to your respawn script immediately after the teleportation, and the respawn script should compute the correct respawn position: +16 pixels in the direction of the stairs.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

Okay, that makes sense. Do you know how I can get the staircase teleporters to notify my script? The only way I can think of it map:get_entities by type, getting spiral staircase types, and comparing all their coordinates to the hero's coordinate, and if any are the same, having a conditional branch that set the respawn x-value to +-16.

But that seems like an inordinately difficult way to do it, but is that the only way?

May 03, 2018, 11:43:16 PM #27 Last Edit: May 03, 2018, 11:45:14 PM by Diarandor
I got a new idea: use the state of the hero "stairs" together with some event: on_state_changed or on_map_changed. I'm not sure if that state is used for both types of stairs, but if so, you may need to detect the good case somehow. Maybe using the direction of the hero instead of the direction of the stairs could work too.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."