Files
Lucksmith/lucksmith.lua
T
2025-10-07 13:41:50 +13:00

565 lines
14 KiB
Lua

-- title: Lucksmith
-- author: DigNZ
-- desc: card + dice roguelike prototype
-- script: lua
-- === GAME STATE ===
player = {
hp = 10,
roll_mod = 0,
mult = 1,
counter = false
}
enemies = {{
hp = 8,
roll_mod = 0,
mult = 1,
counter = false
},
{
hp = 10,
roll_mod = 0,
mult = 1,
counter = false
}, {
hp = 12,
roll_mod = 0,
mult = 1,
counter = false
}}
level = 1
function get_enemy(level)
return {
hp = enemies[level].hp,
roll_mod = enemies[level].roll_mod,
mult = enemies[level].mult,
counter = enemies[level].counter
}
end
enemy = get_enemy(level)
msg = ""
-- State
phase = "choose"
phase_time = 0
roll_text = ""
damage_applied = false
pending_effects = {
player = {},
enemy = {}
}
-- animation + shake
player.display_roll = 0
enemy.display_roll = 0
shake = 0
d = 2 -- shake intensity
-- basic card pool
cards = {{
name = "Str",
desc = "+2 to roll",
timing = "pre_roll",
use = function(p, e)
p.roll_mod = p.roll_mod + 2
end
}, {
name = "Hex",
desc = "-2 enemy roll",
timing = "pre_roll",
use = function(p, e)
e.roll_mod = e.roll_mod - 2
end
}, {
name = "2x",
desc = "win/lose double dmg",
timing = "pre_roll",
use = function(p, e)
p.mult = 2
end
}, {
name = "0.5x",
desc = "win/lose half dmg",
timing = "pre_roll",
use = function(p, e)
p.mult = 0.5
end
}, {
name = "Heal",
desc = "+2 hp",
timing = "immediate",
use = function(p, e)
p.hp = math.min(10, p.hp + 2)
end
}, {
name = "Counter",
desc = "hit enemy for 1 if you lose",
timing = "post_result",
use = function(p, e, player_won)
-- player_won is from player's perspective when called on player cards
if player_won == false then -- player lost
e.hp = e.hp - 1
end
end
}, {
name = "Weaken",
desc = "-1 enemy dmg",
timing = "pre_roll",
use = function(p, e)
e.mult = math.max(0.5, e.mult - 0.5)
end
}, {
name = "Rage",
desc = "+1 your dmg",
timing = "pre_roll",
use = function(p, e)
p.mult = p.mult + 0.5
end
}, {
name = "Tier",
desc = "if tie, you win",
timing = "post_result",
use = function(p, e, player_won)
if player_won == nil then -- tie occurred
-- Player wins the tie, so enemy takes damage
e.hp = e.hp - (2 * p.mult)
end
end
}, {
name = "Shield",
desc = "take 1 less dmg this turn",
timing = "pre_roll",
use = function(p, e)
p.shield = (p.shield or 0) + 1
end
}, {
name = "Poison",
desc = "enemy loses 1 hp at turn end",
timing = "post_result",
use = function(p, e, player_won)
e.hp = e.hp - 1
end
}, {
name = "Spike",
desc = "deal 1 dmg to whoever wins",
timing = "post_result",
use = function(p, e, player_won)
if player_won == true then
p.hp = p.hp - 1 -- hurt yourself if you win
elseif player_won == false then
e.hp = e.hp - 1 -- hurt enemy if they win
end
-- ties do nothing
end
}, {
name = "Lucky",
desc = "reroll if you roll 1-3",
timing = "post_roll", -- New timing needed
use = function(p, e)
if p.roll <= 3 then
p.roll = math.random(1, 6)
p.total = p.roll + p.roll_mod
end
end
}, {
name = "Curse",
desc = "enemy rerolls if they roll 4-6",
timing = "post_roll",
use = function(p, e)
if e.roll >= 4 then
e.roll = math.random(1, 6)
e.total = e.roll + e.roll_mod
end
end
}, {
name = "Swap",
desc = "switch your roll with enemy's",
timing = "post_roll",
use = function(p, e)
local temp = p.roll
p.roll = e.roll
e.roll = temp
p.total = p.roll + p.roll_mod
e.total = e.roll + e.roll_mod
end
}, {
name = "Mirror",
desc = "copy enemy's roll modifier",
timing = "pre_roll",
use = function(p, e)
p.roll_mod = p.roll_mod + e.roll_mod
end
}, {
name = "Drain",
desc = "heal 1 if you win",
timing = "post_result",
use = function(p, e, player_won)
if player_won == true then
p.hp = math.min(10, p.hp + 1)
end
end
}, {
name = "Gamble",
desc = "50% chance: +3 roll or -1 roll",
timing = "pre_roll",
use = function(p, e)
if math.random() < 0.5 then
p.roll_mod = p.roll_mod + 3
else
p.roll_mod = p.roll_mod - 1
end
end
}, {
name = "Fury",
desc = "+1 dmg per hp you're missing",
timing = "pre_roll",
use = function(p, e)
local missing_hp = 10 - p.hp
p.mult = p.mult + (missing_hp * 0.1)
end
}, {
name = "Turtle",
desc = "set your roll to 3",
timing = "post_roll",
use = function(p, e)
p.roll = 3
p.total = p.roll + p.roll_mod
end
}, {
name = "Berserk",
desc = "2x dmg but take 1 dmg",
timing = "immediate",
use = function(p, e)
p.mult = p.mult * 2
p.hp = p.hp - 1
end
}}
enemy_cards = {{
name = "Str",
desc = "+2 to roll",
timing = "pre_roll",
use = function(p, e)
e.roll_mod = e.roll_mod + 2
end
}, {
name = "Hex",
desc = "-2 enemy roll",
timing = "pre_roll",
use = function(p, e)
p.roll_mod = p.roll_mod - 2
end
}, {
name = "2x",
desc = "win/lose double dmg",
timing = "pre_roll",
use = function(p, e)
e.mult = 2
end
}, {
name = "0.5x",
desc = "win/lose half dmg",
timing = "pre_roll",
use = function(p, e)
e.mult = 0.5
end
}, {
name = "Heal",
desc = "+2 hp",
timing = "immediate",
use = function(p, e)
e.hp = math.min(10, e.hp + 2)
end
}, {
name = "Counter",
desc = "hit enemy for 1 if you lose",
timing = "post_result",
use = function(p, e, player_won)
-- player_won is from player's perspective, so enemy perspective is flipped
if player_won == true then -- enemy lost (player won)
p.hp = p.hp - 1
end
end
}, {
name = "Weaken",
desc = "-1 enemy dmg",
timing = "pre_roll",
use = function(p, e)
p.mult = math.max(0.5, p.mult - 0.5)
end
}, {
name = "Rage",
desc = "+1 your dmg",
timing = "pre_roll",
use = function(p, e)
e.mult = e.mult + 0.5
end
}, {
name = "Tier",
desc = "if tie, you win",
timing = "post_result",
use = function(p, e, player_won)
if player_won == nil then -- tie occurred
-- Enemy wins the tie, so player takes damage
p.hp = p.hp - (2 * e.mult)
end
end
}}
-- player's starting hand (random subset)
hand = {}
function draw_hand()
hand = {}
for i = 1, 3 do
table.insert(hand, cards[math.random(#cards)])
end
end
draw_hand()
-- === CORE TURN LOGIC ===
function apply_effects(timing, player_won)
for _, effect in ipairs(pending_effects.player) do
if effect.timing == timing then
if timing == "post_result" then
effect.use(player, enemy, player_won)
else
effect.use(player, enemy)
end
end
end
for _, effect in ipairs(pending_effects.enemy) do
if effect.timing == timing then
if timing == "post_result" then
effect.use(player, enemy, player_won)
else
effect.use(player, enemy)
end
end
end
end
function start_turn(card)
current_card = card
enemy_card = enemy_cards[math.random(#enemy_cards)]
-- Store effects for proper timing
pending_effects.player = {card}
pending_effects.enemy = {enemy_card}
-- Apply immediate effects
apply_effects("immediate")
-- Apply pre-roll effects
apply_effects("pre_roll")
-- Now roll dice
player.roll = math.random(1, 6)
enemy.roll = math.random(1, 6)
apply_effects("post_roll")
player.total = player.roll + player.roll_mod
enemy.total = enemy.roll + enemy.roll_mod
player.display_roll = 0
enemy.display_roll = 0
phase = "roll"
phase_time = 0
end
picked = 1
-- === INPUT ===
function TIC()
cls(1)
if shake > 0 then
poke(0x3FF9, math.random(-d, d))
poke(0x3FF9 + 1, math.random(-d, d))
shake = shake - 1
if shake == 0 then
memset(0x3FF9, 0, 2)
end
end
-- camera(dx, dy)
-- Draw HUD
print("Level: " .. level, 100, 4, 12)
print("PLAYER HP: " .. player.hp, 4, 4, 12)
print("ENEMY HP: " .. enemy.hp, 4, 14, 8)
if phase == "choose" then
-- Card Picker
if btnp(2) then
picked = picked - 1
end
if btnp(3) then
picked = picked + 1
end
picked = math.min(3, math.max(1, picked))
-- Show cards
for i, card in ipairs(hand) do
local x = 10 + (i - 1) * 64
rectb(x, 80, 62, 24, i == picked and 1 or 12)
print(card.name, x + 3, 90, i == picked and 11 or 10, false,1,true)
end
print(hand[picked].desc, 4, 120, 15)
print(msg, 4, 40, 15)
if btnp(4) then
start_turn(hand[picked])
end
elseif phase == "roll" then
phase_time = phase_time + 1
-- flicker numbers every few frames
if phase_time % 3 == 0 then
player.display_roll = math.random(1, 6)
enemy.display_roll = math.random(1, 6)
end
-- lock in final rolls after 30 frames
if phase_time > 30 then
player.display_roll = player.roll
enemy.display_roll = enemy.roll
phase = "mod"
phase_time = 0
end
card_text = "You played " .. current_card.name
enemy_card_text = "Enemy played " .. enemy_card.name
print(card_text, 4, 30, 14)
print(enemy_card_text, 4, 20, 8)
-- show rolls
roll_text = "You roll " .. player.display_roll .. " / Enemy rolls " .. enemy.display_roll
print(roll_text, 4, 40, 12)
elseif phase == "mod" then
print(card_text, 4, 30, 14)
print(enemy_card_text, 4, 20, 8)
print(roll_text, 4, 40, 12)
mod_text = "mods applied: +" .. player.roll_mod .. " / " .. enemy.roll_mod
print(mod_text, 4, 50, 10)
phase_time = phase_time + 1
if phase_time > 60 then
phase = "total"
phase_time = 0
end
elseif phase == "total" then
print(card_text, 4, 30, 14)
print(enemy_card_text, 4, 20, 8)
print(roll_text, 4, 40, 12)
print(mod_text, 4, 50, 12)
print("Total " .. player.total .. " / " .. enemy.total, 4, 60, 10)
phase_time = phase_time + 1
if phase_time > 120 then
phase = "result"
phase_time = 0
end
elseif phase == "result" then
print(card_text, 4, 30, 14)
print(enemy_card_text, 4, 20, 8)
print(roll_text, 4, 40, 12)
print(mod_text, 4, 50, 12)
print("Total " .. player.total .. " / " .. enemy.total, 4, 60, 10)
local msgtxt = ""
local player_won = nil
if player.total > enemy.total then
player_won = true
local dmg = 2 * player.mult
if not damage_applied then
enemy.hp = enemy.hp - dmg
end
msgtxt = "You win! Enemy -" .. dmg
elseif enemy.total > player.total then
player_won = false
local dmg = 2 * enemy.mult
if not damage_applied then
-- Apply shield reduction
local shield_reduction = player.shield or 0
dmg = math.max(0, dmg - shield_reduction)
player.hp = player.hp - dmg
shake = 10
end
msgtxt = "Enemy wins! You -" .. dmg
else
msgtxt = "Tie!"
end
-- Apply post-result effects (like Counter)
if not damage_applied then
apply_effects("post_result", player_won)
end
damage_applied = true
print(msgtxt, 4, 70, 14)
if btnp(4) then
-- reset mods & back to choose
player.roll_mod, enemy.roll_mod = 0, 0
player.mult, enemy.mult = 1, 1
player.counter, enemy.counter = false, false
player.shield, enemy.shield = 0, 0
draw_hand()
phase = (enemy.hp <= 0 or player.hp <= 0) and "game_over" or "choose"
phase_time = 0
damage_applied = false
end
elseif phase == "game_over" then
if enemy.hp < player.hp then
print("You Win", 4, 60, 12)
else
print("You Lose", 4, 60, 14)
end
if btnp(5) then
if enemy.hp < player.hp then
level = math.min(level + 1, #enemies)
else
level = 1
end
player = {
hp = 10,
roll_mod = 0,
mult = 1,
counter = false
}
enemy = get_enemy(level)
msg = ""
-- State
phase = "choose"
phase_time = 0
roll_text = ""
damage_applied = false
end
end
-- camera(0,0)
end
-- <TILES>
-- 001:eccccccccc888888caaaaaaaca888888cacccccccacc0ccccacc0ccccacc0ccc
-- 002:ccccceee8888cceeaaaa0cee888a0ceeccca0ccc0cca0c0c0cca0c0c0cca0c0c
-- 003:eccccccccc888888caaaaaaaca888888cacccccccacccccccacc0ccccacc0ccc
-- 004:ccccceee8888cceeaaaa0cee888a0ceeccca0cccccca0c0c0cca0c0c0cca0c0c
-- 017:cacccccccaaaaaaacaaacaaacaaaaccccaaaaaaac8888888cc000cccecccccec
-- 018:ccca00ccaaaa0ccecaaa0ceeaaaa0ceeaaaa0cee8888ccee000cceeecccceeee
-- 019:cacccccccaaaaaaacaaacaaacaaaaccccaaaaaaac8888888cc000cccecccccec
-- 020:ccca00ccaaaa0ccecaaa0ceeaaaa0ceeaaaa0cee8888ccee000cceeecccceeee
-- </TILES>
-- <WAVES>
-- 000:00000000ffffffff00000000ffffffff
-- 001:0123456789abcdeffedcba9876543210
-- 002:0123456789abcdef0123456789abcdef
-- </WAVES>
-- <SFX>
-- 000:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000304000000000
-- </SFX>
-- <TRACKS>
-- 000:100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- </TRACKS>
-- <PALETTE>
-- 000:1a1c2c5d275db13e53ef7d57ffcd75a7f07038b76425717929366f3b5dc941a6f673eff7f4f4f494b0c2566c86333c57
-- </PALETTE>