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

#61
General discussion / Re: Need Help With Enemy Scripting
November 01, 2018, 03:26:44 PM
Quote from: wrightmat on November 01, 2018, 02:24:28 PM
I believe I borrowed my pike script from one of christopho's games, and I don't recall having any issues with it.
https://github.com/wrightmat/zbom/blob/master/data/enemies/pike_detect.lua

Nice! This one goes all 4 directions.

Ohh, it also travels only a max of 8 squares in any direction (and ensures the hero is within that range).

It's checking on_obstacle_reached as well as on_movement_finished to decide whether to send it back to its original point. Obviously "on_obstacle_reached" would be triggered when hitting the hero, hitting any other blocking object, or hitting the wall if the wall is less than 8 squares away.
#62
I'm experimenting with making NPCs shiver back and forth when I use my vacuum item on them. It's a pixel movement that jitters an entity left and right by 2px.

When I try it, it says: Error: Illegal direction 3 for sprite 'animals/seagull_trash2' in animation 'walking'

I'm guessing that using a movement on an NPC is "smart" and makes the NPC automatically change direction toward the movement and try to walk that way. Am I right? If so, is there an easy way to override it?

It's not a big deal, and I might just not do this in my game. It doesn't seem documented though, and kinda surprised me.
#63
Development / Re: Stopping a sound effect
October 29, 2018, 08:14:50 PM
Sooo, I tried both methods (Max's and llamazing's). I thought Max's idea was pretty bright but I was getting all kinds of audio buffer errors. It also had a choppy sound. Code was basically this:

Code ( lua) Select
function item:_play_sound()
  local speed = 390 / self._power
  sol.audio.play_sound("alex/vacuum_2")
  self._sound_timers[self._power] = sol.timer.start(self, speed, function()
    self._sound_timers[self._power] = nil
    self:_play_sound()
  end)
end


Maybe there was a way to make this work right, but I'm not sure. Anyway I ended up breaking it up into 3 different sounds and writing this monstrous code:

Code ( lua) Select
-- Handle repeating sfx
-- FIXME: Make this code not horrible!
-- See: https://gitlab.com/solarus-games/solarus/issues/1289
function item:_play_sound()
  if self._power == 1 and not self._sound_timers[1] then
    log("sfx start: Vacuum 1")
    sol.audio.play_sound("vacuum_1")
    self._sound_timers[1] = sol.timer.start(self, 180, function()
      self._sound_timers[1] = nil
      item:_play_sound()
    end)
  elseif self._power == 2 and not self._sound_timers[2] then
    log("sfx start: Vacuum 2")
    sol.audio.play_sound("vacuum_2")
    self._sound_timers[2] = sol.timer.start(self, 125, function()
      self._sound_timers[2] = nil
      item:_play_sound()
    end)
  elseif self._power == 3 and not self._sound_timers[3] then
    log("sfx start: Vacuum 3")
    sol.audio.play_sound("vacuum_3")
    self._sound_timers[3] = sol.timer.start(self, 125, function()
      self._sound_timers[3] = nil
      item:_play_sound()
    end)
  end
end


I may go back and encapsulate the repeating parts into a function, but this was bad code anyway. ::)

I also had to do this when the item command was released, which I don't totally understand.

Code ( lua) Select
  -- Set all sound timers to nil
  -- FIXME: I don't understand why looping through them didn't work
  self._sound_timers[1] = nil
  self._sound_timers[2] = nil
  self._sound_timers[3] = nil


Ultimately, _play_sound() is called whenever the vacuum's power level is changed, so that's nice. It syncs the sound up to the vacuum quite nicely. Each clip is also <200ms so it feels like a pretty immediate stop when you release the button.

Here's the full commit if anyone is interested: https://gitlab.com/voadi/voadi/commit/497699088b4db58860213ca3acc3577acd1dbd09
#64
General discussion / Re: Need Help With Enemy Scripting
October 29, 2018, 01:38:29 AM
This is an interesting challenge because the spikes could move either left or right from their initial position. I can think of many ways to go about it. A primitive solution would be to have 2 scripts: one for a left-aiming spike, one for a right-aiming one. Solarus 1.6 introduces custom entity properties, letting you configure whether a spike aims left or right by double clicking it and adding custom fields to each one.

But I think it makes more sense to assume that all spikes have a starting position, and they can move left *and* right depending on whether the hero triggers them in that direction. I don't think a Zelda game ever has a spike that can be triggered from both directions since they always start in a corner, but maybe this is how they actually are.

I'd consider storing the initial position of the enemy in the enemy itself, so you can come back to it. Something like this:

Code ( lua) Select
function enemy:on_created()
  local x, y = self:get_position()
  self._initial_x = x
  self._initial_y = y
end


Then I'd probably use enemy:on_update() (this gets called every frame) to constantly check the current position of the hero against the current position of the enemy, and if their Y values are within a certain range, activate the movement. Set the movement's angle based on difference between the X positions of the hero and the enemy.

There are also a few ways to determine when the enemy has reached the end and must reverse. I'd consider trying the entity:on_obstacle_reached() event (walls are obstacles). Also, it's possible that movement:on_finished() might be called for a straight movement if the entity hits a wall? I'm not sure, but if so that's easier.

Keep in mind that movement:start() also has a second optional callback parameter, which would be functionally the same as movement:on_finished() in this case.

To reverse back home, simply use a target movement to the enemy's initial x/y

It's a good idea to use print() a lot when writing a script like this so you can take things one step at a time. For example, make it print "hero crossed paths!" when the hero enters a zone that should trigger the enemy, before you even make the enemy actually move.

Since you said you were new to programming I tried to be thorough. Please feel free to write more questions, or say so if you can't figure it out and I can write out more parts of the script.
#65
General discussion / Re: Installing Solarus in ubuntu
October 28, 2018, 03:51:16 PM
Quote from: bonez001 on October 28, 2018, 12:51:14 PM
I already installed it. But the solarus editor is unusable. I don't know why.

Please open the terminal and give the output of the following two commands:

cat /etc/*-release
This command will give us more information about exactly what distro you are running, and what version. Then perhaps someone can reproduce the problem.

solarus-quest-editor
By running the quest editor from the command line instead of the applications menu, we will be able to see exactly why it fails by looking at the error output.
#66
Development / Re: Solarus could use a UI framework
October 27, 2018, 01:30:42 AM
Quote from: llamazing on October 27, 2018, 01:15:47 AM
I've been working feverishly the last few months on improving just that library and making it more universally usable. There have been some significant changes in its implementation for the better. It's going to be another few months before I'm anywhere near ready to release anything, though.

That's super awesome!! Glad I brought it up, I'll look forward to using your library once you release it. ;D For now I'm coding my menus in a horrible way that I'll surely regret, lol.

Quote from: llamazing on October 27, 2018, 01:15:47 AM
I think having some sort of ui support built-in to solarus is a good idea, even if it only covers some of the lower-level stuff.

I'm wondering if a package manager to manage third-party libraries would be a more flexible solution. I'm aware of LuaRocks (although I've never used it). But yeah, having something like a package.json (Node) or requirements.txt (Python) that lets you install a list of modules with a single command would be great.
#67
Development / Solarus could use a UI framework
October 26, 2018, 11:19:04 PM
I've been focusing on menus lately. At first I found them intimidating but I'm starting to get the hang of it now. As a web developer, creating menus by drawing pixels is very weird. I know this is just how games work, though.

Still, I'm tempted to start breaking my menus into reusable components like in Vue.js. For example, I have multiple menus which contain a grid of items the player may highlight and select. I could see having an API like

Code ( lua) Select
-- Example menu with reusable components

local title_bar = require("scripts/ui/components/title_bar")
local item_picker = require("scripts/ui/components/item_picker")

local menu = {
  components = {
    title_bar:new({bg_color={0,0,0}, text="Inventory"),
    item_picker:new({row=3, col=5, gutter=8})
  }
}

function menu:on_draw()
  for _, component in ipairs(self.components) do
    component:draw(self)
  end
end

return menu


idk, I haven't fully thought this through obviously, but I'd love to see some sort of component-based design in action. I haven't fully worked out how this needs to happen in Lua.
#68
Your scripts / Re: NPC item picker script
October 26, 2018, 05:30:42 PM
Quote from: llamazing on October 26, 2018, 05:19:07 AM
btw, you should change line 7 so that it doesn't overwrite the real get_resource_ids() function once v1.6 is released.
Quote from: llamazing on October 26, 2018, 05:19:07 AM
Note that v1.6 also allows dynamically adding new resources at runtime, so parsing the project_db.dat will no longer be a viable way to get all resource ids: issue #630

Updated my post above. Thanks!
#69
Your scripts / Re: NPC item picker script
October 25, 2018, 09:59:58 PM
I ended up with npc:prompt_item(callback, [filter])

scripts/npc_prompt_item.lua
https://gitlab.com/voadi/voadi/blob/07cfbfd037ba12545cc46fcada04c18b1deb0c88/data/scripts/npc_prompt_item.lua

scripts/menus/picker.lua
https://gitlab.com/voadi/voadi/blob/07cfbfd037ba12545cc46fcada04c18b1deb0c88/data/scripts/menus/picker.lua

When the menu starts, it loops through all the game's items and applies the filter (if given). If a filter isn't given, it uses a default filter which checks that item:is_key_item() == true (a quest-specific, custom property).

Thanks again llamazing (and Christopho) for your help. :)
#70
Your scripts / Re: NPC item picker script
October 25, 2018, 05:16:44 PM
Quote from: Christopho on October 25, 2018, 09:50:40 AM
Note that Solarus 1.6 will have a new feature sol.main.get_resource_ids("item").

Nice! Using llamazing's code I created a polyfill for the feature.

scripts/polyfills/get_resource_ids.lua:
Code ( lua) Select
-- Polyfill for sol.main.get_resource_ids(resource_type)
--   See: http://www.solarus-games.org/doc/1.6/lua_api_main.html#lua_api_main_get_resource_ids
--
-- Note that v1.6 also allows dynamically adding new resources at runtime, so
-- parsing project_db.dat will no longer be a viable way to get all resource IDs
--
-- Originally created by llamazing, modified here
--   Source: http://forum.solarus-games.org/index.php/topic,1256.msg7443.html#msg7443

local function get_resource_ids(resource_type)
  local all_resources = {}

  local env = {}
  env[resource_type] = function(properties)
      local id = properties.id
      assert(type(id)=="string", "id must be a string")

      table.insert(all_resources, id)
  end

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

  local chunk = sol.main.load_file("project_db.dat")
  setfenv(chunk, env)
  chunk()

  return all_resources
end

-- Only use this polyfill if Solarus < 1.6
if not sol.main.get_resource_ids then
  sol.main.get_resource_ids = get_resource_ids
end


Added to scripts/features.lua:
Code ( lua) Select
require("scripts/polyfills/get_resource_ids")

It totally works!

> for _, item in ipairs(sol.main.get_resource_ids("item")) do print(item) end
stick
trade
vacuum
whiskey
> for _, map in ipairs(sol.main.get_resource_ids("map")) do print(map) end
0000
Water_Dungeon_final
bar
beach
beach_house_1
beach_house_2
beach_house_3
...
#71
Your scripts / Re: NPC item picker script
October 25, 2018, 05:27:37 AM
I had to stare at your code, read about Lua environments, and stare some more for like 30 minutes, until it finally clicked. quest_db is a lua file that makes function calls (item()), so we can simply define item(), load quest_db, and set its environment to our custom function(s). Awesome.

I did notice that Lua 5.2 drops setfenv() apparently. I'm guessing Solarus runs on an older version?
#72
Your scripts / Re: NPC item picker script
October 25, 2018, 04:41:04 AM
Quote from: llamazing on October 25, 2018, 04:32:32 AM
Here's a bit of a hack, but you can get the names of all items in your quest by reading the contents of the project_db.dat file:

Woaahh, thank you!! I considered doing something like this but I didn't know how. It's great to have help from a Lua wizard. ;D Thank you.
#73
Development / Re: How do PNG fonts work?
October 25, 2018, 03:17:58 AM
Thanks a lot to you both!!
#74
Your scripts / Re: NPC item picker script
October 25, 2018, 03:15:55 AM
Quote from: llamazing on October 25, 2018, 02:15:11 AM
That's nifty! Excellent work.

You should consider defining the items eligible to appear in the picker as part of the item scripts with some sort of custom property rather than inside the picker script itself. Perhaps add an is_giftable() method to the item metatable?

Thanks!

In general, items you trade will always be non-equipment items (as with most Zelda games). So I'm planning to add something like item:set_key_item(boolean), where "true" means a non-equipment item.

I'm not sure how fancy I want to get yet, but I've been thinking about npc:ask_item(filter, callback) where "filter" is a function like filter(item) that returns true if the given item should be listed.

Eg,

Code ( lua) Select
local function filter(item)
  if item:get_name() == "bad_item" then
    return false
  end
  return item:is_key_item()
end

npc:ask_item(filter, function(item)
  print(item:get_name()
)



The only thing blocking me is that Solarus doesn't seem to have a way to list all the possible items in the quest, so I can't loop through them.
#75
Your scripts / NPC item picker script
October 24, 2018, 11:24:07 PM
This is sorta hard to explain, but basically I wanted the ability for any NPC to prompt the hero for an item, then the hero has the option to select an item in their inventory to give. It works like Paper Mario if you've ever played it. Demo:



My game revolves a lot around trading items, so it's important that this is streamlined. It allows writing code like this:

Code ( lua) Select
local bunny = map:get_entity("bunny1")

function bunny:on_interaction()
  game:start_dialog("bunny.want_carrot", function()
    bunny:ask_item(function(item)
      if item and item:get_name() == "carrot"
        item:set_variant(0)
        game:start_dialog("bunny.thanks_for_carrot")
      elseif item then
        -- a non-carrot item is given
        game:start_dialog("bunny.no_thanks")
      else
        -- The action was cancelled (item == nil). Do nothing.
      end
    end)
  end)
end


I'm not sure I organized my code the best way, but here it is:

scripts/menus/picker.lua:
Code ( lua) Select
-- ♡ Copying is an act of love. Please copy and share.

-- This menu ultimately exists to let you call npc:ask_item()
-- It's designed to be called from npc:ask_item() and really cannot stand alone

require("scripts/multi_events")

-- Items that could appear in this menu
local key_items = {
  'trade',
  'whiskey',
}
local player_items = {} -- Items the player actually has


-- Get the XY for an item sprite based on its position
local function get_slot_xy(i)
  local mx, my = 24, 32 -- offset from menu origin

  local r = math.ceil(i/4) -- row of this slot
  local ix = ((i-1)%4)*24
  local iy = (r-1)*24

  local x = mx + ix
  local y = my + iy

  return x, y
end


local picker = {} -- item picker menu


function picker:initialize()
  local font, font_size = "Comicoro", 16

  -- Graphics
  self.menu = sol.surface.create(144, 104)
  self.menu_bg = sol.sprite.create("menus/menu_bg")
  self.item_sprite = sol.sprite.create("entities/items")
  self.cursor_sprite = sol.sprite.create("menus/cursor")

  -- Player sprite
  self.player_sprite = sol.sprite.create("main_heroes/rachel")
  self.player_sprite:set_animation("walking")
  self.player_sprite:set_direction(3)

  -- NPC sprite
  self.npc_sprite = nil

  -- Question text
  self.title = sol.text_surface.create({text_key="menus.picker.question", font=font, font_size=font_size})
  self.title:set_color({0, 0, 0})

  -- Nope button
  self.nope_button = sol.surface.create(32, 16)
  self.nope_button:fill_color({198, 34, 0})
  local button_text = sol.text_surface.create({text_key="menus.picker.nope", font=font, font_size=font_size})
  button_text:draw(self.nope_button, 4, 8)
end


-- Called when the picker menu starts
function picker:on_started()
  sol.main.game:get_hero():freeze()
  sol.main.game._picker_enabled = true

  -- Set items the player has
  player_items = {}
  for i, v in ipairs(key_items) do
    if sol.main.game:has_item(v) then
      table.insert(player_items, v)
    end
  end

  -- Graphics
  self.npc_sprite = sol.sprite.create(self._npc:get_sprite():get_animation_set())
  self.npc_sprite:set_direction(3)

  -- Initialize properties
  self.cursor = 1
  self._selection = nil
end


-- Set/get the selected item (when "action" is pressed)
function picker:set_selection(item)
  self._selection = item
end

function picker:get_selection()
  return self._selection
end


-- Called every frame
function picker:on_draw(dst_surface)

  -- Draw BG graphics
  self.menu:draw(dst_surface, 56, 16)
  self.menu_bg:draw(self.menu)

  -- Draw characters
  local x, y
  x, y = self.player_sprite:get_origin()
  self.player_sprite:draw(self.menu, 24+x, 8+y) -- draw player sprite
  x, y = self.npc_sprite:get_origin()
  self.npc_sprite:draw(self.menu, 104+x, 8+y) -- draw NPC sprite

  -- Draw question
  self.title:draw(self.menu, 49, 16)

  -- Draw items, loop through inventory
  for i, item in ipairs(player_items) do
    if sol.main.game:has_item(item) then
      self.item_sprite:set_animation(item) -- all items are in one sheet
      local x, y = get_slot_xy(i) -- item slot XY
      local ox, oy = self.item_sprite:get_origin() -- origin offset
      self.item_sprite:draw(self.menu, x+ox, y+oy) -- draw item
      if self.cursor == i then
        self.cursor_sprite:draw(self.menu, x, y) -- draw cursor
      end
    end
  end

  -- Draw cancel button
  self.nope_button:draw(self.menu, 56, 80)
  if self.cursor == #player_items+1 then
    self.cursor_sprite:draw(self.menu, 64, 80) -- cancel button cursor
  end
end


-- Called when a button is pressed
function picker:on_command_pressed(command)

  -- D-Pad controls
  if command == "up" then
    self.cursor = self.cursor - 4 -- up and down navigate between rows
  elseif command == "down" then
    self.cursor = self.cursor + 4
  elseif command == "left" then
    self.cursor = self.cursor - 1
  elseif command == "right" then
    self.cursor = self.cursor + 1
  end

  -- Cursor must be between 1 and #player_items+1 (cancel)
  self.cursor = math.max(1, self.cursor)
  self.cursor = math.min(self.cursor, #player_items+1)

  -- Handle selection
  if command == "action" then
    if self.cursor ~= #player_items+1 then
      local item = sol.main.game:get_item(player_items[self.cursor])
      self:set_selection(item)
    end
    sol.menu.stop(self)
    sol.main.game:get_hero():unfreeze()
    sol.main.game._picker_enabled = false
  end

  return true
end


-- Initialize picker when the game starts
local game_meta = sol.main.get_metatable("game")
game_meta:register_event("on_started", function(self)
  picker:initialize()
end)


return picker


scripts/npc_ask_item.lua:
Code ( lua) Select
-- ♡ Copying is an act of love. Please copy and share.

require("scripts/multi_events")
local picker = require("scripts/menus/picker")


-- Add npc:ask_item(callback)

local npc_meta =  sol.main.get_metatable("npc")

-- Start a picker menu for the NPC
function npc_meta:ask_item(callback)
  picker._npc = self
  sol.menu.start(self:get_map(), picker)
  function picker:on_finished()
    callback(self:get_selection())
    picker._npc = nil
    self.on_finished = nil -- destroy this function between calls
  end
end


-- Initialize game with picker menu

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

-- Check whether a picker is already active
function game_meta:is_picker_enabled()
  return self._picker_enabled
end

-- Initialize game with _picker_enabled attribute
game_meta:register_event("on_started", function(self)
  self._picker_enabled = false
end)


Finally, I've just added the script to features.lua:
Code ( lua) Select
require("scripts/npc_ask_item") -- NPC item picker menu