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.


Messages - llamazing

Pages: [1] 2 3 ... 7
1
Your projects / Re: Ocean's Heart
« on: March 15, 2018, 01:34:22 pm »
Yes, I have been enjoying it. The graphics are gorgeous, there's a decent balance between dialog and exploration, and there's a certain charm to the overall plot.

2
Development / Re: Generating a map image from map .dat file
« on: March 15, 2018, 06:08:27 am »
Small critique- I wouldn't go with purple for stairs, I'd go with white or something that looked more natural with the blue, green, and brown that dominate the colors.
Fair enough. This was really more of a proof of concept and I was more concerned about different things being discernible, however obnoxious their appearance may be. There are a number of things I don't like about the colors that I would tweak on my next pass. I'm also thinking that showing prickles is probably too much detail, and it is probably better to just ignore them.

That's cool that you've designed it to go into such detail, yet wisely omitted things like NPCs. I wonder if it would be configured to show particular NPCs- a person who's the object of your quest, for example, or if particular teleporters could be color coded differently to indicate they're the dungeon you're looking for.
NPCs are an entirely different hurdle to tackle, but still entirely possible to implement. Due to how they wander around, you'd want to find their present locations on the current map rather than getting their starting point from the map.dat file. Dynamic tiles are a similar obstacle that would need to properly be accounted for too. Coding particular teletransporters to appear differently at certain times is also doable.

I wouldn't personally want that because I like games about exploring, but it'd be an interesting take if you were interested in further developing this idea.
Another possibility is to hide tiles that the player hasn't seen yet by painting over them in black. I'm not sure if that would end up being too processor intensive, though, since I have not tried it.

An additional improvement I'd make would be to do certain tileset-specific coloring, but this would require deliberate planning when naming the tiles of the tileset. For example, all exterior house tiles could be given a name that ends in "_house" and then instead of coloring those tiles grey like a wall, they could have a unique color. I would do something similar with tree tops and give those partial transparency.

Also, as I was manually overlaying the individual layer images, I ended up manually darkening the traversible tiles on layer 1 so that bridges could be properly seen. In retrospect in would have made more sense to have the upper layers brighter and the lower layers darker. It would be pretty easy to make the script slightly lighten or darken the color depending on what layer it is on (but it would probably only make sense to do that for traversable type tiles and possibly wall tiles as well).

3
Development / Re: Enemy Timers
« on: March 15, 2018, 05:46:16 am »
There's another way to get around it. When you start a timer that you don't want aborted, save the timestamp when the timer started:
Code: Lua
  1. local start_time = sol.main.get_elapsed_time()

Then in your on_restarted() function, since you know the timer was destroyed, start a new timer with however much time was remaining on the original timer:
Code: Lua
  1. local remaining_time = start time + TIMER_DURATION - sol.main.get_elapsed_time()
  2. if remaining_time > 0 then
  3.         sol.timer.start(self, remaining_time, some_function)
  4. end --otherwise timer expired before enemy was hit so do nothing
Where TIMER_DURATION is a constant equal to however long the timer is.

4
Your projects / Re: Ocean's Heart
« on: March 11, 2018, 07:25:42 pm »
Found a few more bugs.

If the pause menu is opened while the player is carrying something (like a flower or bush), then once the pause menu is closed again the player is unable to throw the item and the action button no longer works.

Got this error when the guy in Crabhook Village shows the player how to use monster bait
Error: In dialog callback: [string "maps/goatshead_island/crabhook_village.lua"]:81: attempt to index global 'crow' (a nil value)

When returning the charts to the chart shop in Ballast Harbor:
Error: In dialog callback: [string "maps/ballast_harbor/chart_shop.lua"]:51: bad argument #1 to start_dialog (No such dialog: '_game:quest_log_update')

The sensor to activate the charging pirate in the council_building map at Ballast Harbor should be larger so that the player cannot avoid stepping on it. I got through without stepping on it my first time and was confused about what was supposed to happen.

Is the player supposed to receive bombs when buying the coral ore from Honestbeard in Ballast Harbor? If it is intentional then there should be a follow-up dialog that explains it is intended.

When resuming a save sometimes music does not start (like when saving in a shop or other inside building in a town).

The battle with the rock spider (goat_tunnel_spider) in the Goatshead tunnels is a bit lame. The spider stands right in front of the door blocking the player from entering. The player can swing sword from doorway, but landing a hit causes the player to recoil back in to the previous room. Maybe have the door close behind the player as they enter the room?

Chest on far east side of Kingsdown contains 20 crowns that can be re-opened every time player enters map.


Dialog Typos:

Quote from: _goatshead.npcs.dockworkers.4
I heard to tavern owner was looking
for him... something about money?

Quote from: _goatshead.npcs.tavern_people.adventurers.4
need a Volunteer Navyman... person for
is to
clear out the beasts.
('is' should be 'us'?)

Quote from: _goatshead.npcs.ilex.1
Don't worry about. I didn't know him that well,
he lived here watchinging the shrine.
('about' should be 'about it'?)

Quote from: _yarrowmouth.npcs.town_people.4
bIf you're looking for Briarwood Distillery, it's

5
Development / Generating a map image from map .dat file
« on: March 11, 2018, 08:41:09 am »
Given the discussion on the Ocean's Heart project topic about making a map menu, I wanted to test the feasibility of generating a mini-map image from the info in the map .dat file. The endeavor has been a success.

What I did is read the map .dat file from a lua script, which gives the size, position and stacking order of all tiles. Also reading the tileset .dat file used by that map gives the ground type (e.g. traversable, wall, etc.) for each tile. I assigned colors to the tiles based on the ground type. Then on a surface I drew rectangles whose size and position corresponds to each tile at 1/8th scale, drawn in the same order as on the map. I drew layers 0 to 2 separately and ended up with this result for the overworld maps:



Note that sometimes the layer 2 image got clipped because the window I was using was only 480 pixels in height. It didn't appear to be losing anything in the cases where it was clipped.

I then manually overlaid the layers, using 50% opacity for layers 1 & 2, and lined up the maps for how they fit together. In hindsight I realize I could have made the script to do the same thing.



Note that the Hourglass Fort Outside and Naerreturn Bay maps overlap with each other, so I moved the Hourglass map off to the side.

This mapping technique could be useful either in generating a reference image from which to create hand-drawn images of the maps, or a modified version of the script could be used to have the quest itself generate the map images to use for the map menu. For pixel-precise accuracy, the purple border between maps (so that edge teletranpsorters are visible) should be removed.

In order to reproduce my results and run the script, do the following:
1) Download version 0.1 of the Ocean's Heart project from the topic linked above (back up any existing save files if you already have the quest)
2) Change the quest size to 640 by 480 in quest.dat
3) In main.lua of that quest comment out line 5, require("scripts/features"), so that the HUD is not displayed
4) Replace the scripts/game_manager.lua file with this one:
Code: Lua
  1. local game_manager = {}
  2.  
  3. local initial_game = require("scripts/initial_game")
  4. require("scripts/multi_events")
  5.  
  6. -- Starts the game from the given savegame file,
  7. -- initializing it if necessary.
  8. function game_manager:start_game(file_name)
  9.  
  10.         local exists = sol.game.exists(file_name)
  11.         local game = sol.game.load(file_name)
  12.         if not exists then
  13.                 -- Initialize a new savegame.
  14.                 initial_game:initialize_new_savegame(game)
  15.         end
  16.         sol.video.set_window_size(640, 480) --to prevent display from scaling at 2x
  17.         game:start()
  18.        
  19.         local map_layers = require("scripts/menus/display_map")
  20.        
  21.         function game:on_draw(dst_surface)
  22.                 dst_surface:fill_color{0,0,0} --fill black, don't want to see what's underneath
  23.                
  24.                 for layer = 0,2 do
  25.                         local surface = map_layers[layer]
  26.                         if surface then
  27.                                 local width, height = surface:get_size()
  28.                                 surface:draw(dst_surface, 1, layer*(height+1)+1) --1 px from edge of screen with images separated by 1 px
  29.                         end
  30.                 end
  31.         end
  32. end
  33.  
  34. return game_manager
  35.  
5) Add this script to the scripts/menus directory, named display_map.lua:
Code: Lua
  1. local map_id = "new_limestone/limestone_present"
  2. --local map_id = "goatshead_island/goatshead_harbor"
  3. --local map_id = "goatshead_island/west_goat"
  4. --local map_id = "goatshead_island/goat_hill"
  5. --local map_id = "goatshead_island/lighthouse_cape"
  6. --local map_id = "goatshead_island/poplar_forest"
  7. --local map_id = "goatshead_island/poplar_coast"
  8. --local map_id = "goatshead_island/crabhook_village"
  9. --local map_id = "Yarrowmouth/yarrowmouth_village"
  10. --local map_id = "Yarrowmouth/juniper_grove"
  11. --local map_id = "Yarrowmouth/foothills"
  12. --local map_id = "Yarrowmouth/silent_glade"
  13. --local map_id = "Yarrowmouth/naerreturn_bay"
  14. --local map_id = "Yarrowmouth/kingsdown"
  15. --local map_id = "Yarrowmouth/hourglass_fort/outside"
  16. --local map_id = "ballast_harbor/ballast_harbor"
  17.  
  18. local tilesets = {}
  19. local maps = {}
  20.  
  21. local SCALE_FACTOR = 8 --draw maps at 1/8th actual size
  22.  
  23. --color to draw tile based on ground property; ignore if not listed
  24. local tile_colors = {
  25.         traversable = {0, 255, 0}, --green
  26.         wall = {127, 127, 127}, --grey
  27.         low_wall = {204, 204, 204}, --light grey
  28.         teletransporter = {255, 255, 0}, --yellow; this is a type of entity, not a tile ground property
  29.         shallow_water = {0, 255, 255}, --light blue
  30.         deep_water = {0, 0, 255}, --blue
  31.         hole = {0, 0, 0}, --black
  32.         ladder = {255, 0, 255}, --pink
  33.         lava = {255, 0, 0}, --red
  34.         prickles = {255, 127, 0}, --orange
  35.         ice = {127, 255, 255}, --pale blue
  36. }
  37.  
  38. --load tileset .dat file to determine ground property of each tile
  39. local function load_tileset(tileset_id)
  40.         local tileset = {}
  41.        
  42.         local env = {}
  43.         function env.tile_pattern(properties)
  44.                 local id = properties.id
  45.                 assert(id, "tile pattern without id")
  46.                 id = tonumber(id)
  47.                 assert(id, "tile pattern id must be a number")
  48.                
  49.                 local ground = properties.ground
  50.                 assert(ground, "tile pattern without ground")
  51.                
  52.                 if tile_colors[ground] then --ignore ground properties that don't have a color assigned
  53.                         tileset[id] = tile_colors[ground] --link the color to use with the tile id
  54.                 end
  55.         end
  56.        
  57.         setmetatable(env, {__index = function() return function() end end})
  58.        
  59.         local chunk = sol.main.load_file("tilesets/"..tileset_id..".dat")
  60.         setfenv(chunk, env)
  61.         chunk()
  62.        
  63.         return tileset
  64. end
  65.  
  66. --load map .dat file to get list of tiles used
  67. local function load_map(map_id)
  68.         local map = { tiles = {} }
  69.        
  70.         local env = {}
  71.        
  72.         --properties stores the size and coordinates for the map and the tileset used
  73.         function env.properties(properties)
  74.                 local x = tonumber(properties.x)
  75.                 assert(x, "property x must be a number")
  76.                 local y = tonumber(properties.y)
  77.                 assert(y, "property y must be a number")
  78.                
  79.                 local width = tonumber(properties.width)
  80.                 assert(width, "property width must be a number")
  81.                 local height = tonumber(properties.height)
  82.                 assert(height, "property height must be a number")
  83.                
  84.                 local tileset = properties.tileset
  85.                 assert(tileset, "properties without tileset")
  86.                
  87.                 map.x = x
  88.                 map.y = y
  89.                 map.width = width
  90.                 map.height = height
  91.                 map.tileset = tileset
  92.         end
  93.        
  94.         --each tile defines a size, coordinates and layer as well as the tile id to use
  95.         function env.tile(properties)
  96.                 local pattern = properties.pattern --pattern is the tile id
  97.                 assert(pattern, "tile without pattern")
  98.                 pattern = tonumber(pattern)
  99.                 assert(pattern, "tile pattern must be a number")
  100.                
  101.                 local layer = properties.layer
  102.                 assert(layer, "tile without layer")
  103.                 layer = tonumber(layer)
  104.                 assert(layer, "tile layer must be a number")
  105.                
  106.                 local x = tonumber(properties.x)
  107.                 assert(x, "tile x must be a number")
  108.                 local y = tonumber(properties.y)
  109.                 assert(y, "tile y must be a number")
  110.                
  111.                 local width = tonumber(properties.width)
  112.                 assert(width, "tile width must be a number")
  113.                 local height = tonumber(properties.height)
  114.                 assert(height, "tile height must be a number")
  115.                
  116.                 table.insert(map.tiles, {
  117.                         pattern = pattern,
  118.                         layer = layer,
  119.                         x = x,
  120.                         y = y,
  121.                         width = width,
  122.                         height = height,
  123.                 })
  124.         end
  125.        
  126.         --also extract teletransporter usage to be able to draw teletransporter locations
  127.         function env.teletransporter(properties)
  128.                 local layer = properties.layer
  129.                 assert(layer, "tile without layer")
  130.                 layer = tonumber(layer)
  131.                 assert(layer, "tile layer must be a number")
  132.                
  133.                 local x = tonumber(properties.x)
  134.                 assert(x, "tile x must be a number")
  135.                 local y = tonumber(properties.y)
  136.                 assert(y, "tile y must be a number")
  137.                
  138.                 local width = tonumber(properties.width)
  139.                 assert(width, "tile width must be a number")
  140.                 local height = tonumber(properties.height)
  141.                 assert(height, "tile height must be a number")
  142.                
  143.                 table.insert(map.tiles, {
  144.                         pattern = "teletransporter", --instead of using tile pattern to determine color, tile_colors has a "teletransporter" entry
  145.                         layer = layer,
  146.                         x = x,
  147.                         y = y,
  148.                         width = width,
  149.                         height = height,
  150.                 })
  151.         end
  152.        
  153.         setmetatable(env, {__index = function() return function() end end})
  154.        
  155.         local chunk, err = sol.main.load_file("maps/"..map_id..".dat")
  156.         setfenv(chunk, env)
  157.         chunk()
  158.  
  159.         return map
  160. end
  161.  
  162. local function generate_map(map_id)
  163.         assert(type(map_id)=="string", "Bad argument #1 to 'generate_map' (string expected)")
  164.        
  165.         --load map info from .dat file if not already loaded
  166.         if not maps[map_id] then maps[map_id] = load_map(map_id) end
  167.         local map = maps[map_id]
  168.        
  169.         --load tileset info from .dat file if not already loaded
  170.         if not tilesets[map.tileset] then tilesets[map.tileset] = load_tileset(map.tileset) end
  171.         local tileset = tilesets[map.tileset]
  172.        
  173.         --create a surface for each layer
  174.         local layers = {}
  175.         for layer = 0,2 do
  176.                 layers[layer] = sol.surface.create(map.width/SCALE_FACTOR+2, map.height/SCALE_FACTOR+2) --include room for 1px border
  177.                 layers[layer]:fill_color({127, 0, 255}, 0, 0, map.width/SCALE_FACTOR+2, 1) --top 1px purple border
  178.                 layers[layer]:fill_color({127, 0, 255}, 0, 0, 1, map.height/SCALE_FACTOR+2) --left 1px purple border
  179.                 layers[layer]:fill_color({127, 0, 255}, 0, map.height/SCALE_FACTOR+1, map.width/SCALE_FACTOR+2, 1) --bottom 1px purple border
  180.                 layers[layer]:fill_color({127, 0, 255}, map.width/SCALE_FACTOR+1, 0, 1, map.height/SCALE_FACTOR+2) --right 1px purple border
  181.         end
  182.        
  183.         --draw each tile
  184.         for _,tile in ipairs(map.tiles) do
  185.                 local pattern = tile.pattern
  186.                 local tile_color = tile_colors[pattern] or tileset[pattern]
  187.                
  188.                 --draw the tile as solid color on surface corresponding to the correct layer
  189.                 if tile_color and layers[tile.layer] then --ignore corner and empty tiles
  190.                         layers[tile.layer]:fill_color(
  191.                                 tile_color,
  192.                                 tile.x/SCALE_FACTOR+1,
  193.                                 tile.y/SCALE_FACTOR+1,
  194.                                 tile.width/SCALE_FACTOR,
  195.                                 tile.height/SCALE_FACTOR
  196.                         )
  197.                 end
  198.         end
  199.        
  200.         return layers
  201. end
  202.  
  203. return generate_map(map_id)
  204.  

6
Bugs & Feature requests / Re: Block on top of transporter bug
« on: March 07, 2018, 04:13:48 am »
Use a custom entity instead of the teletransporter and you can script it to do whatever behavior you want.

7
Your projects / Re: Ocean's Heart
« on: March 06, 2018, 03:10:53 am »
Good so far, I've been enjoying it. I like the humor in the dialogs.

I encountered this error:
Error: Failed to load script 'maps/goatshead_island/interiors/sodden_cormorant': [string "maps/goatshead_island/interiors/sodden_cormor..."]:43: function arguments expected near ':'

Looks like you forgot parentheses after map:get_hero on line 43 of that map script.

8
Development / Re: Creating a respawn point on map changed?
« on: March 05, 2018, 12:58:59 am »
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).

9
Development / Re: Displaying an image on pause
« on: March 03, 2018, 10:11:45 pm »
Since you're still learning, I'll post some examples of other things you can do too.

One trick you can do is change the color of a bitmap font. In your case it works great because your font is black and white.

To change all black pixels to another color while leaving white pixels unchanged is to use the add blend mode. Think of black as having a value of 0 and white having a value of 1. black + color2 = color2 (0 plus anything is the value you added); white + color2 = white (because 1 for white is already the max value).

And likewise to change all white pixels to another color while leaving black pixels unchanged is to use the multiply blend mode. white * color2 = color2 (1 times anything is the value you multiplied by); black * color2 = black (0 times anything is 0).

This code shows an example of changing black to light blue (see top left screenshot) at lines 48-51. Lines 54-57 change white to yellow (see top right). You don't want to do both at the same time because that will cause funny results (since then there'd be more colors than just black and white present).

Code: Lua
  1. pause_infra = {}
  2. local current_log = sol.surface.create(138,72) --22 characters across, 4+ lines tall
  3.  
  4. local text_surface = sol.text_surface.create({
  5.     vertical_alignment = "top",
  6.     horizontal_alignment = "center",
  7.     font = "oceansfont",
  8. })
  9. --note that a size is not specified for a text surface, it will be as big as the text it contains
  10.  
  11. --Returns iterator to split text at line breaks
  12. local function line_it(text)
  13.     assert(type(text)=="string", "Bad argument #1 to 'line_it' (string expected, got "..type(text)..")")
  14.  
  15.     text = text:gsub("\r\n", "\n"):gsub("\r", "\n").."\n" --convert instances of \r to \n and add line break to end
  16.     return text:gmatch("([^\n]*)\n") -- Each line including empty ones.
  17. end
  18.  
  19.  
  20. function pause_infra:update_game(game)
  21.     current_log:clear() --erase any previous content on surface
  22.  
  23.     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
  24.     local next_line = line_it(sol.language.get_string(log_a_text)) --gets localized string in current language
  25.  
  26.     text_surface:set_text(next_line()) --line 1 of quest_log_a
  27.     text_surface:draw(current_log, 69, 0) --renders first line of quest_log_a text and draws on current_log surface
  28.         --x value 69 because horizontal alignment is center (138/2 = 69)
  29.  
  30.     text_surface:set_text(next_line()) --line 2 of quest_log_a
  31.     text_surface:draw(current_log, 69, 16)
  32.  
  33.     --NOTE: if quest_log_a contains more than 2 lines of text then the extra lines do not get drawn
  34.  
  35.     local log_b_text = game:get_value("quest_log_b")
  36.     next_line = line_it(sol.language.get_string(log_b_text))
  37.  
  38.     text_surface:set_text(next_line()) --line 1 of quest_log_b
  39.     text_surface:draw(current_log, 69, 39)
  40.  
  41.     text_surface:set_text(next_line()) --line 2 of quest_log_b
  42.     text_surface:draw(current_log, 69, 55)
  43.  
  44.     --Now current_log contains and image rendered from text strings of log A & log B (up to 4 lines)
  45.     --content won't change while pause menu open
  46.    
  47.     --use this trick to change the color of black pixels to something else
  48.     local blend_add_surface = sol.surface.create(current_log:get_size()) --same size as current_log
  49.     blend_add_surface:fill_color{0,128,255} --light blue
  50.     blend_add_surface:set_blend_mode("add") --allows changing black pixels without changing white pixels
  51.     blend_add_surface:draw(current_log)
  52.    
  53.     --use this trick to change the color of white pixels to something else
  54.     --[[local blend_mult_surface = sol.surface.create(current_log:get_size()) --same size as current_log
  55.     blend_mult_surface:fill_color{255,255,0} --yellow
  56.     blend_mult_surface:set_blend_mode("multiply") --allows changing white pixels without changing black pixels
  57.     blend_mult_surface:draw(current_log)]]
  58. end
  59.  
  60.  
  61. local pause_img = sol.surface.create("hud/pause_infra.png")
  62.  
  63. function pause_infra:on_draw(dst_surface)
  64.     --draw menu architecture
  65.     pause_img:draw(dst_surface)
  66.  
  67.     --draw quest log
  68.     current_log:draw(dst_surface, 167, 27) --specify coordinates for where you want to draw it on the screen
  69. end

Another application for this technique is you can use it to change the color of just some words. You would do this by specifying x, y, width and height values when you call the fill_color() function of the blend mode surface. That way only the text within that rectangle would change color. You'd have to figure out the position of the words that you want to have a different color.



If you wanted to use a non-bitmap font, here's how you could do it. The trick here is drawing the text twice. The first time draws a shadow in one color, then the second pass draws the text again in a different color with a slight offset. This makes the text easier to read against a wide variety of background colors when using a semi-transparent background.

The font I used is EncodeSansCompressed-SemiBold, which you can find here (Open SIL license).

(see bottom left screenshot)
Code: Lua
  1. pause_infra = {}
  2. local current_log = sol.surface.create(138,72)
  3.  
  4. local FONT_COLOR = {255, 255 ,255} --white
  5. local FONT_SHAODW_COLOR = {255,0,0} --red
  6.  
  7. local text_surface = sol.text_surface.create({
  8.     vertical_alignment = "top",
  9.     horizontal_alignment = "center",
  10.     font = "EncodeSansCompressed-SemiBold",
  11.     font_size = 14,
  12.     rendering_mode = "antialiasing",
  13. })
  14. --note that a size is not specified for a text surface, it will be as big as the text it contains
  15.  
  16. --Returns iterator to split text at line breaks
  17. local function line_it(text)
  18.     assert(type(text)=="string", "Bad argument #1 to 'line_it' (string expected, got "..type(text)..")")
  19.  
  20.     text = text:gsub("\r\n", "\n"):gsub("\r", "\n").."\n" --convert instances of \r to \n and add line break to end
  21.     return text:gmatch("([^\n]*)\n") -- Each line including empty ones.
  22. end
  23.  
  24.  
  25. --This function consolidates repeated lines of code to reduce redundancy in pause_infra:update_game()
  26. local function draw_text(text, dst_surface, x, y)
  27.     --ensure inputs valid
  28.         assert(type(text)=="string", "Bad argument #1 to 'draw_text' (string expected, got "..type(text)..")")
  29.         assert(sol.main.get_type(dst_surface)=="surface", "Bad argument #2 to 'draw_text' (surface expected, got "..sol.main.get_type(dst_surface)..")")
  30.         x = math.floor(tonumber(x) or 0) --try to force x to integer or use 0 instead
  31.         y = math.floor(tonumber(y) or 0)
  32.        
  33.         text_surface:set_text(text)
  34.        
  35.     text_surface:set_color(FONT_SHAODW_COLOR)
  36.     text_surface:draw(current_log, x+1, y+1) --draw shadow text first, offset 1 pixel to the right and down
  37.    
  38.     text_surface:set_color(FONT_COLOR)
  39.     text_surface:draw(current_log, x, y) --draw non-shadow text on top of shadow text
  40. end
  41.  
  42.  
  43. function pause_infra:update_game(game)
  44.     current_log:clear() --erase any previous content on surface
  45.  
  46.     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
  47.     local next_line = line_it(sol.language.get_string(log_a_text)) --gets localized string in current language
  48.  
  49.     draw_text(next_line(), current_log, 69, 0) --renders first line of quest_log_a text and draws on current_log surface
  50.          --x value 69 because horizontal alignment is center (138/2 = 69)
  51.     draw_text(next_line(), current_log, 69, 16) --line 2 of quest_log_a
  52.  
  53.     --NOTE: if quest_log_a contains more than 2 lines of text then the extra lines do not get drawn
  54.  
  55.     local log_b_text = game:get_value("quest_log_b")
  56.     next_line = line_it(sol.language.get_string(log_b_text))
  57.  
  58.     draw_text(next_line(), current_log, 69, 39) --line 1 of quest_log_b
  59.     draw_text(next_line(), current_log, 69, 55) --line 2 of quest_log_b
  60.  
  61.     --Now current_log contains and image rendered from text strings of log A & log B (up to 4 lines)
  62.     --content won't change while pause menu open
  63. end
  64.  
  65.  
  66. local pause_img = sol.surface.create("hud/pause_infra.png")
  67.  
  68. function pause_infra:on_draw(dst_surface)
  69.     --draw menu architecture
  70.     pause_img:draw(dst_surface)
  71.  
  72.     --draw quest log
  73.     current_log:draw(dst_surface, 167, 27) --specify coordinates for where you want to draw it on the screen
  74. end

Note that the new draw_text function is just to consolidate repeated code and make things cleaner.

Be aware that there is nothing to prevent the text from clipping in this example, and since I am using a font that isn't monospaced, the number of characters that can fit on a line will vary.

If you had actually wanted to use a non-bitmap, non-monospaced font, then it would probably be best to determine where the line breaks should occur dynamically in the code rather than manually entering the \n line breaks. This is getting into more details than I want to get into, but the gist of it is to split the text at the spaces and add one word at a time, checking the current text width using text_surface:get_size() and moving the new words to a new line once the length is exceeded.



Finally, one more thing you can do is create the borders you have around your quest log text box dynamically so that you don't need to use a different image for each size box you use. You would create a new image that contains the four corners of your box (7x7 pixels each) plus the repeating horizontal and vertical segments (1x7 and 7x1 pixels), plus 1 pixel in the center, giving a total size of 15x15 pixels.

To simplify things, steal my ui_draw.lua file from one of my sample quests here and put it in your scripts folder. You'll also need the pause_infra_small.png image I've attached which is derived from your original image. Then the following code will work.

One thing to note is I deleted the transparent pixels in the middle of the pause_infra.png image and am generating the transparent background dynamically in the code. This allows you to change the background color however you see fit. In the code I changed the background of the bottom box to be blue so you can see this in effect (see bottom right screenshot).

The following code generates an image similar to your pause_infra.png from just my pause_infra_small.png. This includes having to draw the "QUEST LOG" text separately as well as the line below it.

Code: Lua
  1. pause_infra = {}
  2. local current_log = sol.surface.create(138,72) --22 characters across, 4+ lines tall
  3.  
  4. local text_surface = sol.text_surface.create({
  5.     vertical_alignment = "top",
  6.     horizontal_alignment = "center",
  7.     font = "oceansfont",
  8. })
  9. --note that a size is not specified for a text surface, it will be as big as the text it contains
  10.  
  11. --Returns iterator to split text at line breaks
  12. local function line_it(text)
  13.     assert(type(text)=="string", "Bad argument #1 to 'line_it' (string expected, got "..type(text)..")")
  14.  
  15.     text = text:gsub("\r\n", "\n"):gsub("\r", "\n").."\n" --convert instances of \r to \n and add line break to end
  16.     return text:gmatch("([^\n]*)\n") -- Each line including empty ones.
  17. end
  18.  
  19.  
  20. function pause_infra:update_game(game)
  21.     current_log:clear() --erase any previous content on surface
  22.  
  23.     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
  24.     local next_line = line_it(sol.language.get_string(log_a_text)) --gets localized string in current language
  25.  
  26.     text_surface:set_text(next_line()) --line 1 of quest_log_a
  27.     text_surface:draw(current_log, 69, 0) --renders first line of quest_log_a text and draws on current_log surface
  28.         --x value 69 because horizontal alignment is center (138/2 = 69)
  29.  
  30.     text_surface:set_text(next_line()) --line 2 of quest_log_a
  31.     text_surface:draw(current_log, 69, 16)
  32.  
  33.     --NOTE: if quest_log_a contains more than 2 lines of text then the extra lines do not get drawn
  34.  
  35.     local log_b_text = game:get_value("quest_log_b")
  36.     next_line = line_it(sol.language.get_string(log_b_text))
  37.  
  38.     text_surface:set_text(next_line()) --line 1 of quest_log_b
  39.     text_surface:draw(current_log, 69, 39)
  40.  
  41.     text_surface:set_text(next_line()) --line 2 of quest_log_b
  42.     text_surface:draw(current_log, 69, 55)
  43.  
  44.     --Now current_log contains and image rendered from text strings of log A & log B (up to 4 lines)
  45.     --content won't change while pause menu open
  46. end
  47.  
  48. --create image for quest log
  49. local ui_draw = require("scripts/ui_draw.lua")
  50. local pause_borders = sol.surface.create("hud/pause_infra_small.png") --15x15 image
  51. local pause_img = sol.surface.create(152,103) --size to make the quest log box
  52. pause_img:fill_color( --first draw semi-transparent background
  53.     {0,0,0,204}, --black with 80% opacity
  54.     1,1,152-2,103-2 --fill surface with color except for 1 pixel border around edge
  55. )
  56. ui_draw.draw_frame(
  57.         { --table describing the source image to use
  58.                 surface = pause_borders,
  59.                 border = 7, --x & y offset to use for determining repeating section of border image
  60.         },
  61.         pause_img --border will be drawn on top of existing pause_img surface
  62. )
  63. text_surface:set_text("QUEST_LOG") --this string should come from a new entry in string.dat to facilitate localization to other languages
  64. --text_surface:set_text(sol.language.get_string("interface.title.quest_log")) --do something like this instead of line above once you create the new string.dat entry
  65. text_surface:draw(pause_img, 69, 3) --draw QUEST_LOG title text, you may have to temporarily change the horizontal alignment to center first if using left for objectives text
  66. pause_img:fill_color({0,0,0},8,19,136,3) --draw line under title, starting with black outline
  67. pause_img:fill_color({255,255,255},8+1,19+1,136-2,3-2) --draw white line under title
  68.  
  69. --create image below quest log
  70. local pause_img2 = sol.surface.create(152,46) --size to make bottom box
  71. pause_img2:fill_color(
  72.         {0,64,128,204}, --let's make this one blue because why not?
  73.         1,1,152-2,46-2
  74. )
  75. ui_draw.draw_frame({surface=pause_borders, border=7}, pause_img2)
  76.  
  77. function pause_infra:on_draw(dst_surface)
  78.     --draw menu architecture
  79.     pause_img:draw(dst_surface, 160, 1)
  80.  
  81.     --draw quest log
  82.     current_log:draw(dst_surface, 167, 27) --specify coordinates for where you want to draw it on the screen
  83.    
  84.     --draw bottom box
  85.     pause_img2:draw(dst_surface, 160, 105)
  86. end

10
Development / Re: Creating a respawn point on map changed?
« on: March 03, 2018, 08:15:09 pm »
Something like the following should work
Code: Lua
  1. --game_manager.lua
  2. local map_meta = sol.main.get_metatable("map")
  3. map_meta:register_event("on_started", function()
  4.   local hero = game:get_hero()
  5.   if game:get_value("currently_gameover") then
  6.     hero:set_position( --move to respawn location
  7.       game:get_value("respawn_x"),
  8.       game:get_value("respawn_y"),
  9.       game:get_value("respawn_layer")
  10.     )
  11.     hero:set_direction(game:get_value("respawn_direction"))
  12.   else
  13.     --save location in respawn savegame data
  14.     local x, y, layer = hero:get_position()
  15.     game:set_value("respawn_x", x) game:set_value("respawn_y", y) game:set_value("respawn_layer", layer)
  16.     game:set_value("respawn_direction", hero:get_direction())
  17.     game:set_value("respawn_map", self:get_id() )
  18.   end
  19. end)
  20.  
  21. -----
  22.  
  23. --blank map script
  24. function map:on_started() --overrides the map_meta:on_started function, which is what you want
  25.   hero:teleport(game:get_value("respawn_map"))
  26. end
  27.  

11
Development / Re: Displaying an image on pause
« on: March 03, 2018, 12:14:14 am »
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)
Correct

The pattern you're using is encased in quotes, because patterns need to be strings, I think?
Correct

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?
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).

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.
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 +.

12
Development / Re: Creating a respawn point on map changed?
« on: March 02, 2018, 02:49:25 pm »
As I said in my first post, the destination is OPTIONAL! You don't really need to create a new destination for this, and therefore you shouldn't.
It is true that is is not necessary to specify a destination in hero:teleport(), in which case the hero gets sent to the default destination. My point is that you'd have to go though each and every map to verify that sending the hero first to the default destination then moving the hero in map:on_started() to the respawn destination is not going to cause a problem. For example, there may be unintended consequences if the default destination overlaps a sensor that causes things to change on the map.

13
Development / Re: Displaying an image on pause
« on: March 02, 2018, 06:36:41 am »
Here is how I would do the script:
Code: Lua
  1. pause_infra = {}
  2. local current_log = sol.surface.create(138,72) --22 characters across, 4+ lines tall
  3.  
  4. local text_surface = sol.text_surface.create({
  5.     vertical_alignment = "top",
  6.     horizontal_alignment = "center",
  7.     font = "oceansfont",
  8. })
  9. --note that a size is not specified for a text surface, it will be as big as the text it contains
  10.  
  11. --Returns iterator to split text at line breaks
  12. local function line_it(text)
  13.     assert(type(text)=="string", "Bad argument #1 to 'line_it' (string expected, got "..type(text)..")")
  14.  
  15.     text = text:gsub("\r\n", "\n"):gsub("\r", "\n").."\n" --convert instances of \r to \n and add line break to end
  16.     return text:gmatch("([^\n]*)\n") -- Each line including empty ones.
  17. end
  18.  
  19.  
  20. function pause_infra:update_game(game)
  21.     current_log:clear() --erase any previous content on surface
  22.  
  23.     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
  24.     local next_line = line_it(sol.language.get_string(log_a_text)) --gets localized string in current language
  25.  
  26.     text_surface:set_text(next_line()) --line 1 of quest_log_a
  27.     text_surface:draw(current_log, 69, 0) --renders first line of quest_log_a text and draws on current_log surface
  28.         --x value 69 because horizontal alignment is center (138/2 = 69)
  29.  
  30.     text_surface:set_text(next_line()) --line 2 of quest_log_a
  31.     text_surface:draw(current_log, 69, 16)
  32.  
  33.     --NOTE: if quest_log_a contains more than 2 lines of text then the extra lines do not get drawn
  34.  
  35.     local log_b_text = game:get_value("quest_log_b")
  36.     next_line = line_it(sol.language.get_string(log_b_text))
  37.  
  38.     text_surface:set_text(next_line()) --line 1 of quest_log_b
  39.     text_surface:draw(current_log, 69, 39)
  40.  
  41.     text_surface:set_text(next_line()) --line 2 of quest_log_b
  42.     text_surface:draw(current_log, 69, 55)
  43.  
  44.     --Now current_log contains and image rendered from text strings of log A & log B (up to 4 lines)
  45.     --content won't change while pause menu open
  46. end
  47.  
  48.  
  49. local pause_img = sol.surface.create("hud/pause_infra.png")
  50.  
  51. function pause_infra:on_draw(dst_surface)
  52.     --draw menu architecture
  53.     pause_img:draw(dst_surface)
  54.  
  55.     --draw quest log
  56.     current_log:draw(dst_surface, 167, 27) --specify coordinates for where you want to draw it on the screen
  57. 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
  1. text{ key = "0", value = [[
  2. Find the chart salesman
  3. in Ballast Harbor
  4. ]]}

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.

14
Development / Re: Displaying an image on pause
« on: 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.

15
Development / Re: Displaying an image on pause
« on: 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.

Pages: [1] 2 3 ... 7