1012 lines
29 KiB
Lua
1012 lines
29 KiB
Lua
-- title: Lucksmith
|
|
-- author: DigNZ
|
|
-- desc: card + dice roguelike prototype
|
|
-- script: lua
|
|
-- === GAME STATE ===
|
|
player = {
|
|
hp = 10,
|
|
roll_mod = 0,
|
|
mult = 1,
|
|
energy = 0
|
|
}
|
|
enemies = {{
|
|
name = "Goblin",
|
|
hp = 2,
|
|
roll_mod = 0,
|
|
mult = 1
|
|
}, {
|
|
name = "Orc",
|
|
hp = 10,
|
|
roll_mod = 0,
|
|
mult = 1
|
|
}, {
|
|
name = "Troll",
|
|
hp = 12,
|
|
roll_mod = 0,
|
|
mult = 1
|
|
}}
|
|
level = 1
|
|
function get_enemy(level)
|
|
return {
|
|
name = enemies[level].name,
|
|
hp = enemies[level].hp,
|
|
max_hp = enemies[level].hp, -- store original max HP
|
|
roll_mod = enemies[level].roll_mod,
|
|
mult = enemies[level].mult
|
|
}
|
|
end
|
|
enemy = get_enemy(level)
|
|
msg = ""
|
|
|
|
-- State
|
|
phase = "choose"
|
|
phase_time = 0
|
|
roll_text = ""
|
|
damage_applied = false
|
|
current_damage = {player = 0, enemy = 0} -- store damage for display
|
|
pending_effects = {
|
|
player = {},
|
|
enemy = {}
|
|
}
|
|
-- animation + shake
|
|
player.display_roll = 0
|
|
enemy.display_roll = 0
|
|
shake = 0
|
|
d = 2 -- shake intensity
|
|
flash = 0
|
|
|
|
original_palette = {}
|
|
for i = 0, 15 do
|
|
original_palette[i] = peek(0x3FC0 + i)
|
|
end
|
|
|
|
-- Deck system
|
|
draw_pile = {}
|
|
discard_pile = {}
|
|
unlocked_cards = {} -- tracks which cards are in your deck
|
|
|
|
-- basic card pool
|
|
cards = {{
|
|
name = "Strength",
|
|
desc = "+2 to roll",
|
|
timing = "pre_roll",
|
|
energy = -1, -- costs 1 energy
|
|
use = function(p, e)
|
|
p.roll_mod = p.roll_mod + 2
|
|
end
|
|
}, {
|
|
name = "Hex",
|
|
desc = "-2 enemy roll",
|
|
timing = "pre_roll",
|
|
energy = -1, -- costs 1 energy
|
|
use = function(p, e)
|
|
e.roll_mod = e.roll_mod - 2
|
|
end
|
|
}, {
|
|
name = "Reckless",
|
|
desc = "win/lose double dmg",
|
|
timing = "pre_roll",
|
|
energy = -1, -- reduced from -2
|
|
use = function(p, e)
|
|
p.mult = p.mult * 2
|
|
e.mult = e.mult * 2
|
|
end
|
|
}, {
|
|
name = "Cautious",
|
|
desc = "win/lose half dmg",
|
|
timing = "pre_roll",
|
|
energy = 0, -- reduced from +1 since we get +1 per turn
|
|
use = function(p, e)
|
|
p.mult = p.mult * 0.5
|
|
e.mult = e.mult * 0.5
|
|
end
|
|
}, {
|
|
name = "Heal",
|
|
desc = "+2 hp",
|
|
timing = "immediate",
|
|
energy = -1, -- costs 1 energy
|
|
use = function(p, e)
|
|
p.hp = math.min(10, p.hp + 2)
|
|
current_damage.player = current_damage.player - 2 -- show damage taken
|
|
end
|
|
}, {
|
|
name = "Counter",
|
|
desc = "hit enemy for 1 if you lose",
|
|
timing = "post_result",
|
|
energy = 0, -- reduced from -1
|
|
use = function(p, e, player_won)
|
|
if player_won == false then -- fixed: should trigger when player loses
|
|
e.hp = e.hp - 1
|
|
current_damage.enemy = current_damage.enemy + 1 -- show damage taken
|
|
end
|
|
end
|
|
}, {
|
|
name = "Weaken",
|
|
desc = "-1 enemy dmg",
|
|
timing = "pre_roll",
|
|
energy = 0, -- reduced from -1
|
|
use = function(p, e)
|
|
e.weaken = (e.weaken or 0) + 1 -- enemy is weakened (does less damage)
|
|
end
|
|
}, {
|
|
name = "Rage",
|
|
desc = "+1 your dmg",
|
|
timing = "pre_roll", -- changed from post_result for clarity
|
|
energy = -1, -- reduced from -1
|
|
use = function(p, e)
|
|
p.rage = (p.rage or 0) + 1 -- add rage bonus
|
|
end
|
|
}, {
|
|
name = "Tier",
|
|
desc = "if tie, you win",
|
|
timing = "post_result",
|
|
energy = -1, -- reduced from -2
|
|
use = function(p, e, player_won)
|
|
if player_won == nil then -- tie occurred
|
|
e.hp = e.hp - (2 * p.mult)
|
|
end
|
|
end
|
|
}, {
|
|
name = "Shield",
|
|
desc = "take 1 less dmg until damaged",
|
|
timing = "immediate",
|
|
energy = -1, -- increased from 0
|
|
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",
|
|
energy = 0, -- reduced from -1
|
|
use = function(p, e, player_won)
|
|
e.hp = e.hp - 1
|
|
end
|
|
}, {
|
|
name = "Spike",
|
|
desc = "deal 1 dmg to whoever wins",
|
|
timing = "post_result",
|
|
energy = 1, -- now gives energy since it's risky
|
|
use = function(p, e, player_won)
|
|
if player_won == true then
|
|
p.hp = p.hp - 1 -- hurt yourself if you win
|
|
current_damage.player = current_damage.player + 1 -- show damage taken
|
|
elseif player_won == false then
|
|
e.hp = e.hp - 1 -- hurt enemy if they win
|
|
current_damage.enemy = current_damage.enemy + 1 -- show damage taken
|
|
end
|
|
-- ties do nothing
|
|
end
|
|
}, {
|
|
name = "Lucky",
|
|
desc = "reroll if you roll 1-3",
|
|
timing = "post_roll",
|
|
energy = -1, -- reduced from -2
|
|
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",
|
|
energy = -1, -- reduced from -2
|
|
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 = "Intuition",
|
|
desc = "Pick the higher roll",
|
|
timing = "post_roll",
|
|
energy = -3, -- reduced from -3
|
|
use = function(p, e)
|
|
if p.roll >= e.roll then
|
|
return -- no change needed
|
|
end
|
|
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",
|
|
energy = -1, -- reduced from -1
|
|
use = function(p, e)
|
|
p.roll_mod = p.roll_mod + e.roll_mod
|
|
end
|
|
}, {
|
|
name = "Double Vision",
|
|
desc = "copy enemy's multiplier",
|
|
timing = "post_roll",
|
|
energy = -1, -- reduced from -2
|
|
use = function(p, e)
|
|
p.mult = e.mult
|
|
end
|
|
}, {
|
|
name = "Drain",
|
|
desc = "heal 1 if you win",
|
|
timing = "post_result",
|
|
energy = 0, -- reduced from -1
|
|
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",
|
|
energy = 1, -- now gives energy since it's risky
|
|
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 = "+0.5 dmg per hp missing",
|
|
timing = "pre_roll",
|
|
energy = -1, -- reduced from -1
|
|
use = function(p, e)
|
|
local missing_hp = 10 - p.hp
|
|
p.mult = p.mult + (missing_hp * 0.5) -- increased effect
|
|
end
|
|
}, {
|
|
name = "Turtle",
|
|
desc = "set your roll to 3",
|
|
timing = "post_roll",
|
|
energy = 2, -- increased from 1
|
|
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",
|
|
energy = -1, -- reduced from -2
|
|
use = function(p, e)
|
|
p.mult = p.mult * 2
|
|
p.hp = p.hp - 1
|
|
end
|
|
}, {
|
|
name = "Focus",
|
|
desc = "+2 energy",
|
|
timing = "immediate",
|
|
energy = 2, -- gives 2 energy (net +3 with the +1 per turn)
|
|
use = function(p, e)
|
|
-- Energy gain handled automatically
|
|
end
|
|
}, {
|
|
name = "Rest",
|
|
desc = "+1 energy, +1 hp",
|
|
timing = "immediate",
|
|
energy = 1, -- gives 1 energy (net +2 with the +1 per turn)
|
|
use = function(p, e)
|
|
p.hp = math.min(10, p.hp + 1)
|
|
end
|
|
}}
|
|
|
|
enemy_cards = {{
|
|
name = "Str",
|
|
desc = "+2 to roll",
|
|
timing = "pre_roll",
|
|
energy = -1,
|
|
use = function(p, e)
|
|
e.roll_mod = e.roll_mod + 2
|
|
end
|
|
}, {
|
|
name = "Hex",
|
|
desc = "-2 enemy roll",
|
|
timing = "pre_roll",
|
|
energy = -2,
|
|
use = function(p, e)
|
|
p.roll_mod = p.roll_mod - 2
|
|
end
|
|
}, {
|
|
name = "2x",
|
|
desc = "win/lose double dmg",
|
|
timing = "pre_roll",
|
|
energy = -2,
|
|
use = function(p, e)
|
|
e.mult = e.mult * 2
|
|
p.mult = p.mult * 2
|
|
end
|
|
}, {
|
|
name = "0.5x",
|
|
desc = "win/lose half dmg",
|
|
timing = "pre_roll",
|
|
energy = 1,
|
|
use = function(p, e)
|
|
e.mult = e.mult * 0.5
|
|
p.mult = p.mult * 0.5
|
|
end
|
|
}, {
|
|
name = "Heal",
|
|
desc = "+2 hp",
|
|
timing = "immediate",
|
|
energy = -1,
|
|
use = function(p, e)
|
|
e.hp = math.min(e.max_hp, e.hp + 2)
|
|
current_damage.enemy = current_damage.enemy - 2 -- show damage taken
|
|
end
|
|
}, {
|
|
name = "Counter",
|
|
desc = "hit enemy for 1 if you lose",
|
|
timing = "post_result",
|
|
energy = -1,
|
|
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
|
|
current_damage.player = current_damage.player + 1 -- show damage taken
|
|
end
|
|
end
|
|
}, {
|
|
name = "Weaken",
|
|
desc = "-1 enemy dmg",
|
|
timing = "pre_roll",
|
|
energy = -1,
|
|
use = function(p, e)
|
|
p.weaken = (p.weaken or 0) + 1 -- player is weakened (does less damage)
|
|
end
|
|
}, {
|
|
name = "Rage",
|
|
desc = "+1 your dmg",
|
|
timing = "pre_roll",
|
|
energy = -1,
|
|
use = function(p, e)
|
|
e.rage = (e.rage or 0) + 1 -- add rage bonus for enemy
|
|
end
|
|
}, {
|
|
name = "Tier",
|
|
desc = "if tie, you win",
|
|
timing = "post_result",
|
|
energy = -2,
|
|
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 = {}
|
|
card_choice_options = {} -- for level up card selection
|
|
|
|
function shuffle_deck(deck)
|
|
for i = #deck, 2, -1 do
|
|
local j = math.random(i)
|
|
deck[i], deck[j] = deck[j], deck[i]
|
|
end
|
|
end
|
|
|
|
function initialize_deck()
|
|
draw_pile = {}
|
|
discard_pile = {}
|
|
|
|
-- Start with first 8 cards for level 1, or use unlocked_cards for higher levels
|
|
local cards_to_add = {}
|
|
if level == 1 then
|
|
unlocked_cards = {}
|
|
for i = 1, 8 do
|
|
unlocked_cards[i] = true
|
|
cards_to_add = {1, 2, 3, 4, 5, 6, 7, 8}
|
|
end
|
|
end
|
|
|
|
-- Add 2 copies of each unlocked card to deck
|
|
for i, card in ipairs(cards) do
|
|
if unlocked_cards[i] then
|
|
table.insert(draw_pile, card)
|
|
table.insert(draw_pile, card)
|
|
end
|
|
end
|
|
|
|
shuffle_deck(draw_pile)
|
|
end
|
|
|
|
function generate_card_choices()
|
|
card_choice_options = {}
|
|
local available_cards = {}
|
|
|
|
-- Find cards not yet unlocked
|
|
for i, card in ipairs(cards) do
|
|
if not unlocked_cards[i] then
|
|
table.insert(available_cards, i)
|
|
end
|
|
end
|
|
|
|
-- If we have available cards, pick 3 random ones
|
|
if #available_cards > 0 then
|
|
shuffle_deck(available_cards)
|
|
for i = 1, math.min(3, #available_cards) do
|
|
table.insert(card_choice_options, available_cards[i])
|
|
end
|
|
end
|
|
end
|
|
|
|
function add_card_to_deck(card_index)
|
|
unlocked_cards[card_index] = true
|
|
local card = cards[card_index]
|
|
-- Add 2 copies to discard pile (will be shuffled in next game)
|
|
table.insert(discard_pile, card)
|
|
table.insert(discard_pile, card)
|
|
end
|
|
|
|
-- player's starting hand (random subset)
|
|
hand = {}
|
|
|
|
function shuffle_deck(deck)
|
|
for i = #deck, 2, -1 do
|
|
local j = math.random(i)
|
|
deck[i], deck[j] = deck[j], deck[i]
|
|
end
|
|
end
|
|
|
|
function reshuffle_if_needed()
|
|
if #draw_pile < 3 and #discard_pile > 0 then
|
|
-- Move discard pile to draw pile and shuffle
|
|
for _, card in ipairs(discard_pile) do
|
|
table.insert(draw_pile, card)
|
|
end
|
|
discard_pile = {}
|
|
shuffle_deck(draw_pile)
|
|
end
|
|
end
|
|
|
|
function draw_hand()
|
|
hand = {}
|
|
reshuffle_if_needed()
|
|
|
|
-- If player has no energy, ensure at least one energy-positive card
|
|
if player.energy == 0 then
|
|
local energy_positive_found = false
|
|
local temp_hand = {}
|
|
|
|
-- Try to draw 3 cards, checking for energy-positive ones
|
|
for i = 1, 3 do
|
|
if #draw_pile > 0 then
|
|
local card = table.remove(draw_pile, 1)
|
|
table.insert(temp_hand, card)
|
|
if card.energy >= 0 then
|
|
energy_positive_found = true
|
|
end
|
|
end
|
|
end
|
|
|
|
-- If no energy-positive card found, replace one with an energy-positive card
|
|
if not energy_positive_found and #temp_hand > 0 then
|
|
-- Find an energy-positive card in remaining deck or discard
|
|
local energy_card = nil
|
|
|
|
-- Look in draw pile first
|
|
for i, card in ipairs(draw_pile) do
|
|
if card.energy >= 0 then
|
|
energy_card = table.remove(draw_pile, i)
|
|
break
|
|
end
|
|
end
|
|
|
|
-- If not found, look in discard pile
|
|
if not energy_card then
|
|
for i, card in ipairs(discard_pile) do
|
|
if card.energy >= 0 then
|
|
energy_card = table.remove(discard_pile, i)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Replace first card with energy-positive card
|
|
if energy_card then
|
|
table.insert(draw_pile, temp_hand[1]) -- Put replaced card back
|
|
temp_hand[1] = energy_card
|
|
end
|
|
end
|
|
|
|
hand = temp_hand
|
|
else
|
|
-- Normal draw when player has energy
|
|
for i = 1, 3 do
|
|
if #draw_pile > 0 then
|
|
table.insert(hand, table.remove(draw_pile, 1))
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Fill remaining slots if deck was too small
|
|
while #hand < 3 and (#draw_pile > 0 or #discard_pile > 0) do
|
|
reshuffle_if_needed()
|
|
if #draw_pile > 0 then
|
|
table.insert(hand, table.remove(draw_pile, 1))
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Initialize deck at start
|
|
initialize_deck()
|
|
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)]
|
|
|
|
-- Apply card energy cost/gain
|
|
player.energy = math.max(0, math.min(5, player.energy + card.energy))
|
|
|
|
-- Discard all cards in hand
|
|
for _, hand_card in ipairs(hand) do
|
|
table.insert(discard_pile, hand_card)
|
|
end
|
|
hand = {}
|
|
|
|
-- 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
|
|
card_pick_selection = 1
|
|
|
|
-- === DRAWING FUNCTIONS ===
|
|
function draw_hud()
|
|
-- Draw level
|
|
spr(0, 110, 2, 0)
|
|
print(level, 115, 2, 7, false, 1, true)
|
|
|
|
-- Draw energy sprites
|
|
for i = 1, player.energy do
|
|
local x = -4 + (8 * i)
|
|
spr(17, x, 12, 0)
|
|
end
|
|
|
|
-- Draw deck info
|
|
spr(18, 220, 115, 0)
|
|
print(#draw_pile, 230, 116, 10, false, 1, true)
|
|
spr(19, 220, 125, 0)
|
|
print(#discard_pile, 230, 126, 14, false, 1, true)
|
|
end
|
|
|
|
function draw_health_bars()
|
|
-- Draw player health bar
|
|
for i = 1, 10 do
|
|
local x = 4 + (i - 1) * 8
|
|
if i <= player.hp then
|
|
spr(1, x, 2, 0) -- healthy HP
|
|
else
|
|
spr(3, x, 2, 0) -- damaged HP
|
|
end
|
|
end
|
|
|
|
-- Show player damage
|
|
if current_damage.player ~= 0 then
|
|
print((current_damage.player < 0 and "+" or "") .. -math.floor(current_damage.player), 88, 3, current_damage.player > 0 and 2 or 6, false, 1, true)
|
|
end
|
|
|
|
-- Draw enemy health bar
|
|
for i = 1, enemy.max_hp do
|
|
local x = 240 - (enemy.max_hp * 8) + (i - 1) * 8
|
|
if i <= (enemy.max_hp - enemy.hp) then
|
|
spr(3, x, 2, 0) -- damaged HP on the left
|
|
else
|
|
spr(2, x, 2, 0) -- healthy HP on the right
|
|
end
|
|
end
|
|
|
|
-- Show enemy damage
|
|
if current_damage.enemy > 0 then
|
|
local enemy_bar_start = 240 - (enemy.max_hp * 8) - 20
|
|
print((current_damage.enemy < 0 and "+" or "") .. -math.floor(current_damage.enemy), enemy_bar_start, 3, current_damage.enemy > 0 and 2 or 6, false, 1, true)
|
|
end
|
|
|
|
-- Draw enemy name under health bar
|
|
local enemy_name_x = 240 - (enemy.max_hp * 8) + (enemy.max_hp * 4) - (#enemy.name * 3)
|
|
print(enemy.name, enemy_name_x, 12, 7, false, 1, true)
|
|
|
|
-- Show shield sprite if player has shield
|
|
if player.shield and player.shield > 0 then
|
|
spr(16, 20, 22, 0)
|
|
if player.shield > 1 then
|
|
print(player.shield, 228, 23, 12, false, 1, true)
|
|
end
|
|
end
|
|
end
|
|
|
|
function draw_card(card, x, is_selected, can_afford)
|
|
local border_color = can_afford and (is_selected and 1 or 12) or (is_selected and 10 or 9)
|
|
rectb(x, 40, 52, 64, border_color)
|
|
if is_selected then rect(x+1, 41, 50, 62, 14) end
|
|
local text_color = can_afford and (is_selected and 11 or 10) or (is_selected and 10 or 9)
|
|
print(card.name, x + 3, 70, text_color, false, 1, true)
|
|
|
|
-- Show energy cost/gain
|
|
local energy_text = card.energy >= 0 and ("+" .. card.energy) or tostring(card.energy)
|
|
spr(17, x + 30, 42, 0)
|
|
print(energy_text, x + 40, 44, card.energy >= 0 and 11 or 8, false, 1, true)
|
|
end
|
|
|
|
-- === INPUT ===
|
|
function TIC()
|
|
cls(0)
|
|
|
|
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
|
|
|
|
-- Handle flash effect
|
|
if flash > 0 then
|
|
-- Tint screen red on alternating frames
|
|
if flash % 4 < 2 then -- Flash on/off every 2 frames
|
|
poke(0x3FC0, 255) -- Red tint
|
|
else
|
|
poke(0x3FC0, original_palette[0])
|
|
end
|
|
flash = flash - 1
|
|
|
|
-- When effect ends, ensure palette is restored
|
|
if flash == 0 then
|
|
poke(0x3FC0, original_palette[0])
|
|
end
|
|
end
|
|
|
|
-- Draw HUD and health bars
|
|
draw_hud()
|
|
draw_health_bars()
|
|
|
|
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
|
|
local can_afford = (card.energy >= 0) or (player.energy >= -card.energy)
|
|
local is_selected = i == picked
|
|
draw_card(card, x, is_selected, can_afford)
|
|
end
|
|
|
|
local selected_card = hand[picked]
|
|
local can_afford = (selected_card.energy >= 0) or (player.energy >= -selected_card.energy)
|
|
print(selected_card.desc, 4, 120, can_afford and 15 or 6)
|
|
if not can_afford then
|
|
print("Not enough energy!", 4, 130, 9)
|
|
end
|
|
print(msg, 4, 40, 15)
|
|
|
|
if btnp(4) and can_afford then
|
|
start_turn(hand[picked])
|
|
end
|
|
|
|
elseif phase == "card_reward" then
|
|
-- Card selection after level victory
|
|
print("Choose a card to add to your deck:", 4, 30, 12)
|
|
|
|
if btnp(2) then
|
|
card_pick_selection = card_pick_selection - 1
|
|
end
|
|
if btnp(3) then
|
|
card_pick_selection = card_pick_selection + 1
|
|
end
|
|
card_pick_selection = math.min(#card_choice_options, math.max(1, card_pick_selection))
|
|
|
|
-- Show card options
|
|
for i, card_index in ipairs(card_choice_options) do
|
|
local card = cards[card_index]
|
|
local x = 10 + (i - 1) * 64
|
|
local is_selected = i == card_pick_selection
|
|
draw_card(card, x, is_selected, true) -- always affordable in reward phase
|
|
end
|
|
|
|
-- Show selected card description
|
|
if #card_choice_options > 0 then
|
|
local selected_card = cards[card_choice_options[card_pick_selection]]
|
|
print(selected_card.desc, 4, 110, 15)
|
|
end
|
|
|
|
if btnp(4) and #card_choice_options > 0 then
|
|
add_card_to_deck(card_choice_options[card_pick_selection])
|
|
-- Advance to next level
|
|
level = math.min(level + 1, #enemies)
|
|
player = {
|
|
hp = 10,
|
|
roll_mod = 0,
|
|
mult = 1,
|
|
energy = 0
|
|
}
|
|
enemy = get_enemy(level)
|
|
initialize_deck()
|
|
draw_hand()
|
|
phase = "choose"
|
|
phase_time = 0
|
|
damage_applied = false
|
|
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.name .. " played " .. enemy_card.name
|
|
print(card_text, 4, 40, 2)
|
|
print(enemy_card_text, 4, 50, 6)
|
|
-- show rolls
|
|
roll_text = "You roll " .. player.display_roll .. " / " .. enemy.name .. " rolls " .. enemy.display_roll
|
|
print(roll_text, 4, 60, 12)
|
|
|
|
elseif phase == "mod" then
|
|
print(card_text, 4, 40, 2)
|
|
print(enemy_card_text, 4, 50, 6)
|
|
print(roll_text, 4, 60, 14)
|
|
mod_text = "mods applied: +" .. player.roll_mod .. " / " .. enemy.roll_mod
|
|
print(mod_text, 4, 70, 12)
|
|
phase_time = phase_time + 1
|
|
if phase_time > 60 then
|
|
phase = "total"
|
|
phase_time = 0
|
|
end
|
|
elseif phase == "total" then
|
|
current_damage.player, current_damage.enemy = 0, 0 -- reset damage display
|
|
print(card_text, 4, 40, 2)
|
|
print(enemy_card_text, 4, 50, 6)
|
|
print(roll_text, 4, 60, 14)
|
|
print(mod_text, 4, 70, 14)
|
|
print("Total " .. player.total .. " / " .. enemy.total, 4, 80, 12)
|
|
phase_time = phase_time + 1
|
|
if phase_time > 120 then
|
|
phase = "result"
|
|
phase_time = 0
|
|
end
|
|
elseif phase == "result" then
|
|
print(card_text, 4, 40, 2)
|
|
print(enemy_card_text, 4, 50, 6)
|
|
print(roll_text, 4, 60, 14)
|
|
print(mod_text, 4, 70, 14)
|
|
print("Total " .. player.total .. " / " .. enemy.total, 4, 80, 14)
|
|
local msgtxt = ""
|
|
local player_won = nil
|
|
if player.total > enemy.total then
|
|
player_won = true
|
|
local dmg = 2 * player.mult
|
|
-- Apply rage bonus to increase damage
|
|
local rage_bonus = player.rage or 0
|
|
dmg = dmg + rage_bonus
|
|
-- Apply weaken debuff (player is weakened, so their attacks do less damage)
|
|
local weaken_reduction = player.weaken or 0
|
|
dmg = math.max(0, dmg - weaken_reduction)
|
|
|
|
if not damage_applied then
|
|
|
|
enemy.hp = enemy.hp - dmg
|
|
current_damage.enemy = current_damage.enemy + dmg
|
|
flash = 8
|
|
end
|
|
msgtxt = "You win!"
|
|
elseif enemy.total > player.total then
|
|
player_won = false
|
|
local dmg = 2 * enemy.mult
|
|
-- Apply rage bonus to increase damage
|
|
local rage_bonus = enemy.rage or 0
|
|
dmg = dmg + rage_bonus
|
|
-- Apply weaken debuff (enemy is weakened, so their attacks do less damage)
|
|
local weaken_reduction = enemy.weaken or 0
|
|
dmg = math.max(0, dmg - weaken_reduction)
|
|
|
|
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
|
|
current_damage.player = current_damage.player + dmg
|
|
-- Remove shield after taking damage
|
|
if shield_reduction > 0 and dmg < (2 * enemy.mult) then
|
|
player.shield = math.max(0, (player.shield or 0) - 1)
|
|
end
|
|
shake = 10
|
|
end
|
|
msgtxt = enemy.name .. " wins!"
|
|
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
|
|
local color = player_won == true and 2 or (player_won == false and 6 or 9)
|
|
print(msgtxt, 4, 90, color)
|
|
|
|
if btnp(4) then
|
|
-- reset mods & back to choose
|
|
player.roll_mod, enemy.roll_mod = 0, 0
|
|
player.mult, enemy.mult = 1, 1
|
|
player.weaken, enemy.weaken = 0, 0 -- reset weaken debuffs
|
|
player.rage, enemy.rage = 0, 0 -- reset rage bonuses
|
|
-- Don't reset shield - it persists until damage taken
|
|
-- Add 1 energy at end of turn
|
|
player.energy = math.min(5, player.energy + 1)
|
|
-- Reset damage display
|
|
current_damage.player = 0
|
|
current_damage.enemy = 0
|
|
draw_hand() -- Draw new hand from deck
|
|
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 <= 0 and player.hp > 0 then
|
|
local victory_text = "You defeated " .. enemy.name .. "!"
|
|
local victory_x = 120 - (#victory_text * 3) -- center text (6 pixel font, so 3 pixels per char from center)
|
|
print(victory_text, victory_x, 40, 6)
|
|
|
|
local continue_text = "Press Z to choose new card"
|
|
local continue_x = 120 - (#continue_text * 3)
|
|
print(continue_text, continue_x, 80, 9)
|
|
else
|
|
local defeat_text = enemy.name .. " defeated you!"
|
|
local defeat_x = 120 - (#defeat_text * 3)
|
|
print(defeat_text, defeat_x, 40, 2)
|
|
|
|
local restart_text = "Press X to restart"
|
|
local restart_x = 120 - (#restart_text * 3)
|
|
print(restart_text, restart_x, 60, 14)
|
|
end
|
|
if btnp(4) and enemy.hp <= 0 and player.hp > 0 then
|
|
-- Victory - go to card selection
|
|
generate_card_choices()
|
|
if #card_choice_options > 0 then
|
|
phase = "card_reward"
|
|
card_pick_selection = 1
|
|
else
|
|
-- No more cards to unlock, just advance level
|
|
level = math.min(level + 1, #enemies)
|
|
player = {
|
|
hp = 10,
|
|
roll_mod = 0,
|
|
mult = 1,
|
|
energy = 0
|
|
}
|
|
enemy = get_enemy(level)
|
|
initialize_deck()
|
|
draw_hand()
|
|
phase = "choose"
|
|
end
|
|
phase_time = 0
|
|
damage_applied = false
|
|
elseif btnp(5) and (enemy.hp > 0 or player.hp <= 0) then
|
|
-- Loss - restart from level 1
|
|
level = 1
|
|
player = {
|
|
hp = 10,
|
|
roll_mod = 0,
|
|
mult = 1,
|
|
energy = 0
|
|
}
|
|
enemy = get_enemy(level)
|
|
msg = ""
|
|
initialize_deck()
|
|
draw_hand()
|
|
phase = "choose"
|
|
phase_time = 0
|
|
roll_text = ""
|
|
damage_applied = false
|
|
end
|
|
end
|
|
end
|
|
|
|
-- <TILES>
|
|
-- 000:3200000032000000322200003332000033322200333332003333322200000000
|
|
-- 001:0ff00ff0f22ff33ff22f223ff222223f0f2222f000f22f00000ff00000000000
|
|
-- 002:0ff00ff0f66ff55ff66f665ff666665f0f6666f000f66f00000ff00000000000
|
|
-- 003:0ff00ff0feeffddffeefeedffeeeeedf0feeeef000feef00000ff00000000000
|
|
-- 016:0011110001333310133223311333233113323331013323100013310000011000
|
|
-- 017:0000000000333300033443300344c43003444430033443300033330000000000
|
|
-- 018:0aaaaaa00aa99aa00a9999a00a9999a00a9999a00a9999a00aa99aa00aaaaaa0
|
|
-- 019:0eeeeee00eeffee00effffe00effffe00effffe00effffe00eeffee00eeeeee0
|
|
-- </TILES>
|
|
|
|
-- <WAVES>
|
|
-- 000:00000000ffffffff00000000ffffffff
|
|
-- 001:0123456789abcdeffedcba9876543210
|
|
-- 002:0123456789abcdef0123456789abcdef
|
|
-- </WAVES>
|
|
|
|
-- <SFX>
|
|
-- 000:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000304000000000
|
|
-- </SFX>
|
|
|
|
-- <TRACKS>
|
|
-- 000:100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
|
-- </TRACKS>
|
|
|
|
-- <PALETTE>
|
|
-- 000:1a1c2c5d275db13e53ef7d57ffcd75a7f07038b76425717929366f3b5dc941a6f673eff7f4f4f494b0c2566c86333c57
|
|
-- </PALETTE>
|
|
-- <TRACKS>
|
|
-- 000:100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
|
-- </TRACKS>
|
|
|
|
-- <PALETTE>
|
|
-- 000:1a1c2c5d275db13e53ef7d57ffcd75a7f07038b76425717929366f3b5dc941a6f673eff7f4f4f494b0c2566c86333c57
|
|
-- </PALETTE>
|
|
|