565 lines
14 KiB
Lua
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>
|
|
|