Compare commits

..

No commits in common. "23f5ba9f0358d4e4733b558f7e5a99744ee992ce" and "110d02fe2c0139d3ff88a85f56ee734611ad4715" have entirely different histories.

13 changed files with 131 additions and 259 deletions

View File

@ -1,6 +1,3 @@
if not playdate.isSimulator then
return
end
getCurrentTimeMilliseconds = playdate.getCurrentTimeMilliseconds getCurrentTimeMilliseconds = playdate.getCurrentTimeMilliseconds
tinyTrackEntityAges = true tinyTrackEntityAges = true
@ -14,32 +11,3 @@ end
tinyLogSystemUpdateTime = false tinyLogSystemUpdateTime = false
tinyLogSystemChanges = false tinyLogSystemChanges = false
tinyWarnWhenNonDataOnEntities = false
if tinyWarnWhenNonDataOnEntities then
function checkForNonData(e, nested, tableCache)
nested = nested or false
tableCache = tableCache or {}
local valType = type(e)
if valType == "table" then
if tableCache[e] then
return
end
tableCache[e] = true
for k, v in pairs(e) do
local keyWarning = checkForNonData(k, true, tableCache)
if keyWarning then
return keyWarning
end
local valueWarning = checkForNonData(v, true, tableCache)
if valueWarning then
return valueWarning
end
end
elseif valType == "function" or valType == "thread" or valType == "userdata" then
return valType
end
end
end

View File

@ -489,20 +489,6 @@ if tinyTrackEntityAges then
function tiny.addEntity(world, entity) function tiny.addEntity(world, entity)
local added = wrapped(world, entity) local added = wrapped(world, entity)
added[ENTITY_INIT_MS] = getCurrentTimeMilliseconds() added[ENTITY_INIT_MS] = getCurrentTimeMilliseconds()
return added
end
tiny_addEntity = tiny.addEntity
end
if tinyWarnWhenNonDataOnEntities then
local wrapped = tiny.addEntity
function tiny.addEntity(world, entity)
local added = wrapped(world, entity)
local nonDataType = checkForNonData(added)
if nonDataType then
print("Detected non-data type '" .. nonDataType .. "' on entity")
end
return added
end end
tiny_addEntity = tiny.addEntity tiny_addEntity = tiny.addEntity
end end

View File

@ -0,0 +1,42 @@
function bouncer(v)
return function()
local h0 = 0.1 -- m/s
-- local v = 10 -- m/s, current velocity
local g = 10 -- m/s/s
local t = 0 -- starting time
local rho = 0.60 -- coefficient of restitution
local tau = 0.10 -- contact time for bounce
local hmax = h0 -- keep track of the maximum height
local h = h0
local hstop = 0.01 -- stop when bounce is less than 1 cm
local freefall = true -- state: freefall or in contact
local t_last = -math.sqrt(2 * h0 / g) -- time we would have launched to get to h0 at t=0
local vmax = math.sqrt(v * g)
while hmax > hstop do
local dt = coroutine.yield(h, not freefall)
if freefall then
local hnew = h + v * dt - 0.5 * g * dt * dt
if hnew < 0 then
-- Bounced!
t = t_last + 2 * math.sqrt(2 * hmax / g)
freefall = false
t_last = t + tau
h = 0
else
t = t + dt
v = v - g * dt
h = hnew
end
else
t = t + tau
vmax = vmax * rho
v = vmax
freefall = true
h = 0
end
hmax = 0.5 * vmax * vmax / g
end
return 0
end
end

View File

@ -12,8 +12,8 @@ function Cart.reset(o)
y = 50, y = 50,
} }
o.velocity = { o.velocity = {
x = o.baseVelocity.x + (100 * math.random()), x = 300 + (100 * math.random()),
y = o.baseVelocity.y * math.random(), y = 175 * (math.random() - 1),
} }
o.size = size o.size = size
o.canBeBounced = { o.canBeBounced = {
@ -27,7 +27,6 @@ function Cart.reset(o)
---@param world World ---@param world World
function o:spawnEntitiesWhenStopped(world) function o:spawnEntitiesWhenStopped(world)
-- it'd be funny if the cart fully just exploded instead
self.velocity = { x = 300, y = 0 } self.velocity = { x = 300, y = 0 }
self.canCollideWith = 4 self.canCollideWith = 4
world:addEntity({ world:addEntity({
@ -49,10 +48,5 @@ function Cart.reset(o)
end end
function Cart.new() function Cart.new()
return setmetatable(Cart.reset({ return setmetatable(Cart.reset({}), { __index = Cart })
baseVelocity = {
x = 300,
y = -170,
}
}), { __index = Cart })
end end

View File

@ -24,7 +24,6 @@ import("systems/rounds.lua")
import("systems/camera-pan.lua") import("systems/camera-pan.lua")
import("systems/collision-resolution.lua") import("systems/collision-resolution.lua")
import("systems/collision-detection.lua") import("systems/collision-detection.lua")
import("systems/upgrade-utils.lua")
import("systems/draw.lua") import("systems/draw.lua")
import("systems/input.lua") import("systems/input.lua")
@ -38,6 +37,7 @@ local gfx <const> = playdate.graphics
playdate.display.setRefreshRate(50) playdate.display.setRefreshRate(50)
gfx.setBackgroundColor(gfx.kColorWhite) gfx.setBackgroundColor(gfx.kColorWhite)
cart = Cart.new()
local floorSize = { x = 10000, y = 10 } local floorSize = { x = 10000, y = 10 }
floor = { floor = {
position = { x = 0, y = 235 }, position = { x = 0, y = 235 },
@ -67,7 +67,7 @@ function Score:draw()
end end
world:addEntity(floor) world:addEntity(floor)
world:addEntity(Cart.new()) world:addEntity(cart)
addAllSpawners(world) addAllSpawners(world)
world:addEntity(Ingredients.getFirst()) world:addEntity(Ingredients.getFirst())

View File

@ -3,19 +3,14 @@
-- This file is composed of, essentially, "base types" -- This file is composed of, essentially, "base types"
local SOME_TABLE <const> = {}
---@alias AnyComponent any
---@alias BitMask number ---@alias BitMask number
---@alias CanBeBounced { flat: XyPair, mult = XyPair } ---@alias CanBeBounced { flat: XyPair, mult = XyPair }
---@alias CanSpawn { entityToSpawn: Entity } ---@alias CanSpawn { entity: Entity }
---@alias Collision { collisionBetween: Entity[] } ---@alias Collision { collisionBetween: Entity[] }
---@alias Entity table ---@alias Entity table
---@alias InRelations Entity[] ---@alias InRelations Entity[]
---@alias InputState { receivedInputThisFrame: boolean, aJustPressed: boolean, bJustPressed: boolean, upJustPressed: boolean, downJustPressed: boolean, leftJustPressed: boolean, rightJustPressed: boolean } ---@alias InputState { aJustPressed: boolean, bJustPressed: boolean, upJustPressed: boolean, downJustPressed: boolean, leftJustPressed: boolean, rightJustPressed: boolean }
---@alias ReplaceRelation { entityToModify: Entity, replacement: table }
---@alias RoundStateAction "end" | "start" ---@alias RoundStateAction "end" | "start"
---@alias Selectable { additions: Entity[] | nil, replacements: ReplaceRelation[] | nil, highlighted: boolean, navigateDown: Selectable | nil, navigateUp: Selectable | nil }
---@alias XyPair { x: number, y: number } ---@alias XyPair { x: number, y: number }
@ -24,7 +19,7 @@ T = {
number = 0, number = 0,
numberArray = { 1, 2, 3 }, numberArray = { 1, 2, 3 },
str = "", str = "",
marker = SOME_TABLE, marker = {},
---@type fun(self) ---@type fun(self)
SelfFunction = function() end, SelfFunction = function() end,
--- Actor --- Actor
@ -34,46 +29,28 @@ T = {
mult = XyPair, mult = XyPair,
}, },
---@type pd_image ---@type pd_image
pd_image = SOME_TABLE, pd_image = {},
---@type pd_font ---@type pd_font
pd_font = SOME_TABLE, pd_font = {},
---@type AnyComponent
AnyComponent = SOME_TABLE,
---@type BitMask ---@type BitMask
BitMask = 0, BitMask = 0,
---@type CanBeBounced ---@type CanBeBounced
CanBeBounced = SOME_TABLE, CanBeBounced = {},
---@type CanSpawn ---@type CanSpawn
CanSpawn = SOME_TABLE, CanSpawn = {},
---@type Collision ---@type Collision
Collision = SOME_TABLE, Collision = {},
---@type Entity ---@type Entity
Entity = SOME_TABLE, Entity = {},
---@type InRelations ---@type InRelations
InRelations = SOME_TABLE, InRelations = {},
---@type InputState ---@type InputState
InputState = SOME_TABLE, InputState = {},
---@type ReplaceRelation
ReplaceRelation = SOME_TABLE,
---@type RoundStateAction ---@type RoundStateAction
RoundStateAction = "start", RoundStateAction = "start",
---@type Selectable
Selectable = SOME_TABLE,
---@type XyPair ---@type XyPair
XyPair = SOME_TABLE, XyPair = {},
} }
---@generic T ---@generic T
@ -83,13 +60,6 @@ function Maybe(t)
return { maybe = t } return { maybe = t }
end end
---@generic T
---@param t T
---@return T[]
function Arr(t)
return { arrOf = t }
end
TextStyle = { TextStyle = {
Inverted = "INVERTED", Inverted = "INVERTED",
Bordered = "BORDERED", Bordered = "BORDERED",

View File

@ -12,7 +12,7 @@ function t(name, type, value)
elseif type == "string" then elseif type == "string" then
value = "" value = ""
else else
value = "SOME_TABLE" value = "{}"
end end
end end
types[#types + 1] = { name = name, type = type, value = value } types[#types + 1] = { name = name, type = type, value = value }
@ -42,7 +42,7 @@ end
function dumpTypeObjects() function dumpTypeObjects()
local ret = "" local ret = ""
for _, v in ipairs(types) do for _, v in ipairs(types) do
local line = "\n\n ---@type " .. v.name .. "\n " .. v.name .. " = " .. v.value .. "," local line = "\n ---@type " .. v.name .. "\n " .. v.name .. " = " .. v.value .. ","
ret = ret .. line ret = ret .. line
end end
return ret return ret
@ -51,8 +51,6 @@ end
-- This file is composed of, essentially, "base types" -- This file is composed of, essentially, "base types"
local SOME_TABLE <const> = {}
!!(tMany({ !!(tMany({
Entity = "table", Entity = "table",
XyPair = "{ x: number, y: number }", XyPair = "{ x: number, y: number }",
@ -60,14 +58,10 @@ local SOME_TABLE <const> = {}
BitMask = "number", BitMask = "number",
CanBeBounced = "{ flat: XyPair, mult: XyPair }", CanBeBounced = "{ flat: XyPair, mult: XyPair }",
RoundStateAction = { '"end" | "start"', '"start"' }, RoundStateAction = { '"end" | "start"', '"start"' },
AnyComponent = "any", CanSpawn = "{ entity: Entity }",
CanSpawn = "{ entityToSpawn: Entity }",
InRelations = "Entity[]", InRelations = "Entity[]",
CanBeBounced = "{ flat: XyPair, mult = XyPair }", CanBeBounced = "{ flat: XyPair, mult = XyPair }",
InputState = "{ receivedInputThisFrame: boolean, aJustPressed: boolean, bJustPressed: boolean, upJustPressed: boolean, downJustPressed: boolean, leftJustPressed: boolean, rightJustPressed: boolean }", InputState = "{ aJustPressed: boolean, bJustPressed: boolean, upJustPressed: boolean, downJustPressed: boolean, leftJustPressed: boolean, rightJustPressed: boolean }",
ReplaceRelation = "{ entityToModify: Entity, replacement: table }",
Selectable = "{ additions: Entity[] | nil, replacements: ReplaceRelation[] | nil, highlighted: boolean, navigateDown: Selectable | nil, navigateUp: Selectable | nil }",
})) }))
T = { T = {
@ -75,7 +69,7 @@ T = {
number = 0, number = 0,
numberArray = { 1, 2, 3 }, numberArray = { 1, 2, 3 },
str = "", str = "",
marker = SOME_TABLE, marker = {},
---@type fun(self) ---@type fun(self)
SelfFunction = function() end, SelfFunction = function() end,
--- Actor --- Actor
@ -85,9 +79,9 @@ T = {
mult = XyPair, mult = XyPair,
}, },
---@type pd_image ---@type pd_image
pd_image = SOME_TABLE, pd_image = {},
---@type pd_font ---@type pd_font
pd_font = SOME_TABLE, pd_font = {},
!!(dumpTypeObjects()) !!(dumpTypeObjects())
} }
@ -98,13 +92,6 @@ function Maybe(t)
return { maybe = t } return { maybe = t }
end end
---@generic T
---@param t T
---@return T[]
function Arr(t)
return { arrOf = t }
end
TextStyle = { TextStyle = {
Inverted = "INVERTED", Inverted = "INVERTED",
Bordered = "BORDERED", Bordered = "BORDERED",

View File

@ -1,15 +1,4 @@
world:addEntity({ gravity = -300 }) local G = -300
gravities = filteredSystem("gravities", { gravity = T.number })
filteredSystem("changeGravity", { changeGravityTo = T.number }, function(e, _, _)
for _, ge in pairs(gravities.entities) do
ge.gravity = e.changeGravityTo
end
end)
fallSystem = filteredSystem("fall", { velocity = T.XyPair, mass = T.number }, function(e, dt) fallSystem = filteredSystem("fall", { velocity = T.XyPair, mass = T.number }, function(e, dt)
for _, ge in pairs(gravities.entities) do e.velocity.y = e.velocity.y - (G * dt * e.mass) - (0.5 * dt * dt)
e.velocity.y = e.velocity.y - (ge.gravity * dt * e.mass) - (0.5 * dt * dt)
end
end) end)

View File

@ -1,6 +1,6 @@
local buttonJustPressed = playdate.buttonJustPressed ---@alias InputState { upJustPressed: boolean, downJustPressed: boolean, rightJustPressed: boolean, leftJustPressed: boolean, aJustPressed: boolean, bJustPressed: boolean }
---@type InputState local buttonJustPressed = playdate.buttonJustPressed
local inputState = {} local inputState = {}
inputSystem = filteredSystem("input", { canReceiveInput = T.marker }, function(e, _, system) inputSystem = filteredSystem("input", { canReceiveInput = T.marker }, function(e, _, system)
@ -15,12 +15,4 @@ function inputSystem:preProcess()
inputState.leftJustPressed = buttonJustPressed(playdate.kButtonLeft) inputState.leftJustPressed = buttonJustPressed(playdate.kButtonLeft)
inputState.aJustPressed = buttonJustPressed(playdate.kButtonA) inputState.aJustPressed = buttonJustPressed(playdate.kButtonA)
inputState.bJustPressed = buttonJustPressed(playdate.kButtonB) inputState.bJustPressed = buttonJustPressed(playdate.kButtonB)
inputState.receivedInputThisFrame =
inputState.upJustPressed
or inputState.downJustPressed
or inputState.rightJustPressed
or inputState.leftJustPressed
or inputState.aJustPressed
or inputState.bJustPressed
end end

View File

@ -1,43 +1,23 @@
---@param relation ReplaceRelation ---@alias MenuItem { onSelect: fun(), highlighted: boolean, navigateDown: MenuItem | nil, navigateUp: MenuItem | nil }
---@param world World
local function applyReplacementRelation(relation, world)
for k, v in pairs(relation.replacement) do
relation.entityToModify[k] = v
end
world:addEntity(relation.entityToModify)
end
menuController = filteredSystem("menuController", { menuItems = Arr(T.Selectable), inputState = T.InputState }, function(e, _, system) ---@type MenuItem[]
local MenuItems = {}
menuController = filteredSystem("menuController", { menuItems = MenuItems, inputState = T.InputState }, function(e, _, system)
for _, menuItem in pairs(e.menuItems) do for _, menuItem in pairs(e.menuItems) do
if menuItem.highlighted then if menuItem.highlighted then
if e.inputState.aJustPressed then if e.inputState.aJustPressed then
-- Prepare to remove the menu and all menu items menuItem.onSelect(system.world)
system.world:removeEntity(e)
for _, item in pairs(e.menuItems) do for _, item in pairs(e.menuItems) do
system.world:removeEntity(item) system.world:removeEntity(item)
end end
system.world:removeEntity(e)
-- TODO: Larger menu that stays open for more purchases before the next round
world:addEntity({ roundAction = "start" })
if menuItem.replacements then
for _, v in ipairs(menuItem.replacements) do
applyReplacementRelation(v, system.world)
end end
end
if menuItem.additions then
for _, v in ipairs(menuItem.additions) do
system.world:addEntity(v)
end
end
end
if e.inputState.downJustPressed and menuItem.navigateDown then if e.inputState.downJustPressed and menuItem.navigateDown then
menuItem.highlighted = false menuItem.highlighted = false
menuItem.navigateDown.highlighted = true menuItem.navigateDown.highlighted = true
return return
end end
if e.inputState.upJustPressed and menuItem.navigateUp then if e.inputState.upJustPressed and menuItem.navigateUp then
menuItem.highlighted = false menuItem.highlighted = false
menuItem.navigateUp.highlighted = true menuItem.navigateUp.highlighted = true
@ -45,7 +25,6 @@ menuController = filteredSystem("menuController", { menuItems = Arr(T.Selectable
end end
end end
end end
for _, menuItem in pairs(e.menuItems) do for _, menuItem in pairs(e.menuItems) do
if menuItem.highlighted then if menuItem.highlighted then
menuItem.drawAsText.style = TextStyle.Inverted menuItem.drawAsText.style = TextStyle.Inverted

View File

@ -32,12 +32,12 @@ end)
removeAtRoundStart = filteredSystem("removeAtRoundStart", { removeAtRoundStart = T.bool }) removeAtRoundStart = filteredSystem("removeAtRoundStart", { removeAtRoundStart = T.bool })
filteredSystem("afterDelayAdd", { afterDelayAdd = { entityToAdd = T.Entity, delay = T.number } }, function(e, dt, system) filteredSystem("afterDelayAdd", { afterDelayAdd = { entity = T.Entity, delay = T.number } }, function(e, dt, system)
e.afterDelayAdd.delay = e.afterDelayAdd.delay - dt e.afterDelayAdd.delay = e.afterDelayAdd.delay - dt
if e.afterDelayAdd.delay > 0 then if e.afterDelayAdd.delay > 0 then
return return
end end
system.world:addEntity(e.afterDelayAdd.entityToAdd) system.world:addEntity(e.afterDelayAdd.entity)
system.world:removeEntity(e) system.world:removeEntity(e)
end) end)
@ -102,8 +102,7 @@ roundSystem = filteredSystem("round", { roundAction = T.RoundStateAction, positi
system.world:removeEntity(collectable) system.world:removeEntity(collectable)
end end
---@type NamedUpgrade[] local availableUpgrades = Utils.getNDifferentValues(getAvailableSpawnerUpgrades(), 3)
local availableUpgrades = Utils.getNDifferentValues(getAvailableUpgrades(), 3)
-- Sorting from shortest to longest sort of makes them look like a bun? -- Sorting from shortest to longest sort of makes them look like a bun?
table.sort(availableUpgrades, function(a, b) table.sort(availableUpgrades, function(a, b)
return #a.name > #b.name return #a.name > #b.name
@ -120,9 +119,8 @@ roundSystem = filteredSystem("round", { roundAction = T.RoundStateAction, positi
i = i + 1 i = i + 1
local collX, collY = 75, 21 local collX, collY = 75, 21
y = y - collY - 15 - 15 y = y - collY - 15 - 15
---@type Selectable
local upgradeEntity = { local upgradeEntity = {
replacements = { upgrade.replace }, onSelect = upgrade.apply,
drawAsText = { drawAsText = {
text = upgrade.name, text = upgrade.name,
style = TextStyle.Inverted, style = TextStyle.Inverted,
@ -152,7 +150,7 @@ roundSystem = filteredSystem("round", { roundAction = T.RoundStateAction, positi
system.world:addEntity({ system.world:addEntity({
afterDelayAdd = { afterDelayAdd = {
delay = delay, delay = delay,
entityToAdd = upgradeEntity entity = upgradeEntity
}, },
}) })
system.world:addEntity(menuEntity) system.world:addEntity(menuEntity)

View File

@ -35,7 +35,7 @@ function spawnerSystem:preProcess()
return tiny.SKIP_PROCESS return tiny.SKIP_PROCESS
end end
local spawnEveryX = 35 local spawnEveryX = 30
-- Currently spawns AT MOST one new ingredient per frame, which is probably not enough at high speeds! -- Currently spawns AT MOST one new ingredient per frame, which is probably not enough at high speeds!
function spawnerSystem:postProcess() function spawnerSystem:postProcess()
@ -43,7 +43,7 @@ function spawnerSystem:postProcess()
local newlySpawned = Ingredients.nextInCache() local newlySpawned = Ingredients.nextInCache()
-- TODO: If performance becomes an issue, maybe just swap out __index -- TODO: If performance becomes an issue, maybe just swap out __index
for k, v in pairs(selectedSpawner.canSpawn.entityToSpawn) do for k, v in pairs(selectedSpawner.canSpawn.entity) do
newlySpawned[k] = v newlySpawned[k] = v
end end
@ -62,13 +62,13 @@ function addAllSpawners(world)
local size = { x = sizeX, y = sizeY / 2 } local size = { x = sizeX, y = sizeY / 2 }
world:addEntity({ world:addEntity({
-- NOTE: This name should NOT be used to *identify* the spawner. -- NOTE: This name should NOT be used to identify the spawner.
-- It should only be used to supply visual information when searching for available upgrades. -- It should only be used to supply visual information when searching for available upgrades.
name = name, name = name,
odds = spawnerOdds, odds = spawnerOdds,
canSpawn = { canSpawn = {
yRange = yRange, yRange = yRange,
entityToSpawn = { entity = {
score = score, score = score,
canBeCollidedBy = 1, canBeCollidedBy = 1,
expireAfterCollision = true, expireAfterCollision = true,
@ -101,3 +101,47 @@ function addAllSpawners(world)
}) })
end end
---@alias Upgrade { name: string, apply = fun() }
---@return Upgrade[]
function getAvailableSpawnerUpgrades()
local upgrades = {}
for _, spawner in pairs(spawnerSystem.entities) do
if spawner.hasUpgradeSpeed then
-- upgrades[#upgrades + 1] = { hasUpgradeSpeed = spawner.hasUpgradeSpeed }
end
if spawner.canSpawn.entity.score then
local name = "Double " .. spawner.name .. " value"
upgrades[#upgrades + 1] = {
name = name,
apply = function(world)
print("Applying " .. name)
spawner.canSpawn.entity.score = spawner.canSpawn.entity.score * 2
world:addEntity({ roundAction = "start" })
end,
}
end
assert(spawner.odds, "Expected all spawners to have an `odds` field!")
local name = "Double " .. spawner.name .. " frequency"
upgrades[#upgrades + 1] = {
name = name,
apply = function(world)
print("Applying " .. name)
spawner.odds = spawner.odds * 2
world:addEntity({ roundAction = "start" })
end,
}
-- if not spawner.canSpawn.entity.velocity then
-- upgrades[#upgrades + 1] = {
-- name = spawner.name .. " Movement",
-- upgrade = function()
-- spawner.canSpawn.entity.velocity = { x = -10, y = 0 }
-- end,
-- }
-- end
end
return upgrades
end

View File

@ -1,77 +0,0 @@
---@alias NamedUpgrade { name: string, replace: ReplaceRelation }
--- Appends available upgrades to the given upgrades array.
---@param upgrades NamedUpgrade[]
function getAvailableSpawnerUpgrades(upgrades)
for _, spawner in pairs(spawnerSystem.entities) do
-- if spawner.hasUpgradeSpeed then
-- upgrades[#upgrades + 1] = { hasUpgradeSpeed = spawner.hasUpgradeSpeed }
-- end
local entityToSpawn = spawner.canSpawn.entityToSpawn
if entityToSpawn.score then
upgrades[#upgrades + 1] = {
name = "Double " .. spawner.name .. " value",
replace = {
entityToModify = entityToSpawn,
replacement = {
score = entityToSpawn.score * 2,
},
},
}
end
assert(spawner.odds, "Expected all spawners to have an `odds` field!")
upgrades[#upgrades + 1] = {
name = "Double " .. spawner.name .. " frequency",
replace = {
entityToModify = spawner,
replacement = {
odds = spawner.odds,
}
},
}
end
end
--- Appends available upgrades to the given upgrades array.
---@param upgrades NamedUpgrade[]
function getAvailableCartUpgrades(upgrades)
for _, cart in pairs(cartSystem.entities) do
upgrades[#upgrades + 1] = {
name = "10% Lighter Cart",
replace = {
entityToModify = cart,
replacement = {
mass = cart.mass * 0.9
}
},
}
upgrades[#upgrades + 1] = {
name = "+20% Cart Launch Right",
replace = {
entityToModify = cart.baseVelocity,
replacement = {
x = cart.baseVelocity.x * 1.2
}
},
}
upgrades[#upgrades + 1] = {
name = "+20% Cart Launch Up",
replace = {
entityToModify = cart.baseVelocity,
replacement = {
y = cart.baseVelocity.y * 1.2
}
},
}
end
end
---@return NamedUpgrade[]
function getAvailableUpgrades()
local upgrades = {}
getAvailableSpawnerUpgrades(upgrades)
getAvailableCartUpgrades(upgrades)
return upgrades
end