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 - llamazing

#136
Development / Re: Displaying an image on pause
March 02, 2018, 06:36:41 AM
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.
#137
Development / Re: Displaying an image on pause
March 02, 2018, 04:38:55 AM
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.
#138
Development / Re: Displaying an image on pause
March 02, 2018, 02:58:06 AM
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.
#139
Development / Re: Displaying an image on pause
March 02, 2018, 02:12:33 AM
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
#140
Maps are destroyed when you leave them and regenerated when you enter them again. If you put a print statement as the first line in a map script you can see that it gets called every time you enter the map. This means you can only create a destination on the current map.

What you should do is send the hero to the blank map on game over, then in the blank map script send them to the map with respawn location saved at the default destination, then as the hero enters that map (in map_meta:on_started()) create the respawn destination and redirect the hero there. The blank map would have to override the map_meta behavior.

I'm not sure if first sending the hero to a default location on the respawn map before sending them to the real destination could cause problems (like triggering things in your map script you don't want to trigger yet). From in the quest editor on every map you might have to create a dummy destination that is somewhere you can safely send the hero to initially.
#141
Quote from: Max on March 01, 2018, 03:33:01 AM
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.

Interesting, I did not know it worked that way. In that case, Diarandor's suggestion should work.
#142
Development / Re: Displaying an image on pause
March 01, 2018, 04:17:35 AM
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().
#143
I think a simple solution to your problem would be to assign a unique world to every map. Then the default behavior would be that the player respawns at the point they entered the map, and you don't have to code anything special. Could be a problem though if you are already using the world property for something else.
#144
Your projects / Re: Ocean's Heart
February 28, 2018, 01:02:41 AM
That looks amazing. Nice work!
#145
Development / Re: get amount
February 19, 2018, 06:45:40 AM
Try this
Code ( lua) Select
function shop_heart:on_interaction()
  if game:get_life() == game:get_max_life() then
    game:start_dialog("already_have")
    sol.audio.play_sound("bounce")
  elseif game:get_money() < 10 then
    game:start_dialog("cant_buy")
    sol.audio.play_sound("bounce")
  else
    game:start_dialog("empty", function()
      hero:start_treasure("heart", 1)
      game:remove_money(10)
    end)
  end
end
#146
Development / Re: NPC Dialogue
February 09, 2018, 03:54:02 AM
Make sure you have the following line in main.lua in the sol.main:on_started() function
Code ( lua) Select
sol.language.set_language("en")
#147
Development / Re: NPC routines
December 07, 2017, 03:02:14 AM
I think there's still some insight that can be gained from my scripts. Really, the only key difference I'm seeing between my implementation and what you seek is to replace my hard-coded path movements with a script that does the path-finding dynamically.

Of course, that is by no means a trivial task. When I was doing the planning for how to implement my script, I was thinking it would be nice to be able to draw the paths for the NPCs to follow in the map editor. You could do the next best thing and place the waypoints on the map using invisible custom entities (for example, place a waypoint at every intersection along a path and at every relevant destination). Then you could make use of either a target movement or path finding movement instead of the path movement I used in my script.

This implementation still isn't a slam dunk because I'm assuming that you'd want to only have to specify the end destination and not all the intermediate waypoints on the route to get there, so you'd still need a script to find the ideal sequence of waypoints to traverse to get to the ultimate destination.

There are a few complications that you'll have to consider. One example is when the player enters a map, you'll need a script that will determine exactly where every NPC should be located on the map based on the time when the player entered the map. This can be tricky because the NPCs could be in the middle of their movements, so you will have to position the NPCs accordingly and then start a movement from their current location.

Another complication is the timing aspect. If an NPC takes too long getting to their destination, then the subsequent movement will either be delayed (a problem that can become compounded over time), or there will be discontinuities in the movement where the NPC instantly skips across a segment of the movement in order to catch up. In my case, having a precisely determined path helps mitigate this issue.

One thing you can do to help with this problem is to add slop by having the NPC wandering around randomly for a brief duration between movements. For example, if it takes 20 seconds for an NPC to move from point A to B, you could allocate 30 seconds before having the NPC move to point C, where the NPC wanders randomly in the vicinity of point B until the 30 seconds has elapsed. So then whether it takes 18 seconds or 24 seconds for the NPC to travel from A to B, it makes no difference because the NPC would be wondering randomly near point B for either 12 or 6 seconds according.

To go into more details about how I implemented my event tables...

I used a map based timer for my game clock ( timer starts on map:on_started() ). The advantage of linking the timer to the map is that the timer automatically gets paused whenever a dialog is displayed or the game is paused. The tricky part is transitioning the timer on changes of the map. What I did in on map:on_finished(), save the current time to the save data file and then kill the timer. Then when the next map loads, start a new timer from the time saved to the save data file. See game_clock.lua map_meta:register_event().


Quote from: wrightmat on December 06, 2017, 02:51:28 PM
My initial thought was to "register" each NPC's routine when they're created on the map, then have a function that re-arranges this routine in order to place each NPC/waypoint into an hour table.

That is basically the implementation that I used. I have a static table of events for each NPC in my data/events directory. When a map loads (map:on_started()) the relevant table entries corresponding to the current map for any NPCs present are extracted and assembled into a new events_list table with the event start times as keys. (see game_events.lua game.events:load_map_events()). This is the table structure (keep in mind the table is generated dynamically, hard-coded here for clarity):
Code (lua) Select

local events_list = {
["8:00"] = {
{ --NPC_1
npc_id = "NPC Name #1",
location = {x=96, y=157, layer=0, facing=3},
path = {4,4,4,2,2,2,2,2}, --hard-coded path
target = "to_house", --you could do something like this instead, corresponding to a custom entity with this name
},
{ --NPC_2
npc_id = "NPC Name #2",
location = {x=160, y=237, layer=1, facing=1},
path = {1,2,3,4}, --hard-coded path
},
},
["8:23"] = {
{ --NPC_1
npc_id = "NPC Name #1",
location = {x=216, y=109, layer=1, facing=3},
path = {2,2,2,2,2}, --hard-coded path
},
},
["9:00"] = {
{ --NPC_2
npc_id = "NPC Name #2",
location = {x=72, y=61, layer=1, facing=3},
path = {6,6,8,8,2,2,4,4}, --hard-coded path
},
},
}


Then every in-game minute the event manager script (see game_events.lua game.events:new_time(time_str)) can get a list of all the events that need to be started at the current time by calling events_list[current_time].
#148
Development / Re: NPC routines
December 06, 2017, 02:41:40 PM
You might want to check out what I did for NPC movements in my Cythera sample quest. Specifically you should check out npc.Neoptolemus.lua. Local paths defines the routes from every "waypoint" on the map to every other "waypoint" by listing out the steps taken. My recollection is that I timed my NPC movements such that 1 step is taken per minute in-game (where what I'm calling a step is an 8 pixel movement).

The local locations defines the coordinates of each "waypoint". Note that locations are defined for 2 maps: The town map and the map for inside his house. Basically he gets up out of bed in the morning and walks over to his table, waits briefly then walks outside where he stands for the rest of the day, comes back inside at night waiting at his table again, then walks back to the bed and goes to sleep. I placed an NPC for him on both maps, and it is hidden off-screen during times when he is not present on the current map.

Finally the local schedule defines when he moves from waypoint to waypoint, with an entry for each map the NPC is present in. I specify times to the minute. So for example, he starts walking from his table to his door at 8:00, and it is 24 steps to his door. So the next event begins at 8:23. Events where the NPC is waiting stationary link only to a location waypoint; events where the NPC moves from one waypoint to another link to both a location and a path.

Also note that I defined some movements for the NPC Demodocus as well, but I ended up not using them in the quest since the NPC sprite I used for him in the ALTTP pack did not have movement sprites, and in the end I just made him stand stationary in the bar. My plan was to have him enter the town from the West, walk to the tavern, then exit the town to the East at the end of the day.
#150
I'm trying to use a straight movement, and the straight_movement:set_max_distance(max_distance) function is not working the way that I'd expect.

Below is simplified code of what I am trying to do. At 4 seconds the table obj is moved 100 pixels to the right, and then 3 seconds after that it is moved 100 pixels to the left to return it to its original position.

The problem is that the set_max_distance function appears to be calculating the distance from (0,0), when I'd expect it to calculate the distance from what the coordinates were at the start of the movement. The result is that the second movement ends up being a 200 pixel movement to the left instead of the 100 pixels that I wanted (the object stops when x=-100, which is 100 pixels from (0,0)).

Code ( lua) Select
local obj = {x=0, y=0}
sol.timer.start(4000, function()
  print(obj.x) --0 at 4 seconds
 
  local movt1 = sol.movement.create"straight"
  movt1:set_speed(100)
  movt1:set_angle(0)
  movt1:set_max_distance(100)
  movt1:start(obj)
 
  function movt1:on_finished() print(obj.x) end --obj.x=100 at 5 seconds
 
  sol.timer.start(3000, function()
    print(obj.x) --100 at 7 seconds
   
    local movt2 = sol.movement.create"straight"
    movt2:set_speed(100)
    movt2:set_angle(math.pi)
    movt2:set_max_distance(100)
    movt2:start(obj)
   
    function movt2:on_finished() print(obj.x) end --obj.x=-100 at 9 seconds; expected obj.x=0 at 8 seconds
  end)
end)