Version 1.4.0 released!
Version 1.4.0 has landed!
This update focuses on a whole host of new modding features and some fixes sprinkled in too.
New Level commands
Added `waitForNoSpecificEntity(string entity, int timeout = -1)` Added `ifNotSpecificEntityOnScreen(string entity, int places)` Added `ifCreditCountBelow(int creditCount, int places)` Added `ifCreditCountAbove(int creditCount, int places)` Added `ifCreditCountEqual(int creditCount, int places)` Added `ifPlayerWeaponIs(int weaponID, int places)` Added `ifPlayerHasPods(int places)` Added `destroyEntityOfType(string entityName, bool isKill)` Added `destroyEntitiesOfType(string entityName, bool isKill)` Added destroyEnemyEntities(bool isKill)
Audio Definitions
Added `s_pause`: this is a duplicate of `s_star1` Added `s_exit_attract`: this is a duplicate of `s_star1` Added `s_ui_main_toggle_sfx_setting`: this is a duplicate of `s_star1` Added optional `s_fruit_cherry`: if not defined, we simply fall back to `s_fruit` (pickup sfx) Added optional `s_fruit_cherry_small`: if not defined, we simply fall back to `s_fruit` (pickup sfx) Added optional `s_fruit_bananas`: if not defined, we simply fall back to `s_fruit` (pickup sfx) Added optional `s_fruit_bananas_small`: if not defined, we simply fall back to `s_fruit` (pickup sfx) Added optional `s_fruit_grapes`: if not defined, we simply fall back to `s_fruit` (pickup sfx) Added optional `s_fruit_grapes_small`: if not defined, we simply fall back to `s_fruit` (pickup sfx) Added optional `s_fruit_coin`: if not defined, we simply fall back to `s_fruit (pickup sfx
Lua API
Piped lua's default print function to the game's log Made use of JSONObject fallbacks in `saucer.lua`'s commandArgs Changed `SpawnEntityWorld(entityName, worldPosition, arguments)`: Now returns an integer, this is the entityID Changed `SpawnEntityWorld(entityName, worldPosition, arguments)`: Argument parameter is now optional (no longer requiring creation of an empty JSONObject) Changed `SpawnEntityLocal(entityName, localPosition, arguments)`: Now returns an integer, this is the entityID Changed `SpawnEntityLocal(entityName, localPosition, arguments)`: Argument parameter is now optional (no longer requiring creation of an empty JSONObject) Changed `SpawnEntityChild(entityName, parent, positionOffset, arguments)`: Now returns an integer, this is the entityID Changed `SpawnEntityChild(entityName, parent, positionOffset, arguments)`: Argument parameter is now optional (no longer requiring creation of an empty JSONObject) Added `GetEntity(entityID)` Changed `GetPlayer(index)`: Now expects an integer Added `MoveTowardsAngle(current, target, maxDelta)` Added `DeltaAngle(current, target)` Added `IsOriginalVersion()` Added `GetSpriteDimensions(animationName, spriteIndex)` Added `MakeBonuses(x, y, fruitSetID)` Added `AdjustXToWideScreen(x)` Added `NewDiffDictInt(veryEasy, easy, normal, hard, nasty)` Added `NewDiffDictFloat(veryEasy, easy, normal, hard, nasty)` Added `NewDiffDictBool(veryEasy, easy, normal, hard, nasty)` Added `NewDiffDictString(veryEasy, easy, normal, hard, nasty)` Added `NewDiffDictIntArray(veryEasy, easy, normal, hard, nasty)` Added `NewDiffDictFloatArray(veryEasy, easy, normal, hard, nasty)` Added `NewDiffDictBoolArray(veryEasy, easy, normal, hard, nasty)` Added `NewDiffDictStringArray(veryEasy, easy, normal, hard, nasty)` Added `Globals.isPassiveResistance` Added `Globals.trainEntities` Added `Globals.trainBonus` Added `Globals.trainCounter` Added `Globals.custom` Fixed `Globals.ScrollingSpeed(layer)` being nil Fixed `Globals.firewait` being nil Fixed `Globals.levelLifetime` being nil Fixed `Globals.createSplashes` being nil Fixed `Globals.bonusScores` being nil Fixed `Globals.layerScaleValues` being nil Fixed `Globals.horizonLevels` being nil Fixed `Globals.layerReflections` being nil Fixed `Globals.firingChanceSaucer` being nil Fixed `Globals.firingChanceRandomFishFighter` being nil Added `SpriteAnimator.worldPosition` Added `SpriteAnimator.rotation` Added `SpriteAnimator.enabled` Added `Player.lastUpdateTime` Added `Player.nextUpdateTime` Added `Player.lastPosition` Added `Player.nextPosition` Added `Player.position` Added `Player.worldPosition` Added `Player.isActive` Deprecated `Player.GetPosition()`: use `Player.position` instead Deprecated `Player.GetWorldPosition()`: use `Player.worldPosition` instead Added `BaseEntity.customBehaviourData` Added `BaseEntity.entityID` Added `BaseEntity.lastUpdateTime` Added `BaseEntity.nextUpdateTime` Added `BaseEntity.lastPosition` Added `BaseEntity.nextPosition` Added `BaseEntity.rotation` Added `BaseEntity.defaultOnHitByBulletBehaviour` Added `BaseEntity.defaultOnHitByPlayerBehaviour` Added `BaseEntity.BulletConsume(consumer)` Added `BaseEntity.BulletGetKillCreditIndex()` Added `BaseEntity.EndKillTimer()` Fixed `BaseEntity.GetDamageFrame(maxHitPoints, hitPoints, frameCount)` being nil Fixed `BaseEntity.CheckCollision(collider, playerCollisionCallback?, bulletCollisionCallback?)` behaving incorrectly when passing nil as callbacks Fixed `BaseEntity.SpawnShipDebris(count, mxMin, mxMax, myMin, myMax, xOffset, yOffset, mxSpawnDirectionMin, mxSpawnDirectionMax, mySpawnDirectionMin, mySpawnDirectionMax)` not spawning secondary debris Changed `JSONObject.GetFieldString(name)`: To take an additional `fallback?` parameter: e.g. `GetFieldString(my_name, my_fallback_value)` Changed `JSONObject.GetFieldFloat(name)`: To take an additional `fallback?` parameter: e.g. `GetFieldFloat(my_name, my_fallback_value)` Changed `JSONObject.GetFieldInt(name)`: To take an additional `fallback?` parameter: e.g. `GetFieldInt(my_name, my_fallback_value)` Changed `JSONObject.GetFieldBool(name)`: To take an additional `fallback?` parameter: e.g. `GetFieldBool(my_name, my_fallback_value)` Changed `JSONObject.GetFieldStringArray(name)`: To take an additional `fallback?` parameter: e.g. `GetFieldStringArray(my_name, my_fallback_value)` Changed `JSONObject.GetFieldFloatArray(name)`: To take an additional `fallback?` parameter: e.g. `GetFieldFloatArray(my_name, my_fallback_value)` Changed `JSONObject.GetFieldIntArray(name)`: To take an additional `fallback?` parameter: e.g. `GetFieldIntArray(my_name, my_fallback_value)` Changed `JSONObject.GetFieldBoolArray(name)`: To take an additional `fallback?` parameter: e.g. `GetFieldBoolArray(my_name, my_fallback_value)` Removed `FirePattern.NewFirePatternFromEntityData(data)`: It was nil before, so no compatibility breaks Added `TurretData.CalculateBulletParams(worldPosition, angleOffset)` Added `Collider2D.boolean` Added `Collider2D.SetLogicLayerDefault()` Added `Collider2D.SetLogicLayerTransparentFX()` Added `Collider2D.SetLogicLayerIgnoreRaycast()` Added `Collider2D.SetLogicLayerWater()` Added `Collider2D.SetLogicLayerUI()` Added `Collider2D.SetLogicLayerPlayer()` Added `Collider2D.SetLogicLayerPlayerShot()` Added `Collider2D.SetLogicLayerEnemy()` Added `Collider2D.SetLogicLayerEnemyShot()` Added `Collider2D.SetLogicLayerLoadScreen()` Added `Collider2D.SetLogicLayerPlayerPickupCollider()` Added `Collider2D.SetLogicLayerPickup()` Added Collider2D.SetLogicLayerPlayerShotNoFruit()
Lua globals.lua
Defined `AnimationType`: Enum for usable values (previously only in the documentation) Defined `GameDifficulty`: Enum for usable values (previously only in the documentation) Defined `ScoreSource`: Enum for usable values (previously only in the documentation) Added `RandRangeF(min, max)` Added `RandRange(min, max)` Added `Round(value)` Added `Clamp(value, min, max)` Added `Lerp(min, max, t)` Added `CreateTurret(entity, xOff, yOff, parent, waiter)` Deprecated `round(value)`: Use `Round(value)` instead Deprecated `clamp(value, min, max)`: Use `Clamp( alue, min, max` instea
Example scripts
Implemented `car.lua` example script
This is the stage one boss in the game Example of turret entities, in this case spawning them manually Example of Global counters Example of multi entity enemies Example of a boss fight Example of custom hit responses using custom colliders Example of linked enemies (movement depending on the next segment) Example of `BaseEntity.IsKilledManually` Example of spawning other entities Example of `EntityData.customBehaviourData`
Implemented `enemy_bullet.lua` example script
This is the basic bullets fired by saucers and other enemies Example of `EntityData.customBehaviourData` Example of manual sprite frame handling Example of player dependent logic
Implemented `reclayed_turret.lua` example script
This is the base for most of the turrets used Example of TurretData Example of FirePattern Example of stationary enemy, designed to be attached to another Example of properly spawning bullets Example of `EntityData.customBehaviourData` Example of player dependent logic
Misc
Fixed Boss 1 sometimes not sorting correctly, this was visible on the chain element Fixed `EnemyHomerBullet` corrupting when multiple entities used it with differing `customBehaviourData` Exposed `speed` and `tickTurnDegrees` as `customBehaviourData` in `HomingMissile` behaviour Increased class strictness in LLS definition `types.lua` (Will now warn you if you try to use a field that doesn't exist) Added `Trail` behaviour, functioning as a way to create customizable trails for any entity. See `"exampleTrail"` in `entities.json` Fixed `Lua` behaviours not defaulting to the default layer when reused Fixed `Lua` cleaning up their functions too early (the game expects function pointers to still be valid during the frame kill was called) Updated documentation
This announcement was originally published by Claymatic Games on Steam on Apr 20, 2026.
Open the original on Steam