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

#1
Development / Re: Separator_on_activated direction4
February 19, 2017, 02:27:16 AM
I've integrated my door slider into separator_manager.lua so that it can be used in dungeons.

But something unexpected is happening. With this addition, enemies prefixed with auto_enemy are no longer reset. They're simply erased. Strangely, everything else is reset just fine.

Code (lua) Select
-- This script restores entities when there are separators in a map.
-- When taking separators prefixed by "auto_separator", the following entities are restored:
-- - Enemies prefixed by "auto_enemy".
-- - Destructibles prefixed by "auto_destructible".
-- - Blocks prefixed by "auto_block".
--
-- Usage from a map script:
-- local separator_manager = require("maps/lib/separator_manager.lua")
-- separator_manager:manage_map(map)
-- If you prefer, you can also enable it automatically on all maps from game:on_map_changed().

local separator_manager = {}

function separator_manager:manage_map(map)

  local enemy_places = {}
  local destructible_places = {}
  local game = map:get_game()
  local zelda = map:get_entity("zelda")

  -- Function called when a separator was just taken.
  local function separator_on_activated(separator)

    local hero = map:get_hero()

    -- Enemies.
    for _, enemy_place in ipairs(enemy_places) do
      local enemy = enemy_place.enemy

      -- First remove any enemy.
      if enemy:exists() then
        enemy:remove()
      end

      -- Re-create enemies in the new active region.
      if enemy:is_in_same_region(hero) then
        local old_enemy = enemy_place.enemy
        local enemy = map:create_enemy({
          x = enemy_place.x,
          y = enemy_place.y,
          layer = enemy_place.layer,
          breed = enemy_place.breed,
          direction = enemy_place.direction,
          name = enemy_place.name,
        })
        enemy:set_treasure(unpack(enemy_place.treasure))
        enemy.on_dead = old_enemy.on_dead  -- For door_manager.
        enemy_place.enemy = enemy
      end
    end

    -- Blocks.
    for block in map:get_entities("auto_block") do
      -- Reset blocks in regions no longer visible.
      if not block:is_in_same_region(hero) then
        block:reset()
      end
    end

  end

  -- Function called when a separator is being taken.
  local function separator_on_activating(separator)


    local hero = map:get_hero()

    -- Enemies.
    if not map.used_separator then
      -- First separator: remove enemies from other regions like on_activated() does.
      -- Because on_activated() was not called when the map started.
      for _, enemy_place in ipairs(enemy_places) do
        local enemy = enemy_place.enemy
        if enemy:exists() and not enemy:is_in_same_region(hero) then
          enemy:remove()
        end
      end
    end

    -- Destructibles.
    for _, destructible_place in ipairs(destructible_places) do
      local destructible = destructible_place.destructible

      if not destructible:exists() then
        -- Re-create destructibles in all regions except the active one.
        if not destructible:is_in_same_region(hero) then
          local destructible = map:create_destructible({
            x = destructible_place.x,
            y = destructible_place.y,
            layer = destructible_place.layer,
            name = destructible_place.name,
            sprite = destructible_place.sprite,
            destruction_sound = destructible_place.destruction_sound,
            weight = destructible_place.weight,
            can_be_cut = destructible_place.can_be_cut,
            can_explode = destructible_place.can_explode,
            can_regenerate = destructible_place.can_regenerate,
            damage_on_enemies = destructible_place.damage_on_enemies,
            ground = destructible_place.ground,
          })
          -- We don't recreate the treasure.
          destructible_place.destructible = destructible
        end
      end
    end
  end


  for separator in map:get_entities("auto_separator") do
    separator.on_activating = separator_on_activating
    separator.on_activated = separator_on_activated

    --door slider
    function separator:on_activated(direction4)
        if direction4 == 0 then
          local hero = self:get_game():get_hero()
          local heroanim = hero:get_animation()
          hero:set_animation(heroanim)
          local dir = 0
          hero:freeze()
          local m2 = sol.movement.create("straight")
          m2:set_speed(90)
          m2:set_angle(dir*math.pi/2)
          m2:set_max_distance(32)
          m2:start(hero)
          hero:set_animation(heroanim)
          function m2:on_obstacle_reached()
            hero:unfreeze()
          end
          function m2:on_finished()
            hero:unfreeze()
          end
        end
        if direction4 == 1 then
          local hero = self:get_game():get_hero()
          local heroanim = hero:get_animation()
          hero:set_animation(heroanim)
          local dir = 1
          hero:freeze()
          local m2 = sol.movement.create("straight")
          m2:set_speed(90)
          m2:set_angle(dir*math.pi/2)
          m2:set_max_distance(24)
          m2:start(hero)
          hero:set_animation(heroanim)
          function m2:on_obstacle_reached()
            hero:unfreeze()
          end
          function m2:on_finished()
            hero:unfreeze()
          end
        end
        if direction4 == 2 then
          local hero = self:get_game():get_hero()
          local heroanim = hero:get_animation()
          hero:set_animation(heroanim)
          local dir = 2
          hero:freeze()
          local m2 = sol.movement.create("straight")
          m2:set_speed(90)
          m2:set_angle(dir*math.pi/2)
          m2:set_max_distance(32)
          m2:start(hero)
          hero:set_animation(heroanim)
          function m2:on_obstacle_reached()
            hero:unfreeze()
          end
          function m2:on_finished()
            hero:unfreeze()
          end
        end
        if direction4 == 3 then
          local hero = self:get_game():get_hero()
          local heroanim = hero:get_animation()
          hero:set_animation(heroanim)
          local dir = 3
          hero:freeze()
          local m = sol.movement.create("straight")
          m:set_speed(500)
          m:set_angle(dir*math.pi/2)
          m:set_max_distance(32)
          m:start(hero)
          hero:set_animation(heroanim)
          function m:on_obstacle_reached()
            hero:unfreeze()
          end
          function m:on_finished()
            local dir = 3
            local m2 = sol.movement.create("straight")
            m2:set_speed(90)
            m2:set_angle(dir*math.pi/2)
            m2:set_max_distance(16)
            m2:start(hero)
            hero:set_animation(heroanim)
            function m2:on_obstacle_reached()
              hero:unfreeze()
            end
            function m2:on_finished()
              hero:unfreeze()
            end
          end
        end
    end

  end

  for separator in map:get_entities("sub_separator") do

    --door slider
    function separator:on_activated(direction4)
        if direction4 == 0 then
          local hero = self:get_game():get_hero()
          local heroanim = hero:get_animation()
          hero:set_animation(heroanim)
          local dir = 0
          hero:freeze()
          local m2 = sol.movement.create("straight")
          m2:set_speed(90)
          m2:set_angle(dir*math.pi/2)
          m2:set_max_distance(32)
          m2:start(hero)
          hero:set_animation(heroanim)
          function m2:on_obstacle_reached()
            hero:unfreeze()
          end
          function m2:on_finished()
            hero:unfreeze()
          end
        end
        if direction4 == 1 then
          local hero = self:get_game():get_hero()
          local heroanim = hero:get_animation()
          hero:set_animation(heroanim)
          local dir = 1
          hero:freeze()
          local m2 = sol.movement.create("straight")
          m2:set_speed(90)
          m2:set_angle(dir*math.pi/2)
          m2:set_max_distance(24)
          m2:start(hero)
          hero:set_animation(heroanim)
          function m2:on_obstacle_reached()
            hero:unfreeze()
          end
          function m2:on_finished()
            hero:unfreeze()
          end
        end
        if direction4 == 2 then
          local hero = self:get_game():get_hero()
          local heroanim = hero:get_animation()
          hero:set_animation(heroanim)
          local dir = 2
          hero:freeze()
          local m2 = sol.movement.create("straight")
          m2:set_speed(90)
          m2:set_angle(dir*math.pi/2)
          m2:set_max_distance(32)
          m2:start(hero)
          hero:set_animation(heroanim)
          function m2:on_obstacle_reached()
            hero:unfreeze()
          end
          function m2:on_finished()
            hero:unfreeze()
          end
        end
        if direction4 == 3 then
          local hero = self:get_game():get_hero()
          local heroanim = hero:get_animation()
          hero:set_animation(heroanim)
          local dir = 3
          hero:freeze()
          local m = sol.movement.create("straight")
          m:set_speed(500)
          m:set_angle(dir*math.pi/2)
          m:set_max_distance(32)
          m:start(hero)
          hero:set_animation(heroanim)
          function m:on_obstacle_reached()
            hero:unfreeze()
          end
          function m:on_finished()
            local dir = 3
            local m2 = sol.movement.create("straight")
            m2:set_speed(90)
            m2:set_angle(dir*math.pi/2)
            m2:set_max_distance(16)
            m2:start(hero)
            hero:set_animation(heroanim)
            function m2:on_obstacle_reached()
              hero:unfreeze()
            end
            function m2:on_finished()
              hero:unfreeze()
            end
          end
        end
    end
  end

  -- Store the position and properties of enemies.
  for enemy in map:get_entities("auto_enemy") do
    local x, y, layer = enemy:get_position()
    enemy_places[#enemy_places + 1] = {
      x = x,
      y = y,
      layer = layer,
      breed = enemy:get_breed(),
      direction = enemy:get_sprite():get_direction(),
      name = enemy:get_name(),
      treasure = { enemy:get_treasure() },
      enemy = enemy,
    }
  end

  local function get_destructible_sprite_name(destructible)
    -- TODO the engine should have a destructible:get_sprite() method.
    -- As a temporary workaround we use the one of custom entity, fortunately
    -- it happens to work for all types of entities.
    local sprite = sol.main.get_metatable("custom_entity").get_sprite(destructible)
    return sprite ~= nil and sprite:get_animation_set() or ""
  end

  -- Store the position and properties of destructibles.
  for destructible in map:get_entities("auto_destructible") do
    local x, y, layer = destructible:get_position()
    destructible_places[#destructible_places + 1] = {
      x = x,
      y = y,
      layer = layer,
      name = destructible:get_name(),
      treasure = { destructible:get_treasure() },
      sprite = get_destructible_sprite_name(destructible),
      destruction_sound = destructible:get_destruction_sound(),
      weight = destructible:get_weight(),
      can_be_cut = destructible:get_can_be_cut(),
      can_explode = destructible:get_can_explode(),
      can_regenerate = destructible:get_can_regenerate(),
      damage_on_enemies = destructible:get_damage_on_enemies(),
      ground = destructible:get_modified_ground(),
      destructible = destructible,
    }
  end

end

return separator_manager
#2
Development / Separator_on_activated direction4
February 14, 2017, 10:53:12 PM
I'm trying to force the player to get moved into a room after a separator has finished scrolling towards South.

Code (lua) Select
function sub_separator_h:on_activated(3)
-- slide down.
  local hero = self:get_game():get_hero()
  sol.audio.play_sound("explosion")
 
  -- Create movement.
  local dir = 3
    hero:freeze()
  local m = sol.movement.create("straight")
  m:set_speed(90)
  m:set_angle(dir*math.pi/2)
  m:set_max_distance(32)
  m:start(hero)
  function m:on_obstacle_reached()
    hero:unfreeze()
  end
  function m:on_finished()
    hero:unfreeze()
  end
end


But it causes this error:

QuoteError: Failed to load script 'maps/testlevel': [string "maps/testlevel.lua"]:41: <name> or '...' expected near '3'
Line 41 is the beginning of the script here. The documentation for Separators suggests "on_activated(direction4)" only needs a number referring to the proper direction (in this case South), but I don't know how to write it so it won't throw this error.
#3
General discussion / Re: Importing levels from Graal
February 06, 2017, 02:29:38 AM
Quote from: Neovyse on February 05, 2017, 11:03:43 PM
Cool! It could be integrated into Solarus Quest Editor.
Nah, I'd say the scope is too limited for that. All you get out of it is the tile grid, not any of the code or interactivity. And that being the case, it would be far more useful to write in something like support for Tiled Map Editor, which can already export to Lua.

Besides that, having the name "Graal" anywhere near an official feature list in a game engine might set off a few alarm bells at the company who owns it. That's not what this thread is about.
#4
General discussion / Re: Importing levels from Graal
February 05, 2017, 09:33:17 PM
I've built a crude tool from the Python converter I linked upthread. At the moment, it spits out half-broken tmx files with the tile data reformatted to Solarus's specifications. I then used a text editor to paste that data into a Solarus map file.
I've also cobbled together an acceptable tileset to use. I haven't gone through it to set every single Pattern, but I've got some basic walls and water working. It also looks kind of weird because I had to scale it down for Solarus, and I was aiming for just getting it to work instead of making it look good.

I'd prefer not to release the converter itself until I can hack it up a bit more so it at least creates Solarus maps that can just be dropped into a quest. That way, anyone who finds this thread won't have to ask me why it's broken! (I'm still convinced that someone who knows what they're doing could create a better tool by using the information in this thread and the Solarus manual, but eh)

Here's a quick demo quest made for Solarus 1.6 if you want to see the results for yourself:

Demo quest
There are two converted levels here. Leave through the door in the starting area to see the first one, and go to the North edge of the screen from there to see the second.

I do think that the only real advantage of doing this at all is when you want to import your old Graal levels without having to re-build them from scratch. Anyone who wants to create new material would have better luck just using the Solarus editor normally.
#5
General discussion / Re: Importing levels from Graal
February 05, 2017, 11:49:19 AM
The last time I checked, Stefan Knorr (Graal's creator and lead programmer) was skilled enough to be writing his own engine and was trying to get it off the ground as a Facebook game. It looks like the guy who joined this forum recently is just one of the many people who developed games using Graal as a platform, like me. It was very powerful in its heyday, and had a lot of community support and inertia behind it. A pity it was all so centralized that the whole thing could be scuttled by bad actors at the head of the business side of things.

Stefan's new game isn't really a developer platform the way Graal was. But Solarus can do nearly everything Graal was capable of in a familiar Zelda format, which is why old Graal hands like us are interested in using it. (Too bad there isn't more crossover from the Zelda Classic community as well...)


RE: level generation... if I can get an importer to work, the old Graal tool might still prove useful. It was never really that good at its job, though...
#6
General discussion / Re: Importing levels from Graal
February 04, 2017, 09:35:42 PM
Some research has turned up a Python-based converter available on GitHub which can write Graal levels to PNG images or (more usefully) Tiled map editor's TMX format.

https://github.com/Aeva/nw-converter

After struggling for a while to understand Python well enough to make it work (a directory named "out" needs to exist, which isn't in the readme), it looks like a quick Solarus converter can be forked from it. I might try to write one myself, but my programming skills are very crude and it might take me a while to figure out how to plot out a good approach. Ideally, a good converter would be able to notice lines or blocks of repeating tiles and change them to a single resized tile for Solarus. But I'd just settle for translating the entire grid of 4096 tiles to a 512x512-sized Solarus map.

This doesn't address how to actually get the Tileset itself setup in the Solarus quest editor though. While the scale of the image is twice the size of a typical Solarus tileset, that's fixable with some photo editing. The bigger problem is that once it's properly shrunk the whole thing amounts to 4096 patterns in all, which would take forever to do by hand. There's got to be some way to automate this as well.
#7
General discussion / Importing levels from Graal
February 04, 2017, 03:10:10 PM
Graal has been mentioned a few times here. It's a game from 1999 that originally began as a Zelda clone and editor, very similar to Solarus in most respects. It was re-named to Graal in an attempt to break away from Nintendo's IP and eventually become a commercial game. Unlike Solarus, it has built-in netcode for online play which eventually became the focus of the engine.

Graal is fondly remembered as a (formerly) free development platform for online games with Zelda gameplay, and is mostly known today as a popular iPhone game, but it is currently under the control of a company which is infamous among long-time players and its former employees for predatory terms of service and a consistently quasi-legal approach to business. (In fact, even the game's lead programmer eventually left, citing the head of the company as very bad to be in business with.)
It's sufficient to say Graal is no longer an ideal platform.

Now that the classic Graal engine is on its way out, Solarus neatly outdoes it.
Documentation from Graal's former free years isn't easy to come by, but I have some old backups, including a collection of levels that my friends and I built a long time ago which I'm hoping to get converted to Solarus. There's enough material for a large quest here.

The Graal level format is text-based and details a 64x64 tile grid, referencing a single tileset with 4096 patterns. Here's the official documentation:
Format:
  It begins with the signature GLEVNW01.
  It can contain following data types:

  - board lines:

    BOARD x y width layerindex <tilesdata>

    This is one line of the background layer. 'tilesdata' has a length if w*2
    characters; each tile is a 12bit-index into the tile list (pics1.png) and is
    encoded in two base64 characters (upper 6 bit first)
    base64: String = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    so AA is tile 0, AB is tile 1, AC is tile 2, // is tile 4095 (=0xFFF)

  - level links:

    LINK <destination-level> x y width height newx newy

    The link attributes are the same like in the link options window in Graal.
    destination-level, newx and newy must not contain spaces.

  - signs:

    SIGN x y
    text
    ...
    SIGNEND

    Signs may be empty, but SIGNEND must always be there.

  - npcs:

    NPC imagefilename x y
    script
    ...
    NPCEND

    If the npc doesn't have a filename (invisible npc), then write '-' for imagefilename.
    x and y may be floating point values. The npc script may be empty, but NPCEND
    must always be there

  - baddies:

    BADDY x y type
    attackverse
    hurtverse
    winverse
    BADDYEND

    'type' is the baddy name (graysoldier ... dragon ) or index (0...9). You don't need
    to write all 3 verses, it's also possible to only write attackverse+hurtverse or only
    attackverse, but BADDYEND must always be there.

  - chests:

    CHEST x y item signindex

    item is the item name (greenrupee ... spinattack) or item index (0...24)



Simple, right? This could be translated to Solarus's own text-based map format, probably by writing a converter. The trick, I think, is making sure the Base64 tile grid can be re-interpreted to apply to a Solarus tileset. And while almost everything else has a direct analogue in Solarus, NPCs "script" field would have its own challenges which I think are best ignored, since it's a proprietary language that wouldn't translate directly to Lua without a considerable amount of effort.

Here is a sample of an actual level:
GLEVNW01
BOARD 0 0 64 0 FDFEFCHNHOHPIyIzJtf/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/HkGOGOGOGOGOG7f/f/f/f/f/f/f/f/f/f/f/ABf/f/f/f/f/f/f/f/f/f/DiDjDkCqCqCqCqCqCqCq
BOARD 0 1 64 0 FCFCFCHdHeHfJCJDJ9f/f/f/f/ABf/f/f/f/f/f/f/f/f/f/f/HkGOGOGOGOGOG7f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/DSDTDUCqCqCqCqCqCqCq
BOARD 0 2 64 0 FCFCFCHNHOHPIyIzJtf/f/f/f/f/f/f/f/ABf/f/f/f/f/f/f/HkGOGOGOGOGOHnHXf/f/f/f/f/f/f/AAf/f/f/f/f/ABf/f/f/f/f/f/f/DiDjDkCqCqCqCqCqCqCq
BOARD 0 3 64 0 FCFCFCHdHeHfJCJDJ9f/AAf/f/f/f/f/f/f/f/f/f/f/f/f/f/HkGOGOGOGOGOGOHnHXf/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/DSDTDUCqCqCqCqCqCqCq
BOARD 0 4 64 0 FCFCFCHNHOHPIyIzJtf/f/f/f/f/AAf/f/f/f/f/f/f/f/ABf/HkGOH1H0GOGOGOGOG7f/f/f/AAf/f/f/f/f/AAf/f/f/f/f/f/f/f/f/f/DiDjDkCqCqCqCqCqCqCq
BOARD 0 5 64 0 FCFCFCHdHeHfJCJDJ9f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/GmGWGOGOGOGOGOGOG7f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/DSFhC4F9CqCqCqCqCqCq
BOARD 0 6 64 0 FCFCFCHNHOHPIyIzJtf/f/f/f/f/f/f/f/ABf/f/AAf/f/f/f/H/GmGWGOGOGOGOGOG7f/f/f/f/f/f/f/f/ABf/f/f/f/f/f/f/f/f/f/f/DiDYFxC4F9CqCqCqCqCq
BOARD 0 7 64 0 FCFCFCHdHeHfJCJDJ9f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/GmGWGOGOGOGOHnHXf/f/f/f/f/f/DmDnH/f/f/f/f/f/f/f/f/f/f/GQGAGBDIC4F9CqCqCqCq
BOARD 0 8 64 0 FCFCFCHNHOHPIyIzJtf/f/f/f/f/AAf/f/f/f/f/f/f/f/f/f/f/f/f/GmGWGOGOGOGOHnHXf/f/f/AAf/D2D3Dnf/f/f/f/f/f/f/f/f/f/f/GQGRDYFxC4F9CqCqCq
BOARD 0 9 64 0 FCFCFCHdHeHfJCJDJ9f/f/f/f/f/f/f/f/f/f/f/f/ABf/f/f/f/f/ABf/GmGWGOGOGOGOHnHXH/f/f/f/CmCnD3CpCpDnf/f/f/f/f/f/f/f/f/GQGAGBGNDUCqCqCq
BOARD 0 10 64 0 FCFCFCHNHOHPIyIzJtf/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/GmGWGOGOGOGOHnHXf/f/f/C2C3C4C5C6D3Dnf/f/f/f/f/f/f/ABf/GQGRJCDkCqCqCq
BOARD 0 11 64 0 FCFCFCHdHeHfJCJDJ9f/f/f/f/ABf/f/f/f/AAf/f/f/f/f/AAf/f/f/f/f/f/GmGWGOH1H0GOG7f/f/f/f/DHDIDJDKC4D3CpCpCpCpDnf/f/f/f/AADSDTDUCqCqCq
BOARD 0 12 64 0 FCFCFCHNHOHPIyIzJtf/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/GmGWGOGOG3HHf/f/f/f/DXDYDZDaFxC4C5C6C5C6D3Dnf/f/f/DwDiDjDkCqCqCq
BOARD 0 13 64 0 FCFCFCHdHeHfJCJDJ9f/AAf/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/ABf/H/GmH2H3HHH/f/f/f/f/H/DoDpDqGBDIDJDKDJDKC4D3Dnf/DwDxDyDzC1CqCqCq
BOARD 0 14 64 0 FCFCFCHNHOHPIyIzJtf/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/ABf/f/H/D4D5D6GRDYDZDaDZDaFxC4D3DnDxDyDzC1CqCqCqCq
BOARD 0 15 64 0 FCFCFCHdHeHfJCJDJ9f/f/f/f/f/f/f/f/ABf/f/f/f/f/f/f/f/f/AAf/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/GQGADpDqDpDqGBDIC4D3DSDTDUCqCqCqCqCq
BOARD 0 16 64 0 FCFCFCHNHOHPIyIzJtf/f/f/f/f/f/f/f/f/f/f/f/f/f/AAf/f/f/f/f/f/f/f/f/ABf/f/AAf/f/f/f/f/f/f/f/f/f/GQD5D6D5D6GRDYFxC4DiDjDkCqCqCqCqCq
BOARD 0 17 64 0 FCFCFCHdHeHfJCJDJ9f/f/f/f/f/AAf/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/GQGAGBGNEUDTDUCqCqCqCqCq
BOARD 0 18 64 0 FCFCFCHNHOHPIyIzJtf/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/AJAKALAMANAOAJAKALAMANAOAJAKALAMANAOAJAKALAMANAOf/f/GQGRJCEkDjDkCqCqCqCqCq
BOARD 0 19 64 0 FCFCFCHdHeHfJCJDJ9f/f/f/f/f/f/f/f/f/f/f/f/ABf/f/f/f/AYYHYIYIYIYIYJAZAaAbAcAdAeAZAaAbAcAdAeAZAaAbAcAdAeAff/f/DSDTEUDTDUCqCqCqCqCq
BOARD 0 20 64 0 FCFCFCHNHOHPIyIzJtf/f/f/f/f/f/f/ABf/f/f/f/f/f/f/f/f/AoYXYYYcYcYYYZApAqArAsAtAuApAqArAsAtAuApAqArAsAtAuAvf/f/DiDjEkDjDkCqCqCqCqCq
BOARD 0 21 64 0 FCFCFCHdHeHfJCJDJ9I5f/f/f/f/f/f/f/f/AAf/f/f/f/f/f/f/A4YXYZHIH6YXYZA5A6A7A8A9A+A5A6A7A8A9A+A5A6A7A8A9A+A/f/f/DSDTEUDTDUCqCqCqCqCq
BOARD 0 22 64 0 FCFCFCHNHOHPFhC4I3I4I5f/f/AAf/f/f/f/f/f/f/f/f/f/f/f/BIYXYZHJG5YXYZBJBKBLBMBNBOBJBKBLBMBNBOBJBKBLBMBNBOBPf/f/DiDjEkDjDkCqCqCqCqCq
BOARD 0 23 64 0 FCFCFCHdHeGADYFxC4I3I4f/f/f/f/f/f/f/f/f/f/f/f/ABf/f/BYYKYLTnToYKYLBZBaBbBcBdBeBZBaBbBcBdBeBZBaBbBcBdBeBff/f/DSDTEUFhC4F9CqCqCqCq
BOARD 0 24 64 0 FCFCFCI0I1I2GAGBDIIzJtf/f/f/f/f/f/f/f/f/f/f/f/f/f/f/BoBpBqTnToBtBuBpBqBrBsBtBuBpBqBrBsBtBuBpBqBrBsBtBuBvf/f/DiDjEkDYFxC4C5C6C5C6
BOARD 0 25 64 0 FCFCFCFCI0I1I2GADYJDJ9f/f/f/f/f/f/f/f/f/AAf/f/f/f/f/B4B5B6TnToB9B+B5B6B7B8B9B+B5B6B7B8B9B+B5B6B7B8B9B+B/f/f/DSFhH+GAGBDIDJDKDJDK
BOARD 0 26 64 0 FCFCFCFCFCHNHOHPDTIzJtf/f/f/f/f/f/ABf/f/f/f/f/f/f/IAIBCJCKTnToCNCOCJCKCLCMCNCOCJCKCLCMCNCOCJCKCLCMCNCOf/f/f/DiDYFxH+GRDYDZDaDZDa
BOARD 0 27 64 0 FCFCFCFCFCHdHeHfDjJDJ9f/f/f/f/f/f/f/f/f/f/f/f/f/f/IQIRf/CaT3T4Cdf/f/CaCbCcCdf/f/CaCbCcCdf/f/CaCbCcCdf/f/f/f/GQGAGBDIH+GADpDqDpDq
BOARD 0 28 64 0 FCFCFCFCFCHNHOHPDTIzJtf/f/f/AAf/f/f/f/AAf/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/ACADf/f/f/f/f/f/f/f/f/f/f/H/GQGRDYFxH+H8H9H8H9
BOARD 0 29 64 0 FCFCFCFCFCHdHeHfDjJDJ9f/f/f/f/f/ABf/f/f/f/f/f/f/f/f/f/f/f/AAf/f/ACADf/f/f/f/f/f/f/ASATf/f/f/F2FnFmF3f/f/f/f/AAf/GQGAGBDIDJDKDJDK
BOARD 0 30 64 0 FCFCFCFCFCHNHOHPDTIzJtf/f/f/f/f/f/f/f/f/f/f/PzP0f/f/ABf/f/f/f/f/ASATf/f/f/f/f/ABf/f/f/f/f/F2GGAIEtGXACADf/f/f/f/H/GQGRDYDZDaDZDa
BOARD 0 31 64 0 FCFCFCFCFCHdHeHfDjJDJ9f/f/f/f/f/f/f/f/f/f/f/PAPBf/f/f/f/f/f/f/f/f/f/f/f/f/ACADf/f/f/f/f/f/GnAICrFoFpASATf/f/f/f/f/f/GQGADpDqDpDq
BOARD 0 32 64 0 FCFCFCFCFCHNHOHPDTIzJtf/f/f/f/f/f/f/f/f/f/f/PQPRf/f/f/f/f/f/f/f/f/f/f/f/f/ASATf/f/f/f/f/f/GIF4F5Fpf/f/f/f/f/f/f/f/ABH/GQD5D6D5D6
BOARD 0 33 64 0 FCFCFCFCFCHdHeHfDjJDJ9f/f/f/f/AAf/f/f/f/f/f/PgPhf/f/f/f/f/f/f/f/f/f/AAf/f/f/f/f/ACADf/f/ACADf/f/ACADf/f/f/f/f/f/f/f/f/f/f/f/f/f/
BOARD 0 34 64 0 FCFCFCFCFCHNHOHPIyIzJtf/ABf/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/ABf/f/f/f/f/f/f/f/ASATACADASATACADASATf/f/f/f/f/AAf/f/f/f/f/f/f/f/
BOARD 0 35 64 0 FCFCFCFCFCHdHeHfJCJDJ9f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/F2FmF3AAf/ASATf/f/ASATf/f/f/f/ABf/f/f/f/f/f/f/f/f/f/f/
BOARD 0 36 64 0 FCFCFCFCFCHNHOHPIyIzJtf/f/f/f/f/PzP0f/f/f/f/f/f/f/f/f/f/PzP0f/f/f/f/f/F2FmGGCrGXf/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/
BOARD 0 37 64 0 FCFCFCFCFCHdHeHfJCJDJ9f/f/f/f/f/PAPBf/f/f/f/f/f/f/f/f/f/PAPBf/f/f/F2FmGGCrCqCqGHFmFnFmFnFmF3f/f/f/f/f/F2FmFnFmFnF3f/f/AAf/f/f/f/
BOARD 0 38 64 0 FCFCFCFCFCHNHOHPIyIzJtf/f/f/f/f/PQPRf/f/f/f/OzO0f/f/f/f/PQPRf/f/f/GnAICqCqCqCqCqEtCqAPCrEtGHFmF3F2FnFmGGCrAIAPAIGHF3f/f/f/f/f/f/
BOARD 0 39 64 0 FCFCFCFCFCHdHeHfDjJDJ9f/f/f/f/f/PgPhf/f/f/f/OzO0f/f/f/f/PgPhf/F2FmGGAPCqAPCrCqCqEtCqCqCqCqAICqGHGGAICrAPCqCqCqCqCrGXf/f/f/AAf/f/
BOARD 0 40 64 0 FCFCFCFCFCHNHOHPDTIzJtf/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/GnAPEtCqEtEtCqEtEtCrCrCqCrEtAICrEtCqCqAPCqCqCqAIEtFoFpABf/f/f/f/f/
BOARD 0 41 64 0 FCFCFCFCFCHdHeHfDjJDJ9f/f/f/f/AAf/f/f/f/f/f/f/f/f/f/f/AAf/f/f/GIF4F5F4F5GJCqCqCqCqCqCqCqCqCqCqCqCqCqCqCrAIAIFoF5Fpf/f/f/f/f/f/f/
BOARD 0 42 64 0 FCFCFCFCFCHNHOHPDTIzJtf/ABf/f/f/f/f/AAf/f/f/f/f/ABf/f/f/P/f/f/f/f/f/f/f/GIF5GJAICqCrAICqCqEtAIAPCqEtCqFoF4F5Fpf/f/f/f/f/f/f/f/f/
BOARD 0 43 64 0 FCFCFCFCFCHdHeHfDjJDJ9f/f/f/f/ABf/f/f/f/f/f/PzP0f/f/f/f/f/f/f/f/AAf/f/f/f/f/GIF5F4F5GJEtAICqCrFoF4F5F4Fpf/f/f/f/f/f/f/f/f/f/f/f/
BOARD 0 44 64 0 FCFCFCFCFCHNHOHPDTIzJtf/f/f/f/f/AAf/f/f/f/f/PAPBf/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/GIF5F4F5F4Fpf/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/
BOARD 0 45 64 0 FCFCFCFCFCHdHeHfDjJDJ9f/f/ABf/f/f/f/f/f/f/f/PQPRf/f/ABf/f/ABf/f/f/f/f/E0KOKPKOKPKOKPKOKPKOKPKOKPKOKPKOKPKOKPKOKPKOKPKOKPKOKPKOKP
BOARD 0 46 64 0 FCFCFCFCFCHNHOHPIyIzJtf/f/f/f/f/f/f/f/f/f/f/PgPhf/f/f/f/f/f/f/f/f/f/E0E1KeKfKeKfKeKfKeKfKeKfKeKfKeKfKeKfKeKfKeKfKeKfKeKfKeKfKeKf
BOARD 0 47 64 0 FCFCFCFCFCHdHeHfJCJDJ9f/f/f/f/f/AAf/f/f/f/f/f/f/f/f/f/f/P/f/f/f/f/E0E1E2C5C6C5C6C5C6C5C6C5C6C5C6C5C6C5C6C5C6C5C6C5C6C5C6C5C6C5C6
BOARD 0 48 64 0 FCFCFCFCFCHNHOHPIyIzJtf/f/ABf/f/f/f/f/f/AAf/f/f/f/f/f/f/f/f/f/f/E0E1E2C7DJDKDJDKDJDKDJDKDJDKDJDKDJDKDJDKDJDKDJDKDJDKDJDKDJDKDJDK
BOARD 0 49 64 0 FCFCFCFCFCHdHeHfJCJDJ9f/f/f/f/f/f/ABf/f/f/f/f/f/f/f/f/AAf/f/f/E0E1E2C7DeDZDaDZDaDZDaDZDaDZDaDZDaDZDaDZDaDZDaDZDaDZDaDZDaDZDaDZDa
BOARD 0 50 64 0 FCFCFCFCFCHNHOHPIyIzJtf/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/E0E1E2C7DLDuDpDqDpDqDpDqDpDqDpDqDpDqDpDqDpDqDpDqDpDqDpDqDpDqDpDqDpDq
BOARD 0 51 64 0 FCFCFCFCFCHdHeHfJCJDJ9f/f/f/f/f/f/f/f/f/f/f/f/f/ABf/f/f/f/E0E1E2C7DeDbD+IuIvIuIvIuIvIuIvIuIvIuIvIuIvIuIvIuIvIuIvIuIvIuIvIuIvIuIv
BOARD 0 52 64 0 FCFCFCFCFCHNHOHPIyJDJ9I5f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/f/E0E1E2C7DLDuDvEmI+I/I+I/I+I/I+I/I+I/I+I/I+I/I+I/I+I/I+I/I+I/I+I/I+I/I+I/
BOARD 0 53 64 0 FCFCFCFCFCHNHOHPGtC4I3I4KOKPKOKPKOKPKOKPKOKPKOKPKOKPKOKPE1E2C7DeDbD+EmEnJOJPJOJPJOJPJOJPJOJPJOJPJOJPJOJPJOJPJOJPJOJPJOJPJOJPJOJP
BOARD 0 54 64 0 FCFCFCFCFCHdHeHfDYFxC4I3KeKfKeKfKeKfKeKfKeKfKeKfKeKfKeKfE2C7DLDuDvEmEnEoFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFC
BOARD 0 55 64 0 FCFCFCFCFCI0I1I2GAGBDIC4C5C6C5C6C5C6C5C6C5C6C5C6C5C6C5C6C7DeDbD+EmEnEoFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFC
BOARD 0 56 64 0 FCFCFCFCFCFCI0I1I2GAGBDIDJDKDJDKDJDKDJDKDJDKDJDKDJDKDJDKDeDuDvEmEnEoFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFC
BOARD 0 57 64 0 FCFCFCFCFCFCFCI0I1I2GAGBDZDaDZDaDZDaDZDaDZDaDZDaDZDaDZDaDuDvEmEnEoFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFC
BOARD 0 58 64 0 FCFCFCFCFCFCFCFCI0I1I2GADpDqDpDqDpDqDpDqDpDqDpDqDpDqDpDqDvEmEnEoFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFC
BOARD 0 59 64 0 FCFCFCFCFCFCFCFCFCI0I1I2IuIvIuIvIuIvIuIvIuIvIuIvIuIvIuIvEmEnEoFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFC
BOARD 0 60 64 0 FCFCFCFCFCFCFCFCFCFCI0I1I+I/I+I/I+I/I+I/I+I/I+I/I+I/I+I/EnEoFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFC
BOARD 0 61 64 0 FCFCFCFCFCFCFCFCFCFCFCI0JOJPJOJPJOJPJOJPJOJPJOJPJOJPJOJPEoFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFC
BOARD 0 62 64 0 FCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFC
BOARD 0 63 64 0 FCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFC
LINK d2a2010-b2.nw 63 0 1 64 0 playery
LINK d2a2010-bm2.nw 0 0 64 1 playerx 61
SIGN 25 27

#u Shrine
SIGNEND
NPC - 21 11
// invisible NPC sets music and map
if (playerenters) {enableweapons;
//play d.mid;
setminimap dmap2.png,map-d2a2010.txt,1,56;
setmap d2a2010-map-ow.png,map-d2a2010.txt,1,56;
puthorse goodride.gif,40,40;}
if (playertouchsme) {
}
NPCEND


I'd like to get some thoughts from Christopho and other Solarus developers to figure out how to get this to work, since I'm not sure where to begin.
#8
This feature request might not even be necessary if we get that Issue fixed, and add a hotkey/menu option to change a tile's Z-position on the current layer that won't send it all the way to the front or back. This would address most cases where it's a problem as far as I know.
#9
I notice this is the standard in Solarus quests. The overworld works more like I would expect, as it's broken up into a grid of many maps based on various points of interest.

In A Link to the Past, dungeon screens were equivalent to 512x512 maps, no smaller than that. In case a smaller room was needed, the map would be subdivided, which can be done in Solarus with Separator objects.
This explains how you could occasionally hear sounds coming from nearby rooms, and why enemies and puzzles wouldn't respawn or reset right away sometimes. (Because they're not reloaded when you are just going between separators.)
#10
Development / Thinking with overlapping tiles
July 09, 2015, 07:04:08 AM
I suspect newcomers to the editor might have some trouble with this at first, since most everything else out there uses a basic tile grid for editing maps.
You can do that in Solarus of course, but I actually really like how you can overlap tiles as much as you want, and whatever's on top takes priority over whatever's underneath it on the same layer.
You can use it to knock out passageways for doors in an otherwise singular wall object, instead of making two walls on each side. Just lazily put down a walkable floor right on top of the wall and extend it out so it overlaps the other wall, then decorate with door graphics.
Coming from other editors, the first thing you might try to do when you make something that has "fringes" like a carpet is lay out the carpet and then surround it with up to four separate border pieces, but if the borders are just the same tile graphic you don't have to. You can lay out a carpet made of two extended tiles; one is the border piece, and you just make it as big as the whole carpet is going to be. The other is the carpet itself, which you overlap as much as you want.

Every tile is essentially another block of text in your map files, so if you think creatively when you set up and use your tilesets, you can keep things cleaner and take lots of shortcuts that wouldn't be possible in other editors.

Anyway, I like it.
#11
There is a whole world of really fiddly errors with the A Link to the Past tilesets. I think this is because there's just so much going on in the game's graphics that nobody really sweats the small stuff.

Something that bothers me with the way the tilesets are laid out in Solarus is how they sometimes break down objects into smaller pieces than LttP ever needed them to have. Mountains are kind of a big one; they have a very particular "layer cake" style to them that's almost never fudged, and you can certainly re-create that in Solarus, but it's not immediately obvious to someone who's never paid that much attention to the graphics in LttP.

I don't know if that's a priority in current versions of Solarus, whether or not Christopho wants to leave the Zelda IP behind and go for something more safe and independent that just happens to have the same gameplay. But it could be a fun side project for anyone brave enough to try it.
#12
I tried to resize my quest from 320x240 down to 256x224, which is the SNES screen resolution.

In doing this, Scroll teletransporters stopped working. They seem to roll over twice before locking the game.

They stop working in the same way if the quest size is larger than 320x240 as well.
#13
What I mean with the selection tool is, you can hold left-click and drag to create a rectangle. Anything inside the rectangle is then selected. This is useful for working with groups of tiles that form a single object such as trees, mountains, dungeon door frames, and so on.
But if you left-click on a tile that already exists (whether it's from the tile window or the map window) it will not let you create a selection rectangle. There needs to be some kind of keystroke that will invoke the rectangle no matter what.

And what I mean with dialog is:
In a group project scenario, when someone is making maps, they usually want to put in NPCs. The "show a dialog" option currently refers to the master file "dialogs.dat", which is one large file containing every single dialog in the entire quest.
In order to combine work from multiple contributors, things will get very difficult very quickly unless "dialogs.dat" is phased out and each dialog can have its own unique ".dat" file instead. The folder structure is already neatly organized by language anyway, so doing this would still allow for translations to be made.
#14
I've been looking at this engine for a couple days recently and I'm very happy with how it's built as of version 1.4.2. I particularly love how quests are just a collection of files and Lua scripts, not a series of weird formats or a single big package. (Zelda Classic's "qst" format and all its numbered slots are murderously hostile to the very idea of rapid development and group organization with multiple contributors. It deserves to be left in the dust bin of the DOS era from whence it came.)
In fact, I am over the moon with how you can just drop things into the quest's directories and the editor instantly knows they exist without having to reload anything. This is absolutely vital to rapid development and decentralized group collaborations. An engine like this hasn't come along in a very long time and I'm eager to explore what it can do.

There are just a few things I want to look into to really start working on something with this.

First: When editing Maps, it seems that the click+drag selection tool doesn't work unless I start by clicking on empty space. This happens in both the tile selector and the map area itself. It causes me to spend an unnecessary amount of time very carefully shift-clicking on every single tile I want selected.

Second: When dropping resources into the game, sometimes the editor needs to integrate them into its database before they can be used, and this has to be done with user input. Would be nice if this were automated a bit better or perhaps even made unnecessary.
Imagine a group of three to five (or ten!) people collaborating on a single quest. Addressing this issue would help.

Third: Dialog entries in particular are all bundled into a single large file. This is one of the few things in this engine that is hostile to group collaborations. I suppose it could be circumvented somewhat by using some custom scripts, but I don't think it should have to come to that, as doing so would theoretically make everything a little more difficult than it should be. (Particularly translations.)