Displaying an image on pause

Started by Max, February 27, 2018, 04:32:05 PM

Previous topic - Next topic
Hey all, sorry to kind of flood the board with questions. I've been saving them up for a year trying to figure them out myself first but there's a few I need help with.

So, I've been trying to learn Solarus and lua coding for a year and a half, and I still can't figure out how to create a pause menu. I've basically given up and accepted this as a design limitation to constrain myself with, so my game only has two items. But anyway, all my playtesters have given me the feedback that with all the side quests in my game, they'd really like a main quest log kind of feature because they forget what they were supposed to be doing. I think that just displaying the current stage of the main quest on the pause screen is the way to go.

So from studying the various solarus team scripts, I think that the way to show an image would be something like this:
Code (lua) Select

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


However, I'm really confused about how all this works. It seems like you can create a surface, but it won't be immediately drawn? So you have to draw it, but you need a surface to draw it on. But wouldn't the second surface need to be drawn on a surface too? Ahh!

That's a very simple way

But the best way to display a pause menu, is by doing a menu
You can look at how it is done on Mystery of Solarus DX, that's mostly how I've learned to use Solarus

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?

The command "pause_infra_img:draw(pause_surface)" draws that image only once (for 1 millisecond of maybe less).
To show the image, you need to draw it at each iteration, and for that, you need to call it inside some "on_draw" event.
Check scripts of finished projects to learn where that drawing code should be.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

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!

I'm assuming "hud/quest_logs/quest_log_A"..templog_a..".png" is an image of a text string. You'll have more flexibility if you use text_surfaces instead, and it will make localization easier. What you can do instead is create entries in strings.dat to define all of your quest log strings. Here is an example of how I would do it.

Code ( lua) Select
--strings.dat
text{ key = "objective.save_princess", value = "Save the princess in the Mountain Temple" }
text{ key = "objective.defeat_water_boss", value = "Defeat the boss of the Water Temple" }

---------------

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

local text_surface = sol.text_surface.create({
--change these values as you see fit
font = "font_name",
rendering_mode = "solid", --or "antialiasing", depends on font (use solid for bitmap font)
color = {255, 255, 255}, --white, omit if using bitmap font
font_size = 12, --omit if using bitmap font
})
--note that a size is not specified for a text surface, it will be as big as the text it contains

function pause_infra:get_game(game) --if it were me I would name this pause_infra:update(game), and likewise call it any time the contents to be displayed have changed
    local log_a_text_key = game:get_value("quest_log_a") --this string should match a key in strings.dat
    text_surface:set_text_key(log_a_text_key) --gets localized string in current language
    current_log_A:clear() --erase any previous content on surface
    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")
    text_surface:set_text_key(log_b_text_key)
    current_log_B:clear()
    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") --move this function outside on_draw so you aren't destroying and re-creating this image a zillion times per second. You can do this because the image never changes

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

  --draw quest log A
  --delete this line: local log_a_img = sol.surface.create(current_log_A) --the contents of this surface don't change while the pause menu is open, so only create this surface whenever the pause menu is opened
  current_log_A:draw(dst_surface, 100, 250) --specify coordinates for where you want to draw it on the screen

  --draw quest log B
  --delete this line: local log_b_img = sol.surface.create(current_log_B) --the contents of this surface don't change while the pause menu is open, so only create this surface whenever the pause menu is opened
  current_log_B:draw(dst_surface, 100, 300) --places right below log_A
end


On another note, you want to do as little stuff as possible in in the on_draw() function as possible since it gets updated so frequently (see my comments above). In your case, the contents of your pause menu won't change (unless you decide to make it interactive). So what you want to do instead is determine what content to display whenever the pause menu is opened, and create intermediate surfaces containing the corresponding images. No need to regenerate them on every call of on_draw().

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?

March 02, 2018, 02:12:33 AM #7 Last Edit: March 02, 2018, 02:16:10 AM by llamazing
Only the bottom of the text is showing because the default vertical alignment is middle. setting the vertical_alignment = "top" somewhere between lines 11-16 of the code I posted will fix that problem. The alternate way to handle this problem is to specify a y offset at lines 24 & 29 of my code. You are drawing the text_surface at coordinates (0,0) of the intermediate surface. If the intermediate surface has a height of 32 and your font is the same, then you could do text_surface:draw(current_log_A, 0 ,16), for example. (0,16) is where you want the middle left point of the text to go using top and middle alignment. For left and top alignment you want the left top point to be at (0,0).

The second problem with the text being cut off is trickier, and I didn't consider the possibility of you wanting the objectives to wrap across multiple lines of text. Your options are the following:
1) Fix where you want line breaks by adding "\n" in your string.dat text values. You will then have to split apart the text at the line breaks writing one line of text to the text_surface and intermediate surfaces at a time (and offset the lines appropriately when you specify the y coordinate to draw it at)
2) You could auto-wrap the text by splitting the text to a new line once it exceeds your max width (this would be done in pause_infra:get_game()). Like before you'd have to write each line to the text_surface separately. Keep in mind with this second method that the way you determine the text width varies depending on whether you are using a bitmap or non-bitmap font.

I'm willing to help you with this if you provide your pause_infra.png image, the font you are using for the objectives, and a few representative text strings from strings.dat. It's hard to give an example without knowing some of the specifics of your implementation.

EDIT: please also specify your quest size and give the x,y coordinates of where you are drawing pause_infra.png

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.

Okay, great! Why don't you see if you can figure out how to do it, and I'll post how I would do it, then you can compare.

Here are some hints:
No need for additional surfaces. In fact, you can do it with just one text_surface and one surface (i.e. current_log). Size current_log to be something like 134x72 judging by the pause_infra.png image.

Then specify a different y offset for each line of text when you call text_surface:draw(current_log) (first line at 0,0; second line at 0,16; etc. Adjust appropriately for more white-space between lines).

For determining where the line breaks occur with a bitmap font (assuming you don't do the \n method), you can simply count 19 characters (133 pixels) before starting the next line.

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!

March 02, 2018, 04:38:55 AM #11 Last Edit: March 02, 2018, 04:41:38 AM by llamazing
Hint2: Use the function sol.language.get_string(log_a_text_key) to get a localized string that you can play with instead of setting it directly using text_surface:set_text_key(log_a_text_key). Once you've split the string to the substring you want to use, set the text of the text_surface with text_surface:set_text(my_substring)

For help with splitting a string, see http://lua-users.org/wiki/StringLibraryTutorial
string.find() and string.sub() should be useful.

March 02, 2018, 06:36:41 AM #12 Last Edit: March 02, 2018, 06:39:52 AM by llamazing
Here is how I would do the script:
Code (lua) Select
pause_infra = {}
local current_log = sol.surface.create(138,72) --22 characters across, 4+ lines tall

local text_surface = sol.text_surface.create({
    vertical_alignment = "top",
    horizontal_alignment = "center",
    font = "oceansfont",
})
--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() --erase any previous content on surface

    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, 69, 0) --renders first line of quest_log_a text and draws on current_log surface
        --x value 69 because horizontal alignment is center (138/2 = 69)

    text_surface:set_text(next_line()) --line 2 of quest_log_a
    text_surface:draw(current_log, 69, 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, 69, 39)

    text_surface:set_text(next_line()) --line 2 of quest_log_b
    text_surface:draw(current_log, 69, 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
    current_log:draw(dst_surface, 167, 27) --specify coordinates for where you want to draw it on the screen
end


I used center horizontal alignment because it looked better. I had to adjust the x-coordinates to compensate, see my comments.

I went the route of manual line breaks using \n. The disadvantage is you have to ensure none of your lines of text exceed 22 characters or it will get clipped. But it also gives you more control of choosing where the line break occurs. With the other method you might get a first line that is really long and a second line that is only one short word.

Here are your string.dat strings with the \n breaks added:
text{ key = "0", value = "Find the chart salesman\nin Ballast Harbor" }
text{ key = "a1", value = "Get some whisky from\nthe secret stash" }
text{ key = "a2", value = "Meet Juglan\nat the pier" }
text{ key = "a3", value = "Investigate the Sodden\nCormorant Tavern" }
text{ key = "a4", value = "Explore Spruce Head" }
text{ key = "a5", value = "Investigate Briarwood\nDistillery" }
text{ key = "a6", value = "Find a keyhole\non Kingsdown Isle" }
text{ key = "a7", value = "Find clues in\nHourglass Fort" }
text{ key = "b1", value = "Find the chart salesman\nin Ballast Harbor" }
text{ key = "b2", value = "Return the\nstolen charts" }
text{ key = "b3", value = "Find a keyhole\nfor Mallow's Key" }

Also note that you can use the following notation in string.dat if you prefer. It might make it easier to tell if you've exceeded 22 characters on a given line. With this style, the line break is an actual line break rather than \n.
Code (lua) Select
text{ key = "0", value = [[
Find the chart salesman
in Ballast Harbor
]]}


For splitting the strings into lines I borrowed the line iterator from the ATTP dialog box script. Look up iterators if you are having trouble following how it works.

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!

Quote from: Max on March 02, 2018, 09:31:01 PMNow, 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)
Correct

Quote from: Max on March 02, 2018, 09:31:01 PMThe pattern you're using is encased in quotes, because patterns need to be strings, I think?
Correct

Quote from: Max on March 02, 2018, 09:31:01 PMbut 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?
It is necessary to use a char-set because the ^ character has a different meaning when used outside of the [] brackets (forces the match to be from the beginning of the string when used as the first character of the pattern).

Quote from: Max on March 02, 2018, 09:31:01 PMAnyway, 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.
Correct. Note that the asterisk also serves the purpose of capturing more than one consecutive instance of the [^\n] char-set and will match as many as possible. If you didn't want to return empty lines then you could change the * to +.