local odds = 0 ---@type { canSpawn: CanSpawn } local selectedSpawner spawnerSystem = filteredSystem("spawner", { canSpawn = T.CanSpawn, odds = T.number }, function(spawner, _, _) if odds <= 0 then return end odds = odds - spawner.odds if odds <= 0 then selectedSpawner = spawner else selectedSpawner = selectedSpawner or spawner end end) --- May cause process() and postProcess() to be skipped function spawnerSystem:preProcess() local newestX = Ingredients.newestIngredient().position.x local panX = Camera.pan.x local rightEdge = panX + 400 local totalOdds = 0 for _, spawner in pairs(spawnerSystem.entities) do totalOdds = totalOdds + spawner.odds end if newestX < rightEdge + 100 then odds = math.random() * totalOdds selectedSpawner = nil return -- A new ingredient needs spawning! end -- We do not need a new ingredient at this time return tiny.SKIP_PROCESS end local spawnEveryX = 30 -- Currently spawns AT MOST one new ingredient per frame, which is probably not enough at high speeds! function spawnerSystem:postProcess() local newestX = Ingredients.newestIngredient().position.x local newlySpawned = Ingredients.nextInCache() -- TODO: If performance becomes an issue, maybe just swap out __index for k, v in pairs(selectedSpawner.canSpawn.entity) do newlySpawned[k] = v end -- TODO: May not need to include lower spawners when we reach higher altitudes. local panY = math.floor(Camera.pan.y or 0) local yRange = selectedSpawner.canSpawn.yRange or { top = panY - 240, bottom = panY + 220 } newlySpawned.position = { x = newestX + spawnEveryX, y = math.random(yRange.top, yRange.bottom) } self.world:addEntity(newlySpawned) end ---@param world World function addAllSpawners(world) function addCollectableSpawner(name, spawnerOdds, score, sprite, canBounce, yRange) local sizeX, sizeY = sprite:getSize() local size = { x = sizeX, y = sizeY / 2 } world:addEntity({ -- 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. name = name, odds = spawnerOdds, canSpawn = { yRange = yRange, entity = { score = score, canBeCollidedBy = 1, expireAfterCollision = true, size = size, drawAsSprite = sprite, collectable = sprite, expireWhenOffScreenBy = expireWhenOffScreenBy, disableCollisionWhenRoundEnds = T.marker, canBounce = canBounce, }, }, }) end addCollectableSpawner("Lettuce", 0.7, 1, LettuceSprite, { flat = { x = 12, y = 220 }, mult = { x = 1, y = -0.5 }, }) addCollectableSpawner("Tomato", 0.1, 15, TomatoSprite) addCollectableSpawner("Mushroom", 0.02, 5, MushroomSprite, { flat = { x = 0, y = 290 }, mult = { x = 1, y = -1 }, }, { top = 219, bottom = 223 }) addCollectableSpawner("Cheese", 0.05, 1, CheeseSprite, { flat = { x = 50, y = 390 }, mult = { x = 0.7, y = -0.5 }, }) 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