Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b8e88df
chore: add message.debug
caspersg Jan 9, 2025
4efa8ad
feat: split out choose_new_target and ensure_has_target
caspersg Jan 9, 2025
d961f96
wip: try char animation, showing attacker at pos temporarily, but it …
caspersg Jan 9, 2025
07bc153
chore: add debug and remove @ for player
caspersg Jan 10, 2025
1867a1c
feat: new animate step with state.animating lock
caspersg Jan 10, 2025
acc55f6
feat: move cursor bounce off logic, so it animates now
caspersg Jan 11, 2025
12e5b57
feat: don't animate terrain during attacks
caspersg Jan 11, 2025
d52cdf2
fix: bug where player over item when hit, hides the item
caspersg Jan 11, 2025
8df0950
refactor: show hit highlight during attack animation
caspersg Jan 11, 2025
f57454f
feat: only show attacker highlight, add custom highlight groups, alwa…
caspersg Jan 11, 2025
a29382e
feat: hide animations if the attacker is not visible
caspersg Jan 11, 2025
34e7a84
refactor: remove unused highlights
caspersg Jan 11, 2025
93a6fb1
refactor: set original char after animation
caspersg Jan 11, 2025
8ac7c4c
fix: partial fix for waiting
caspersg Jan 11, 2025
7e84bad
feat: enemies move to cell when they pick up an item
caspersg Jan 11, 2025
73a74d5
feat: entity moves as part of dodge
caspersg Jan 11, 2025
464215f
docs: update todo
caspersg Jan 11, 2025
3c07d83
chore: back to normal game type
caspersg Jan 11, 2025
32b1536
feat: unique highlights per entity
caspersg Jan 11, 2025
fbc7272
feat: add option to disable animations
caspersg Jan 11, 2025
2a0666f
test: fix failing tests
caspersg Jan 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Actions
- [x] copy to new buffer on game start
- [x] highlight hurt
- [x] miss hits and dodges
- [x] move to floor in order to dodge
- [x] delete to pickup
- [x] insert floor on delete
- [x] enemies drop loot
Expand All @@ -56,6 +57,8 @@ Actions
- [ ] figure out how to disable on change event listener
- [x] stats in status line
- [x] messages in split
- [x] animate attacks
- [x] highlight on attack animation
- [ ] async enemy movements
- [x] show visible map and hide unexplored sections
- [x] distance
Expand Down
5 changes: 4 additions & 1 deletion lua/neohack/actions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@ M.actions = {
local count = utils.string_to_int(request.object)
if count then
message.notify(utils.capitalize_first_letter(request.action) .. "ing " .. count .. " turns")
for _ = 1, count do
for i = 1, count do
message.notify(utils.capitalize_first_letter(request.action) .. "ing turn", i)
--TODO: keeps being off by 1
state.animating = false -- ignore animations, otherwise turns are skipped
M.tick()
end
end
Expand Down
99 changes: 99 additions & 0 deletions lua/neohack/animate.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
local view_buffer = require("neohack.view_buffer")
local Def = require("neohack.def")
local state = require("neohack.state")

---@class Animation
---@field attacker Entity
---@field target Entity

local M = {
---@type Animation[]
animations = {},
}

---comment
---@param attacker Entity
---@param target Entity
M.attack = function(attacker, target)
-- view_buffer.show_char_at_pos(target.movement.row, target.movement.col, attacker.movement.char)
-- message.notify(attacker.name, "hitting", target.name)
--
-- utils.sleep(0.5)
-- vim.defer_fn(function()
-- view_buffer.show_char_at_pos(target.movement.row, target.movement.col, target.movement.char)
-- end, 800)
-- error("hittttt")

if state.animations_enabled then
table.insert(M.animations, {
attacker = attacker,
target = target,
})
end
end

M.show_time = 200

---comment
---@param animation Animation
---@param get_entity_at_pos function
---@param animation_completed function
M.attack_animation = function(animation, get_entity_at_pos, animation_completed)
local attacker = animation.attacker
local target = animation.target
local target_row = target.movement.row
local target_col = target.movement.col
local attacker_row = attacker.movement.row
local attacker_col = attacker.movement.col

if attacker.type ~= Def.DefType.player then
view_buffer.show_char_at_pos(target_row, target_col, attacker.movement.char)
view_buffer.show_char_at_pos(attacker_row, attacker_col, " ")
end
if attacker.combat.hit_highlight then
view_buffer.highlight_hit(target_row, target_col, attacker.combat.hit_highlight, M.show_time)
end
attacker.combat.hit_highlight = nil

-- message.debug("anmiate", ani.attacker.name, "hitting", ani.target.name)
vim.defer_fn(function()
-- target could be player, so get the original entity
local original_target = get_entity_at_pos(target_row, target_col, true)
local original_attacker = get_entity_at_pos(attacker_row, attacker_col, true)

view_buffer.show_char_at_pos(target_row, target_col, original_target.movement.char)
view_buffer.show_char_at_pos(attacker_row, attacker_col, original_attacker.movement.char)
target.combat.hit_highlight = nil
animation_completed()
end, M.show_time)
end

---comment
---@param get_entity_at_pos function
---@param callback function
M.show_animations = function(get_entity_at_pos, callback)
local total_animations = #M.animations
local completed_animations = 0
local function animation_completed()
completed_animations = completed_animations + 1
if completed_animations >= total_animations then
-- message.debug("animations completed")
callback()
M.animations = {}
end
end

for _, ani in ipairs(M.animations) do
if ani.attacker.movement.visible then
M.attack_animation(ani, get_entity_at_pos, animation_completed)
else
animation_completed()
end
end

if #M.animations == 0 then
animation_completed()
end
end

return M
28 changes: 28 additions & 0 deletions lua/neohack/buffer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

local view_buffer = require("neohack.view_buffer")
local Def = require("neohack.def")
local animate = require("neohack.animate")

---@class Buffer
--- @field bufnr integer
Expand Down Expand Up @@ -122,8 +123,35 @@ end
--- Apply the callback to move to the next frame for a specific buffer
---@param callback function()
M.tick = function(callback)
if state.animating then
message.debug("animating so skip tick")
return
end
callback()
M.write_buf(nil)
M.animations()
end

M.animations = function()
-- message.debug("animating true")
state.animating = true
animate.show_animations(M.get_entity_at_pos, function()
vim.defer_fn(function()
-- message.debug("animating false")
state.animating = false
--TODO: what should this timer be?

-- ensure player always bounces off after animations
local _, _, entity = M.get_under_cursor()
if entity.type ~= Def.DefType.floor and entity.type ~= Def.DefType.item then
-- vim.defer_fn(function()
state.player:restore_previous_position()
-- end, 50)
else
state.player:store_previous_position()
end
end, 10)
end)
end

--- Get the entity at a specific position in a specific buffer, including player
Expand Down
50 changes: 32 additions & 18 deletions lua/neohack/components/combat.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ local state = require("neohack.state")
local chance = require("neohack.chance")
local constant_defs = require("neohack.constant_defs")
local attributes = require("neohack.attribute_getters")
local animate = require("neohack.animate")
local Def = require("neohack.def")
local view_buffer = require("neohack.view_buffer")

---@class Combat
---@field parent Entity
Expand Down Expand Up @@ -121,15 +124,14 @@ function Combat:try_weapon(target, weapon)
end

---comment
---@param target Combat
---@param weapon Entity
---@return boolean
function Combat:try_dodge(target, weapon)
function Combat:try_dodge(weapon)
return chance.action_success(
target:dodge_skill(),
target.parent.attributes.vision,
self:dodge_skill(),
self.parent.attributes.vision,
weapon.attributes.hit_rate or 0,
target.dodge_threshold
self.dodge_threshold
)
end

Expand All @@ -143,41 +145,46 @@ function Combat:try_damage_dealt(target, weapon)

local weapon_success = self:try_weapon(target, weapon)
if not weapon_success then
target.hit_highlight = constant_defs.missing_highlight
message.notify(self.parent.name .. " missed " .. target.parent.name)
return 0, false
end

local dodge_success = self:try_dodge(target, weapon)
local dodge_success = target:try_dodge(weapon)
if dodge_success then
self:dodge(target)
-- return constant_defs.cursor_dodge_highlight
return 0, false
local dodged = target:dodge(self)
if dodged then
return 0, false
end
end

local damage = weapon.attributes.damage - target:deflect_skill()
if damage <= 0 then
message.notify(target.parent.name .. " deflected " .. self.parent.name)
-- return constant_defs.cursor_dodge_highlight
return 0, false
end

return damage, true
end

---comment
---@param target Combat
function Combat:dodge(target)
message.notify(target.parent.name .. " dodged " .. self.parent.name .. ".")
---@param attacker Combat
function Combat:dodge(attacker)
local floor = self.parent.movement:find_floor(false)
if not floor then
message.debug(self.parent.name, "has no space to dodge")
return false
end
if self.parent.type ~= Def.DefType.player then
view_buffer.set_cursor(floor.movement.row, floor.movement.col)
end
self.parent.movement:move_to(floor.movement.row, floor.movement.col)
message.notify(self.parent.name .. " dodged " .. attacker.parent.name .. ".")
return true
end

---comment
---@param target Combat
function Combat:apply_highlights(target)
-- TODO: hit player highlight not working
-- highlight is set, but cursor overrides it
-- view_buffer.highlight_hit(attacker.movement.row, attacker.movement.col, defs.hit_by_highlight, 200)
target.hit_highlight = constant_defs.hurt_highlight
self.hit_highlight = constant_defs.hitting_highlight
end

Expand Down Expand Up @@ -224,10 +231,17 @@ end
function Combat:attack(target)
--the target will target the attacker
target.parent.attributes.target = self.parent
-- if attacking, then it should be the target
self.parent.attributes.target = target.parent
local weapon = self:get_weapon()

if self.parent.type ~= Def.DefType.terrain then
animate.attack(self.parent, target.parent)
end

local damage, dealt = self:try_damage_dealt(target, weapon)
if not dealt then
self.hit_highlight = constant_defs.missing_highlight
return false
end

Expand Down
44 changes: 38 additions & 6 deletions lua/neohack/components/decision.lua
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ function Decision:find_target(visible_targets)
return nil
end

---comment
---@param visible_targets Entity[]
---@return boolean
function Decision:choose_target(visible_targets)
local targets = ""
for _, entity in ipairs(visible_targets) do
Expand All @@ -77,7 +80,13 @@ function Decision:choose_target(visible_targets)

local target = self:find_target(visible_targets)
self.parent.attributes.target = target
message.notify(self.parent.name, "targeting", (target and target.name or "nothing"))
if target then
message.debug(self.parent.name, "targeting", target.name)
return true
else
message.debug(self.parent.name, "targeting", "nothing")
return false
end
end

---what is the target relative to self? DefType are normally relative to the player
Expand Down Expand Up @@ -180,7 +189,11 @@ Decision.actions = {
if #items > 0 then
--TODO: they shouldn't pick everything up
local item = items[1]
return self.parent.inventory:npc_pickup_off_floor(item)
local success = self.parent.inventory:npc_pickup_off_floor(item)
if success then
self.parent.movement:move_to(item.movement.row, item.movement.col)
end
return success
end
return false
end,
Expand Down Expand Up @@ -216,14 +229,24 @@ Decision.actions = {
end,
},

choose_new_target = {
name = "choose_new_target",
weight = 3,
---@param self Decision
---@return boolean
act = function(self)
local targets = state.get_visible_entities(state.current_bufnr, self.parent, self.parent.vision:view_distance())
self:choose_target(targets)
return true
end,
},

move_towards_target = {
name = "move_towards_target",
weight = 30,
---@param self Decision
---@return boolean
act = function(self)
local targets = state.get_visible_entities(state.current_bufnr, self.parent, self.parent.vision:view_distance())
self:choose_target(targets)
return self.parent.movement:move_closer_to_target()
end,
},
Expand Down Expand Up @@ -291,8 +314,17 @@ Decision.actions = {
},
}

function Decision:ensure_has_target()
if not self.parent.attributes.target then
local targets = state.get_visible_entities(state.current_bufnr, self.parent, self.parent.vision:view_distance())
self:choose_target(targets)
end
end

---chose the action to perform and do it
function Decision:choose_action()
self:ensure_has_target()

local list = {}
for _, action in pairs(Decision.actions) do
table.insert(list, action)
Expand All @@ -302,7 +334,7 @@ function Decision:choose_action()
if action and action ~= Decision.actions.wait then
table.remove(list, index) -- don't try this again
if action.act(self) then
-- message.notify(self.parent.name, "chose", action.name)
message.debug(self.parent.name, "chose", action.name)
return
else
-- message.notify(self.parent.name, "failed at", action.name)
Expand All @@ -313,7 +345,7 @@ function Decision:choose_action()
end

Decision.actions.wait.act(self)
-- message.notify(self.parent.name, "fallback to wait")
message.debug(self.parent.name, "fallback to wait")
end

return Decision
2 changes: 1 addition & 1 deletion lua/neohack/components/inventory.lua
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ function Inventory:drop(key)
end

for _, item in ipairs(items) do
local floor = self.parent.movement:find_floor()
local floor = self.parent.movement:find_floor(true)
if not floor then
message.notify(self.parent.name, "has no space to drop", item.name)
return false
Expand Down
Loading