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

#1
Hello, I read on the forums that you could use the new module coroutine_helper.lua to build cutscenes, to avoid endlessly nested callbacks.

However, I couldn't get it to work. I could only get one-time events to work, like playing a sound. I couldn't get the "only if game available" features to work, probably because I didn't make the game available in the right way.

Is there perhaps a working coroutine_helper game or example available, that uses the more complicated "only if game available" features, like dialogue, or characters moving around?

Many thanks for an answer!  :)
#2
Development / Re: Make 2nd hero un-attackable by 1st hero
September 24, 2022, 09:18:03 PM
Thank you very much. I've checked out your additions, and the on_attacking_hero function works very well. If you define it on both heroes and leave it empty, then the heroes won't hurt each other any more, like for a coop game.

I'll play around some more with the multiple heroes, and make a new post when something else comes up.
#3
Development / Re: Make 2nd hero un-attackable by 1st hero
September 19, 2022, 08:57:48 PM
I saw that you can call state:set_can_be_hurt on a state object. And in the parameter function you could probably check if the attacker is another hero, and then do nothing. But I'd have to set that on all the default states ("free", "running", "sword swinging" etc.), and I don't know where these states are created or stored. Does anyone know how I could get a handle on all the default states?
#4
Development / Make 2nd hero un-attackable by 1st hero
September 15, 2022, 09:37:01 AM
Hi, I tried out the new multiple heroes feature, but the default seems to be a player vs. player setup. The heroes hurt each other with their attacks, as in the "friendly fire" test map. For a coop quest, it would be nicer if you could also configure them to be immune to each other's attacks.

There are functions on the "enemy" type to configure attack reaction (set_attack_consequence), but these functions don't seem to exist on the "hero" type. In the CPP files, there are even more functions (is_sword_ignored), but it seems they have no Lua bindings.

Have I maybe overlooked an easy way to set this up? Or could it be implemented easily?
#5
Ah, I got it working by registering "on_map_changed" for the game metatable. If added to main.lua on the current develop branch, the following script will on every entered map spawn a 2nd hero in blue next to the 1st one, controlled with the WASD keys. They both hurt and are hurt by enemies, and can coop-kill them together. Neat. There's still plenty of stuff to add, but I'll ask that in other posts.

A big thanks to all devs for the 2nd hero addition, and everything else!  :)


local game_meta = sol.main.get_metatable("game")
game_meta:register_event("on_map_changed", function()
  local map = sol.main.get_game():get_map()
  local hero1 = map:get_hero()
  -- Adjust controls for hero 1.
  hero1:get_controls():set_keyboard_binding("pause", "p")
  hero1:get_controls():set_keyboard_binding("attack", "right control")
  local x, y, layer = hero1:get_position()
  hero1:set_position(x + 8, y, layer)
  -- Create hero 2 next to hero 1, with WASD controls.
  local hero2 = map:create_hero({x = x - 8, y = y, layer = layer})
  hero2:set_name("hero2")
  hero2:get_controls():set_keyboard_binding("pause", "")
  hero2:get_controls():set_keyboard_binding("up", "w")
  hero2:get_controls():set_keyboard_binding("left", "a")
  hero2:get_controls():set_keyboard_binding("down", "s")
  hero2:get_controls():set_keyboard_binding("right", "d")
  hero2:get_controls():set_keyboard_binding("attack", "left alt")
  hero2:set_tunic_sprite_id("hero/tunic2")
  -- Give them swords and extra life for testing.
  hero1:set_ability("sword", 1)
  hero2:set_ability("sword", 1)
  hero1:set_max_life(16)
  hero1:set_life(16)
  hero2:set_max_life(16)
  hero2:set_life(16)
end)
#6
Thanks for the tips. But so far I'm still having trouble to even get the single-map usecase to work.

How or where would I override or append to game:on_started? Is there an example? I also found I need special behavior when starting each map or multimap area, for spawning the 2nd hero with the map functions. Is there maybe a way to override map:on_started for all maps?

When I tried to register "on_started" on sol.main.get_metatable("map"), it was never called. When I tried register "on_started" on sol.main.get_metatable("game"), the returned hero from create_hero was nil. That using the map metatable with "on_started" doesn't work has apparently been reported by other users. The reason may be that the map already has the function, and the docs say that the instance field always has priority. However, I haven't found any other hook in the map startup that I could use.


local game_meta = sol.main.get_metatable("game")
game_meta:register_event("on_started", function(game)
  local x, y, z = game:get_hero():get_position()
  local hero2 = game:get_map():create_hero {x = x, y = y, layer = z}
  print("This is called without error, but hero2 is nil here, and doesn't show: ", hero2)
end)

local map_meta = sol.main.get_metatable("map")
map_meta:register_event("on_started", function()
  print("This is never called.")
end)
#7
Ah, thanks. hero2:get_controls():set_keyboard_binding("pause", "") (to empty string) did the trick to remove the "d" binding also for the 2nd hero. Interestingly, if you instead reassign "pause" to "p" for both heroes, then it breaks the pause, i.e. the game doesn't pause anymore. I'll have a look at the rest of the new controls API and try to remove all of the now obsolete entity code...

I'd be grateful if you could give me a suggestion as to where custom hero code belongs, if an entire game was to be written for 2 heroes coop. In the testing_quest, it's in the map code. Where should it go if it's used on all maps?
#8
I had implemented the 2nd hero as a custom entity, and now changed this to an actual hero entity, created with map:create_hero. However, this seems to break the key binding of "pause" to "p", with game:set_command_keyboard_binding("pause", "p"), which did not break with the custom entity. This means that the "d" key is now mapped to pause, as well as to the hero movement.

I'll post my script here in case anyone knows how to debug this. The code probably has plenty of other problems, since I've never programmed Lua before. The WASD control is now implemented like the mouse_control script. But perhaps it would be better to have it run as an entity script, as with enemies or NPCs? Only there's no GUI button to link it yet.

To make this script work, you have to include it in "features.lua", and then place a custom entity on the map and name it "wasd_hero". To see it work as a custom entity without the bug, you have to comment out lines 43-48.


require("scripts/multi_events")

-- This replaces an entity named "wasd_hero" on the map
-- with a 2nd hero using a goblin sprite, who can be
-- steered with the WASD keys and throw axes with left Ctrl.
--
-- FIXME: When pressing the WASD keys to steer the 2nd hero,
-- the "d" key is again mapped to "pause", which did not happen
-- when the hero is a custom entity.
local function initialize_wasd_control_features(game)

  game:set_command_keyboard_binding("pause", "p")

  local hero2
  local wasd_control = {}
  local movement = sol.movement.create("straight")
  local current_direction = 3
  local current_angle = 3 * math.pi / 2
  local is_key_pushed = false
  print("WASD control startup")

  -- Movement of hero 2.
  local directions = {
      right = false,
      up = false,
      left = false,
      down = false,
      attack = false
  }
 
  -- Replace entity "wasd_hero" with a 2nd hero
  -- using the goblin green sprite.
  -- FIXME: should be called once on map creation
  function wasd_control:set_hero2()
    if hero2 ~= nil then
      return
    end
    map = game:get_map()
    if map:has_entity("wasd_hero") then
      hero2 = map:get_entity("wasd_hero")
      -- FIXME: Comment out these 6 lines to play as custom entity. This fixes
      -- the "pause" bug, but it's not a hero anymore.
      local x, y, z = hero2:get_position()
      hero2:remove()
      hero2 = map:create_hero {x = x, y = y, layer = z}
      hero2:set_name("wasd_hero")
      hero2:remove_sprite(hero2:get_sprite("tunic"))
      hero2:create_sprite("enemies/goblin_green", "tunic")
    end
  end

  -- detect pressed WASD keys
  function wasd_control:on_key_pressed(key, modifiers)
    if key == "w" then
      directions.up = true
    end
    if key == "a" then
      directions.left = true
    end
    if key == "s" then
      directions.down = true
    end
    if key == "d" then
      directions.right = true
    end
    if key == "left control" then
      directions.attack = true
    end
    self:update_movement()
  end

  -- detect released WASD keys
  function wasd_control:on_key_released(key)
    if key == "w" then
      directions.up = false
    end
    if key == "a" then
      directions.left = false
    end
    if key == "s" then
      directions.down = false
    end
    if key == "d" then
      directions.right = false
    end
    if key == "left control" then
      directions.attack = false
    end
    self:update_movement()
  end

  -- calc direction for sprite:
  -- right = 0, up = 1, left = 2, down = 3
  function wasd_control:calc_direction()
    if directions.right then
      current_direction = 0
    elseif directions.left then
      current_direction = 2
    elseif directions.up then
      current_direction = 1
    elseif directions.down then
      current_direction = 3
    else
      -- direction stays as it is
    end
    return current_direction
  end

  -- cals angle
  function wasd_control:calc_angle()
    if directions.right and directions.up then
      current_angle = 1 * math.pi / 4
    elseif directions.right and directions.down then
      current_angle = 7 * math.pi / 4
    elseif directions.left and directions.up then
      current_angle = 3 * math.pi / 4
    elseif directions.left and directions.down then
      current_angle = 5 * math.pi / 4
    elseif directions.right then
      current_angle = 0 * math.pi / 2
    elseif directions.up then
      current_angle = 1 * math.pi / 2
    elseif directions.left then
      current_angle = 2 * math.pi / 2
    elseif directions.down then
      current_angle = 3 * math.pi / 2
    else
      -- angle stays as it is
    end
    return current_angle
  end

  -- loop over hero2 sprite and set animation
  function wasd_control:set_hero2_anim(anim_name)
    for _, s in hero2:get_sprites() do
      if s:has_animation(anim_name) then s:set_animation(anim_name) end
    end
  end

  -- update movement for the hero, and spawn axes when attacking
  function wasd_control:update_movement()
    local map = game:get_map()
    self:set_hero2()
    if (map == nil or movement == nil or hero2 == nil) then
      return
    end
   
    hero2:set_direction(self:calc_direction())
    if (hero2:get_sprite("tunic") ~= nil) then
      hero2:get_sprite("tunic"):set_direction(self:calc_direction())
    end
   
    -- when attacking, throw goblin axes
    if directions.attack then
      local proj_sprite_id = "enemies/goblin_axe"
      local x, y, layer = hero2:get_position()
      local prop = {x=x, y=y, layer=layer, direction=self:calc_direction(), width=16, height=16}
      local projectile = map:create_custom_entity(prop)
      local proj_sprite = projectile:create_sprite(proj_sprite_id)
      proj_sprite:set_animation("thrown")
      projectile:stop_movement()
      local m = sol.movement.create("straight")
      m:set_angle(self:calc_angle())
      m:set_speed(100)
      m:set_max_distance(300)
      m:start(projectile)
      projectile:set_can_traverse("enemy", false)
      function projectile:on_obstacle_reached() projectile:remove() end
      function projectile:on_movement_finished() projectile:remove() end
      -- FIXME: collision not always detected, perhaps due to on_obstacle_reached
      projectile:add_collision_test("overlapping", function(projectile, other)
        if other:get_type() == "enemy" then
          other:hurt(1)
          projectile:remove()
        end
      end)
    end

    if not directions.right and not directions.left and not directions.up and not directions.down then
      movement:stop()
      hero2:stop_movement()
      self:set_hero2_anim("stopped")
      return
    end
   
    self:set_hero2_anim("walking")
    movement:set_speed(90)
    movement:set_angle(self:calc_angle())
    movement:start(hero2)   
  end
 
  sol.menu.start(game, wasd_control)
end

local game_meta = sol.main.get_metatable("game")
game_meta:register_event("on_started", initialize_wasd_control_features)
return true
#9
Oooh, I found it: The maps with multiple heroes are in tests/testing_quest/data/maps/multiplayer. Even with splitscreen. Truly impressive!  :D
#10
Thanks for the link to the video, I got the projectile attack working with the sample code. (I had failed before with passing a reference to an external function as a callback, but only the inline function from the video worked. I probably need to read up on general Lua syntax too.)

I am actually working with a Quest Editor compiled from the current "dev" branch. Does that one already contain the support for multiple heroes somewhere? If so, what files should I look at to understand the syntax? Or is it in some special feature branch?

Sounds exciting in any case. I'm not even aware of any commercial Zelda-type ARPG that has this feature.
#11
Development / Have a 2nd hero move, attack and be attacked
September 08, 2022, 10:04:02 AM
Hi, I'm trying to implement a 2nd hero for co-op gaming. It's just for laughs, and I know the engine isn't really made for that. So far, I only managed to have a custom entity walk around with the WASD keys, and spawn projectiles with the Ctrl key. However, I can only seem to make the projectiles traverse enemies, or disappear when they hit one.

How can I make the spawned projectiles hurt the enemies, as projectiles from the hero would?

Is there a way to have the enemies and enemy projectiles hurt the custom entity, like they would with the hero?

Since the 2nd hero should behave pretty much like the 1st hero, only with other keys, is there any Lua script for the hero I could copy from? Or is it all in the C++ files?

Many thanks for some hints!  :)