feat: extend game with Game:socketRune and Game:temperItem
This commit is contained in:
1 parent
a43ad023f7
commit
3427a1a8f0
1 file changed
+197
-7
+197
-7
@@ -1498,25 +1498,167 @@ end
|
||||
-- socket attempt would leave the gem untouched in inventory - the rune is
|
||||
-- always spent once it joins the chain, whether or not that completes a
|
||||
-- runeword, since there is no "wrong" rune to protect the player from (see
|
||||
-- Runewords.socket).
|
||||
function Game:socketRune(target, runeIndex)
|
||||
local item = self:resolveGear(target)
|
||||
local rune = self.player.inventory[runeIndex]
|
||||
if not item or not rune or rune.kind ~= Items.RUNE then return false end
|
||||
local success, reason = Runewords.socket(item, rune)
|
||||
if not success then
|
||||
self.log:add("cannot socket rune: " .. reason)
|
||||
return false
|
||||
end
|
||||
table.remove(self.player.inventory, runeIndex)
|
||||
local word = Runewords.activeWord(item)
|
||||
if word then
|
||||
self.log:add("the runes align into " .. word.name .. "! your " .. item.name .. " hums with power")
|
||||
else
|
||||
self.log:add("you socket a " .. rune.name .. " into your " .. item.name)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function Game:temperItem(target)
|
||||
local item = self:resolveGear(target)
|
||||
if not item then return false end
|
||||
local success, reason = Crafting.temper(self.player, item)
|
||||
if not success then
|
||||
self.log:add("cannot temper: " .. reason)
|
||||
return false
|
||||
end
|
||||
self.log:add("you temper your " .. item.name)
|
||||
return true
|
||||
end
|
||||
|
||||
function Game:enchantItem(target, affixKey)
|
||||
local item = self:resolveGear(target)
|
||||
if not item then return false end
|
||||
local success, reason = Crafting.enchant(self.player, item, affixKey)
|
||||
if not success then
|
||||
self.log:add("cannot enchant: " .. reason)
|
||||
return false
|
||||
end
|
||||
self.log:add("you enchant your " .. item.name)
|
||||
return true
|
||||
end
|
||||
|
||||
function Game:disenchantItem(target)
|
||||
local item = self:resolveGear(target)
|
||||
if not item then return false end
|
||||
local success, refund = Crafting.disenchant(self.player, item)
|
||||
if not success then
|
||||
self.log:add("cannot disenchant: " .. refund)
|
||||
return false
|
||||
end
|
||||
self.log:add(string.format("you disenchant %s for %d essence", item.name, refund))
|
||||
return true
|
||||
end
|
||||
|
||||
-- Combines the two carried herbs at indexA and indexB into whichever potion
|
||||
-- Alchemy.brew resolves the pair to. Both herbs are spent regardless of the
|
||||
-- outcome - a botched brew that curdles is not a free retry, and vents its
|
||||
-- fumes as a gas hazard right underfoot instead of handing back a potion.
|
||||
function Game:brewAt(indexA, indexB)
|
||||
if not self.level:cauldronAt(self.player.x, self.player.y) then
|
||||
self.log:add("there is no cauldron here")
|
||||
return false
|
||||
end
|
||||
if indexA == indexB then return false end
|
||||
local herbA, herbB = self.player.inventory[indexA], self.player.inventory[indexB]
|
||||
if not herbA or not herbB or herbA.kind ~= Items.HERB or herbB.kind ~= Items.HERB then
|
||||
self.log:add("you need two herbs to brew")
|
||||
return false
|
||||
end
|
||||
local hi, lo = math.max(indexA, indexB), math.min(indexA, indexB)
|
||||
table.remove(self.player.inventory, hi)
|
||||
table.remove(self.player.inventory, lo)
|
||||
local potion = Alchemy.brew(herbA.herb, herbB.herb)
|
||||
if not potion then
|
||||
Hazards.spawnGas(self.level, self.player.x, self.player.y, 3, 2)
|
||||
self.log:add(string.format("the %s and %s curdle into choking fumes", herbA.name, herbB.name))
|
||||
return false
|
||||
end
|
||||
Identify.reveal(self.identify, potion)
|
||||
self.player.inventory[#self.player.inventory + 1] = potion
|
||||
self.log:add("you brew " .. potion.name)
|
||||
return true
|
||||
end
|
||||
|
||||
-- Companions, hirelings and thralls are all player-owned allies (see
|
||||
-- Game:onActorDeath), never a valid melee target for the player's own bump-
|
||||
-- attack or autopilot targeting - both Game:movePlayer and Game:adjacentMonster
|
||||
-- below share this one check so neither can turn "walk into your own hound"
|
||||
-- into an accidental kill.
|
||||
local function isPlayerAlly(entity)
|
||||
return entity.kind == "companion" or entity.kind == "hireling" or entity.kind == "thrall"
|
||||
end
|
||||
|
||||
local CONFUSE_DIRS = {
|
||||
{ 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 }, { 1, 1 }, { 1, -1 }, { -1, 1 }, { -1, -1 },
|
||||
}
|
||||
|
||||
-- Whether `dx, dy` (the direction the player actually typed, or the one
|
||||
-- autopilot's own targeting picked) gets overridden by a random one of the
|
||||
-- 8 compass directions this call - the confusion-lands roll only ever fires
|
||||
-- once per Game:movePlayer, never re-rolled mid-call. Confusion strikes
|
||||
-- before Game:movePlayer even looks at what's in the chosen direction, so a
|
||||
-- stumble can just as easily open a door, bump a rival into a fight, shove a
|
||||
-- block, or walk the player straight onto a trap as a deliberate move would.
|
||||
local function confusionRedirect(self, dx, dy)
|
||||
if not Status.has(self.player, Status.CONFUSE) then return dx, dy, false end
|
||||
if not self.rng:chance(Status.CONFUSE_NUMERATOR, Status.CONFUSE_DENOMINATOR) then
|
||||
return dx, dy, false
|
||||
end
|
||||
local dir = CONFUSE_DIRS[self.rng:range(1, #CONFUSE_DIRS)]
|
||||
return dir[1], dir[2], true
|
||||
end
|
||||
|
||||
-- `stumbled` (below) is what makes a confused player's failed lurch still
|
||||
-- cost a turn: an ordinary bump into a wall or an unpushable block returns
|
||||
-- false and Game:manualStep spends nothing, exactly as a rejected typed
|
||||
-- command should, but a confused player who meant to go one way and staggered
|
||||
-- into a wall instead still acted - the turn is spent on the stumble itself,
|
||||
-- not on whatever (if anything) it accomplished.
|
||||
function Game:movePlayer(dx, dy)
|
||||
local stumbled
|
||||
dx, dy, stumbled = confusionRedirect(self, dx, dy)
|
||||
if stumbled then self.log:add("your senses reel and you stumble sideways") end
|
||||
local nx, ny = self.player.x + dx, self.player.y + dy
|
||||
local map = self.level.map
|
||||
if Tiles.isClosedDoor(map:get(nx, ny)) then
|
||||
return self:openDoor(nx, ny)
|
||||
end
|
||||
local target = self.level:entityAt(nx, ny)
|
||||
if target then
|
||||
if target and not isPlayerAlly(target) then
|
||||
self:playerAttack(target)
|
||||
return true
|
||||
end
|
||||
if not target then
|
||||
local block = self.level:blockAt(nx, ny)
|
||||
if block then
|
||||
local pushed, line = Blocks.push(self.level, block, dx, dy)
|
||||
if not pushed then return stumbled end
|
||||
self.log:add(line)
|
||||
Blocks.updateGates(self.level)
|
||||
end
|
||||
end
|
||||
if map:isWalkable(nx, ny) then
|
||||
if target then
|
||||
target.x, target.y = self.player.x, self.player.y
|
||||
self.log:add("you swap places with your " .. target.name)
|
||||
end
|
||||
self.player.x, self.player.y = nx, ny
|
||||
local trap = self.level:trapAt(nx, ny)
|
||||
if trap and not trap.revealed then self:triggerTrap(trap) end
|
||||
if trap and not trap.revealed and not Status.has(self.player, Status.LEVITATE) then
|
||||
self:triggerTrap(trap)
|
||||
end
|
||||
self:checkTerrainFor(self.player, dx, dy)
|
||||
local lever = self.level:vaultLeverAt(nx, ny)
|
||||
if lever then self:pullLever(lever) end
|
||||
self:pickupHere()
|
||||
return true
|
||||
end
|
||||
return false
|
||||
return stumbled
|
||||
end
|
||||
|
||||
function Game:useItem(index)
|
||||
@@ -1527,12 +1669,60 @@ function Game:useItem(index)
|
||||
Identify.reveal(self.identify, item)
|
||||
table.remove(self.player.inventory, index)
|
||||
elseif item.kind == Items.FOOD then
|
||||
self.player.satiation = self.player.satiation + item.nutrition
|
||||
self.log:add("you eat the " .. item.name)
|
||||
local bloated = Hunger.eat(self.player, item.nutrition)
|
||||
if bloated then
|
||||
self.log:add("you force down the " .. item.name .. " past comfort and feel bloated")
|
||||
else
|
||||
self.log:add("you eat the " .. item.name)
|
||||
end
|
||||
table.remove(self.player.inventory, index)
|
||||
elseif item.kind == Items.WEAPON or item.kind == Items.ARMOR or item.kind == Items.RING then
|
||||
elseif item.kind == Items.WEAPON or item.kind == Items.ARMOR or item.kind == Items.RING
|
||||
or item.kind == Items.BOW then
|
||||
local slot = slotByItemKind[item.kind]
|
||||
if not Curses.tryRelease(self.player, slot) then
|
||||
self.log:add("the " .. Curses.label(self.player[slot], Identify.label(self.identify, self.player[slot])) ..
|
||||
" will not come off!")
|
||||
return false
|
||||
end
|
||||
Items.useFromInventory(self.player, index)
|
||||
self.log:add("you equip " .. Identify.label(self.identify, item))
|
||||
self.log:add("you equip " .. Curses.label(item, Identify.label(self.identify, item)))
|
||||
if item.set then
|
||||
local count = Sets.equippedCounts(self.player)[item.set]
|
||||
local bonus = Sets.bonusFor(item.set, count)
|
||||
if bonus then
|
||||
self.log:add("the " .. Sets.byKey[item.set].name .. " set (" .. count .. " pieces) awakens")
|
||||
end
|
||||
end
|
||||
elseif item.kind == Items.TOME then
|
||||
if Spells.learn(self.player, item.spell) then
|
||||
self.log:add("you learn " .. Spells.catalog[item.spell].name)
|
||||
else
|
||||
self.log:add("you already know that spell")
|
||||
end
|
||||
table.remove(self.player.inventory, index)
|
||||
elseif item.kind == Items.CHARM then
|
||||
local companion = Companions.summon(self, item.companion)
|
||||
if not companion then return false end
|
||||
self.log:add("your " .. companion.name .. " bonds to you")
|
||||
table.remove(self.player.inventory, index)
|
||||
elseif item.kind == Items.CONTRACT then
|
||||
local hireling = Mercenaries.hire(self, item.hireling)
|
||||
if not hireling then return false end
|
||||
self.log:add("your " .. hireling.name .. " joins you for hire")
|
||||
table.remove(self.player.inventory, index)
|
||||
elseif item.kind == Items.KIT then
|
||||
if self.level:trapAt(self.player.x, self.player.y) then
|
||||
self.log:add("there is already a trap here")
|
||||
return false
|
||||
end
|
||||
self.level.traps[#self.level.traps + 1] = {
|
||||
x = self.player.x, y = self.player.y, effect = item.effect, revealed = false, placedByPlayer = true,
|
||||
}
|
||||
self.log:add("you plant a " .. item.name)
|
||||
table.remove(self.player.inventory, index)
|
||||
elseif item.kind == Items.PICK then
|
||||
Items.useFromInventory(self.player, index)
|
||||
self.log:add("you ready your " .. item.name)
|
||||
else
|
||||
return false
|
||||
end
|
||||
|
||||
Reference in new issue
Block a user