mirror of
https://github.com/ershisan99/Fantoms-Preview.git
synced 2025-12-16 12:32:48 +00:00
FNSimulate init
This commit is contained in:
420
FNSimulate/Engine.lua
Normal file
420
FNSimulate/Engine.lua
Normal file
@@ -0,0 +1,420 @@
|
||||
--- Divvy's Simulation for Balatro - Engine.lua
|
||||
--
|
||||
-- The heart of this library: it replicates the game's score evaluation.
|
||||
|
||||
function DV.SIM.run()
|
||||
local null_ret = {score = {min=0, exact=0, max=0}, dollars = {min=0, exact=0, max=0}}
|
||||
if #G.hand.highlighted < 1 then return null_ret end
|
||||
|
||||
DV.SIM.init()
|
||||
|
||||
DV.SIM.manage_state("SAVE")
|
||||
DV.SIM.update_state_variables()
|
||||
|
||||
if not DV.SIM.simulate_blind_debuffs() then
|
||||
DV.SIM.simulate_joker_before_effects()
|
||||
DV.SIM.add_base_chips_and_mult()
|
||||
DV.SIM.simulate_blind_effects()
|
||||
DV.SIM.simulate_scoring_cards()
|
||||
DV.SIM.simulate_held_cards()
|
||||
DV.SIM.simulate_joker_global_effects()
|
||||
DV.SIM.simulate_consumable_effects()
|
||||
DV.SIM.simulate_deck_effects()
|
||||
else -- Only Matador at this point:
|
||||
DV.SIM.simulate_all_jokers(G.jokers, {debuffed_hand = true})
|
||||
end
|
||||
|
||||
DV.SIM.manage_state("RESTORE")
|
||||
|
||||
return DV.SIM.get_results()
|
||||
end
|
||||
|
||||
function DV.SIM.init()
|
||||
-- Reset:
|
||||
DV.SIM.running = {
|
||||
min = {chips = 0, mult = 0, dollars = 0},
|
||||
exact = {chips = 0, mult = 0, dollars = 0},
|
||||
max = {chips = 0, mult = 0, dollars = 0},
|
||||
reps = 0
|
||||
}
|
||||
|
||||
-- Fetch metadata about simulated play:
|
||||
local hand_name, _, poker_hands, scoring_hand, _ = G.FUNCS.get_poker_hand_info(G.hand.highlighted)
|
||||
DV.SIM.env.scoring_name = hand_name
|
||||
|
||||
-- Identify played cards and extract necessary data:
|
||||
DV.SIM.env.played_cards = {}
|
||||
DV.SIM.env.scoring_cards = {}
|
||||
local is_splash_joker = next(find_joker("Splash"))
|
||||
table.sort(G.hand.highlighted, function(a, b) return a.T.x < b.T.x end) -- Sorts by positional x-value to mirror card order!
|
||||
for _, card in ipairs(G.hand.highlighted) do
|
||||
local is_scoring = false
|
||||
for _, scoring_card in ipairs(scoring_hand) do
|
||||
-- Either card is scoring because it's part of the scoring hand,
|
||||
-- or there is Splash joker, or it's a Stone Card:
|
||||
if card.sort_id == scoring_card.sort_id
|
||||
or is_splash_joker
|
||||
or card.ability.effect == "Stone Card"
|
||||
then
|
||||
is_scoring = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local card_data = DV.SIM.get_card_data(card)
|
||||
table.insert(DV.SIM.env.played_cards, card_data)
|
||||
if is_scoring then table.insert(DV.SIM.env.scoring_cards, card_data) end
|
||||
end
|
||||
|
||||
-- Identify held cards and extract necessary data:
|
||||
DV.SIM.env.held_cards = {}
|
||||
for _, card in ipairs(G.hand.cards) do
|
||||
-- Highlighted cards are simulated as played cards:
|
||||
if not card.highlighted then
|
||||
local card_data = DV.SIM.get_card_data(card)
|
||||
table.insert(DV.SIM.env.held_cards, card_data)
|
||||
end
|
||||
end
|
||||
|
||||
-- Extract necessary joker data:
|
||||
DV.SIM.env.jokers = {}
|
||||
for _, joker in ipairs(G.jokers.cards) do
|
||||
local joker_data = {
|
||||
-- P_CENTER keys for jokers have the form j_NAME, get rid of j_
|
||||
id = joker.config.center.key:sub(3, #joker.config.center.key),
|
||||
ability = copy_table(joker.ability),
|
||||
edition = copy_table(joker.edition),
|
||||
rarity = joker.config.center.rarity,
|
||||
debuff = joker.debuff
|
||||
}
|
||||
table.insert(DV.SIM.env.jokers, joker_data)
|
||||
end
|
||||
|
||||
-- Extract necessary consumable data:
|
||||
DV.SIM.env.consumables = {}
|
||||
for _, consumable in ipairs(G.consumeables.cards) do
|
||||
local consumable_data = {
|
||||
-- P_CENTER keys have the form x_NAME, get rid of x_
|
||||
id = consumable.config.center.key:sub(3, #consumable.config.center.key),
|
||||
ability = copy_table(consumable.ability)
|
||||
}
|
||||
table.insert(DV.SIM.env.consumables, consumable_data)
|
||||
end
|
||||
|
||||
-- Set extensible context template:
|
||||
DV.SIM.get_context = function(cardarea, args)
|
||||
local context = {
|
||||
cardarea = cardarea,
|
||||
full_hand = DV.SIM.env.played_cards,
|
||||
scoring_name = hand_name,
|
||||
scoring_hand = DV.SIM.env.scoring_cards,
|
||||
poker_hands = poker_hands
|
||||
}
|
||||
|
||||
for k, v in pairs(args) do
|
||||
context[k] = v
|
||||
end
|
||||
|
||||
return context
|
||||
end
|
||||
end
|
||||
|
||||
function DV.SIM.get_card_data(card_obj)
|
||||
return {
|
||||
rank = card_obj.base.id,
|
||||
suit = card_obj.base.suit,
|
||||
base_chips = card_obj.base.nominal,
|
||||
ability = copy_table(card_obj.ability),
|
||||
edition = copy_table(card_obj.edition),
|
||||
seal = card_obj.seal,
|
||||
debuff = card_obj.debuff,
|
||||
lucky_trigger = {}
|
||||
}
|
||||
end
|
||||
|
||||
function DV.SIM.get_results()
|
||||
local DVSR = DV.SIM.running
|
||||
|
||||
local min_score = math.floor(DVSR.min.chips * DVSR.min.mult)
|
||||
local exact_score = math.floor(DVSR.exact.chips * DVSR.exact.mult)
|
||||
local max_score = math.floor(DVSR.max.chips * DVSR.max.mult)
|
||||
|
||||
return {
|
||||
score = {min = min_score, exact = exact_score, max = max_score},
|
||||
dollars = {min = DVSR.min.dollars, exact = DVSR.exact.dollars, max = DVSR.max.dollars}
|
||||
}
|
||||
end
|
||||
|
||||
--
|
||||
-- GAME STATE MANAGEMENT:
|
||||
--
|
||||
|
||||
function DV.SIM.manage_state(save_or_restore)
|
||||
local DVSO = DV.SIM.orig
|
||||
|
||||
if save_or_restore == "SAVE" then
|
||||
DVSO.random_data = copy_table(G.GAME.pseudorandom)
|
||||
DVSO.hand_data = copy_table(G.GAME.hands)
|
||||
return
|
||||
end
|
||||
|
||||
if save_or_restore == "RESTORE" then
|
||||
G.GAME.pseudorandom = DVSO.random_data
|
||||
G.GAME.hands = DVSO.hand_data
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
function DV.SIM.update_state_variables()
|
||||
-- Increment poker hand played this run/round:
|
||||
local hand_info = G.GAME.hands[DV.SIM.env.scoring_name]
|
||||
hand_info.played = hand_info.played + 1
|
||||
hand_info.played_this_round = hand_info.played_this_round + 1
|
||||
end
|
||||
|
||||
--
|
||||
-- MACRO LEVEL:
|
||||
--
|
||||
|
||||
function DV.SIM.simulate_scoring_cards()
|
||||
for _, scoring_card in ipairs(DV.SIM.env.scoring_cards) do
|
||||
DV.SIM.simulate_card_in_context(scoring_card, G.play)
|
||||
end
|
||||
end
|
||||
|
||||
function DV.SIM.simulate_held_cards()
|
||||
for _, held_card in ipairs(DV.SIM.env.held_cards) do
|
||||
DV.SIM.simulate_card_in_context(held_card, G.hand)
|
||||
end
|
||||
end
|
||||
|
||||
function DV.SIM.simulate_joker_global_effects()
|
||||
for _, joker in ipairs(DV.SIM.env.jokers) do
|
||||
if joker.edition then -- Foil and Holo:
|
||||
if joker.edition.chips then DV.SIM.add_chips(joker.edition.chips) end
|
||||
if joker.edition.mult then DV.SIM.add_mult(joker.edition.mult) end
|
||||
end
|
||||
|
||||
DV.SIM.simulate_joker(joker, DV.SIM.get_context(G.jokers, {global = true}))
|
||||
|
||||
-- Joker-on-joker effects (eg. Blueprint):
|
||||
DV.SIM.simulate_all_jokers(G.jokers, {other_joker = joker})
|
||||
|
||||
if joker.edition then -- Poly:
|
||||
if joker.edition.x_mult then DV.SIM.x_mult(joker.edition.x_mult) end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function DV.SIM.simulate_consumable_effects()
|
||||
for _, consumable in ipairs(DV.SIM.env.consumables) do
|
||||
if consumable.ability.set == "Planet" and not consumable.debuff then
|
||||
if G.GAME.used_vouchers.v_observatory and consumable.ability.consumeable.hand_type == DV.SIM.env.scoring_name then
|
||||
DV.SIM.x_mult(G.P_CENTERS.v_observatory.config.extra)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function DV.SIM.add_base_chips_and_mult()
|
||||
local played_hand_data = G.GAME.hands[DV.SIM.env.scoring_name]
|
||||
DV.SIM.add_chips(played_hand_data.chips)
|
||||
DV.SIM.add_mult(played_hand_data.mult)
|
||||
end
|
||||
|
||||
function DV.SIM.simulate_joker_before_effects()
|
||||
for _, joker in ipairs(DV.SIM.env.jokers) do
|
||||
DV.SIM.simulate_joker(joker, DV.SIM.get_context(G.jokers, {before = true}))
|
||||
end
|
||||
end
|
||||
|
||||
function DV.SIM.simulate_blind_effects()
|
||||
if G.GAME.blind.disabled then return end
|
||||
|
||||
if G.GAME.blind.name == "The Flint" then
|
||||
local function flint(data)
|
||||
local half_chips = math.floor(data.chips/2 + 0.5)
|
||||
local half_mult = math.floor(data.mult/2 + 0.5)
|
||||
data.chips = mod_chips(math.max(half_chips, 0))
|
||||
data.mult = mod_mult(math.max(half_mult, 1))
|
||||
end
|
||||
|
||||
flint(DV.SIM.running.min)
|
||||
flint(DV.SIM.running.exact)
|
||||
flint(DV.SIM.running.max)
|
||||
else
|
||||
-- Other blinds do not impact scoring; refer to Blind:modify_hand(..)
|
||||
end
|
||||
end
|
||||
|
||||
function DV.SIM.simulate_deck_effects()
|
||||
if G.GAME.selected_back.name == 'Plasma Deck' then
|
||||
local function plasma(data)
|
||||
local sum = data.chips + data.mult
|
||||
local half_sum = math.floor(sum/2)
|
||||
data.chips = mod_chips(half_sum)
|
||||
data.mult = mod_mult(half_sum)
|
||||
end
|
||||
|
||||
plasma(DV.SIM.running.min)
|
||||
plasma(DV.SIM.running.exact)
|
||||
plasma(DV.SIM.running.max)
|
||||
else
|
||||
-- Other decks do not impact scoring; refer to Back:trigger_effect(..)
|
||||
end
|
||||
end
|
||||
|
||||
function DV.SIM.simulate_blind_debuffs()
|
||||
local blind_obj = G.GAME.blind
|
||||
if blind_obj.disabled then return false end
|
||||
|
||||
-- The following are part of Blind:press_play()
|
||||
|
||||
if blind_obj.name == "The Hook" then
|
||||
blind_obj.triggered = true
|
||||
for _ = 1, math.min(2, #DV.SIM.env.held_cards) do
|
||||
-- TODO: Identify cards-in-hand that can affect score, simulate with/without them for min/max
|
||||
local selected_card, card_key = pseudorandom_element(DV.SIM.env.held_cards, pseudoseed('hook'))
|
||||
table.remove(DV.SIM.env.held_cards, card_key)
|
||||
for _, joker in ipairs(DV.SIM.env.jokers) do
|
||||
-- Note that the cardarea argument is largely arbitrary (used for DV.SIM.JOKERS),
|
||||
-- I use G.hand because The Hook discards from the hand
|
||||
DV.SIM.simulate_joker(joker, DV.SIM.get_context(G.hand, {discard = true, other_card = selected_card}))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if blind_obj.name == "The Tooth" then
|
||||
blind_obj.triggered = true
|
||||
DV.SIM.add_dollars((-1) * #DV.SIM.env.played_cards)
|
||||
end
|
||||
|
||||
-- The following are part of Blind:debuff_hand(..)
|
||||
|
||||
if blind_obj.name == "The Arm" then
|
||||
blind_obj.triggered = false
|
||||
|
||||
local played_hand_name = DV.SIM.env.scoring_name
|
||||
if G.GAME.hands[played_hand_name].level > 1 then
|
||||
blind_obj.triggered = true
|
||||
-- NOTE: Important to save/restore G.GAME.hands here
|
||||
-- NOTE: Implementation mirrors level_up_hand(..)
|
||||
local played_hand_data = G.GAME.hands[played_hand_name]
|
||||
played_hand_data.level = math.max(1, played_hand_data.level - 1)
|
||||
played_hand_data.mult = math.max(1, played_hand_data.s_mult + (played_hand_data.level-1) * played_hand_data.l_mult)
|
||||
played_hand_data.chips = math.max(0, played_hand_data.s_chips + (played_hand_data.level-1) * played_hand_data.l_chips)
|
||||
end
|
||||
return false -- IMPORTANT: Avoid duplicate effects from Blind:debuff_hand() below
|
||||
end
|
||||
|
||||
if blind_obj.name == "The Ox" then
|
||||
blind_obj.triggered = false
|
||||
|
||||
if DV.SIM.env.scoring_name == G.GAME.current_round.most_played_poker_hand then
|
||||
blind_obj.triggered = true
|
||||
DV.SIM.add_dollars(-G.GAME.dollars)
|
||||
end
|
||||
return false -- IMPORTANT: Avoid duplicate effects from Blind:debuff_hand() below
|
||||
end
|
||||
|
||||
return blind_obj:debuff_hand(DV.SIM.env.played_cards, DV.SIM.env.poker_hands, DV.SIM.env.scoring_name, true)
|
||||
end
|
||||
|
||||
--
|
||||
-- MICRO LEVEL (CARDS):
|
||||
--
|
||||
|
||||
function DV.SIM.simulate_card_in_context(card, cardarea)
|
||||
-- Reset and collect repetitions:
|
||||
DV.SIM.running.reps = 1
|
||||
if card.seal == "Red" then DV.SIM.add_reps(1) end
|
||||
DV.SIM.simulate_all_jokers(cardarea, {other_card = card, repetition = true})
|
||||
|
||||
-- Apply effects:
|
||||
for _ = 1, DV.SIM.running.reps do
|
||||
DV.SIM.simulate_card(card, DV.SIM.get_context(cardarea, {}))
|
||||
DV.SIM.simulate_all_jokers(cardarea, {other_card = card, individual = true})
|
||||
end
|
||||
end
|
||||
|
||||
function DV.SIM.simulate_card(card_data, context)
|
||||
-- Do nothing if debuffed:
|
||||
if card_data.debuff then return end
|
||||
|
||||
if context.cardarea == G.play then
|
||||
-- Chips:
|
||||
if card_data.ability.effect == "Stone Card" then
|
||||
DV.SIM.add_chips(card_data.ability.bonus + (card_data.ability.perma_bonus or 0))
|
||||
else
|
||||
DV.SIM.add_chips(card_data.base_chips + card_data.ability.bonus + (card_data.ability.perma_bonus or 0))
|
||||
end
|
||||
|
||||
-- Mult:
|
||||
if card_data.ability.effect == "Lucky Card" then
|
||||
local exact_mult, min_mult, max_mult = DV.SIM.get_probabilistic_extremes(pseudorandom("nope"), 5, card_data.ability.mult, 0)
|
||||
DV.SIM.add_mult(exact_mult, min_mult, max_mult)
|
||||
-- Careful not to overwrite `card_data.lucky_trigger` outright:
|
||||
if exact_mult > 0 then card_data.lucky_trigger.exact = true end
|
||||
if min_mult > 0 then card_data.lucky_trigger.min = true end
|
||||
if max_mult > 0 then card_data.lucky_trigger.max = true end
|
||||
else
|
||||
DV.SIM.add_mult(card_data.ability.mult)
|
||||
end
|
||||
|
||||
-- XMult:
|
||||
if card_data.ability.x_mult > 1 then
|
||||
DV.SIM.x_mult(card_data.ability.x_mult)
|
||||
end
|
||||
|
||||
-- Dollars:
|
||||
if card_data.seal == "Gold" then
|
||||
DV.SIM.add_dollars(3)
|
||||
end
|
||||
if card_data.ability.p_dollars > 0 then
|
||||
if card_data.ability.effect == "Lucky Card" then
|
||||
local exact_dollars, min_dollars, max_dollars = DV.SIM.get_probabilistic_extremes(pseudorandom("notthistime"), 15, card_data.ability.p_dollars, 0)
|
||||
DV.SIM.add_dollars(exact_dollars, min_dollars, max_dollars)
|
||||
-- Careful not to overwrite `card_data.lucky_trigger` outright:
|
||||
if exact_dollars > 0 then card_data.lucky_trigger.exact = true end
|
||||
if min_dollars > 0 then card_data.lucky_trigger.min = true end
|
||||
if max_dollars > 0 then card_data.lucky_trigger.max = true end
|
||||
else
|
||||
DV.SIM.add_dollars(card_data.ability.p_dollars)
|
||||
end
|
||||
end
|
||||
|
||||
-- Edition:
|
||||
if card_data.edition then
|
||||
if card_data.edition.chips then DV.SIM.add_chips(card_data.edition.chips) end
|
||||
if card_data.edition.mult then DV.SIM.add_mult(card_data.edition.mult) end
|
||||
if card_data.edition.x_mult then DV.SIM.x_mult(card_data.edition.x_mult) end
|
||||
end
|
||||
|
||||
elseif context.cardarea == G.hand then
|
||||
if card_data.ability.h_mult > 0 then
|
||||
DV.SIM.add_mult(card_data.ability.h_mult)
|
||||
end
|
||||
|
||||
if card_data.ability.h_x_mult > 0 then
|
||||
DV.SIM.x_mult(card_data.ability.h_x_mult)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- MICRO LEVEL (JOKERS):
|
||||
--
|
||||
|
||||
function DV.SIM.simulate_all_jokers(cardarea, context_args)
|
||||
for _, joker in ipairs(DV.SIM.env.jokers) do
|
||||
DV.SIM.simulate_joker(joker, DV.SIM.get_context(cardarea, context_args))
|
||||
end
|
||||
end
|
||||
|
||||
function DV.SIM.simulate_joker(joker_obj, context)
|
||||
-- Do nothing if debuffed:
|
||||
if joker_obj.debuff then return end
|
||||
|
||||
local joker_simulation_function = DV.SIM.JOKERS["simulate_" .. joker_obj.id]
|
||||
if joker_simulation_function then joker_simulation_function(joker_obj, context) end
|
||||
end
|
||||
38
FNSimulate/Init.lua
Normal file
38
FNSimulate/Init.lua
Normal file
@@ -0,0 +1,38 @@
|
||||
--- Divvy's Simulation for Balatro - Init.lua
|
||||
--
|
||||
-- Global values that must be present for the rest of this mod to work.
|
||||
|
||||
if not DV then DV = {} end
|
||||
|
||||
DV.SIM = {
|
||||
JOKERS = {},
|
||||
|
||||
running = {
|
||||
--- Table to store workings (ie. running totals):
|
||||
min = {chips = 0, mult = 0, dollars = 0},
|
||||
exact = {chips = 0, mult = 0, dollars = 0},
|
||||
max = {chips = 0, mult = 0, dollars = 0},
|
||||
reps = 0,
|
||||
},
|
||||
|
||||
env = {
|
||||
--- Table to store data about the simulated play:
|
||||
jokers = {}, -- Derived from G.jokers.cards
|
||||
played_cards = {}, -- Derived from G.hand.highlighted
|
||||
scoring_cards = {}, -- Derived according to evaluate_play()
|
||||
held_cards = {}, -- Derived from G.hand minus G.hand.highlighted
|
||||
consumables = {}, -- Derived from G.consumeables.cards
|
||||
scoring_name = "" -- Derived according to evaluate_play()
|
||||
},
|
||||
|
||||
orig = {
|
||||
--- Table to store game data that gets modified during simulation:
|
||||
random_data = {}, -- G.GAME.pseudorandom
|
||||
hand_data = {} -- G.GAME.hands
|
||||
},
|
||||
|
||||
misc = {
|
||||
--- Table to store ancillary status variables:
|
||||
next_stone_id = -1
|
||||
}
|
||||
}
|
||||
201
FNSimulate/Utils.lua
Normal file
201
FNSimulate/Utils.lua
Normal file
@@ -0,0 +1,201 @@
|
||||
--- Divvy's Simulation for Balatro - Utils.lua
|
||||
--
|
||||
-- Utilities for writing simulation functions for jokers.
|
||||
--
|
||||
-- In general, these functions replicate the game's internal calculations and
|
||||
-- variables in order to avoid affecting the game's state during simulation.
|
||||
-- These functions ensure that the score calculation remains identical to the
|
||||
-- game; DO NOT directly modify the `DV.SIM.running` score variables.
|
||||
|
||||
--
|
||||
-- HIGH-LEVEL:
|
||||
--
|
||||
|
||||
function DV.SIM.JOKERS.add_suit_mult(joker_obj, context)
|
||||
if context.cardarea == G.play and context.individual then
|
||||
if DV.SIM.is_suit(context.other_card, joker_obj.ability.extra.suit) and not context.other_card.debuff then
|
||||
DV.SIM.add_mult(joker_obj.ability.extra.s_mult)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function DV.SIM.JOKERS.add_type_mult(joker_obj, context)
|
||||
if context.cardarea == G.jokers and context.global
|
||||
and next(context.poker_hands[joker_obj.ability.type])
|
||||
then
|
||||
DV.SIM.add_mult(joker_obj.ability.t_mult)
|
||||
end
|
||||
end
|
||||
|
||||
function DV.SIM.JOKERS.add_type_chips(joker_obj, context)
|
||||
if context.cardarea == G.jokers and context.global
|
||||
and next(context.poker_hands[joker_obj.ability.type])
|
||||
then
|
||||
DV.SIM.add_chips(joker_obj.ability.t_chips)
|
||||
end
|
||||
end
|
||||
|
||||
function DV.SIM.JOKERS.x_mult_if_global(joker_obj, context)
|
||||
if context.cardarea == G.jokers and context.global then
|
||||
if joker_obj.ability.x_mult > 1 and
|
||||
(joker_obj.ability.type == "" or next(context.poker_hands[joker_obj.ability.type])) then
|
||||
DV.SIM.x_mult(joker_obj.ability.x_mult)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function DV.SIM.get_probabilistic_extremes(random_value, odds, reward, default)
|
||||
-- Exact mirrors the game's probability calculation
|
||||
local exact = default
|
||||
if random_value < G.GAME.probabilities.normal/odds then
|
||||
exact = reward
|
||||
end
|
||||
|
||||
-- Minimum is default unless probability is guaranteed (eg. 2 in 2 chance)
|
||||
local min = default
|
||||
if G.GAME.probabilities.normal >= odds then
|
||||
min = reward
|
||||
end
|
||||
|
||||
-- Maximum is always reward (probability is always > 0); redundant variable is for readability
|
||||
local max = reward
|
||||
|
||||
return exact, min, max
|
||||
end
|
||||
|
||||
function DV.SIM.adjust_field_with_range(adj_func, field, mod_func, exact_value, min_value, max_value)
|
||||
if not exact_value then error("Cannot adjust field, exact_value is missing.") end
|
||||
|
||||
if not min_value or not max_value then
|
||||
min_value = exact_value
|
||||
max_value = exact_value
|
||||
end
|
||||
|
||||
DV.SIM.running.min[field] = mod_func(adj_func(DV.SIM.running.min[field], min_value))
|
||||
DV.SIM.running.exact[field] = mod_func(adj_func(DV.SIM.running.exact[field], exact_value))
|
||||
DV.SIM.running.max[field] = mod_func(adj_func(DV.SIM.running.max[field], max_value))
|
||||
end
|
||||
|
||||
function DV.SIM.add_chips(exact, min, max)
|
||||
DV.SIM.adjust_field_with_range(function(x, y) return x + y end, "chips", mod_chips, exact, min, max)
|
||||
end
|
||||
|
||||
function DV.SIM.add_mult(exact, min, max)
|
||||
DV.SIM.adjust_field_with_range(function(x, y) return x + y end, "mult", mod_mult, exact, min, max)
|
||||
end
|
||||
|
||||
function DV.SIM.x_mult(exact, min, max)
|
||||
DV.SIM.adjust_field_with_range(function(x, y) return x * y end, "mult", mod_mult, exact, min, max)
|
||||
end
|
||||
|
||||
function DV.SIM.add_dollars(exact, min, max)
|
||||
-- NOTE: no mod_func for dollars, so have to declare an identity function
|
||||
DV.SIM.adjust_field_with_range(function(x, y) return x + y end, "dollars", function(x) return x end, exact, min, max)
|
||||
end
|
||||
|
||||
function DV.SIM.add_reps(n)
|
||||
DV.SIM.running.reps = DV.SIM.running.reps + n
|
||||
end
|
||||
|
||||
--
|
||||
-- LOW-LEVEL:
|
||||
--
|
||||
|
||||
function DV.SIM.is_suit(card_data, suit, ignore_scorability)
|
||||
if card_data.debuff and not ignore_scorability then return end
|
||||
if card_data.ability.effect == "Stone Card" then
|
||||
return false
|
||||
end
|
||||
if card_data.ability.effect == "Wild Card" and not card_data.debuff then
|
||||
return true
|
||||
end
|
||||
if next(find_joker("Smeared Joker")) then
|
||||
local is_card_suit_light = (card_data.suit == "Hearts" or card_data.suit == "Diamonds")
|
||||
local is_check_suit_light = (suit == "Hearts" or suit == "Diamonds")
|
||||
if is_card_suit_light == is_check_suit_light then return true end
|
||||
end
|
||||
return card_data.suit == suit
|
||||
end
|
||||
|
||||
function DV.SIM.get_rank(card_data)
|
||||
if card_data.ability.effect == "Stone Card" and not card_data.vampired then
|
||||
DV.SIM.misc.next_stone_id = DV.SIM.misc.next_stone_id - 1
|
||||
return DV.SIM.misc.next_stone_id
|
||||
end
|
||||
return card_data.rank
|
||||
end
|
||||
|
||||
function DV.SIM.is_rank(card_data, ranks)
|
||||
if card_data.ability.effect == "Stone Card" then return false end
|
||||
|
||||
if type(ranks) == "number" then ranks = {ranks} end
|
||||
for _, r in ipairs(ranks) do
|
||||
if card_data.rank == r then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function DV.SIM.check_rank_parity(card_data, check_even)
|
||||
if check_even then
|
||||
local is_even_numbered = (card_data.rank <= 10 and card_data.rank >= 0 and card_data.rank % 2 == 0)
|
||||
return is_even_numbered
|
||||
else
|
||||
local is_odd_numbered = (card_data.rank <= 10 and card_data.rank >= 0 and card_data.rank % 2 == 1)
|
||||
local is_ace = (card_data.rank == 14)
|
||||
return (is_odd_numbered or is_ace)
|
||||
end
|
||||
end
|
||||
|
||||
function DV.SIM.is_face(card_data)
|
||||
return (DV.SIM.is_rank(card_data, {11, 12, 13}) or next(find_joker("Pareidolia")))
|
||||
end
|
||||
|
||||
function DV.SIM.set_ability(card_data, center)
|
||||
-- See Card:set_ability()
|
||||
card_data.ability = {
|
||||
name = center.name,
|
||||
effect = center.effect,
|
||||
set = center.set,
|
||||
mult = center.config.mult or 0,
|
||||
h_mult = center.config.h_mult or 0,
|
||||
h_x_mult = center.config.h_x_mult or 0,
|
||||
h_dollars = center.config.h_dollars or 0,
|
||||
p_dollars = center.config.p_dollars or 0,
|
||||
t_mult = center.config.t_mult or 0,
|
||||
t_chips = center.config.t_chips or 0,
|
||||
x_mult = center.config.Xmult or 1,
|
||||
h_size = center.config.h_size or 0,
|
||||
d_size = center.config.d_size or 0,
|
||||
extra = copy_table(center.config.extra) or nil,
|
||||
extra_value = 0,
|
||||
type = center.config.type or '',
|
||||
order = center.order or nil,
|
||||
forced_selection = card_data.ability and card_data.ability.forced_selection or nil,
|
||||
perma_bonus = card_data.ability and card_data.ability.perma_bonus or 0,
|
||||
bonus = (card_data.ability and card_data.ability.bonus or 0) + (center.config.bonus or 0)
|
||||
}
|
||||
end
|
||||
|
||||
function DV.SIM.set_edition(card_data, edition)
|
||||
card_data.edition = nil
|
||||
if not edition then return end
|
||||
|
||||
if edition.holo then
|
||||
if not card_data.edition then card_data.edition = {} end
|
||||
card_data.edition.mult = G.P_CENTERS.e_holo.config.extra
|
||||
card_data.edition.holo = true
|
||||
card_data.edition.type = 'holo'
|
||||
elseif edition.foil then
|
||||
if not card_data.edition then card_data.edition = {} end
|
||||
card_data.edition.chips = G.P_CENTERS.e_foil.config.extra
|
||||
card_data.edition.foil = true
|
||||
card_data.edition.type = 'foil'
|
||||
elseif edition.polychrome then
|
||||
if not card_data.edition then card_data.edition = {} end
|
||||
card_data.edition.x_mult = G.P_CENTERS.e_polychrome.config.extra
|
||||
card_data.edition.polychrome = true
|
||||
card_data.edition.type = 'polychrome'
|
||||
elseif edition.negative then
|
||||
-- TODO
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user