feat: add Streaks.new, Streaks.record and Terrain.slide
This commit is contained in:
1 parent
530042d48b
commit
b7a55eabed
3 files changed
+191
No files matched your search
@@ -0,0 +1,57 @@
|
|||||||
|
-- Win/loss streak tracking over meta.lua's own run history: not "how many
|
||||||
|
-- wins in total" (Meta.recordRun's own totalRuns/wins tally already answers
|
||||||
|
-- that) but how many of the *most recent* runs in a row ended the same
|
||||||
|
-- way - the number that actually swings the instant a lucky or unlucky run
|
||||||
|
-- lands, unlike a lifetime win rate that a hundred prior runs already numbs.
|
||||||
|
|
||||||
|
local Streaks = {}
|
||||||
|
|
||||||
|
Streaks.HISTORY_CAP = 50
|
||||||
|
|
||||||
|
function Streaks.new()
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Appends `won` (a boolean) as the newest entry, trimming the oldest once
|
||||||
|
-- history grows past Streaks.HISTORY_CAP - a streak only ever needs to look
|
||||||
|
-- back so far, and an unbounded history would grow meta.lua's save file
|
||||||
|
-- forever for no further benefit.
|
||||||
|
function Streaks.record(history, won)
|
||||||
|
history[#history + 1] = won
|
||||||
|
while #history > Streaks.HISTORY_CAP do
|
||||||
|
table.remove(history, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Length of the run of identical outcomes ending at the most recent entry,
|
||||||
|
-- and which outcome that is - (0, nil) for an empty history. A single win
|
||||||
|
-- right after a loss reads as a streak of exactly 1, not 0: the run just
|
||||||
|
-- recorded is itself part of its own streak.
|
||||||
|
function Streaks.current(history)
|
||||||
|
if #history == 0 then return 0, nil end
|
||||||
|
local outcome = history[#history]
|
||||||
|
local length = 0
|
||||||
|
for i = #history, 1, -1 do
|
||||||
|
if history[i] ~= outcome then break end
|
||||||
|
length = length + 1
|
||||||
|
end
|
||||||
|
return length, outcome
|
||||||
|
end
|
||||||
|
|
||||||
|
-- The longest run of consecutive wins anywhere in history, not just the one
|
||||||
|
-- still active - a player's all-time best winning streak survives even
|
||||||
|
-- after it eventually breaks.
|
||||||
|
function Streaks.bestWinStreak(history)
|
||||||
|
local best, running = 0, 0
|
||||||
|
for _, won in ipairs(history) do
|
||||||
|
if won then
|
||||||
|
running = running + 1
|
||||||
|
if running > best then best = running end
|
||||||
|
else
|
||||||
|
running = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return best
|
||||||
|
end
|
||||||
|
|
||||||
|
return Streaks
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
-- Permanent map terrain that punishes careless movement, as opposed to
|
||||||
|
-- hazards.lua's fire and gas which are timed and spawned mid-game. Lava
|
||||||
|
-- scorches whoever stands on it every time they step there, a chasm drops
|
||||||
|
-- the player to the floor below (or swallows a monster outright), and ice
|
||||||
|
-- keeps sliding a traveller in the direction they started moving until
|
||||||
|
-- something stops them. None of it spreads, fades, or needs saving beyond
|
||||||
|
-- the glyph already baked into the map by the builder.
|
||||||
|
|
||||||
|
local Tiles = require("tiles")
|
||||||
|
local Status = require("status")
|
||||||
|
local Resistances = require("resistances")
|
||||||
|
|
||||||
|
local Terrain = {}
|
||||||
|
|
||||||
|
Terrain.LAVA_DAMAGE = 8
|
||||||
|
|
||||||
|
-- Slides entity through consecutive ice tiles in direction (dx, dy), from
|
||||||
|
-- wherever it just came to rest. Stops the instant the current tile isn't
|
||||||
|
-- ice, the next cell is unwalkable, or another actor already occupies it -
|
||||||
|
-- so ice never carries a traveller through a wall or on top of someone else.
|
||||||
|
function Terrain.slide(level, entity, dx, dy)
|
||||||
|
local map = level.map
|
||||||
|
while map:get(entity.x, entity.y) == Tiles.ICE do
|
||||||
|
local nx, ny = entity.x + dx, entity.y + dy
|
||||||
|
if not map:isWalkable(nx, ny) then break end
|
||||||
|
if level:entityAt(nx, ny) then break end
|
||||||
|
entity.x, entity.y = nx, ny
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Applies lava's burn to whoever is currently standing on it. Returns a log
|
||||||
|
-- line, or nil when the tile underfoot isn't lava.
|
||||||
|
function Terrain.afflictLava(level, entity)
|
||||||
|
if level.map:get(entity.x, entity.y) ~= Tiles.LAVA then return nil end
|
||||||
|
local dealt = Resistances.apply(entity, Terrain.LAVA_DAMAGE, Resistances.FIRE)
|
||||||
|
entity.hp = entity.hp - dealt
|
||||||
|
if dealt <= 0 then
|
||||||
|
return entity.kind == "player" and "you wade through the lava unharmed"
|
||||||
|
or string.format("%s wades through the lava unharmed", entity.name)
|
||||||
|
end
|
||||||
|
Status.apply(entity, Status.BURNING, 2, math.max(1, dealt // 3))
|
||||||
|
return string.format("molten rock sears %s for %d", entity.name, dealt)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Terrain.isChasm(level, entity)
|
||||||
|
return level.map:get(entity.x, entity.y) == Tiles.CHASM
|
||||||
|
end
|
||||||
|
|
||||||
|
return Terrain
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
-- Player ranged option that costs no mana: any spare weapon or potion sitting
|
||||||
|
-- in the inventory (not the one currently wielded) can be hurled at a monster
|
||||||
|
-- in range and line of sight. A thrown weapon hits harder than a melee swing
|
||||||
|
-- and lands where it struck, ready to be picked back up; a thrown potion
|
||||||
|
-- shatters and visits its effect on the target instead of the thrower, which
|
||||||
|
-- only makes sense for potions with a hostile reading.
|
||||||
|
|
||||||
|
local Los = require("los")
|
||||||
|
local Status = require("status")
|
||||||
|
local Items = require("items")
|
||||||
|
local Hazards = require("hazards")
|
||||||
|
local Resistances = require("resistances")
|
||||||
|
|
||||||
|
local Throwing = {}
|
||||||
|
|
||||||
|
Throwing.RANGE = 6
|
||||||
|
local POWER_MULT = 2
|
||||||
|
|
||||||
|
-- Hostile reading of each potion effect. Potions with no meaningful effect on
|
||||||
|
-- an enemy (healing, strength) just shatter harmlessly rather than helping
|
||||||
|
-- whoever they were thrown at.
|
||||||
|
local hostileEffects = {
|
||||||
|
poison = function(game, target)
|
||||||
|
Status.apply(target, Status.POISON, 6, 3)
|
||||||
|
Hazards.spawnGas(game.level, target.x, target.y, 4, 2)
|
||||||
|
return string.format("the potion shatters, poisoning %s in a cloud of fumes", target.name)
|
||||||
|
end,
|
||||||
|
haste = function(_, target)
|
||||||
|
Status.apply(target, Status.HASTE, 10, 1)
|
||||||
|
return string.format("the potion shatters, and %s bolts away", target.name)
|
||||||
|
end,
|
||||||
|
confuse = function(_, target)
|
||||||
|
Status.apply(target, Status.CONFUSE, 6, 1)
|
||||||
|
return string.format("the potion shatters, and %s reels in confusion", target.name)
|
||||||
|
end,
|
||||||
|
blind = function(_, target)
|
||||||
|
Status.apply(target, Status.BLIND, 6, 1)
|
||||||
|
return string.format("the potion shatters in a blinding flash, and %s is struck blind", target.name)
|
||||||
|
end,
|
||||||
|
-- Same hostile reading as haste above: lifting a dangerous target off the
|
||||||
|
-- ground lets it drift clear of whatever lava, chasm, or trap the thrower
|
||||||
|
-- was counting on to block or punish it (see status.lua's Status.LEVITATE),
|
||||||
|
-- so handing it to an enemy is a liability, not a gift.
|
||||||
|
levitate = function(_, target)
|
||||||
|
Status.apply(target, Status.LEVITATE, 10, 1)
|
||||||
|
return string.format("the potion shatters, and %s rises off the ground", target.name)
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
function Throwing.canThrow(item)
|
||||||
|
return item.kind == Items.WEAPON or item.kind == Items.POTION
|
||||||
|
end
|
||||||
|
|
||||||
|
function Throwing.canReach(game, x1, y1, x2, y2)
|
||||||
|
local dist = math.max(math.abs(x1 - x2), math.abs(y1 - y2))
|
||||||
|
return dist <= Throwing.RANGE and Los.clear(game.level.map, x1, y1, x2, y2)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Resolves a thrown item against a target. Returns (true, lines) if the item
|
||||||
|
-- left the thrower's hand - whether or not it did anything useful on arrival
|
||||||
|
-- - or (false, lines) if the throw never happened at all.
|
||||||
|
function Throwing.resolve(game, thrower, item, target)
|
||||||
|
if not Throwing.canThrow(item) then
|
||||||
|
return false, { "that can't be thrown" }
|
||||||
|
end
|
||||||
|
if not target or not target:isAlive() then
|
||||||
|
return false, { "no target" }
|
||||||
|
end
|
||||||
|
if not Throwing.canReach(game, thrower.x, thrower.y, target.x, target.y) then
|
||||||
|
return false, { "you have no clear throw" }
|
||||||
|
end
|
||||||
|
if item.kind == Items.WEAPON then
|
||||||
|
local dmg = Resistances.apply(target, item.power * POWER_MULT, item.damageType or Resistances.PHYSICAL)
|
||||||
|
target.hp = target.hp - dmg
|
||||||
|
local lines = { string.format("your thrown %s hits %s for %d", item.name, target.name, dmg) }
|
||||||
|
game.level.floorItems[#game.level.floorItems + 1] = { x = target.x, y = target.y, item = item }
|
||||||
|
if not target:isAlive() then game:awardKill(target) end
|
||||||
|
return true, lines
|
||||||
|
end
|
||||||
|
local handler = hostileEffects[item.effect]
|
||||||
|
local line = handler and handler(game, target) or "the potion shatters harmlessly"
|
||||||
|
return true, { line }
|
||||||
|
end
|
||||||
|
|
||||||
|
return Throwing
|
||||||
Reference in new issue
Block a user