Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ Actions
- [ ] refactor
- [x] extract all features into components
- [x] all player actions can be performed by entities
- [ ] enemy sneak makes them not visible
- [x] enemy sneak makes them not visible
- [ ] use the same move logic for everything
- [x] ai for entities to choose action
- [x] choose target
Expand Down Expand Up @@ -144,7 +144,7 @@ Actions
- [x] change/replace - both pickup and drop
- [x] visual - move without attacking ie sneaking
- [x] an entity stat for sneakability
- [ ] steal items
- [x] steal items from entity inventory
- [x] yank - search, works in combination with sneaking
- [ ] paste - ???
- [ ] jumplist - ???
Expand Down
3 changes: 1 addition & 2 deletions lua/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ require("lazy").setup({
config = function()
require("which-key").setup({
triggers = {
{ "<auto>", mode = "n" },
{ "a", mode = { "n" } },
{ "<leader>", mode = { "n", "v" } },
},
})
end,
Expand Down
30 changes: 28 additions & 2 deletions lua/neohack/actions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ M.actions = {
M.tick()
end,

---comment
pick = function(request)
state.player.sneak:pick(request.object)
M.tick()
end,

---comment
wear = function(request)
state.player.inventory:wear(request.object, request.target)
Expand Down Expand Up @@ -124,10 +130,13 @@ M.synonyms = {

p = "pilfer",
steal = "pilfer",
pickpocket = "pilfer",
loot = "pilfer",
take = "pilfer",

P = "pick",
pick = "pick",
pickpocket = "pick",

e = "eat",

d = "drop",
Expand Down Expand Up @@ -300,6 +309,13 @@ M.execute_action = function(inserted_chars)
end
end

local directions = {
{ key = "h", desc = "left" },
{ key = "j", desc = "down" },
{ key = "k", desc = "up" },
{ key = "l", desc = "right" },
}

---comment
---@param bufnr number
---@param tick function
Expand All @@ -309,7 +325,15 @@ M.setup = function(bufnr, tick)

M.tick = tick
local function map(key, func, desc)
vim.keymap.set("n", M.leader_key .. key, func, { buffer = bufnr, desc = desc })
vim.keymap.set({ "n", "v" }, M.leader_key .. key, func, { buffer = bufnr, desc = desc })
end

local function direction_keymaps(key, func, name)
for _, map_info in ipairs(directions) do
map(key .. map_info.key, function()
func({ object = map_info.key })
end, name .. " " .. map_info.desc)
end
end

map("?", M.actions.help, "help")
Expand All @@ -322,6 +346,8 @@ M.setup = function(bufnr, tick)
map("r", M.prompt_rename, "rename")
map("l", M.prompt_look, "look")
map("p", M.prompt_pilfer, "pilfer")
--TODO:make it operator pending
direction_keymaps("P", M.actions.pick, "Pick")
map("e", M.prompt_eat, "eat")
map("d", M.prompt_drop, "drop")
map("s", M.prompt_say, "say")
Expand Down
12 changes: 2 additions & 10 deletions lua/neohack/buffer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -139,17 +139,9 @@ M.animations = 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
state.player:handle_bounce()
--TODO: what should this timer be?
end, 10)
end)
end
Expand Down
84 changes: 67 additions & 17 deletions lua/neohack/components/decision.lua
Original file line number Diff line number Diff line change
Expand Up @@ -41,27 +41,32 @@ end
function Decision:find_target(visible_targets)
--keep existing target, until not visible
--TODO: or on a timer?

local old_target = self.parent.attributes.target
if old_target then
for _, entity in ipairs(visible_targets) do
if entity == old_target then
return old_target
if self.parent.vision:can_see(entity) then
return old_target
else
message.debug(self.parent.name, "cannot see old target", old_target.name)
end
end
end
end

for _, entity in ipairs(visible_targets) do
if entity.type == Def.DefType.player and entity ~= self.parent then
if entity.type == Def.DefType.player and entity ~= self.parent and self.parent.vision:can_see(entity) then
return entity
end
end
for _, entity in ipairs(visible_targets) do
if entity.type == Def.DefType.friend and entity ~= self.parent then
if entity.type == Def.DefType.friend and entity ~= self.parent and self.parent.vision:can_see(entity) then
return entity
end
end
for _, entity in ipairs(visible_targets) do
if entity.type == Def.DefType.item and entity ~= self.parent then
if entity.type == Def.DefType.item and entity ~= self.parent and self.parent.vision:can_see(entity) then
return entity
end
end
Expand Down Expand Up @@ -125,12 +130,12 @@ Decision.actions = {
-- attack player from 1 space away regardless of entity's available moves
local can = self.parent ~= state.player
and Move.manhattan_distance(
self.parent.movement.row,
self.parent.movement.col,
state.player.movement.row,
state.player.movement.col
)
<= 1
self.parent.movement.row,
self.parent.movement.col,
state.player.movement.row,
state.player.movement.col
) <= 1
and self.parent.vision:can_see(state.player)
if can then
state.player:hit_by(self.parent)
return true -- attack was attempted
Expand Down Expand Up @@ -162,12 +167,10 @@ Decision.actions = {
self.parent.movement.col,
1 -- only right next to
)
if #enemies > 0 then
for _, enemy in ipairs(enemies) do
if enemy ~= self.parent then
self.parent.combat:attack(enemy.combat)
return true -- attack was attempted
end
for _, enemy in ipairs(enemies) do
if enemy ~= self.parent and self.parent.vision:can_see(enemy) then
self.parent.combat:attack(enemy.combat)
return true -- attack was attempted
end
end
return false
Expand Down Expand Up @@ -251,9 +254,29 @@ Decision.actions = {
end,
},

sneak_on = {
name = "sneak_on",
weight = 18,
---@param self Decision
---@return boolean
act = function(self)
return self.parent.sneak:sneak_on()
end,
},

sneak_off = {
name = "sneak_off",
weight = 12,
---@param self Decision
---@return boolean
act = function(self)
return self.parent.sneak:sneak_off()
end,
},

kick = {
name = "kick",
weight = 8,
weight = 10,
---@param self Decision
---@return boolean
act = function(self)
Expand All @@ -262,6 +285,32 @@ Decision.actions = {
end,
},

pick = {
name = "pick",
weight = 8,
---@param self Decision
---@return boolean
act = function(self)
if self.parent.sneak.sneaking then
local enemies, _ = state.scan_area(
state.current_bufnr,
self.parent.movement.row,
self.parent.movement.col,
1 -- only right next to
)
for _, enemy in ipairs(enemies) do
if enemy ~= self.parent and self.parent.vision:can_see(enemy) then
return self.parent.sneak:pick_entity(enemy)
end
end
return false
else
-- don't bother picking if not sneaking
return false
end
end,
},

rename = {
name = "rename",
weight = 6,
Expand Down Expand Up @@ -337,6 +386,7 @@ function Decision:choose_action()
message.debug(self.parent.name, "chose", action.name)
return
else
-- that action isn't possible this turn
-- message.notify(self.parent.name, "failed at", action.name)
end
else
Expand Down
6 changes: 5 additions & 1 deletion lua/neohack/components/health.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

local constant_defs = require("neohack.constant_defs")
local Def = require("neohack.def")
local utils = require("neohack.utils")

---@class Health
---@field parent Entity
Expand Down Expand Up @@ -41,6 +42,7 @@ end
function Health:check_dead()
if self:is_dead() then
self.alive = false
self.parent.sneak.sneaking = false
self:drop_corpse()
return true
end
Expand All @@ -49,7 +51,9 @@ end

function Health:drop_corpse()
self.parent.movement.char = constant_defs.enemy_corpse
self.parent.name = self.parent.name .. "_corpse"
if not utils.ends_with(self.parent.name, constant_defs.corpse_suffix) then
self.parent.name = self.parent.name .. constant_defs.corpse_suffix
end
self.parent.type = Def.DefType.item
self.parent.attributes.block_vision = false

Expand Down
2 changes: 1 addition & 1 deletion lua/neohack/components/inventory.lua
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ function Inventory:retrieve_items(keys)
end
end
if not found then
message.notify(self.parent.name("has no"), name, "to retrieve")
message.notify(self.parent.name, "has no", name, "to retrieve")
-- get all or nothing
return nil
end
Expand Down
67 changes: 67 additions & 0 deletions lua/neohack/components/sneak.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@
local state = require("neohack.state")
local chance = require("neohack.chance")
local attributes = require("neohack.attribute_getters")
local buffer = require("neohack.buffer")
local Def = require("neohack.def")
local Move = require("neohack.move")
local message = require("neohack.message")

---@class Sneak
---@field parent Entity
---@field sneak_threshold number
---@field sneaking boolean
local Sneak = {}
Sneak.__index = Sneak
Sneak.__name = "Sneak"
Expand All @@ -20,6 +25,8 @@ function Sneak.new(parent)
parent = parent,

sneak_threshold = 0.5,

sneaking = false,
}
parent.attributes.sneak_attribute = 0.5
setmetatable(instance, Sneak)
Expand Down Expand Up @@ -50,4 +57,64 @@ function Sneak:try_sneak(target)
return chance.action_success(self:sneak_skill(), 0, target.parent.attributes.vision, self.sneak_threshold)
end

---pick pocket
---@param direction string
---@return boolean
function Sneak:pick(direction)
local move = Move.directions[direction]
if move == nil then
message.debug(self.parent.name, "pick failed without direction")
return false
end
local row, col = self.parent.movement.row, self.parent.movement.col
local pick_row, pick_col = row + move.row, col + move.col
local target = buffer.get_entity_at_pos(pick_row, pick_col)
return self:pick_entity(target)
end

---comment
---@param target Entity
---@return boolean
function Sneak:pick_entity(target)
if
target
and target.type ~= Def.DefType.floor
and target.type ~= Def.DefType.terrain
and target.type ~= Def.DefType.item
then
local sneak_bonus = self.sneaking and 0.4 or 0.0
local success =
chance.action_success(self:sneak_skill(), sneak_bonus, target.attributes.vision, self.sneak_threshold)
if not success then
message.notify(self.parent.name, "pick failed on", target.name)
return true
end
local index = math.random(#target.inventory.items)
local items = target.inventory:retrieve_items({ tostring(index) })
if items and #items > 0 then
local item = items[1]
self.parent.inventory:pickup(item)
message.notify(self.parent.name, "picked", item.name, "from", target.name)
else
message.notify(self.parent.name, "picked nothing from", target.name)
end
return true
else
message.debug(self.parent.name, "picked from non target")
return false
end
end

function Sneak:sneak_on()
self.sneaking = true
message.debug(self.parent.name, "sneaking")
return true
end

function Sneak:sneak_off()
self.sneaking = false
message.debug(self.parent.name, "not sneaking")
return true
end

return Sneak
Loading