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

#121
Your scripts / Re: Deku/Turret Type Enemy
April 29, 2018, 06:17:27 AM
FYI--
Your code to set the default properties can be made more concise with the use of a table. This equivalent code replaces lines 26 through 104:
Code (lua) Select
    local defaults = {
      life = 2,
      damage = 0,
      normal_speed = 32,
      faster_speed = 48,
      size_x = 16,
      size_y = 16,
      hurt_style = "normal",
      pushed_when_hurt = false,
      push_hero_on_sword = false,
      ignore_obstacles = false,
      detection_distance = 80,
      obstacle_behavior = "normal",
      projectile_breed = "misc/octorok_stone",
      shooting_frequency = 1500,
      sword_consequence = 1,
      arrow_consequence = 1,
      explosion_consequence = 1,
      fire_consequence = 1,
      movement_create = function()
        local m = sol.movement.create("random_path")
        return m
      end,
      asleep_animation = "asleep",
      awake_animation = "awake",
      must_be_aligned_to_shoot = true,
      max_range = 100,
      min_range = 45,
      must_be_aligned_to_shoot = true,
    }
   
    -- Set default properties.
    for property,default_value in pairs(defaults) do
      if properties[property] == nil then
        properties[property] = default_value
      end
    end


Also, it looks like the script hard codes some offsets specific to the projectile sprite. It might be helpful if you included an image of the projectile sprite to make it more clear what it is doing. For that matter it might be helpful to include the monster sprite too. It's always nice to be able to replicate the author's setup exactly in order to be sure things are working as intended.
#122
Development / Re: The dangerous escape key
April 29, 2018, 05:27:46 AM
Your problem is from the main.lua line 49 to 53. Change the behavior to something else or comment out line 51.

Code (lua) Select
-- This is the main Lua script of your project.
-- You will probably make a title screen and then start a game.
-- See the Lua API! http://www.solarus-games.org/doc/latest

require("scripts/features")
local game_manager = require("scripts/game_manager")

-- This function is called when Solarus starts.
function sol.main:on_started()
  --preload the sounds for faster access
  sol.audio.preload_sounds()
  --set the language
  sol.language.set_language("en")


--[[local solarus_logo = require("scripts/menus/solarus_logo")

  -- Show the Solarus logo initially.
  sol.menu.start(self, solarus_logo)

  -- Start the game when the Solarus logo menu is finished.
  solarus_logo.on_finished = function()

  end
--]]

    game_manager:start_game("save1.dat")
end



-- Event called when the player pressed a keyboard key.
function sol.main:on_key_pressed(key, modifiers)

  local handled = false
  if key == "f5" then
    -- F5: change the video mode.
    sol.video.switch_mode()
    handled = true
  elseif key == "f11" or
    (key == "return" and (modifiers.alt or modifiers.control)) then
    -- F11 or Ctrl + return or Alt + Return: switch fullscreen.
    sol.video.set_fullscreen(not sol.video.is_fullscreen())
    handled = true
  elseif key == "f4" and modifiers.alt then
    -- Alt + F4: stop the program.
    sol.main.exit()
    handled = true
  elseif key == "escape" and sol.main.game == nil then
    -- Escape in title screens: stop the program.
    sol.main.exit()
    handled = true
  end

  return handled
end
#123
Development / Re: Setting Up A Basic Equipment Menu?
April 28, 2018, 03:55:07 AM
Creating an interactive menu really isn't all that difficult, especially if you already know how to create a static menu, which is the biggest hurdle in my opinion. Drawing all the various menu components at the correct location can be tedious.

Going from a static menu to an interactive menu needs the following components:

  • Draw an indicator highlighting the currently selected item in the menu. You can get fancy here an make it animated
  • process keyboard/gamepad inputs from the player. menu:on_command_pressed(command) can be used for this.
  • Move the indicator in response to player inputs. This part can be tricky because you'll have to plan out the layout of your menu (probably a grid) and define rules for how the indicator can move around.
  • Handle other actions in response to player inputs, such as making the currently selected item active or perhaps open a new menu to display a text box with a description for the current item, etc.
#124
Your projects / Re: Ocean's Heart
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.
#125
Quote from: Max on March 14, 2018, 03:00:06 AMSmall 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.

Quote from: Max on March 14, 2018, 03:00:06 AMThat'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.

Quote from: Max on March 14, 2018, 03:00:06 AMI 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).
#126
Development / Re: Enemy Timers
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) Select
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) Select
local remaining_time = start time + TIMER_DURATION - sol.main.get_elapsed_time()
if remaining_time > 0 then
sol.timer.start(self, remaining_time, some_function)
end --otherwise timer expired before enemy was hit so do nothing

Where TIMER_DURATION is a constant equal to however long the timer is.
#127
Your projects / Re: Ocean's Heart
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.4I heard to tavern owner was looking
for him... something about money?

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

Quote from: _goatshead.npcs.ilex.1Don'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
#128
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) Select
local game_manager = {}

local initial_game = require("scripts/initial_game")
require("scripts/multi_events")

-- Starts the game from the given savegame file,
-- initializing it if necessary.
function game_manager:start_game(file_name)

local exists = sol.game.exists(file_name)
local game = sol.game.load(file_name)
if not exists then
-- Initialize a new savegame.
initial_game:initialize_new_savegame(game)
end
sol.video.set_window_size(640, 480) --to prevent display from scaling at 2x
game:start()

local map_layers = require("scripts/menus/display_map")

function game:on_draw(dst_surface)
dst_surface:fill_color{0,0,0} --fill black, don't want to see what's underneath

for layer = 0,2 do
local surface = map_layers[layer]
if surface then
local width, height = surface:get_size()
surface:draw(dst_surface, 1, layer*(height+1)+1) --1 px from edge of screen with images separated by 1 px
end
end
end
end

return game_manager

5) Add this script to the scripts/menus directory, named display_map.lua:
Code (lua) Select
local map_id = "new_limestone/limestone_present"
--local map_id = "goatshead_island/goatshead_harbor"
--local map_id = "goatshead_island/west_goat"
--local map_id = "goatshead_island/goat_hill"
--local map_id = "goatshead_island/lighthouse_cape"
--local map_id = "goatshead_island/poplar_forest"
--local map_id = "goatshead_island/poplar_coast"
--local map_id = "goatshead_island/crabhook_village"
--local map_id = "Yarrowmouth/yarrowmouth_village"
--local map_id = "Yarrowmouth/juniper_grove"
--local map_id = "Yarrowmouth/foothills"
--local map_id = "Yarrowmouth/silent_glade"
--local map_id = "Yarrowmouth/naerreturn_bay"
--local map_id = "Yarrowmouth/kingsdown"
--local map_id = "Yarrowmouth/hourglass_fort/outside"
--local map_id = "ballast_harbor/ballast_harbor"

local tilesets = {}
local maps = {}

local SCALE_FACTOR = 8 --draw maps at 1/8th actual size

--color to draw tile based on ground property; ignore if not listed
local tile_colors = {
traversable = {0, 255, 0}, --green
wall = {127, 127, 127}, --grey
low_wall = {204, 204, 204}, --light grey
teletransporter = {255, 255, 0}, --yellow; this is a type of entity, not a tile ground property
shallow_water = {0, 255, 255}, --light blue
deep_water = {0, 0, 255}, --blue
hole = {0, 0, 0}, --black
ladder = {255, 0, 255}, --pink
lava = {255, 0, 0}, --red
prickles = {255, 127, 0}, --orange
ice = {127, 255, 255}, --pale blue
}

--load tileset .dat file to determine ground property of each tile
local function load_tileset(tileset_id)
local tileset = {}

local env = {}
function env.tile_pattern(properties)
local id = properties.id
assert(id, "tile pattern without id")
id = tonumber(id)
assert(id, "tile pattern id must be a number")

local ground = properties.ground
assert(ground, "tile pattern without ground")

if tile_colors[ground] then --ignore ground properties that don't have a color assigned
tileset[id] = tile_colors[ground] --link the color to use with the tile id
end
end

setmetatable(env, {__index = function() return function() end end})

local chunk = sol.main.load_file("tilesets/"..tileset_id..".dat")
setfenv(chunk, env)
chunk()

return tileset
end

--load map .dat file to get list of tiles used
local function load_map(map_id)
local map = { tiles = {} }

local env = {}

--properties stores the size and coordinates for the map and the tileset used
function env.properties(properties)
local x = tonumber(properties.x)
assert(x, "property x must be a number")
local y = tonumber(properties.y)
assert(y, "property y must be a number")

local width = tonumber(properties.width)
assert(width, "property width must be a number")
local height = tonumber(properties.height)
assert(height, "property height must be a number")

local tileset = properties.tileset
assert(tileset, "properties without tileset")

map.x = x
map.y = y
map.width = width
map.height = height
map.tileset = tileset
end

--each tile defines a size, coordinates and layer as well as the tile id to use
function env.tile(properties)
local pattern = properties.pattern --pattern is the tile id
assert(pattern, "tile without pattern")
pattern = tonumber(pattern)
assert(pattern, "tile pattern must be a number")

local layer = properties.layer
assert(layer, "tile without layer")
layer = tonumber(layer)
assert(layer, "tile layer must be a number")

local x = tonumber(properties.x)
assert(x, "tile x must be a number")
local y = tonumber(properties.y)
assert(y, "tile y must be a number")

local width = tonumber(properties.width)
assert(width, "tile width must be a number")
local height = tonumber(properties.height)
assert(height, "tile height must be a number")

table.insert(map.tiles, {
pattern = pattern,
layer = layer,
x = x,
y = y,
width = width,
height = height,
})
end

--also extract teletransporter usage to be able to draw teletransporter locations
function env.teletransporter(properties)
local layer = properties.layer
assert(layer, "tile without layer")
layer = tonumber(layer)
assert(layer, "tile layer must be a number")

local x = tonumber(properties.x)
assert(x, "tile x must be a number")
local y = tonumber(properties.y)
assert(y, "tile y must be a number")

local width = tonumber(properties.width)
assert(width, "tile width must be a number")
local height = tonumber(properties.height)
assert(height, "tile height must be a number")

table.insert(map.tiles, {
pattern = "teletransporter", --instead of using tile pattern to determine color, tile_colors has a "teletransporter" entry
layer = layer,
x = x,
y = y,
width = width,
height = height,
})
end

setmetatable(env, {__index = function() return function() end end})

local chunk, err = sol.main.load_file("maps/"..map_id..".dat")
setfenv(chunk, env)
chunk()

return map
end

local function generate_map(map_id)
assert(type(map_id)=="string", "Bad argument #1 to 'generate_map' (string expected)")

--load map info from .dat file if not already loaded
if not maps[map_id] then maps[map_id] = load_map(map_id) end
local map = maps[map_id]

--load tileset info from .dat file if not already loaded
if not tilesets[map.tileset] then tilesets[map.tileset] = load_tileset(map.tileset) end
local tileset = tilesets[map.tileset]

--create a surface for each layer
local layers = {}
for layer = 0,2 do
layers[layer] = sol.surface.create(map.width/SCALE_FACTOR+2, map.height/SCALE_FACTOR+2) --include room for 1px border
layers[layer]:fill_color({127, 0, 255}, 0, 0, map.width/SCALE_FACTOR+2, 1) --top 1px purple border
layers[layer]:fill_color({127, 0, 255}, 0, 0, 1, map.height/SCALE_FACTOR+2) --left 1px purple border
layers[layer]:fill_color({127, 0, 255}, 0, map.height/SCALE_FACTOR+1, map.width/SCALE_FACTOR+2, 1) --bottom 1px purple border
layers[layer]:fill_color({127, 0, 255}, map.width/SCALE_FACTOR+1, 0, 1, map.height/SCALE_FACTOR+2) --right 1px purple border
end

--draw each tile
for _,tile in ipairs(map.tiles) do
local pattern = tile.pattern
local tile_color = tile_colors[pattern] or tileset[pattern]

--draw the tile as solid color on surface corresponding to the correct layer
if tile_color and layers[tile.layer] then --ignore corner and empty tiles
layers[tile.layer]:fill_color(
tile_color,
tile.x/SCALE_FACTOR+1,
tile.y/SCALE_FACTOR+1,
tile.width/SCALE_FACTOR,
tile.height/SCALE_FACTOR
)
end
end

return layers
end

return generate_map(map_id)

#129
Use a custom entity instead of the teletransporter and you can script it to do whatever behavior you want.
#130
Your projects / Re: Ocean's Heart
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.
#131
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).
#132
Development / Re: Displaying an image on pause
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) 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
   
    --use this trick to change the color of black pixels to something else
    local blend_add_surface = sol.surface.create(current_log:get_size()) --same size as current_log
    blend_add_surface:fill_color{0,128,255} --light blue
    blend_add_surface:set_blend_mode("add") --allows changing black pixels without changing white pixels
    blend_add_surface:draw(current_log)
   
    --use this trick to change the color of white pixels to something else
    --[[local blend_mult_surface = sol.surface.create(current_log:get_size()) --same size as current_log
    blend_mult_surface:fill_color{255,255,0} --yellow
    blend_mult_surface:set_blend_mode("multiply") --allows changing white pixels without changing black pixels
    blend_mult_surface:draw(current_log)]]
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


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) Select
pause_infra = {}
local current_log = sol.surface.create(138,72)

local FONT_COLOR = {255, 255 ,255} --white
local FONT_SHAODW_COLOR = {255,0,0} --red

local text_surface = sol.text_surface.create({
    vertical_alignment = "top",
    horizontal_alignment = "center",
    font = "EncodeSansCompressed-SemiBold",
    font_size = 14,
    rendering_mode = "antialiasing",
})
--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


--This function consolidates repeated lines of code to reduce redundancy in pause_infra:update_game()
local function draw_text(text, dst_surface, x, y)
    --ensure inputs valid
assert(type(text)=="string", "Bad argument #1 to 'draw_text' (string expected, got "..type(text)..")")
assert(sol.main.get_type(dst_surface)=="surface", "Bad argument #2 to 'draw_text' (surface expected, got "..sol.main.get_type(dst_surface)..")")
x = math.floor(tonumber(x) or 0) --try to force x to integer or use 0 instead
y = math.floor(tonumber(y) or 0)

text_surface:set_text(text)

    text_surface:set_color(FONT_SHAODW_COLOR)
    text_surface:draw(current_log, x+1, y+1) --draw shadow text first, offset 1 pixel to the right and down
   
    text_surface:set_color(FONT_COLOR)
    text_surface:draw(current_log, x, y) --draw non-shadow text on top of shadow text
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

    draw_text(next_line(), 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)
    draw_text(next_line(), current_log, 69, 16) --line 2 of quest_log_a

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

    draw_text(next_line(), current_log, 69, 39) --line 1 of quest_log_b
    draw_text(next_line(), current_log, 69, 55) --line 2 of quest_log_b

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


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

--create image for quest log
local ui_draw = require("scripts/ui_draw.lua")
local pause_borders = sol.surface.create("hud/pause_infra_small.png") --15x15 image
local pause_img = sol.surface.create(152,103) --size to make the quest log box
pause_img:fill_color( --first draw semi-transparent background
    {0,0,0,204}, --black with 80% opacity
    1,1,152-2,103-2 --fill surface with color except for 1 pixel border around edge
)
ui_draw.draw_frame(
{ --table describing the source image to use
surface = pause_borders,
border = 7, --x & y offset to use for determining repeating section of border image
},
pause_img --border will be drawn on top of existing pause_img surface
)
text_surface:set_text("QUEST_LOG") --this string should come from a new entry in string.dat to facilitate localization to other languages
--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
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
pause_img:fill_color({0,0,0},8,19,136,3) --draw line under title, starting with black outline
pause_img:fill_color({255,255,255},8+1,19+1,136-2,3-2) --draw white line under title

--create image below quest log
local pause_img2 = sol.surface.create(152,46) --size to make bottom box
pause_img2:fill_color(
{0,64,128,204}, --let's make this one blue because why not?
1,1,152-2,46-2
)
ui_draw.draw_frame({surface=pause_borders, border=7}, pause_img2)

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

    --draw quest log
    current_log:draw(dst_surface, 167, 27) --specify coordinates for where you want to draw it on the screen
   
    --draw bottom box
    pause_img2:draw(dst_surface, 160, 105)
end
#133
Something like the following should work
Code (lua) Select

--game_manager.lua
local map_meta = sol.main.get_metatable("map")
map_meta:register_event("on_started", function()
  local hero = game:get_hero()
  if game:get_value("currently_gameover") then
    hero:set_position( --move to respawn location
      game:get_value("respawn_x"),
      game:get_value("respawn_y"),
      game:get_value("respawn_layer")
    )
    hero:set_direction(game:get_value("respawn_direction"))
  else
    --save location in respawn savegame data
    local x, y, layer = hero:get_position()
    game:set_value("respawn_x", x) game:set_value("respawn_y", y) game:set_value("respawn_layer", layer)
    game:set_value("respawn_direction", hero:get_direction())
    game:set_value("respawn_map", self:get_id() )
  end
end)

-----

--blank map script
function map:on_started() --overrides the map_meta:on_started function, which is what you want
  hero:teleport(game:get_value("respawn_map"))
end
#134
Development / Re: Displaying an image on pause
March 03, 2018, 12:14:14 AM
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 +.
#135
Quote from: Diarandor on March 02, 2018, 08:05:14 AM
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.