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:
--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:
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.
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!