Menu

Show posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Show posts Menu

Messages - Max

#256
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.
#257
Development / Re: Displaying an image on pause
March 03, 2018, 01:11:00 AM
Awesome! So much learning. Thanks again.
#258
Development / Enemies drowning animation?
March 02, 2018, 10:14:06 PM
Hey all.

So when you knock an enemy or throw a carried item into a hazard (water, pit, probably lava too), the engine recognizes this and will remove it and play an appropriate sound. However, it doesn't show any animation. I'm not sure if I just need to create one in the right location or what, but is there a default animation for items or enemies to fall into hazards?

I'm sure one could code a script that checks for enemies or items tossed into hazards, but I'm really just curious for now if that's something the engine can already do that I'm just missing resources for.
#259
Can confirm, sensors seem to only start on_map_transition_finished().

Interestingly, I still can't get the map to create a destination based off the saved respawn data and send the hero to it. My last attempt, to summarize, altered the map:on_opening_transition_finished() meta, so that it did two things.

If game:get_value("currently_gameover") was false, it'd save your location in respawn savegame data.
If game:get_value("currently_gameover") was true, it should create a destination from the respawn savegame data and teleport you there.

Then if you die, the engine would set "currently_gameover" true, and send you to a blank map. The blank map's script would send you to the map saved for respawning, and since "currently_gameover" was true, it SHOULD then create the destination from the respawn data and send you there.... but it didn't.



Anyway, I'm starting to feel like this technique is ballooning out. The much simpler, if less elegant, method would just be to place sensors that use game:set_starting_location() to set a manually placed destination as the starting location. It would require something like:

sensor_east:on_activated()
  game:set_starting_location(map:get_id(), "respawn_destination_1")
end

sensor_wast:on_activated()
  game:set_starting_location(map:get_id(), "respawn_destination_2")
end


to be placed on every map that is reached by scrolling, but honestly for my game, that's probably not more than a dozen maps tops, and in the time I've spent trying to figure out an alternate solution, I could have used this less elegant one and gotten it done a while ago, haha.
#260
Development / Re: Displaying an image on pause
March 02, 2018, 09:31:01 PM
That works great! I switched the horizontal alignment back to "left" both as practice for learning how alignment affects the rest of the script and because it feels more like it's written in a journal when it's aligned left. But yeah, totally just personal preference. Here's what I did, which is basically what you have with tweaked values, and it works wonderfully.

Code (lua) Select

pause_infra = {}
local current_log = sol.surface.create(144, 72)

local text_surface = sol.text_surface.create({
        font = "oceansfont",
        vertical_alignment = "top",
        horizontal_alignment = "left",
})
--note that a size is not specified for a text surface, it will be as big as the text it contains


--Returns iterator to split text at line breaks
local function line_it(text)
    assert(type(text)=="string", "Bad argument #1 to 'line_it' (string expected, got "..type(text)..")")

    text = text:gsub("\r\n", "\n"):gsub("\r", "\n").."\n" --convert instances of \r to \n and add line break to end
    return text:gmatch("([^\n]*)\n") -- Each line including empty ones.
end


function pause_infra:update_game(game)

    current_log:clear()

    local log_a_text = game:get_value("quest_log_a") --the string saved to the game value "quest_log_a" should match a key in strings.dat
    local next_line = line_it(sol.language.get_string(log_a_text)) --gets localized string in current language

    text_surface:set_text(next_line()) --line 1 of quest_log_a
    text_surface:draw(current_log, 0, 0) --renders first line of quest_log_a text and draws on current_log surface

    text_surface:set_text(next_line()) --line 2 of quest_log_a
    text_surface:draw(current_log, 0, 16)

    --NOTE: if quest_log_a contains more than 2 lines of text then the extra lines do not get drawn

    local log_b_text = game:get_value("quest_log_b")
    next_line = line_it(sol.language.get_string(log_b_text))

    text_surface:set_text(next_line()) --line 1 of quest_log_b
    text_surface:draw(current_log, 0, 39)

    text_surface:set_text(next_line()) --line 2 of quest_log_b
    text_surface:draw(current_log, 0, 55)

    --Now current_log contains and image rendered from text strings of log A & log B (up to 4 lines)
    --content won't change while pause menu open
end


local pause_img = sol.surface.create("hud/pause_infra.png")

function pause_infra:on_draw(dst_surface)
  --draw menu architecture
  pause_img:draw(dst_surface)
  --draw quest log A
  current_log:draw(dst_surface, 166, 27) --specify coordinates for where you want to draw it on the screen

end



Now, I don't ever plan on using more than two lines for the quest log items, but for someone who might, I think you'd do, for example, 4 lines, like this? (starting at line 30)

Code (lua) Select

text_surface:set_text(next_line()) --line 1 of quest_log_a
    text_surface:draw(current_log, 0, 0) --renders first line of quest_log_a text and draws on current_log surface

    text_surface:set_text(next_line()) --line 2 of quest_log_a
    text_surface:draw(current_log, 0, 16)

    text_surface:set_text(next_line()) --line 3 of quest_log_a
    text_surface:draw(current_log, 0, 32)

    text_surface:set_text(next_line()) --line 4 of quest_log_a
    text_surface:draw(current_log, 0, 48)




So far far as the line_it function, I think I've got a grasp of it. The tricky line is

return text:gmatch("([^\n]*)\n")

From what I'm researching, this returns a pattern finding iterator, which (I've still got some research on iterators to fully understand them) will basically return some separate strings based on the pattern supplied to gmatch, like gmatch(pattern). The pattern you're using is encased in quotes, because patterns need to be strings, I think? Then you've got ([^\n]*)\n.  The ^ symbol basically means, every character other than- in this case, since \n means line break, ^\n means everything other than line breaks. And this is in square brackets because the ^\n is a char-set you've created- but since you're char-set only contains one type (characters other than line breaks), is it necessary to make this its own char-set, or just a convention? Anyway, after that, you've got an asterisk, *, which is basically there to return empty lines, as it means no character will count as a character other than a line break. So all together, it basically means match and send back all characters other than line breaks that end with a line break, which since there's a line break added to the end of each line in the code above this, will send back individual lines.


This definitely requires a full scoop of lua knowledge, so I'm taking a lot out of this! Thanks so much, llamazing!
#261
Development / Re: Displaying an image on pause
March 02, 2018, 04:01:50 AM
This is great, thanks! I've got a step in the right direction, I think. So far, my code is this:

Code (lua) Select

function pause_infra:update_game(game)

    local log_a_text_key = game:get_value("quest_log_a") --this string should match a key in strings.dat
    current_log_A:clear() --erase any previous content on surface
    text_surface:set_text_key(log_a_text_key) --gets localized string in current language
    text_surface:draw(current_log_A) --puts rendered text on surface, may need to handle cases where clipping occurs
    text_surface:set_text("second line, somehow")
    text_surface:draw(current_log_A, 0, 16)

    local log_b_text_key = game:get_value("quest_log_b")
    current_log_B:clear()
    text_surface:set_text_key(log_b_text_key)
    text_surface:draw(current_log_B)
    text_surface:set_text("second line, somehow")
    text_surface:draw(current_log_B, 0, 16)
    --Now current_log_A and current_log_B contain images rendered from text string, content won't change while pause menu open

end


Once I find a way to get the part of my string after the line break, I'm pretty sure this will work. I've looked through the Solarus documentation quite a bit and I can't find any functions or anything to extract a line from a string (I've been working on this since you posted, haha). I'm currently looking through various Lua documentation and tutorials to figure this part out, any more hints as to where I'll find this sort of function?

Thanks for the help again, llamazing! I'm learning quite a bit!
#262
Development / Re: Displaying an image on pause
March 02, 2018, 02:25:15 AM
If you're willing to help, definitely! I've attached them to this post. The first one is the bitmap font I made for this, each letter is 7 pixels wide, just like the ALTTP font for convenience. I'm working in (what I think is default) 320x240 quest size. I made the pause_infra.png image the same size so that I could just draw it at 0,0. It's really just the box outlines.

Here's a few strings from the strings.dat:

text{ key = "0", value = "Find the chart salesman \n in Ballast Harbor" }   (I gave the line break thing a try to see what would happen)
text{ key = "a1", value = "Get some whisky from the secret stash" }
text{ key = "a2", value = "Meet Juglan at the pier" }
text{ key = "a3", value = "Investigat the Sodden Cormorant Tavern" }
text{ key = "a4", value = "Explore Spruce Head" }
text{ key = "a5", value = "Investigate Briarwood Distillery" }
text{ key = "a6", value = "Find a keyhole on Kingsdown Isle" }
text{ key = "a7", value = "Find clues in Hourglass Fort" }
text{ key = "b1", value = "Find the chart salesman in Ballast Harbor" }
text{ key = "b2", value = "Return the stolen charts" }
text{ key = "b3", value = "Find a keyhole for Mallow's Key" }


So if I'm understanding what I need to do to fix this, I'll want something like current_log_A1 and current_log_A2 surfaces?


The alignment thing was a quick fix, so that's nice. I'll have to think about it a little more to make sure I thoroughly understand it.
#263
Development / Re: Displaying an image on pause
March 02, 2018, 01:46:07 AM
Thanks llamazing! Yeah, the quest logs are images of text strings. I've gone and implemented your suggestions into my script, but it's given me a couple problems- the strings are cut off because they're too long and also I can only get the bottom half of the text to show. Here's what I did:

Code (lua) Select

pause_infra = {}
local current_log_A = sol.surface.create() --choose an appropriate size for the image
local current_log_B = sol.surface.create() --ditto choose size

local text_surface = sol.text_surface.create({
        font = "oceansfont",
})
--note that a size is not specified for a text surface, it will be as big as the text it contains

function pause_infra:update_game(game)

    local log_a_text_key = game:get_value("quest_log_a") --this string should match a key in strings.dat
    current_log_A:clear() --erase any previous content on surface
    text_surface:set_text_key(log_a_text_key) --gets localized string in current language
    text_surface:draw(current_log_A) --puts rendered text on surface, may need to handle cases where clipping occurs

    local log_b_text_key = game:get_value("quest_log_b")
    current_log_B:clear()
    text_surface:set_text_key(log_b_text_key)
    text_surface:draw(current_log_B)
   
    --Now current_log_A and current_log_B contain images rendered from text string, content won't change while pause menu open
end


local pause_img = sol.surface.create("hud/pause_infra.png")

function pause_infra:on_draw(dst_surface)
  --draw menu architecture
  pause_img:draw(dst_surface)

  --draw quest log A

  current_log_A:draw(dst_surface, 165, 27) --specify coordinates for where you want to draw it on the screen

  --draw quest log B
  current_log_B:draw(dst_surface, 165, 62) --places right below log_A

end


Any advice?
#264
Quote from: Diarandor on February 28, 2018, 11:35:31 PM
I am not sure what the engine does with those 2 lines of code, but I think it only calls the second one. You can call "Hero:teleport(blank_map)" in your current map and "Hero:teleport(respawn_map, respawn_destination)" in your blank map. That should work.

I thought it would work too, but I can't seem to get it to. So, here's what I can do. I can send the hero to the blank map on gameover, then in the blank map's script, send them back to the map that has the respawn location saved. However, I can't seem to get the respawn destination entity to show up and send the hero to it.

Here's the script for the blank map:
Code (lua) Select

function map:on_opening_transition_finished()
  hero:teleport(game:get_value("respawn_map")) --this line doesn't seem to have any issues, it sends the hero to the default destination though. So let's make a destination for respawning with the info we saved earlier.
          map:create_destination({
            name = "respawn_destination",
            layer = game:get_value("respawn_layer"), x = game:get_value("respawn_x"), y = game:get_value("respawn_y"),
            direction = game:get_value("respawn_direction"), save_location = "yes",
          })
  hero:teleport(game:get_value("respawn_map"), "respawn_destination")
end


My best guess why this won't work is that the map:create_distination() method is creating this destination on the blank map. So I tried rewriting that line as:

Code (lua) Select

  hero:teleport(game:get_value("respawn_map"))
  local new_map = game:get_map()
          new_map:create_destination({
            name = "respawn_destination",
            layer = game:get_value("respawn_layer"), x = game:get_value("respawn_x"), y = game:get_value("respawn_y"),
            direction = game:get_value("respawn_direction"), save_location = "yes",
          })
  hero:teleport(game:get_value("respawn_map"), "respawn_destination")


But it still won't work. For all these, I'm getting the No such destination: "respawn_destination" error. So it seems like the map:create_destination() method isn't working as I'm expecting it to. I don't believe there's a function to get the map userdata for the map you aren't currently on, which would make sense as I'm not sure it technically exists when you aren't on it. So I'm not sure exactly how the engine is interpreting this code.

Perhaps for now I'll just leave this, maybe make some map design decisions that make things a little easier for the player, and come back to it when I've learned more.
#265
It's a nice idea, but that behavior isn't actually default. The default, on restarting the game, teleports you to the last destination entity that saved your location. It might seem like that's the last map that has a different world, but that's actually only the case if you arrive on that world via a destination entity that has "on world changed" as its save location property.

But yeah, if you only change maps via teleports to a destination (ie, not side-of-map transitions), and set a new world for every map, that will work great, and I'd definitely recommend it, haha. I might end up going back and reworking all my maps so there's point-to-destination transition instead of scrolling ones : /
#266
Development / Re: Displaying an image on pause
March 01, 2018, 12:02:20 AM
Awesome! Thanks, Diarandor! I should probably get used to typing that, haha.




So for anyone interested in replicating what I've done (as it's like a baby step toward building your first pause menu, haha), here's how it goes. I'll try to do a little tutorial my past self would appreciate. My goal was, when you pause the game, a box comes up with the current step of the main quest. It's a little more complicated for me because there's a number of times when there's two main quest lines that you can do in either order, but anyway. Here's how it looks.

In my game_manager script, I've defined what to do when the game is paused. "pause_infra" is a separate script I've created that will work as a menu, which is a special way the engine can treat any table, from what I understand. So when the game is paused, I get that script using the require() syntax. Then, now that I have it, I'm going to call one of its functions, as well as start the menu via sol.menu.start()

After the game is unpaused, I want to stop the menu so it doesn't stay up. I've also got the choice to save, continue, or quit the game, which I learned from Christopho's wonderful tutorial on his youtube channel.

Code (lua) Select

--Pause Menu
function game:on_paused()

  require("scripts/menus/pause_infra")
  pause_infra:get_game(game)
  sol.menu.start(game, pause_infra)


  --save dialog
  game:start_dialog("_game.pause", function(answer)
    if answer == 1 then
      game:set_paused(false)
    elseif answer == 2 then
      game:save()
      game:set_paused(false)
    elseif answer == 3 then
      sol.main.exit()
    end
  end)


end --end of on:paused function

function game:on_unpaused()
  require("scripts/menus/pause_infra")
  sol.menu.stop(pause_infra)
end



So that's all in the game_manager script. And here's the script I have that's called "pause_infra.lua"
I've added some comments to help anyone who might be able to learn from me.

Code (lua) Select

pause_infra = {}    --this line means that we can refer to this a table called "pause_infra"
local current_log_A       -- these two lines are just setting up some local variables I'm going to be using
local current_log_B



--so if you remember in my game_manager script, I called pause_infra:get_game(game). I'm not 100% sure if I needed to type
--"game" into the argument on both ends, maybe someone can correct me if I'm wrong. But the purpose of that is to give this
--script the "game" userdata, just so it can call game:get_value(). This will allow us to display savegame values when we pause
--the game, such as what our stats are, or in this case, what step of the main quest we're on.

function pause_infra:get_game(game)

--so what's going to happen here is I'm going to get the current step of the main quest from game:get_value("quest_log_a")
--this value will be changed on each map where you complete a step. So for example, if you needed to defeat a monster,
--when that happens, I'd change the value of "quest_log_a" in the monster's on_dead() event.
--Then, I'm making a string variable called "current_log_A" which uses the number from "quest_log_a" to form the name of
--a png file I have saved. So if you're on step 4 of the quest, current_log_A would equal "hud/quest_logs/quest_logA4.png"
--Then this is repeated because I have two quest logs, an A and a B.

    local templog_a = game:get_value("quest_log_a")
    current_log_A = "hud/quest_logs/quest_log_A"..templog_a..".png"

    local templog_b = game:get_value("quest_log_b")
    current_log_B = "hud/quest_logs/quest_log_B"..templog_b..".png"

end



--Then this is where we actually display everything. The function pause_infra:on_draw() is automatically called by the engine
--when the pause_infra menu is started (which I did in my game_manager script). You have to display images in the on_draw function
--because otherwise, they'll only be drawn for 1 ms, which nobody will see. So now, every time the engine calles the :on_draw()
--function (which seems to be constantly when the menu is running), the image will be displayed.
--The way we do the image is to create a surface from a .PNG file, then just draw it.
--I'm personally doing this for 3 things, 2 quest logs and a box around them. Later, I'll add in things like your attack or defense
--stats, or an inventory, but this is just step one.

function pause_infra:on_draw(dst_surface)
  --draw menu architecture
  local pause_img = sol.surface.create("hud/pause_infra.png")
  pause_img:draw(dst_surface)

  --draw quest log A
  local log_a_img = sol.surface.create(current_log_A)
  log_a_img:draw(dst_surface)

  --draw quest log B
  local log_b_img = sol.surface.create(current_log_B)
  log_b_img:draw(dst_surface)

end





So yeah, thanks again, learning this has been super helpful, hopefully me documenting what I learned at a very fine level can help someone else trying to make a menu. I've still got a long way to go before I can make an item select menu, but this has been a good start!
#267
Your scripts / Re: Leever-type enemy
February 28, 2018, 10:53:50 PM
I've tried your first solution already for a boss, if the above ground duration is short enough it works unless you trap him in a corner. Perhaps if the on_hurt event burrowed it underground immediately it'd work.

The problem arises because the timer for burrowing underground is started from the on_restarted or on_reset event. Since the engine calls this automatically when the enemy gets hurt, it restarts the timer. I suppose you could make the timer start from an on_created event or something like this. I'll try and test that out sometime after I work on a few other issues.
#268
I actually tried this- still didn't reset the map. Perhaps it's because I used the same script to send the hero there and send her back. The game over function went something like:

Hero:teleport(blank_map)
Hero:teleport(respawn_map, respawn_destination)

Maybe it happens so quick the engine didn't register the change?
#269
Thanks Diarandor! I've got it basically working. In my game_manager script, I've used the multi events script to register a function for map:on_opening_transition_finished() to set a respawn point. Map:on_started wouldn't work because the hero would be stuck halfway into the map and couldn't move.  My code looks like this for any others interested:

Code (lua) Select

local map_meta = sol.main.get_metatable("map")
map_meta:register_event("on_opening_transition_finished", function()
    local map = game:get_map()
    game:set_value("respawn_map", map:get_id() )
    print(map:get_id().." respawn saved") --this is for testing
    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)


Then, when the game over event is called, I first teleport the hero to the saved map, then use map:create_destination() to make a respawn destination using the respawn data I've saved. Then I teleport the hero to this destination, refill her health, and do game:stop_game_over()



So, this basically works. There's a couple problems. The most easily solved is that this won't work for any maps that have map:on_opening_transition_finished() defined already until I go back and fix those to fit with the syntax for the multi events script, but that's easily solved.

More pressingly, this method of sending the hero back to the saved respawn point doesn't reset the map- for example, all enemies you've killed are still dead as if you've never left the map, any blocks you've pushed don't reset, they're still where you've pushed them. I can see this causing serious problems with puzzles, if you partially finish one, then die, you could easily trap yourself since it's basically as if you've never left the room. I haven't tested this much yet, but I'm pretty sure it'll break my game at some point- for example if you die during a boss battle and the room has been closed to trap you in, you'll then be locked outside and won't be able to get back in to fight.

I'm looking around to try to figure out if there's a way to reset the map. I've tried calling game:start(), which I thought would work because I call this after I teleport the hero to a destination that has {save_location = "yes"} as one of it's properties. Since being sent to a destination that saves your location should save your location, I'd assume that after that, calling game:start() would take you back to the saved location, but in my testing, it won't. It'll go back to the last destination that had that property.



For anyone following along and trying to replicate this, I'd definitely recommend just designing your maps so that if you want the respawn point to be saved, you have to transition to the new map using a destination. Doing it this way is complicated and could have been solved by just designing better upfront, haha.
#270
Development / Re: Displaying an image on pause
February 27, 2018, 08:21:01 PM
I'm okay with doing it the simple way at first, haha. I've looked through the code for ZS:DX, which is why I think the code I wrote in the first post should work, but it's not showing me anything. Is there a problem with that code?

From looking at the pause scripts in ZS:DX and others, it seems like this should work:
Code (lua) Select

function game:on_paused()
local pause_infra_img = sol.surface.create("hud/pause_infra.png")
end


But that doesn't show anything either. I can't get either of those to work, am I missing something?