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

Topics - Diarandor

Hi! Although I'm not active these days, I have a question about the default blend mode.

The engine v1.6.x currently uses the following as default blend mode (called "blend"):
Quote"blend" (default): This drawable object is alpha-blended onto the destination surface.
dstRGB = (srcRGB * srcA) + (dstRGB * (1 - srcA))
dstA = srcA + (dstA * (1 - srcA))
Link to the Lua API:

Why was this blend mode chosen? I do not like it because it is not associative, which is a bad property because this is not what happens in the real world. Let me explain this. In our real world, when you overlap 3 semitransparent surfaces A, B and C, the associative property
(A * B) * C = A * (B * C)
is satisfied, where "A * B" means putting or drawing A above B.

However, the "over" blending mode ( does satisfy the associativity, and is given by
newA = srcA + dstA - dstA * srcA      // This is equivalent to the current alpha in "blend".
newRGB = ( srcA * srcRGB + (1 - srcA) *  dstA *  dstRGB ) / newA.

Note that if newA is 0, the denominator in newRGB should be replaced by 1 (or whatever, because the new surface becomes transparent) to avoid a NaN error.

So these are my suggestions:
1) Add a new blend mode to the engine, called "over".
2) Set the "over" blend mode as default, if this does not produce incompatibilities with the old "blend" mode.

If you like the idea, we could open an issue for this. If there is a reason to keep "blend" as default, I would like to know, just by curiosity.
General discussion / We have moved
September 20, 2018, 11:50:26 AM
Just to inform people who may be interested in asking questions in the chat: the team has left Gitter and moved to Discord (#solarus chanel). Also recall that we left the Github repos and we are working on Gitlab.
Your scripts / New "debug_dialogs" script
March 08, 2018, 01:45:16 AM
I made a useful script to debug dialogs in our projects: debug_dialogs.lua
I tested it and it works perfect with dialogs without extra info/variables.
I haven't tested it with dialogs that need extra info/variables, so there may be issues in those cases, and I don't know how we could avoid those errors, although I think that does not matter so much.
Game art & music / Free tileset by DragonDePlatino
March 04, 2018, 01:46:40 AM
There is an unfinished artwork (tilesets) by @DragonDePlatino that is free and looks very charming:
Someone could be interested in this art to make a game with Solarus.
Development / Diagonal directions for hero
March 02, 2018, 10:38:27 AM
Some devs may be interested in this. I think that it is possible (not tested yet) and easy to code directly from Lua scripts the feature of allowing animations for the movement of the hero in 8 directions (that is, including diagonal animations). The idea is as follows:

1) Use the event "hero:on_movement_changed(movement)" to get the movement, and calculate its direction from its angle.
2) Use a modification of my "direction fix" script to include all directions.

This feature could be used with all animations/actions/weapons of the hero. This includes the animations for walking/stopped, swimming, sword attacks, etc. Our hero Eldran does not have art for diagonal animations yet (so we cannot use this feature), but that may change someday. :)

General discussion / Some Zelda-like games
February 14, 2018, 07:45:21 PM
I found some interesting projects of other devs working on Zelda-like games. Some info here:

-The waking cloack (this looks a lot like Link's Awakening in style, very charming):

-This man (, a.k.a. baranot3nshi, seems to be working on a Zelda-like game too, and the art is really awesome:
but his project seems not to have a name yet.

Let's hope these Zelda-like games, and many others, get finished someday and they become as good as they seem.
Your projects / Children of Solarus [official thread]
January 16, 2018, 03:08:21 PM
Project CHILDREN OF SOLARUS (by the Solarus Team)

I think this project deserves its own topic where we, the Solarus Team, can post our progress and some screenshots, and anyone can post their comments and suggestions about it. The project "Children of Solarus", still not very known, is intended to become a 100% free (as in freedom) remake version of "Mystery of Solarus DX", which will improve its predecessor in many ways. Feedback is always welcome (testing, bug reports, translations, suggestions, etc).

This project, led by Christopho, was started millions of years ago (before the dinosaur extinction), and hopefully will be finished before the incoming apocalypse in 2023. Much progress has been done so far, including the hero sprites, 95% of the overworld tileset (still a bit experimental), some the first overworld maps (remapped recently by Chris), some NPCs and enemies, most part of of the music (composed by Eduardo DueƱas), most part of the sounds, new scripts, and much more.

This is a list of the new work that is planned to be finished for this year 2018: completion of the overworld mapping (at least the light overlworld for now, and maybe the dark overworld too), all remaining NPCs sprites, many more new enemies (art and scripts), the indoor houses tileset, the first dungeon tileset (with color variants for other dungeons), and hopefully much more (including more mapping).

News and screenshots will be posted in the Official Website:
[Some members of the Solarus Team are now secretly working on a new version of the website. Please, keep the secret.  ;)]
General discussion / Okami on Steam
December 16, 2017, 01:13:56 AM
The game "OKAMI HD" is finally available on Steam!!! People say this is one of the best non-Zelda Zelda games, and it even has a bark button!!! ;D I tried it once with an emulator but it ran too slow on my computer and was unplayable... but now I will definitely buy it on Steam when I get some free time to play it.
Your scripts / New rain script
November 10, 2017, 09:40:39 AM
You can download from here an updated version of my rain script:

This new version does not use custom entities anymore. It draws all rain drops on a surface, and only 1 single sprite is used for all the rain. There is also the storm mode, which is more spectacular. And the best of all is that the transition between the 3 modes ("rain", "storm" and nil) is smooth.
General discussion / DreamTales, a Zelda-like videogame
October 12, 2017, 12:50:08 AM
Hi! Some news here:

It seems that some devs are working on a new Zelda-like game called DreamTales, where the main mechanic is about turning enemies into chocolate (and then they can be eaten, or pushed like statues). The graphics seem very close to ALTTP, and the plot has a lot of influence from LA too (the hero is inside a dream).

Links here (the second link is in Spanish):

EDIT: it seems that they need 25.000$ for their kickstarter before the end of this month, so it seems to me that this project will never be finished.
For the project Children of Solarus, which kind of shield would you prefer?
I found a video that shows some easter eggs of Link's Awakening:
I know that there are tons of videos showing bugs and secrets of this game, but the easter eggs of this video are really interesting and some of them are not so well-known (like the one of the walrus, which I found quite surprising).
Hi! I am posting here some links of tutorials that explain the technique of "selective outlining", also known as "selout" or "broken outlines", which is just a type of anti-aliasing in the borders of the sprites. I am still learning this useful technique, which seems complicated to master, and it may be bad to abuse too much from it.

Selective outlining is used to show borders in a good way independently of the background color. For instance, if all borders of an NPC are black, these will not be seen over a dark ground, which may make ugly "saw" borders to appear. To avoid this problem, we use color borders with a darker color very close to the inner one; some artists also use some kind of "dithering" in these darker borders, but others say that this is not very clean and bad. My guess is that if this is done in the wrong way, the result may be bad, but if done correctly and in the correct proportions, the result is impressive. Using this technique is also a choice for the pixel artist.

My current personal choice will be just to keep it simple, with just a slightly darker color border, close to the inner color (but without discontinuous color alternance in the borders because my sprites are too small and also to avoid extra-work). I don't know if color borders really are or not considered as selout (maybe that depends on the definition each artists give to the concept), but I think that using color borders, without color alternance, is good enough for very small sprites.
General discussion / Design of graphs of dungeons
April 17, 2017, 05:58:19 PM
This video explains a bit how to make a dungeon graph, which is useful to design a dungeon. It is not exactly a tutorial, but it is interesting.
Hi @Christopho! I need some help. I have a problem with my rain script. I don't kow if this is a bug or something I did wrong. I think the problem started when I began refactoring my code, but I am not sure. My (unfinished) rain script is this one:

Code (Lua) Select

-- Rain manager script.
To add this script to your game, call from game_manager script:

The functions here defined are:
    game:set_rain_type(world, rain_type)

Rain types: nil (no rain), "rain", "storm".

-- This script requires the multi_event script:
local rain_manager = {}

local game_meta = sol.main.get_metatable("game")
local map_meta = sol.main.get_metatable("map")

-- Default settings. Change these for testing.
local rain_enabled = true -- Do not change this property, unless you are testing.
local lightning_enabled = false
local rain_speed = 100 -- Default drop speed 100.
local drop_max_distance = 300 -- Max possible distance for drop movements.
local drop_delay = 10 -- Delay between drops, in milliseconds.
local drop_sprite_id = "test/rain"

-- Initialize rain on maps when necessary.
game_meta:register_event("on_map_changed", function()
  if self ~= nil then
    local map = self:get_map()

-- Get the raining state for a given world.
function game_meta:get_rain_type(world)
  local rain_type = self:get_value("rain_state_" .. world)
  return rain_enabled and rain_type
-- Set the raining state for a given world.
function game_meta:set_rain_type(world, rain_type)
  -- Update savegame variable.
  self:set_value("rain_state_" .. world, rain_type)
  -- Check if rain is necessary: if we are in that world and rain is needed. 
  local current_world = self:get_map():get_world()
  local rain_needed = (current_world == world) and rain_enabled and rain_type
  if (not rain_needed) then return end -- Do nothing if rain is not needed!
  -- We need to start the rain in the current map.
  local map = self:get_map()

-- Create rain if necessary when entering a new map.
function rain_manager:update_rain(map)
  -- Get rain state in this world.
  local world = map:get_world()
  local rain_type = map:get_game():get_rain_type(world)
  -- Start rain if necessary.
  if rain_type == "rain" then
  elseif rain_type == "storm" then

-- Define function to create splash effects.
-- If no parameters x, y are given, the position is random.
local function create_drop_splash(map, x, y)
  local max_layer = map:get_max_layer()
  local min_layer = map:get_min_layer()
  local camera = map:get_camera()
  local cx, cy, cw, ch = camera:get_bounding_box()
  local drop_properties = {direction = 0, x = 0, y = 0, layer = max_layer,
    width = 16, height = 16, sprite = drop_sprite_id}
  -- Initialize parameters.
  local x = x or cx + cw * math.random()
  local y = y or cy + ch * math.random()
  local layer = max_layer
  while map:get_ground(x,y,layer) == "empty" and layer > min_layer do
    layer = layer - 1 -- Draw the splash at the lower layer we can.
  -- Do not draw splash over some bad grounds: "hole" and "lava".
  local ground = map:get_ground(x, y, layer)
  if ground ~= "hole" and ground ~= "lava" then
    drop_properties.x = x
    drop_properties.y = y
    drop_properties.layer = layer
    local drop_splash = map:create_custom_entity(drop_properties)
    assert(drop_splash ~= nil)
    local splash_sprite = drop_splash:get_sprite()
    function splash_sprite:on_animation_finished() drop_splash:remove() end

-- Define function to create drops.
-- If no parameters x, y are given, the position is random.
local function create_drop(map, x, y)
  local max_layer = map:get_max_layer()
  local min_layer = map:get_min_layer()
  local camera = map:get_camera()
  local cx, cy, cw, ch = camera:get_bounding_box()
  local drop_properties = {direction = 0, x = 0, y = 0, layer = max_layer,
    width = 16, height = 16, sprite = drop_sprite_id}
  -- Initialize parameters.
  drop_properties.x = x or cx + cw * math.random() + 30
  drop_properties.y = y or cy + ch * math.random() - 100
  drop_properties.layer = max_layer
  local drop = map:create_custom_entity(drop_properties)
  local m = sol.movement.create("straight")
  m:set_angle(7 * math.pi / 5)
  local random_max_distance = math.random(1, drop_max_distance)
  function m:on_finished() drop:remove() end
  function m:on_obstacle_reached() drop:remove() end
  function drop:on_removed()
    local x, y = drop:get_position()
    create_drop_splash(map, x, y)

-- Start rain in the current map.
function rain_manager:start_rain(map)
  local max_layer = map:get_max_layer()
  local min_layer = map:get_min_layer()
  local camera = map:get_camera()
  local drop_properties = {direction = 0, x = 0, y = 0, layer = max_layer,
    width = 16, height = 16, sprite = drop_sprite_id}
  -- Initialize random seed for positions.

  -- Start timer to draw rain drops.
  sol.timer.start(map, drop_delay, function()
    -- Create drops on random positions.
    -- Repeat loop.
    return true

-- Stop rain in the current map.
function rain_manager:stop_rain(map)


-- Return rain manager.
return rain_manager

The problem appears when leaving a map with rain to another (where there is no rain, but this is probably not important). The bug appears in the console (no crash) at lines 92-93, because the entity "drop_splash" does not exist in that moment. I know that this can be fixed with an extra condition "if drop_splash ~= nil then blablabla end", but I'd like to know why does this happen.

I had the same problem at lines 31-32, because the "self" variable does not exist sometimes, and the condition "if self ~= nil then" fixed this. Why do not these variables exist when these events are called? Is this a bug?
Thanks in advance for the help.
I was asked by @Porksteak to explain how to use the animation sprites of my custom dialog box:
My old script does not explain how to use this feature (I forgot to add these explanations). I post this here so anyone will know how to use it:

An example would be this dialog:
Code (Lua) Select

  id = "old_man.thanks",
  animation = "talking",
  animation_sprite = "dialog_old_man",
  text = [[
Thanks for saving my
cats, young man.
See you later!

The (optional) sprite of the character has to be located in the path given by the concatenated string:
"dialogs/" .. dialog.animation_sprite
and the animation used is the one given by the string: dialog.animation
Your projects / A Zelda game by Kory Toombs
December 05, 2016, 06:30:03 AM
Hi! I found a Zelda game made with the Solarus engine by Kory Toombs, this is the website:
I thought it would be a good idea to create a topic about this; but I have not played this game so I know nothing about it.
Your scripts / Raft script
November 08, 2016, 11:15:00 PM
Hi! I was asked by @Porksteak in a personal message if I could share the platform-raft script that I use in this video:

So here goes the code. But note that some parts of the code are a bit old and could be improved (for instance, I should use "require" instead of "load_file" in the raft script, to make the code faster), and some things of the script may require other scripts. My raft script requires my platform script to work, so I include both scripts here. It is recommended that you study the code to make another script that suits better for your purposes, and remove unnecesary things from my code (for instance, you probably do not need the raft to move secondary heroes or custom entities, so you should delete these parts of the code and other similar things).

generic_platform.lua script:
Code (Lua) Select

-- Platform: entity which moves in either horizontally or vertically (depending on direction)
-- and carries the hero on it.
local entity = ...
local map = entity:get_map()
local game = entity:get_game()
local hero = map:get_hero()

entity.can_save_state = true

local time_stopped = 1000
local is_moving = false
-- Remark: speeds bigger than 12 give problems for water platforms (rafts) when trying to enter or go out. No problem above holes.
entity.speed = 50 -- Set speed.

function entity:on_created()
  self:set_size(32, 32)
  self:set_origin(16, 13) -- Important: the 32x32 sprite must have center in (16,13).
  -- Set dynamic solid ground position for the hero to avoid problems.
  sol.timer.start(entity, 10, function()
    -- Do nothing if hero is not on solid ground.
    local is_hero_on_solid_ground = map:is_solid_ground(hero:get_ground_position())
    if hero.is_jumping or not is_hero_on_solid_ground then return true end
    -- Save or clear solid ground position on this platform.
    if map.current_hero_platform ~= entity and self:is_on_platform(hero) then
      map.current_hero_platform = entity
      self:save_hero_position() -- Save solid ground on the platform.
    elseif map.current_hero_platform == entity and (not self:is_on_platform(hero)) then
      map.current_hero_platform = nil
      hero:reset_solid_ground() -- Clear solid ground.
    return true

  -- Custom function.
  if entity.on_custom_created then
  -- Initialize properties.
  self:set_can_traverse("jumper", true)
  self:set_can_traverse_ground("hole", true)
  self:set_can_traverse_ground("deep_water", true)
  self:set_can_traverse_ground("traversable", true)
  self:set_can_traverse_ground("shallow_water", true)
  self:set_can_traverse_ground("wall", false)

  -- Start movement.
  local direction = self:get_direction()
  local m = sol.movement.create("path")
  m:set_path{direction * 2}; m:set_speed(entity.speed)
  m:set_loop(true); m:start(self)
  is_moving = true


-- Function to save hero position on the platform.
function entity:save_hero_position()
    --[[ When the hero reappears, the camera moves towards the new position,
    which produces a delay, and the platform has already moved when the hero appers.
    To fix this problem, change the position of the hero directly, so the movement
    will be instantaneous with no delay. To avoid this we must not call
    "hero:get_solid_ground_position()", or there will be problems with platforms.
    local x, y, layer = entity:get_center_position()
    -- Change position at next cycle to avoid engine problems (keep this timer!).
    sol.timer.start(self, 1, function()
      hero:set_blinking(true, 1000)
      hero:set_invincible(true, 1000)
      hero:set_position(x, y, layer)
    return x, y, layer

-- Update shifting variables for the translation.
local function get_shifts()
  local direction = entity:get_direction()
  local dx, dy = 0, 0 -- Variables for the translation.
  if direction == 0 then dx = 1 elseif direction == 1 then dy = -1
  elseif direction == 2 then dx = -1 elseif direction == 3 then dy = 1 end
  return dx, dy

-- Get movable entities that were on the previous position of the platform.
function entity:get_movable_entities()
  local movable_entities = {}
  for other in map:get_entities_in_rectangle(entity:get_bounding_box()) do
    -- Check only entities that can be moved, including the hero.
    if other.moved_on_platform or other:get_type() == "hero" then
      -- Check if the entity was on the platform before the movement.
      if entity:was_on_platform(other) and entity:is_on_platform(other) then
        -- Exclude portable entities unless they are on the ground.
        if (not other.is_portable) or other.state == "on_ground" then
          table.insert(movable_entities, other)
  return movable_entities

-- Return true if an entity is on the platform.
-- IMPORTANT: This function is only used in the script generic_portable.lua.
function entity:is_on_platform(other)
  local x, y, layer = other:get_ground_position()
  return entity:overlaps(x, y)

-- Return true if an entity was on the platform before the movement.
function entity:was_on_platform(other)
  local dx, dy = get_shifts()
  local ox, oy, oz = other:get_ground_position()
  local bx, by, w, h = entity:get_bounding_box()
  local pbx, pby = bx-dx, by-dy -- Previous position of bounding box (before the movement).
  return ox >= pbx and ox < pbx+w and oy >= pby and oy < pby+h

function entity:on_obstacle_reached(movement)
  --Make the platform turn back.
  local direction = self:get_direction()
  movement:stop(); is_moving = false
  movement = sol.movement.create("path")   
  direction = (direction+2)%4
  movement:set_path{direction * 2}
  sol.timer.start(self, time_stopped, function()
    is_moving = true 

-- Move the movable entities that were on the platform before the movement.
function entity:on_position_changed()
  -- Get the movable entities that were on the platform before the change of position.
  local dx, dy = get_shifts() -- Variables for the translation.
  local movable_entities = entity:get_movable_entities()
  local ex, ey, ez = self:get_position()
  local bx, by, w, h = self:get_bounding_box()
  local pbx, pby = bx-dx, by-dy -- Previous position of bounding box (before the movement).
  for _, other in pairs(movable_entities) do
    local ox, oy, oz = other:get_position()
    -- Move the entity with the platform.
    if not other:test_obstacles(dx, dy, oz) then
      other:set_position(ox + dx, oy + dy, oz)

raft.lua script:
Code (Lua) Select

local entity = ...
-- Raft entity.

local sea_foam_sprite -- Foam sprite for the raft.
entity.speed = 20 -- Set speed.

function entity:on_custom_created()
  -- Set custom properties.
  time_stopped = 1000
  self:set_can_traverse_ground("deep_water", true)
  self:set_can_traverse_ground("wall", false)
  self:set_can_traverse("jumper", false)
  self:set_can_traverse_ground("hole", false)
  self:set_can_traverse_ground("traversable", true)
  self:set_can_traverse_ground("shallow_water", false)
  self:set_can_traverse_ground("grass", false)
  -- Customize movement.
  local direction = self:get_direction()
  local m = sol.movement.create("path")
  m:set_path{direction * 2}; m:set_speed(entity.speed)
  m:set_loop(true); m:start(self)
  is_moving = true
  -- Start the sea foam and wavering.
  self:start_wavering() -- Start moving raft sprite to simulate the waves.

----------------- Raft functions:

-- Add the sea foam sprite.
function entity:add_sea_foam()
  -- Remark: The variable sea_foam_sprite is already local in this script!
  sea_foam_sprite = self:create_sprite("things/platform")

-- Make the sprite move up and down.
function entity:start_wavering()
  local sprite = self:get_sprite()
  -- Define vectors with the shifts of each "frame" of the sprite and time for each pause between "frames".
  local raft_dy = {0,-1,-2,-1} -- Shifts for the "y" coordinate of the sprite.
  local entities_dy = {1,-1,-1,1} -- Shifts for the "y" coordinate of position of entities.
  local pause_time = {200,1000,200,1000} -- Pause times between "frames".
  -- Move the movable entities located over the platform, if possible.
  local function shift_entities(dy_shift)
    local movable_entities = entity:get_movable_entities()
    local x, y, z = self:get_position()
    local bx, by, w, h = self:get_bounding_box()
    for _, something in pairs(movable_entities) do   
      if entity:is_on_platform(something, bx, by, w, h) then   
      local sx, sy, sz = something:get_position()
        -- Move only entities that can be moved.
        local needs_move = false
        if something:get_type() == "hero" then
          if something:get_animation() ~= "walking" then
            needs_move = true
        elseif something:get_sprite() then
          if something:get_sprite():get_animation() ~= "walking" then
            needs_move = true
        -- Check for obstacles.
        local found_obstacles = something:test_obstacles(0, dy_shift, sz)
        -- Finally check if the entity is not in the "bad border" to move it without getting out the platform.
        if needs_move and (not found_obstacles) then
          if (sy + dy_shift) >= by+2 and (sy + dy_shift) <= by+h-3 then sy = sy +dy_shift end
          something:set_position(sx, sy, sz)

  -- Shift sprite and move entities.
  local function set_frame_loop(frame_nb)
    -- Shift the sprite. Shift entities above the rift, if possible.
    sprite:set_xy(0, raft_dy[frame_nb])
    -- Restart the loop.
    frame_nb = (frame_nb %4) +1 -- Next frame index.
    sol.timer.start(entity, pause_time[frame_nb], function() set_frame_loop(frame_nb) end)
  -- Start the wavering loop. 
Hi solarusiens!
I was thinking that for the project Children of Solarus we could use mushrooms that go up/down instead of crystal platforms. That would make the game more different from Zelda ALTTP, which would be nice. But I do not know if you like the idea, so I made this poll. Which option do you prefer?
Hi! One of the many things I will be working on is the creation of new free enemies with original art and fully scripted. I will keep adding them to the sample quest, and many of them are planned to be used in the project Children of Solarus. Some of my current work is here:

Feedback wanted: If you have some cool and original idea of some type of enemy you want, then post it here. If I like the idea, I might make them when I get free time (but please, be patient). If the behavior is a bit complicated I might not do it, because I prefer to focus more on making free art than coding strange things, so think of basic stuff (no bosses or similar things); but also try to think of enemies with different and new original behaviors too, so that we will have more variety of enemies. Try to give as much details as possible, for the graphic part and also the behavior of the enemy.

Here you can also share your own original enemies, with both sprites and code, and maybe a link to some video to show them in action.
Quest makers, I challenge you to make something cool and post it here!!! hehehe! :D