diff --git a/README.md b/README.md index 2f4408a..33da368 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 diff --git a/lua/neohack/actions.lua b/lua/neohack/actions.lua index dc5216f..46d33b6 100644 --- a/lua/neohack/actions.lua +++ b/lua/neohack/actions.lua @@ -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 diff --git a/lua/neohack/animate.lua b/lua/neohack/animate.lua new file mode 100644 index 0000000..f025e04 --- /dev/null +++ b/lua/neohack/animate.lua @@ -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 diff --git a/lua/neohack/buffer.lua b/lua/neohack/buffer.lua index f842cc0..db00a04 100644 --- a/lua/neohack/buffer.lua +++ b/lua/neohack/buffer.lua @@ -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 @@ -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 diff --git a/lua/neohack/components/combat.lua b/lua/neohack/components/combat.lua index af01114..bb8cf06 100644 --- a/lua/neohack/components/combat.lua +++ b/lua/neohack/components/combat.lua @@ -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 @@ -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 @@ -143,22 +145,21 @@ 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 @@ -166,18 +167,24 @@ function Combat:try_damage_dealt(target, weapon) 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 @@ -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 diff --git a/lua/neohack/components/decision.lua b/lua/neohack/components/decision.lua index 80f6a72..d4d97d1 100644 --- a/lua/neohack/components/decision.lua +++ b/lua/neohack/components/decision.lua @@ -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 @@ -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 @@ -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, @@ -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, }, @@ -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) @@ -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) @@ -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 diff --git a/lua/neohack/components/inventory.lua b/lua/neohack/components/inventory.lua index d5431ac..4ce185b 100644 --- a/lua/neohack/components/inventory.lua +++ b/lua/neohack/components/inventory.lua @@ -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 diff --git a/lua/neohack/components/movement.lua b/lua/neohack/components/movement.lua index ea29457..1371b98 100644 --- a/lua/neohack/components/movement.lua +++ b/lua/neohack/components/movement.lua @@ -93,20 +93,7 @@ function Movement:make_move(move) local on_player = entity and entity.type == Def.DefType.player local is_floor = entity and entity.movement.char == constant_defs.floor_char if is_floor and not on_player and new_row > 0 and new_col > 0 then - -- message.notify("making move " .. vim.inspect(self) .. " " .. vim.inspect(move)) - -- don't delete item under player - if self.parent.type ~= Def.DefType.player then - buffer.set_entity_at_cell(self.row, self.col, state.entity_generator.new_floor(self.row, self.col)) - view_buffer.set_highlight(self.row, self.col, nil) - else - buffer.highlight_cursor(constant_defs.cursor_hit_highlight) - end - buffer.set_entity_at_cell(new_row, new_col, self.parent) - - -- TODO: without this it leaves a hit to the enemy location - if self.visible then - view_buffer.set_highlight(new_row, new_col, constant_defs.enemy_highlight) - end + self:move_to(new_row, new_col) return true else -- message.notify("can't move to " .. char or "" .. " " .. vim.inspect(mover) .. " " .. vim.inspect(move)) @@ -114,6 +101,20 @@ function Movement:make_move(move) end end +---@param row integer +---@param col integer +function Movement:move_to(row, col) + -- message.notify("making move " .. vim.inspect(self) .. " " .. vim.inspect(move)) + -- don't delete item under player + if self.parent.type ~= Def.DefType.player then + buffer.set_entity_at_cell(self.row, self.col, state.entity_generator.new_floor(self.row, self.col)) + view_buffer.set_highlight(self.row, self.col, nil) + else + -- buffer.highlight_cursor(constant_defs.cursor_hit_highlight) + end + buffer.set_entity_at_cell(row, col, self.parent) +end + ---comment function Movement:move_closer_to_target() if self.parent.attributes.target then @@ -237,11 +238,12 @@ local function local_cells(row, col, callback) end ---comment +---@param skip_player boolean true if player counts as floor ---@return Entity? -function Movement:find_floor() +function Movement:find_floor(skip_player) local floor = nil local_cells(self.parent.movement.row, self.parent.movement.col, function(row, col) - local entity = buffer.get_entity_at_pos(row, col, true) + local entity = buffer.get_entity_at_pos(row, col, skip_player) if entity and entity.type == Def.DefType.floor then floor = entity return true diff --git a/lua/neohack/constant_defs.lua b/lua/neohack/constant_defs.lua index b2beb55..737bb79 100644 --- a/lua/neohack/constant_defs.lua +++ b/lua/neohack/constant_defs.lua @@ -5,21 +5,19 @@ local Def = require("neohack.def") local M = {} -M.player = "@" +M.player = " " M.enemy_corpse = "x" M.not_visible = "." M.floor_char = " " -M.hidden_highlight = "LineNr" -- gray fg -M.hurt_highlight = "CurSearch" -- red bg -M.hitting_highlight = "Search" -- green bg -M.missing_highlight = "Question" -- yellow fg +-- M.hidden_highlight = "LineNr" -- gray fg + +M.missing_highlight = "NeoHack_missing" -- yellow bg +M.hitting_highlight = "NeoHack_hitting" -- red bg + M.cursor_hit_highlight = { bg = "#8f1122" } -- dark red bg M.cursor_dodge_highlight = { bg = "#8f5d11" } -- dark orange bg -M.item_highlight = "Directory" -- green fg -M.enemy_highlight = "ErrorMsg" -- red fg - M.slap = Def.new({ type = Def.DefType.item, name = "slap", @@ -28,6 +26,7 @@ M.slap = Def.new({ durability = 10, hit_rate = 0.5, block_vision = false, + view_distance = 1, }) return M diff --git a/lua/neohack/defs.lua b/lua/neohack/defs.lua index 4b356ab..51a1a41 100644 --- a/lua/neohack/defs.lua +++ b/lua/neohack/defs.lua @@ -250,7 +250,7 @@ M.enemy_defs = { damage = 1, hit_rate = 0.5, block_vision = true, - vision = 0.01, + vision = 0.1, view_distance = 1, durability = 10, }), diff --git a/lua/neohack/entity.lua b/lua/neohack/entity.lua index 59c77b2..9c621d3 100644 --- a/lua/neohack/entity.lua +++ b/lua/neohack/entity.lua @@ -55,7 +55,7 @@ function Entity.new(def, row, col) instance.decision = Decision.new(instance, def.randomness) -- must come last to include above attributes - instance.highlight_group = highlight.create_highlight(instance) + instance.highlight_group = highlight.create_entity_highlight(instance) return instance end diff --git a/lua/neohack/event.lua b/lua/neohack/event.lua index e8fe050..f8df2df 100644 --- a/lua/neohack/event.lua +++ b/lua/neohack/event.lua @@ -43,10 +43,6 @@ M.make_moves = function(movers) message.notify(string.gsub(mover.name, "_corpse", "") .. " died") else mover.decision:choose_action() - if mover.combat.hit_highlight then - view_buffer.highlight_hit(mover.movement.row, mover.movement.col, mover.combat.hit_highlight, 400) - end - mover.combat.hit_highlight = nil end end end diff --git a/lua/neohack/game.lua b/lua/neohack/game.lua index 9c15514..f53ef09 100644 --- a/lua/neohack/game.lua +++ b/lua/neohack/game.lua @@ -54,7 +54,11 @@ M.create_new_buffer = function(lines) handle_yanked = M.handle_yanked, }) + -- small map + -- buffer.buffers[bufnr].cells = map.generate_new_map(20, 20, 1000) + -- large map buffer.buffers[bufnr].cells = map.generate_new_map(30, 160, 10000) + buffer.write_buf(bufnr) M.setup_buffer(bufnr, level) end @@ -97,6 +101,7 @@ end ---comment M.handle_moved = function() + message.debug("handle_moved") if state.player.health:is_dead() then M.end_game() end @@ -105,6 +110,7 @@ end ---comment M.handle_insert = function() + message.debug("handle_insert") if state.player.health:is_dead() then M.end_game() end @@ -119,9 +125,10 @@ end ---TODO: deprecated cursor moved also handles changed ---comment M.handle_changed = function() - if state.player.health:is_dead() then - M.end_game() - end + message.debug("handle_changed") + -- if state.player.health:is_dead() then + -- M.end_game() + -- end -- message.notify("changed") -- if not event.in_sneak_move() then -- edits.handle_changed() @@ -130,7 +137,7 @@ end ---comment M.handle_yanked = function() - -- message.notify("yanked") + message.debug("handle_yanked") edits.handle_yanked() end @@ -156,6 +163,9 @@ M.tick = function() state.player:set_position_from_cursor() + -- map.generate_new_map_sections(state.current_bufnr, state.player.attributes.view_distance) + map.show_visible_line_of_sight(state.current_bufnr, state.player.vision:view_distance()) + -- detect deleted on every turn -- state.deleted = map.find_deleted(state.current_bufnr) @@ -177,9 +187,6 @@ M.tick = function() if state.player.health:is_dead() then M.end_game() end - - -- map.generate_new_map_sections(state.current_bufnr, state.player.attributes.view_distance) - map.show_visible_line_of_sight(state.current_bufnr, state.player.vision:view_distance()) end) end diff --git a/lua/neohack/generated_defs.lua b/lua/neohack/generated_defs.lua index 3aa28d9..a2c55e3 100644 --- a/lua/neohack/generated_defs.lua +++ b/lua/neohack/generated_defs.lua @@ -39,7 +39,6 @@ M.random_word_starting_with = function(words, letter) return matching_words[math.random(1, #matching_words)] else error("none found for " .. letter) - return nil end end @@ -94,6 +93,7 @@ M.generate_item = function() local randomness = calculate_stat_float(0.2, portions.randomness, 0.5) local vision = calculate_stat_float(0.2, portions.vision, 0.1) local block_vision = math.random() > 0.9 + local view_distance = math.random(2, 10) local spell = nil local new_def = Def.new({ @@ -110,6 +110,7 @@ M.generate_item = function() spell = spell, -- TODO: random moveset moves = moves.eight, + view_distance = view_distance, }) -- print( diff --git a/lua/neohack/highlight.lua b/lua/neohack/highlight.lua index a872fb4..c1c4410 100644 --- a/lua/neohack/highlight.lua +++ b/lua/neohack/highlight.lua @@ -1,17 +1,32 @@ local Def = require("neohack.def") +local constant_defs = require("neohack.constant_defs") +local message = require("neohack.message") + local M = {} ---comment ---@param instance Entity ---@return string -M.create_highlight = function(instance) +M.create_entity_highlight = function(instance) local colour = M.generate_colour(instance) - local highlight_group = instance.name:gsub("[^%w]", "_") - -- print("highlight_group", "'" .. highlight_group .. "'") + local highlight_group = instance.name:gsub("[^%w]", "_") .. colour:gsub("#", "_") + -- message.debug("highlight_group", highlight_group, colour) + --TODO: do we really want totally unique highlight group for every entity? + --TODO: memoize vim.api.nvim_command("highlight " .. highlight_group .. " guifg=" .. colour) return highlight_group end +---comment +---@param name string +---@param colour string +---@return string +M.create_highlight_background = function(name, colour) + -- message.debug("highlight_group", name, colour) + vim.api.nvim_command("highlight " .. name .. " guibg=" .. colour) + return name +end + --- Convert a number to a value between 0 and 255 for color calculation --- @param num number --- @return number @@ -70,4 +85,9 @@ M.generate_colour = function(entity) return color end +M.setup_highlights = function() + M.create_highlight_background(constant_defs.hitting_highlight, "#8f1122") -- red bg + M.create_highlight_background(constant_defs.missing_highlight, "#8f6311") -- orange bg +end + return M diff --git a/lua/neohack/init.lua b/lua/neohack/init.lua index 682350d..27e85f0 100644 --- a/lua/neohack/init.lua +++ b/lua/neohack/init.lua @@ -6,6 +6,8 @@ local M = {} local game = require("neohack.game") local message = require("neohack.message") local actions = require("neohack.actions") +local state = require("neohack.state") +local highlight = require("neohack.highlight") ---comment M.start_game = function() @@ -27,6 +29,9 @@ M.setup = function(opts) --- TODO: separate map generate seed from the rest math.randomseed(12345) + -- state.debug = true + -- state.animations_enabled = false + -- local leader could be an idea actions.leader_key = "" -- message.notify_func = print @@ -42,6 +47,8 @@ M.setup = function(opts) ) vim.api.nvim_create_user_command("EndGame", M.end_game, { nargs = 0, desc = "End the current NeoHack game" }) + + highlight.setup_highlights() end M.status_line = function() diff --git a/lua/neohack/map.lua b/lua/neohack/map.lua index b7ee456..233f7e3 100644 --- a/lua/neohack/map.lua +++ b/lua/neohack/map.lua @@ -134,8 +134,11 @@ M.show_visible_line_of_sight = function(bufnr, view_distance) for row_index, line in ipairs(cells) do for col_index, entity in ipairs(line) do if - M.manhattan_distance(row_index, col_index, player_row, player_col) > view_distance - or not M.is_visible_line_of_sight(player_row, player_col, row_index, col_index, cells) + not state.debug + and ( + M.manhattan_distance(row_index, col_index, player_row, player_col) > view_distance + or not M.is_visible_line_of_sight(player_row, player_col, row_index, col_index, cells) + ) then entity.movement.visible = false -- setting highlights individually for non visible chars hurts performance a lot @@ -147,6 +150,7 @@ M.show_visible_line_of_sight = function(bufnr, view_distance) end local hi = entity.highlight_group if hi then + --TODO: highlights sometimes get cleared somehow view_buffer.set_highlight(row_index, col_index, hi) end end diff --git a/lua/neohack/message.lua b/lua/neohack/message.lua index eedc224..700f220 100644 --- a/lua/neohack/message.lua +++ b/lua/neohack/message.lua @@ -1,3 +1,5 @@ +local state = require("neohack.state") + local M = { notify_func = nil, message_buf = nil, @@ -18,6 +20,14 @@ M.notify = function(...) M.notify_func({ ... }) end +M.debug = function(...) + if state.debug then + local args = { ... } + table.insert(args, 1, "DEBUG:") + M.notify_func(args) + end +end + ---comment ---@param s string ---@return string[] diff --git a/lua/neohack/player.lua b/lua/neohack/player.lua index 1e733f0..22fa0cd 100644 --- a/lua/neohack/player.lua +++ b/lua/neohack/player.lua @@ -69,33 +69,41 @@ function Player:player_hit_move() if entity.type == Def.DefType.item then -- delete to pickup -- self:hit_item(row, col, defs.item_defs[curr]) + self:store_previous_position() elseif entity.type == Def.DefType.floor then -- do nothing + self:store_previous_position() elseif entity.type == Def.DefType.friend then self:restore_previous_position() entity.attributes.health = entity.attributes.health + 0.5 - message.notify("Hugged " .. entity.name) + message.notify(self.name, "hugged", entity.name) elseif entity.type == Def.DefType.enemy then - self:hit_enemy(row, col) - elseif entity.type == Def.DefType.terrain then - self:hit_terrain(row, col, entity) + self:hit_enemy(row, col, entity) + -- elseif entity.type == Def.DefType.terrain then + -- -- bounce off terrain + -- self:restore_previous_position() + -- self:hit_terrain(row, col, entity) else -- assume other characters are terrain + -- bounce off terrain + self:restore_previous_position() + --TODO: fix issue where cursor can keep skipping past terrain self:hit_terrain(row, col, entity) end - self:store_previous_position() + -- self:store_previous_position() -- TODO: do we need to mark the player? -- mark the player position -- buffer.replace_char_at(state.prev_cursor.row, state.prev_cursor.col, constant_defs.player) end function Player:store_previous_position() - -- message.notify("store prev position") + message.debug("store_previous_position") local prevRow, prevCol = unpack(vim.api.nvim_win_get_cursor(0)) state.prev_cursor = { row = prevRow, col = prevCol + 1 } end function Player:restore_previous_position() + message.debug("restore_previous_position") view_buffer.move_prev_cursor() self:set_position_from_cursor() end @@ -115,15 +123,9 @@ end --- bounce off enemies, unless you kill them ---@param row integer ---@param col integer -function Player:hit_enemy(row, col) - local enemy = buffer.get_entity_at_pos(row, col, true) +function Player:hit_enemy(row, col, enemy) if enemy then - -- if not self.combat:try_hit_enemy(enemy) then self.combat:attack(enemy.combat) - if not enemy.health:is_dead() then - -- bounce if enemy didn't die - self:restore_previous_position() - end end end @@ -132,9 +134,8 @@ end ---@param col integer ---@param terrain Entity function Player:hit_terrain(row, col, terrain) - view_buffer.highlight_hit(row, col, constant_defs.hitting_highlight, 150) - -- bounce off terrain - self:restore_previous_position() + -- TODO: no need to highlight the terrain that hit the player + -- view_buffer.highlight_hit(row, col, constant_defs.hitting_highlight, 150) if terrain.name == "down" then local level = self:handle_down() message.notify("Went down to " .. level) diff --git a/lua/neohack/state.lua b/lua/neohack/state.lua index 751104d..c835f75 100644 --- a/lua/neohack/state.lua +++ b/lua/neohack/state.lua @@ -39,6 +39,16 @@ local M = { ---@type function scan_area = nil, + + -- show debug messages + ---@type boolean + debug = false, + + ---@type boolean if currently animating, don't do a tick + animating = false, + + ---@type boolean if animations are enabled at all + animations_enabled = true, } M.slot_types = { diff --git a/lua/neohack/utils.lua b/lua/neohack/utils.lua index f2d7b6c..41d91c6 100644 --- a/lua/neohack/utils.lua +++ b/lua/neohack/utils.lua @@ -186,4 +186,10 @@ M.weighted_random = function(actions) return nil, nil end +M.sleep = function(a) + local sec = tonumber(os.clock() + a) + while os.clock() < sec do + end +end + return M diff --git a/lua/neohack/view_buffer.lua b/lua/neohack/view_buffer.lua index 12594b8..5b7966a 100644 --- a/lua/neohack/view_buffer.lua +++ b/lua/neohack/view_buffer.lua @@ -115,9 +115,10 @@ M.highlight_hit = function(row, col, highlight, timeout) error("no bufnr") end M.set_highlight(row, col, highlight) - vim.defer_fn(function() - vim.api.nvim_buf_clear_namespace(bufnr, namespace, row - 1, row) - end, timeout) + --TODO: fix clearing too many highlights, keeping hurt highlight is better + -- vim.defer_fn(function() + -- vim.api.nvim_buf_clear_namespace(bufnr, namespace, row - 1, row) + -- end, timeout) end ---comment @@ -135,6 +136,10 @@ M.move_prev_cursor = function() -- end, 10) end +M.show_char_at_pos = function(row, col, char) + vim.api.nvim_buf_set_text(state.current_bufnr, row - 1, col - 1, row - 1, col, { char }) +end + -- TODO: do we need to save/restore buffer settings anymore with a copy of the buffer? ---comment ---@param bufnr integer diff --git a/tests/components/combat_spec.lua b/tests/components/combat_spec.lua index d15a8c2..dfc3e24 100644 --- a/tests/components/combat_spec.lua +++ b/tests/components/combat_spec.lua @@ -53,18 +53,16 @@ describe("combat", function() it("try_dodge success", function() local weapon = helpers.bad_weapon - local one = new_combat("one", weapon) local two = new_combat("two", weapon) - eq(true, one:try_dodge(two, weapon())) + eq(true, two:try_dodge(weapon())) end) it("try_dodge failure", function() local weapon = helpers.good_weapon - local one = new_combat("one", weapon) local two = new_combat("two", weapon) - eq(false, one:try_dodge(two, weapon())) + eq(false, two:try_dodge(weapon())) end) it("try_damage_dealt success", function() diff --git a/tests/player_spec.lua b/tests/player_spec.lua index 2e2219d..037ba82 100644 --- a/tests/player_spec.lua +++ b/tests/player_spec.lua @@ -79,7 +79,7 @@ describe("inspect", function() eq(0.1, player.eat:eat_skill()) eq(0.05, player.combat:deflect_skill()) eq( - "name=player char=@ row=0 col=0 health=10 durability=20 damage=1 weapon_damage=1 hit_rate=0.5 dodge_skill=0.5 weapon_skill=0.5 deflect_skill=0.05 sneak_skill=0.5 fuse_skill=0.5 eat_skill=0.1 view_distance=10 search_skill=0.5 vision=0.5 inscription= randomness=0.01 wearing: \nitems:\n turns:0", + "name=player char= row=0 col=0 health=10 durability=20 damage=1 weapon_damage=1 hit_rate=0.5 dodge_skill=0.5 weapon_skill=0.5 deflect_skill=0.05 sneak_skill=0.5 fuse_skill=0.5 eat_skill=0.1 view_distance=10 search_skill=0.5 vision=0.5 inscription= randomness=0.01 wearing: \nitems:\n turns:0", player:inspect() ) end)