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.


Topics - alexgleason

Pages: [1] 2
1
Development / How do you organize your quest data files?
« on: December 06, 2018, 11:11:09 pm »
Here's how badly disorganized I am:



It's becoming an actual blocker for me. I'm afraid to create new maps and experiment because I don't want to worsen to this mess.

I may solve this by writing documentation on how Vegan on a Desert Island's files are organized. But I'm curious if anyone has already done this, or has any insight to offer.

Some parts I'd like to cover:

  • Dialogs - when do you create folders? Do you categorize by area, by character, by time period? How do you organize branching dialog?
  • Maps - same as above.
  • Sprites - when do you create new a sprite sheet? what is the folder structure?
  • Scripts - same as above.

2
Development / Lua promises
« on: December 03, 2018, 06:24:02 pm »
I've been working on animating an intro sequence for my game, and running into callback hell with movements. Eg, one movement must happen, then the next movement happens, then a timer happens, then another movement happens, etc.

I found a few Lua promise libraries:


The top one looks very promising. Anyone have experience with this? Also just wanted to share in case others find it interesting.

3
Your scripts / "End Credits" script (rolling credits)
« on: November 15, 2018, 09:54:14 pm »
I didn't share this before because it'd likely need to be adapted and parts of the code aren't super nice, but I figure maybe this will benefit someone.

It requires a dialog called "end_credits", and it just rolls through it until it's done.
Code: Lua
  1. -- ♡ Copying is an act of love. Please copy and share.
  2.  
  3. -- Game credits. Plays at the end of the game.
  4.  
  5. local end_credits = {}
  6.  
  7.  
  8. -- https://stackoverflow.com/a/8316375/8811886
  9. function build_array(...)
  10.   local arr = {}
  11.   for v in ... do
  12.     arr[#arr + 1] = v
  13.   end
  14.   return arr
  15. end
  16.  
  17.  
  18. -- Called when the menu is started
  19. function end_credits:on_started()
  20.   local lh = 12 -- line height in pixels
  21.   local speed = 24 -- scroll speed in px/s
  22.  
  23.   -- Credits dialog
  24.   self.dialog = sol.language.get_dialog("end_credits")
  25.  
  26.   -- Break dialog text into a table of lines
  27.   local lines = self.dialog.text
  28.   lines = lines:gsub("\r\n", "\n"):gsub("\r", "\n")
  29.   lines = build_array(lines:gmatch("([^\n]*)\n"))
  30.  
  31.  
  32.   -- Box where the credits go
  33.   self.credits_surface = sol.surface.create(
  34.     160, -- center the surface on the screen
  35.     (#lines * lh) -- surface is large enough to hold all lines
  36.       + 144 -- surface scrolls in from the bottom, so has a padding top equal to the screen size
  37.       + 16 -- room for 8px top and bottom padding
  38.   )
  39.  
  40.   -- Loop through all dialog lines and draw them
  41.   for i, line in ipairs(lines) do
  42.     local line_surface =  sol.text_surface.create({font="Comicoro", font_size=16, text=line})
  43.     -- Draw the given line
  44.     line_surface:draw(
  45.       self.credits_surface,
  46.       8, -- left padding
  47.       i * lh -- bump it down by line number and line height
  48.         + 8 -- top padding for whole box
  49.     )
  50.   end
  51.  
  52.   -- Animate the text box upwards
  53.   local m = sol.movement.create("straight")
  54.   m:set_angle(math.pi / 2)
  55.   m:set_speed(speed)
  56.   m:start(self.credits_surface)
  57.  
  58.   function m:on_position_changed()
  59.     local credits_surface = end_credits.credits_surface
  60.     local x, y = credits_surface:get_xy()
  61.     local w, h = credits_surface:get_size()
  62.  
  63.     log(string.format("Credits text box: (%d, %d)  ;;  y+h = %d", x, y, y+h))
  64.  
  65.     if y + h < 0 then
  66.       -- Credits are out of view, end the menu
  67.       log("Credits animation finished.")
  68.       self:stop()
  69.       sol.menu.stop(end_credits)
  70.     end
  71.   end
  72.  
  73. end
  74.  
  75.  
  76. -- Called each frame
  77. function end_credits:on_draw(dst_surface)
  78.   dst_surface:clear()
  79.   self.credits_surface:draw(dst_surface, 48, 144)
  80. end
  81.  
  82.  
  83. return end_credits

It's a menu, so put it in a .lua file (eg scripts/menus/end_credits.lua) and use something like this:

Code: Lua
  1. local end_credits = require("scripts/menus/end_credits")
  2.  
  3. sol.menu.start(game, end_credits)
  4.  

4
General discussion / How loud should sounds be?
« on: November 04, 2018, 05:50:11 pm »
One thing I don't quite understand, is how loud to make each of my sounds/music so it meets the user's expectation based on the volume they've set on their computer.

For example, sometimes I'll mute my game while working and be listening to other music. Then when I unmute my game, I have to turn the volume down. This is because the music in my game is louder than the other music I'm listening to. The other music is usually an album played with VLC, or a YouTube video.

I'm aware of "the loudness wars." Where did we ever end up with that? Is there any sort of standard for loudness? Ideally, I want users to not have to turn the volume up or down when switching from other software to my game.

5
Development / Has anyone tried compiling Solarus for the web?
« on: November 02, 2018, 09:16:31 pm »
There are C++ to ES6 transpilers that seem to work reasonably well, like Emscripten: http://kripken.github.io/emscripten-site/

For example, you can see the open source game FreeDink running in the browser: https://play.freedink.org/ This game is written in C++ and ported to the web with Ecmascripten.

I think Solarus would have to be modified for this to work since it reads the filesystem to load the quest and write save data, but it might be possible.

6
General discussion / Puzzle design
« on: November 01, 2018, 03:48:52 pm »
I've seen some great resources here about creating beautiful maps, but what about designing puzzles? How do you even approach designing something that you yourself should not be able to easily solve?

I've found one great video series, starting here: https://www.youtube.com/watch?v=4YpQVdZOjgI

Curious if others have any more input or resources.

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

8
Development / Solarus could use a UI framework
« on: 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
  1. -- Example menu with reusable components
  2.  
  3. local title_bar = require("scripts/ui/components/title_bar")
  4. local item_picker = require("scripts/ui/components/item_picker")
  5.  
  6. local menu = {
  7.   components = {
  8.     title_bar:new({bg_color={0,0,0}, text="Inventory"),
  9.     item_picker:new({row=3, col=5, gutter=8})
  10.   }
  11. }
  12.  
  13. function menu:on_draw()
  14.   for _, component in ipairs(self.components) do
  15.     component:draw(self)
  16.   end
  17. end
  18.  
  19. 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.

9
Your scripts / NPC item picker script
« on: 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
  1. local bunny = map:get_entity("bunny1")
  2.  
  3. function bunny:on_interaction()
  4.   game:start_dialog("bunny.want_carrot", function()
  5.     bunny:ask_item(function(item)
  6.       if item and item:get_name() == "carrot"
  7.         item:set_variant(0)
  8.         game:start_dialog("bunny.thanks_for_carrot")
  9.       elseif item then
  10.         -- a non-carrot item is given
  11.         game:start_dialog("bunny.no_thanks")
  12.       else
  13.         -- The action was cancelled (item == nil). Do nothing.
  14.       end
  15.     end)
  16.   end)
  17. end

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

scripts/menus/picker.lua:
Code: Lua
  1. -- ♡ Copying is an act of love. Please copy and share.
  2.  
  3. -- This menu ultimately exists to let you call npc:ask_item()
  4. -- It's designed to be called from npc:ask_item() and really cannot stand alone
  5.  
  6. require("scripts/multi_events")
  7.  
  8. -- Items that could appear in this menu
  9. local key_items = {
  10.   'trade',
  11.   'whiskey',
  12. }
  13. local player_items = {} -- Items the player actually has
  14.  
  15.  
  16. -- Get the XY for an item sprite based on its position
  17. local function get_slot_xy(i)
  18.   local mx, my = 24, 32 -- offset from menu origin
  19.  
  20.   local r = math.ceil(i/4) -- row of this slot
  21.   local ix = ((i-1)%4)*24
  22.   local iy = (r-1)*24
  23.  
  24.   local x = mx + ix
  25.   local y = my + iy
  26.  
  27.   return x, y
  28. end
  29.  
  30.  
  31. local picker = {} -- item picker menu
  32.  
  33.  
  34. function picker:initialize()
  35.   local font, font_size = "Comicoro", 16
  36.  
  37.   -- Graphics
  38.   self.menu = sol.surface.create(144, 104)
  39.   self.menu_bg = sol.sprite.create("menus/menu_bg")
  40.   self.item_sprite = sol.sprite.create("entities/items")
  41.   self.cursor_sprite = sol.sprite.create("menus/cursor")
  42.  
  43.   -- Player sprite
  44.   self.player_sprite = sol.sprite.create("main_heroes/rachel")
  45.   self.player_sprite:set_animation("walking")
  46.   self.player_sprite:set_direction(3)
  47.  
  48.   -- NPC sprite
  49.   self.npc_sprite = nil
  50.  
  51.   -- Question text
  52.   self.title = sol.text_surface.create({text_key="menus.picker.question", font=font, font_size=font_size})
  53.   self.title:set_color({0, 0, 0})
  54.  
  55.   -- Nope button
  56.   self.nope_button = sol.surface.create(32, 16)
  57.   self.nope_button:fill_color({198, 34, 0})
  58.   local button_text = sol.text_surface.create({text_key="menus.picker.nope", font=font, font_size=font_size})
  59.   button_text:draw(self.nope_button, 4, 8)
  60. end
  61.  
  62.  
  63. -- Called when the picker menu starts
  64. function picker:on_started()
  65.   sol.main.game:get_hero():freeze()
  66.   sol.main.game._picker_enabled = true
  67.  
  68.   -- Set items the player has
  69.   player_items = {}
  70.   for i, v in ipairs(key_items) do
  71.     if sol.main.game:has_item(v) then
  72.       table.insert(player_items, v)
  73.     end
  74.   end
  75.  
  76.   -- Graphics
  77.   self.npc_sprite = sol.sprite.create(self._npc:get_sprite():get_animation_set())
  78.   self.npc_sprite:set_direction(3)
  79.  
  80.   -- Initialize properties
  81.   self.cursor = 1
  82.   self._selection = nil
  83. end
  84.  
  85.  
  86. -- Set/get the selected item (when "action" is pressed)
  87. function picker:set_selection(item)
  88.   self._selection = item
  89. end
  90.  
  91. function picker:get_selection()
  92.   return self._selection
  93. end
  94.  
  95.  
  96. -- Called every frame
  97. function picker:on_draw(dst_surface)
  98.  
  99.   -- Draw BG graphics
  100.   self.menu:draw(dst_surface, 56, 16)
  101.   self.menu_bg:draw(self.menu)
  102.  
  103.   -- Draw characters
  104.   local x, y
  105.   x, y = self.player_sprite:get_origin()
  106.   self.player_sprite:draw(self.menu, 24+x, 8+y) -- draw player sprite
  107.   x, y = self.npc_sprite:get_origin()
  108.   self.npc_sprite:draw(self.menu, 104+x, 8+y) -- draw NPC sprite
  109.  
  110.   -- Draw question
  111.   self.title:draw(self.menu, 49, 16)
  112.  
  113.   -- Draw items, loop through inventory
  114.   for i, item in ipairs(player_items) do
  115.     if sol.main.game:has_item(item) then
  116.       self.item_sprite:set_animation(item) -- all items are in one sheet
  117.       local x, y = get_slot_xy(i) -- item slot XY
  118.       local ox, oy = self.item_sprite:get_origin() -- origin offset
  119.       self.item_sprite:draw(self.menu, x+ox, y+oy) -- draw item
  120.       if self.cursor == i then
  121.         self.cursor_sprite:draw(self.menu, x, y) -- draw cursor
  122.       end
  123.     end
  124.   end
  125.  
  126.   -- Draw cancel button
  127.   self.nope_button:draw(self.menu, 56, 80)
  128.   if self.cursor == #player_items+1 then
  129.     self.cursor_sprite:draw(self.menu, 64, 80) -- cancel button cursor
  130.   end
  131. end
  132.  
  133.  
  134. -- Called when a button is pressed
  135. function picker:on_command_pressed(command)
  136.  
  137.   -- D-Pad controls
  138.   if command == "up" then
  139.     self.cursor = self.cursor - 4 -- up and down navigate between rows
  140.   elseif command == "down" then
  141.     self.cursor = self.cursor + 4
  142.   elseif command == "left" then
  143.     self.cursor = self.cursor - 1
  144.   elseif command == "right" then
  145.     self.cursor = self.cursor + 1
  146.   end
  147.  
  148.   -- Cursor must be between 1 and #player_items+1 (cancel)
  149.   self.cursor = math.max(1, self.cursor)
  150.   self.cursor = math.min(self.cursor, #player_items+1)
  151.  
  152.   -- Handle selection
  153.   if command == "action" then
  154.     if self.cursor ~= #player_items+1 then
  155.       local item = sol.main.game:get_item(player_items[self.cursor])
  156.       self:set_selection(item)
  157.     end
  158.     sol.menu.stop(self)
  159.     sol.main.game:get_hero():unfreeze()
  160.     sol.main.game._picker_enabled = false
  161.   end
  162.  
  163.   return true
  164. end
  165.  
  166.  
  167. -- Initialize picker when the game starts
  168. local game_meta = sol.main.get_metatable("game")
  169. game_meta:register_event("on_started", function(self)
  170.   picker:initialize()
  171. end)
  172.  
  173.  
  174. return picker
  175.  

scripts/npc_ask_item.lua:
Code: Lua
  1. -- ♡ Copying is an act of love. Please copy and share.
  2.  
  3. require("scripts/multi_events")
  4. local picker = require("scripts/menus/picker")
  5.  
  6.  
  7. -- Add npc:ask_item(callback)
  8.  
  9. local npc_meta =  sol.main.get_metatable("npc")
  10.  
  11. -- Start a picker menu for the NPC
  12. function npc_meta:ask_item(callback)
  13.   picker._npc = self
  14.   sol.menu.start(self:get_map(), picker)
  15.   function picker:on_finished()
  16.     callback(self:get_selection())
  17.     picker._npc = nil
  18.     self.on_finished = nil -- destroy this function between calls
  19.   end
  20. end
  21.  
  22.  
  23. -- Initialize game with picker menu
  24.  
  25. local game_meta = sol.main.get_metatable("game")
  26.  
  27. -- Check whether a picker is already active
  28. function game_meta:is_picker_enabled()
  29.   return self._picker_enabled
  30. end
  31.  
  32. -- Initialize game with _picker_enabled attribute
  33. game_meta:register_event("on_started", function(self)
  34.   self._picker_enabled = false
  35. end)
  36.  

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

10
Development / How do PNG fonts work?
« on: October 24, 2018, 10:03:38 pm »
I noticed the docs say that .png fonts are supported. I'm hoping I can get clarity on what this means exactly.

  • Is the PNG font a recognized standard? Eg do multiple programs use the same format
  • Is it just a row of characters in a specific order?
  • Are only monospace fonts supported?
  • Does it have to be 8px?
  • Are there any examples?

Thanks!

11
Development / Stopping a sound effect
« on: October 21, 2018, 07:17:12 pm »
Hi all, wondering if there's any way to do this or any plans to add it? I have an item that should make a continuous sound until stopped.

12
General discussion / The stupidest commit message I have ever written
« on: October 20, 2018, 10:33:26 pm »
So, I've been learning Lua. For a while I was using pairs() to loop over an array, but later learned that ipairs() is actually the behavior I want. So I went back and edited my game's scripts, and wound up with this totally incoherent commit message: https://gitlab.com/voadi/voadi/commit/22885ca8e0ba0e1ab68a07422163f56b0b8e1048

I had written "Always use ipairs!! Now that I know the difference", but Bash interpreted my double exclamation points as a bang command. Geez... maybe I'll just write my commit messages into nano from now on instead of using the git commit -m <message> syntax.

13
Development / Multi-events as the default?
« on: October 17, 2018, 08:41:47 pm »
I was just having this moment of great realization for why Lua is a powerful programming language, then I hit a wall again.

To give an example, I have a script which does get_metatable("map"):register_event("on_started", ...)

The problem is, Solarus Quest Editor creates every map script with an empty on_started defined. So I'll need to edit all my maps and change it to map:register_event("on_started", ...) in order to make both functions run.

Basically, I'm trying to have events in "layers". Every map will always call the same "on_started" function in the metatable, but it will also call the "on_started" function of the specific map itself.

So I'm wondering if there should be a push towards using multi-event functions in Quest Editor instead. Eg, creating a new map might look like this:

Code: Lua
  1. -- Lua script of map test_map.
  2. -- This script is executed every time the hero enters this map.
  3.  
  4. -- Feel free to modify the code below.
  5. -- You can add more events and remove the ones you don't need.
  6.  
  7. -- See the Solarus Lua API documentation:
  8. -- http://www.solarus-games.org/doc/latest
  9. require("scripts/multi_events.lua")
  10.  
  11. local map = ...
  12. local game = map:get_game()
  13.  
  14. -- Event called at initialization time, as soon as this map becomes is loaded.
  15. function map:register_event("on_started", function()
  16.  
  17.   -- You can initialize the movement and sprites of various
  18.   -- map entities here.
  19. end)
  20.  
  21. -- Event called after the opening transition effect of the map,
  22. -- that is, when the player takes control of the hero.
  23. function map:register_event("on_opening_transition_finished", function()
  24.  
  25. end)

This would offer a lot more flexibility, because you could disable the default behavior by switching to an empty function. This seems like a more common use-case. I can't think of a use-case where it's better off the way it currently is.

The only real issue I see is that Quest Editor would have to assume you have a multi_events script. So then it makes me think... maybe package that into the engine itself? I mean, it's an essential tool for quest development.

14
Your scripts / Script to save the state of hundreds of objects
« on: October 17, 2018, 08:49:55 am »
Video of this in action: https://vimeo.com/295527145

This is basically solving the problem of Banjo Kazooie's music notes (or Donkey Kong 64's bananas) - how do you save each individual banana/music note separately so that once you picked it up once, it is gone forever?

This script is unfinished, and it's only in a single map for now, but I wanted to share my progress. Later I'll clean it up and abstract it into a proper separate lua module.

My game has hundreds of pieces of trash on the map. When you load the map, the "beach_trash" savegame value looks like this:

Code: [Select]
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
After vacuuming 2 pieces of trash:

Code: [Select]
1111111111111111111111100111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
After vacuuming 6 pieces:

Code: [Select]
1111111111111110011111100111111111111111111111111111111111111111110011111111111111101111111111111111111111111111111111111111111111111111111111111111111111111111111110111111111111111111111
Essentially, when the map loads, it finds all the trash entities, loops through them, and gives them an ID based on the order they're returned from map:get_entities(). So the first piece of trash returned from get_entities() would have an ID of 1 and so on. A "state" table then keeps track of every piece of trash. The trash's ID is used as a key for the state table, and the value is 1 if existing, and 0 if removed. The state table is converted into the above binary string when saved. Here's the full code:

EDIT: Better version in the post below. Also, someone pointed out that this is basically a bitmap. Pretty much - it's like removing all the gaps between the entities and then creating a bitmap over that.

Code: Lua
  1. -- ♡ Copying is an act of love. Please copy and share.
  2.  
  3. local map = ...
  4. local game = map:get_game()
  5. local trash_state = {}
  6.  
  7. -- Takes in an entity state table and converts it to a string for save
  8. local function state_tostring(state)
  9.   local chars = {}
  10.   for i, v in ipairs(state) do
  11.     table.insert(chars, tostring(v))
  12.   end
  13.   return table.concat(chars, "")
  14. end
  15.  
  16. -- Takes a state string and returns a table
  17. local function state_fromstring(str)
  18.   if str == nil then return {} end
  19.   local state = {}
  20.   for i=1, #str do
  21.     local n = tonumber(str:sub(i, i))
  22.     state[i] = n
  23.   end
  24.   return state
  25. end
  26.  
  27. -- Get all trash entities for a map
  28. local function get_trash_entities()
  29.   local trash_entities = {}
  30.   local custom_entities = map:get_entities_by_type("custom_entity")
  31.   for entity in custom_entities do
  32.     if entity:get_model() == "trash" then
  33.       table.insert(trash_entities, entity)
  34.     end
  35.   end
  36.   return trash_entities
  37. end
  38.  
  39. -- Assign ID's to all trash entities.
  40. -- Add on_removed event.
  41. local function process_trash()
  42.   trash_state = state_fromstring(game:get_value("beach_trash")) -- load from save
  43.   local entities = get_trash_entities()
  44.   for i, entity in ipairs(entities) do
  45.     entity.id = i -- Assign id to each piece of trash
  46.     if not trash_state[entity.id] then
  47.       trash_state[entity.id] = 1
  48.     end
  49.     function entity:on_removed() -- Update the trash state table
  50.       trash_state[self.id] = 0
  51.       local state_string = state_tostring(trash_state)
  52.       game:set_value("beach_trash", state_string) -- Save the trash state
  53.     end
  54.     if trash_state[entity.id] == 0 then
  55.       entity:remove()
  56.     end
  57.   end
  58. end
  59.  
  60.  
  61. function map:on_started()
  62.   process_trash()
  63. end
  64.  
  65. -- Remove the on_removed event, otherwise trash_state will be set to all zeros when the map is left
  66. function map:on_finished()
  67.   for i, entity in ipairs(get_trash_entities()) do
  68.     entity.on_removed = nil
  69.   end
  70. end
  71.  

15
I noticed something weird today: my sword can be activated during a dialog box. It goes to the first frame of animation and then is frozen until the dialog box finishes.



When I first opened the dialog box I checked "hero:get_state()" and it says "free". When I press the item button assigned to my sword, it says "sword activated". Keep in mind my sword is an item assigned to item_1 (using hero:start_attack() internally), so it's not surprising that I have strange behavior. But I did think it was strange the dialog box doesn't freeze my hero. I can fix this issue by calling hero:freeze() when the dialog box starts, and hero:unfreeze() when it finishes.

Okay, I understand that some menus (like a HUD) shouldn't freeze the player. But it raises a question: how exactly are button events handled? My dialog box has this:

Code: Lua
  1. function dialog_box:on_command_pressed(command)
  2.   -- "action", "attack", "up", and "down" are handled here. I'm using "item_1" and not pressing any other buttons.
  3.   ...
  4.  
  5.   -- Don't propagate the event to anything below the dialog box.
  6.   return true
  7. end

It returns true, so it shouldn't propagate. The context of dialog box is "game", and "game:on_command_pressed" is where the hero swings her sword.

In main.lua,

Code: Lua
  1.   -- I've simplified my code, but it shouldn't matter. This function shouldn't be called in the first place.
  2.  
  3. function game:on_command_pressed(command)
  4.  
  5.   print("you've reached me") -- This prints in the console when I perform the action in the screenshot, confirming that this function is called
  6.  
  7.   -- Makes the sword swing
  8.   local item = get_item_for_command(command)
  9.   item:on_command_pressed(command) -- this code calls hero:start_attack() internally
  10.  
  11.   return true
  12. end

Any ideas why the event is propagated to game:on_command_press?? Thank you!

Pages: [1] 2