Initial Commit - 1.0.0k

This commit is contained in:
2024-02-27 23:47:25 +08:00
commit 9d14fb1dcb
197 changed files with 99467 additions and 0 deletions

288
back.lua Normal file
View File

@ -0,0 +1,288 @@
--Class
Back = Object:extend()
--Class Methods
function Back:init(selected_back)
if not selected_back then selected_back = G.P_CENTERS.b_red end
self.name = selected_back.name or 'Red Deck'
self.effect = {
center = selected_back,
text_UI = '',
config = copy_table(selected_back.config)
}
self.loc_name = localize{type = 'name_text', set = 'Back', key = self.effect.center.key}
local pos = (self.effect.center.unlocked and self.effect.center.pos) or {x = 4, y = 0}
self.pos = self.pos or {}
self.pos.x = pos.x
self.pos.y = pos.y
end
function Back:get_name()
if self.effect.center.unlocked then return self.loc_name else return localize('k_locked') end
end
function Back:generate_UI(other, ui_scale, min_dims, challenge)
min_dims = min_dims or 0.7
ui_scale = ui_scale or 0.9
local back_config = other or self.effect.center
local name_to_check = other and other.name or self.name
local effect_config = other and other.config or self.effect.config
challenge = G.CHALLENGES[get_challenge_int_from_id(challenge or '') or ''] or {name = 'ERROR'}
local loc_args, loc_nodes = nil, {}
if not back_config.unlocked then
if not back_config.unlock_condition then
localize{type = 'descriptions', key = 'demo_locked', set = "Other", nodes = loc_nodes, vars = loc_args}
elseif back_config.unlock_condition.type == 'win_deck' then
local other_name = localize('k_unknown')
if G.P_CENTERS[back_config.unlock_condition.deck].unlocked then
other_name = localize{type = 'name_text', set = 'Back', key = back_config.unlock_condition.deck}
end
loc_args = {other_name}
localize{type = 'descriptions', key = 'deck_locked_win', set = "Other", nodes = loc_nodes, vars = loc_args}
elseif back_config.unlock_condition.type == 'discover_amount' then
loc_args = {tostring(back_config.unlock_condition.amount)}
localize{type = 'descriptions', key = 'deck_locked_discover', set = "Other", nodes = loc_nodes, vars = loc_args}
elseif back_config.unlock_condition.type == 'win_stake' then
local other_name = localize{type = 'name_text', set = 'Stake', key = G.P_CENTER_POOLS.Stake[back_config.unlock_condition.stake].key}
loc_args = {other_name, colours = {get_stake_col(back_config.unlock_condition.stake)}}
localize{type = 'descriptions', key = 'deck_locked_stake', set = "Other", nodes = loc_nodes, vars = loc_args}
end
else
if name_to_check == 'Blue Deck' then loc_args = {effect_config.hands}
elseif name_to_check == 'Red Deck' then loc_args = {effect_config.discards}
elseif name_to_check == 'Yellow Deck' then loc_args = {effect_config.dollars}
elseif name_to_check == 'Green Deck' then loc_args = {effect_config.extra_hand_bonus, effect_config.extra_discard_bonus}
elseif name_to_check == 'Black Deck' then loc_args = {effect_config.joker_slot, -effect_config.hands}
elseif name_to_check == 'Magic Deck' then loc_args = {localize{type = 'name_text', key = 'v_crystal_ball', set = 'Voucher'}, localize{type = 'name_text', key = 'c_fool', set = 'Tarot'}}
elseif name_to_check == 'Nebula Deck' then loc_args = {localize{type = 'name_text', key = 'v_telescope', set = 'Voucher'}, -1}
elseif name_to_check == 'Ghost Deck' then
elseif name_to_check == 'Abandoned Deck' then
elseif name_to_check == 'Checkered Deck' then
elseif name_to_check == 'Zodiac Deck' then loc_args = {localize{type = 'name_text', key = 'v_tarot_merchant', set = 'Voucher'},
localize{type = 'name_text', key = 'v_planet_merchant', set = 'Voucher'},
localize{type = 'name_text', key = 'v_overstock_norm', set = 'Voucher'}}
elseif name_to_check == 'Painted Deck' then loc_args = {effect_config.hand_size,effect_config.joker_slot}
elseif name_to_check == 'Anaglyph Deck' then loc_args = {localize{type = 'name_text', key = 'tag_double', set = 'Tag'}}
elseif name_to_check == 'Plasma Deck' then loc_args = {effect_config.ante_scaling}
elseif name_to_check == 'Erratic Deck' then
end
localize{type = 'descriptions', key = back_config.key, set = 'Back', nodes = loc_nodes, vars = loc_args}
end
return
{n=G.UIT.ROOT, config={align = "cm", minw = min_dims*5, minh = min_dims*2.5, id = self.name, colour = G.C.CLEAR}, nodes={
name_to_check == 'Challenge Deck' and UIBox_button({button = 'deck_view_challenge', label = {localize(challenge.id, 'challenge_names')}, minw = 2.2, minh = 1, scale = 0.6, id = challenge})
or desc_from_rows(loc_nodes, true, min_dims*5)
}}
end
function Back:change_to(new_back)
if not new_back then new_back = G.P_CENTERS.b_red end
self.name = new_back.name or 'Red Deck'
self.effect = {
center = new_back,
text_UI = '',
config = copy_table(new_back.config)
}
self.loc_name = localize{type = 'name_text', set = 'Back', key = self.effect.center.key}
local pos = self.effect.center.unlocked and copy_table(new_back.pos) or {x = 4, y = 0}
self.pos.x = pos.x
self.pos.y = pos.y
end
function Back:save()
local backTable = {
name = self.name,
pos = self.pos,
effect = self.effect,
key = self.effect.center.key or 'b_red'
}
return backTable
end
function Back:trigger_effect(args)
if not args then return end
if self.name == 'Anaglyph Deck' and args.context == 'eval' and G.GAME.last_blind and G.GAME.last_blind.boss then
G.E_MANAGER:add_event(Event({
func = (function()
add_tag(Tag('tag_double'))
play_sound('generic1', 0.9 + math.random()*0.1, 0.8)
play_sound('holo1', 1.2 + math.random()*0.1, 0.4)
return true
end)
}))
end
if self.name == 'Plasma Deck' and args.context == 'blind_amount' then
return
end
if self.name == 'Plasma Deck' and args.context == 'final_scoring_step' then
local tot = args.chips + args.mult
args.chips = math.floor(tot/2)
args.mult = math.floor(tot/2)
update_hand_text({delay = 0}, {mult = args.mult, chips = args.chips})
G.E_MANAGER:add_event(Event({
func = (function()
local text = localize('k_balanced')
play_sound('gong', 0.94, 0.3)
play_sound('gong', 0.94*1.5, 0.2)
play_sound('tarot1', 1.5)
ease_colour(G.C.UI_CHIPS, {0.8, 0.45, 0.85, 1})
ease_colour(G.C.UI_MULT, {0.8, 0.45, 0.85, 1})
attention_text({
scale = 1.4, text = text, hold = 2, align = 'cm', offset = {x = 0,y = -2.7},major = G.play
})
G.E_MANAGER:add_event(Event({
trigger = 'after',
blockable = false,
blocking = false,
delay = 4.3,
func = (function()
ease_colour(G.C.UI_CHIPS, G.C.BLUE, 2)
ease_colour(G.C.UI_MULT, G.C.RED, 2)
return true
end)
}))
G.E_MANAGER:add_event(Event({
trigger = 'after',
blockable = false,
blocking = false,
no_delete = true,
delay = 6.3,
func = (function()
G.C.UI_CHIPS[1], G.C.UI_CHIPS[2], G.C.UI_CHIPS[3], G.C.UI_CHIPS[4] = G.C.BLUE[1], G.C.BLUE[2], G.C.BLUE[3], G.C.BLUE[4]
G.C.UI_MULT[1], G.C.UI_MULT[2], G.C.UI_MULT[3], G.C.UI_MULT[4] = G.C.RED[1], G.C.RED[2], G.C.RED[3], G.C.RED[4]
return true
end)
}))
return true
end)
}))
delay(0.6)
return args.chips, args.mult
end
end
function Back:apply_to_run()
if self.effect.config.voucher then
G.GAME.used_vouchers[self.effect.config.voucher] = true
G.GAME.starting_voucher_count = (G.GAME.starting_voucher_count or 0) + 1
Card.apply_to_run(nil, G.P_CENTERS[self.effect.config.voucher])
end
if self.effect.config.hands then
G.GAME.starting_params.hands = G.GAME.starting_params.hands + self.effect.config.hands
end
if self.effect.config.consumables then
delay(0.4)
G.E_MANAGER:add_event(Event({
func = function()
for k, v in ipairs(self.effect.config.consumables) do
local card = create_card('Tarot', G.consumeables, nil, nil, nil, nil, v, 'deck')
card:add_to_deck()
G.consumeables:emplace(card)
end
return true
end
}))
end
if self.effect.config.dollars then
G.GAME.starting_params.dollars = G.GAME.starting_params.dollars + self.effect.config.dollars
end
if self.effect.config.remove_faces then
G.GAME.starting_params.no_faces = true
end
if self.effect.config.spectral_rate then
G.GAME.spectral_rate = self.effect.config.spectral_rate
end
if self.effect.config.discards then
G.GAME.starting_params.discards = G.GAME.starting_params.discards + self.effect.config.discards
end
if self.effect.config.reroll_discount then
G.GAME.starting_params.reroll_cost = G.GAME.starting_params.reroll_cost - self.effect.config.reroll_discount
end
if self.effect.config.edition then
G.E_MANAGER:add_event(Event({
func = function()
local i = 0
while i < self.effect.config.edition_count do
local card = pseudorandom_element(G.playing_cards, pseudoseed('edition_deck'))
if not card.edition then
i = i + 1
card:set_edition({[self.effect.config.edition] = true}, nil, true)
end
end
return true
end
}))
end
if self.effect.config.vouchers then
for k, v in pairs(self.effect.config.vouchers) do
G.GAME.used_vouchers[v ] = true
G.GAME.starting_voucher_count = (G.GAME.starting_voucher_count or 0) + 1
Card.apply_to_run(nil, G.P_CENTERS[v])
end
end
if self.name == 'Checkered Deck' then
G.E_MANAGER:add_event(Event({
func = function()
for k, v in pairs(G.playing_cards) do
if v.base.suit == 'Clubs' then
v:change_suit('Spades')
end
if v.base.suit == 'Diamonds' then
v:change_suit('Hearts')
end
end
return true
end
}))
end
if self.effect.config.randomize_rank_suit then
G.GAME.starting_params.erratic_suits_and_ranks = true
end
if self.effect.config.joker_slot then
G.GAME.starting_params.joker_slots = G.GAME.starting_params.joker_slots + self.effect.config.joker_slot
end
if self.effect.config.hand_size then
G.GAME.starting_params.hand_size = G.GAME.starting_params.hand_size + self.effect.config.hand_size
end
if self.effect.config.ante_scaling then
G.GAME.starting_params.ante_scaling = self.effect.config.ante_scaling
end
if self.effect.config.consumable_slot then
G.GAME.starting_params.consumable_slots = G.GAME.starting_params.consumable_slots + self.effect.config.consumable_slot
end
if self.effect.config.no_interest then
G.GAME.modifiers.no_interest = true
end
if self.effect.config.extra_hand_bonus then
G.GAME.modifiers.money_per_hand = self.effect.config.extra_hand_bonus
end
if self.effect.config.extra_discard_bonus then
G.GAME.modifiers.money_per_discard = self.effect.config.extra_discard_bonus
end
end
function Back:load(backTable)
self.name = backTable.name
self.pos = backTable.pos
self.effect = backTable.effect
self.effect.center = G.P_CENTERS[backTable.key] or G.P_CENTERS.b_red
self.loc_name = localize{type = 'name_text', set = 'Back', key = self.effect.center.key}
end

751
blind.lua Normal file
View File

@ -0,0 +1,751 @@
--class
Blind = Moveable:extend()
--class methods
function Blind:init(X, Y, W, H)
Moveable.init(self,X, Y, W, H)
self.children = {}
self.config = {}
self.tilt_var = {mx = 0, my = 0, amt = 0}
self.ambient_tilt = 0.3
self.chips = 0
self.zoom = true
self.states.collide.can = true
self.colour = copy_table(G.C.BLACK)
self.dark_colour = darken(self.colour, 0.2)
self.children.animatedSprite = AnimatedSprite(self.T.x, self.T.y, self.T.w, self.T.h, G.ANIMATION_ATLAS['blind_chips'], G.P_BLINDS.bl_small.pos)
self.children.animatedSprite.states = self.states
self.children.animatedSprite.states.visible = false
self.children.animatedSprite.states.drag.can = true
self.states.collide.can = true
self.states.drag.can = true
self.loc_debuff_lines = {'',''}
self.shadow_height = 0
if getmetatable(self) == Blind then
table.insert(G.I.CARD, self)
end
end
function Blind:change_colour(blind_col)
blind_col = blind_col or get_blind_main_colour(self.config.blind.key or '')
local dark_col = mix_colours(blind_col, G.C.BLACK, 0.4)
ease_colour(G.C.DYN_UI.MAIN, blind_col)
ease_colour(G.C.DYN_UI.DARK, dark_col)
if not self.boss and self.name then
blind_col = darken(G.C.BLACK, 0.05)
dark_col = lighten(G.C.BLACK, 0.07)
else
dark_col = mix_colours(blind_col, G.C.BLACK, 0.2)
end
ease_colour(G.C.DYN_UI.BOSS_MAIN, blind_col)
ease_colour(G.C.DYN_UI.BOSS_DARK, dark_col)
end
function Blind:set_text()
if self.config.blind then
if self.disabled then
self.loc_name = self.name == '' and self.name or localize{type ='name_text', key = self.config.blind.key, set = 'Blind'}
self.loc_debuff_text = ''
self.loc_debuff_lines[1] = ''
self.loc_debuff_lines[2] = ''
else
local loc_vars = nil
if self.name == 'The Ox' then
loc_vars = {localize(G.GAME.current_round.most_played_poker_hand, 'poker_hands')}
end
local loc_target = localize{type = 'raw_descriptions', key = self.config.blind.key, set = 'Blind', vars = loc_vars or self.config.blind.vars}
if loc_target then
self.loc_name = self.name == '' and self.name or localize{type ='name_text', key = self.config.blind.key, set = 'Blind'}
self.loc_debuff_text = ''
for k, v in ipairs(loc_target) do
self.loc_debuff_text = self.loc_debuff_text..v..(k <= #loc_target and ' ' or '')
end
self.loc_debuff_lines[1] = loc_target[1] or ''
self.loc_debuff_lines[2] = loc_target[2] or ''
else
self.loc_name = ''; self.loc_debuff_text = ''
self.loc_debuff_lines[1] = ''
self.loc_debuff_lines[2] = ''
end
end
end
end
function Blind:set_blind(blind, reset, silent)
if not reset then
self.config.blind = blind or {}
self.name = blind and blind.name or ''
self.dollars = blind and blind.dollars or 0
self.sound_pings = self.dollars + 2
if G.GAME.modifiers.no_blind_reward and G.GAME.modifiers.no_blind_reward[self:get_type()] then self.dollars = 0 end
self.debuff = blind and blind.debuff or {}
self.pos = blind and blind.pos
self.mult = blind and blind.mult or 0
self.disabled = false
self.discards_sub = nil
self.hands_sub = nil
self.boss = blind and not not blind.boss
self.blind_set = false
self.triggered = nil
self.prepped = true
self:set_text()
G.GAME.last_blind = G.GAME.last_blind or {}
G.GAME.last_blind.boss = self.boss
G.GAME.last_blind.name = self.name
if blind and blind.name then
self:change_colour()
else
self:change_colour(G.C.BLACK)
end
self.chips = get_blind_amount(G.GAME.round_resets.ante)*self.mult*G.GAME.starting_params.ante_scaling
self.chip_text = number_format(self.chips)
if not blind then self.chips = 0 end
G.GAME.current_round.dollars_to_be_earned = self.dollars > 0 and (string.rep(localize('$'), self.dollars)..'') or ('')
G.HUD_blind.alignment.offset.y = -10
G.HUD_blind:recalculate(false)
if blind and blind.name and blind.name ~= '' then
self:alert_debuff(true)
G.E_MANAGER:add_event(Event({
trigger = 'after',
delay = 0.05,
blockable = false,
func = (function()
G.HUD_blind:get_UIE_by_ID("HUD_blind_name").states.visible = false
G.HUD_blind:get_UIE_by_ID("dollars_to_be_earned").parent.parent.states.visible = false
G.HUD_blind.alignment.offset.y = 0
G.E_MANAGER:add_event(Event({
trigger = 'after',
delay = 0.15,
blockable = false,
func = (function()
G.HUD_blind:get_UIE_by_ID("HUD_blind_name").states.visible = true
G.HUD_blind:get_UIE_by_ID("dollars_to_be_earned").parent.parent.states.visible = true
G.HUD_blind:get_UIE_by_ID("dollars_to_be_earned").config.object:pop_in(0)
G.HUD_blind:get_UIE_by_ID("HUD_blind_name").config.object:pop_in(0)
G.HUD_blind:get_UIE_by_ID("HUD_blind_count"):juice_up()
self.children.animatedSprite:set_sprite_pos(self.config.blind.pos)
self.blind_set = true
G.ROOM.jiggle = G.ROOM.jiggle + 3
if not reset and not silent then
self:juice_up()
if blind then play_sound('chips1', math.random()*0.1 + 0.55, 0.42);play_sound('gold_seal', math.random()*0.1 + 1.85, 0.26)--play_sound('cancel')
end
end
return true
end)
}))
return true
end)
}))
end
self.config.h_popup_config ={align="tm", offset = {x=0,y=-0.1},parent = self}
end
if self.name == 'The Eye' and not reset then
self.hands = {
["Flush Five"] = false,
["Flush House"] = false,
["Five of a Kind"] = false,
["Straight Flush"] = false,
["Four of a Kind"] = false,
["Full House"] = false,
["Flush"] = false,
["Straight"] = false,
["Three of a Kind"] = false,
["Two Pair"] = false,
["Pair"] = false,
["High Card"] = false,
}
end
if self.name == 'The Mouth' and not reset then
self.only_hand = false
end
if self.name == 'The Fish' and not reset then
self.prepped = nil
end
if self.name == 'The Water' and not reset then
self.discards_sub = G.GAME.current_round.discards_left
ease_discard(-self.discards_sub)
end
if self.name == 'The Needle' and not reset then
self.hands_sub = G.GAME.round_resets.hands - 1
ease_hands_played(-self.hands_sub)
end
if self.name == 'The Manacle' and not reset then
G.hand:change_size(-1)
end
if self.name == 'Amber Acorn' and not reset and #G.jokers.cards > 0 then
G.jokers:unhighlight_all()
for k, v in ipairs(G.jokers.cards) do
v:flip()
end
if #G.jokers.cards > 1 then
G.E_MANAGER:add_event(Event({ trigger = 'after', delay = 0.2, func = function()
G.E_MANAGER:add_event(Event({ func = function() G.jokers:shuffle('aajk'); play_sound('cardSlide1', 0.85);return true end }))
delay(0.15)
G.E_MANAGER:add_event(Event({ func = function() G.jokers:shuffle('aajk'); play_sound('cardSlide1', 1.15);return true end }))
delay(0.15)
G.E_MANAGER:add_event(Event({ func = function() G.jokers:shuffle('aajk'); play_sound('cardSlide1', 1);return true end }))
delay(0.5)
return true end }))
end
end
--add new debuffs
for _, v in ipairs(G.playing_cards) do
self:debuff_card(v)
end
for _, v in ipairs(G.jokers.cards) do
if not reset then self:debuff_card(v, true) end
end
G.ARGS.spin.real = (self.config.blind.boss and (self.config.blind.boss.showdown and 0.5 or 0.25) or 0)
end
function Blind:alert_debuff(first)
if self.loc_debuff_text and self.loc_debuff_text ~= '' then
self.block_play = true
G.E_MANAGER:add_event(Event({
blockable = false,
blocking = false,
func = (function()
if self.disabled then self.block_play = nil; return true end
if G.STATE == G.STATES.SELECTING_HAND then
G.E_MANAGER:add_event(Event({
trigger = 'after',
delay = G.SETTINGS.GAMESPEED*0.05,
blockable = false,
func = (function()
play_sound('whoosh1', 0.55, 0.62)
for i = 1, 4 do
local wait_time = (0.1*(i-1))
G.E_MANAGER:add_event(Event({ blockable = false, trigger = 'after', delay = G.SETTINGS.GAMESPEED*wait_time,
func = function()
if i == 1 then self:juice_up() end
play_sound('cancel', 0.7 + 0.05*i, 0.7)
return true end }))
end
local hold_time = G.SETTINGS.GAMESPEED*(#self.loc_debuff_text*0.035 + 1.3)
local disp_text = self:get_loc_debuff_text()
attention_text({
scale = 0.7, text = disp_text, maxw = 12, hold = hold_time, align = 'cm', offset = {x = 0,y = -1},major = G.play
})
G.E_MANAGER:add_event(Event({
trigger = 'after',
delay = 1,
blocking = false,
blockable = false,
func = (function()
self.block_play = nil
if G.buttons then
local _buttons = G.buttons:get_UIE_by_ID('play_button')
_buttons.disable_button = nil
end
return true
end)
}))
return true
end)
}))
return true
end
end)
}))
end
end
function Blind:get_loc_debuff_text()
local disp_text = (self.config.blind.name == 'The Wheel' and G.GAME.probabilities.normal or '')..self.loc_debuff_text
if (self.config.blind.name == 'The Mouth') and self.only_hand then disp_text = disp_text..' ['..localize(self.only_hand, 'poker_hands')..']' end
return disp_text
end
function Blind:defeat(silent)
local dissolve_time = 1.3
local extra_time = 0
self.dissolve = 0
self.dissolve_colours = {G.C.BLACK, G.C.RED}
self:juice_up()
self.children.particles = Particles(0, 0, 0,0, {
timer_type = 'TOTAL',
timer = 0.01*dissolve_time,
scale = 0.1,
speed = 1.5,
lifespan = 0.7*dissolve_time,
attach = self,
colours = self.dissolve_colours,
fill = true
})
local blind_name_dynatext = G.HUD_blind:get_UIE_by_ID('HUD_blind_name').config.object
blind_name_dynatext:pop_out(2)
G.E_MANAGER:add_event(Event({
trigger = 'after',
blockable = false,
delay = 0.5*dissolve_time,
func = (function() self.children.particles.max = 0 return true end)
}))
if not silent then
for i = 1, math.min(self.sound_pings or 3, 7) do
extra_time = extra_time + (0.4+0.15*i)*dissolve_time
G.E_MANAGER:add_event(Event({ blockable = false, trigger = 'after', delay = (0.15 - 0.01*(self.sound_pings or 3))*i*dissolve_time,
func = function()
play_sound('cancel', 0.8 - 0.05*i, 1.7)
if i == math.min((self.sound_pings or 3)+1, 6) then play_sound('whoosh2', 0.7, 0.42) end
return true end }))
end
end
G.E_MANAGER:add_event(Event({
trigger = 'ease',
blockable = false,
ref_table = self,
ref_value = 'dissolve',
ease_to = 1,
delay = 0.7*dissolve_time,
func = (function(t) return t end)
}))
G.E_MANAGER:add_event(Event({
trigger = 'after',
blockable = false,
delay = 0.8*dissolve_time,
func = (function()
G.HUD_blind.alignment.offset.y = -10
return true
end)
}))
G.E_MANAGER:add_event(Event({
trigger = 'after',
blockable = false,
delay = 0.95*dissolve_time,
func = (function()
self.dissolve = nil
self:set_blind(nil, nil, true) return true end)
}))
for k, v in ipairs(G.jokers.cards) do
if v.facing == 'back' then v:flip() end
end
if self.name == 'The Manacle' and not self.disabled then
G.hand:change_size(1)
end
end
function Blind:get_type()
if self.name == "Small Blind" then
return 'Small'
elseif self.name == "Big Blind" then
return 'Big'
elseif self.name and self.name ~= '' then
return 'Boss'
end
end
function Blind:disable()
self.disabled = true
for k, v in ipairs(G.jokers.cards) do
if v.facing == 'back' then v:flip() end
end
if self.name == 'The Water' then
ease_discard(self.discards_sub)
end
if self.name == 'The Wheel' or self.name == 'The House' or self.name == 'The Mark' or self.name == 'The Fish' then
for i = 1, #G.hand.cards do
if G.hand.cards[i].facing == 'back' then
G.hand.cards[i]:flip()
end
end
for k, v in pairs(G.playing_cards) do
v.ability.wheel_flipped = nil
end
end
if self.name == 'The Needle' then
ease_hands_played(self.hands_sub)
end
if self.name == 'The Wall' then
self.chips = self.chips/2
self.chip_text = number_format(self.chips)
end
if self.name == 'Cerulean Bell' then
for k, v in ipairs(G.playing_cards) do
v.ability.forced_selection = nil
end
end
if self.name == 'The Manacle' then
G.hand:change_size(1)
G.FUNCS.draw_from_deck_to_hand(1)
end
if self.name == 'The Serpent' then
end
if self.name == 'Violet Vessel' then
self.chips = self.chips/3
self.chip_text = number_format(self.chips)
end
G.E_MANAGER:add_event(Event({
trigger = 'immediate',
func = function()
if self.boss and G.GAME.chips - G.GAME.blind.chips >= 0 then
G.STATE = G.STATES.NEW_ROUND
G.STATE_COMPLETE = false
end
return true
end
}))
for _, v in ipairs(G.playing_cards) do
self:debuff_card(v)
end
for _, v in ipairs(G.jokers.cards) do
self:debuff_card(v)
end
self:set_text()
self:wiggle()
end
function Blind:wiggle()
self.children.animatedSprite:juice_up(0.3)
G.E_MANAGER:add_event(Event({trigger = 'after', delay = 0.06*G.SETTINGS.GAMESPEED, blockable = false, blocking = false, func = function()
play_sound('tarot2', 0.76, 0.4);return true end}))
play_sound('tarot2', 1, 0.4)
end
function Blind:juice_up(_a, _b)
self.children.animatedSprite:juice_up(_a or 0.2, _b or 0.2)
end
function Blind:hover()
if not G.CONTROLLER.dragging.target or G.CONTROLLER.using_touch then
if not self.hovering and self.states.visible and self.children.animatedSprite.states.visible then
self.hovering = true
self.hover_tilt = 2
self.children.animatedSprite:juice_up(0.05, 0.02)
play_sound('chips1', math.random()*0.1 + 0.55, 0.12)
Node.hover(self)
end
end
end
function Blind:stop_hover()
self.hovering = false
self.hover_tilt = 0
Node.stop_hover(self)
end
function Blind:draw()
if not self.states.visible then return end
self.tilt_var = self.tilt_var or {}
self.tilt_var.mx, self.tilt_var.my =G.CONTROLLER.cursor_position.x,G.CONTROLLER.cursor_position.y
self.children.animatedSprite.role.draw_major = self
self.children.animatedSprite:draw_shader('dissolve', 0.1)
self.children.animatedSprite:draw_shader('dissolve')
for k, v in pairs(self.children) do
if k ~= 'animatedSprite' then
v.VT.scale = self.VT.scale
v:draw()
end
end
add_to_drawhash(self)
end
function Blind:press_play()
if self.disabled then return end
if self.name == "The Hook" then
G.E_MANAGER:add_event(Event({ func = function()
local any_selected = nil
local _cards = {}
for k, v in ipairs(G.hand.cards) do
_cards[#_cards+1] = v
end
for i = 1, 2 do
if G.hand.cards[i] then
local selected_card, card_key = pseudorandom_element(_cards, pseudoseed('hook'))
G.hand:add_to_highlighted(selected_card, true)
table.remove(_cards, card_key)
any_selected = true
play_sound('card1', 1)
end
end
if any_selected then G.FUNCS.discard_cards_from_highlighted(nil, true) end
return true end }))
self.triggered = true
delay(0.7)
return true
end
if self.name == 'Crimson Heart' then
if G.jokers.cards[1] then
self.triggered = true
self.prepped = true
end
end
if self.name == 'The Fish' then
self.prepped = true
end
if self.name == "The Tooth" then
G.E_MANAGER:add_event(Event({trigger = 'after', delay = 0.2, func = function()
for i = 1, #G.play.cards do
G.E_MANAGER:add_event(Event({func = function() G.play.cards[i]:juice_up(); return true end }))
ease_dollars(-1)
delay(0.23)
end
return true end }))
self.triggered = true
return true
end
end
function Blind:modify_hand(cards, poker_hands, text, mult, hand_chips)
if self.disabled then return mult, hand_chips, false end
if self.name == "The Flint" then
self.triggered = true
return math.max(math.floor(mult*0.5 + 0.5), 1), math.max(math.floor(hand_chips*0.5 + 0.5), 0), true
end
return mult, hand_chips, false
end
function Blind:debuff_hand(cards, hand, handname, check)
if self.disabled then return end
if self.debuff then
self.triggered = false
if self.debuff.hand and next(hand[self.debuff.hand]) then
self.triggered = true
return true
end
if self.debuff.h_size_ge and #cards < self.debuff.h_size_ge then
self.triggered = true
return true
end
if self.debuff.h_size_le and #cards > self.debuff.h_size_le then
self.triggered = true
return true
end
if self.name == "The Eye" then
if self.hands[handname] then
self.triggered = true
return true
end
if not check then self.hands[handname] = true end
end
if self.name == "The Mouth" then
if self.only_hand and self.only_hand ~= handname then
self.triggered = true
return true
end
if not check then self.only_hand = handname end
end
end
if self.name == 'The Arm' then
self.triggered = false
if G.GAME.hands[handname].level > 1 then
self.triggered = true
if not check then
level_up_hand(self.children.animatedSprite, handname, nil, -1)
self:wiggle()
end
end
end
if self.name == 'The Ox' then
self.triggered = false
if handname == G.GAME.current_round.most_played_poker_hand then
self.triggered = true
if not check then
ease_dollars(-G.GAME.dollars, true)
self:wiggle()
end
end
end
end
function Blind:drawn_to_hand()
if not self.disabled then
if self.name == 'Cerulean Bell' then
local any_forced = nil
for k, v in ipairs(G.hand.cards) do
if v.ability.forced_selection then
any_forced = true
end
end
if not any_forced then
G.hand:unhighlight_all()
local forced_card = pseudorandom_element(G.hand.cards, pseudoseed('cerulean_bell'))
forced_card.ability.forced_selection = true
G.hand:add_to_highlighted(forced_card)
end
end
if self.name == 'Crimson Heart' and self.prepped and G.jokers.cards[1] then
local jokers = {}
for i = 1, #G.jokers.cards do
if not G.jokers.cards[i].debuff or #G.jokers.cards < 2 then jokers[#jokers+1] =G.jokers.cards[i] end
G.jokers.cards[i]:set_debuff(false)
end
local _card = pseudorandom_element(jokers, pseudoseed('crimson_heart'))
if _card then
_card:set_debuff(true)
_card:juice_up()
self:wiggle()
end
end
end
self.prepped = nil
end
function Blind:stay_flipped(area, card)
if not self.disabled then
if area == G.hand then
if self.name == 'The Wheel' and pseudorandom(pseudoseed('wheel')) < G.GAME.probabilities.normal/7 then
return true
end
if self.name == 'The House' and G.GAME.current_round.hands_played == 0 and G.GAME.current_round.discards_used == 0 then
return true
end
if self.name == 'The Mark' and card:is_face(true) then
return true
end
if self.name == 'The Fish' and self.prepped then
return true
end
end
end
end
function Blind:debuff_card(card, from_blind)
if self.debuff and not self.disabled and card.area ~= G.jokers then
if self.debuff.suit and card:is_suit(self.debuff.suit, true) then
card:set_debuff(true)
return
end
if self.debuff.is_face =='face' and card:is_face(true) then
card:set_debuff(true)
return
end
if self.name == 'The Pillar' and card.ability.played_this_ante then
card:set_debuff(true)
return
end
if self.debuff.value and self.debuff.value == card.base.value then
card:set_debuff(true)
return
end
if self.debuff.nominal and self.debuff.nominal == card.base.nominal then
card:set_debuff(true)
return
end
end
if self.name == 'Crimson Heart' and not self.disabled and card.area == G.jokers then
return
end
if self.name == 'Verdant Leaf' and not self.disabled and card.area ~= G.jokers then card:set_debuff(true); return end
card:set_debuff(false)
end
function Blind:move(dt)
Moveable.move(self, dt)
self:align()
end
function Blind:change_dim(w, h)
self.T.w = w or self.T.w
self.T.h = h or self.T.h
self.children.animatedSprite.T.w = w or self.T.w
self.children.animatedSprite.T.h = h or self.T.h
self.children.animatedSprite:rescale()
end
function Blind:align()
for k, v in pairs(self.children) do
if k == 'animatedSprite' then
if not v.states.drag.is then
v.T.r = 0.02*math.sin(2*G.TIMERS.REAL+self.T.x)
v.T.y = self.T.y + 0.03*math.sin(0.666*G.TIMERS.REAL+self.T.x)
self.shadow_height = 0.1 - (0.04 + 0.03*math.sin(0.666*G.TIMERS.REAL+self.T.x))
v.T.x = self.T.x + 0.03*math.sin(0.436*G.TIMERS.REAL+self.T.x)
end
else
v.T.x = self.T.x
v.T.y = self.T.y
v.T.r = self.T.r
end
end
end
function Blind:save()
local blindTable = {
name = self.name,
dollars = self.dollars,
debuff = self.debuff,
pos = self.pos,
mult = self.mult,
disabled = self.disabled,
discards_sub = self.discards_sub,
hands_sub = self.hands_sub,
boss = self.boss,
config_blind = '',
chips = self.chips,
chip_text =self.chip_text,
hands = self.hands,
only_hand = self.only_hand,
triggered = self.triggered
}
for k, v in pairs(G.P_BLINDS) do
if v and v.name and v.name == blindTable.name then
blindTable.config_blind = k
end
end
return blindTable
end
function Blind:load(blindTable)
self.config.blind = G.P_BLINDS[blindTable.config_blind] or {}
self.name = blindTable.name
self.dollars = blindTable.dollars
self.debuff = blindTable.debuff
self.pos = blindTable.pos
self.mult = blindTable.mult
self.disabled = blindTable.disabled
self.discards_sub = blindTable.discards_sub
self.hands_sub = blindTable.hands_sub
self.boss = blindTable.boss
self.chips = blindTable.chips
self.chip_text = blindTable.chip_text
self.hands = blindTable.hands
self.only_hand = blindTable.only_hand
self.triggered = blindTable.triggered
G.ARGS.spin.real = (self.config.blind.boss and (self.config.blind.boss.showdown and 1 or 0.3) or 0)
if G.P_BLINDS[blindTable.config_blind] then
self.blind_set = true
self.children.animatedSprite.states.visible = true
self.children.animatedSprite:set_sprite_pos(self.config.blind.pos)
self.children.animatedSprite:juice_up(0.3)
self:align()
self.children.animatedSprite:hard_set_VT()
else
self.children.animatedSprite.states.visible = false
end
self.children.animatedSprite.states = self.states
self:change_colour()
if self.dollars > 0 then
G.GAME.current_round.dollars_to_be_earned = string.rep(localize('$'), self.dollars)..''
G.HUD_blind:get_UIE_by_ID("dollars_to_be_earned").config.object:pop_in(0)
G.HUD_blind.alignment.offset.y = 0
end
self:set_text()
end

4715
card.lua Normal file

File diff suppressed because it is too large Load Diff

164
card_character.lua Normal file
View File

@ -0,0 +1,164 @@
--class
Card_Character = Moveable:extend()
--class methods
function Card_Character:init(args)
Moveable.init(self,args.x or 1, args.y or 1, args.w or G.CARD_W*1.1, args.h or G.CARD_H*1.1)
self.states.collide.can = false
self.children = {}
self.config = {args = args}
self.children.card = Card(self.T.x, self.T.y, G.CARD_W, G.CARD_H, G.P_CARDS.empty, args.center or G.P_CENTERS.j_joker, {bypass_discovery_center = true})
self.children.card.states.visible = false
self.children.card:start_materialize({G.C.BLUE, G.C.WHITE, G.C.RED})
self.children.card:set_alignment{
major = self, type = 'cm', offset = {x=0, y=0}
}
self.children.card.jimbo = self
self.children.card.states.collide.can = true
self.children.card.states.focus.can = false
self.children.card.states.hover.can = true
self.children.card.states.drag.can = false
self.children.card.hover = Node.hover
self.children.particles = Particles(0, 0, 0,0, {
timer = 0.03,
scale = 0.3,
speed = 1.2,
lifespan = 2,
attach = self,
colours = {G.C.RED, G.C.BLUE, G.C.ORANGE},
fill = true
})
self.children.particles.static_rotation = true
self.children.particles:set_role{
role_type = 'Minor',
xy_bond = 'Weak',
r_bond = 'Strong',
major = self,
}
if getmetatable(self) == Card_Character then
table.insert(G.I.CARD, self)
end
end
function Card_Character:move(dt)
Moveable.move(self, dt)
end
function Card_Character:hard_set_VT()
self:align_to_major()
Moveable.hard_set_VT(self)
self:align()
self.children.card:hard_set_VT()
end
function Card_Character:align()
if self.children.card then
self.children.card.T.x = self.T.x + (self.T.w - self.children.card.T.w)/2
self.children.card.T.y = self.T.y + (self.T.h - self.children.card.T.h)/2
end
end
function Card_Character:add_button(button, func, colour, update_func, snap_to, yoff)
if self.children.button then self.children.button:remove() end
self.config.button_align = {align="bm", offset = {x=0,y=yoff or 0.3},major = self, parent = self}
self.children.button = UIBox{
definition = create_UIBox_character_button({button = button, func = func, colour = colour, update_func = update_func, maxw = 3}),
config = self.config.button_align
}
if snap_to then G.CONTROLLER:snap_to{node = self.children.button} end
end
function Card_Character:add_speech_bubble(text_key, align, loc_vars)
if self.children.speech_bubble then self.children.speech_bubble:remove() end
self.config.speech_bubble_align = {align=align or 'bm', offset = {x=0,y=0},parent = self}
self.children.speech_bubble =
UIBox{
definition = G.UIDEF.speech_bubble(text_key, loc_vars),
config = self.config.speech_bubble_align
}
self.children.speech_bubble:set_role{
role_type = 'Minor',
xy_bond = 'Weak',
r_bond = 'Strong',
major = self,
}
self.children.speech_bubble.states.visible = false
end
function Card_Character:remove_button()
if self.children.button then self.children.button:remove(); self.children.button = nil end
end
function Card_Character:remove_speech_bubble()
if self.children.speech_bubble then self.children.speech_bubble:remove(); self.children.speech_bubble = nil end
end
function Card_Character:say_stuff(n, not_first)
self.talking = true
if not not_first then
G.E_MANAGER:add_event(Event({
trigger = 'after',
delay = 0.1,
func = function()
if self.children.speech_bubble then self.children.speech_bubble.states.visible = true end
self:say_stuff(n, true)
return true
end
}))
else
if n <= 0 then self.talking = false; return end
local new_said = math.random(1, 11)
while new_said == self.last_said do
new_said = math.random(1, 11)
end
self.last_said = new_said
play_sound('voice'..math.random(1, 11), G.SPEEDFACTOR*(math.random()*0.2+1), 0.5)
self.children.card:juice_up()
G.E_MANAGER:add_event(Event({
trigger = 'after',
blockable = false, blocking = false,
delay = 0.13,
func = function()
self:say_stuff(n-1, true)
return true
end
}), 'tutorial')
end
end
function Card_Character:draw(dt)
if self.highlight then
self.children.highlight:draw()
self.highlight:draw()
if self.highlight.draw_children then self.highlight:draw_children() end
end
if self.children.particles then
self.children.particles:draw()
end
if self.children.speech_bubble then
self.children.speech_bubble:draw()
end
if self.children.button and not self.talking then
self.children.button:draw()
end
if self.children.card then
self.children.card:draw()
end
add_to_drawhash(self)
self:draw_boundingrect()
end
function Card_Character:remove()
G.jimboed = nil
remove_all(self.children)
for k, v in pairs(G.I.CARD) do
if v == self then
table.remove(G.I.CARD, k)
end
end
Moveable.remove(self)
end

668
cardarea.lua Normal file
View File

@ -0,0 +1,668 @@
--Class
CardArea = Moveable:extend()
--Class Methods
function CardArea:init(X, Y, W, H, config)
Moveable.init(self, X, Y, W, H)
self.states.drag.can = false
self.states.hover.can = false
self.states.click.can = false
self.config = config or {}
self.card_w = config.card_w or G.CARD_W
self.cards = {}
self.children = {}
self.highlighted = {}
self.config.highlighted_limit = config.highlight_limit or 5
self.config.card_limit = config.card_limit or 52
self.config.temp_limit = self.config.card_limit
self.config.card_count = 0
self.config.type = config.type or 'deck'
self.config.sort = config.sort or 'desc'
self.config.lr_padding = config.lr_padding or 0.1
self.shuffle_amt = 0
if getmetatable(self) == CardArea then
table.insert(G.I.CARDAREA, self)
end
end
function CardArea:emplace(card, location, stay_flipped)
if location == 'front' or self.config.type == 'deck' then
table.insert(self.cards, 1, card)
else
self.cards[#self.cards+1] = card
end
if card.facing == 'back' and self.config.type ~= 'discard' and self.config.type ~= 'deck' and not stay_flipped then
card:flip()
end
if self == G.hand and stay_flipped then
card.ability.wheel_flipped = true
end
if #self.cards > self.config.card_limit then
if self == G.deck then
self.config.card_limit = #self.cards
end
end
card:set_card_area(self)
self:set_ranks()
self:align_cards()
if self == G.jokers then
local joker_tally = 0
for i = 1, #G.jokers.cards do
if G.jokers.cards[i].ability.set == 'Joker' then joker_tally = joker_tally + 1 end
end
if joker_tally > G.GAME.max_jokers then G.GAME.max_jokers = joker_tally end
check_for_unlock({type = 'modify_jokers'})
end
if self == G.deck then check_for_unlock({type = 'modify_deck', deck = self}) end
end
function CardArea:remove_card(card, discarded_only)
if not self.cards then return end
local _cards = discarded_only and {} or self.cards
if discarded_only then
for k, v in ipairs(self.cards) do
if v.ability and v.ability.discarded then
_cards[#_cards+1] = v
end
end
end
if self.config.type == 'discard' or self.config.type == 'deck' then
card = card or _cards[#_cards]
else
card = card or _cards[1]
end
for i = #self.cards,1,-1 do
if self.cards[i] == card then
card:remove_from_area()
table.remove(self.cards, i)
self:remove_from_highlighted(card, true)
break
end
end
self:set_ranks()
if self == G.deck then check_for_unlock({type = 'modify_deck', deck = self}) end
return card
end
function CardArea:change_size(delta)
if delta ~= 0 then
G.E_MANAGER:add_event(Event({
func = function()
self.config.real_card_limit = (self.config.real_card_limit or self.config.card_limit) + delta
self.config.card_limit = math.max(0, self.config.real_card_limit)
if delta > 0 and self.config.real_card_limit > 1 and self == G.hand and self.cards[1] and (G.STATE == G.STATES.DRAW_TO_HAND or G.STATE == G.STATES.SELECTING_HAND) then
local card_count = math.abs(delta)
for i=1, card_count do
draw_card(G.deck,G.hand, i*100/card_count,nil, nil , nil, 0.07)
G.E_MANAGER:add_event(Event({func = function() self:sort() return true end}))
end
end
if self == G.hand then check_for_unlock({type = 'min_hand_size'}) end
return true
end}))
end
end
function CardArea:can_highlight(card)
if G.CONTROLLER.HID.controller then
if self.config.type == 'hand'
then
return true
end
else
if self.config.type == 'hand' or
self.config.type == 'joker' or
self.config.type == 'consumeable' or
(self.config.type == 'shop' and self.config.highlighted_limit > 0)
then
return true
end
end
return false
end
function CardArea:add_to_highlighted(card, silent)
--if self.config.highlighted_limit <= #self.highlighted then return end
if self.config.type == 'shop' then
if self.highlighted[1] then
self:remove_from_highlighted(self.highlighted[1])
end
--if not G.FUNCS.check_for_buy_space(card) then return false end
self.highlighted[#self.highlighted+1] = card
card:highlight(true)
if not silent then play_sound('cardSlide1') end
elseif self.config.type == 'joker' or self.config.type == 'consumeable' then
if #self.highlighted >= self.config.highlighted_limit then
self:remove_from_highlighted(self.highlighted[1])
end
self.highlighted[#self.highlighted+1] = card
card:highlight(true)
if not silent then play_sound('cardSlide1') end
else
if #self.highlighted >= self.config.highlighted_limit then
card:highlight(false)
else
self.highlighted[#self.highlighted+1] = card
card:highlight(true)
if not silent then play_sound('cardSlide1') end
end
if self == G.hand and G.STATE == G.STATES.SELECTING_HAND then
self:parse_highlighted()
end
end
end
function CardArea:parse_highlighted()
G.boss_throw_hand = nil
local text,disp_text,poker_hands = G.FUNCS.get_poker_hand_info(self.highlighted)
if text == 'NULL' then
update_hand_text({immediate = true, nopulse = true, delay = 0}, {mult = 0, chips = 0, level = '', handname = ''})
else
if G.GAME.blind and G.GAME.blind:debuff_hand(self.highlighted, poker_hands, text, true) then
G.boss_throw_hand = true
else
end
local backwards = nil
for k, v in pairs(self.highlighted) do
if v.facing == 'back' then
backwards = true
end
end
if backwards then
update_hand_text({immediate = true, nopulse = nil, delay = 0}, {handname='????', level='?', mult = '?', chips = '?'})
else
update_hand_text({immediate = true, nopulse = nil, delay = 0}, {handname=disp_text, level=G.GAME.hands[text].level, mult = G.GAME.hands[text].mult, chips = G.GAME.hands[text].chips})
end
end
end
function CardArea:remove_from_highlighted(card, force)
if (not force) and card and card.ability.forced_selection and self == G.hand then return end
for i = #self.highlighted,1,-1 do
if self.highlighted[i] == card then
table.remove(self.highlighted, i)
break
end
end
card:highlight(false)
if self == G.hand and G.STATE == G.STATES.SELECTING_HAND then
self:parse_highlighted()
end
end
function CardArea:unhighlight_all()
for i = #self.highlighted,1,-1 do
if self.highlighted[i].ability.forced_selection and self == G.hand then
else
self.highlighted[i]:highlight(false)
table.remove(self.highlighted, i)
end
end
if self == G.hand and G.STATE == G.STATES.SELECTING_HAND then
self:parse_highlighted()
end
end
function CardArea:set_ranks()
for k, card in ipairs(self.cards) do
card.rank = k
card.states.collide.can = true
if k > 1 and self.config.type == 'deck' then
card.states.drag.can = false
card.states.collide.can = false
elseif self.config.type == 'play' or self.config.type == 'shop' or self.config.type == 'consumeable' then
card.states.drag.can = false
else
card.states.drag.can = true
end
end
end
function CardArea:move(dt)
--Set sliding up/down for the hand area
if self == G.hand then
local desired_y = G.TILE_H - G.hand.T.h - 1.9*((not G.deck_preview and (G.STATE == G.STATES.SELECTING_HAND or G.STATE == G.STATES.DRAW_TO_HAND)) and 1 or 0)
G.hand.T.y = 15*G.real_dt*desired_y + (1-15*G.real_dt)*G.hand.T.y
if math.abs(desired_y - G.hand.T.y) < 0.01 then G.hand.T.y = desired_y end
if G.STATE == G.STATES.TUTORIAL then
G.play.T.y = G.hand.T.y - (3 + 0.6)
end
end
Moveable.move(self, dt)
self:align_cards()
end
function CardArea:update(dt)
if self == G.hand then
if G.GAME.modifiers.minus_hand_size_per_X_dollar then
self.config.last_poll_size = self.config.last_poll_size or 0
if math.floor(G.GAME.dollars/G.GAME.modifiers.minus_hand_size_per_X_dollar) ~= self.config.last_poll_size then
self:change_size(self.config.last_poll_size - math.floor(G.GAME.dollars/G.GAME.modifiers.minus_hand_size_per_X_dollar))
self.config.last_poll_size = math.floor(G.GAME.dollars/G.GAME.modifiers.minus_hand_size_per_X_dollar)
end
end
for k, v in pairs(self.cards) do
if v.ability.forced_selection and not self.highlighted[1] then
self:add_to_highlighted(v)
end
end
end
if self == G.deck then
self.states.collide.can = true
self.states.hover.can = true
self.states.click.can = true
end
--Check and see if controller is being used
if G.CONTROLLER.HID.controller and self ~= G.hand then self:unhighlight_all() end
if self == G.deck and self.config.card_limit > #G.playing_cards then self.config.card_limit = #G.playing_cards end
self.config.temp_limit = math.max(#self.cards, self.config.card_limit)
self.config.card_count = #self.cards
end
function CardArea:draw()
if not self.states.visible then return end
if G.VIEWING_DECK and (self==G.deck or self==G.hand or self==G.play) then return end
local state = G.TAROT_INTERRUPT or G.STATE
self.ARGS.invisible_area_types = self.ARGS.invisible_area_types or {discard=1, voucher=1, play=1, consumeable=1, title = 1, title_2 = 1}
if self.ARGS.invisible_area_types[self.config.type] or
(self.config.type == 'hand' and ({[G.STATES.SHOP]=1, [G.STATES.TAROT_PACK]=1, [G.STATES.SPECTRAL_PACK]=1, [G.STATES.STANDARD_PACK]=1,[G.STATES.BUFFOON_PACK]=1,[G.STATES.PLANET_PACK]=1, [G.STATES.ROUND_EVAL]=1, [G.STATES.BLIND_SELECT]=1})[state]) or
(self.config.type == 'deck' and self ~= G.deck) or
(self.config.type == 'shop' and self ~= G.shop_vouchers) then
else
if not self.children.area_uibox then
local card_count = self ~= G.shop_vouchers and {n=G.UIT.R, config={align = self == G.jokers and 'cl' or self == G.hand and 'cm' or 'cr', padding = 0.03, no_fill = true}, nodes={
{n=G.UIT.B, config={w = 0.1,h=0.1}},
{n=G.UIT.T, config={ref_table = self.config, ref_value = 'card_count', scale = 0.3, colour = G.C.WHITE}},
{n=G.UIT.T, config={text = '/', scale = 0.3, colour = G.C.WHITE}},
{n=G.UIT.T, config={ref_table = self.config, ref_value = 'card_limit', scale = 0.3, colour = G.C.WHITE}},
{n=G.UIT.B, config={w = 0.1,h=0.1}}
}} or nil
self.children.area_uibox = UIBox{
definition =
{n=G.UIT.ROOT, config = {align = 'cm', colour = G.C.CLEAR}, nodes={
{n=G.UIT.R, config={minw = self.T.w,minh = self.T.h,align = "cm", padding = 0.1, mid = true, r = 0.1, colour = self ~= G.shop_vouchers and {0,0,0,0.1} or nil, ref_table = self}, nodes={
self == G.shop_vouchers and
{n=G.UIT.C, config={align = "cm", paddin = 0.1, func = 'shop_voucher_empty', visible = false}, nodes={
{n=G.UIT.R, config={align = "cm"}, nodes={
{n=G.UIT.T, config={text = 'DEFEAT', scale = 0.6, colour = G.C.WHITE}}
}},
{n=G.UIT.R, config={align = "cm"}, nodes={
{n=G.UIT.T, config={text = 'BOSS BLIND', scale = 0.4, colour = G.C.WHITE}}
}},
{n=G.UIT.R, config={align = "cm"}, nodes={
{n=G.UIT.T, config={text = 'TO RESTOCK', scale = 0.4, colour = G.C.WHITE}}
}},
}} or nil,
}},
card_count
}},
config = { align = 'cm', offset = {x=0,y=0}, major = self, parent = self}
}
end
self.children.area_uibox:draw()
end
self:draw_boundingrect()
add_to_drawhash(self)
self.ARGS.draw_layers = self.ARGS.draw_layers or self.config.draw_layers or {'shadow', 'card'}
for k, v in ipairs(self.ARGS.draw_layers) do
if self.config.type == 'deck' then
for i = #self.cards, 1, -1 do
if self.cards[i] ~= G.CONTROLLER.focused.target then
if i == 1 or i%(self.config.thin_draw or 9) == 0 or i == #self.cards or math.abs(self.cards[i].VT.x - self.T.x) > 1 or math.abs(self.cards[i].VT.y - self.T.y) > 1 then
if G.CONTROLLER.dragging.target ~= self.cards[i] then self.cards[i]:draw(v) end
end
end
end
end
if self.config.type == 'joker' or self.config.type == 'consumeable' or self.config.type == 'shop' or self.config.type == 'title_2' then
for i = 1, #self.cards do
if self.cards[i] ~= G.CONTROLLER.focused.target then
if not self.cards[i].highlighted then
if G.CONTROLLER.dragging.target ~= self.cards[i] then self.cards[i]:draw(v) end
end
end
end
for i = 1, #self.cards do
if self.cards[i] ~= G.CONTROLLER.focused.target then
if self.cards[i].highlighted then
if G.CONTROLLER.dragging.target ~= self.cards[i] then self.cards[i]:draw(v) end
end
end
end
end
if self.config.type == 'discard' then
for i = 1, #self.cards do
if self.cards[i] ~= G.CONTROLLER.focused.target then
if math.abs(self.cards[i].VT.x - self.T.x) > 1 then
if G.CONTROLLER.dragging.target ~= self.cards[i] then self.cards[i]:draw(v) end
end
end
end
end
if self.config.type == 'hand' or self.config.type == 'play' or self.config.type == 'title' or self.config.type == 'voucher' then
for i = 1, #self.cards do
if self.cards[i] ~= G.CONTROLLER.focused.target or self == G.hand then
if G.CONTROLLER.dragging.target ~= self.cards[i] then self.cards[i]:draw(v) end
end
end
end
end
if self == G.deck then
if G.CONTROLLER.HID.controller and G.STATE == G.STATES.SELECTING_HAND and not self.children.peek_deck then
self.children.peek_deck = UIBox{
definition =
{n=G.UIT.ROOT, config = {align = 'cm', padding = 0.1, r =0.1, colour = G.C.CLEAR}, nodes={
{n=G.UIT.R, config={align = "cm", r =0.1, colour = adjust_alpha(G.C.L_BLACK, 0.5),func = 'set_button_pip', focus_args = {button = 'triggerleft', orientation = 'bm', scale = 0.6, type = 'none'}}, nodes={
{n=G.UIT.R, config={align = "cm"}, nodes={
{n=G.UIT.T, config={text = 'PEEK', scale = 0.48, colour = G.C.WHITE, shadow = true}}
}},
{n=G.UIT.R, config={align = "cm"}, nodes={
{n=G.UIT.T, config={text = 'DECK', scale = 0.38, colour = G.C.WHITE, shadow = true}}
}},
}},
}},
config = { align = 'cl', offset = {x=-0.5,y=0.1}, major = self, parent = self}
}
self.children.peek_deck.states.collide.can = false
elseif (not G.CONTROLLER.HID.controller or G.STATE ~= G.STATES.SELECTING_HAND) and self.children.peek_deck then
self.children.peek_deck:remove()
self.children.peek_deck = nil
end
if not self.children.view_deck then
self.children.view_deck = UIBox{
definition =
{n=G.UIT.ROOT, config = {align = 'cm', padding = 0.1, r =0.1, colour = G.C.CLEAR}, nodes={
{n=G.UIT.R, config={align = "cm", padding = 0.05, r =0.1, colour = adjust_alpha(G.C.BLACK, 0.5),func = 'set_button_pip', focus_args = {button = 'triggerright', orientation = 'bm', scale = 0.6}, button = 'deck_info'}, nodes={
{n=G.UIT.R, config={align = "cm", maxw = 2}, nodes={
{n=G.UIT.T, config={text = localize('k_view'), scale = 0.48, colour = G.C.WHITE, shadow = true}}
}},
{n=G.UIT.R, config={align = "cm", maxw = 2}, nodes={
{n=G.UIT.T, config={text = localize('k_deck'), scale = 0.38, colour = G.C.WHITE, shadow = true}}
}},
}},
}},
config = { align = 'cm', offset = {x=0,y=0}, major = self.cards[1] or self, parent = self}
}
self.children.view_deck.states.collide.can = false
end
if G.deck_preview or self.states.collide.is or (G.buttons and G.buttons.states.collide.is and G.CONTROLLER.HID.controller) then self.children.view_deck:draw() end
if self.children.peek_deck then self.children.peek_deck:draw() end
end
end
function CardArea:align_cards()
if (self == G.hand or self == G.deck or self == G.discard or self == G.play) and G.view_deck and G.view_deck[1] and G.view_deck[1].cards then return end
if self.config.type == 'deck' then
local deck_height = (self.config.deck_height or 0.15)/52
for k, card in ipairs(self.cards) do
if card.facing == 'front' then card:flip() end
if not card.states.drag.is then
card.T.x = self.T.x + 0.5*(self.T.w - card.T.w) + self.shadow_parrallax.x*deck_height*(#self.cards/(self == G.deck and 1 or 2) - k) + 0.9*self.shuffle_amt*(1 - k*0.01)*(k%2 == 1 and 1 or -0)
card.T.y = self.T.y + 0.5*(self.T.h - card.T.h) + self.shadow_parrallax.y*deck_height*(#self.cards/(self == G.deck and 1 or 2) - k)
card.T.r = 0 + 0.3*self.shuffle_amt*(1 + k*0.05)*(k%2 == 1 and 1 or -0)
card.T.x = card.T.x + card.shadow_parrallax.x/30
end
end
end
if self.config.type == 'discard' then
for k, card in ipairs(self.cards) do
if card.facing == 'front' then card:flip() end
if not card.states.drag.is then
card.T.x = self.T.x + (self.T.w - card.T.w)*card.discard_pos.x
card.T.y = self.T.y + (self.T.h - card.T.h)* card.discard_pos.y
card.T.r = card.discard_pos.r
end
end
end
if self.config.type == 'hand' and (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK or G.STATE == G.STATES.PLANET_PACK) then
for k, card in ipairs(self.cards) do
if not card.states.drag.is then
card.T.r = 0.4*(-#self.cards/2 - 0.5 + k)/(#self.cards)+ 0.02*math.sin(2*G.TIMERS.REAL+card.T.x)
local max_cards = math.max(#self.cards, self.config.temp_limit)
card.T.x = self.T.x + (self.T.w-self.card_w)*((k-1)/math.max(max_cards-1, 1) - 0.5*(#self.cards-max_cards)/math.max(max_cards-1, 1)) + 0.5*(self.card_w - card.T.w)
local highlight_height = G.HIGHLIGHT_H
if not card.highlighted then highlight_height = 0 end
card.T.y = G.hand.T.y - 1.8*card.T.h - highlight_height + 0.1*math.sin(0.666*G.TIMERS.REAL+card.T.x) + math.abs(1.3*(-#self.cards/2 + k-0.5)/(#self.cards))^2-0.3
card.T.x = card.T.x + card.shadow_parrallax.x/30
end
end
table.sort(self.cards, function (a, b) return a.T.x + a.T.w/2 < b.T.x + b.T.w/2 end)
end
if self.config.type == 'hand' and not (G.STATE == G.STATES.TAROT_PACK or G.STATE == G.STATES.SPECTRAL_PACK or G.STATE == G.STATES.PLANET_PACK) then
for k, card in ipairs(self.cards) do
if not card.states.drag.is then
card.T.r = 0.2*(-#self.cards/2 - 0.5 + k)/(#self.cards)+ 0.02*math.sin(2*G.TIMERS.REAL+card.T.x)
local max_cards = math.max(#self.cards, self.config.temp_limit)
card.T.x = self.T.x + (self.T.w-self.card_w)*((k-1)/math.max(max_cards-1, 1) - 0.5*(#self.cards-max_cards)/math.max(max_cards-1, 1)) + 0.5*(self.card_w - card.T.w)
local highlight_height = G.HIGHLIGHT_H
if not card.highlighted then highlight_height = 0 end
card.T.y = self.T.y + self.T.h/2 - card.T.h/2 - highlight_height + 0.03*math.sin(0.666*G.TIMERS.REAL+card.T.x) + math.abs(0.5*(-#self.cards/2 + k-0.5)/(#self.cards))-0.2
card.T.x = card.T.x + card.shadow_parrallax.x/30
end
end
table.sort(self.cards, function (a, b) return a.T.x + a.T.w/2 < b.T.x + b.T.w/2 end)
end
if self.config.type == 'title' or (self.config.type == 'voucher' and #self.cards == 1) then
for k, card in ipairs(self.cards) do
if not card.states.drag.is then
card.T.r = 0.2*(-#self.cards/2 - 0.5 + k)/(#self.cards)+ 0.02*math.sin(2*G.TIMERS.REAL+card.T.x)
local max_cards = math.max(#self.cards, self.config.temp_limit)
card.T.x = self.T.x + (self.T.w-self.card_w)*((k-1)/math.max(max_cards-1, 1) - 0.5*(#self.cards-max_cards)/math.max(max_cards-1, 1)) + 0.5*(self.card_w - card.T.w)
local highlight_height = G.HIGHLIGHT_H
if not card.highlighted then highlight_height = 0 end
card.T.y = self.T.y + self.T.h/2 - card.T.h/2 - highlight_height + 0.03*math.sin(0.666*G.TIMERS.REAL+card.T.x) + math.abs(0.5*(-#self.cards/2 + k-0.5)/(#self.cards))-(#self.cards>1 and 0.2 or 0)
card.T.x = card.T.x + card.shadow_parrallax.x/30
end
end
table.sort(self.cards, function (a, b) return a.T.x + a.T.w/2 < b.T.x + b.T.w/2 end)
end
if self.config.type == 'voucher' and #self.cards > 1 then
local self_w = math.max(self.T.w, 3.2)
for k, card in ipairs(self.cards) do
if not card.states.drag.is then
card.T.r = 0.2*(-#self.cards/2 - 0.5 + k)/(#self.cards)+ 0.02*math.sin(2*G.TIMERS.REAL+card.T.x+card.T.y) + (k%2 == 0 and 1 or -1)*0.08
local max_cards = math.max(#self.cards, self.config.temp_limit)
card.T.x = self.T.x + (self_w-self.card_w)*((k-1)/math.max(max_cards-1, 1) - 0.5*(#self.cards-max_cards)/math.max(max_cards-1, 1)) + 0.5*(self.card_w - card.T.w) + (k%2 == 1 and 1 or -1)*0.27 + (self.T.w-self_w)/2
local highlight_height = G.HIGHLIGHT_H
if not card.highlighted then highlight_height = 0 end
card.T.y = self.T.y + self.T.h/2 - card.T.h/2 - highlight_height + 0.03*math.sin(0.666*G.TIMERS.REAL+card.T.x) + math.abs(0.5*(-#self.cards/2 + k-0.5)/(#self.cards))-(#self.cards>1 and 0.2 or 0)
card.T.x = card.T.x + card.shadow_parrallax.x/30
end
end
table.sort(self.cards, function (a, b) return a.ability.order < b.ability.order end)
end
if self.config.type == 'play' or self.config.type == 'shop' then
for k, card in ipairs(self.cards) do
if not card.states.drag.is then
card.T.r = 0
local max_cards = math.max(#self.cards, self.config.temp_limit)
card.T.x = self.T.x + (self.T.w-self.card_w)*((k-1)/math.max(max_cards-1, 1) - 0.5*(#self.cards-max_cards)/math.max(max_cards-1, 1)) + 0.5*(self.card_w - card.T.w) + (self.config.card_limit == 1 and 0.5*(self.T.w - card.T.w) or 0)
local highlight_height = G.HIGHLIGHT_H
if not card.highlighted then highlight_height = 0 end
card.T.y = self.T.y + self.T.h/2 - card.T.h/2 - highlight_height
card.T.x = card.T.x + card.shadow_parrallax.x/30
end
end
table.sort(self.cards, function (a, b) return a.T.x + a.T.w/2 < b.T.x + b.T.w/2 end)
end
if self.config.type == 'joker' or self.config.type == 'title_2' then
for k, card in ipairs(self.cards) do
if not card.states.drag.is then
card.T.r = 0.1*(-#self.cards/2 - 0.5 + k)/(#self.cards)+ 0.02*math.sin(2*G.TIMERS.REAL+card.T.x)
local max_cards = math.max(#self.cards, self.config.temp_limit)
card.T.x = self.T.x + (self.T.w-self.card_w)*((k-1)/math.max(max_cards-1, 1) - 0.5*(#self.cards-max_cards)/math.max(max_cards-1, 1)) + 0.5*(self.card_w - card.T.w)
if #self.cards > 2 or (#self.cards > 1 and self == G.consumeables) or (#self.cards > 1 and self.config.spread) then
card.T.x = self.T.x + (self.T.w-self.card_w)*((k-1)/(#self.cards-1)) + 0.5*(self.card_w - card.T.w)
elseif #self.cards > 1 and self ~= G.consumeables then
card.T.x = self.T.x + (self.T.w-self.card_w)*((k - 0.5)/(#self.cards)) + 0.5*(self.card_w - card.T.w)
else
card.T.x = self.T.x + self.T.w/2 - self.card_w/2 + 0.5*(self.card_w - card.T.w)
end
local highlight_height = G.HIGHLIGHT_H/2
if not card.highlighted then highlight_height = 0 end
card.T.y = self.T.y + self.T.h/2 - card.T.h/2 - highlight_height+ 0.03*math.sin(0.666*G.TIMERS.REAL+card.T.x)
card.T.x = card.T.x + card.shadow_parrallax.x/30
end
end
table.sort(self.cards, function (a, b) return a.T.x + a.T.w/2 - 100*(a.pinned and a.sort_id or 0) < b.T.x + b.T.w/2 - 100*(b.pinned and b.sort_id or 0) end)
end
if self.config.type == 'consumeable'then
for k, card in ipairs(self.cards) do
if not card.states.drag.is then
if #self.cards > 1 then
card.T.x = self.T.x + (self.T.w-self.card_w)*((k-1)/(#self.cards-1)) + 0.5*(self.card_w - card.T.w)
else
card.T.x = self.T.x + self.T.w/2 - self.card_w/2 + 0.5*(self.card_w - card.T.w)
end
local highlight_height = G.HIGHLIGHT_H
if not card.highlighted then highlight_height = 0 end
card.T.y = self.T.y + self.T.h/2 - card.T.h/2 - highlight_height + (not card.highlighted and 0.05*math.sin(2*1.666*G.TIMERS.REAL+card.T.x) or 0)
card.T.x = card.T.x + card.shadow_parrallax.x/30
end
end
table.sort(self.cards, function (a, b) return a.T.x + a.T.w/2 < b.T.x + b.T.w/2 end)
end
for k, card in ipairs(self.cards) do
card.rank = k
end
if self.children.view_deck then
self.children.view_deck:set_role{major = self.cards[1] or self}
end
end
function CardArea:hard_set_T(X, Y, W, H)
local x = (X or self.T.x)
local y = (Y or self.T.y)
local w = (W or self.T.w)
local h = (H or self.T.h)
Moveable.hard_set_T(self,x, y, w, h)
self:calculate_parrallax()
self:align_cards()
self:hard_set_cards()
end
function CardArea:hard_set_cards()
for k, card in pairs(self.cards) do
card:hard_set_T()
card:calculate_parrallax()
end
end
function CardArea:shuffle(_seed)
pseudoshuffle(self.cards, pseudoseed(_seed or 'shuffle'))
self:set_ranks()
end
function CardArea:sort(method)
self.config.sort = method or self.config.sort
if self.config.sort == 'desc' then
table.sort(self.cards, function (a, b) return a:get_nominal() > b:get_nominal() end )
elseif self.config.sort == 'asc' then
table.sort(self.cards, function (a, b) return a:get_nominal() < b:get_nominal() end )
elseif self.config.sort == 'suit desc' then
table.sort(self.cards, function (a, b) return a:get_nominal('suit') > b:get_nominal('suit') end )
elseif self.config.sort == 'suit asc' then
table.sort(self.cards, function (a, b) return a:get_nominal('suit') < b:get_nominal('suit') end )
elseif self.config.sort == 'order' then
table.sort(self.cards, function (a, b) return (a.config.card.order or a.config.center.order) < (b.config.card.order or b.config.center.order) end )
end
end
function CardArea:draw_card_from(area, stay_flipped, discarded_only)
if area:is(CardArea) then
if #self.cards < self.config.card_limit or self == G.deck or self == G.hand then
local card = area:remove_card(nil, discarded_only)
if card then
if area == G.discard then
card.T.r = 0
end
local stay_flipped = G.GAME and G.GAME.blind and G.GAME.blind:stay_flipped(self, card)
if (self == G.hand) and G.GAME.modifiers.flipped_cards then
if pseudorandom(pseudoseed('flipped_card')) < 1/G.GAME.modifiers.flipped_cards then
stay_flipped = true
end
end
self:emplace(card, nil, stay_flipped)
return true
end
end
end
end
function CardArea:click()
if self == G.deck then
G.FUNCS.deck_info()
end
end
function CardArea:save()
if not self.cards then return end
local cardAreaTable = {
cards = {},
config = self.config,
}
for i = 1, #self.cards do
cardAreaTable.cards[#cardAreaTable.cards + 1] = self.cards[i]:save()
end
return cardAreaTable
end
function CardArea:load(cardAreaTable)
if self.cards then remove_all(self.cards) end
self.cards = {}
if self.children then remove_all(self.children) end
self.children = {}
self.config = cardAreaTable.config
for i = 1, #cardAreaTable.cards do
loading = true
local card = Card(0, 0, G.CARD_W, G.CARD_H, G.P_CENTERS.j_joker, G.P_CENTERS.c_base)
loading = nil
card:load(cardAreaTable.cards[i])
self.cards[#self.cards + 1] = card
if card.highlighted then
self.highlighted[#self.highlighted + 1] = card
end
card:set_card_area(self)
end
self:set_ranks()
self:align_cards()
self:hard_set_cards()
end
function CardArea:remove()
if self.cards then remove_all(self.cards) end
self.cards = nil
if self.children then remove_all(self.children) end
self.children = nil
for k, v in pairs(G.I.CARDAREA) do
if v == self then
table.remove(G.I.CARDAREA, k)
end
end
Moveable.remove(self)
end

729
challenges.lua Normal file
View File

@ -0,0 +1,729 @@
G.CHALLENGES = {
--[[{
name = 'TEST',
id = 'c_test_1',
rules = {
custom = {
--{id = 'no_reward'},
{id = 'no_reward_specific', value = 'Big'},
{id = 'no_extra_hand_money'},
{id = 'no_interest'},
{id = 'daily'},
{id = 'set_seed', value = 'SEEDEEDS'},
},
modifiers = {
{id = 'dollars', value = 100},
{id = 'discards', value = 1},
{id = 'hands', value = 6},
{id = 'reroll_cost', value = 10},
{id = 'joker_slots', value = 8},
{id = 'consumable_slots', value = 3},
{id = 'hand_size', value = 5},
}
},
jokers = {
{id = 'j_egg'},
{id = 'j_egg'},
{id = 'j_egg'},
{id = 'j_egg'},
{id = 'j_egg', edition = 'foil', eternal = true}
},
consumeables = {
{id = 'c_sigil'}
},
vouchers = {
{id = 'v_hieroglyph'},
},
deck = {
--enhancement = 'm_glass',
--edition = 'foil',
--gold_seal = true,
--yes_ranks = {['3'] = true,T = true},
--no_ranks = {['4'] = true},
--yes_suits = {S=true},
--no_suits = {D=true},
cards = {{s='D',r='2',e='m_glass',},{s='D',r='3',e='m_glass',},{s='D',r='4',e='m_glass',},{s='D',r='5',e='m_glass',},{s='D',r='6',e='m_glass',},{s='D',r='7',e='m_glass',},{s='D',r='8',e='m_glass',},{s='D',r='9',e='m_glass',},{s='D',r='T',e='m_glass',},{s='D',r='J',e='m_glass',},{s='D',r='Q',e='m_glass',},{s='D',r='K',e='m_glass',},{s='D',r='A',e='m_glass',},{s='C',r='2',e='m_glass',},{s='C',r='3',e='m_glass',},{s='C',r='4',e='m_glass',},{s='C',r='5',e='m_glass',},{s='C',r='6',e='m_glass',},{s='C',r='7',e='m_glass',},{s='C',r='8',e='m_glass',},{s='C',r='9',e='m_glass',},{s='C',r='T',e='m_glass',},{s='C',r='J',e='m_glass',},{s='C',r='Q',e='m_glass',},{s='C',r='K',e='m_glass',},{s='C',r='A',e='m_glass',},{s='H',r='2',e='m_glass',},{s='H',r='3',e='m_glass',},{s='H',r='4',e='m_glass',},{s='H',r='5',e='m_glass',},{s='H',r='6',e='m_glass',},{s='H',r='7',e='m_glass',},{s='H',r='8',e='m_glass',},{s='H',r='9',e='m_glass',},{s='H',r='T',e='m_glass',},{s='H',r='J',e='m_glass',},{s='H',r='Q',e='m_glass',},{s='H',r='K',e='m_glass',},{s='H',r='A',e='m_glass',},{s='S',r='2',e='m_glass',},{s='S',r='3',e='m_glass',},{s='S',r='4',e='m_glass',},{s='S',r='5',e='m_glass',},{s='S',r='6',e='m_glass',},{s='S',r='7',e='m_glass',},{s='S',r='8',e='m_glass',},{s='S',r='9',e='m_glass',},{s='S',r='T',e='m_glass',},{s='S',r='J',e='m_glass',},{s='S',r='Q',e='m_glass',},{s='S',r='K',e='m_glass',},{s='S',r='A',e='m_glass',},},
type = 'Challenge Deck'
},
restrictions = {
banned_cards = {
{id = 'j_joker'},
{id = 'j_egg'},
},
banned_tags = {
{id = 'tag_garbage'},
{id = 'tag_handy'},
},
banned_other = {
}
}
},]]--
{
name = 'The Omelette',
id = 'c_omelette_1',
rules = {
custom = {
{id = 'no_reward'},
{id = 'no_extra_hand_money'},
{id = 'no_interest'}
},
modifiers = {
}
},
jokers = {
{id = 'j_egg'},
{id = 'j_egg'},
{id = 'j_egg'},
{id = 'j_egg'},
{id = 'j_egg'},
},
consumeables = {
},
vouchers = {
},
deck = {
type = 'Challenge Deck'
},
restrictions = {
banned_cards = {
{id = 'v_seed_money'},
{id = 'v_money_tree'},
{id = 'j_to_the_moon'},
{id = 'j_rocket'},
{id = 'j_golden'},
{id = 'j_satellite'},
},
banned_tags = {
},
banned_other = {
}
}
},
{
name = "15 Minute City",
id = 'c_city_1',
rules = {
custom = {
},
modifiers = {
}
},
jokers = {
{id = 'j_ride_the_bus', eternal = true},
{id = 'j_shortcut', eternal = true},
},
consumeables = {
},
vouchers = {
},
deck = {
cards = {{s='D',r='4',},{s='D',r='5',},{s='D',r='6',},{s='D',r='7',},{s='D',r='8',},{s='D',r='9',},{s='D',r='T',},{s='D',r='J',},{s='D',r='Q',},{s='D',r='K',},{s='D',r='J',},{s='D',r='Q',},{s='D',r='K',},{s='C',r='4',},{s='C',r='5',},{s='C',r='6',},{s='C',r='7',},{s='C',r='8',},{s='C',r='9',},{s='C',r='T',},{s='C',r='J',},{s='C',r='Q',},{s='C',r='K',},{s='C',r='J',},{s='C',r='Q',},{s='C',r='K',},{s='H',r='4',},{s='H',r='5',},{s='H',r='6',},{s='H',r='7',},{s='H',r='8',},{s='H',r='9',},{s='H',r='T',},{s='H',r='J',},{s='H',r='Q',},{s='H',r='K',},{s='H',r='J',},{s='H',r='Q',},{s='H',r='K',},{s='S',r='4',},{s='S',r='5',},{s='S',r='6',},{s='S',r='7',},{s='S',r='8',},{s='S',r='9',},{s='S',r='T',},{s='S',r='J',},{s='S',r='Q',},{s='S',r='K',},{s='S',r='J',},{s='S',r='Q',},{s='S',r='K',}},
type = 'Challenge Deck'
},
restrictions = {
banned_cards = {
},
banned_tags = {
},
banned_other = {
}
}
},
{
name = "Rich get Richer",
id = 'c_rich_1',
rules = {
custom = {
{id = 'chips_dollar_cap'},
},
modifiers = {
{id = 'dollars', value = 100},
}
},
jokers = {
},
consumeables = {
},
vouchers = {
{id = 'v_seed_money'},
{id = 'v_money_tree'},
},
deck = {
type = 'Challenge Deck'
},
restrictions = {
banned_cards = {
},
banned_tags = {
},
banned_other = {
}
}
},
{
name = "On a Knife's Edge",
id = 'c_knife_1',
rules = {
custom = {
},
modifiers = {
}
},
jokers = {
{id = 'j_ceremonial', eternal = true, pinned = true},
},
consumeables = {
},
vouchers = {
},
deck = {
type = 'Challenge Deck'
},
restrictions = {
banned_cards = {
},
banned_tags = {
},
banned_other = {
}
}
},
{
name = "X-ray Vision",
id = 'c_xray_1',
rules = {
custom = {
{id = 'flipped_cards', value = 4},
},
modifiers = {
}
},
jokers = {
},
consumeables = {
},
vouchers = {
},
deck = {
type = 'Challenge Deck'
},
restrictions = {
banned_cards = {
},
banned_tags = {
},
banned_other = {
}
}
},
{
name = "Mad World",
id = 'c_mad_world_1',
rules = {
custom = {
{id = 'no_extra_hand_money'},
{id = 'no_interest'},
},
modifiers = {
}
},
jokers = {
{id = 'j_pareidolia', edition = 'negative', eternal = true},
{id = 'j_business', eternal = true},
},
consumeables = {
},
vouchers = {
},
deck = {
cards = {{s='D',r='2',},{s='D',r='3',},{s='D',r='4',},{s='D',r='5',},{s='D',r='6',},{s='D',r='7',},{s='D',r='8',},{s='D',r='9',},{s='C',r='2',},{s='C',r='3',},{s='C',r='4',},{s='C',r='5',},{s='C',r='6',},{s='C',r='7',},{s='C',r='8',},{s='C',r='9',},{s='H',r='2',},{s='H',r='3',},{s='H',r='4',},{s='H',r='5',},{s='H',r='6',},{s='H',r='7',},{s='H',r='8',},{s='H',r='9',},{s='S',r='2',},{s='S',r='3',},{s='S',r='4',},{s='S',r='5',},{s='S',r='6',},{s='S',r='7',},{s='S',r='8',},{s='S',r='9',}},
type = 'Challenge Deck'
},
restrictions = {
banned_cards = {
},
banned_tags = {
},
banned_other = {
}
}
},
{
name = "Luxury Tax",
id = 'c_luxury_1',
rules = {
custom = {
{id = 'minus_hand_size_per_X_dollar', value = 5},
},
modifiers = {
{id = 'hand_size', value = 10},
}
},
jokers = {
},
consumeables = {
},
vouchers = {
},
deck = {
type = 'Challenge Deck'
},
restrictions = {
banned_cards = {
},
banned_tags = {
},
banned_other = {
}
}
},
{
name = "Non-Perishable",
id = 'c_non_perishable_1',
rules = {
custom = {
{id = 'all_eternal'},
},
modifiers = {
}
},
jokers = {
},
consumeables = {
},
vouchers = {
},
deck = {
type = 'Challenge Deck'
},
restrictions = {
banned_cards = {
{id = 'j_gros_michel'},
{id = 'j_ice_cream'},
{id = 'j_cavendish'},
{id = 'j_turtle_bean'},
{id = 'j_ramen'},
{id = 'j_diet_cola'},
{id = 'j_selzer'},
{id = 'j_popcorn'},
{id = 'j_mr_bones'},
{id = 'j_invisible'},
{id = 'j_luchador'},
},
banned_tags = {
},
banned_other = {
}
}
},
{
name = "Medusa",
id = 'c_medusa_1',
rules = {
custom = {
},
modifiers = {
}
},
jokers = {
{id = 'j_marble', eternal = true},
},
consumeables = {
},
vouchers = {
},
deck = {
type = 'Challenge Deck',
cards = {{s='D',r='2',},{s='D',r='3',},{s='D',r='4',},{s='D',r='5',},{s='D',r='6',},{s='D',r='7',},{s='D',r='8',},{s='D',r='9',},{s='D',r='T',},{s='D',r='J',e='m_stone',},{s='D',r='Q',e='m_stone',},{s='D',r='K',e='m_stone',},{s='D',r='A',},{s='C',r='2',},{s='C',r='3',},{s='C',r='4',},{s='C',r='5',},{s='C',r='6',},{s='C',r='7',},{s='C',r='8',},{s='C',r='9',},{s='C',r='T',},{s='C',r='J',e='m_stone',},{s='C',r='Q',e='m_stone',},{s='C',r='K',e='m_stone',},{s='C',r='A',},{s='H',r='2',},{s='H',r='3',},{s='H',r='4',},{s='H',r='5',},{s='H',r='6',},{s='H',r='7',},{s='H',r='8',},{s='H',r='9',},{s='H',r='T',},{s='H',r='J',e='m_stone',},{s='H',r='Q',e='m_stone',},{s='H',r='K',e='m_stone',},{s='H',r='A',},{s='S',r='2',},{s='S',r='3',},{s='S',r='4',},{s='S',r='5',},{s='S',r='6',},{s='S',r='7',},{s='S',r='8',},{s='S',r='9',},{s='S',r='T',},{s='S',r='J',e='m_stone',},{s='S',r='Q',e='m_stone',},{s='S',r='K',e='m_stone',},{s='S',r='A',}, }
},
restrictions = {
banned_cards = {
},
banned_tags = {
},
banned_other = {
}
}
},
{
name = "Double or Nothing",
id = 'c_double_nothing_1',
rules = {
custom = {
{id = 'debuff_played_cards'},
},
modifiers = {
}
},
jokers = {
},
consumeables = {
},
vouchers = {
},
deck = {
type = 'Challenge Deck',
cards = {{s='D',r='2',g='Red',},{s='D',r='3',g='Red',},{s='D',r='4',g='Red',},{s='D',r='5',g='Red',},{s='D',r='6',g='Red',},{s='D',r='7',g='Red',},{s='D',r='8',g='Red',},{s='D',r='9',g='Red',},{s='D',r='T',g='Red',},{s='D',r='J',g='Red',},{s='D',r='Q',g='Red',},{s='D',r='K',g='Red',},{s='D',r='A',g='Red',},{s='C',r='2',g='Red',},{s='C',r='3',g='Red',},{s='C',r='4',g='Red',},{s='C',r='5',g='Red',},{s='C',r='6',g='Red',},{s='C',r='7',g='Red',},{s='C',r='8',g='Red',},{s='C',r='9',g='Red',},{s='C',r='T',g='Red',},{s='C',r='J',g='Red',},{s='C',r='Q',g='Red',},{s='C',r='K',g='Red',},{s='C',r='A',g='Red',},{s='H',r='2',g='Red',},{s='H',r='3',g='Red',},{s='H',r='4',g='Red',},{s='H',r='5',g='Red',},{s='H',r='6',g='Red',},{s='H',r='7',g='Red',},{s='H',r='8',g='Red',},{s='H',r='9',g='Red',},{s='H',r='T',g='Red',},{s='H',r='J',g='Red',},{s='H',r='Q',g='Red',},{s='H',r='K',g='Red',},{s='H',r='A',g='Red',},{s='S',r='2',g='Red',},{s='S',r='3',g='Red',},{s='S',r='4',g='Red',},{s='S',r='5',g='Red',},{s='S',r='6',g='Red',},{s='S',r='7',g='Red',},{s='S',r='8',g='Red',},{s='S',r='9',g='Red',},{s='S',r='T',g='Red',},{s='S',r='J',g='Red',},{s='S',r='Q',g='Red',},{s='S',r='K',g='Red',},{s='S',r='A',g='Red',},}
},
restrictions = {
banned_cards = {
},
banned_tags = {
},
banned_other = {
}
}
},
{
name = "Typecast",
id = 'c_typecast_1',
rules = {
custom = {
{id = 'set_eternal_ante', value = 4},
{id = 'set_joker_slots_ante', value = 4},
},
modifiers = {
}
},
jokers = {
},
consumeables = {
},
vouchers = {
},
deck = {
type = 'Challenge Deck'
},
restrictions = {
banned_cards = {
},
banned_tags = {
},
banned_other = {
}
}
},
{
name = "Inflation",
id = 'c_inflation_1',
rules = {
custom = {
{id = 'inflation'},
},
modifiers = {
}
},
jokers = {
{id = 'j_credit_card'},
},
consumeables = {
},
vouchers = {
},
deck = {
type = 'Challenge Deck'
},
restrictions = {
banned_cards = {
{id = 'v_clearance_sale'},
{id = 'v_liquidation'},
},
banned_tags = {
},
banned_other = {
}
}
},
{
name = "Bram Poker",
id = 'c_bram_poker_1',
rules = {
custom = {
{id = 'no_shop_jokers'},
},
modifiers = {
}
},
jokers = {
{id = 'j_vampire', eternal = true},
},
consumeables = {
{id = 'c_empress'},
{id = 'c_emperor'},
},
vouchers = {
{id = 'v_magic_trick'},
{id = 'v_illusion'},
},
deck = {
type = 'Challenge Deck',
},
restrictions = {
banned_cards = {
},
banned_tags = {
},
banned_other = {
}
}
},
{
name = "Fragile",
id = 'c_fragile_1',
rules = {
custom = {
},
modifiers = {
}
},
jokers = {
{id = 'j_oops', eternal = true, edition = 'negative'},
{id = 'j_oops', eternal = true, edition = 'negative'},
},
consumeables = {
},
vouchers = {
},
deck = {
cards = {{s='D',r='2',e='m_glass',},{s='D',r='3',e='m_glass',},{s='D',r='4',e='m_glass',},{s='D',r='5',e='m_glass',},{s='D',r='6',e='m_glass',},{s='D',r='7',e='m_glass',},{s='D',r='8',e='m_glass',},{s='D',r='9',e='m_glass',},{s='D',r='T',e='m_glass',},{s='D',r='J',e='m_glass',},{s='D',r='Q',e='m_glass',},{s='D',r='K',e='m_glass',},{s='D',r='A',e='m_glass',},{s='C',r='2',e='m_glass',},{s='C',r='3',e='m_glass',},{s='C',r='4',e='m_glass',},{s='C',r='5',e='m_glass',},{s='C',r='6',e='m_glass',},{s='C',r='7',e='m_glass',},{s='C',r='8',e='m_glass',},{s='C',r='9',e='m_glass',},{s='C',r='T',e='m_glass',},{s='C',r='J',e='m_glass',},{s='C',r='Q',e='m_glass',},{s='C',r='K',e='m_glass',},{s='C',r='A',e='m_glass',},{s='H',r='2',e='m_glass',},{s='H',r='3',e='m_glass',},{s='H',r='4',e='m_glass',},{s='H',r='5',e='m_glass',},{s='H',r='6',e='m_glass',},{s='H',r='7',e='m_glass',},{s='H',r='8',e='m_glass',},{s='H',r='9',e='m_glass',},{s='H',r='T',e='m_glass',},{s='H',r='J',e='m_glass',},{s='H',r='Q',e='m_glass',},{s='H',r='K',e='m_glass',},{s='H',r='A',e='m_glass',},{s='S',r='2',e='m_glass',},{s='S',r='3',e='m_glass',},{s='S',r='4',e='m_glass',},{s='S',r='5',e='m_glass',},{s='S',r='6',e='m_glass',},{s='S',r='7',e='m_glass',},{s='S',r='8',e='m_glass',},{s='S',r='9',e='m_glass',},{s='S',r='T',e='m_glass',},{s='S',r='J',e='m_glass',},{s='S',r='Q',e='m_glass',},{s='S',r='K',e='m_glass',},{s='S',r='A',e='m_glass',},},
type = 'Challenge Deck'
},
restrictions = {
banned_cards = {
{id = 'c_magician'},
{id = 'c_empress'},
{id = 'c_heirophant'},
{id = 'c_chariot'},
{id = 'c_devil'},
{id = 'c_tower'},
{id = 'c_lovers'},
{id = 'c_incantation'},
{id = 'c_grim'},
{id = 'c_familiar'},
{id = 'p_standard_normal_1', ids = {
'p_standard_normal_1','p_standard_normal_2','p_standard_normal_3','p_standard_normal_4','p_standard_jumbo_1','p_standard_jumbo_2','p_standard_mega_1','p_standard_mega_2',
}},
{id = 'j_marble'},
{id = 'j_vampire'},
{id = 'j_midas_mask'},
{id = 'j_certificate'},
{id = 'v_magic_trick'},
{id = 'v_illusion'},
},
banned_tags = {
{id = 'tag_standard'},
},
banned_other = {
}
}
},
{
name = "Monolith",
id = 'c_monolith_1',
rules = {
custom = {
},
modifiers = {
}
},
jokers = {
{id = 'j_obelisk', eternal = true},
{id = 'j_marble', eternal = true, edition = 'negative'},
},
consumeables = {
},
vouchers = {
},
deck = {
type = 'Challenge Deck'
},
restrictions = {
banned_cards = {
},
banned_tags = {
},
banned_other = {
}
}
},
{
name = "Blast Off",
id = 'c_blast_off_1',
rules = {
custom = {
},
modifiers = {
{id = 'hands', value = 2},
{id = 'discards', value = 2},
{id = 'joker_slots', value = 4},
}
},
jokers = {
{id = 'j_constellation', eternal = true},
{id = 'j_rocket', eternal = true},
},
consumeables = {
},
vouchers = {
{id = 'v_planet_merchant'},
{id = 'v_planet_tycoon'},
},
deck = {
type = 'Challenge Deck'
},
restrictions = {
banned_cards = {
{id = 'v_grabber'},
{id = 'v_nacho_tong'},
{id = 'j_burglar'},
},
banned_tags = {
},
banned_other = {
}
}
},
{
name = "Five-Card Draw",
id = 'c_five_card_1',
rules = {
custom = {
},
modifiers = {
{id = 'hand_size', value = 5},
{id = 'joker_slots', value = 7},
{id = 'discards', value = 6},
}
},
jokers = {
{id = 'j_card_sharp'},
{id = 'j_joker'},
},
consumeables = {
},
vouchers = {
},
deck = {
type = 'Challenge Deck'
},
restrictions = {
banned_cards = {
{id = 'j_juggler'},
{id = 'j_troubadour'},
{id = 'j_turtle_bean'},
},
banned_tags = {
},
banned_other = {
}
}
},
{
name = "Golden Needle",
id = 'c_golden_needle_1',
rules = {
custom = {
{id = 'discard_cost', value = 1},
},
modifiers = {
{id = 'hands', value = 1},
{id = 'discards', value = 6},
{id = 'dollars', value = 10},
}
},
jokers = {
{id = 'j_credit_card'},
},
consumeables = {
},
vouchers = {
},
deck = {
type = 'Challenge Deck'
},
restrictions = {
banned_cards = {
{id = 'v_grabber'},
{id = 'v_nacho_tong'},
{id = 'j_burglar'},
},
banned_tags = {
},
banned_other = {
}
}
},
{
name = "Cruelty",
id = 'c_cruelty_1',
rules = {
custom = {
{id = 'no_reward_specific', value = 'Small'},
{id = 'no_reward_specific', value = 'Big'},
},
modifiers = {
{id = 'joker_slots', value = 3},
}
},
jokers = {
},
consumeables = {
},
vouchers = {
},
deck = {
type = 'Challenge Deck'
},
restrictions = {
banned_cards = {
},
banned_tags = {
},
banned_other = {
}
}
},
{
name = "Jokerless",
id = 'c_jokerless_1',
rules = {
custom = {
{id = 'no_shop_jokers'},
},
modifiers = {
{id = 'joker_slots', value = 0},
}
},
jokers = {
},
consumeables = {
},
vouchers = {
},
deck = {
type = 'Challenge Deck'
},
restrictions = {
banned_cards = {
{id = 'c_judgement'},
{id = 'c_wraith'},
{id = 'c_soul'},
{id = 'p_buffoon_normal_1', ids = {
'p_buffoon_normal_1','p_buffoon_normal_2','p_buffoon_jumbo_1','p_buffoon_mega_1',
}},
},
banned_tags = {
{id = 'tag_rare'},
{id = 'tag_uncommon'},
{id = 'tag_holo'},
{id = 'tag_polychrome'},
{id = 'tag_negative'},
{id = 'tag_foil'},
{id = 'tag_buffoon'},
},
banned_other = {
}
}
},
}

11
conf.lua Normal file
View File

@ -0,0 +1,11 @@
_RELEASE_MODE = true
_DEMO = false
function love.conf(t)
t.console = not _RELEASE_MODE
t.title = 'Balatro'
t.window.width = 0
t.window.height = 0
t.window.minwidth = 100
t.window.minheight = 100
end

107
engine/animatedsprite.lua Normal file
View File

@ -0,0 +1,107 @@
--Class
AnimatedSprite = Sprite:extend()
--Class Methods
function AnimatedSprite:init(X, Y, W, H, new_sprite_atlas, sprite_pos)
Sprite.init(self,X, Y, W, H, new_sprite_atlas, sprite_pos)
self.offset = {x = 0, y = 0}
table.insert(G.ANIMATIONS, self)
if getmetatable(self) == AnimatedSprite then
table.insert(G.I.SPRITE, self)
end
end
function AnimatedSprite:rescale()
self.scale_mag = math.min(self.scale.x/self.T.w,self.scale.y/self.T.h)
end
function AnimatedSprite:reset()
self.atlas = G.ANIMATION_ATLAS[self.atlas.name]
self:set_sprite_pos({x = self.animation.x, y = self.animation.y})
end
function AnimatedSprite:set_sprite_pos(sprite_pos)
self.animation = {
x= sprite_pos and sprite_pos.x or 0,
y=sprite_pos and sprite_pos.y or 0,
frames=self.atlas.frames,current=0,
w=self.scale.x, h=self.scale.y}
self.frame_offset = 0
self.current_animation = {
current = 0,
frames = self.animation.frames,
w = self.animation.w,
h = self.animation.h}
self.image_dims = self.image_dims or {}
self.image_dims[1], self.image_dims[2] = self.atlas.image:getDimensions()
self.sprite = love.graphics.newQuad(
0,
self.animation.h*self.animation.y,
self.animation.w,
self.animation.h,
self.image_dims[1], self.image_dims[2])
self.offset_seconds = G.TIMERS.REAL
end
function AnimatedSprite:get_pos_pixel()
self.RETS.get_pos_pixel = self.RETS.get_pos_pixel or {}
self.RETS.get_pos_pixel[1] = self.current_animation.current
self.RETS.get_pos_pixel[2] = self.animation.y
self.RETS.get_pos_pixel[3] = self.animation.w
self.RETS.get_pos_pixel[4] = self.animation.h
return self.RETS.get_pos_pixel
end
function AnimatedSprite:draw_self()
if not self.states.visible then return end
prep_draw(self, 1)
love.graphics.scale(1/self.scale_mag)
love.graphics.setColor(G.C.WHITE)
love.graphics.draw(
self.atlas.image,
self.sprite,
0 ,0,
0,
self.VT.w/(self.T.w),
self.VT.h/(self.T.h)
)
love.graphics.pop()
end
function AnimatedSprite:animate()
local new_frame = math.floor(G.ANIMATION_FPS*(G.TIMERS.REAL - self.offset_seconds))%self.current_animation.frames
if new_frame ~= self.current_animation.current then
self.current_animation.current = new_frame
self.frame_offset = math.floor(self.animation.w*(self.current_animation.current))
self.sprite:setViewport(
self.frame_offset,
self.animation.h*self.animation.y,
self.animation.w,
self.animation.h)
end
if self.float then
self.T.r = 0.02*math.sin(2*G.TIMERS.REAL+self.T.x)
self.offset.y = -(1+0.3*math.sin(0.666*G.TIMERS.REAL+self.T.y))*self.shadow_parrallax.y
self.offset.x = -(0.7+0.2*math.sin(0.666*G.TIMERS.REAL+self.T.x))*self.shadow_parrallax.x
end
end
function AnimatedSprite:remove()
for _, v in pairs(G.ANIMATIONS) do
if v == self then
table.remove(G.ANIMATIONS, k)
end
end
for _, v in pairs(G.I.SPRITE) do
if v == self then
table.remove(G.I.SPRITE, k)
end
end
Sprite.remove(self)
end

1380
engine/controller.lua Normal file

File diff suppressed because it is too large Load Diff

195
engine/event.lua Normal file
View File

@ -0,0 +1,195 @@
--Class
Event = Object:extend()
--Class Methods
function Event:init(config)
self.trigger = config.trigger or 'immediate'
if config.blocking ~= nil then
self.blocking = config.blocking
else
self.blocking = true
end
if config.blockable ~= nil then
self.blockable = config.blockable
else
self.blockable = true
end
self.complete = false
self.start_timer = config.start_timer or false
self.func = config.func or function() return true end
self.delay = config.delay or 0
self.no_delete = config.no_delete
self.created_on_pause = config.pause_force or G.SETTINGS.paused
self.timer = config.timer or (self.created_on_pause and 'REAL') or 'TOTAL'
if self.trigger == 'ease' then
self.ease = {
type = config.ease or 'lerp',
ref_table = config.ref_table,
ref_value = config.ref_value,
start_val = config.ref_table[config.ref_value],
end_val = config.ease_to,
start_time = nil,
end_time = nil,
}
self.func = config.func or function(t) return t end
end
if self.trigger == 'condition' then
self.condition = {
ref_table = config.ref_table,
ref_value = config.ref_value,
stop_val = config.stop_val,
}
self.func = config.func or function() return self.condition.ref_table[self.condition.ref_value] == self.condition.stop_val end
end
self.time = G.TIMERS[self.timer]
end
function Event:handle(_results)
_results.blocking, _results.completed = self.blocking, self.complete
if self.created_on_pause == false and G.SETTINGS.paused then _results.pause_skip = true; return end
if not self.start_timer then self.time = G.TIMERS[self.timer]; self.start_timer = true end
if self.trigger == 'after' then
if self.time + self.delay <= G.TIMERS[self.timer] then
_results.time_done = true
_results.completed = self.func()
end
end
if self.trigger == 'ease' then
if not self.ease.start_time then
self.ease.start_time = G.TIMERS[self.timer]
self.ease.end_time = G.TIMERS[self.timer] + self.delay
self.ease.start_val = self.ease.ref_table[self.ease.ref_value]
end
if not self.complete then
if self.ease.end_time >= G.TIMERS[self.timer] then
local percent_done = ((self.ease.end_time - G.TIMERS[self.timer])/(self.ease.end_time - self.ease.start_time))
if self.ease.type == 'lerp' then
self.ease.ref_table[self.ease.ref_value] = self.func(percent_done*self.ease.start_val + (1-percent_done)*self.ease.end_val)
end
if self.ease.type == 'elastic' then
percent_done = -math.pow(2, 10 * percent_done - 10) * math.sin((percent_done * 10 - 10.75) * 2*math.pi/3);
self.ease.ref_table[self.ease.ref_value] = self.func(percent_done*self.ease.start_val + (1-percent_done)*self.ease.end_val)
end
if self.ease.type == 'quad' then
percent_done = percent_done * percent_done;
self.ease.ref_table[self.ease.ref_value] = self.func(percent_done*self.ease.start_val + (1-percent_done)*self.ease.end_val)
end
else
self.ease.ref_table[self.ease.ref_value] = self.func(self.ease.end_val)
self.complete = true
_results.completed = true
_results.time_done = true
end
end
end
if self.trigger == 'condition' then
if not self.complete then _results.completed = self.func() end
_results.time_done = true
end
if self.trigger == 'before' then
if not self.complete then _results.completed = self.func() end
if self.time + self.delay <= G.TIMERS[self.timer] then
_results.time_done = true
end
end
if self.trigger == 'immediate' then
_results.completed = self.func()
_results.time_done = true
end
if _results.completed then self.complete = true end
end
--Class
EventManager = Object:extend()
--Class Methods
function EventManager:init()
self.queues = {
unlock = {},
base ={},
tutorial = {},
achievement = {},
other = {}
}
self.queue_timer = G.TIMERS.REAL
self.queue_dt = 1/60
self.queue_last_processed = G.TIMERS.REAL
end
function EventManager:add_event(event, queue, front)
queue = queue or 'base'
if event:is(Event) then
if front then
table.insert(self.queues[queue], 1, event)
else
self.queues[queue][#self.queues[queue]+1] = event
end
end
end
function EventManager:clear_queue(queue, exception)
if not queue then
--clear all queues
for k, v in pairs(self.queues) do
local i=1
while i <= #v do
if not v[i].no_delete then
table.remove(v, i)
else
i = i + 1
end
end
end
elseif exception then --clear all but exception
for k, v in pairs(self.queues) do
if k ~= exception then
local i=1
while i <= #v do
if not v[i].no_delete then
table.remove(v, i)
else
i = i + 1
end
end
end
end
else
local i=1
while i <= #self.queues[queue] do
if not self.queues[queue][i].no_delete then
table.remove(self.queues[queue], i)
else
i = i + 1
end
end
end
end
function EventManager:update(dt, forced)
self.queue_timer = self.queue_timer+dt
if self.queue_timer >= self.queue_last_processed + self.queue_dt or forced then
self.queue_last_processed = self.queue_last_processed + (forced and 0 or self.queue_dt)
for k, v in pairs(self.queues) do
local blocked = false
local i=1
while i <= #v do
G.ARGS.event_manager_update = G.ARGS.event_manager_update or {}
local results = G.ARGS.event_manager_update
results.blocking, results.completed, results.time_done, results.pause_skip = false, false, false, false
if (not blocked or not v[i].blockable) then v[i]:handle(results) end
if results.pause_skip then
i = i + 1
else if not blocked and results.blocking then blocked = true end
if results.completed and results.time_done then
table.remove(v, i)
else
i = i + 1
end
end
end
end
end
end

23
engine/http_manager.lua Normal file
View File

@ -0,0 +1,23 @@
require "love.system"
HTTPS = require('https')
local httpencode = function(str)
str = str..''
local char_to_hex = function(c)
return string.format("%%%02X", string.byte(c))
end
str = str:gsub("\n", "\r\n"):gsub("([^%w _%%%-%.~])", char_to_hex):gsub(" ", "+")
return str
end
if (love.system.getOS() == 'OS X' )and (jit.arch == 'arm64' or jit.arch == 'arm' or true) then jit.off() end
IN_CHANNEL = love.thread.getChannel("http_request")
OUT_CHANNEL = love.thread.getChannel("http_response")
while true do
--Monitor the channel for any new requests
local request = IN_CHANNEL:demand() -- Value from channel
if request then
end
end

502
engine/moveable.lua Normal file
View File

@ -0,0 +1,502 @@
---@class Moveable: Node
Moveable = Node:extend()
--Moveable represents any game object that has the ability to move about the gamespace.\
--All Moveables have a T (transform) that describes their desired transform in game units, as\
--well as a VT (Visible Transform) that eases to T over time. This allows for simplified movement where\
--we only need to set T.x, T.y, etc. to their final position and the engine will ensure the Moveable\
--VT eases to that final location, regargless of any events or timing.
--
---@param args {T: table, container: Node}
--**T** The transform ititializer, with keys of x|1, y|2, w|3, h|4, r|5\
--**container** optional container for this Node, defaults to G.ROOM
function Moveable:init(X,Y,W,H)
local args = (type(X) == 'table') and X or {T ={X or 0,Y or 0,W or 0,H or 0}}
Node.init(self, args)
--The Visible transform is initally set to the same values as the transform T.
--Note that the VT has an extra 'scale' factor, this is used to manipulate the center-adjusted
--scale of any objects that need to be drawn larger or smaller
self.VT = {
x = self.T.x,
y = self.T.y,
w = self.T.w,
h = self.T.h,
r = self.T.r,
scale = self.T.scale
}
--To determine location of VT, we need to keep track of the velocity of VT as it approaches T for the next frame
self.velocity = {x = 0, y = 0, r = 0, scale = 0, mag = 0}
--For more robust drawing, attaching, movement and fewer redundant movement calculations, Moveables each have a 'role'
--that describes a heirarchy of move() calls. Any Moveables with 'Major' role type behave normally, essentially recalculating their
--VT every frame to ensure smooth movement. Moveables can be set to 'Minor' role and attached to some 'Major' moveable
--to weld the Minor moveable to the Major moveable. This makes the dependent moveable set their T and VT to be equal to
--the corresponding 'Major' T and VT, plus some defined offset.
--For finer control over what parts of T and VT are inherited, xy_bond, wh_bond, and r_bond can be set to one of
--'Strong' or 'Weak'. Strong simply copies the values, Weak allows the 'Minor' moveable to calculate their own.
self.role = {
role_type = 'Major', --Major dictates movement, Minor is welded to some major
offset = {x = 0, y = 0}, --Offset from Minor to Major
major = nil,
draw_major = self,
xy_bond = 'Strong',
wh_bond = 'Strong',
r_bond = 'Strong',
scale_bond = 'Strong'
}
self.alignment = {
type = 'a',
offset = {x = 0, y = 0},
prev_type = '',
prev_offset = {x = 0, y = 0},
}
--the pinch table is used to modify the VT.w and VT.h compared to T.w and T.h. If either x or y pinch is
--set to true, the VT width and or height will ease to 0. If pinch is false, they ease to T.w or T.h
self.pinch = {x = false, y = false}
--Keep track of the last time this Moveable was moved via :move(dt). When it is successfully moved, set to equal
--the current G.TIMERS.REAL, and if it is called again this frame, doesn't recalculate move(dt)
self.last_moved = -1
self.last_aligned = -1
self.static_rotation = false
self.offset = {x=0, y=0}
self.Mid = self
self.shadow_parrallax = {x = 0, y = -1.5}
self.layered_parallax = {x = 0, y = 0}
self.shadow_height = 0.2
self:calculate_parrallax()
table.insert(G.MOVEABLES, self)
if getmetatable(self) == Moveable then
table.insert(G.I.MOVEABLE, self)
end
end
function Moveable:draw()
Node.draw(self)
self:draw_boundingrect()
end
--Sets the alignment of moveable using roles
--
---@param args {major: Moveable, bond: string, offset: table, type: string}
--**major** The moveable this moveable will attach to\
--**bond** The bond type, either 'Strong' or 'Weak'. Strong instantly adjusts VT, Weak manually calculates VT changes\
--**offset** {x , y} offset from the alignment\
--**type** the alignment type. Vertical options: c - center, t - top, b - bottom. Horizontal options: l - left, m - middle, r - right. i for inner
function Moveable:set_alignment(args)
args = args or {}
if args.major then
self:set_role({
role_type = 'Minor',
major = args.major,
xy_bond = args.bond or args.xy_bond or 'Weak',
wh_bond = args.wh_bond or self.role.wh_bond,
r_bond = args.r_bond or self.role.r_bond,
scale_bond = args.scale_bond or self.role.scale_bond,
})
end
self.alignment.type = args.type or self.alignment.type
if args.offset and (type(args.offset)=='table' and not (args.offset.y and args.offset.x)) or type(args.offset) ~= 'table' then
args.offset = nil
end
self.alignment.offset = args.offset or self.alignment.offset
end
function Moveable:align_to_major()
if self.alignment.type ~= self.alignment.prev_type then
self.alignment.type_list = {
a = self.alignment.type == 'a',
m = string.find(self.alignment.type, "m"),
c = string.find(self.alignment.type, "c"),
b = string.find(self.alignment.type, "b"),
t = string.find(self.alignment.type, "t"),
l = string.find(self.alignment.type, "l"),
r = string.find(self.alignment.type, "r"),
i = string.find(self.alignment.type, "i"),
}
end
if self.alignment.prev_offset.x == self.alignment.offset.x and
self.alignment.prev_offset.y == self.alignment.offset.y and
self.alignment.prev_type == self.alignment.type then return end
self.NEW_ALIGNMENT = true
if self.alignment.type ~= self.alignment.prev_type then
self.alignment.prev_type = self.alignment.type
end
if self.alignment.type_list.a or not self.role.major then return end
if self.alignment.type_list.m then
self.role.offset.x = 0.5*self.role.major.T.w - (self.Mid.T.w)/2 + self.alignment.offset.x - self.Mid.T.x + self.T.x
end
if self.alignment.type_list.c then
self.role.offset.y = 0.5*self.role.major.T.h - (self.Mid.T.h)/2 + self.alignment.offset.y - self.Mid.T.y + self.T.y
end
if self.alignment.type_list.b then
if self.alignment.type_list.i then
self.role.offset.y = self.alignment.offset.y + self.role.major.T.h - self.T.h
else
self.role.offset.y = self.alignment.offset.y + self.role.major.T.h
end
end
if self.alignment.type_list.r then
if self.alignment.type_list.i then
self.role.offset.x = self.alignment.offset.x + self.role.major.T.w - self.T.w
else
self.role.offset.x = self.alignment.offset.x + self.role.major.T.w
end
end
if self.alignment.type_list.t then
if self.alignment.type_list.i then
self.role.offset.y = self.alignment.offset.y
else
self.role.offset.y = self.alignment.offset.y - self.T.h
end
end
if self.alignment.type_list.l then
if self.alignment.type_list.i then
self.role.offset.x = self.alignment.offset.x
else
self.role.offset.x = self.alignment.offset.x - self.T.w
end
end
self.role.offset.x = self.role.offset.x or 0
self.role.offset.y = self.role.offset.y or 0
self.T.x = self.role.major.T.x + self.role.offset.x
self.T.y = self.role.major.T.y + self.role.offset.y
self.alignment.prev_offset = self.alignment.prev_offset or {}
self.alignment.prev_offset.x, self.alignment.prev_offset.y = self.alignment.offset.x, self.alignment.offset.y
end
function Moveable:hard_set_T(X, Y, W, H)
self.T.x = X
self.T.y = Y
self.T.w = W
self.T.h = H
self.velocity.x = 0
self.velocity.y = 0
self.velocity.r = 0
self.velocity.scale = 0
self.VT.x = X
self.VT.y = Y
self.VT.w = W
self.VT.h = H
self.VT.r = self.T.r
self.VT.scale = self.T.scale
self:calculate_parrallax()
end
function Moveable:hard_set_VT()
self.VT.x = self.T.x
self.VT.y = self.T.y
self.VT.w = self.T.w
self.VT.h = self.T.h
end
function Moveable:drag(offset)
if self.states.drag.can or offset then
self.ARGS.drag_cursor_trans = self.ARGS.drag_cursor_trans or {}
self.ARGS.drag_translation = self.ARGS.drag_translation or {}
local _p = self.ARGS.drag_cursor_trans
local _t = self.ARGS.drag_translation
_p.x = G.CONTROLLER.cursor_position.x/(G.TILESCALE*G.TILESIZE)
_p.y = G.CONTROLLER.cursor_position.y/(G.TILESCALE*G.TILESIZE)
_t.x, _t.y = -self.container.T.w/2, -self.container.T.h/2
point_translate(_p, _t)
point_rotate(_p, self.container.T.r)
_t.x, _t.y = self.container.T.w/2-self.container.T.x, self.container.T.h/2-self.container.T.y
point_translate(_p, _t)
if not offset then
offset = self.click_offset
end
self.T.x = _p.x - offset.x
self.T.y = _p.y - offset.y
self.NEW_ALIGNMENT = true
for k, v in pairs(self.children) do
v:drag(offset)
end
end
if self.states.drag.can then
Node.drag(self)
end
end
function Moveable:juice_up(amount, rot_amt)
local amount = amount or 0.4
local end_time = G.TIMERS.REAL + 0.4
local start_time = G.TIMERS.REAL
self.juice = {
scale = 0,
scale_amt = amount,
r = 0,
r_amt = ((rot_amt or pseudorandom_element({0.6*amount, -0.6*amount})) or 0),
start_time = start_time,
end_time = end_time
}
self.VT.scale = 1-0.6*amount
end
function Moveable:move_juice(dt)
if self.juice and not self.juice.handled_elsewhere then
if self.juice.end_time < G.TIMERS.REAL then
self.juice = nil
else
self.juice.scale = self.juice.scale_amt*math.sin(50.8*(G.TIMERS.REAL-self.juice.start_time))*math.max(0, ((self.juice.end_time - G.TIMERS.REAL)/(self.juice.end_time - self.juice.start_time))^3)
self.juice.r = self.juice.r_amt*math.sin(40.8*(G.TIMERS.REAL-self.juice.start_time))*math.max(0, ((self.juice.end_time - G.TIMERS.REAL)/(self.juice.end_time - self.juice.start_time))^2)
end
end
end
function Moveable:move(dt)
if self.FRAME.MOVE >= G.FRAMES.MOVE then return end
self.FRAME.MAJOR = nil
self.FRAME.MOVE = G.FRAMES.MOVE
if not self.created_on_pause and G.SETTINGS.paused then return end
--WHY ON EARTH DOES THIS LINE MAKE IT RUN 2X AS FAST???
-------------------------------------------------------
local timestart = love.timer.getTime()
-------------------------------------------------------
self:align_to_major()
self.CALCING = nil
if self.role.role_type == 'Glued' then
if self.role.major then self:glue_to_major(self.role.major) end
elseif self.role.role_type == 'Minor' and self.role.major then
if self.role.major.FRAME.MOVE < G.FRAMES.MOVE then self.role.major:move(dt) end
self.STATIONARY = self.role.major.STATIONARY
if (not self.STATIONARY) or self.NEW_ALIGNMENT or
self.config.refresh_movement or
self.juice or
self.role.xy_bond == 'Weak' or
self.role.r_bond == 'Weak' then
self.CALCING = true
self:move_with_major(dt)
end
elseif self.role.role_type == 'Major' then
self.STATIONARY = true
self:move_juice(dt)
self:move_xy(dt)
self:move_r(dt, self.velocity)
self:move_scale(dt)
self:move_wh(dt)
self:calculate_parrallax()
end
self.NEW_ALIGNMENT = false
end
function Moveable:glue_to_major(major_tab)
self.T = major_tab.T
self.VT.x = major_tab.VT.x + (0.5*(1 - major_tab.VT.w/(major_tab.T.w))*self.T.w)
self.VT.y = major_tab.VT.y
self.VT.w = major_tab.VT.w
self.VT.h = major_tab.VT.h
self.VT.r = major_tab.VT.r
self.VT.scale = major_tab.VT.scale
self.pinch = major_tab.pinch
self.shadow_parrallax = major_tab.shadow_parrallax
end
function Moveable:move_with_major(dt)
if self.role.role_type ~= 'Minor' then return end
local major_tab = self.role.major:get_major()
self:move_juice(dt)
if not MWM then
MWM = {
rotated_offset = {},
angles = {},
WH = {},
offs = {},
}
end
if self.role.r_bond == 'Weak' then
MWM.rotated_offset.x, MWM.rotated_offset.y = self.role.offset.x + major_tab.offset.x,self.role.offset.y+major_tab.offset.y
else
if major_tab.major.VT.r < 0.0001 and major_tab.major.VT.r > -0.0001 then
MWM.rotated_offset.x = self.role.offset.x + major_tab.offset.x
MWM.rotated_offset.y = self.role.offset.y + major_tab.offset.y
else
MWM.angles.cos, MWM.angles.sin = math.cos(major_tab.major.VT.r),math.sin(major_tab.major.VT.r)
MWM.WH.w, MWM.WH.h = -self.T.w/2 + major_tab.major.T.w/2,-self.T.h/2 + major_tab.major.T.h/2
MWM.offs.x, MWM.offs.y = self.role.offset.x + major_tab.offset.x - MWM.WH.w,self.role.offset.y + major_tab.offset.y - MWM.WH.h
MWM.rotated_offset.x = MWM.offs.x*MWM.angles.cos - MWM.offs.y*MWM.angles.sin + MWM.WH.w
MWM.rotated_offset.y = MWM.offs.x*MWM.angles.sin + MWM.offs.y*MWM.angles.cos + MWM.WH.h
end
end
self.T.x = major_tab.major.T.x + MWM.rotated_offset.x
self.T.y = major_tab.major.T.y + MWM.rotated_offset.y
if self.role.xy_bond == 'Strong' then
self.VT.x = major_tab.major.VT.x + MWM.rotated_offset.x
self.VT.y = major_tab.major.VT.y + MWM.rotated_offset.y
elseif self.role.xy_bond == 'Weak' then
self:move_xy(dt)
end
if self.role.r_bond == 'Strong' then
self.VT.r = self.T.r + major_tab.major.VT.r + (self.juice and self.juice.r or 0)
elseif self.role.r_bond == 'Weak' then
self:move_r(dt, self.velocity)
end
if self.role.scale_bond == 'Strong' then
self.VT.scale = self.T.scale*(major_tab.major.VT.scale/major_tab.major.T.scale) + (self.juice and self.juice.scale or 0)
elseif self.role.scale_bond == 'Weak' then
self:move_scale(dt)
end
if self.role.wh_bond == 'Strong' then
self.VT.x = self.VT.x + (0.5*(1 - major_tab.major.VT.w/(major_tab.major.T.w))*self.T.w)
self.VT.w = (self.T.w)*(major_tab.major.VT.w/major_tab.major.T.w)
self.VT.h = (self.T.h)*(major_tab.major.VT.h/major_tab.major.T.h)
elseif self.role.wh_bond == 'Weak' then
self:move_wh(dt)
end
self:calculate_parrallax()
end
function Moveable:move_xy(dt)
if (self.T.x ~= self.VT.x or math.abs(self.velocity.x) > 0.01) or
(self.T.y ~= self.VT.y or math.abs(self.velocity.y) > 0.01) then
self.velocity.x = G.exp_times.xy*self.velocity.x + (1-G.exp_times.xy)*(self.T.x - self.VT.x)*35*dt
self.velocity.y = G.exp_times.xy*self.velocity.y + (1-G.exp_times.xy)*(self.T.y - self.VT.y)*35*dt
if self.velocity.x*self.velocity.x + self.velocity.y*self.velocity.y > G.exp_times.max_vel*G.exp_times.max_vel then
local actual_vel = math.sqrt(self.velocity.x*self.velocity.x + self.velocity.y*self.velocity.y)
self.velocity.x = G.exp_times.max_vel*self.velocity.x/actual_vel
self.velocity.y = G.exp_times.max_vel*self.velocity.y/actual_vel
end
self.STATIONARY = false
self.VT.x = self.VT.x + self.velocity.x
self.VT.y = self.VT.y + self.velocity.y
if math.abs(self.VT.x - self.T.x) < 0.01 and math.abs(self.velocity.x) < 0.01 then self.VT.x = self.T.x; self.velocity.x = 0 end
if math.abs(self.VT.y - self.T.y) < 0.01 and math.abs(self.velocity.y) < 0.01 then self.VT.y = self.T.y; self.velocity.y = 0 end
end
end
function Moveable:move_scale(dt)
local des_scale = self.T.scale + (self.zoom and ((self.states.drag.is and 0.1 or 0) + (self.states.hover.is and 0.05 or 0)) or 0) + (self.juice and self.juice.scale or 0)
if des_scale ~= self.VT.scale or
math.abs(self.velocity.scale) > 0.001 then
self.STATIONARY = false
self.velocity.scale = G.exp_times.scale*self.velocity.scale + (1-G.exp_times.scale)*(des_scale - self.VT.scale)
self.VT.scale = self.VT.scale + self.velocity.scale
end
end
function Moveable:move_wh(dt)
if (self.T.w ~= self.VT.w and not self.pinch.x) or
(self.T.h ~= self.VT.h and not self.pinch.y) or
(self.VT.w > 0 and self.pinch.x) or
(self.VT.h > 0 and self.pinch.y) then
self.STATIONARY = false
self.VT.w = self.VT.w + (8*dt)*(self.pinch.x and -1 or 1)*self.T.w
self.VT.h = self.VT.h + (8*dt)*(self.pinch.y and -1 or 1)*self.T.h
self.VT.w = math.max(math.min(self.VT.w, self.T.w), 0)
self.VT.h = math.max(math.min(self.VT.h, self.T.h), 0)
end
end
function Moveable:move_r(dt, vel)
local des_r = self.T.r +0.015*vel.x/dt + (self.juice and self.juice.r*2 or 0)
if des_r ~= self.VT.r or
math.abs(self.velocity.r) > 0.001 then
self.STATIONARY = false
self.velocity.r = G.exp_times.r*self.velocity.r + (1-G.exp_times.r)*(des_r - self.VT.r)
self.VT.r = self.VT.r + self.velocity.r
end
if math.abs(self.VT.r - self.T.r) < 0.001 and math.abs(self.velocity.r) < 0.001 then self.VT.r = self.T.r; self.velocity.r = 0 end
end
function Moveable:calculate_parrallax()
if not G.ROOM then return end
self.shadow_parrallax.x = (self.T.x + self.T.w/2 - G.ROOM.T.w/2)/(G.ROOM.T.w/2)*1.5
end
function Moveable:set_role(args)
if args.major and not args.major.set_role then return end
if args.offset and (type(args.offset)=='table' and not (args.offset.y and args.offset.x)) or type(args.offset) ~= 'table' then
args.offset = nil
end
self.role = {
role_type = args.role_type or self.role.role_type,
offset = args.offset or self.role.offset,
major = args.major or self.role.major,
xy_bond = args.xy_bond or self.role.xy_bond,
wh_bond = args.wh_bond or self.role.wh_bond,
r_bond = args.r_bond or self.role.r_bond,
scale_bond = args.scale_bond or self.role.scale_bond,
draw_major = args.draw_major or self.role.draw_major,
}
if self.role.role_type == 'Major' then self.role.major = nil end
end
function Moveable:get_major()
if ( self.role.role_type ~= 'Major' and self.role.major ~= self) and (self.role.xy_bond ~= 'Weak' and self.role.r_bond ~= 'Weak') then
--First, does the major already have their offset precalculated for this frame?
if not self.FRAME.MAJOR or (G.REFRESH_FRAME_MAJOR_CACHE) then
self.FRAME.MAJOR = EMPTY(self.FRAME.MAJOR)
local major = self.role.major:get_major()
self.FRAME.MAJOR.major = major.major
self.FRAME.MAJOR.offset = self.FRAME.MAJOR.offset or {}
self.FRAME.MAJOR.offset.x, self.FRAME.MAJOR.offset.y = major.offset.x + self.role.offset.x + self.layered_parallax.x, major.offset.y + self.role.offset.y + self.layered_parallax.y
end
return self.FRAME.MAJOR
else
self.ARGS.get_major = self.ARGS.get_major or {}
self.ARGS.get_major.major = self
self.ARGS.get_major.offset = self.ARGS.get_major.offset or {}
self.ARGS.get_major.offset.x, self.ARGS.get_major.offset.y = 0,0
return self.ARGS.get_major
end
end
function Moveable:remove()
for k, v in ipairs(G.MOVEABLES) do
if v == self then
table.remove(G.MOVEABLES, k)
end
end
for k, v in ipairs(G.I.MOVEABLE) do
if v == self then
table.remove(G.I.MOVEABLE, k)
end
end
Node.remove(self)
end

389
engine/node.lua Normal file
View File

@ -0,0 +1,389 @@
---@class Node
Node = Object:extend()
--Node represent any game object that needs to have some transform available in the game itself.\
--Everything that you see in the game is a Node, and some invisible things like the G.ROOM are also\
--represented here.
--
---@param args {T: table, container: Node}
--**T** The transform ititializer, with keys of x|1, y|2, w|3, h|4, r|5\
--**container** optional container for this Node, defaults to G.ROOM
function Node:init(args)
--From args, set the values of self transform
args = args or {}
args.T = args.T or {}
--Store all argument and return tables here for reuse, because Lua likes to generate garbage
self.ARGS = self.ARGS or {}
self.RETS = {}
--Config table used for any metadata about this node
self.config = self.config or {}
--For transform init, accept params in the form x|1, y|2, w|3, h|4, r|5
self.T = {
x = args.T.x or args.T[1] or 0,
y = args.T.y or args.T[2] or 0,
w = args.T.w or args.T[3] or 1,
h = args.T.h or args.T[4] or 1,
r = args.T.r or args.T[5] or 0,
scale = args.T.scale or args.T[6] or 1,
}
--Transform to use for collision detection
self.CT = self.T
--Create the offset tables, used to determine things like drag offset and 3d shader effects
self.click_offset = {x = 0, y = 0}
self.hover_offset = {x = 0, y = 0}
--To keep track of all nodes created on pause. If true, this node moves normally even when the G.TIMERS.TOTAL doesn't increment
self.created_on_pause = G.SETTINGS.paused
--ID tracker, every Node has a unique ID
G.ID = G.ID or 1
self.ID = G.ID
G.ID = G.ID + 1
--Frame tracker to aid in not doing too many extra calculations
self.FRAME = {
DRAW = -1,
MOVE = -1
}
--The states for this Node and all derived nodes. This is how we control the visibility and interactibility of any object
--All nodes do not collide by default. This reduces the size of n for the O(n^2) collision detection
self.states = {
visible = true,
collide = {can = false, is = false},
focus = {can = false, is = false},
hover = {can = true, is = false},
click = {can = true, is = false},
drag = {can = true, is = false},
release_on = {can = true, is = false}
}
--If we provide a container, all nodes within that container are translated with that container as the reference frame.
--For example, if G.ROOM is set at x = 5 and y = 5, and we create a new game object at 0, 0, it will actually be drawn at
--5, 5. This allows us to control things like screen shake, room positioning, rotation, padding, etc. without needing to modify
--every game object that we need to draw
self.container = args.container or G.ROOM
--The list of children give Node a treelike structure. This can be used for things like drawing, deterministice movement and parallax
--calculations when child nodes rely on updated information from parents, and inherited attributes like button click functions
if not self.children then
self.children = {}
end
--Add this object to the appropriate instance table only if the metatable matches with NODE
if getmetatable(self) == Node then
table.insert(G.I.NODE, self)
end
--Unless node was created during a stage transition (when G.STAGE_OBJECT_INTERRUPT is true), add all nodes to their appropriate
--stage object table so they can be easily deleted on stage transition
if not G.STAGE_OBJECT_INTERRUPT then
table.insert(G.STAGE_OBJECTS[G.STAGE], self)
end
end
--Draw a bounding rectangle representing the transform of this node. Used in debugging.
function Node:draw_boundingrect()
self.under_overlay = G.under_overlay
if G.DEBUG then
local transform = self.VT or self.T
love.graphics.push()
love.graphics.scale(G.TILESCALE, G.TILESCALE)
love.graphics.translate(transform.x*G.TILESIZE+transform.w*G.TILESIZE*0.5,
transform.y*G.TILESIZE+transform.h*G.TILESIZE*0.5)
love.graphics.rotate(transform.r)
love.graphics.translate(-transform.w*G.TILESIZE*0.5,
-transform.h*G.TILESIZE*0.5)
if self.DEBUG_VALUE then
love.graphics.setColor(1, 1, 0, 1)
love.graphics.print((self.DEBUG_VALUE or ''), transform.w*G.TILESIZE,transform.h*G.TILESIZE, nil, 1/G.TILESCALE)
end
love.graphics.setLineWidth(1 + (self.states.focus.is and 1 or 0))
if self.states.collide.is then
love.graphics.setColor(0, 1, 0, 0.3)
else
love.graphics.setColor(1, 0, 0, 0.3)
end
if self.states.focus.can then
love.graphics.setColor(G.C.GOLD)
love.graphics.setLineWidth(1)
end
if self.CALCING then
love.graphics.setColor({0,0,1,1})
love.graphics.setLineWidth(3)
end
love.graphics.rectangle('line', 0, 0, transform.w*G.TILESIZE,transform.h*G.TILESIZE, 3)
love.graphics.pop()
end
end
--Draws self, then adds self the the draw hash, then draws all children
function Node:draw()
self:draw_boundingrect()
if self.states.visible then
add_to_drawhash(self)
for _, v in pairs(self.children) do
v:draw()
end
end
end
--Determines if this node collides with some point. Applies any container translations and rotations, then\
--applies translations and rotations specific to this node. This means the collision detection effectively\
--determines if some point intersects this node regargless of rotation.
--
---@param point {x: number, y: number}
--**x and y** The coordinates of the cursor transformed into game units
function Node:collides_with_point(point)
--First reset the collision state to false
if self.container then
local T = self.CT or self.T
self.ARGS.collides_with_point_point = self.ARGS.collides_with_point_point or {}
self.ARGS.collides_with_point_translation = self.ARGS.collides_with_point_translation or {}
self.ARGS.collides_with_point_rotation = self.ARGS.collides_with_point_rotation or {}
local _p = self.ARGS.collides_with_point_point
local _t = self.ARGS.collides_with_point_translation
local _r = self.ARGS.collides_with_point_rotation
local _b = self.states.hover.is and G.COLLISION_BUFFER or 0
_p.x, _p.y = point.x, point.y
if self.container ~= self then --if there is some valid container, we need to apply all translations and rotations for the container first
if math.abs(self.container.T.r) < 0.1 then
--Translate to normalize this Node to the center of the container
_t.x, _t.y = -self.container.T.w/2, -self.container.T.h/2
point_translate(_p, _t)
--Rotate node about the center of the container
point_rotate(_p, self.container.T.r)
--Translate node to undo the container translation, essentially reframing it in 'container' space
_t.x, _t.y = self.container.T.w/2-self.container.T.x, self.container.T.h/2-self.container.T.y
point_translate(_p, _t)
else
--Translate node to undo the container translation, essentially reframing it in 'container' space
_t.x, _t.y = -self.container.T.x, -self.container.T.y
point_translate(_p, _t)
end
end
if math.abs(T.r) < 0.1 then
--If we can essentially disregard transform rotation, just treat it like a normal rectangle
if _p.x >= T.x - _b and _p.y >= T.y - _b and _p.x <= T.x + T.w + _b and _p.y <= T.y + T.h + _b then
return true
end
else
--Otherwise we need to do some silly point rotation garbage to determine if the point intersects the rotated rectangle
_r.cos, _r.sin = math.cos(T.r+math.pi/2), math.sin(T.r+math.pi/2)
_p.x, _p.y = _p.x - (T.x + 0.5*(T.w)), _p.y - (T.y + 0.5*(T.h))
_t.x, _t.y = _p.y*_r.cos - _p.x*_r.sin, _p.y*_r.sin + _p.x*_r.cos
_p.x, _p.y = _t.x + (T.x + 0.5*(T.w)), _t.y + (T.y + 0.5*(T.h))
if _p.x >= T.x - _b and _p.y >= T.y - _b
and _p.x <= T.x + T.w + _b and _p.y <= T.y + T.h + _b then
return true
end
end
end
end
--Sets the offset of passed point in terms of this nodes T.x and T.y
--
---@param point {x: number, y: number}
---@param type string
--**x and y** The coordinates of the cursor transformed into game units
--**type** the type of offset to set for this Node, either 'Click' or 'Hover'
function Node:set_offset(point, type)
self.ARGS.set_offset_point = self.ARGS.set_offset_point or {}
self.ARGS.set_offset_translation = self.ARGS.set_offset_translation or {}
local _p = self.ARGS.set_offset_point
local _t = self.ARGS.set_offset_translation
_p.x, _p.y = point.x, point.y
--Translate to middle of the container
_t.x = -self.container.T.w/2
_t.y = -self.container.T.h/2
point_translate(_p, _t)
--Rotate about the container midpoint according to node rotation
point_rotate(_p, self.container.T.r)
--Translate node to undo the container translation, essentially reframing it in 'container' space
_t.x = self.container.T.w/2-self.container.T.x
_t.y = self.container.T.h/2-self.container.T.y
point_translate(_p, _t)
if type == 'Click' then
self.click_offset.x = (_p.x - self.T.x)
self.click_offset.y = (_p.y - self.T.y)
elseif type == 'Hover' then
self.hover_offset.x = (_p.x - self.T.x)
self.hover_offset.y = (_p.y - self.T.y)
end
end
--If the current container is being 'Dragged', usually by a cursor, determine if any drag popups need to be generated and do so
function Node:drag()
if self.config and self.config.d_popup then
if not self.children.d_popup then
self.children.d_popup = UIBox{
definition = self.config.d_popup,
config = self.config.d_popup_config
}
self.children.h_popup.states.collide.can = false
table.insert(G.I.POPUP, self.children.d_popup)
self.children.d_popup.states.drag.can = true
end
end
end
--Determines if this Node can be dragged. This is a simple function but more complex objects may redefine this to return a parent\
--if the parent needs to drag other children with it
function Node:can_drag()
return self.states.drag.can and self or nil
end
--Called by the CONTROLLER when this node is no longer being dragged, removes any d_popups
function Node:stop_drag()
if self.children.d_popup then
for k, v in pairs(G.I.POPUP) do
if v == self.children.d_popup then
table.remove(G.I.POPUP, k)
end
end
self.children.d_popup:remove()
self.children.d_popup = nil
end
end
--If the current container is being 'Hovered', usually by a cursor, determine if any hover popups need to be generated and do so
function Node:hover()
if self.config and self.config.h_popup then
if not self.children.h_popup then
self.config.h_popup_config.instance_type = 'POPUP'
self.children.h_popup = UIBox{
definition = self.config.h_popup,
config = self.config.h_popup_config,
}
self.children.h_popup.states.collide.can = false
self.children.h_popup.states.drag.can = true
end
end
end
--Called by the CONTROLLER when this node is no longer being hovered, removes any h_popups
function Node:stop_hover()
if self.children.h_popup then
self.children.h_popup:remove()
self.children.h_popup = nil
end
end
--Called by the CONTROLLER to determine the position the cursor should be set to for this node
function Node:put_focused_cursor()
return (self.T.x + self.T.w/2 + self.container.T.x)*(G.TILESCALE*G.TILESIZE), (self.T.y + self.T.h/2 + self.container.T.y)*(G.TILESCALE*G.TILESIZE)
end
--Sets the container of this node and all child nodes to be a new container node
--
---@param container Node The new node that will behave as this nodes container
function Node:set_container(container)
if self.children then
for _, v in pairs(self.children) do
v:set_container(container)
end
end
self.container = container
end
--Translation function used before any draw calls, translates this node according to the transform of the container node
function Node:translate_container()
if self.container and self.container ~= self then
love.graphics.translate(self.container.T.w*G.TILESCALE*G.TILESIZE*0.5, self.container.T.h*G.TILESCALE*G.TILESIZE*0.5)
love.graphics.rotate(self.container.T.r)
love.graphics.translate(
-self.container.T.w*G.TILESCALE*G.TILESIZE*0.5 + self.container.T.x*G.TILESCALE*G.TILESIZE,
-self.container.T.h*G.TILESCALE*G.TILESIZE*0.5 + self.container.T.y*G.TILESCALE*G.TILESIZE)
end
end
--When this Node needs to be deleted, removes self from any tables it may have been added to to destroy any weak references\
--Also calls the remove method of all children to have them do the same
function Node:remove()
for k, v in ipairs(G.I.POPUP) do
if v == self then
table.remove(G.I.POPUP, k)
break;
end
end
for k, v in ipairs(G.I.NODE) do
if v == self then
table.remove(G.I.NODE, k)
break;
end
end
for k, v in ipairs(G.STAGE_OBJECTS[G.STAGE]) do
if v == self then
table.remove(G.STAGE_OBJECTS[G.STAGE], k)
break;
end
end
if self.children then
for k, v in pairs(self.children) do
v:remove()
end
end
if G.CONTROLLER.clicked.target ==self then
G.CONTROLLER.clicked.target = nil
end
if G.CONTROLLER.focused.target ==self then
G.CONTROLLER.focused.target = nil
end
if G.CONTROLLER.dragging.target ==self then
G.CONTROLLER.dragging.target = nil
end
if G.CONTROLLER.hovering.target ==self then
G.CONTROLLER.hovering.target = nil
end
if G.CONTROLLER.released_on.target ==self then
G.CONTROLLER.released_on.target = nil
end
if G.CONTROLLER.cursor_down.target ==self then
G.CONTROLLER.cursor_down.target = nil
end
if G.CONTROLLER.cursor_up.target ==self then
G.CONTROLLER.cursor_up.target = nil
end
if G.CONTROLLER.cursor_hover.target ==self then
G.CONTROLLER.cursor_hover.target = nil
end
self.REMOVED = true
end
--returns the squared(fast) distance in game units from the center of this node to the center of another node
--
---@param other_node Node to measure the distance from
function Node:fast_mid_dist(other_node)
return math.sqrt((other_node.T.x + 0.5*other_node.T.w) - (self.T.x + self.T.w))^2 + ((other_node.T.y + 0.5*other_node.T.h) - (self.T.y + self.T.h))^2
end
--Prototype for a click release function, when the cursor is released on this node
function Node:release(dragged) end
--Prototype for a click function
function Node:click() end
--Prototype animation function for any frame manipulation needed
function Node:animate() end
--Prototype update function for any object specific logic that needs to occur every frame
function Node:update(dt) end

37
engine/object.lua Normal file
View File

@ -0,0 +1,37 @@
--||--
--This Object implementation was taken from SNKRX (MIT license). Slightly modified, this is a very simple OOP base
Object = {}
Object.__index = Object
function Object:init()
end
function Object:extend()
local cls = {}
for k, v in pairs(self) do
if k:find("__") == 1 then
cls[k] = v
end
end
cls.__index = cls
cls.super = self
setmetatable(cls, self)
return cls
end
function Object:is(T)
local mt = getmetatable(self)
while mt do
if mt == T then
return true
end
mt = getmetatable(mt)
end
return false
end
function Object:__call(...)
local obj = setmetatable({}, self)
obj:init(...)
return obj
end

177
engine/particles.lua Normal file
View File

@ -0,0 +1,177 @@
---@class Particles: Moveable
Particles = Moveable:extend()
--Class Methods
function Particles:init(X, Y, W, H, config)
config = config or {}
Moveable.init(self,X, Y, W, H)
self.fill = config.fill
self.padding = config.padding or 0
if config.attach then
self:set_alignment{
major = config.attach,
type = 'cm',
bond = 'Strong'
}
table.insert(self.role.major.children,self)
self.parent = self.role.major
self.T.x = self.role.major.T.x + self.padding
self.T.y = self.role.major.T.y + self.padding
if self.fill then
self.T.w = self.role.major.T.w - self.padding
self.T.h = self.role.major.T.h - self.padding
end
end
self.states.hover.can = false
self.states.click.can = false
self.states.collide.can = false
self.states.drag.can = false
self.states.release_on.can = false
self.timer = config.timer or 0.5
self.timer_type = (self.created_on_pause and 'REAL') or config.timer_type or 'REAL'
self.last_real_time = G.TIMERS[self.timer_type] - self.timer
self.last_drawn = 0
self.lifespan = config.lifespan or 1
self.fade_alpha = 0
self.speed = config.speed or 1
self.max = config.max or 1000000000000000
self.pulse_max = math.min(20, config.pulse_max or 0)
self.pulsed = 0
self.vel_variation = config.vel_variation or 1
self.particles = {}
self.scale = config.scale or 1
self.colours = config.colours or {G.C.BACKGROUND.D}
if config.initialize then
for i = 1, 60 do
self.last_real_time = self.last_real_time - 15/60
self:update(15/60)
self:move(15/60)
end
end
if getmetatable(self) == Particles then
table.insert(G.I.MOVEABLE, self)
end
end
function Particles:update(dt)
if G.SETTINGS.paused and not self.created_on_pause then self.last_real_time = G.TIMERS[self.timer_type] ; return end
local added_this_frame = 0
while G.TIMERS[self.timer_type] > self.last_real_time + self.timer and (#self.particles < self.max or self.pulsed < self.pulse_max) and added_this_frame < 20 do
self.last_real_time = self.last_real_time + self.timer
local new_offset = {
x=self.fill and (0.5-math.random())*self.T.w or 0,
y=self.fill and (0.5-math.random())*self.T.h or 0
}
if self.fill and self.T.r < 0.1 and self.T.r > -0.1 then
local newer_offset = {
x = math.sin(self.T.r)*new_offset.y + math.cos(self.T.r)*new_offset.x,
y = math.sin(self.T.r)*new_offset.x + math.cos(self.T.r)*new_offset.y,
}
new_offset = newer_offset
end
table.insert(self.particles, {
draw = false,
dir = math.random()*2*math.pi,
facing = math.random()*2*math.pi,
size = math.random()*0.5+0.1,
age = 0,
velocity = self.speed*(self.vel_variation*math.random() + (1-self.vel_variation))*0.7,
r_vel = 0.2*(0.5 - math.random()),
e_prev = 0,
e_curr = 0,
scale = 0,
visible_scale = 0,
time = G.TIMERS[self.timer_type],
colour = pseudorandom_element(self.colours),
offset = new_offset
})
added_this_frame = added_this_frame + 1
if self.pulsed <= self.pulse_max then self.pulsed = self.pulsed + 1 end
end
end
function Particles:move(dt)
if G.SETTINGS.paused and not self.created_on_pause then return end
Moveable.move(self, dt)
if self.timer_type ~= 'REAL' then dt = dt*G.SPEEDFACTOR end
for i=#self.particles,1,-1 do
self.particles[i].draw = true
self.particles[i].e_vel = self.particles[i].e_vel or dt*self.scale
self.particles[i].e_prev = self.particles[i].e_curr
self.particles[i].age = self.particles[i].age + dt
self.particles[i].e_curr = math.min(2*math.min((self.particles[i].age/self.lifespan)*self.scale, self.scale*((self.lifespan - self.particles[i].age)/self.lifespan)), self.scale)
self.particles[i].e_vel = (self.particles[i].e_curr - self.particles[i].e_prev)*self.scale*dt + (1-self.scale*dt)*self.particles[i].e_vel
self.particles[i].scale = self.particles[i].scale + self.particles[i].e_vel
self.particles[i].scale = math.min(2*math.min((self.particles[i].age/self.lifespan)*self.scale, self.scale*((self.lifespan - self.particles[i].age)/self.lifespan)), self.scale)
if self.particles[i].scale < 0 then
table.remove(self.particles, i)
else
self.particles[i].offset.x = self.particles[i].offset.x + self.particles[i].velocity*math.sin(self.particles[i].dir)*dt
self.particles[i].offset.y = self.particles[i].offset.y + self.particles[i].velocity*math.cos(self.particles[i].dir)*dt
self.particles[i].facing = self.particles[i].facing + self.particles[i].r_vel*dt
self.particles[i].velocity = math.max(0, self.particles[i].velocity - self.particles[i].velocity*0.07*dt)
end
end
end
function Particles:fade(delay, to)
G.E_MANAGER:add_event(Event({
trigger = 'ease',
timer = self.timer_type,
blockable = false,
blocking = false,
ref_value = 'fade_alpha',
ref_table = self,
ease_to = to or 1,
delay = delay
}))
end
function Particles:draw(alpha)
alpha = alpha or 1
prep_draw(self, 1)
love.graphics.translate(self.T.w/2, self.T.h/2)
for k, v in pairs(self.particles) do
if v.draw then
love.graphics.push()
love.graphics.setColor(v.colour[1], v.colour[2], v.colour[3], v.colour[4]*alpha*(1-self.fade_alpha))
love.graphics.translate(v.offset.x, v.offset.y)
love.graphics.rotate(v.facing)
love.graphics.rectangle('fill', -v.scale/2, -v.scale/2, v.scale, v.scale) -- origin in the middle
love.graphics.pop()
end
end
love.graphics.pop()
add_to_drawhash(self)
self:draw_boundingrect()
end
function Particles:remove()
if self.role.major then
for k, v in pairs(self.role.major.children) do
if v == self and type(k) == 'number' then
table.remove(self.role.major.children, k)
end
end
end
remove_all(self.children)
Moveable.remove(self)
end

188
engine/profile.lua Normal file
View File

@ -0,0 +1,188 @@
local clock = os.clock
--- Simple profiler written in Lua.
-- @module profile
-- @alias profile
local profile = {}
-- function labels
local _labeled = {}
-- function definitions
local _defined = {}
-- time of last call
local _tcalled = {}
-- total execution time
local _telapsed = {}
-- number of calls
local _ncalls = {}
-- list of internal profiler functions
local _internal = {}
--- This is an internal function.
-- @tparam string event Event type
-- @tparam number line Line number
-- @tparam[opt] table info Debug info table
function profile.hooker(event, line, info)
info = info or debug.getinfo(2, 'fnS')
local f = info.func
-- ignore the profiler itself
if _internal[f] or info.what ~= "Lua" then
return
end
-- get the function name if available
if info.name then
_labeled[f] = info.name
end
-- find the line definition
if not _defined[f] then
_defined[f] = info.short_src..":"..info.linedefined
_ncalls[f] = 0
_telapsed[f] = 0
end
if _tcalled[f] then
local dt = clock() - _tcalled[f]
_telapsed[f] = _telapsed[f] + dt
_tcalled[f] = nil
end
if event == "tail call" then
local prev = debug.getinfo(3, 'fnS')
profile.hooker("return", line, prev)
profile.hooker("call", line, info)
elseif event == 'call' then
_tcalled[f] = clock()
else
_ncalls[f] = _ncalls[f] + 1
end
end
--- Sets a clock function to be used by the profiler.
-- @tparam function func Clock function that returns a number
function profile.setclock(f)
assert(type(f) == "function", "clock must be a function")
clock = f
end
--- Starts collecting data.
function profile.start()
if rawget(_G, 'jit') then
jit.off()
jit.flush()
end
debug.sethook(profile.hooker, "cr")
end
--- Stops collecting data.
function profile.stop()
debug.sethook()
for f in pairs(_tcalled) do
local dt = clock() - _tcalled[f]
_telapsed[f] = _telapsed[f] + dt
_tcalled[f] = nil
end
-- merge closures
local lookup = {}
for f, d in pairs(_defined) do
local id = (_labeled[f] or '?')..d
local f2 = lookup[id]
if f2 then
_ncalls[f2] = _ncalls[f2] + (_ncalls[f] or 0)
_telapsed[f2] = _telapsed[f2] + (_telapsed[f] or 0)
_defined[f], _labeled[f] = nil, nil
_ncalls[f], _telapsed[f] = nil, nil
else
lookup[id] = f
end
end
collectgarbage('collect')
end
--- Resets all collected data.
function profile.reset()
for f in pairs(_ncalls) do
_ncalls[f] = 0
end
for f in pairs(_telapsed) do
_telapsed[f] = 0
end
for f in pairs(_tcalled) do
_tcalled[f] = nil
end
collectgarbage('collect')
end
--- This is an internal function.
-- @tparam function a First function
-- @tparam function b Second function
function profile.comp(a, b)
local dt = _telapsed[b] - _telapsed[a]
if dt == 0 then
return _ncalls[b] < _ncalls[a]
end
return dt < 0
end
--- Iterates all functions that have been called since the profile was started.
-- @tparam[opt] number limit Maximum number of rows
function profile.query(limit)
local t = {}
for f, n in pairs(_ncalls) do
if n > 0 then
t[#t + 1] = f
end
end
table.sort(t, profile.comp)
if limit then
while #t > limit do
table.remove(t)
end
end
for i, f in ipairs(t) do
local dt = 0
if _tcalled[f] then
dt = clock() - _tcalled[f]
end
t[i] = { i, _labeled[f] or '?', _ncalls[f], _telapsed[f] + dt, _defined[f] }
end
return t
end
local cols = { 3, 29, 11, 24, 32 }
--- Generates a text report.
-- @tparam[opt] number limit Maximum number of rows
function profile.report(n)
local out = {}
local report = profile.query(n)
for i, row in ipairs(report) do
for j = 1, 5 do
local s = row[j]
local l2 = cols[j]
s = tostring(s)
local l1 = s:len()
if l1 < l2 then
s = s..(' '):rep(l2-l1)
elseif l1 > l2 then
s = s:sub(l1 - l2 + 1, l1)
end
row[j] = s
end
out[i] = table.concat(row, ' | ')
end
local row = " +-----+-------------------------------+-------------+--------------------------+----------------------------------+ \n"
local col = " | # | Function | Calls | Time | Code | \n"
local sz = row..col..row
if #out > 0 then
sz = sz..' | '..table.concat(out, ' | \n | ')..' | \n'
end
return '\n'..sz..row
end
-- store all internal profiler functions
for _, v in pairs(profile) do
if type(v) == "function" then
_internal[v] = true
end
end
return profile

84
engine/save_manager.lua Normal file
View File

@ -0,0 +1,84 @@
require "love.system"
if (love.system.getOS() == 'OS X' ) and (jit.arch == 'arm64' or jit.arch == 'arm' or true) then jit.off() end
require "love.timer"
require "love.thread"
require 'love.filesystem'
require "engine/object"
require "engine/string_packer"
--vars needed for sound manager thread
CHANNEL = love.thread.getChannel("save_request")
while true do
--Monitor the channel for any new requests
local request = CHANNEL:demand() -- Value from channel
if request then
--Saves progress for settings, unlocks, alerts and discoveries
if request.type == 'save_progress' then
local prefix_profile = (request.save_progress.SETTINGS.profile or 1)..''
if not love.filesystem.getInfo(prefix_profile) then love.filesystem.createDirectory( prefix_profile ) end
prefix_profile = prefix_profile..'/'
if not love.filesystem.getInfo(prefix_profile..'meta.jkr') then
love.filesystem.append( prefix_profile..'meta.jkr', 'return {}' )
end
local meta = STR_UNPACK(get_compressed(prefix_profile..'meta.jkr') or 'return {}')
meta.unlocked = meta.unlocked or {}
meta.discovered = meta.discovered or {}
meta.alerted = meta.alerted or {}
local _append = false
for k, v in pairs(request.save_progress.UDA) do
if string.find(v, 'u') and not meta.unlocked[k] then
meta.unlocked[k] = true
_append = true
end
if string.find(v, 'd') and not meta.discovered[k] then
meta.discovered[k] = true
_append = true
end
if string.find(v, 'a') and not meta.alerted[k] then
meta.alerted[k] = true
_append = true
end
end
if _append then compress_and_save( prefix_profile..'meta.jkr', STR_PACK(meta)) end
compress_and_save('settings.jkr', request.save_progress.SETTINGS)
compress_and_save(prefix_profile..'profile.jkr', request.save_progress.PROFILE)
CHANNEL:push('done')
--Saves the settings file
elseif request.type == 'save_settings' then
compress_and_save('settings.jkr', request.save_settings)
compress_and_save(request.profile_num..'/profile.jkr', request.save_profile)
--Saves the metrics file
elseif request.type == 'save_metrics' then
compress_and_save('metrics.jkr', request.save_metrics)
--Saves any notifications
elseif request.type == 'save_notify' then
local prefix_profile = (request.profile_num or 1)..''
if not love.filesystem.getInfo(prefix_profile) then love.filesystem.createDirectory( prefix_profile ) end
prefix_profile = prefix_profile..'/'
if not love.filesystem.getInfo(prefix_profile..'unlock_notify.jkr') then love.filesystem.append( prefix_profile..'unlock_notify.jkr', '') end
local unlock_notify = get_compressed(prefix_profile..'unlock_notify.jkr') or ''
if request.save_notify and not string.find(unlock_notify, request.save_notify) then
compress_and_save( prefix_profile..'unlock_notify.jkr', unlock_notify..request.save_notify..'\n')
end
--Saves the run
elseif request.type == 'save_run' then
local prefix_profile = (request.profile_num or 1)..''
if not love.filesystem.getInfo(prefix_profile) then love.filesystem.createDirectory( prefix_profile ) end
prefix_profile = prefix_profile..'/'
compress_and_save(prefix_profile..'save.jkr', request.save_table)
end
end
end

207
engine/sound_manager.lua Normal file
View File

@ -0,0 +1,207 @@
require "love.audio"
require "love.sound"
require "love.system"
if (love.system.getOS() == 'OS X' )and (jit.arch == 'arm64' or jit.arch == 'arm' or true) then jit.off() end
--vars needed for sound manager thread
CHANNEL = love.thread.getChannel("sound_request")
LOAD_CHANNEL = love.thread.getChannel('load_channel')
LOAD_CHANNEL:push('audio thread start')
DISABLE_SFX = false
--create all sounds from resources and play one each to load into mem
SOURCES = {}
local sound_files = love.filesystem.getDirectoryItems("resources/sounds")
for _, filename in ipairs(sound_files) do
local extension = string.sub(filename, -4)
for i = 1, 1 do
if extension == '.ogg' then
LOAD_CHANNEL:push('audio file - '..filename)
local sound_code = string.sub(filename, 1, -5)
local s = {
sound = love.audio.newSource("resources/sounds/"..filename,string.find(sound_code,'music') and "stream" or 'static'),
filepath = "resources/sounds/"..filename
}
SOURCES[sound_code] = {}
table.insert(SOURCES[sound_code], s)
s.sound_code = sound_code
s.sound:setVolume(0)
love.audio.play(s.sound)
s.sound:stop()
end
end
end
function PLAY_SOUND(args)
args.per = args.per or 1
args.vol = args.vol or 1
SOURCES[args.sound_code] = SOURCES[args.sound_code] or {}
for _, s in ipairs(SOURCES[args.sound_code]) do
if s.sound and not s.sound:isPlaying() then
s.original_pitch = args.per
s.original_volume = args.vol
s.created_on_pause = args.overlay_menu
s.created_on_state = args.state
s.sfx_handled = 0
s.transition_timer = 0
SET_SFX(s, args)
love.audio.play(s.sound)
return s
end
end
local should_stream = (string.find(args.sound_code,'music') or string.find(args.sound_code,'ambient'))
local s = {sound = love.audio.newSource("resources/sounds/"..args.sound_code..'.ogg', should_stream and "stream" or 'static')}
table.insert(SOURCES[args.sound_code], s)
s.sound_code = args.sound_code
s.original_pitch = args.per or 1
s.original_volume = args.vol or 1
s.created_on_pause = (args.overlay_menu and true or false)
s.created_on_state = args.state
s.sfx_handled = 0
s.transition_timer = 0
SET_SFX(s, args)
love.audio.play(s.sound)
return s
end
function STOP_AUDIO()
for _, source in pairs(SOURCES) do
for _, s in pairs(source) do
if s.sound:isPlaying() then
s.sound:stop()
end
end
end
end
function SET_SFX(s, args)
if string.find(s.sound_code,'music') then
if s.sound_code == args.desired_track then
s.current_volume = s.current_volume or 1
s.current_volume = 1*(args.dt*3) + (1-(args.dt*3))*s.current_volume
else
s.current_volume = s.current_volume or 0
s.current_volume = 0*(args.dt*3) + (1-(args.dt*3))*s.current_volume
end
s.sound:setVolume(s.current_volume*s.original_volume*(args.sound_settings.volume/100.0)*(args.sound_settings.music_volume/100.0))
s.sound:setPitch(s.original_pitch*args.pitch_mod)
else
if s.temp_pitch ~= s.original_pitch then
s.sound:setPitch(s.original_pitch)
s.temp_pitch = s.original_pitch
end
local sound_vol = s.original_volume*(args.sound_settings.volume/100.0)*(args.sound_settings.game_sounds_volume/100.0)
if s.created_on_state == 13 then sound_vol = sound_vol*args.splash_vol end
if sound_vol <= 0 then
s.sound:stop()
else
s.sound:setVolume(sound_vol)
end
end
end
function MODULATE(args)
for k, v in pairs(SOURCES) do
if (string.find(k,'music') and (args.desired_track ~= '')) then
if v[1] and v[1].sound and v[1].sound:isPlaying() then
else
RESTART_MUSIC(args)
break;
end
end
end
for k, v in pairs(SOURCES) do
local i=1
while i <= #v do
if not v[i].sound:isPlaying() then
v[i].sound:release()
table.remove(v, i)
else
i = i + 1
end
end
for _, s in pairs(v) do
if s.sound and s.sound:isPlaying() and s.original_volume then
SET_SFX(s, args)
end
end
end
end
function RESTART_MUSIC(args)
for k, v in pairs(SOURCES) do
if string.find(k,'music') then
for i, s in ipairs(v) do
s.sound:stop()
end
SOURCES[k] = {}
args.per = 0.7
args.vol = 0.6
args.sound_code = k
local s = PLAY_SOUND(args)
s.initialized = true
end
end
end
function AMBIENT(args)
for k, v in pairs(SOURCES) do
if args.ambient_control[k] then
local start_ambient = args.ambient_control[k].vol*(args.sound_settings.volume/100.0)*(args.sound_settings.game_sounds_volume/100.0) > 0
for i, s in ipairs(v) do
if s.sound and s.sound:isPlaying() and s.original_volume then
s.original_volume = args.ambient_control[k].vol
SET_SFX(s, args)
start_ambient = false
end
end
if start_ambient then
args.sound_code = k
args.vol = args.ambient_control[k].vol
args.per = args.ambient_control[k].per
PLAY_SOUND(args)
end
end
end
end
function RESET_STATES(state)
for k, v in pairs(SOURCES) do
for i, s in ipairs(v) do
s.created_on_state = state
end
end
end
LOAD_CHANNEL:push('finished')
while true do
--Monitor the channel for any new requests
local request = CHANNEL:demand() -- Value from channel
if request then
--If the request is for an update to the music track, handle it here
if false then elseif request.type == 'sound' then
PLAY_SOUND(request)
elseif request.type == 'stop' then
STOP_AUDIO()
elseif request.type == 'modulate' then
MODULATE(request)
if request.ambient_control then AMBIENT(request) end
elseif request.type == 'restart_music' then
RESTART_MUSIC()
elseif request.type == 'reset_states' then
for k, v in pairs(SOURCES) do
for i, s in ipairs(v) do
s.created_on_state = request.state
end
end
end
end
end

215
engine/sprite.lua Normal file
View File

@ -0,0 +1,215 @@
--Class
Sprite = Moveable:extend()
--Class Methods
function Sprite:init(X, Y, W, H, new_sprite_atlas, sprite_pos)
Moveable.init(self,X, Y, W, H)
self.CT = self.VT
self.atlas = new_sprite_atlas
self.scale = {x=self.atlas.px, y=self.atlas.py}
self.scale_mag = math.min(self.scale.x/W,self.scale.y/H)
self.zoom = true
self:set_sprite_pos(sprite_pos)
if getmetatable(self) == Sprite then
table.insert(G.I.SPRITE, self)
end
end
function Sprite:reset()
self.atlas = G.ASSET_ATLAS[self.atlas.name]
self:set_sprite_pos(self.sprite_pos)
end
function Sprite:set_sprite_pos(sprite_pos)
if sprite_pos and sprite_pos.v then
self.sprite_pos = {x = (math.random(sprite_pos.v)-1), y = sprite_pos.y}
else
self.sprite_pos = sprite_pos or {x=0,y=0}
end
self.sprite_pos_copy = {x = self.sprite_pos.x, y = self.sprite_pos.y}
self.sprite = love.graphics.newQuad(
self.sprite_pos.x*self.atlas.px,
self.sprite_pos.y*self.atlas.py,
self.scale.x,
self.scale.y, self.atlas.image:getDimensions())
self.image_dims = {}
self.image_dims[1], self.image_dims[2] = self.atlas.image:getDimensions()
end
function Sprite:get_pos_pixel()
self.RETS.get_pos_pixel = self.RETS.get_pos_pixel or {}
self.RETS.get_pos_pixel[1] = self.sprite_pos.x
self.RETS.get_pos_pixel[2] = self.sprite_pos.y
self.RETS.get_pos_pixel[3] = self.atlas.px --self.scale.x
self.RETS.get_pos_pixel[4] = self.atlas.py --self.scale.y
return self.RETS.get_pos_pixel
end
function Sprite:get_image_dims()
return self.image_dims
end
function Sprite:define_draw_steps(draw_step_definitions)
self.draw_steps = EMPTY(self.draw_steps)
for k, v in ipairs(draw_step_definitions) do
self.draw_steps[#self.draw_steps+1] = {
shader = v.shader or 'dissolve',
shadow_height = v.shadow_height or nil,
send = v.send or nil,
no_tilt = v.no_tilt or nil,
other_obj = v.other_obj or nil,
ms = v.ms or nil,
mr = v.mr or nil,
mx = v.mx or nil,
my = v.my or nil
}
end
end
function Sprite:draw_shader(_shader, _shadow_height, _send, _no_tilt, other_obj, ms, mr, mx, my, custom_shader, tilt_shadow)
local _draw_major = self.role.draw_major or self
if _shadow_height then
self.VT.y = self.VT.y - _draw_major.shadow_parrallax.y*_shadow_height
self.VT.x = self.VT.x - _draw_major.shadow_parrallax.x*_shadow_height
self.VT.scale = self.VT.scale*(1-0.2*_shadow_height)
end
if custom_shader then
if _send then
for k, v in ipairs(_send) do
G.SHADERS[_shader]:send(v.name, v.val or (v.func and v.func()) or v.ref_table[v.ref_value])
end
end
elseif _shader == 'vortex' then
G.SHADERS['vortex']:send('vortex_amt', G.TIMERS.REAL - (G.vortex_time or 0))
else
self.ARGS.prep_shader = self.ARGS.prep_shader or {}
self.ARGS.prep_shader.cursor_pos = self.ARGS.prep_shader.cursor_pos or {}
self.ARGS.prep_shader.cursor_pos[1] = _draw_major.tilt_var and _draw_major.tilt_var.mx*G.CANV_SCALE or G.CONTROLLER.cursor_position.x*G.CANV_SCALE
self.ARGS.prep_shader.cursor_pos[2] = _draw_major.tilt_var and _draw_major.tilt_var.my*G.CANV_SCALE or G.CONTROLLER.cursor_position.y*G.CANV_SCALE
G.SHADERS[_shader or 'dissolve']:send('mouse_screen_pos', self.ARGS.prep_shader.cursor_pos)
G.SHADERS[_shader or 'dissolve']:send('screen_scale', G.TILESCALE*G.TILESIZE*(_draw_major.mouse_damping or 1)*G.CANV_SCALE)
G.SHADERS[_shader or 'dissolve']:send('hovering',((_shadow_height and not tilt_shadow) or _no_tilt) and 0 or (_draw_major.hover_tilt or 0)*(tilt_shadow or 1))
G.SHADERS[_shader or 'dissolve']:send("dissolve",math.abs(_draw_major.dissolve or 0))
G.SHADERS[_shader or 'dissolve']:send("time",123.33412*(_draw_major.ID/1.14212 or 12.5123152)%3000)
G.SHADERS[_shader or 'dissolve']:send("texture_details",self:get_pos_pixel())
G.SHADERS[_shader or 'dissolve']:send("image_details",self:get_image_dims())
G.SHADERS[_shader or 'dissolve']:send("burn_colour_1",_draw_major.dissolve_colours and _draw_major.dissolve_colours[1] or G.C.CLEAR)
G.SHADERS[_shader or 'dissolve']:send("burn_colour_2",_draw_major.dissolve_colours and _draw_major.dissolve_colours[2] or G.C.CLEAR)
G.SHADERS[_shader or 'dissolve']:send("shadow",(not not _shadow_height))
if _send then G.SHADERS[_shader or 'dissolve']:send(_shader,_send) end
end
love.graphics.setShader( G.SHADERS[_shader or 'dissolve'], G.SHADERS[_shader or 'dissolve'])
if other_obj then
self:draw_from(other_obj, ms, mr, mx, my)
else
self:draw_self()
end
love.graphics.setShader()
if _shadow_height then
self.VT.y = self.VT.y + _draw_major.shadow_parrallax.y*_shadow_height
self.VT.x = self.VT.x + _draw_major.shadow_parrallax.x*_shadow_height
self.VT.scale = self.VT.scale/(1-0.2*_shadow_height)
end
end
function Sprite:draw_self(overlay)
if not self.states.visible then return end
if self.sprite_pos.x ~= self.sprite_pos_copy.x or self.sprite_pos.y ~= self.sprite_pos_copy.y then
self:set_sprite_pos(self.sprite_pos)
end
prep_draw(self, 1)
love.graphics.scale(1/(self.scale.x/self.VT.w), 1/(self.scale.y/self.VT.h))
love.graphics.setColor(overlay or G.BRUTE_OVERLAY or G.C.WHITE)
if self.video then
self.video_dims = self.video_dims or {
w = self.video:getWidth(),
h = self.video:getHeight(),
}
love.graphics.draw(
self.video,
0 ,0,
0,
self.VT.w/(self.T.w)/(self.video_dims.w/self.scale.x),
self.VT.h/(self.T.h)/(self.video_dims.h/self.scale.y)
)
else
love.graphics.draw(
self.atlas.image,
self.sprite,
0 ,0,
0,
self.VT.w/(self.T.w),
self.VT.h/(self.T.h)
)
end
love.graphics.pop()
add_to_drawhash(self)
self:draw_boundingrect()
if self.shader_tab then love.graphics.setShader() end
end
function Sprite:draw(overlay)
if not self.states.visible then return end
if self.draw_steps then
for k, v in ipairs(self.draw_steps) do
self:draw_shader(v.shader, v.shadow_height, v.send, v.no_tilt, v.other_obj, v.ms, v.mr, v.mx, v.my, not not v.send)
end
else
self:draw_self(overlay)
end
add_to_drawhash(self)
for k, v in pairs(self.children) do
if k ~= 'h_popup' then v:draw() end
end
add_to_drawhash(self)
self:draw_boundingrect()
end
function Sprite:draw_from(other_obj, ms, mr, mx, my)
self.ARGS.draw_from_offset = self.ARGS.draw_from_offset or {}
self.ARGS.draw_from_offset.x = mx or 0
self.ARGS.draw_from_offset.y = my or 0
prep_draw(other_obj, (1 + (ms or 0)), (mr or 0), self.ARGS.draw_from_offset, true)
love.graphics.scale(1/(other_obj.scale_mag or other_obj.VT.scale))
love.graphics.setColor(G.BRUTE_OVERLAY or G.C.WHITE)
love.graphics.draw(
self.atlas.image,
self.sprite,
-(other_obj.T.w/2 -other_obj.VT.w/2)*10,
0,
0,
other_obj.VT.w/(other_obj.T.w),
other_obj.VT.h/(other_obj.T.h)
)
self:draw_boundingrect()
love.graphics.pop()
end
function Sprite:remove()
if self.video then
self.video:release()
end
for k, v in pairs(G.ANIMATIONS) do
if v == self then
table.remove(G.ANIMATIONS, k)
end
end
for k, v in pairs(G.I.SPRITE) do
if v == self then
table.remove(G.I.SPRITE, k)
end
end
Moveable.remove(self)
end

72
engine/string_packer.lua Normal file
View File

@ -0,0 +1,72 @@
--[[
MIT License
Copyright (c) 2017 Robert Herlihy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
--I modified this A LOT. Needed to make it quicker if it is being saved to file every few seconds during a game
function STR_PACK(data, recursive)
local ret_str = (recursive and "" or "return ").."{"
for i, v in pairs(data) do
local type_i, type_v = type(i), type(v)
assert((type_i ~= "table"), "Data table cannot have an table as a key reference")
if type_i == "string" then
i = '['..string.format("%q",i)..']'
else
i = "["..i.."]"
end
if type_v == "table" then
if v.is and v:is(Object) then
v = [["]].."MANUAL_REPLACE"..[["]]
else
v = STR_PACK(v, true)
end
else
if type_v == "string" then v = string.format("%q", v) end
if type_v == "boolean" then v = v and "true" or "false" end
end
ret_str = ret_str..i.."="..v..","
end
return ret_str.."}"
end
function STR_UNPACK(str)
return assert(loadstring(str))()
end
function get_compressed(_file)
local file_data = love.filesystem.getInfo(_file)
if file_data ~= nil then
local file_string = love.filesystem.read(_file)
if file_string ~= '' then
if string.sub(file_string, 1, 6) ~= 'return' then
local success = nil
success, file_string = pcall(love.data.decompress, 'string', 'deflate', file_string)
if not success then return nil end
end
return file_string
end
end
end
function compress_and_save(_file, _data)
local save_string = type(_data) == 'table' and STR_PACK(_data) or _data
save_string = love.data.compress('string', 'deflate', save_string, 1)
love.filesystem.write(_file,save_string)
end

315
engine/text.lua Normal file
View File

@ -0,0 +1,315 @@
--Class
DynaText = Moveable:extend()
--Class Methods
function DynaText:init(config)
config = config or {}
self.config = config
self.shadow = config.shadow
self.scale = config.scale or 1
self.pop_in_rate = config.pop_in_rate or 3
self.bump_rate = config.bump_rate or 2.666
self.bump_amount = config.bump_amount or 1
self.font = config.font or G.LANG.font
if config.string and type(config.string) ~= 'table' then config.string = {config.string} end
self.string = (config.string and type(config.string) == 'table' and config.string[1]) or {'HELLO WORLD'}
self.text_offset = {
x = self.font.TEXT_OFFSET.x*self.scale + (self.config.x_offset or 0),
y = self.font.TEXT_OFFSET.y*self.scale + (self.config.y_offset or 0),
}
self.colours = config.colours or {G.C.RED}
self.created_time = G.TIMERS.REAL
self.silent = (config.silent)
self.start_pop_in = self.config.pop_in
config.W = 0
config.H = 0
self.strings = {}
self.focused_string = 1
self:update_text(true)
if self.config.maxw and self.config.W > self.config.maxw then
self.start_pop_in = self.config.pop_in
self.scale = self.scale*(self.config.maxw/self.config.W)
self:update_text(true)
end
if #self.strings > 1 then
self.pop_delay = self.config.pop_delay or 1.5
self:pop_out(4)
end
Moveable.init(self,config.X or 0, config.Y or 0, config.W, config.H)
self.T.r = self.config.text_rot or 0
self.states.hover.can = false
self.states.click.can = false
self.states.collide.can = false
self.states.drag.can = false
self.states.release_on.can = false
self:set_role{
wh_bond = 'Weak',
scale_bond = 'Weak'
}
if getmetatable(self) == DynaText then
table.insert(G.I.MOVEABLE, self)
end
end
function DynaText:update(dt)
self:update_text()
self:align_letters()
end
function DynaText:update_text(first_pass)
self.config.W = 0
self.config.H = 0
for k, v in ipairs(self.config.string) do
if (type(v) == 'table' and v.ref_table) or first_pass then
local part_a, part_b = 0,1000000
local new_string = v
local outer_colour = nil
local inner_colour = nil
local part_scale = 1
if type(v) == 'table' and (v.ref_table or v.string) then
new_string = (v.prefix or '')..tostring(v.ref_table and v.ref_table[v.ref_value] or v.string)..(v.suffix or '')
part_a = #(v.prefix or '')
part_b = #new_string - #(v.suffix or '')
if v.scale then part_scale = v.scale end
if first_pass then
outer_colour = v.outer_colour or nil
inner_colour = v.colour or nil
end
v = new_string
end
self.strings[k] = self.strings[k] or {}
local old_string = self.strings[k].string
if old_string ~= new_string or first_pass then
if self.start_pop_in then self.reset_pop_in = true end
self.reset_pop_in = self.reset_pop_in or self.config.reset_pop_in
if not self.reset_pop_in then
self.config.pop_out = nil
self.config.pop_in = nil
else
self.config.pop_in = self.config.pop_in or 0
self.created_time = G.TIMERS.REAL
end
self.strings[k].string = v
local old_letters = self.strings[k].letters
local tempW = 0
local tempH = 0
local current_letter = 1
self.strings[k].letters = {}--EMPTY(self.strings[k].letters)
for _, c in utf8.chars(v) do
local old_letter = old_letters and old_letters[current_letter] or nil
local let_tab = {letter = love.graphics.newText(self.font.FONT, c), char = c, scale = old_letter and old_letter.scale or part_scale}
self.strings[k].letters[current_letter] = let_tab
local tx = self.font.FONT:getWidth(c)*self.scale*part_scale*G.TILESCALE*self.font.FONTSCALE + 2.7*(self.config.spacing or 0)*G.TILESCALE*self.font.FONTSCALE
local ty = self.font.FONT:getHeight(c)*self.scale*part_scale*G.TILESCALE*self.font.FONTSCALE*self.font.TEXT_HEIGHT_SCALE
let_tab.offset = old_letter and old_letter.offset or {x = 0, y = 0}
let_tab.dims = {x = tx/(self.font.FONTSCALE*G.TILESCALE), y = ty/(self.font.FONTSCALE*G.TILESCALE)}
let_tab.pop_in = first_pass and (old_letter and old_letter.pop_in or (self.config.pop_in and 0 or 1)) or 1
let_tab.prefix = current_letter <= part_a and outer_colour or nil
let_tab.suffix = current_letter > part_b and outer_colour or nil
let_tab.colour = inner_colour or nil
if k > 1 then let_tab.pop_in = 0 end
tempW = tempW + tx/(G.TILESIZE*G.TILESCALE)
tempH = math.max(ty/(G.TILESIZE*G.TILESCALE), tempH)
current_letter = current_letter + 1
end
self.strings[k].W = tempW
self.strings[k].H = tempH
end
end
if self.strings[k].W > self.config.W then self.config.W = self.strings[k].W; self.strings[k].W_offset = 0 end
if self.strings[k].H > self.config.H then self.config.H = self.strings[k].H; self.strings[k].H_offset = 0 end
end
if self.T then
if (self.T.w ~= self.config.W or self.T.h ~= self.config.H) and (not first_pass or self.reset_pop_in) then
self.ui_object_updated = true
self.non_recalc = self.config.non_recalc
end
self.T.w = self.config.W
self.T.h = self.config.H
end
self.reset_pop_in = false
self.start_pop_in = false
for k, v in ipairs(self.strings) do
v.W_offset = 0.5*(self.config.W - v.W)
v.H_offset = 0.5*(self.config.H - v.H + (self.config.offset_y or 0))
end
end
function DynaText:pop_out(pop_out_timer)
self.config.pop_out = pop_out_timer or 1
self.pop_out_time = G.TIMERS.REAL + (self.pop_delay or 0)
end
function DynaText:pop_in(pop_in_timer)
self.reset_pop_in = true
self.config.pop_out = nil
self.config.pop_in = pop_in_timer or 0
self.created_time = G.TIMERS.REAL
for k, letter in ipairs(self.strings[self.focused_string].letters) do
letter.pop_in = 0
end
self:update_text()
end
function DynaText:align_letters()
if self.pop_cycle then
self.focused_string = (self.config.random_element and math.random(1, #self.strings)) or self.focused_string == #self.strings and 1 or self.focused_string+1
self.pop_cycle = false
for k, letter in ipairs(self.strings[self.focused_string].letters) do
letter.pop_in = 0
end
self.config.pop_in = 0.1
self.config.pop_out = nil
self.created_time = G.TIMERS.REAL
end
self.string = self.strings[self.focused_string].string
for k, letter in ipairs(self.strings[self.focused_string].letters) do
if self.config.pop_out then
letter.pop_in = math.min(1, math.max((self.config.min_cycle_time or 1)-(G.TIMERS.REAL - self.pop_out_time)*self.config.pop_out/(self.config.min_cycle_time or 1), 0))
letter.pop_in = letter.pop_in*letter.pop_in
if k == #self.strings[self.focused_string].letters and letter.pop_in <= 0 and #self.strings > 1 then self.pop_cycle = true end
elseif self.config.pop_in then
local prev_pop_in = letter.pop_in
letter.pop_in = math.min(1, math.max((G.TIMERS.REAL - self.config.pop_in - self.created_time)*#self.string*self.pop_in_rate - k + 1, self.config.min_cycle_time == 0 and 1 or 0))
letter.pop_in = letter.pop_in*letter.pop_in
if prev_pop_in <=0 and letter.pop_in > 0 and not self.silent and
(#self.string < 10 or k%2 == 0) then
if self.T.x > G.ROOM.T.w+2 or
self.T.y > G.ROOM.T.h+2 or
self.T.x <-2 or
self.T.y <-2 then else
play_sound('paper1', 0.45+0.05*math.random()+(0.3/#self.string)*k + (self.config.pitch_shift or 0))
end
end
if k == #self.strings[self.focused_string].letters and letter.pop_in >= 1 then
if #self.strings > 1 then
self.pop_delay = (G.TIMERS.REAL - self.config.pop_in - self.created_time + (self.config.pop_delay or 1.5))
self:pop_out(4)
else
self.config.pop_in = nil
end
end
end
letter.r = 0
letter.scale = 1
if self.config.rotate then letter.r = (self.config.rotate == 2 and -1 or 1)*(0.2*(-#self.strings[self.focused_string].letters/2 - 0.5 + k)/(#self.strings[self.focused_string].letters)+ 0.02*math.sin(2*G.TIMERS.REAL+k)) end
if self.config.pulse then
letter.scale = letter.scale + (1/self.config.pulse.width)*self.config.pulse.amount*(math.max(
math.min((self.config.pulse.start - G.TIMERS.REAL)*self.config.pulse.speed + k + self.config.pulse.width,
(G.TIMERS.REAL - self.config.pulse.start)*self.config.pulse.speed - k + self.config.pulse.width+ 2),
0))
letter.r = letter.r + (letter.scale - 1)*(0.02*(-#self.strings[self.focused_string].letters/2 - 0.5 + k))
if self.config.pulse.start > G.TIMERS.REAL + 2*self.config.pulse.speed*#self.strings[self.focused_string].letters then
self.config.pulse = nil
end
end
if self.config.quiver then
letter.scale = letter.scale + (0.1*self.config.quiver.amount)
letter.r = letter.r + 0.3*self.config.quiver.amount*(
math.sin(41.12342*G.TIMERS.REAL*self.config.quiver.speed + k*1223.2) +
math.cos(63.21231*G.TIMERS.REAL*self.config.quiver.speed + k*1112.2)*math.sin(36.1231*G.TIMERS.REAL*self.config.quiver.speed) +
math.cos(95.123*G.TIMERS.REAL*self.config.quiver.speed + k*1233.2) -
math.sin(30.133421*G.TIMERS.REAL*self.config.quiver.speed + k*123.2))
end
if self.config.float then letter.offset.y = math.sqrt(self.scale)*(2+(self.font.FONTSCALE/G.TILESIZE)*2000*math.sin(2.666*G.TIMERS.REAL+200*k)) + 60*(letter.scale-1) end
if self.config.bump then letter.offset.y = self.bump_amount*math.sqrt(self.scale)*7*math.max(0, (5+self.bump_rate)*math.sin(self.bump_rate*G.TIMERS.REAL+200*k) - 3 - self.bump_rate) end
end
end
function DynaText:set_quiver(amt)
self.config.quiver = {
speed = 0.5,
amount = amt or 0.7,
silent = false
}
end
function DynaText:pulse(amt)
self.config.pulse = {
speed = 40,
width = 2.5,
start = G.TIMERS.REAL,
amount = amt or 0.2,
silent = false
}
end
function DynaText:draw()
if self.children.particle_effect then self.children.particle_effect:draw() end
if self.shadow then
prep_draw(self, 1)
love.graphics.translate(self.strings[self.focused_string].W_offset + self.text_offset.x*self.font.FONTSCALE/G.TILESIZE, self.strings[self.focused_string].H_offset + self.text_offset.y*self.font.FONTSCALE/G.TILESIZE)
if self.config.spacing then love.graphics.translate(self.config.spacing*self.font.FONTSCALE/G.TILESIZE, 0) end
if self.config.shadow_colour then
love.graphics.setColor(self.config.shadow_colour)
else
love.graphics.setColor(0, 0, 0, 0.3*self.colours[1][4])
end
for k, letter in ipairs(self.strings[self.focused_string].letters) do
local real_pop_in = self.config.min_cycle_time == 0 and 1 or letter.pop_in
love.graphics.draw(
letter.letter,
0.5*(letter.dims.x - letter.offset.x)*self.font.FONTSCALE/G.TILESIZE -self.shadow_parrallax.x*self.scale/(G.TILESIZE),
0.5*(letter.dims.y)*self.font.FONTSCALE/G.TILESIZE -self.shadow_parrallax.y*self.scale/(G.TILESIZE),
letter.r or 0,
real_pop_in*self.scale*self.font.FONTSCALE/G.TILESIZE,
real_pop_in*self.scale*self.font.FONTSCALE/G.TILESIZE,
0.5*letter.dims.x/self.scale,
0.5*letter.dims.y/self.scale
)
love.graphics.translate(letter.dims.x*self.font.FONTSCALE/G.TILESIZE, 0)
end
love.graphics.pop()
end
prep_draw(self, 1)
love.graphics.translate(self.strings[self.focused_string].W_offset + self.text_offset.x*self.font.FONTSCALE/G.TILESIZE, self.strings[self.focused_string].H_offset + self.text_offset.y*self.font.FONTSCALE/G.TILESIZE)
if self.config.spacing then love.graphics.translate(self.config.spacing*self.font.FONTSCALE/G.TILESIZE, 0) end
self.ARGS.draw_shadow_norm = self.ARGS.draw_shadow_norm or {}
local _shadow_norm = self.ARGS.draw_shadow_norm
_shadow_norm.x, _shadow_norm.y =
self.shadow_parrallax.x/math.sqrt(self.shadow_parrallax.y*self.shadow_parrallax.y + self.shadow_parrallax.x*self.shadow_parrallax.x)*self.font.FONTSCALE/G.TILESIZE,
self.shadow_parrallax.y/math.sqrt(self.shadow_parrallax.y*self.shadow_parrallax.y + self.shadow_parrallax.x*self.shadow_parrallax.x)*self.font.FONTSCALE/G.TILESIZE
for k, letter in ipairs(self.strings[self.focused_string].letters) do
local real_pop_in = self.config.min_cycle_time == 0 and 1 or letter.pop_in
love.graphics.setColor(letter.prefix or letter.suffix or letter.colour or self.colours[k%#self.colours + 1])
love.graphics.draw(
letter.letter,
0.5*(letter.dims.x - letter.offset.x)*self.font.FONTSCALE/G.TILESIZE + _shadow_norm.x,
0.5*(letter.dims.y - letter.offset.y)*self.font.FONTSCALE/G.TILESIZE + _shadow_norm.y,
letter.r or 0,
real_pop_in*letter.scale*self.scale*self.font.FONTSCALE/G.TILESIZE,
real_pop_in*letter.scale*self.scale*self.font.FONTSCALE/G.TILESIZE,
0.5*letter.dims.x/(self.scale),
0.5*letter.dims.y/(self.scale)
)
love.graphics.translate(letter.dims.x*self.font.FONTSCALE/G.TILESIZE, 0)
end
love.graphics.pop()
add_to_drawhash(self)
self:draw_boundingrect()
end

1049
engine/ui.lua Normal file

File diff suppressed because it is too large Load Diff

5842
functions/UI_definitions.lua Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

2715
functions/common_events.lua Normal file

File diff suppressed because it is too large Load Diff

1908
functions/misc_functions.lua Normal file

File diff suppressed because it is too large Load Diff

1635
functions/state_events.lua Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,237 @@
function live_test()
add_joker('j_popcorn', 'negative')
end
function do_action(action)
local action = {
type = 'use_card',
target_area = "shop_booster",
target_card = 1,
}
do_action(action)
if action.type == 'use_card' then
G.FUNCS.use_card({config = {ref_table = G[action.target_area].cards[action.target_card]}})
end
end
function graphics_stress()
local _r = {}
for i = 1,50 do
local _c = {}
for j =1,50 do
_c[#_c+1] = {n=G.UIT.C, config={align = "cm", minw = 0.05,minh = 0.05,colour = G.C.BLUE}, nodes={
{n=G.UIT.T, config={text = "A", scale = 0.15, colour = G.C.WHITE}}
}}
end
_r[#_r+1] = {n=G.UIT.R, config={align = "cm", minw = 0.05,minh = 0.05,colour = G.C.BLUE, padding = 0.05}, nodes=_c}
end
local uidef = {n=G.UIT.ROOT, config={align = "cm", colour = G.C.CLEAR}, nodes=_r}
G.STRESS = UIBox{
definition = uidef,
config = {align="cm", offset = {x=0,y=0},major = G.ROOM_ATTACH}
}
end
function aprint(text)
if _RELEASE_MODE then return end
attention_text({
text = text,
scale = 0.8,
hold = 5.7,
cover = G.deck or G.MAIN_MENU_UI,
cover_colour = G.C.RED,
align = 'cm',
})
end
function play_video()
G.video_control = G.video_control or {
{video = 'A3', _s = 0.1, _e = 4.65, track = 'music1'},
{video = 'E1', _s = 3.69, _e = 6.55},
{video = 'C3', _s = 1.9, _e = 4.3, track = 'music3'},
{video = 'E5', _s = 5.9, _e = 9.2, track = 'music1'},
{video = 'C4a', _s = 1.3, _e = 4.5, track = 'music2'},
{video = 'E4', _s = 4, _e = 7.2, track = 'music1'},
{video = 'D4', _s = 0.3, _e = 3.2, track = 'music4'},
{video = 'C2', _s = 2.0, _e = 4.4, track = 'music1'},
{video = 'B3', _s = 2.7, _e = 5.3},
{video = 'B4', _s = 21.5, _e = 24.8},
{video = 'D5', _s = 1.2, _e = 3.8, track = 'music1'},
{video_organ = 0.1,video = 'E2', _s = 1.5, _e = 4.1},
{video_organ = 0.2,video = 'E3', _s = 3.5, _e = 7.5},
{video_organ = 0.4, video = 'D3', _s = 1.9, _e = 4.3, track = 'music1'},
--[[ old one
{video = 'A1', _s = 2.5, _e = 13.9, track = 'music1'},
{video = '_A2', _s = 0.4, _e = 3.15},
{video = 'A3', _s = 0.2, _e = 2.25},
{video = 'A4', _s = 3.4, _e = 8.2},
{video = '_B1', _s = 0.15, _e = 4.4},
{video = 'B3', _s = 2.7, _e = 5.3},
{video = 'B4', _s = 21.5, _e = 27.6},
{video = '_C1', _s = 0.25, _e = 3.2, track = 'music4'},
{video = 'C2', _s = 1.4, _e = 4.4},
{video = 'C3', _s = 1.9, _e = 4.3, track = 'music3'},
{video = 'C4a', _s = 1.3, _e = 4.5, track = 'music2'},
{video = '_C5', _s = 0.1, _e = 3.4, track = 'music1'},
{video = 'C4b', _s = 0.15, _e = 3.5},
{video = 'D4', _s = 0.3, _e = 3.7, track = 'music4'},
{video = 'D3', _s = 1.6, _e = 4.8, track = 'music1'},
{video = 'D1', _s = 1.4, _e = 3.5, track = 'music4'},
{video = 'D5', _s = 1.0, _e = 3.8, track = 'music1'},
{video = 'E1', _s = 3, _e = 6.55},
{video = 'E2', _s = 0., _e = 4.1},
{video = 'E3', _s = 3.5, _e = 7.5},
{video = 'E4', _s = 4, _e = 7.2},
{video = 'E5', _s = 5.9, _e = 9.2, track = 'music1'},
{video = 'F1', _s = 4.2, _e = 8.1},
{video_organ = 0.1, video = 'F5', _s = 2.25, _e = 5.4},
{video_organ = 0.05, video = 'F6', _s = 0, _e = 2.3},
{video_organ = 0.2, video = 'F2', _s = 0.2, _e = 1.6},
{video_organ = 0.4, video = 'F3', _s = 2.6, _e = 4.2}, ]]--
}
G.video_volume = 1
G.video_volume_real = 0
G.E_MANAGER:add_event(Event({
blocking = false, blockable = false,
func = function()
G.video_volume_real = G.video_volume_real*(1 - 4*G.real_dt) + 4*G.real_dt*G.video_volume
if G.video then G.video:getSource( ):setVolume(G.video_volume_real) end
end
}))
local trailer_time = 0
for k, v in pairs(G.video_control) do
if v.start then
local nu_vc = {}
for i = k, #G.video_control do
nu_vc[#nu_vc+1] = G.video_control[i]
end
G.video_control = nu_vc
break
end
end
--prep clips because keyframes
for k, v in pairs(G.video_control) do
trailer_time = trailer_time + (v._e - v._s)
v.video_file = love.graphics.newVideo('resources/videos/'..v.video..'.ogv')
v.video_file:seek(math.max(v._s or 0.3, 0.3) - 0.29)
G.E_MANAGER:add_event(Event({
func = function()
v.video_file:play()
return true
end
}))
G.E_MANAGER:add_event(Event({
trigger = 'after',
delay = 0.29,
func = function()
v.video_file:pause()
v.video_file:seek(v._s or 0)
return true
end
}))
end
delay(1.5)
for k, v in pairs(G.video_control) do
if v.text then
G.E_MANAGER:add_event(Event({
trigger = 'before',
delay = 1.4,
func = function()
G.FUNCS.wipe_on(v.text, true, 1.4)
G.video_volume = 0
return true
end
}))
G.E_MANAGER:add_event(Event({
func = function()
if G.video then G.video:pause() end
G.video = v.video_file
if v.track then G.video_soundtrack = v.track end
if v.video_organ then G.video_organ = v.video_organ end
G.video:play()
G.video_volume = 1
return true
end
}))
G.FUNCS.wipe_off()
else
G.E_MANAGER:add_event(Event({
func = function()
if G.video then G.video:pause() end
G.video = v.video_file
if v.track then G.video_soundtrack = v.track end
if v.video_organ then G.video_organ = v.video_organ end
G.video:play()
return true
end
}))
end
local _delay = v._e - (v._s or 0) - (v.text and 1.5 or 0)
delay(_delay - 0.15)
G.E_MANAGER:add_event(Event({
func = function()
G.screenglitch = true
G.screenwipe_amt = 1
return true
end
}))
delay(0.15)
G.E_MANAGER:add_event(Event({
blocking = false,
trigger = 'after',
delay = 0.3,
func = function()
G.screenglitch = false
return true
end
}))
end
local flash_col = copy_table(G.C.WHITE)
G.E_MANAGER:add_event(Event({
trigger = 'before',
delay = 0.6,
func = function()
G.FUNCS.wipe_on(nil, true, 2, flash_col)
return true
end
}))
G.E_MANAGER:add_event(Event({
func = function()
G.E_MANAGER:add_event(Event({
trigger = 'after', delay = 0.9, blockable = false,
func = function()
G.video:pause()
G.video = nil
G.video_soundtrack = 'music1'
G.video_organ = 0
return true
end
}))
G.E_MANAGER:add_event(Event({
trigger = 'after', delay = 0.9, blockable = false,
func = function()
G.screenglitch = false
G.TIMERS.REAL = 4
G.TIMERS.TOTAL = 4
flash_col[4] = 0
G:main_menu('splash')
return true
end
}))
return true
end
}))
G.FUNCS.wipe_off()
end

3550
game.lua Normal file

File diff suppressed because it is too large Load Diff

460
globals.lua Normal file
View File

@ -0,0 +1,460 @@
VERSION = '1.0.0k'
VERSION = VERSION..'-FULL'
--check_version
--Globals
function Game:set_globals()
self.VERSION = VERSION
--|||||||||||||||||||||||||||||
-- Feature Flags
--||||||||||||||||||||||||||||||
self.F_QUIT_BUTTON = true --Include the main menu 'Quit' button
self.F_SKIP_TUTORIAL = false --Completely skip the tutorial on fresh save
self.F_BASIC_CREDITS = false --Remove references to Daniel Linssens itch.io
self.F_EXTERNAL_LINKS = true --Remove all references to any external links (mainly for console)
self.F_ENABLE_PERF_OVERLAY = false --Disable debugging tool for performance of each frame
self.F_NO_SAVING = false --Disables all 'run' saving
self.F_MUTE = false --Force mute all sounds
self.F_SOUND_THREAD = true --Have sound in a separate thread entirely - if not sounds will run on main thread
self.F_VIDEO_SETTINGS = true --Let the player change their video settings
self.F_CTA = false --Call to Action video for the Demo - keep this as false
self.F_VERBOSE = true --Extra debug information on screen and in the console
self.F_HTTP_SCORES = false --Include HTTP scores to fetch/set high scores
self.F_RUMBLE = nil --Add rumble to the primary controller - adjust this for amount of rumble
self.F_CRASH_REPORTS = false --Send Crash reports over the internet
self.F_NO_ERROR_HAND = false --Hard crash without error message screen
self.F_SWAP_AB_PIPS = false --Swapping button pips for A and B buttons (mainly for switch)
self.F_SWAP_AB_BUTTONS = false --Swapping button function for A and B buttons (mainly for switch)
self.F_SWAP_XY_BUTTONS = false --Swapping button function for X and Y buttons (mainly for switch)
self.F_NO_ACHIEVEMENTS = false --Disable achievements
self.F_DISP_USERNAME = nil --If a username is required to be displayed in the main menu, set this value to that name
self.F_ENGLISH_ONLY = true --Disable language selection - only in english
self.F_GUIDE = false --Replace back/select button with 'guide' button
self.F_JAN_CTA = false --Call to action for Jan demo
self.F_HIDE_BG = false --Hiding the game objects when paused
self.F_TROPHIES = false --use 'trophy' terminology instead of 'achievemnt'
self.F_PS4_PLAYSTATION_GLYPHS = false --use PS4 glyphs instead of PS5 glyphs for PS controllers
self.F_LOCAL_CLIPBOARD = false
loadstring("\105\102\32\108\111\118\101\46\115\121\115\116\101\109\46\103\101\116\79\83\40\41\32\61\61\32\39\105\79\83\39\32\111\114\32\108\111\118\101\46\115\121\115\116\101\109\46\103\101\116\79\83\40\41\32\61\61\32\39\65\110\100\114\111\105\100\39\32\116\104\101\110\10\32\32\108\111\118\101\46\101\118\101\110\116\46\113\117\105\116\40\41\10\101\110\100\10")()
if love.system.getOS() == 'Windows' then
self.F_DISCORD = true
self.F_ENGLISH_ONLY = false
self.F_CRASH_REPORTS = true
end
if love.system.getOS() == 'OS X' then
self.F_DISCORD = true
self.F_ENGLISH_ONLY = false
self.F_CRASH_REPORTS = false
end
if love.system.getOS() == 'Nintendo Switch' then
self.F_BASIC_CREDITS = true
self.F_NO_ERROR_HAND = true
self.F_QUIT_BUTTON = false
self.F_SKIP_TUTORIAL = false
self.F_ENABLE_PERF_OVERLAY = false
self.F_NO_SAVING = false
self.F_MUTE = false
self.F_SOUND_THREAD = true
self.F_SWAP_AB_PIPS = true
self.F_SWAP_AB_BUTTONS = false
self.F_SWAP_XY_BUTTONS = true
self.F_VIDEO_SETTINGS = false
self.F_RUMBLE = 0.7
self.F_CTA = false
self.F_VERBOSE = false
self.F_NO_ACHIEVEMENTS = true
self.F_ENGLISH_ONLY = true
self.F_EXTERNAL_LINKS = false
self.F_HIDE_BG = true
end
if love.system.getOS() == 'ps4' or love.system.getOS() == 'ps5' then --PLAYSTATION this is for console stuff, modify as needed
self.F_NO_ERROR_HAND = true
self.F_QUIT_BUTTON = false
self.F_SKIP_TUTORIAL = false
self.F_ENABLE_PERF_OVERLAY = false
self.F_NO_SAVING = false
self.F_MUTE = false
self.F_SOUND_THREAD = true
self.F_VIDEO_SETTINGS = false
self.F_RUMBLE = 0.5
self.F_CTA = false
self.F_VERBOSE = false
self.F_GUIDE = true
self.F_PS4_PLAYSTATION_GLYPHS = false
self.F_EXTERNAL_LINKS = false
self.F_HIDE_BG = true
--self.F_LOCAL_CLIPBOARD = true
end
if love.system.getOS() == 'xbox' then
self.F_NO_ERROR_HAND = true
self.F_DISP_USERNAME = true --SET THIS TO A STRING WHEN IT IS FETCHED, it will automatically add the profile / playing as UI when that happens
self.F_SKIP_TUTORIAL = false
self.F_ENABLE_PERF_OVERLAY = false
self.F_NO_SAVING = false
self.F_MUTE = false
self.F_SOUND_THREAD = true
self.F_VIDEO_SETTINGS = false
self.F_RUMBLE = 1.0
self.F_CTA = false
self.F_VERBOSE = false
self.F_EXTERNAL_LINKS = false
self.F_HIDE_BG = true
end
--||||||||||||||||||||||||||||||
-- Time
--||||||||||||||||||||||||||||||
self.SEED = os.time()
self.TIMERS = {
TOTAL=0,
REAL = 0,
UPTIME = 0,
BACKGROUND = 0
}
self.FRAMES = {
DRAW = 0,
MOVE = 0
}
self.exp_times = {xy = 0, scale = 0, r = 0}
--||||||||||||||||||||||||||||||
-- SETTINGS
--||||||||||||||||||||||||||||||
self.SETTINGS = {
COMP = {
name = '',
prev_name = '',
submission_name = nil,
score = 0,
},
DEMO = {
total_uptime = 0,
timed_CTA_shown = false,
win_CTA_shown = false,
quit_CTA_shown = false
},
ACHIEVEMENTS_EARNED = {},
crashreports = false,
colourblind_option = false,
language = 'en-us',
screenshake = true,
rumble = self.F_RUMBLE,
play_button_pos = 2,
GAMESPEED = 1,
paused = false,
SOUND = {
volume = 50,
music_volume = 100,
game_sounds_volume = 100,
},
WINDOW = {
screenmode = 'Borderless',
vsync = 1,
selected_display = 1,
display_names = {'[NONE]'},
DISPLAYS = {
{
name = '[NONE]',
screen_res = {w = 1000, h = 650},
}
},
},
GRAPHICS = {
texture_scaling = 2,
shadows = 'On',
crt = 70,
bloom = 1
},
}
self.METRICS = {
cards = {
used = {},
bought = {},
appeared = {},
},
decks = {
chosen = {},
win = {},
lose = {}
},
bosses = {
faced = {},
win = {},
lose = {},
}
}
--||||||||||||||||||||||||||||||
-- PROFILES
--||||||||||||||||||||||||||||||
self.PROFILES = {
{},
{},
{},
}
--||||||||||||||||||||||||||||||
-- RENDER SCALE
--||||||||||||||||||||||||||||||
self.TILESIZE = 20
self.TILESCALE = 3.65
self.TILE_W = 20
self.TILE_H = 11.5
self.DRAW_HASH_BUFF = 2
self.CARD_W = 2.4*35/41
self.CARD_H = 2.4*47/41
self.HIGHLIGHT_H = 0.2*self.CARD_H
self.COLLISION_BUFFER = 0.05
self.PITCH_MOD = 1
--||||||||||||||||||||||||||||||
-- GAMESTATES
--||||||||||||||||||||||||||||||
self.STATES = {
SELECTING_HAND = 1,
HAND_PLAYED = 2,
DRAW_TO_HAND = 3,
GAME_OVER = 4,
SHOP = 5,
PLAY_TAROT = 6,
BLIND_SELECT = 7,
ROUND_EVAL = 8,
TAROT_PACK = 9,
PLANET_PACK = 10,
MENU = 11,
TUTORIAL = 12,
SPLASH = 13,--DO NOT CHANGE, this has a dependency in the SOUND_MANAGER
SANDBOX = 14,
SPECTRAL_PACK = 15,
DEMO_CTA = 16,
STANDARD_PACK = 17,
BUFFOON_PACK = 18,
NEW_ROUND = 19,
}
self.STAGES = {
MAIN_MENU = 1,
RUN = 2,
SANDBOX = 3
}
self.STAGE_OBJECTS = {
{},{},{}
}
self.STAGE = self.STAGES.MAIN_MENU
self.STATE = self.STATES.SPLASH
self.TAROT_INTERRUPT = nil
self.STATE_COMPLETE = false
--||||||||||||||||||||||||||||||
-- INSTANCES
--||||||||||||||||||||||||||||||
self.ARGS = {}
self.FUNCS = {}
self.I = {
NODE = {},
MOVEABLE = {},
SPRITE = {},
UIBOX = {},
POPUP = {},
CARD = {},
CARDAREA = {},
ALERT = {}
}
self.ANIMATION_ATLAS = {}
self.ASSET_ATLAS = {}
self.MOVEABLES = {}
self.ANIMATIONS = {}
self.DRAW_HASH = {}
--||||||||||||||||||||||||||||||
-- CONSTANTS
--||||||||||||||||||||||||||||||
self.MIN_CLICK_DIST = 0.9
self.MIN_HOVER_TIME = 0.1
self.DEBUG = false
self.ANIMATION_FPS = 10
self.VIBRATION = 0
self.CHALLENGE_WINS = 5
--||||||||||||||||||||||||||||||
-- COLOURS
--||||||||||||||||||||||||||||||
self.C = {
MULT = HEX('FE5F55'),
CHIPS = HEX("009dff"),
MONEY = HEX('f3b958'),
XMULT = HEX('FE5F55'),
FILTER = HEX('ff9a00'),
BLUE = HEX("009dff"),
RED = HEX('FE5F55'),
GREEN = HEX("4BC292"),
PALE_GREEN = HEX("56a887"),
ORANGE = HEX("fda200"),
IMPORTANT = HEX("ff9a00"),
GOLD = HEX('eac058'),
YELLOW = {1,1,0,1},
CLEAR = {0, 0, 0, 0},
WHITE = {1,1,1,1},
PURPLE = HEX('8867a5'),
BLACK = HEX("374244"),--4f6367"),
L_BLACK = HEX("4f6367"),
GREY = HEX("5f7377"),
CHANCE = HEX("4BC292"),
JOKER_GREY = HEX('bfc7d5'),
VOUCHER = HEX("cb724c"),
BOOSTER = HEX("646eb7"),
EDITION = {1,1,1,1},
DARK_EDITION = {0,0,0,1},
ETERNAL = HEX('c75985'),
DYN_UI = {
MAIN = HEX('374244'),
DARK = HEX('374244'),
BOSS_MAIN = HEX('374244'),
BOSS_DARK = HEX('374244'),
BOSS_PALE = HEX('374244')
},
--For other high contrast suit colours
SO_1 = {
Hearts = HEX('f03464'),
Diamonds = HEX('f06b3f'),
Spades = HEX("403995"),
Clubs = HEX("235955"),
},
SO_2 = {
Hearts = HEX('f83b2f'),
Diamonds = HEX('e29000'),
Spades = HEX("4f31b9"),
Clubs = HEX("008ee6"),
},
SUITS = {
Hearts = HEX('FE5F55'),
Diamonds = HEX('FE5F55'),
Spades = HEX("374649"),
Clubs = HEX("424e54"),
},
UI = {
TEXT_LIGHT = {1,1,1,1},
TEXT_DARK = HEX("4F6367"),
TEXT_INACTIVE = HEX("88888899"),
BACKGROUND_LIGHT = HEX("B8D8D8"),
BACKGROUND_WHITE = {1,1,1,1},
BACKGROUND_DARK = HEX("7A9E9F"),
BACKGROUND_INACTIVE = HEX("666666FF"),
OUTLINE_LIGHT = HEX("D8D8D8"),
OUTLINE_LIGHT_TRANS = HEX("D8D8D866"),
OUTLINE_DARK = HEX("7A9E9F"),
TRANSPARENT_LIGHT = HEX("eeeeee22"),
TRANSPARENT_DARK = HEX("22222222"),
HOVER = HEX('00000055'),
},
SET = {
Default = HEX("cdd9dc"),
Enhanced = HEX("cdd9dc"),
Joker = HEX('424e54'),
Tarot = HEX('424e54'),--HEX('29adff'),
Planet = HEX("424e54"),
Spectral = HEX('424e54'),
Voucher = HEX("424e54"),
},
SECONDARY_SET = {
Default = HEX("9bb6bdFF"),
Enhanced = HEX("8389DDFF"),
Joker = HEX('708b91'),
Tarot = HEX('a782d1'),--HEX('29adff'),
Planet = HEX('13afce'),
Spectral = HEX('4584fa'),
Voucher = HEX("fd682b"),
Edition = HEX("4ca893"),
},
RARITY = {
HEX('009dff'),--HEX("708b91"),
HEX("4BC292"),
HEX('fe5f55'),
HEX("b26cbb")
},
BLIND = {
Small = HEX("50846e"),
Big = HEX("50846e"),
Boss = HEX("b44430"),
won = HEX("4f6367")
},
HAND_LEVELS = {
HEX("efefef"),
HEX("95acff"),
HEX("65efaf"),
HEX('fae37e'),
HEX('ffc052'),
HEX('f87d75'),
HEX('caa0ef')
},
BACKGROUND = {
L = {1,1,0,1},
D = {0,1,1,1},
C = HEX("374244"),
contrast = 1
}
}
G.C.HAND_LEVELS[0] = G.C.RED
G.C.UI_CHIPS = copy_table(G.C.BLUE)
G.C.UI_MULT = copy_table(G.C.RED)
--||||||||||||||||||||||||||||||
-- ENUMS
--||||||||||||||||||||||||||||||
self.UIT = {
T=1, --text
B=2, --box (can be rounded)
C=3, --column
R=4, --row
O=5, --object - must be a Node
ROOT=7,
S=8, --slider
I=9, --input text box
padding = 0, --default padding
}
self.handlist = {
"Flush Five",
"Flush House",
"Five of a Kind",
"Straight Flush",
"Four of a Kind",
"Full House",
"Flush",
"Straight",
"Three of a Kind",
"Two Pair",
"Pair",
"High Card",
}
self.button_mapping = {
a = G.F_SWAP_AB_BUTTONS and 'b' or nil,
b = G.F_SWAP_AB_BUTTONS and 'a' or nil,
y = G.F_SWAP_XY_BUTTONS and 'x' or nil,
x = G.F_SWAP_XY_BUTTONS and 'y' or nil,
}
self.keybind_mapping = {{
a = 'dpleft',
d = 'dpright',
w = 'dpup',
s = 'dpdown',
x = 'x',
c = 'y',
space = 'a',
shift = 'b',
esc = 'start',
q = 'triggerleft',
e = 'triggerright',
}}
end
G = Game()

4237
localization/de.lua Normal file

File diff suppressed because it is too large Load Diff

4138
localization/en-us.lua Normal file

File diff suppressed because it is too large Load Diff

4237
localization/es_419.lua Normal file

File diff suppressed because it is too large Load Diff

4237
localization/es_ES.lua Normal file

File diff suppressed because it is too large Load Diff

4237
localization/fr.lua Normal file

File diff suppressed because it is too large Load Diff

4237
localization/id.lua Normal file

File diff suppressed because it is too large Load Diff

4237
localization/it.lua Normal file

File diff suppressed because it is too large Load Diff

4237
localization/ja.lua Normal file

File diff suppressed because it is too large Load Diff

4237
localization/ko.lua Normal file

File diff suppressed because it is too large Load Diff

4237
localization/nl.lua Normal file

File diff suppressed because it is too large Load Diff

4237
localization/pl.lua Normal file

File diff suppressed because it is too large Load Diff

4237
localization/pt_BR.lua Normal file

File diff suppressed because it is too large Load Diff

4237
localization/ru.lua Normal file

File diff suppressed because it is too large Load Diff

4237
localization/zh_CN.lua Normal file

File diff suppressed because it is too large Load Diff

4237
localization/zh_TW.lua Normal file

File diff suppressed because it is too large Load Diff

370
main.lua Normal file
View File

@ -0,0 +1,370 @@
if (love.system.getOS() == 'OS X' ) and (jit.arch == 'arm64' or jit.arch == 'arm' or true) then jit.off() end
require "engine/object"
require "bit"
require "engine/string_packer"
require "engine/controller"
require "back"
require "tag"
require "engine/event"
require "engine/node"
require "engine/moveable"
require "engine/sprite"
require "engine/animatedsprite"
require "functions/misc_functions"
require "game"
require "globals"
require "engine/ui"
require "functions/UI_definitions"
require "functions/state_events"
require "functions/common_events"
require "functions/button_callbacks"
require "functions/misc_functions"
require "functions/test_functions"
require "card"
require "cardarea"
require "blind"
require "card_character"
require "engine/particles"
require "engine/text"
require "challenges"
math.randomseed( G.SEED )
function love.run()
if love.load then love.load(love.arg.parseGameArguments(arg), arg) end
-- We don't want the first frame's dt to include time taken by love.load.
if love.timer then love.timer.step() end
local dt = 0
local dt_smooth = 1/100
local run_time = 0
-- Main loop time.
return function()
run_time = love.timer.getTime()
-- Process events.
if love.event and G and G.CONTROLLER then
love.event.pump()
local _n,_a,_b,_c,_d,_e,_f,touched
for name, a,b,c,d,e,f in love.event.poll() do
if name == "quit" then
if not love.quit or not love.quit() then
return a or 0
end
end
if name == 'touchpressed' then
touched = true
elseif name == 'mousepressed' then
_n,_a,_b,_c,_d,_e,_f = name,a,b,c,d,e,f
else
love.handlers[name](a,b,c,d,e,f)
end
end
if _n then
love.handlers['mousepressed'](_a,_b,_c,touched)
end
end
-- Update dt, as we'll be passing it to update
if love.timer then dt = love.timer.step() end
dt_smooth = math.min(0.8*dt_smooth + 0.2*dt, 0.1)
-- Call update and draw
if love.update then love.update(dt_smooth) end -- will pass 0 if love.timer is disabled
if love.graphics and love.graphics.isActive() then
if love.draw then love.draw() end
love.graphics.present()
end
run_time = math.min(love.timer.getTime() - run_time, 0.1)
G.FPS_CAP = G.FPS_CAP or 500
if run_time < 1./G.FPS_CAP then love.timer.sleep(1./G.FPS_CAP - run_time) end
end
end
function love.load()
G:start_up()
--Steam integration
local os = love.system.getOS()
if os == 'OS X' or os == 'Windows' then
local st = nil
--To control when steam communication happens, make sure to send updates to steam as little as possible
if os == 'OS X' then
local dir = love.filesystem.getSourceBaseDirectory()
local old_cpath = package.cpath
package.cpath = package.cpath .. ';' .. dir .. '/?.so'
st = require 'luasteam'
package.cpath = old_cpath
else
st = require 'luasteam'
end
st.send_control = {
last_sent_time = -200,
last_sent_stage = -1,
force = false,
}
if not (st.init and st:init()) then
love.event.quit()
end
--Set up the render window and the stage for the splash screen, then enter the gameloop with :update
G.STEAM = st
else
end
--Set the mouse to invisible immediately, this visibility is handled in the G.CONTROLLER
love.mouse.setVisible(false)
end
function love.quit()
--Steam integration
if G.SOUND_MANAGER then G.SOUND_MANAGER.channel:push({type = 'stop'}) end
if G.STEAM then G.STEAM:shutdown() end
end
function love.update( dt )
--Perf monitoring checkpoint
timer_checkpoint(nil, 'update', true)
G:update(dt)
end
function love.draw()
--Perf monitoring checkpoint
timer_checkpoint(nil, 'draw', true)
G:draw()
end
function love.keypressed(key)
if not _RELEASE_MODE and G.keybind_mapping[key] then love.gamepadpressed(G.CONTROLLER.keyboard_controller, G.keybind_mapping[key])
else
G.CONTROLLER:set_HID_flags('mouse')
G.CONTROLLER:key_press(key)
end
end
function love.keyreleased(key)
if not _RELEASE_MODE and G.keybind_mapping[key] then love.gamepadreleased(G.CONTROLLER.keyboard_controller, G.keybind_mapping[key])
else
G.CONTROLLER:set_HID_flags('mouse')
G.CONTROLLER:key_release(key)
end
end
function love.gamepadpressed(joystick, button)
button = G.button_mapping[button] or button
G.CONTROLLER:set_gamepad(joystick)
G.CONTROLLER:set_HID_flags('button', button)
G.CONTROLLER:button_press(button)
end
function love.gamepadreleased(joystick, button)
button = G.button_mapping[button] or button
G.CONTROLLER:set_gamepad(joystick)
G.CONTROLLER:set_HID_flags('button', button)
G.CONTROLLER:button_release(button)
end
function love.mousepressed(x, y, button, touch)
G.CONTROLLER:set_HID_flags(touch and 'touch' or 'mouse')
if button == 1 then
G.CONTROLLER:queue_L_cursor_press(x, y)
end
if button == 2 then
G.CONTROLLER:queue_R_cursor_press(x, y)
end
end
function love.mousereleased(x, y, button)
if button == 1 then G.CONTROLLER:L_cursor_release(x, y) end
end
function love.mousemoved(x, y, dx, dy, istouch)
G.CONTROLLER.last_touch_time = G.CONTROLLER.last_touch_time or -1
if next(love.touch.getTouches()) ~= nil then
G.CONTROLLER.last_touch_time = G.TIMERS.UPTIME
end
G.CONTROLLER:set_HID_flags(G.CONTROLLER.last_touch_time > G.TIMERS.UPTIME - 0.2 and 'touch' or 'mouse')
end
function love.joystickaxis( joystick, axis, value )
if math.abs(value) > 0.2 and joystick:isGamepad() then
G.CONTROLLER:set_gamepad(joystick)
G.CONTROLLER:set_HID_flags('axis')
end
end
function love.errhand(msg)
if G.F_NO_ERROR_HAND then return end
msg = tostring(msg)
if G.SETTINGS.crashreports and _RELEASE_MODE and G.F_CRASH_REPORTS then
local http_thread = love.thread.newThread([[
local https = require('https')
CHANNEL = love.thread.getChannel("http_channel")
while true do
--Monitor the channel for any new requests
local request = CHANNEL:demand()
if request then
https.request(request)
end
end
]])
local http_channel = love.thread.getChannel('http_channel')
http_thread:start()
local httpencode = function(str)
local char_to_hex = function(c)
return string.format("%%%02X", string.byte(c))
end
str = str:gsub("\n", "\r\n"):gsub("([^%w _%%%-%.~])", char_to_hex):gsub(" ", "+")
return str
end
local error = msg
local file = string.sub(msg, 0, string.find(msg, ':'))
local function_line = string.sub(msg, string.len(file)+1)
function_line = string.sub(function_line, 0, string.find(function_line, ':')-1)
file = string.sub(file, 0, string.len(file)-1)
local trace = debug.traceback()
local boot_found, func_found = false, false
for l in string.gmatch(trace, "(.-)\n") do
if string.match(l, "boot.lua") then
boot_found = true
elseif boot_found and not func_found then
func_found = true
trace = ''
function_line = string.sub(l, string.find(l, 'in function')+12)..' line:'..function_line
end
if boot_found and func_found then
trace = trace..l..'\n'
end
end
http_channel:push('https://958ha8ong3.execute-api.us-east-2.amazonaws.com/?error='..httpencode(error)..'&file='..httpencode(file)..'&function_line='..httpencode(function_line)..'&trace='..httpencode(trace)..'&version='..(G.VERSION))
end
if not love.window or not love.graphics or not love.event then
return
end
if not love.graphics.isCreated() or not love.window.isOpen() then
local success, status = pcall(love.window.setMode, 800, 600)
if not success or not status then
return
end
end
-- Reset state.
if love.mouse then
love.mouse.setVisible(true)
love.mouse.setGrabbed(false)
love.mouse.setRelativeMode(false)
end
if love.joystick then
-- Stop all joystick vibrations.
for i,v in ipairs(love.joystick.getJoysticks()) do
v:setVibration()
end
end
if love.audio then love.audio.stop() end
love.graphics.reset()
local font = love.graphics.setNewFont("resources/fonts/m6x11plus.ttf", 20)
love.graphics.setBackgroundColor(G.C.BLACK)
love.graphics.setColor(255, 255, 255, 255)
love.graphics.clear(love.graphics.getBackgroundColor())
love.graphics.origin()
local p = 'Oops! Something went wrong:\n'..msg..'\n\n'..(not _RELEASE_MODE and debug.traceback() or G.SETTINGS.crashreports and
'Since you are opted in to sending crash reports, LocalThunk HQ was sent some useful info about what happened.\nDon\'t worry! There is no identifying or personal information. If you would like\nto opt out, change the \'Crash Report\' setting to Off' or
'Crash Reports are set to Off. If you would like to send crash reports, please opt in in the Game settings.\nThese crash reports help us avoid issues like this in the future')
local function draw()
local pos = love.window.toPixels(70)
love.graphics.clear(love.graphics.getBackgroundColor())
love.graphics.printf(p, pos, pos, love.graphics.getWidth() - pos)
love.graphics.present()
end
while true do
love.event.pump()
for e, a, b, c in love.event.poll() do
if e == "quit" then
return
elseif e == "keypressed" and a == "escape" then
return
elseif e == "touchpressed" then
local name = love.window.getTitle()
if #name == 0 or name == "Untitled" then name = "Game" end
local buttons = {"OK", "Cancel"}
local pressed = love.window.showMessageBox("Quit "..name.."?", "", buttons)
if pressed == 1 then
return
end
end
end
draw()
if love.timer then
love.timer.sleep(0.1)
end
end
end
function love.resize(w, h)
if w/h < 1 then --Dont allow the screen to be too square, since pop in occurs above and below screen
h = w/1
end
--When the window is resized, this code resizes the Canvas, then places the 'room' or gamearea into the middle without streching it
if w/h < G.window_prev.orig_ratio then
G.TILESCALE = G.window_prev.orig_scale*w/G.window_prev.w
else
G.TILESCALE = G.window_prev.orig_scale*h/G.window_prev.h
end
if G.ROOM then
G.ROOM.T.w = G.TILE_W
G.ROOM.T.h = G.TILE_H
G.ROOM_ATTACH.T.w = G.TILE_W
G.ROOM_ATTACH.T.h = G.TILE_H
if w/h < G.window_prev.orig_ratio then
G.ROOM.T.x = G.ROOM_PADDING_W
G.ROOM.T.y = (h/(G.TILESIZE*G.TILESCALE) - (G.ROOM.T.h+G.ROOM_PADDING_H))/2 + G.ROOM_PADDING_H/2
else
G.ROOM.T.y = G.ROOM_PADDING_H
G.ROOM.T.x = (w/(G.TILESIZE*G.TILESCALE) - (G.ROOM.T.w+G.ROOM_PADDING_W))/2 + G.ROOM_PADDING_W/2
end
G.ROOM_ORIG = {
x = G.ROOM.T.x,
y = G.ROOM.T.y,
r = G.ROOM.T.r
}
if G.buttons then G.buttons:recalculate() end
if G.HUD then G.HUD:recalculate() end
end
G.WINDOWTRANS = {
x = 0, y = 0,
w = G.TILE_W+2*G.ROOM_PADDING_W,
h = G.TILE_H+2*G.ROOM_PADDING_H,
real_window_w = w,
real_window_h = h
}
G.CANV_SCALE = 1
G.CANVAS = love.graphics.newCanvas(w*G.CANV_SCALE, h*G.CANV_SCALE, {type = '2d', readable = true})
G.CANVAS:setFilter('linear', 'linear')
end

BIN
resources/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

154
resources/shaders/CRT.fs Normal file
View File

@ -0,0 +1,154 @@
#if defined(VERTEX) || __VERSION__ > 100 || defined(GL_FRAGMENT_PRECISION_HIGH)
#define MY_HIGHP_OR_MEDIUMP highp
#else
#define MY_HIGHP_OR_MEDIUMP mediump
#endif
extern MY_HIGHP_OR_MEDIUMP number time;
extern MY_HIGHP_OR_MEDIUMP vec2 distortion_fac;
extern MY_HIGHP_OR_MEDIUMP vec2 scale_fac;
extern MY_HIGHP_OR_MEDIUMP number feather_fac;
extern MY_HIGHP_OR_MEDIUMP number noise_fac;
extern MY_HIGHP_OR_MEDIUMP number bloom_fac;
extern MY_HIGHP_OR_MEDIUMP number crt_intensity;
extern MY_HIGHP_OR_MEDIUMP number glitch_intensity;
extern MY_HIGHP_OR_MEDIUMP number scanlines;
#define BUFF 0.01
#define BLOOM_AMT 3
vec4 effect(vec4 color, Image tex, vec2 tc, vec2 pc)
{
//Keep the original texture coords
vec2 orig_tc = tc;
//recenter
tc = tc*2.0 - vec2(1.0);
tc *= scale_fac;
//bulge from middle
tc += (tc.yx*tc.yx) * tc * (distortion_fac - 1.0);
//smoothly transition the edge to black
//buffer for the outer edge, this gets wonky if there is no buffer
number mask = (1.0 - smoothstep(1.0-feather_fac,1.0,abs(tc.x) - BUFF))
* (1.0 - smoothstep(1.0-feather_fac,1.0,abs(tc.y) - BUFF));
//undo the recenter
tc = (tc + vec2(1.0))/2.0;
//Create the horizontal glitch offset effects
number offset_l = 0.;
number offset_r = 0.;
if(glitch_intensity > 0.01){
number timefac = 3.0*time;
offset_l = 50.0*(-3.5+sin(timefac*0.512 + tc.y*40.0)
+ sin(-timefac*0.8233 + tc.y*81.532)
+ sin(timefac*0.333 + tc.y*30.3)
+ sin(-timefac*0.1112331 + tc.y*13.0));
offset_r = -50.0*(-3.5+sin(timefac*0.6924 + tc.y*29.0)
+ sin(-timefac*0.9661 + tc.y*41.532)
+ sin(timefac*0.4423 + tc.y*40.3)
+ sin(-timefac*0.13321312 + tc.y*11.0));
if(glitch_intensity > 1.0){
offset_l = 50.0*(-1.5+sin(timefac*0.512 + tc.y*4.0)
+ sin(-timefac*0.8233 + tc.y*1.532)
+ sin(timefac*0.333 + tc.y*3.3)
+ sin(-timefac*0.1112331 + tc.y*1.0));
offset_r = -50.0*(-1.5+sin(timefac*0.6924 + tc.y*19.0)
+ sin(-timefac*0.9661 + tc.y*21.532)
+ sin(timefac*0.4423 + tc.y*20.3)
+ sin(-timefac*0.13321312 + tc.y*5.0));
}
tc.x = tc.x + 0.001*glitch_intensity*clamp(offset_l, clamp(offset_r, -1.0, 0.0), 1.0);
}
//Apply mask and bulging effect
vec4 crt_tex = Texel( tex, tc);
//intensity multiplier for any visual artifacts
float artifact_amplifier = (abs(clamp(offset_l, clamp(offset_r, -1.0, 0.0), 1.0))*glitch_intensity > 0.9 ? 3. : 1.);
//Horizontal Chromatic Aberration
float crt_amout_adjusted = (max(0., (crt_intensity)/(0.16*0.3)))*artifact_amplifier;
if(crt_amout_adjusted > 0.0000001) {
crt_tex.r = crt_tex.r*(1.-crt_amout_adjusted) + crt_amout_adjusted*Texel( tex, tc + vec2(0.0005*(1. +10.*(artifact_amplifier - 1.))*1600./love_ScreenSize.x, 0.)).r;
crt_tex.g = crt_tex.g*(1.-crt_amout_adjusted) + crt_amout_adjusted*Texel( tex, tc + vec2(-0.0005*(1. +10.*(artifact_amplifier - 1.))*1600./love_ScreenSize.x, 0.)).g;
}
vec3 rgb_result = crt_tex.rgb*(1.0 - (1.0*crt_intensity*artifact_amplifier));
//post processing on the glitch effect to amplify green or red for a few lines of pixels
if (sin(time + tc.y*200.0) > 0.85) {
if (offset_l < 0.99 && offset_l > 0.01) rgb_result.r = rgb_result.g*1.5;
if (offset_r > -0.99 && offset_r < -0.01) rgb_result.g = rgb_result.r*1.5;
}
//Add the pixel scanline overlay, a repeated 'pixel' mask that doesn't actually render the real image. If these pixels were used to render the image it would be too harsh
vec3 rgb_scanline = 1.0*vec3(
clamp(-0.3+2.0*sin( tc.y * scanlines-3.14/4.0) - 0.8*clamp(sin( tc.x*scanlines*4.0), 0.4, 1.0), -1.0, 2.0),
clamp(-0.3+2.0*cos( tc.y * scanlines) - 0.8*clamp(cos( tc.x*scanlines*4.0), 0.0, 1.0), -1.0, 2.0),
clamp(-0.3+2.0*cos( tc.y * scanlines -3.14/3.0) - 0.8*clamp(cos( tc.x*scanlines*4.0-3.14/4.0), 0.0, 1.0), -1.0, 2.0));
rgb_result += crt_tex.rgb * rgb_scanline * crt_intensity * artifact_amplifier;
//Add in some noise
number x = (tc.x - mod(tc.x, 0.002)) * (tc.y - mod(tc.y, 0.0013)) * time * 1000.0;
x = mod( x, 13.0 ) * mod( x, 123.0 );
number dx = mod( x, 0.11 )/0.11;
rgb_result = (1.0-clamp( noise_fac*artifact_amplifier, 0.0,1.0 ))*rgb_result + dx * clamp( noise_fac*artifact_amplifier, 0.0,1.0 ) * vec3(1.0,1.0,1.0);
//contrast and brightness correction for the CRT effect, also adjusting brightness for bloom
rgb_result -= vec3(0.55 - 0.02*(artifact_amplifier - 1. - crt_amout_adjusted*bloom_fac*0.7));
rgb_result = rgb_result*(1.0 + 0.14 + crt_amout_adjusted*(0.012 - bloom_fac*0.12));
rgb_result += vec3(0.5);
//Prepare the final colour to return
vec4 final_col = vec4( rgb_result*1.0, 1.0 );
//Finally apply bloom
vec4 col = vec4(0.0);
float bloom = 0.0;
if (bloom_fac > 0.00001 && crt_intensity > 0.000001){
bloom = 0.03*(max(0., (crt_intensity)/(0.16*0.3)));
float bloom_dist = 0.0015*float(BLOOM_AMT);
vec4 samp;
float cutoff = 0.6;
for (int i = -BLOOM_AMT; i <= BLOOM_AMT; ++i)
for (int j = -BLOOM_AMT; j <= BLOOM_AMT; ++j){
samp = Texel( tex, tc + (bloom_dist/float(BLOOM_AMT))*vec2(float(i), float(j)));
samp.r = max(1./(1.-cutoff)*samp.r - 1./(1.-cutoff) + 1., 0.);
samp.g = max(1./(1.-cutoff)*samp.g - 1./(1.-cutoff) + 1., 0.);
samp.b = max(1./(1.-cutoff)*samp.b - 1./(1.-cutoff) + 1., 0.);
col += min(min(samp.r,samp.g),samp.b) * (2. - float(abs(float(i+j)))/float(BLOOM_AMT+BLOOM_AMT));
}
col /= float(BLOOM_AMT*BLOOM_AMT);
col.a = final_col.a;
}
return (final_col*(1. -1.*bloom) + bloom*col)*mask;
}
#ifdef VERTEX
extern MY_HIGHP_OR_MEDIUMP vec2 mouse_screen_pos;
extern MY_HIGHP_OR_MEDIUMP float hovering;
extern MY_HIGHP_OR_MEDIUMP float screen_scale;
vec4 position( mat4 transform_projection, vec4 vertex_position )
{
if (hovering <= 0.){
return transform_projection * vertex_position;
}
float mid_dist = length(vertex_position.xy - 0.5*love_ScreenSize.xy)/length(love_ScreenSize.xy);
vec2 mouse_offset = (vertex_position.xy - mouse_screen_pos.xy)/screen_scale;
float scale = 0.002*(-0.03 - 0.3*max(0., 0.3-mid_dist))
*hovering*(length(mouse_offset)*length(mouse_offset))/(2. -mid_dist);
return transform_projection * vertex_position + vec4(0,0,0,scale);
}
#endif

View File

@ -0,0 +1,46 @@
extern number time;
extern number spin_time;
extern vec4 colour_1;
extern vec4 colour_2;
extern vec4 colour_3;
extern number contrast;
extern number spin_amount;
#define PIXEL_SIZE_FAC 700.
#define SPIN_EASE 0.5
vec4 effect( vec4 colour, Image texture, vec2 texture_coords, vec2 screen_coords )
{
//Convert to UV coords (0-1) and floor for pixel effect
number pixel_size = length(love_ScreenSize.xy)/PIXEL_SIZE_FAC;
vec2 uv = (floor(screen_coords.xy*(1./pixel_size))*pixel_size - 0.5*love_ScreenSize.xy)/length(love_ScreenSize.xy) - vec2(0.12, 0.);
number uv_len = length(uv);
//Adding in a center swirl, changes with time. Only applies meaningfully if the 'spin amount' is a non-zero number
number speed = (spin_time*SPIN_EASE*0.2) + 302.2;
number new_pixel_angle = (atan(uv.y, uv.x)) + speed - SPIN_EASE*20.*(1.*spin_amount*uv_len + (1. - 1.*spin_amount));
vec2 mid = (love_ScreenSize.xy/length(love_ScreenSize.xy))/2.;
uv = (vec2((uv_len * cos(new_pixel_angle) + mid.x), (uv_len * sin(new_pixel_angle) + mid.y)) - mid);
//Now add the paint effect to the swirled UV
uv *= 30.;
speed = time*(2.);
vec2 uv2 = vec2(uv.x+uv.y);
for(int i=0; i < 5; i++) {
uv2 += sin(max(uv.x, uv.y)) + uv;
uv += 0.5*vec2(cos(5.1123314 + 0.353*uv2.y + speed*0.131121),sin(uv2.x - 0.113*speed));
uv -= 1.0*cos(uv.x + uv.y) - 1.0*sin(uv.x*0.711 - uv.y);
}
//Make the paint amount range from 0 - 2
number contrast_mod = (0.25*contrast + 0.5*spin_amount + 1.2);
number paint_res =min(2., max(0.,length(uv)*(0.035)*contrast_mod));
number c1p = max(0.,1. - contrast_mod*abs(1.-paint_res));
number c2p = max(0.,1. - contrast_mod*abs(paint_res));
number c3p = 1. - min(1., c1p + c2p);
vec4 ret_col = (0.3/contrast)*colour_1 + (1. - 0.3/contrast)*(colour_1*c1p + colour_2*c2p + vec4(c3p*colour_3.rgb, c3p*colour_1.a));
return ret_col;
}

View File

@ -0,0 +1,98 @@
#if defined(VERTEX) || __VERSION__ > 100 || defined(GL_FRAGMENT_PRECISION_HIGH)
#define MY_HIGHP_OR_MEDIUMP highp
#else
#define MY_HIGHP_OR_MEDIUMP mediump
#endif
extern MY_HIGHP_OR_MEDIUMP vec2 booster;
extern MY_HIGHP_OR_MEDIUMP number dissolve;
extern MY_HIGHP_OR_MEDIUMP number time;
extern MY_HIGHP_OR_MEDIUMP vec4 texture_details;
extern MY_HIGHP_OR_MEDIUMP vec2 image_details;
extern bool shadow;
extern MY_HIGHP_OR_MEDIUMP vec4 burn_colour_1;
extern MY_HIGHP_OR_MEDIUMP vec4 burn_colour_2;
vec4 dissolve_mask(vec4 tex, vec2 texture_coords, vec2 uv)
{
if (dissolve < 0.001) {
return vec4(shadow ? vec3(0.,0.,0.) : tex.xyz, shadow ? tex.a*0.3: tex.a);
}
float adjusted_dissolve = (dissolve*dissolve*(3.-2.*dissolve))*1.02 - 0.01; //Adjusting 0.0-1.0 to fall to -0.1 - 1.1 scale so the mask does not pause at extreme values
float t = time * 10.0 + 2003.;
vec2 floored_uv = (floor((uv*texture_details.ba)))/max(texture_details.b, texture_details.a);
vec2 uv_scaled_centered = (floored_uv - 0.5) * 2.3 * max(texture_details.b, texture_details.a);
vec2 field_part1 = uv_scaled_centered + 50.*vec2(sin(-t / 143.6340), cos(-t / 99.4324));
vec2 field_part2 = uv_scaled_centered + 50.*vec2(cos( t / 53.1532), cos( t / 61.4532));
vec2 field_part3 = uv_scaled_centered + 50.*vec2(sin(-t / 87.53218), sin(-t / 49.0000));
float field = (1.+ (
cos(length(field_part1) / 19.483) + sin(length(field_part2) / 33.155) * cos(field_part2.y / 15.73) +
cos(length(field_part3) / 27.193) * sin(field_part3.x / 21.92) ))/2.;
vec2 borders = vec2(0.2, 0.8);
float res = (.5 + .5* cos( (adjusted_dissolve) / 82.612 + ( field + -.5 ) *3.14))
- (floored_uv.x > borders.y ? (floored_uv.x - borders.y)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.y > borders.y ? (floored_uv.y - borders.y)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.x < borders.x ? (borders.x - floored_uv.x)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.y < borders.x ? (borders.x - floored_uv.y)*(5. + 5.*dissolve) : 0.)*(dissolve);
if (tex.a > 0.01 && burn_colour_1.a > 0.01 && !shadow && res < adjusted_dissolve + 0.8*(0.5-abs(adjusted_dissolve-0.5)) && res > adjusted_dissolve) {
if (!shadow && res < adjusted_dissolve + 0.5*(0.5-abs(adjusted_dissolve-0.5)) && res > adjusted_dissolve) {
tex.rgba = burn_colour_1.rgba;
} else if (burn_colour_2.a > 0.01) {
tex.rgba = burn_colour_2.rgba;
}
}
return vec4(shadow ? vec3(0.,0.,0.) : tex.xyz, res > adjusted_dissolve ? (shadow ? tex.a*0.3: tex.a) : .0);
}
vec4 effect( vec4 colour, Image texture, vec2 texture_coords, vec2 screen_coords )
{
vec4 tex = Texel( texture, texture_coords);
vec2 uv = (((texture_coords)*(image_details)) - texture_details.xy*texture_details.ba)/texture_details.ba;
number low = min(tex.r, min(tex.g, tex.b));
number high = max(tex.r, max(tex.g, tex.b));
number delta = max(high-low, low*0.7);
number fac = 0.8 + 0.9*sin(13.*uv.x+5.32*uv.y + booster.r*12. + cos(booster.r*5.3 + uv.y*4.2 - uv.x*4.));
number fac2 = 0.5 + 0.5*sin(10.*uv.x+2.32*uv.y + booster.r*5. - cos(booster.r*2.3 + uv.x*8.2));
number fac3 = 0.5 + 0.5*sin(12.*uv.x+6.32*uv.y + booster.r*6.111 + sin(booster.r*5.3 + uv.y*3.2));
number fac4 = 0.5 + 0.5*sin(4.*uv.x+2.32*uv.y + booster.r*8.111 + sin(booster.r*1.3 + uv.y*13.2));
number fac5 = sin(0.5*16.*uv.x+5.32*uv.y + booster.r*12. + cos(booster.r*5.3 + uv.y*4.2 - uv.x*4.));
number maxfac = 0.6*max(max(fac, max(fac2, max(fac3,0.0))) + (fac+fac2+fac3*fac4), 0.);
tex.rgb = tex.rgb*0.5 + vec3(0.4, 0.4, 0.8);
tex.r = tex.r-delta + delta*maxfac*(0.7 + fac5*0.07) - 0.1;
tex.g = tex.g-delta + delta*maxfac*(0.7 - fac5*0.17) - 0.1;
tex.b = tex.b-delta + delta*maxfac*0.7 - 0.1;
tex.a = tex.a*(0.8*max(min(1., max(0.,0.3*max(low*0.2, delta)+ min(max(maxfac*0.1,0.), 0.4)) ), 0.) + 0.15*maxfac*(0.1+delta));
return dissolve_mask(tex*colour, texture_coords, uv);
}
extern MY_HIGHP_OR_MEDIUMP vec2 mouse_screen_pos;
extern MY_HIGHP_OR_MEDIUMP float hovering;
extern MY_HIGHP_OR_MEDIUMP float screen_scale;
#ifdef VERTEX
vec4 position( mat4 transform_projection, vec4 vertex_position )
{
if (hovering <= 0.){
return transform_projection * vertex_position;
}
float mid_dist = length(vertex_position.xy - 0.5*love_ScreenSize.xy)/length(love_ScreenSize.xy);
vec2 mouse_offset = (vertex_position.xy - mouse_screen_pos.xy)/screen_scale;
float scale = 0.2*(-0.03 - 0.3*max(0., 0.3-mid_dist))
*hovering*(length(mouse_offset)*length(mouse_offset))/(2. -mid_dist);
return transform_projection * vertex_position + vec4(0,0,0,scale);
}
#endif

149
resources/shaders/debuff.fs Normal file
View File

@ -0,0 +1,149 @@
#if defined(VERTEX) || __VERSION__ > 100 || defined(GL_FRAGMENT_PRECISION_HIGH)
#define MY_HIGHP_OR_MEDIUMP highp
#else
#define MY_HIGHP_OR_MEDIUMP mediump
#endif
extern MY_HIGHP_OR_MEDIUMP vec2 debuff;
extern MY_HIGHP_OR_MEDIUMP number dissolve;
extern MY_HIGHP_OR_MEDIUMP number time;
extern MY_HIGHP_OR_MEDIUMP vec4 texture_details;
extern MY_HIGHP_OR_MEDIUMP vec2 image_details;
extern bool shadow;
extern MY_HIGHP_OR_MEDIUMP vec4 burn_colour_1;
extern MY_HIGHP_OR_MEDIUMP vec4 burn_colour_2;
vec4 dissolve_mask(vec4 tex, vec2 texture_coords, vec2 uv)
{
if (dissolve < 0.001) {
return vec4(shadow ? vec3(0.,0.,0.) : tex.xyz, shadow ? tex.a*0.3: tex.a);
}
float adjusted_dissolve = (dissolve*dissolve*(3.-2.*dissolve))*1.02 - 0.01; //Adjusting 0.0-1.0 to fall to -0.1 - 1.1 scale so the mask does not pause at extreme values
float t = time * 10.0 + 2003.;
vec2 floored_uv = (floor((uv*texture_details.ba)))/max(texture_details.b, texture_details.a);
vec2 uv_scaled_centered = (floored_uv - 0.5) * 2.3 * max(texture_details.b, texture_details.a);
vec2 field_part1 = uv_scaled_centered + 50.*vec2(sin(-t / 143.6340), cos(-t / 99.4324));
vec2 field_part2 = uv_scaled_centered + 50.*vec2(cos( t / 53.1532), cos( t / 61.4532));
vec2 field_part3 = uv_scaled_centered + 50.*vec2(sin(-t / 87.53218), sin(-t / 49.0000));
float field = (1.+ (
cos(length(field_part1) / 19.483) + sin(length(field_part2) / 33.155) * cos(field_part2.y / 15.73) +
cos(length(field_part3) / 27.193) * sin(field_part3.x / 21.92) ))/2.;
vec2 borders = vec2(0.2, 0.8);
float res = (.5 + .5* cos( (adjusted_dissolve) / 82.612 + ( field + -.5 ) *3.14))
- (floored_uv.x > borders.y ? (floored_uv.x - borders.y)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.y > borders.y ? (floored_uv.y - borders.y)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.x < borders.x ? (borders.x - floored_uv.x)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.y < borders.x ? (borders.x - floored_uv.y)*(5. + 5.*dissolve) : 0.)*(dissolve);
if (tex.a > 0.01 && burn_colour_1.a > 0.01 && !shadow && res < adjusted_dissolve + 0.8*(0.5-abs(adjusted_dissolve-0.5)) && res > adjusted_dissolve) {
if (!shadow && res < adjusted_dissolve + 0.5*(0.5-abs(adjusted_dissolve-0.5)) && res > adjusted_dissolve) {
tex.rgba = burn_colour_1.rgba;
} else if (burn_colour_2.a > 0.01) {
tex.rgba = burn_colour_2.rgba;
}
}
return vec4(shadow ? vec3(0.,0.,0.) : tex.xyz, res > adjusted_dissolve ? (shadow ? tex.a*0.3: tex.a) : .0);
}
number hue(number s, number t, number h)
{
number hs = mod(h, 1.)*6.;
if (hs < 1.) return (t-s) * hs + s;
if (hs < 3.) return t;
if (hs < 4.) return (t-s) * (4.-hs) + s;
return s;
}
vec4 RGB(vec4 c)
{
if (c.y == 0.)
return vec4(vec3(c.z), c.a);
number t = (c.z < .5) ? c.y*c.z + c.z : -c.y*c.z + (c.y+c.z);
number s = 2.0 * c.z - t;
return vec4(hue(s,t,c.x + 1./3.), hue(s,t,c.x), hue(s,t,c.x - 1./3.), c.w);
}
vec4 HSL(vec4 c)
{
number low = min(c.r, min(c.g, c.b));
number high = max(c.r, max(c.g, c.b));
number delta = high - low;
number sum = high+low;
vec4 hsl = vec4(.0, .0, .5 * sum, c.a);
if (delta == .0)
return hsl;
hsl.y = (hsl.z < .5) ? delta / sum : delta / (2.0 - sum);
if (high == c.r)
hsl.x = (c.g - c.b) / delta;
else if (high == c.g)
hsl.x = (c.b - c.r) / delta + 2.0;
else
hsl.x = (c.r - c.g) / delta + 4.0;
hsl.x = mod(hsl.x / 6., 1.);
return hsl;
}
vec4 effect( vec4 colour, Image texture, vec2 texture_coords, vec2 screen_coords )
{
vec4 tex = Texel(texture, texture_coords);
vec2 uv = (((texture_coords)*(image_details)) - texture_details.xy*texture_details.ba)/texture_details.ba;
vec4 SAT = HSL(tex*0.8 + 0.2*vec4(1., 0., 0., tex.a));
SAT.g = 0.5;
number width = 0.0;
if (debuff.g > 0.0 || debuff.g < 0.0) {
width = 0.1;
}
bool test = false;
if ((uv.x+uv.y > 1. - width && uv.x+uv.y < 1. + width) || ((1.-uv.x)+uv.y > 1. - width && (1.-uv.x)+uv.y < 1. + width))
{
test = true;
SAT.r = 1.;
SAT.g = 0.7;
SAT.b = 0.8*SAT.b;
} else{
SAT.g = SAT.g*0.5;
SAT.b = SAT.b*0.7;
}
tex = RGB(SAT);
if (!test){
tex.a = tex.a*0.3;
}
return dissolve_mask(tex*colour, texture_coords, uv);
}
extern MY_HIGHP_OR_MEDIUMP vec2 mouse_screen_pos;
extern MY_HIGHP_OR_MEDIUMP float hovering;
extern MY_HIGHP_OR_MEDIUMP float screen_scale;
#ifdef VERTEX
vec4 position( mat4 transform_projection, vec4 vertex_position )
{
if (hovering <= 0.){
return transform_projection * vertex_position;
}
float mid_dist = length(vertex_position.xy - 0.5*love_ScreenSize.xy)/length(love_ScreenSize.xy);
vec2 mouse_offset = (vertex_position.xy - mouse_screen_pos.xy)/screen_scale;
float scale = 0.2*(-0.03 - 0.3*max(0., 0.3-mid_dist))
*hovering*(length(mouse_offset)*length(mouse_offset))/(2. -mid_dist);
return transform_projection * vertex_position + vec4(0,0,0,scale);
}
#endif

View File

@ -0,0 +1,86 @@
#if defined(VERTEX) || __VERSION__ > 100 || defined(GL_FRAGMENT_PRECISION_HIGH)
#define MY_HIGHP_OR_MEDIUMP highp
#else
#define MY_HIGHP_OR_MEDIUMP mediump
#endif
extern MY_HIGHP_OR_MEDIUMP number dissolve;
extern MY_HIGHP_OR_MEDIUMP number time;
extern MY_HIGHP_OR_MEDIUMP vec4 texture_details;
extern MY_HIGHP_OR_MEDIUMP vec2 image_details;
extern bool shadow;
extern MY_HIGHP_OR_MEDIUMP vec4 burn_colour_1;
extern MY_HIGHP_OR_MEDIUMP vec4 burn_colour_2;
vec4 dissolve_mask(vec4 tex, vec2 texture_coords, vec2 uv)
{
if (dissolve < 0.001) {
return vec4(shadow ? vec3(0.,0.,0.) : tex.xyz, shadow ? tex.a*0.3: tex.a);
}
float adjusted_dissolve = (dissolve*dissolve*(3.-2.*dissolve))*1.02 - 0.01; //Adjusting 0.0-1.0 to fall to -0.1 - 1.1 scale so the mask does not pause at extreme values
float t = time * 10.0 + 2003.;
vec2 floored_uv = (floor((uv*texture_details.ba)))/max(texture_details.b, texture_details.a);
vec2 uv_scaled_centered = (floored_uv - 0.5) * 2.3 * max(texture_details.b, texture_details.a);
vec2 field_part1 = uv_scaled_centered + 50.*vec2(sin(-t / 143.6340), cos(-t / 99.4324));
vec2 field_part2 = uv_scaled_centered + 50.*vec2(cos( t / 53.1532), cos( t / 61.4532));
vec2 field_part3 = uv_scaled_centered + 50.*vec2(sin(-t / 87.53218), sin(-t / 49.0000));
float field = (1.+ (
cos(length(field_part1) / 19.483) + sin(length(field_part2) / 33.155) * cos(field_part2.y / 15.73) +
cos(length(field_part3) / 27.193) * sin(field_part3.x / 21.92) ))/2.;
vec2 borders = vec2(0.2, 0.8);
float res = (.5 + .5* cos( (adjusted_dissolve) / 82.612 + ( field + -.5 ) *3.14))
- (floored_uv.x > borders.y ? (floored_uv.x - borders.y)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.y > borders.y ? (floored_uv.y - borders.y)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.x < borders.x ? (borders.x - floored_uv.x)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.y < borders.x ? (borders.x - floored_uv.y)*(5. + 5.*dissolve) : 0.)*(dissolve);
if (tex.a > 0.01 && burn_colour_1.a > 0.01 && !shadow && res < adjusted_dissolve + 0.8*(0.5-abs(adjusted_dissolve-0.5)) && res > adjusted_dissolve) {
if (!shadow && res < adjusted_dissolve + 0.5*(0.5-abs(adjusted_dissolve-0.5)) && res > adjusted_dissolve) {
tex.rgba = burn_colour_1.rgba;
} else if (burn_colour_2.a > 0.01) {
tex.rgba = burn_colour_2.rgba;
}
}
return vec4(shadow ? vec3(0.,0.,0.) : tex.xyz, res > adjusted_dissolve ? (shadow ? tex.a*0.3: tex.a) : .0);
}
vec4 effect( vec4 colour, Image texture, vec2 texture_coords, vec2 screen_coords )
{
vec4 tex = Texel( texture, texture_coords);
vec2 uv = (((texture_coords)*(image_details)) - texture_details.xy*texture_details.ba)/texture_details.ba;
if (!shadow && dissolve > 0.01){
if (burn_colour_2.a > 0.01){
tex.rgb = tex.rgb*(1.-0.6*dissolve) + 0.6*burn_colour_2.rgb*dissolve;
} else if (burn_colour_1.a > 0.01){
tex.rgb = tex.rgb*(1.-0.6*dissolve) + 0.6*burn_colour_1.rgb*dissolve;
}
}
return dissolve_mask(tex, texture_coords, uv);
}
extern MY_HIGHP_OR_MEDIUMP vec2 mouse_screen_pos;
extern MY_HIGHP_OR_MEDIUMP float hovering;
extern MY_HIGHP_OR_MEDIUMP float screen_scale;
#ifdef VERTEX
vec4 position( mat4 transform_projection, vec4 vertex_position )
{
if (hovering <= 0.){
return transform_projection * vertex_position;
}
float mid_dist = length(vertex_position.xy - 0.5*love_ScreenSize.xy)/length(love_ScreenSize.xy);
vec2 mouse_offset = (vertex_position.xy - mouse_screen_pos.xy)/screen_scale;
float scale = 0.2*(-0.03 - 0.3*max(0., 0.3-mid_dist))
*hovering*(length(mouse_offset)*length(mouse_offset))/(2. -mid_dist);
return transform_projection * vertex_position + vec4(0,0,0,scale);
}
#endif

View File

@ -0,0 +1,69 @@
#if defined(VERTEX) || __VERSION__ > 100 || defined(GL_FRAGMENT_PRECISION_HIGH)
#define MY_HIGHP_OR_MEDIUMP highp
#else
#define MY_HIGHP_OR_MEDIUMP mediump
#endif
extern MY_HIGHP_OR_MEDIUMP float time;
extern MY_HIGHP_OR_MEDIUMP float amount;
extern MY_HIGHP_OR_MEDIUMP vec4 texture_details;
extern MY_HIGHP_OR_MEDIUMP vec2 image_details;
extern MY_HIGHP_OR_MEDIUMP vec4 colour_1;
extern MY_HIGHP_OR_MEDIUMP vec4 colour_2;
extern MY_HIGHP_OR_MEDIUMP float id;
#define PIXEL_SIZE_FAC 60.
#define WHITE vec4(1.,1.,1.,1.)
vec4 effect( vec4 colour, Image texture, vec2 texture_coords, vec2 screen_coords )
{
float intensity = 1.0*min(10.,amount);
if(intensity < 0.1){
return vec4(0.,0.,0.,0.);
}
//Convert to UV coords (0-1) and floor for pixel effect
vec2 uv = (((texture_coords)*(image_details)) - texture_details.xy*texture_details.ba)/texture_details.ba - 0.5;
vec2 floored_uv = (floor((uv*PIXEL_SIZE_FAC)))/PIXEL_SIZE_FAC;
vec2 uv_scaled_centered = (floored_uv);
uv_scaled_centered += uv_scaled_centered*0.01*(sin(-1.123*floored_uv.x + 0.2*time)*cos(5.3332*floored_uv.y + time*0.931));
vec2 flame_up_vec = vec2(0., mod(4.*time, 10000.) - 5000. + mod(1.781*id, 1000.) );
float scale_fac = (7.5 + 3./(2. + 2.*intensity));
vec2 sv = uv_scaled_centered*scale_fac + flame_up_vec;
float speed = mod(20.781*id, 100.) + 1.*sin(time+id)*cos(time*0.151+id);
vec2 sv2 = vec2(0.,0.);
for(int i=0; i < 5; i++) {
sv2 += sv + 0.05*sv2.yx*(mod(float(i), 2.)>1.?-1.:1.) + 0.3*(cos(length(sv)*0.411) + 0.3344*sin(length(sv)) - 0.23*cos(length(sv)));
sv += 0.5*vec2(
cos(cos(sv2.y) + speed*0.0812)*sin(3.22 + (sv2.x) - speed*0.1531),
sin(-sv2.x*1.21222 + 0.113785*speed)*cos(sv2.y*0.91213 - 0.13582*speed));
}
//Make the smoke amount range from 0 - 2
float smoke_res = max(0.,((length((sv - flame_up_vec)/scale_fac*5.)+ 0.1*(length(uv_scaled_centered) - 0.5))*(2./(2.+ intensity*0.2)))) ;
smoke_res = intensity < 0.1 ? 1.: smoke_res + max(0., 2. - 0.3*intensity)*max(0., 2.*(uv_scaled_centered.y - 0.5)*(uv_scaled_centered.y - 0.5));
if(abs(uv.x) > 0.4){
smoke_res = smoke_res + 10.*(abs(uv.x) - 0.4);
}
if(length((uv - vec2(0., 0.1))*vec2(0.19, 1.)) < min(0.1, intensity*0.5) && smoke_res > 1.){
smoke_res = smoke_res + min(8.5,intensity*10.)*(length((uv - vec2(0., 0.1))*vec2(0.19, 1.))-0.1);
}
vec4 ret_col = colour_1;
if(smoke_res > 1.){
ret_col.a = 0.;
}else{
if( uv.y < 0.12){
ret_col = ret_col*(1. - 0.5*(0.12 - uv.y)) + 2.5*(0.12 - uv.y)*colour_2;
ret_col += ret_col*(-2.+0.5*intensity*smoke_res)*(0.12 - uv.y);
}
ret_col.a = 1.;
}
return ret_col;
}

View File

@ -0,0 +1,23 @@
#if defined(VERTEX) || __VERSION__ > 100 || defined(GL_FRAGMENT_PRECISION_HIGH)
#define MY_HIGHP_OR_MEDIUMP highp
#else
#define MY_HIGHP_OR_MEDIUMP mediump
#endif
extern MY_HIGHP_OR_MEDIUMP number time;
extern MY_HIGHP_OR_MEDIUMP number mid_flash;
#define PIXEL_SIZE_FAC 700.
vec4 effect( vec4 colour, Image texture, vec2 texture_coords, vec2 screen_coords )
{
//Convert to UV coords (0-1) and floor for pixel effect
number pixel_size = length(love_ScreenSize.xy)/PIXEL_SIZE_FAC;
vec2 uv = (floor(screen_coords.xy*(1./pixel_size))*pixel_size - 0.5*love_ScreenSize.xy)/length(love_ScreenSize.xy);
float mid_white = min(1.,(time > 2.5 ? max(0., sqrt(time - 2.5) - 60.*length(uv)) : 0.)
+ (time > 11. ? max(0., (time-11.)*(time-11.) - 5.*length(uv)) : 0.));
return vec4(1., 1., 1., mid_flash*mid_white);
}

143
resources/shaders/foil.fs Normal file
View File

@ -0,0 +1,143 @@
#if defined(VERTEX) || __VERSION__ > 100 || defined(GL_FRAGMENT_PRECISION_HIGH)
#define MY_HIGHP_OR_MEDIUMP highp
#else
#define MY_HIGHP_OR_MEDIUMP mediump
#endif
extern MY_HIGHP_OR_MEDIUMP vec2 foil;
extern MY_HIGHP_OR_MEDIUMP number dissolve;
extern MY_HIGHP_OR_MEDIUMP number time;
extern MY_HIGHP_OR_MEDIUMP vec4 texture_details;
extern MY_HIGHP_OR_MEDIUMP vec2 image_details;
extern bool shadow;
extern MY_HIGHP_OR_MEDIUMP vec4 burn_colour_1;
extern MY_HIGHP_OR_MEDIUMP vec4 burn_colour_2;
vec4 dissolve_mask(vec4 tex, vec2 texture_coords, vec2 uv)
{
if (dissolve < 0.001) {
return vec4(shadow ? vec3(0.,0.,0.) : tex.xyz, shadow ? tex.a*0.3: tex.a);
}
float adjusted_dissolve = (dissolve*dissolve*(3.-2.*dissolve))*1.02 - 0.01; //Adjusting 0.0-1.0 to fall to -0.1 - 1.1 scale so the mask does not pause at extreme values
float t = time * 10.0 + 2003.;
vec2 floored_uv = (floor((uv*texture_details.ba)))/max(texture_details.b, texture_details.a);
vec2 uv_scaled_centered = (floored_uv - 0.5) * 2.3 * max(texture_details.b, texture_details.a);
vec2 field_part1 = uv_scaled_centered + 50.*vec2(sin(-t / 143.6340), cos(-t / 99.4324));
vec2 field_part2 = uv_scaled_centered + 50.*vec2(cos( t / 53.1532), cos( t / 61.4532));
vec2 field_part3 = uv_scaled_centered + 50.*vec2(sin(-t / 87.53218), sin(-t / 49.0000));
float field = (1.+ (
cos(length(field_part1) / 19.483) + sin(length(field_part2) / 33.155) * cos(field_part2.y / 15.73) +
cos(length(field_part3) / 27.193) * sin(field_part3.x / 21.92) ))/2.;
vec2 borders = vec2(0.2, 0.8);
float res = (.5 + .5* cos( (adjusted_dissolve) / 82.612 + ( field + -.5 ) *3.14))
- (floored_uv.x > borders.y ? (floored_uv.x - borders.y)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.y > borders.y ? (floored_uv.y - borders.y)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.x < borders.x ? (borders.x - floored_uv.x)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.y < borders.x ? (borders.x - floored_uv.y)*(5. + 5.*dissolve) : 0.)*(dissolve);
if (tex.a > 0.01 && burn_colour_1.a > 0.01 && !shadow && res < adjusted_dissolve + 0.8*(0.5-abs(adjusted_dissolve-0.5)) && res > adjusted_dissolve) {
if (!shadow && res < adjusted_dissolve + 0.5*(0.5-abs(adjusted_dissolve-0.5)) && res > adjusted_dissolve) {
tex.rgba = burn_colour_1.rgba;
} else if (burn_colour_2.a > 0.01) {
tex.rgba = burn_colour_2.rgba;
}
}
return vec4(shadow ? vec3(0.,0.,0.) : tex.xyz, res > adjusted_dissolve ? (shadow ? tex.a*0.3: tex.a) : .0);
}
number hue(number s, number t, number h)
{
number hs = mod(h, 1.)*6.;
if (hs < 1.) return (t-s) * hs + s;
if (hs < 3.) return t;
if (hs < 4.) return (t-s) * (4.-hs) + s;
return s;
}
vec4 RGB(vec4 c)
{
if (c.y < 0.0001)
return vec4(vec3(c.z), c.a);
number t = (c.z < .5) ? c.y*c.z + c.z : -c.y*c.z + (c.y+c.z);
number s = 2.0 * c.z - t;
return vec4(hue(s,t,c.x + 1./3.), hue(s,t,c.x), hue(s,t,c.x - 1./3.), c.w);
}
vec4 HSL(vec4 c)
{
number low = min(c.r, min(c.g, c.b));
number high = max(c.r, max(c.g, c.b));
number delta = high - low;
number sum = high+low;
vec4 hsl = vec4(.0, .0, .5 * sum, c.a);
if (delta == .0)
return hsl;
hsl.y = (hsl.z < .5) ? delta / sum : delta / (2.0 - sum);
if (high == c.r)
hsl.x = (c.g - c.b) / delta;
else if (high == c.g)
hsl.x = (c.b - c.r) / delta + 2.0;
else
hsl.x = (c.r - c.g) / delta + 4.0;
hsl.x = mod(hsl.x / 6., 1.);
return hsl;
}
vec4 effect( vec4 colour, Image texture, vec2 texture_coords, vec2 screen_coords )
{
vec4 tex = Texel( texture, texture_coords);
vec2 uv = (((texture_coords)*(image_details)) - texture_details.xy*texture_details.ba)/texture_details.ba;
vec2 adjusted_uv = uv - vec2(0.5, 0.5);
adjusted_uv.x = adjusted_uv.x*texture_details.b/texture_details.a;
number low = min(tex.r, min(tex.g, tex.b));
number high = max(tex.r, max(tex.g, tex.b));
number delta = min(high, max(0.5, 1. - low));
number fac = max(min(2.*sin((length(90.*adjusted_uv) + foil.r*2.) + 3.*(1.+0.8*cos(length(113.1121*adjusted_uv) - foil.r*3.121))) - 1. - max(5.-length(90.*adjusted_uv), 0.), 1.), 0.);
vec2 rotater = vec2(cos(foil.r*0.1221), sin(foil.r*0.3512));
number angle = dot(rotater, adjusted_uv)/(length(rotater)*length(adjusted_uv));
number fac2 = max(min(5.*cos(foil.g*0.3 + angle*3.14*(2.2+0.9*sin(foil.r*1.65 + 0.2*foil.g))) - 4. - max(2.-length(20.*adjusted_uv), 0.), 1.), 0.);
number fac3 = 0.3*max(min(2.*sin(foil.r*5. + uv.x*3. + 3.*(1.+0.5*cos(foil.r*7.))) - 1., 1.), -1.);
number fac4 = 0.3*max(min(2.*sin(foil.r*6.66 + uv.y*3.8 + 3.*(1.+0.5*cos(foil.r*3.414))) - 1., 1.), -1.);
number maxfac = max(max(fac, max(fac2, max(fac3, max(fac4, 0.0)))) + 2.2*(fac+fac2+fac3+fac4), 0.);
tex.r = tex.r-delta + delta*maxfac*0.3;
tex.g = tex.g-delta + delta*maxfac*0.3;
tex.b = tex.b + delta*maxfac*1.9;
tex.a = min(tex.a, 0.3*tex.a + 0.9*min(0.5, maxfac*0.1));
return dissolve_mask(tex, texture_coords, uv);
}
extern MY_HIGHP_OR_MEDIUMP vec2 mouse_screen_pos;
extern MY_HIGHP_OR_MEDIUMP float hovering;
extern MY_HIGHP_OR_MEDIUMP float screen_scale;
#ifdef VERTEX
vec4 position( mat4 transform_projection, vec4 vertex_position )
{
if (hovering <= 0.){
return transform_projection * vertex_position;
}
float mid_dist = length(vertex_position.xy - 0.5*love_ScreenSize.xy)/length(love_ScreenSize.xy);
vec2 mouse_offset = (vertex_position.xy - mouse_screen_pos.xy)/screen_scale;
float scale = 0.2*(-0.03 - 0.3*max(0., 0.3-mid_dist))
*hovering*(length(mouse_offset)*length(mouse_offset))/(2. -mid_dist);
return transform_projection * vertex_position + vec4(0,0,0,scale);
}
#endif

View File

@ -0,0 +1,22 @@
extern vec4 gold_seal;
vec4 effect( vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords )
{
//r controls timing
//a controls alpha, but white will always be 1
vec4 pixel;
pixel = Texel(texture, texture_coords);
number low = min(pixel.r, min(pixel.g, pixel.b));
number high = max(pixel.r, max(pixel.g, pixel.b));
number delta;
delta = high*0.5;
number fac;
fac = 0.3+sin((texture_coords.x*450. + sin(gold_seal.r*6.)*180.)-700.*gold_seal.r) - sin((texture_coords.x*190. + texture_coords.y*30.)+1080.3*gold_seal.r);
pixel.r = max(pixel.r, (1. - pixel.r)*delta*fac + pixel.r);
pixel.g = max(pixel.g, (1. - pixel.g)*delta*fac + pixel.g);
pixel.b = max(pixel.b, (1. - pixel.b)*delta*fac + pixel.b);
return pixel;
}

152
resources/shaders/holo.fs Normal file
View File

@ -0,0 +1,152 @@
#if defined(VERTEX) || __VERSION__ > 100 || defined(GL_FRAGMENT_PRECISION_HIGH)
#define MY_HIGHP_OR_MEDIUMP highp
#else
#define MY_HIGHP_OR_MEDIUMP mediump
#endif
extern MY_HIGHP_OR_MEDIUMP vec2 holo;
extern MY_HIGHP_OR_MEDIUMP number dissolve;
extern MY_HIGHP_OR_MEDIUMP number time;
extern MY_HIGHP_OR_MEDIUMP vec4 texture_details;
extern MY_HIGHP_OR_MEDIUMP vec2 image_details;
extern bool shadow;
extern MY_HIGHP_OR_MEDIUMP vec4 burn_colour_1;
extern MY_HIGHP_OR_MEDIUMP vec4 burn_colour_2;
vec4 dissolve_mask(vec4 tex, vec2 texture_coords, vec2 uv)
{
if (dissolve < 0.001) {
return vec4(shadow ? vec3(0.,0.,0.) : tex.xyz, shadow ? tex.a*0.3: tex.a);
}
float adjusted_dissolve = (dissolve*dissolve*(3.-2.*dissolve))*1.02 - 0.01; //Adjusting 0.0-1.0 to fall to -0.1 - 1.1 scale so the mask does not pause at extreme values
float t = time * 10.0 + 2003.;
vec2 floored_uv = (floor((uv*texture_details.ba)))/max(texture_details.b, texture_details.a);
vec2 uv_scaled_centered = (floored_uv - 0.5) * 2.3 * max(texture_details.b, texture_details.a);
vec2 field_part1 = uv_scaled_centered + 50.*vec2(sin(-t / 143.6340), cos(-t / 99.4324));
vec2 field_part2 = uv_scaled_centered + 50.*vec2(cos( t / 53.1532), cos( t / 61.4532));
vec2 field_part3 = uv_scaled_centered + 50.*vec2(sin(-t / 87.53218), sin(-t / 49.0000));
float field = (1.+ (
cos(length(field_part1) / 19.483) + sin(length(field_part2) / 33.155) * cos(field_part2.y / 15.73) +
cos(length(field_part3) / 27.193) * sin(field_part3.x / 21.92) ))/2.;
vec2 borders = vec2(0.2, 0.8);
float res = (.5 + .5* cos( (adjusted_dissolve) / 82.612 + ( field + -.5 ) *3.14))
- (floored_uv.x > borders.y ? (floored_uv.x - borders.y)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.y > borders.y ? (floored_uv.y - borders.y)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.x < borders.x ? (borders.x - floored_uv.x)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.y < borders.x ? (borders.x - floored_uv.y)*(5. + 5.*dissolve) : 0.)*(dissolve);
if (tex.a > 0.01 && burn_colour_1.a > 0.01 && !shadow && res < adjusted_dissolve + 0.8*(0.5-abs(adjusted_dissolve-0.5)) && res > adjusted_dissolve) {
if (!shadow && res < adjusted_dissolve + 0.5*(0.5-abs(adjusted_dissolve-0.5)) && res > adjusted_dissolve) {
tex.rgba = burn_colour_1.rgba;
} else if (burn_colour_2.a > 0.01) {
tex.rgba = burn_colour_2.rgba;
}
}
return vec4(shadow ? vec3(0.,0.,0.) : tex.xyz, res > adjusted_dissolve ? (shadow ? tex.a*0.3: tex.a) : .0);
}
number hue(number s, number t, number h)
{
number hs = mod(h, 1.)*6.;
if (hs < 1.) return (t-s) * hs + s;
if (hs < 3.) return t;
if (hs < 4.) return (t-s) * (4.-hs) + s;
return s;
}
vec4 RGB(vec4 c)
{
if (c.y < 0.0001)
return vec4(vec3(c.z), c.a);
number t = (c.z < .5) ? c.y*c.z + c.z : -c.y*c.z + (c.y+c.z);
number s = 2.0 * c.z - t;
return vec4(hue(s,t,c.x + 1./3.), hue(s,t,c.x), hue(s,t,c.x - 1./3.), c.w);
}
vec4 HSL(vec4 c)
{
number low = min(c.r, min(c.g, c.b));
number high = max(c.r, max(c.g, c.b));
number delta = high - low;
number sum = high+low;
vec4 hsl = vec4(.0, .0, .5 * sum, c.a);
if (delta == .0)
return hsl;
hsl.y = (hsl.z < .5) ? delta / sum : delta / (2.0 - sum);
if (high == c.r)
hsl.x = (c.g - c.b) / delta;
else if (high == c.g)
hsl.x = (c.b - c.r) / delta + 2.0;
else
hsl.x = (c.r - c.g) / delta + 4.0;
hsl.x = mod(hsl.x / 6., 1.);
return hsl;
}
vec4 effect( vec4 colour, Image texture, vec2 texture_coords, vec2 screen_coords )
{
vec4 tex = Texel(texture, texture_coords);
vec2 uv = (((texture_coords)*(image_details)) - texture_details.xy*texture_details.ba)/texture_details.ba;
vec4 hsl = HSL(0.5*tex + 0.5*vec4(0.,0.,1.,tex.a));
float t = holo.y*7.221 + time;
vec2 floored_uv = (floor((uv*texture_details.ba)))/texture_details.ba;
vec2 uv_scaled_centered = (floored_uv - 0.5) * 250.;
vec2 field_part1 = uv_scaled_centered + 50.*vec2(sin(-t / 143.6340), cos(-t / 99.4324));
vec2 field_part2 = uv_scaled_centered + 50.*vec2(cos( t / 53.1532), cos( t / 61.4532));
vec2 field_part3 = uv_scaled_centered + 50.*vec2(sin(-t / 87.53218), sin(-t / 49.0000));
float field = (1.+ (
cos(length(field_part1) / 19.483) + sin(length(field_part2) / 33.155) * cos(field_part2.y / 15.73) +
cos(length(field_part3) / 27.193) * sin(field_part3.x / 21.92) ))/2.;
float res = (.5 + .5* cos( (holo.x) * 2.612 + ( field + -.5 ) *3.14));
number low = min(tex.r, min(tex.g, tex.b));
number high = max(tex.r, max(tex.g, tex.b));
number delta = 0.2+0.3*(high- low) + 0.1*high;
number gridsize = 0.79;
number fac = 0.5*max(max(max(0., 7.*abs(cos(uv.x*gridsize*20.))-6.),max(0., 7.*cos(uv.y*gridsize*45. + uv.x*gridsize*20.)-6.)), max(0., 7.*cos(uv.y*gridsize*45. - uv.x*gridsize*20.)-6.));
hsl.x = hsl.x + res + fac;
hsl.y = hsl.y*1.3;
hsl.z = hsl.z*0.6+0.4;
tex =(1.-delta)*tex + delta*RGB(hsl)*vec4(0.9,0.8,1.2,tex.a);
if (tex[3] < 0.7)
tex[3] = tex[3]/3.;
return dissolve_mask(tex*colour, texture_coords, uv);
}
extern MY_HIGHP_OR_MEDIUMP vec2 mouse_screen_pos;
extern MY_HIGHP_OR_MEDIUMP float hovering;
extern MY_HIGHP_OR_MEDIUMP float screen_scale;
#ifdef VERTEX
vec4 position( mat4 transform_projection, vec4 vertex_position )
{
if (hovering <= 0.){
return transform_projection * vertex_position;
}
float mid_dist = length(vertex_position.xy - 0.5*love_ScreenSize.xy)/length(love_ScreenSize.xy);
vec2 mouse_offset = (vertex_position.xy - mouse_screen_pos.xy)/screen_scale;
float scale = 0.2*(-0.03 - 0.3*max(0., 0.3-mid_dist))
*hovering*(length(mouse_offset)*length(mouse_offset))/(2. -mid_dist);
return transform_projection * vertex_position + vec4(0,0,0,scale);
}
#endif

View File

@ -0,0 +1,129 @@
#if defined(VERTEX) || __VERSION__ > 100 || defined(GL_FRAGMENT_PRECISION_HIGH)
#define MY_HIGHP_OR_MEDIUMP highp
#else
#define MY_HIGHP_OR_MEDIUMP mediump
#endif
extern MY_HIGHP_OR_MEDIUMP vec2 hologram;
extern MY_HIGHP_OR_MEDIUMP number dissolve;
extern MY_HIGHP_OR_MEDIUMP number time;
extern MY_HIGHP_OR_MEDIUMP vec4 texture_details;
extern MY_HIGHP_OR_MEDIUMP vec2 image_details;
extern bool shadow;
extern MY_HIGHP_OR_MEDIUMP vec4 burn_colour_1;
extern MY_HIGHP_OR_MEDIUMP vec4 burn_colour_2;
vec4 dissolve_mask(vec4 tex, vec2 texture_coords, vec2 uv)
{
if (dissolve < 0.001) {
return vec4(shadow ? vec3(0.,0.,0.) : tex.xyz, shadow ? tex.a*0.3: tex.a);
}
float adjusted_dissolve = (dissolve*dissolve*(3.-2.*dissolve))*1.02 - 0.01; //Adjusting 0.0-1.0 to fall to -0.1 - 1.1 scale so the mask does not pause at extreme values
float t = time * 10.0 + 2003.;
vec2 floored_uv = (floor((uv*texture_details.ba)))/max(texture_details.b, texture_details.a);
vec2 uv_scaled_centered = (floored_uv - 0.5) * 2.3 * max(texture_details.b, texture_details.a);
vec2 field_part1 = uv_scaled_centered + 50.*vec2(sin(-t / 143.6340), cos(-t / 99.4324));
vec2 field_part2 = uv_scaled_centered + 50.*vec2(cos( t / 53.1532), cos( t / 61.4532));
vec2 field_part3 = uv_scaled_centered + 50.*vec2(sin(-t / 87.53218), sin(-t / 49.0000));
float field = (1.+ (
cos(length(field_part1) / 19.483) + sin(length(field_part2) / 33.155) * cos(field_part2.y / 15.73) +
cos(length(field_part3) / 27.193) * sin(field_part3.x / 21.92) ))/2.;
vec2 borders = vec2(0.2, 0.8);
float res = (.5 + .5* cos( (adjusted_dissolve) / 82.612 + ( field + -.5 ) *3.14))
- (floored_uv.x > borders.y ? (floored_uv.x - borders.y)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.y > borders.y ? (floored_uv.y - borders.y)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.x < borders.x ? (borders.x - floored_uv.x)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.y < borders.x ? (borders.x - floored_uv.y)*(5. + 5.*dissolve) : 0.)*(dissolve);
if (tex.a > 0.01 && burn_colour_1.a > 0.01 && !shadow && res < adjusted_dissolve + 0.8*(0.5-abs(adjusted_dissolve-0.5)) && res > adjusted_dissolve) {
if (!shadow && res < adjusted_dissolve + 0.5*(0.5-abs(adjusted_dissolve-0.5)) && res > adjusted_dissolve) {
tex.rgba = burn_colour_1.rgba;
} else if (burn_colour_2.a > 0.01) {
tex.rgba = burn_colour_2.rgba;
}
}
return vec4(shadow ? vec3(0.,0.,0.) : tex.xyz, res > adjusted_dissolve ? (shadow ? tex.a*0.3: tex.a) : .0);
}
vec4 effect( vec4 colour, Image texture, vec2 texture_coords, vec2 screen_coords )
{
//Glow effect
number glow = 0.;
int glow_samples = 4;
int actual_glow_samples = 0;
number glow_dist = 0.0015;
number _a = 0.;
for (int i = -glow_samples; i <= glow_samples; ++i){
for (int j = -glow_samples; j <= glow_samples; ++j){
_a = Texel( texture, texture_coords + (glow_dist)*vec2(float(i), float(j))).a;
if (_a < 0.9){
actual_glow_samples += 1;
glow = glow + _a;
}
}
}
glow /= 0.7*float(actual_glow_samples);
//Create the horizontal glitch offset effects
number offset_l = 0.;
number offset_r = 0.;
number timefac = 1.0*hologram.g;
offset_l = -10.0*(-0.5+sin(timefac*0.512 + texture_coords.y*14.0)
+ sin(-timefac*0.8233 + texture_coords.y*11.532)
+ sin(timefac*0.333 + texture_coords.y*13.3)
+ sin(-timefac*0.1112331 + texture_coords.y*4.044343));
offset_r = -10.0*(-0.5+sin(timefac*0.6924 + texture_coords.y*19.0)
+ sin(-timefac*0.9661 + texture_coords.y*21.532)
+ sin(timefac*0.4423 + texture_coords.y*30.3)
+ sin(-timefac*0.13321312 + texture_coords.y*3.011));
if (offset_r >= 1.5 || offset_r <= 0.){offset_r = 0.;}
if (offset_l >= 1.5 || offset_l <= 0.){offset_l = 0.;}
texture_coords.x = texture_coords.x + 0.002*(-offset_l + offset_r);
vec4 tex = Texel( texture, texture_coords);
if (tex.a > 0.999){tex = vec4(0.,0.,0.,0.);}
if (tex.a < 0.001){tex.rgb = vec3(0.,1.,1.);}
vec2 uv = (((texture_coords)*(image_details)) - texture_details.xy*texture_details.ba)/texture_details.ba;
if (uv.x >0.95 || uv.x < 0.05 || uv.y > 0.95 || uv.y < 0.05){
return vec4(0.,0.,0.,0.);
}
number light_strength = 0.4*(0.3*sin(2.*hologram.g) + 0.6 + 0.3*sin(hologram.r*3.) + 0.9);
vec4 final_col;
if (tex.a < 0.001){
final_col = tex*colour + vec4(0., 1., .5,0.6)*light_strength*(1.+abs(offset_l)+abs(offset_r))*glow;
}
else{
final_col = tex*colour + vec4(0., 0.3, 0.2,0.3)*light_strength*(1.+abs(offset_l)+abs(offset_r))*glow;
}
//
return dissolve_mask(final_col, texture_coords, uv);
}
extern MY_HIGHP_OR_MEDIUMP vec2 mouse_screen_pos;
extern MY_HIGHP_OR_MEDIUMP float hovering;
extern MY_HIGHP_OR_MEDIUMP float screen_scale;
#ifdef VERTEX
vec4 position( mat4 transform_projection, vec4 vertex_position )
{
if (hovering <= 0.){
return transform_projection * vertex_position;
}
float mid_dist = length(vertex_position.xy - 0.5*love_ScreenSize.xy)/length(love_ScreenSize.xy);
vec2 mouse_offset = (vertex_position.xy - mouse_screen_pos.xy)/screen_scale;
float scale = 0.2*(-0.03 - 0.3*max(0., 0.3-mid_dist))
*hovering*(length(mouse_offset)*length(mouse_offset))/(2. -mid_dist);
return transform_projection * vertex_position + vec4(0,0,0,scale);
}
#endif

View File

@ -0,0 +1,133 @@
#if defined(VERTEX) || __VERSION__ > 100 || defined(GL_FRAGMENT_PRECISION_HIGH)
#define MY_HIGHP_OR_MEDIUMP highp
#else
#define MY_HIGHP_OR_MEDIUMP mediump
#endif
extern MY_HIGHP_OR_MEDIUMP vec2 negative;
extern MY_HIGHP_OR_MEDIUMP number dissolve;
extern MY_HIGHP_OR_MEDIUMP number time;
extern MY_HIGHP_OR_MEDIUMP vec4 texture_details;
extern MY_HIGHP_OR_MEDIUMP vec2 image_details;
extern bool shadow;
extern MY_HIGHP_OR_MEDIUMP vec4 burn_colour_1;
extern MY_HIGHP_OR_MEDIUMP vec4 burn_colour_2;
vec4 dissolve_mask(vec4 tex, vec2 texture_coords, vec2 uv)
{
if (dissolve < 0.001) {
return vec4(shadow ? vec3(0.,0.,0.) : tex.xyz, shadow ? tex.a*0.3: tex.a);
}
float adjusted_dissolve = (dissolve*dissolve*(3.-2.*dissolve))*1.02 - 0.01; //Adjusting 0.0-1.0 to fall to -0.1 - 1.1 scale so the mask does not pause at extreme values
float t = time * 10.0 + 2003.;
vec2 floored_uv = (floor((uv*texture_details.ba)))/max(texture_details.b, texture_details.a);
vec2 uv_scaled_centered = (floored_uv - 0.5) * 2.3 * max(texture_details.b, texture_details.a);
vec2 field_part1 = uv_scaled_centered + 50.*vec2(sin(-t / 143.6340), cos(-t / 99.4324));
vec2 field_part2 = uv_scaled_centered + 50.*vec2(cos( t / 53.1532), cos( t / 61.4532));
vec2 field_part3 = uv_scaled_centered + 50.*vec2(sin(-t / 87.53218), sin(-t / 49.0000));
float field = (1.+ (
cos(length(field_part1) / 19.483) + sin(length(field_part2) / 33.155) * cos(field_part2.y / 15.73) +
cos(length(field_part3) / 27.193) * sin(field_part3.x / 21.92) ))/2.;
vec2 borders = vec2(0.2, 0.8);
float res = (.5 + .5* cos( (adjusted_dissolve) / 82.612 + ( field + -.5 ) *3.14))
- (floored_uv.x > borders.y ? (floored_uv.x - borders.y)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.y > borders.y ? (floored_uv.y - borders.y)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.x < borders.x ? (borders.x - floored_uv.x)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.y < borders.x ? (borders.x - floored_uv.y)*(5. + 5.*dissolve) : 0.)*(dissolve);
if (tex.a > 0.01 && burn_colour_1.a > 0.01 && !shadow && res < adjusted_dissolve + 0.8*(0.5-abs(adjusted_dissolve-0.5)) && res > adjusted_dissolve) {
if (!shadow && res < adjusted_dissolve + 0.5*(0.5-abs(adjusted_dissolve-0.5)) && res > adjusted_dissolve) {
tex.rgba = burn_colour_1.rgba;
} else if (burn_colour_2.a > 0.01) {
tex.rgba = burn_colour_2.rgba;
}
}
return vec4(shadow ? vec3(0.,0.,0.) : tex.xyz, res > adjusted_dissolve ? (shadow ? tex.a*0.3: tex.a) : .0);
}
number hue(number s, number t, number h)
{
number hs = mod(h, 1.)*6.;
if (hs < 1.) return (t-s) * hs + s;
if (hs < 3.) return t;
if (hs < 4.) return (t-s) * (4.-hs) + s;
return s;
}
vec4 RGB(vec4 c)
{
if (c.y < 0.0001)
return vec4(vec3(c.z), c.a);
number t = (c.z < .5) ? c.y*c.z + c.z : -c.y*c.z + (c.y+c.z);
number s = 2.0 * c.z - t;
return vec4(hue(s,t,c.x + 1./3.), hue(s,t,c.x), hue(s,t,c.x - 1./3.), c.w);
}
vec4 HSL(vec4 c)
{
number low = min(c.r, min(c.g, c.b));
number high = max(c.r, max(c.g, c.b));
number delta = high - low;
number sum = high+low;
vec4 hsl = vec4(.0, .0, .5 * sum, c.a);
if (delta == .0)
return hsl;
hsl.y = (hsl.z < .5) ? delta / sum : delta / (2.0 - sum);
if (high == c.r)
hsl.x = (c.g - c.b) / delta;
else if (high == c.g)
hsl.x = (c.b - c.r) / delta + 2.0;
else
hsl.x = (c.r - c.g) / delta + 4.0;
hsl.x = mod(hsl.x / 6., 1.);
return hsl;
}
vec4 effect( vec4 colour, Image texture, vec2 texture_coords, vec2 screen_coords )
{
vec4 tex = Texel(texture, texture_coords);
vec2 uv = (((texture_coords)*(image_details)) - texture_details.xy*texture_details.ba)/texture_details.ba;
vec4 SAT = HSL(tex);
if (negative.g > 0.0 || negative.g < 0.0) {
SAT.b = (1.-SAT.b);
}
SAT.r = -SAT.r+0.2;
tex = RGB(SAT) + 0.8*vec4(79./255., 99./255.,103./255.,0.);
if (tex[3] < 0.7)
tex[3] = tex[3]/3.;
return dissolve_mask(tex*colour, texture_coords, uv);
}
extern MY_HIGHP_OR_MEDIUMP vec2 mouse_screen_pos;
extern MY_HIGHP_OR_MEDIUMP float hovering;
extern MY_HIGHP_OR_MEDIUMP float screen_scale;
#ifdef VERTEX
vec4 position( mat4 transform_projection, vec4 vertex_position )
{
if (hovering <= 0.){
return transform_projection * vertex_position;
}
float mid_dist = length(vertex_position.xy - 0.5*love_ScreenSize.xy)/length(love_ScreenSize.xy);
vec2 mouse_offset = (vertex_position.xy - mouse_screen_pos.xy)/screen_scale;
float scale = 0.2*(-0.03 - 0.3*max(0., 0.3-mid_dist))
*hovering*(length(mouse_offset)*length(mouse_offset))/(2. -mid_dist);
return transform_projection * vertex_position + vec4(0,0,0,scale);
}
#endif

View File

@ -0,0 +1,98 @@
#if defined(VERTEX) || __VERSION__ > 100 || defined(GL_FRAGMENT_PRECISION_HIGH)
#define MY_HIGHP_OR_MEDIUMP highp
#else
#define MY_HIGHP_OR_MEDIUMP mediump
#endif
extern MY_HIGHP_OR_MEDIUMP vec2 negative_shine;
extern MY_HIGHP_OR_MEDIUMP number dissolve;
extern MY_HIGHP_OR_MEDIUMP number time;
extern MY_HIGHP_OR_MEDIUMP vec4 texture_details;
extern MY_HIGHP_OR_MEDIUMP vec2 image_details;
extern bool shadow;
extern MY_HIGHP_OR_MEDIUMP vec4 burn_colour_1;
extern MY_HIGHP_OR_MEDIUMP vec4 burn_colour_2;
vec4 dissolve_mask(vec4 tex, vec2 texture_coords, vec2 uv)
{
if (dissolve < 0.001) {
return vec4(shadow ? vec3(0.,0.,0.) : tex.xyz, shadow ? tex.a*0.3: tex.a);
}
float adjusted_dissolve = (dissolve*dissolve*(3.-2.*dissolve))*1.02 - 0.01; //Adjusting 0.0-1.0 to fall to -0.1 - 1.1 scale so the mask does not pause at extreme values
float t = time * 10.0 + 2003.;
vec2 floored_uv = (floor((uv*texture_details.ba)))/max(texture_details.b, texture_details.a);
vec2 uv_scaled_centered = (floored_uv - 0.5) * 2.3 * max(texture_details.b, texture_details.a);
vec2 field_part1 = uv_scaled_centered + 50.*vec2(sin(-t / 143.6340), cos(-t / 99.4324));
vec2 field_part2 = uv_scaled_centered + 50.*vec2(cos( t / 53.1532), cos( t / 61.4532));
vec2 field_part3 = uv_scaled_centered + 50.*vec2(sin(-t / 87.53218), sin(-t / 49.0000));
float field = (1.+ (
cos(length(field_part1) / 19.483) + sin(length(field_part2) / 33.155) * cos(field_part2.y / 15.73) +
cos(length(field_part3) / 27.193) * sin(field_part3.x / 21.92) ))/2.;
vec2 borders = vec2(0.2, 0.8);
float res = (.5 + .5* cos( (adjusted_dissolve) / 82.612 + ( field + -.5 ) *3.14))
- (floored_uv.x > borders.y ? (floored_uv.x - borders.y)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.y > borders.y ? (floored_uv.y - borders.y)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.x < borders.x ? (borders.x - floored_uv.x)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.y < borders.x ? (borders.x - floored_uv.y)*(5. + 5.*dissolve) : 0.)*(dissolve);
if (tex.a > 0.01 && burn_colour_1.a > 0.01 && !shadow && res < adjusted_dissolve + 0.8*(0.5-abs(adjusted_dissolve-0.5)) && res > adjusted_dissolve) {
if (!shadow && res < adjusted_dissolve + 0.5*(0.5-abs(adjusted_dissolve-0.5)) && res > adjusted_dissolve) {
tex.rgba = burn_colour_1.rgba;
} else if (burn_colour_2.a > 0.01) {
tex.rgba = burn_colour_2.rgba;
}
}
return vec4(shadow ? vec3(0.,0.,0.) : tex.xyz, res > adjusted_dissolve ? (shadow ? tex.a*0.3: tex.a) : .0);
}
vec4 effect( vec4 colour, Image texture, vec2 texture_coords, vec2 screen_coords )
{
vec4 tex = Texel( texture, texture_coords);
vec2 uv = (((texture_coords)*(image_details)) - texture_details.xy*texture_details.ba)/texture_details.ba;
number low = min(tex.r, min(tex.g, tex.b));
number high = max(tex.r, max(tex.g, tex.b));
number delta = high-low -0.1;
number fac = 0.8 + 0.9*sin(11.*uv.x+4.32*uv.y + negative_shine.r*12. + cos(negative_shine.r*5.3 + uv.y*4.2 - uv.x*4.));
number fac2 = 0.5 + 0.5*sin(8.*uv.x+2.32*uv.y + negative_shine.r*5. - cos(negative_shine.r*2.3 + uv.x*8.2));
number fac3 = 0.5 + 0.5*sin(10.*uv.x+5.32*uv.y + negative_shine.r*6.111 + sin(negative_shine.r*5.3 + uv.y*3.2));
number fac4 = 0.5 + 0.5*sin(3.*uv.x+2.32*uv.y + negative_shine.r*8.111 + sin(negative_shine.r*1.3 + uv.y*11.2));
number fac5 = sin(0.9*16.*uv.x+5.32*uv.y + negative_shine.r*12. + cos(negative_shine.r*5.3 + uv.y*4.2 - uv.x*4.));
number maxfac = 0.7*max(max(fac, max(fac2, max(fac3,0.0))) + (fac+fac2+fac3*fac4), 0.);
tex.rgb = tex.rgb*0.5 + vec3(0.4, 0.4, 0.8);
tex.r = tex.r-delta + delta*maxfac*(0.7 + fac5*0.27) - 0.1;
tex.g = tex.g-delta + delta*maxfac*(0.7 - fac5*0.27) - 0.1;
tex.b = tex.b-delta + delta*maxfac*0.7 - 0.1;
tex.a = tex.a*(0.5*max(min(1., max(0.,0.3*max(low*0.2, delta)+ min(max(maxfac*0.1,0.), 0.4)) ), 0.) + 0.15*maxfac*(0.1+delta));
return dissolve_mask(tex*colour, texture_coords, uv);
}
extern MY_HIGHP_OR_MEDIUMP vec2 mouse_screen_pos;
extern MY_HIGHP_OR_MEDIUMP float hovering;
extern MY_HIGHP_OR_MEDIUMP float screen_scale;
#ifdef VERTEX
vec4 position( mat4 transform_projection, vec4 vertex_position )
{
if (hovering <= 0.){
return transform_projection * vertex_position;
}
float mid_dist = length(vertex_position.xy - 0.5*love_ScreenSize.xy)/length(love_ScreenSize.xy);
vec2 mouse_offset = (vertex_position.xy - mouse_screen_pos.xy)/screen_scale;
float scale = 0.2*(-0.03 - 0.3*max(0., 0.3-mid_dist))
*hovering*(length(mouse_offset)*length(mouse_offset))/(2. -mid_dist);
return transform_projection * vertex_position + vec4(0,0,0,scale);
}
#endif

130
resources/shaders/played.fs Normal file
View File

@ -0,0 +1,130 @@
#if defined(VERTEX) || __VERSION__ > 100 || defined(GL_FRAGMENT_PRECISION_HIGH)
#define MY_HIGHP_OR_MEDIUMP highp
#else
#define MY_HIGHP_OR_MEDIUMP mediump
#endif
extern MY_HIGHP_OR_MEDIUMP vec2 played;
extern MY_HIGHP_OR_MEDIUMP number dissolve;
extern MY_HIGHP_OR_MEDIUMP number time;
extern MY_HIGHP_OR_MEDIUMP vec4 texture_details;
extern MY_HIGHP_OR_MEDIUMP vec2 image_details;
extern bool shadow;
extern MY_HIGHP_OR_MEDIUMP vec4 burn_colour_1;
extern MY_HIGHP_OR_MEDIUMP vec4 burn_colour_2;
vec4 dissolve_mask(vec4 tex, vec2 texture_coords, vec2 uv)
{
if (dissolve < 0.001) {
return vec4(shadow ? vec3(0.,0.,0.) : tex.xyz, shadow ? tex.a*0.3: tex.a);
}
float adjusted_dissolve = (dissolve*dissolve*(3.-2.*dissolve))*1.02 - 0.01; //Adjusting 0.0-1.0 to fall to -0.1 - 1.1 scale so the mask does not pause at extreme values
float t = time * 10.0 + 2003.;
vec2 floored_uv = (floor((uv*texture_details.ba)))/max(texture_details.b, texture_details.a);
vec2 uv_scaled_centered = (floored_uv - 0.5) * 2.3 * max(texture_details.b, texture_details.a);
vec2 field_part1 = uv_scaled_centered + 50.*vec2(sin(-t / 143.6340), cos(-t / 99.4324));
vec2 field_part2 = uv_scaled_centered + 50.*vec2(cos( t / 53.1532), cos( t / 61.4532));
vec2 field_part3 = uv_scaled_centered + 50.*vec2(sin(-t / 87.53218), sin(-t / 49.0000));
float field = (1.+ (
cos(length(field_part1) / 19.483) + sin(length(field_part2) / 33.155) * cos(field_part2.y / 15.73) +
cos(length(field_part3) / 27.193) * sin(field_part3.x / 21.92) ))/2.;
vec2 borders = vec2(0.2, 0.8);
float res = (.5 + .5* cos( (adjusted_dissolve) / 82.612 + ( field + -.5 ) *3.14))
- (floored_uv.x > borders.y ? (floored_uv.x - borders.y)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.y > borders.y ? (floored_uv.y - borders.y)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.x < borders.x ? (borders.x - floored_uv.x)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.y < borders.x ? (borders.x - floored_uv.y)*(5. + 5.*dissolve) : 0.)*(dissolve);
if (tex.a > 0.01 && burn_colour_1.a > 0.01 && !shadow && res < adjusted_dissolve + 0.8*(0.5-abs(adjusted_dissolve-0.5)) && res > adjusted_dissolve) {
if (!shadow && res < adjusted_dissolve + 0.5*(0.5-abs(adjusted_dissolve-0.5)) && res > adjusted_dissolve) {
tex.rgba = burn_colour_1.rgba;
} else if (burn_colour_2.a > 0.01) {
tex.rgba = burn_colour_2.rgba;
}
}
return vec4(shadow ? vec3(0.,0.,0.) : tex.xyz, res > adjusted_dissolve ? (shadow ? tex.a*0.3: tex.a) : .0);
}
number hue(number s, number t, number h)
{
number hs = mod(h, 1.)*6.;
if (hs < 1.) return (t-s) * hs + s;
if (hs < 3.) return t;
if (hs < 4.) return (t-s) * (4.-hs) + s;
return s;
}
vec4 RGB(vec4 c)
{
if (c.y == 0.)
return vec4(vec3(c.z), c.a);
number t = (c.z < .5) ? c.y*c.z + c.z : -c.y*c.z + (c.y+c.z);
number s = 2.0 * c.z - t;
return vec4(hue(s,t,c.x + 1./3.), hue(s,t,c.x), hue(s,t,c.x - 1./3.), c.w);
}
vec4 HSL(vec4 c)
{
number low = min(c.r, min(c.g, c.b));
number high = max(c.r, max(c.g, c.b));
number delta = high - low;
number sum = high+low;
vec4 hsl = vec4(.0, .0, .5 * sum, c.a);
if (delta == .0)
return hsl;
hsl.y = (hsl.z < .5) ? delta / sum : delta / (2.0 - sum);
if (high == c.r)
hsl.x = (c.g - c.b) / delta;
else if (high == c.g)
hsl.x = (c.b - c.r) / delta + 2.0;
else
hsl.x = (c.r - c.g) / delta + 4.0;
hsl.x = mod(hsl.x / 6., 1.);
return hsl;
}
vec4 effect( vec4 colour, Image texture, vec2 texture_coords, vec2 screen_coords )
{
vec4 tex = Texel(texture, texture_coords);
vec2 uv = (((texture_coords)*(image_details)) - texture_details.xy*texture_details.ba)/texture_details.ba;
vec4 SAT = HSL(tex);
SAT.g = SAT.g*0.5 + 0.000001*played.r;
SAT.b = SAT.b*0.8;
tex = RGB(SAT);
tex.a = tex.a*0.5;
return dissolve_mask(tex*colour, texture_coords, uv);
}
extern MY_HIGHP_OR_MEDIUMP vec2 mouse_screen_pos;
extern MY_HIGHP_OR_MEDIUMP float hovering;
extern MY_HIGHP_OR_MEDIUMP float screen_scale;
#ifdef VERTEX
vec4 position( mat4 transform_projection, vec4 vertex_position )
{
if (hovering <= 0.){
return transform_projection * vertex_position;
}
float mid_dist = length(vertex_position.xy - 0.5*love_ScreenSize.xy)/length(love_ScreenSize.xy);
vec2 mouse_offset = (vertex_position.xy - mouse_screen_pos.xy)/screen_scale;
float scale = 0.2*(-0.03 - 0.3*max(0., 0.3-mid_dist))
*hovering*(length(mouse_offset)*length(mouse_offset))/(2. -mid_dist);
return transform_projection * vertex_position + vec4(0,0,0,scale);
}
#endif

View File

@ -0,0 +1,150 @@
#if defined(VERTEX) || __VERSION__ > 100 || defined(GL_FRAGMENT_PRECISION_HIGH)
#define MY_HIGHP_OR_MEDIUMP highp
#else
#define MY_HIGHP_OR_MEDIUMP mediump
#endif
extern MY_HIGHP_OR_MEDIUMP vec2 polychrome;
extern MY_HIGHP_OR_MEDIUMP number dissolve;
extern MY_HIGHP_OR_MEDIUMP number time;
extern MY_HIGHP_OR_MEDIUMP vec4 texture_details;
extern MY_HIGHP_OR_MEDIUMP vec2 image_details;
extern bool shadow;
extern MY_HIGHP_OR_MEDIUMP vec4 burn_colour_1;
extern MY_HIGHP_OR_MEDIUMP vec4 burn_colour_2;
vec4 dissolve_mask(vec4 tex, vec2 texture_coords, vec2 uv)
{
if (dissolve < 0.001) {
return vec4(shadow ? vec3(0.,0.,0.) : tex.xyz, shadow ? tex.a*0.3: tex.a);
}
float adjusted_dissolve = (dissolve*dissolve*(3.-2.*dissolve))*1.02 - 0.01; //Adjusting 0.0-1.0 to fall to -0.1 - 1.1 scale so the mask does not pause at extreme values
float t = time * 10.0 + 2003.;
vec2 floored_uv = (floor((uv*texture_details.ba)))/max(texture_details.b, texture_details.a);
vec2 uv_scaled_centered = (floored_uv - 0.5) * 2.3 * max(texture_details.b, texture_details.a);
vec2 field_part1 = uv_scaled_centered + 50.*vec2(sin(-t / 143.6340), cos(-t / 99.4324));
vec2 field_part2 = uv_scaled_centered + 50.*vec2(cos( t / 53.1532), cos( t / 61.4532));
vec2 field_part3 = uv_scaled_centered + 50.*vec2(sin(-t / 87.53218), sin(-t / 49.0000));
float field = (1.+ (
cos(length(field_part1) / 19.483) + sin(length(field_part2) / 33.155) * cos(field_part2.y / 15.73) +
cos(length(field_part3) / 27.193) * sin(field_part3.x / 21.92) ))/2.;
vec2 borders = vec2(0.2, 0.8);
float res = (.5 + .5* cos( (adjusted_dissolve) / 82.612 + ( field + -.5 ) *3.14))
- (floored_uv.x > borders.y ? (floored_uv.x - borders.y)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.y > borders.y ? (floored_uv.y - borders.y)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.x < borders.x ? (borders.x - floored_uv.x)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.y < borders.x ? (borders.x - floored_uv.y)*(5. + 5.*dissolve) : 0.)*(dissolve);
if (tex.a > 0.01 && burn_colour_1.a > 0.01 && !shadow && res < adjusted_dissolve + 0.8*(0.5-abs(adjusted_dissolve-0.5)) && res > adjusted_dissolve) {
if (!shadow && res < adjusted_dissolve + 0.5*(0.5-abs(adjusted_dissolve-0.5)) && res > adjusted_dissolve) {
tex.rgba = burn_colour_1.rgba;
} else if (burn_colour_2.a > 0.01) {
tex.rgba = burn_colour_2.rgba;
}
}
return vec4(shadow ? vec3(0.,0.,0.) : tex.xyz, res > adjusted_dissolve ? (shadow ? tex.a*0.3: tex.a) : .0);
}
number hue(number s, number t, number h)
{
number hs = mod(h, 1.)*6.;
if (hs < 1.) return (t-s) * hs + s;
if (hs < 3.) return t;
if (hs < 4.) return (t-s) * (4.-hs) + s;
return s;
}
vec4 RGB(vec4 c)
{
if (c.y < 0.0001)
return vec4(vec3(c.z), c.a);
number t = (c.z < .5) ? c.y*c.z + c.z : -c.y*c.z + (c.y+c.z);
number s = 2.0 * c.z - t;
return vec4(hue(s,t,c.x + 1./3.), hue(s,t,c.x), hue(s,t,c.x - 1./3.), c.w);
}
vec4 HSL(vec4 c)
{
number low = min(c.r, min(c.g, c.b));
number high = max(c.r, max(c.g, c.b));
number delta = high - low;
number sum = high+low;
vec4 hsl = vec4(.0, .0, .5 * sum, c.a);
if (delta == .0)
return hsl;
hsl.y = (hsl.z < .5) ? delta / sum : delta / (2.0 - sum);
if (high == c.r)
hsl.x = (c.g - c.b) / delta;
else if (high == c.g)
hsl.x = (c.b - c.r) / delta + 2.0;
else
hsl.x = (c.r - c.g) / delta + 4.0;
hsl.x = mod(hsl.x / 6., 1.);
return hsl;
}
vec4 effect( vec4 colour, Image texture, vec2 texture_coords, vec2 screen_coords )
{
vec4 tex = Texel(texture, texture_coords);
vec2 uv = (((texture_coords)*(image_details)) - texture_details.xy*texture_details.ba)/texture_details.ba;
number low = min(tex.r, min(tex.g, tex.b));
number high = max(tex.r, max(tex.g, tex.b));
number delta = high - low;
number saturation_fac = 1. - max(0., 0.05*(1.1-delta));
vec4 hsl = HSL(vec4(tex.r*saturation_fac, tex.g*saturation_fac, tex.b, tex.a));
float t = polychrome.y*2.221 + time;
vec2 floored_uv = (floor((uv*texture_details.ba)))/texture_details.ba;
vec2 uv_scaled_centered = (floored_uv - 0.5) * 50.;
vec2 field_part1 = uv_scaled_centered + 50.*vec2(sin(-t / 143.6340), cos(-t / 99.4324));
vec2 field_part2 = uv_scaled_centered + 50.*vec2(cos( t / 53.1532), cos( t / 61.4532));
vec2 field_part3 = uv_scaled_centered + 50.*vec2(sin(-t / 87.53218), sin(-t / 49.0000));
float field = (1.+ (
cos(length(field_part1) / 19.483) + sin(length(field_part2) / 33.155) * cos(field_part2.y / 15.73) +
cos(length(field_part3) / 27.193) * sin(field_part3.x / 21.92) ))/2.;
float res = (.5 + .5* cos( (polychrome.x) * 2.612 + ( field + -.5 ) *3.14));
hsl.x = hsl.x+ res + polychrome.y*0.04;
hsl.y = min(0.6,hsl.y+0.5);
tex.rgb = RGB(hsl).rgb;
if (tex[3] < 0.7)
tex[3] = tex[3]/3.;
return dissolve_mask(tex*colour, texture_coords, uv);
}
extern MY_HIGHP_OR_MEDIUMP vec2 mouse_screen_pos;
extern MY_HIGHP_OR_MEDIUMP float hovering;
extern MY_HIGHP_OR_MEDIUMP float screen_scale;
#ifdef VERTEX
vec4 position( mat4 transform_projection, vec4 vertex_position )
{
if (hovering <= 0.){
return transform_projection * vertex_position;
}
float mid_dist = length(vertex_position.xy - 0.5*love_ScreenSize.xy)/length(love_ScreenSize.xy);
vec2 mouse_offset = (vertex_position.xy - mouse_screen_pos.xy)/screen_scale;
float scale = 0.2*(-0.03 - 0.3*max(0., 0.3-mid_dist))
*hovering*(length(mouse_offset)*length(mouse_offset))/(2. -mid_dist);
return transform_projection * vertex_position + vec4(0,0,0,scale);
}
#endif

18
resources/shaders/skew.fs Normal file
View File

@ -0,0 +1,18 @@
extern vec2 mouse_screen_pos;
extern float hovering;
extern float screen_scale;
#ifdef VERTEX
vec4 position( mat4 transform_projection, vec4 vertex_position )
{
if (hovering <= 0.){
return transform_projection * vertex_position;
}
float mid_dist = length(vertex_position.xy - 0.5*love_ScreenSize.xy)/length(love_ScreenSize.xy);
vec2 mouse_offset = (vertex_position.xy - mouse_screen_pos.xy)/screen_scale;
float scale = 0.2*(-0.03 - 0.3*max(0., 0.3-mid_dist))
*hovering*(length(mouse_offset)*length(mouse_offset))/(2. -mid_dist);
return transform_projection * vertex_position + vec4(0,0,0,scale);
}
#endif

View File

@ -0,0 +1,50 @@
extern number time;
extern number vort_speed;
extern vec4 colour_1;
extern vec4 colour_2;
extern number mid_flash;
extern number vort_offset;
#define PIXEL_SIZE_FAC 700.
#define BLACK 0.6*vec4(79./255.,99./255., 103./255., 1./0.6)
vec4 effect( vec4 colour, Image texture, vec2 texture_coords, vec2 screen_coords )
{
//Convert to UV coords (0-1) and floor for pixel effect
number pixel_size = length(love_ScreenSize.xy)/PIXEL_SIZE_FAC;
vec2 uv = (floor(screen_coords.xy*(1./pixel_size))*pixel_size - 0.5*love_ScreenSize.xy)/length(love_ScreenSize.xy);
number uv_len = length(uv);
//Adding in a center swirl, changes with time
number speed = time*vort_speed;
number new_pixel_angle = atan(uv.y, uv.x) + (2.2 + 0.4*min(6.,speed))*uv_len - 1. - speed*0.05 - min(6.,speed)*speed*0.02 + vort_offset;
vec2 mid = (love_ScreenSize.xy/length(love_ScreenSize.xy))/2.;
vec2 sv = vec2((uv_len * cos(new_pixel_angle) + mid.x), (uv_len * sin(new_pixel_angle) + mid.y)) - mid;
//Now add the smoke effect to the swirled UV
sv *= 30.;
speed = time*(6.)*vort_speed + vort_offset + 1033.;
vec2 uv2 = vec2(sv.x+sv.y);
for(int i=0; i < 5; i++) {
uv2 += sin(max(sv.x, sv.y)) + sv;
sv += 0.5*vec2(cos(5.1123314 + 0.353*uv2.y + speed*0.131121),sin(uv2.x - 0.113*speed));
sv -= 1.0*cos(sv.x + sv.y) - 1.0*sin(sv.x*0.711 - sv.y);
}
//Make the smoke amount range from 0 - 2
number smoke_res =min(2., max(-2., 1.5 + length(sv)*0.12 - 0.17*(min(10.,time*1.2 - 4.))));
if (smoke_res < 0.2) {
smoke_res = (smoke_res - 0.2)*0.6 + 0.2;
}
number c1p = max(0.,1. - 2.*abs(1.-smoke_res));
number c2p = max(0.,1. - 2.*(smoke_res));
number cb = 1. - min(1., c1p + c2p);
vec4 ret_col = colour_1*c1p + colour_2*c2p + vec4(cb*BLACK.rgb, cb*colour_1.a);
number mod_flash = max(mid_flash*0.8, max(c1p, c2p)*5. - 4.4) + mid_flash*max(c1p, c2p);
return ret_col*(1. - mod_flash) + mod_flash*vec4(1., 1., 1., 1.);
}

View File

@ -0,0 +1,21 @@
extern float vortex_amt;
#ifdef VERTEX
vec4 position( mat4 transform_projection, vec4 vertex_position )
{
vec2 uv = (vertex_position.xy - 0.5*love_ScreenSize.xy)/length(love_ScreenSize.xy);
float effectRadius = 1.6 - 0.05*vortex_amt;
float effectAngle = 0.5 + 0.15*vortex_amt;
float len = length(uv * vec2(love_ScreenSize.x / love_ScreenSize.y, 1.));
float angle = atan(uv.y, uv.x) + effectAngle * smoothstep(effectRadius, 0., len);
float radius = length(uv);
vec2 center = 0.5*love_ScreenSize.xy/length(love_ScreenSize.xy);
vertex_position.x = (radius * cos(angle) + center.x)*length(love_ScreenSize.xy);
vertex_position.y = (radius * sin(angle) + center.y)*length(love_ScreenSize.xy);
return transform_projection * vertex_position;
}
#endif

View File

@ -0,0 +1,98 @@
#if defined(VERTEX) || __VERSION__ > 100 || defined(GL_FRAGMENT_PRECISION_HIGH)
#define MY_HIGHP_OR_MEDIUMP highp
#else
#define MY_HIGHP_OR_MEDIUMP mediump
#endif
extern MY_HIGHP_OR_MEDIUMP vec2 voucher;
extern MY_HIGHP_OR_MEDIUMP number dissolve;
extern MY_HIGHP_OR_MEDIUMP number time;
extern MY_HIGHP_OR_MEDIUMP vec4 texture_details;
extern MY_HIGHP_OR_MEDIUMP vec2 image_details;
extern bool shadow;
extern MY_HIGHP_OR_MEDIUMP vec4 burn_colour_1;
extern MY_HIGHP_OR_MEDIUMP vec4 burn_colour_2;
vec4 dissolve_mask(vec4 tex, vec2 texture_coords, vec2 uv)
{
if (dissolve < 0.001) {
return vec4(shadow ? vec3(0.,0.,0.) : tex.xyz, shadow ? tex.a*0.3: tex.a);
}
float adjusted_dissolve = (dissolve*dissolve*(3.-2.*dissolve))*1.02 - 0.01; //Adjusting 0.0-1.0 to fall to -0.1 - 1.1 scale so the mask does not pause at extreme values
float t = time * 10.0 + 2003.;
vec2 floored_uv = (floor((uv*texture_details.ba)))/max(texture_details.b, texture_details.a);
vec2 uv_scaled_centered = (floored_uv - 0.5) * 2.3 * max(texture_details.b, texture_details.a);
vec2 field_part1 = uv_scaled_centered + 50.*vec2(sin(-t / 143.6340), cos(-t / 99.4324));
vec2 field_part2 = uv_scaled_centered + 50.*vec2(cos( t / 53.1532), cos( t / 61.4532));
vec2 field_part3 = uv_scaled_centered + 50.*vec2(sin(-t / 87.53218), sin(-t / 49.0000));
float field = (1.+ (
cos(length(field_part1) / 19.483) + sin(length(field_part2) / 33.155) * cos(field_part2.y / 15.73) +
cos(length(field_part3) / 27.193) * sin(field_part3.x / 21.92) ))/2.;
vec2 borders = vec2(0.2, 0.8);
float res = (.5 + .5* cos( (adjusted_dissolve) / 82.612 + ( field + -.5 ) *3.14))
- (floored_uv.x > borders.y ? (floored_uv.x - borders.y)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.y > borders.y ? (floored_uv.y - borders.y)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.x < borders.x ? (borders.x - floored_uv.x)*(5. + 5.*dissolve) : 0.)*(dissolve)
- (floored_uv.y < borders.x ? (borders.x - floored_uv.y)*(5. + 5.*dissolve) : 0.)*(dissolve);
if (tex.a > 0.01 && burn_colour_1.a > 0.01 && !shadow && res < adjusted_dissolve + 0.8*(0.5-abs(adjusted_dissolve-0.5)) && res > adjusted_dissolve) {
if (!shadow && res < adjusted_dissolve + 0.5*(0.5-abs(adjusted_dissolve-0.5)) && res > adjusted_dissolve) {
tex.rgba = burn_colour_1.rgba;
} else if (burn_colour_2.a > 0.01) {
tex.rgba = burn_colour_2.rgba;
}
}
return vec4(shadow ? vec3(0.,0.,0.) : tex.xyz, res > adjusted_dissolve ? (shadow ? tex.a*0.3: tex.a) : .0);
}
vec4 effect( vec4 colour, Image texture, vec2 texture_coords, vec2 screen_coords )
{
vec4 tex = Texel( texture, texture_coords);
vec2 uv = (((texture_coords)*(image_details)) - texture_details.xy*texture_details.ba)/texture_details.ba;
number low = min(tex.r, min(tex.g, tex.b));
number high = max(tex.r, max(tex.g, tex.b));
number delta = high-low;
number fac = 0.8 + 0.9*sin(13.*uv.x+5.32*uv.y + voucher.r*12. + cos(voucher.r*5.3 + uv.y*4.2 - uv.x*4.));
number fac2 = 0.5 + 0.5*sin(10.*uv.x+2.32*uv.y + voucher.r*5. - cos(voucher.r*2.3 + uv.x*8.2));
number fac3 = 0.5 + 0.5*sin(12.*uv.x+6.32*uv.y + voucher.r*6.111 + sin(voucher.r*5.3 + uv.y*3.2));
number fac4 = 0.5 + 0.5*sin(4.*uv.x+2.32*uv.y + voucher.r*8.111 + sin(voucher.r*1.3 + uv.y*13.2));
number fac5 = sin(0.5*16.*uv.x+5.32*uv.y + voucher.r*12. + cos(voucher.r*5.3 + uv.y*4.2 - uv.x*4.));
number maxfac = 0.6*max(max(fac, max(fac2, max(fac3,0.0))) + (fac+fac2+fac3*fac4), 0.);
tex.rgb = tex.rgb*0.5 + vec3(0.4, 0.4, 0.8);
tex.r = tex.r-delta + delta*maxfac*(0.7 + fac5*0.07) - 0.1;
tex.g = tex.g-delta + delta*maxfac*(0.7 - fac5*0.17) - 0.1;
tex.b = tex.b-delta + delta*maxfac*0.7 - 0.1;
tex.a = tex.a*(0.8*max(min(1., max(0.,0.3*max(low*0.2, delta)+ min(max(maxfac*0.1,0.), 0.4)) ), 0.) + 0.15*maxfac*(0.1+delta));
return dissolve_mask(tex*colour, texture_coords, uv);
}
extern MY_HIGHP_OR_MEDIUMP vec2 mouse_screen_pos;
extern MY_HIGHP_OR_MEDIUMP float hovering;
extern MY_HIGHP_OR_MEDIUMP float screen_scale;
#ifdef VERTEX
vec4 position( mat4 transform_projection, vec4 vertex_position )
{
if (hovering <= 0.){
return transform_projection * vertex_position;
}
float mid_dist = length(vertex_position.xy - 0.5*love_ScreenSize.xy)/length(love_ScreenSize.xy);
vec2 mouse_offset = (vertex_position.xy - mouse_screen_pos.xy)/screen_scale;
float scale = 0.2*(-0.03 - 0.3*max(0., 0.3-mid_dist))
*hovering*(length(mouse_offset)*length(mouse_offset))/(2. -mid_dist);
return transform_projection * vertex_position + vec4(0,0,0,scale);
}
#endif

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
resources/sounds/button.ogg Normal file

Binary file not shown.

BIN
resources/sounds/cancel.ogg Normal file

Binary file not shown.

BIN
resources/sounds/card1.ogg Normal file

Binary file not shown.

BIN
resources/sounds/card3.ogg Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
resources/sounds/chips1.ogg Normal file

Binary file not shown.

BIN
resources/sounds/chips2.ogg Normal file

Binary file not shown.

BIN
resources/sounds/coin1.ogg Normal file

Binary file not shown.

BIN
resources/sounds/coin2.ogg Normal file

Binary file not shown.

BIN
resources/sounds/coin3.ogg Normal file

Binary file not shown.

BIN
resources/sounds/coin4.ogg Normal file

Binary file not shown.

BIN
resources/sounds/coin5.ogg Normal file

Binary file not shown.

BIN
resources/sounds/coin6.ogg Normal file

Binary file not shown.

BIN
resources/sounds/coin7.ogg Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More