HomeGamesUpdatesPricingMethodology
Steam News20 April 20262mo ago

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.

Full notes

Full Platypus Reclayed update

Read the full published notes in a cleaner layout. The original post stays linked below.

What changed

17 fixes86 additions25 changes4 removals
  • Gameplay
  • Performance
  • UI and audio
  • Workshop
  • Fixes
  • Compatibility
addedThis update focuses on a whole host of new modding features and some fixes sprinkled in too.
addedNew Level commands
addedAdded `waitForNoSpecificEntity(string entity, int timeout = -1)`
addedAdded `ifNotSpecificEntityOnScreen(string entity, int places)`
addedAdded `ifCreditCountBelow(int creditCount, int places)`
addedAdded `ifCreditCountAbove(int creditCount, int places)`

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 [c]destroyEnemyEntities(bool isKill)[/c]

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[c] (pickup sfx[/c]

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 [c]Collider2D.SetLogicLayerPlayerShotNoFruit()[/c]

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([c]alue, min, max` instea[/c]

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

Source

Steam News / 20 April 2026

Open original post

Changelog.gg summarizes and formats this update. How we read updates.