You are viewing a potentially older version of this package. View all versions.
Vippy-ScalerCore-1.0.0 icon

ScalerCore

Scaling library for shrink and grow mods in R.E.P.O. Handles physics, audio, NavMesh, animation, colliders, and sync. Enemies, players, valuables, items, carts, doors. Vehicles stay drivable and pocketable when scaled. API for size mods.

Date uploaded a day ago
Version 1.0.0
Download link Vippy-ScalerCore-1.0.0.zip
Downloads 6509
Dependency string Vippy-ScalerCore-1.0.0

This mod requires the following mods to function

BepInEx-BepInExPack-5.4.2305 icon
BepInEx-BepInExPack

BepInEx pack for Mono Unity games. Preconfigured and ready to use.

Preferred version: 5.4.2305

README

ScalerCore

A scaling library for R.E.P.O. modders. Handles the hard parts of scaling game objects -- physics, audio, animation, colliders, NavMesh, multiplayer sync -- so you don't have to.

v1.0.0: the API is stable. Growth is a first-class direction now, not just shrinking: enemies, players, and props grow as well as shrink, with the audio, reach, mass, and physics that sells the size. Grown enemies cap their physical footprint so they still fit the level, voices pitch with an intelligibility floor, and grown players hold items at arm's length. The public surface (ScaleManager, ScaleOptions, IScaleHandler) is stable for the 1.x line.

If you're building a mod that changes the size of things (shrink rays, growth potions, whatever), ScalerCore gives you a clean API and takes care of the edge cases.

This is a library, not a standalone mod. End users don't need to interact with it directly. If you're a player, you probably got here because a mod like ShrinkerGun: COMPRESSOR depends on it.

For modders building size-changing mechanics. If you're looking for a shrink ray, check out ShrinkerGun: COMPRESSOR which is built on ScalerCore.

Installation

Reference ScalerCore.dll in your project. Add a hard dependency in your plugin:

[BepInDependency("Vippy.ScalerCore", BepInDependency.DependencyFlags.HardDependency)]

ScalerCore automatically attaches ScaleController components to all enemies, players, valuables, items, carts, and doors at runtime via Harmony patches. You don't need to add them yourself.

Quick Start

using ScalerCore;

// Shrink with default options (40% scale)
ScaleManager.Apply(targetGameObject);

// Shrink with custom options
var opts = ScaleOptions.Default;
opts.Factor = 0.5f;
opts.Duration = 60f;
ScaleManager.Apply(targetGameObject, opts);

// Restore with animation
ScaleManager.Restore(targetGameObject);

// Restore instantly (for bonk/damage reactions)
ScaleManager.RestoreImmediate(targetGameObject);

// Check state
bool tiny = ScaleManager.IsScaled(targetGameObject);

ScaleManager.Apply is host-authoritative. The host calls it, and RPCs propagate to all clients automatically. Late-joining players receive the current state on connect.

How It Works

ScalerCore attaches a ScaleController (a MonoBehaviourPunCallbacks) to every enemy, player, valuable, item, and door. On the first scale operation, the controller:

  1. Resolves a handler via ScaleHandlerRegistry based on what the object is
  2. Applies the target scale with a smooth animation (back-out easing for players, linear interpolation for objects)
  3. Adjusts physics (mass, grab force, collider radius), audio pitch, and NavMesh speed
  4. Syncs state to all clients via Photon RPCs
  5. Force-applies the target scale every LateUpdate to fight game code that resets localScale

Built-in handlers cover enemies, players, valuables, items, carts, and doors.

Custom Handlers

Implement IScaleHandler and register it with a predicate:

using ScalerCore;
using ScalerCore.Handlers;

public class MyBossHandler : IScaleHandler
{
    public void Setup(ScaleController ctrl)
    {
        // Cache components, set ScaleTarget if the visual root
        // is different from the physics root
    }

    public void OnScale(ScaleController ctrl)
    {
        // Called when the object is scaled down
    }

    public void OnRestore(ScaleController ctrl, bool isBonk)
    {
        // Called when restored. isBonk = true means instant (damage),
        // false means animated (timer/gun toggle)
    }

    public void OnUpdate(ScaleController ctrl)    { }
    public void OnLateUpdate(ScaleController ctrl) { }
    public void OnDestroy(ScaleController ctrl)    { }
}

Register it at plugin startup. Use priority > 0 to override built-in handlers:

ScaleHandlerRegistry.Register(
    new MyBossHandler(),
    go => go.GetComponentInParent<EnemyParent>()?.name.Contains("MyBoss") == true,
    priority: 10
);

The registry resolves handlers by checking predicates in descending priority order. First match wins.

API Reference

ScaleManager (static)

Method Description
Apply(GameObject target) Scale with default options (ScaleOptions.Default).
Apply(GameObject target, ScaleOptions options) Scale with custom options. Same factor on an already-scaled target toggles it back; different factor rescales.
ApplyIfNotScaled(GameObject target) Scale only if not already scaled. No-op if already scaled. Returns true if scaling was applied.
ApplyIfNotScaled(GameObject target, ScaleOptions options) Same with custom options. Ideal for cart mods and continuous triggers.
GetController(GameObject target) Get the ScaleController for a game object (resolves through PlayerShrinkLink). Returns null if none.
Restore(GameObject target) Restore with smooth animation. No-op when locked via RejectExternalApply.
RestoreImmediate(GameObject target) Restore instantly (respects bonk immunity timer). No-op when locked via RejectExternalApply.
ForceApply(GameObject target, ScaleOptions options) Apply without the RejectExternalApply check. For the mod that owns the lock.
ForceRestore(GameObject target) Restore without the RejectExternalApply check. For the mod that owns the lock.
UpdateOptions(GameObject target, ScaleOptions options) Replace the stored options on a live session without re-dispatching scale. Read CurrentOptions, mutate, pass back. Useful when restore-direction fields (RestoreSpeed, SuppressImpactFlash, SuppressCameraShake) need to track a config change mid-session. Returns false on missing controller, no active session, or RejectExternalApply lock.
ForceUpdateOptions(GameObject target, ScaleOptions options) UpdateOptions without the lock check. For the mod that owns the lock.
IsScaled(GameObject target) Returns true if the object is currently scaled.
CleanupAll() Restore all scaled objects. Called automatically on level change.
AllowDeadHeads Static bool, default false. Whether scaling may hit dead Semibot heads. Policy belongs to the calling mod: bind a config on your side and assign this (ShrinkerGun does exactly that). ScalerCore itself ships no user-facing settings for it.

ScaleController (MonoBehaviourPunCallbacks)

Attached automatically to game objects. Key public members:

Member Description
IsScaled Whether the object is currently scaled.
OriginalScale The object's scale before any modification.
CurrentOptions Snapshot of the active session's options. Read-only.
TargetType What kind of object this is (ScaleTargets.Players, .Enemies, etc.).
ScaleTarget Override in handler's Setup to scale a different transform than the controller's.
AllowManualScale Static bool, gates debug shrink/expand requests. Host sets it.
RequestBonkExpand() Client-safe expand request (sends RPC to host if called on non-host).
RequestManualExpand() Manual expand (skips bonk immunity).
RequestManualShrink() Manual shrink request.

IScaleHandler

public interface IScaleHandler
{
    void Setup(ScaleController ctrl);
    void OnScale(ScaleController ctrl);
    void OnRestore(ScaleController ctrl, bool isBonk);
    void OnUpdate(ScaleController ctrl);
    void OnLateUpdate(ScaleController ctrl);
    void OnDestroy(ScaleController ctrl);
}

ScaleHandlerRegistry (static)

Method Description
Register(IScaleHandler handler, Func<GameObject, bool> predicate, int priority = 0) Register a handler. Higher priority wins. Built-ins use priority 0.
Resolve(GameObject target) Returns the highest-priority matching handler, or null.

ScaleOptions

Each Apply() call takes a ScaleOptions struct. Use ScaleOptions.Default as a starting point and override what you need.

Field Default Description
Factor 0.4 Scale multiplier (0.4 = 40% of original size)
Duration 0 Seconds until auto-restore (0 = permanent)
Speed 2.0 Scale animation speed (used for both directions when RestoreSpeed is 0)
RestoreSpeed 0 Animation speed for the expand direction. 0 falls back to Speed.
BonkImmuneDuration 5.0 Grace period after scaling before damage can restore
MassCap 50.0 Max rigidbody mass while scaled
SpeedFactor 0.75 Enemy NavMesh speed multiplier
AnimSpeedMultiplier 1.5 Player animation speed while scaled
FootstepPitchMultiplier 1.5 Player footstep pitch while scaled
AudioPresence 1.0 Grow-side audio presence: 0 = pitch only, 1 = full (volume lift, light reverb, falloff scales with size). Rides the sync RPC.
EnemyPhysicalFactorCap 0 Grow-only, enemies only: colliders and nav radius stop at this factor while visuals/reach/audio keep climbing to Factor, so a giant still fits the level. 0 disables.
AllowedTargets All Flags: Players, Enemies, Items, Valuables, All
InvertedMode false If true, scaled state is the default and bonk temporarily grows back
SuppressValueDropExpand false If true, valuables won't expand when damaged while scaled (for cart mods)
PreserveMass false If true, rigidbody mass stays at original value while scaled (for cart mods)
SuppressImpactFlash false Skips the impact flash on shrink/expand (for cart-style mods that fire constantly)
SuppressCameraShake false Skips the camera shake on expand (pair with SuppressImpactFlash for a silent restore)
SuppressVoicePitch false No audio pitch shift on the controller's sounds or player's voice chat
IgnoreBonkExpand false Damage does not restore the controller. Covers every bonk path.
RejectExternalApply false External Apply/Restore/RestoreImmediate calls from other mods no-op. Owning mod uses ForceApply/ForceRestore.

ScaleTargets

Flags enum for filtering what Apply() affects:

var opts = ScaleOptions.Default;
opts.AllowedTargets = ScaleTargets.Enemies | ScaleTargets.Valuables;
ScaleManager.Apply(target, opts); // skips players and items

Configuration

ScalerCore is a pure library with no user-facing config. All scaling behavior (factor, speed, duration, etc.) is controlled per-call via ScaleOptions -- consuming mods expose whatever settings make sense for them.

What ScalerCore Handles

For enemies:

  • Visual mesh + physics rigidbody scaled separately (EnemyParent is never scaled)
  • NavMesh agent speed and radius
  • Grab force reduced to zero (instantly grabbable when shrunken)
  • Follow force scaled so grabbed enemies don't fight back
  • Damage output reduced
  • Knockback force reduced
  • Bonk restore on taking damage (with immunity window)

For players:

  • Camera offset, crouch/crawl positions, vision targets
  • Collision capsules (stand, crouch, stand-check)
  • Grab strength, range, throw strength
  • Movement speed
  • FOV adjustment
  • Voice chat pitch
  • Footstep sound pitch
  • Animation speed
  • Enlarged pupils (big cute eyes)
  • Pause menu avatar scaled to match
  • Near clip plane adjusted

For valuables:

  • Mass scaled and clamped
  • Extraction zone detection box scaled
  • Brief indestructibility after shrinking (prevents fall damage from collider resize)
  • Value-drop detection triggers bonk restore
  • ForceGrabPoint disabled to prevent grab oscillation

For items:

  • Effect fields auto-scaled (explosion size, orb radius, damage)
  • Inventory system compatibility (yields during equip/unequip)
  • ForceGrabPoint handling

For non-pocketable items (carts, cart cannon, cart laser, tracker):

  • Become pocketable while shrunken (press inventory key to stash)
  • Shrunken players can't pocket shrunken items. If you get shrunk while holding one, it drops
  • Restoring removes the equip ability so full-size items stay on the ground

For all types:

  • Audio pitch shifted on all Sound objects
  • Smooth scale animation with force-apply in LateUpdate
  • Multiplayer sync via Photon RPCs
  • Late-join state sync
  • Automatic cleanup on level change

Dependencies

Reference Implementation

ShrinkerGun: COMPRESSOR is a shrink ray gun built on ScalerCore. Shows how to build ScaleOptions, call Apply, and handle per-target-type durations.

CHANGELOG

Changelog

1.0.1

Fixed

  • Shrunken valuables no longer read as inside the extraction point when they physically aren't. The detection probe (RoomVolumeCheck) is a box built from a size (currentSize) and an offset to its center (CheckPosition); ScalerCore scaled the size on shrink but left the offset at full length, so the probe drifted off the shrunken body and could still overhang the extraction zone. The offset now scales with the factor alongside the size, captured once and put back on expand.

Internal

  • Dropped the Compatibility / RepoXR VR support config toggle. The bridge already no-ops unless RepoXR is installed and the player is in VR, both found by reflection at runtime, so the toggle was redundant gating. Back to a pure library with no user-facing config.

1.0.0

New

  • Dead Semibot heads can be scaled. Off by default and policy lives with the calling mod: ScalerCore exposes ScaleManager.AllowDeadHeads, ShrinkerGun binds the user-facing Targets / ShrinkDeadHeads setting. Heads scale like any other prop, minus pocketing (a pocketed head would fight the revive logic).
  • The shop radio is scalable now. It was the one grabbable prop in the truck the ray refused to touch.
  • Objects that re-broadcast a player's voice now pitch that voice with their own size. The teeth valuable, the walkie-talkie, and a dead Semibot head re-assert the voice override every frame while transmitting, so scaling the routing object now rides the routed voice: a shrunken teeth toy squeaks, a grown one rumbles, a shrunken dead head chipmunks its owner. Runs locally on every client off synced scale state, so everyone hears the same thing.

New (growth support rounded out)

  • Giant feel pass from playtesting: FOV shift for growth runs at 40% strength floored at -8 degrees (the linear curve hit -20 at 2x and kept going); head bob and footstep cadence follow body size instead of the speed change (CameraBob couples its cycle to the speed multiplier, and footsteps fire per bob cycle): long heavy stomps when big, quick patter when small; pupils go small and hard when big instead of inheriting the tiny-player dilation; growth footsteps land deeper (0.55x pitch).
  • Grown players hold grabbed items at proportional arm's length. The vanilla hold distance and look-target offset are flat world units that sit inside a giant's body, so an item rode half-buried in the chest. Both scale with the factor when grown; shrunken keeps the existing 0.7x hold and zeroed offset.
  • Enemy attack reach follows body size. The AI's "close enough to attack" distances were fixed numbers, so a grown Loom's own bulk kept players outside a reach check it could never satisfy, and shrunk enemies (the Clown among them) swung from distances their tiny bodies don't cover. The Loom's reach fields scale with its factor, and the Clown's hardcoded melee trigger gets the same treatment through an IL swap that reports loudly if a game update moves it. Both directions.
  • The Loom's hand-reach envelope scales too, not just the distance check. EnemyShadow clamps its wrists to a box built from marker transforms whose localScale never inherits the body's, so a grown Loom told to attack would reach and reach and the slap never fired, her hands penned in the vanilla envelope. The markers scale with the factor, captured and restored like everything else.
  • Grown enemies stop growing PHYSICALLY before they wedge in the level. New ScaleOptions.EnemyPhysicalFactorCap (1.4x in the Growth preset, 0 disables): past the cap the colliders and nav agent radius hold while the visuals, reach, audio, and mass keep climbing to Factor, so a giant still fits the doorways the navmesh was baked for instead of jamming in the geometry. Grow-only, enemies only; shrinking and other types ignore it.
  • Scaled enemies' explosions scale with them. Enemy ability code passes hardcoded numbers to the explosion spawner (a Bang head always went up at size 1, damage 30), so size, damage, and force now multiply by the enemy's factor at spawn, and the blast audio pitches with it. A grown Bang is a bassy BOOM; a shrunk one pops.
  • Grown things sound the part beyond pitch, and it all rides one knob: ScaleOptions.AudioPresence (0 = pitch only, 1 = full effect, default 1 in both presets). Volume lifts a touch, reverb gets a light lift (lean in too hard and the wet tail just buries the dry transient that reads as "big"), sound falloff scales with the factor so a giant carries across the level while a tiny one goes sneaky-quiet at range, and grown players' voice chat gets the same light-reverb-and-carry treatment (mic volume stays the player's own). Captured and restored with the rest of the audio state, and the knob rides the sync RPC so every client hears the same presence. Items are untouched, their explosion fields already scale.
  • ScaleOptions.Growth preset: twice the size, heavier, a touch faster, slower deliberate animation, low footsteps. Voice and entity sounds already deepen automatically from the factor; the preset covers the knobs that don't derive from it.
  • Pitch curves got two fixes. Entity and effect sounds use a shallower grow slope floored at 0.5: deep reads as "big" well before half pitch, and below that the highs are gone and everything turns to mud (the old linear curve also ran negative past 3x growth and inverted the audio). Voices get their own curve apart from the SFX one, with an intelligibility floor: a grown voice only drops to 0.8 (a growl can go deep, a callout still has to land), shrink keeps the chipmunk up to 2x and stays understandable. The teeth, walkie, and dead-head routing above ride the same voice curve.

Fixed

  • Every ScaleController RPC validates its sender now. Scale state (RPC_Shrink/RPC_Expand/pitch cancel) only accepts the master; client requests (manual scale, bonk expand, inverted reshrink) only accept the player who owns the view. Before this, anyone in the room could spoof a shrink at any controller.
  • Grown valuables stay heavy through the game's own mass resets. PhysGrabObject keeps a private massOriginal and writes it back to the rigidbody whenever an alter-mass episode ends, silently undoing the scaled mass mid-session: some grown props stopped feeling heavy while others that never hit that path stayed heavy. ScalerCore scales massOriginal alongside the live mass so the game's resets land on the scaled value, and puts the vanilla number back at expand.
  • A rescale mid-session (a grown object re-shot as shrunken, or the reverse) carries its per-session treatments across instead of stranding them. The audio treatment, mass, force-grab point, and item effect fields all re-derive from the new factor; before this a re-shot object kept its old giant audio and a grab point that stayed disabled, so it couldn't be picked up. The audio capture also restores any live treatment before re-capturing, so the already-treated values never get recorded as the originals.
  • The mass-drift diagnostics (per-physics-call logs with a stack walk) only arm with SCALERCORE_DIAG=1 in the environment. They were logging at Info for every scaled valuable.
  • Map collapse (the April 1st event) actually shows up for everyone now. The relay component only existed on the machine that fired the shot, so the start RPC arrived at a PhotonView with nothing listening and non-hosts saw nothing. It attaches on every client at PunManager.Awake.
  • Map collapse runs off Photon's synchronized clock instead of each client integrating its own deltaTime from whenever the RPC landed. Blink period, alarm pitch, and the scale curve are identical on every machine at every instant; late joiners fast-forward into the running collapse instead of missing it.
  • The collapse start RPC validates its sender (master only), and the request RPC checks the sender exists.
  • The CartSteer transpiler announces itself loudly when a game update breaks its pattern instead of silently disabling cart pull-distance scaling. Both failure paths (missing grabber local, no Lerp callsites) log what died and ask for a report.
  • Scale apply stays cheap on big hierarchies. The reflection field walk that finds Sound objects per component is cached by type (first encounter scans, the rest are a dictionary hit), so the whole apply runs in one frame without paying reflection across the tree every time. A slow apply (>=10ms) or slow sound pass (>=5ms) warns with the object name so any remaining hitch can be reported and narrowed down.

0.6.2

New

  • RepoXR (VR) compatibility. Shrinking a player in VR used to leave their headset viewpoint and hands full-size. RepoXR replaces the flat-screen camera ScalerCore steers with a head-tracked rig, so the camera and FOV nudges had nothing to act on. A shrunk VR player now drops to the right eye height, the hand rig scales with them, and room-scale walking covers proportionally less ground. RepoXR is found by reflection at runtime, so ScalerCore neither references nor depends on it and non-VR play is unchanged. New config toggle Compatibility / RepoXR VR support (default on) turns it off.

0.6.1

New

  • ScaleManager.UpdateOptions(GameObject, ScaleOptions) plus a ForceUpdateOptions variant that skips the lock check. Replaces the stored options on a live session so cart mods can retune RestoreSpeed / SuppressImpactFlash / SuppressCameraShake when a config slider moves mid-session, instead of reflecting into the private _options field. Pairs with the existing read-only CurrentOptions getter: read, mutate, pass back. Fields consumed once at dispatch (Factor, MassCap, BonkImmuneDuration) don't reapply retroactively, the next Apply picks them up.

0.6.0

New

  • Added ScaleOptions.RestoreSpeed, animation speed for the expand direction. 0 falls back to Speed.
  • Added ScaleOptions.SuppressImpactFlash, skips the impact flash on shrink/expand.
  • Added ScaleOptions.SuppressCameraShake, skips the camera shake on expand. Pair with SuppressImpactFlash for a silent restore.
  • Added ScaleOptions.SuppressVoicePitch, skips the audio pitch shift and per-frame voice chat overrides.
  • Added ScaleOptions.IgnoreBonkExpand, damage doesn't restore the controller. Gated inside DispatchExpandNow so every bonk path honors it (player, valuable, enemy, cosmetic).
  • Added ScaleOptions.RejectExternalApply plus ScaleManager.ForceApply / ScaleManager.ForceRestore. Opt-in lock so other mods' Apply/Restore/RestoreImmediate no-op on your controller; the Force* variants bypass.
  • Added ScaleController.CurrentOptions, read-only snapshot of the active session's options.

Bug fixes

  • Fixed Loom's arms when shrunken (finally). The IK solver (BotSystemSpringPoseAnimator) walks down a cached LimbChain.lenBind[] to place joints in world space, those values are full-size, the body wasn't. Idle path put joints outside the shrunken body, reach path left a gap between elbow mesh and the hand target. LoomVisualHandler now snapshots lenBind at Setup and writes orig * ratio every LateUpdate while shrunken, restoring the originals on expand.
  • Fixed shrunken players holding guns (or any forceGrabPoint item) at waist height instead of eye height. The game bakes a fixed -up*0.3 offset in StartGrabbingPhysObject that doesn't scale, the new postfix lifts the puller back proportionally to player size.
  • Fixed shrunken players ending up mis-sized after a rescale. Apply with a different factor on an already-scaled controller re-fired Handler.OnScale on non-host clients, which read already-scaled singleton values as the new originals and compounded to factor². ApplyLocalPlayerShrinkEffects and RestoreLocalPlayerShrinkEffects are now guarded by a LocalEffectsApplied flag.
  • Shrunken players who died and got revived stayed shrunken on everyone else's screen but looked normal on their own. The bonk-expand path normally cancels the shrink on damage, but the bonk-immunity timer blocks the expand for the duration of the shrink animation, so anything that kills you in that window (most often the kill plane after falling out of map while shrunken) puts you into the death sequence still shrunken, and revive doesn't resync the scale state. Host now auto-cancels the shrink on PlayerAvatar.PlayerDeathRPC. Inverted/challenge mode skipped so dying small in that mode stays small.

Internal

  • RPC payload extended for the new options (PackOpts slot 7, PackBools slots 2-6). Decoder length-guards every slot, old hosts can still drive new clients.
  • EnemyBonkPatch now calls ctrl.DispatchExpandNow() directly instead of going through ScaleManager.RestoreImmediate. Internal bonk paths bypass the RejectExternalApply lock, only external mod calls honor it. Matches what ValuableHandler and CosmeticHandler already did.
  • ScaleManager deduplication, the handler-type filter and lock check moved into IsTargetAllowed / IsLockedFromExternal helpers used by Apply/ApplyIfNotScaled/Restore/RestoreImmediate.
  • ScaleController deduplication, PlayImpactEffect(), PlayCameraShake(), and ResolveExpandSpeed() helpers replace the repeated gate + ternary that had been inlined at every dispatch site.

0.5.2

  • PlayerHandler.GetBaseGrabStats was guarding only one of three StatsManager upgrade dictionaries with ContainsKey before indexing all three. v0.4 (or some combination of mods + game state) leaves players with a strength entry but no range/throw entry, so the indexer threw KeyNotFoundException. The throw aborted OnRestore before RPC_PlayerPitchCancel could fire, killed ApplyLocalPlayerShrinkEffects partway through (camera/FOV/collision/grab range never scaled, so shrunken players looked tiny but played full-size), and inside CleanupAll's foreach it killed the whole loop after the first bad controller, leaving every player past that one stuck in shrunken state across level transitions. Switched to TryGetValue, missing entries default to 0 which matches the "no upgrades purchased" baseline.
  • Non-host clients didn't reset shrink state on level transitions. RunManager.ChangeLevel early-returns on non-host during gameplay, so the existing LevelChangePatch postfix never reached CleanupAll for them. Their OnUpdate kept re-asserting voice pitch every frame, overriding the cancel the host RPC'd in. Added a RunManagerPUN.UpdateLevelRPC postfix gated to non-host; that RPC fires on every client via AllBuffered, so the cleanup runs everywhere.

0.5.1

New v0.4 content support

  • Cosmetic boxes (CosmeticWorldObject) are shrinkable. They have PhysGrabObject but no ValuableObject or ItemAttributes, so neither the handler predicates nor the ScaleController attach patch matched, added a CosmeticHandler and extended AttachToValuablePatch to also pick up CosmeticWorldObject. Handler tracks NotValuableObject.healthCurrent for bonk-on-damage expand, same pattern ValuableHandler uses for dollar value.
  • Vehicles now actually shrink visually instead of leaving the mesh full-size with a tiny collider hidden inside. ItemVehicle.DeparentMesh runs meshTransform.SetParent(null, true) whenever a player sits in the vehicle. The visible mesh becomes a scene-root object that no longer inherits the vehicle's transform scale, while ItemVehicle drives meshTransform.position directly each frame. Shrinking the root therefore shrunk the colliders but left a normal-size mesh visibly floating around them; worse, the next time the game ran ReparentMesh (SetParent(originalParent, worldPositionStays: true)) Unity rewrote the child's localScale to 1/factor to preserve world scale, so when expand fired the mesh ended up at 1/factor times original size. Added a VehicleHandler that matches ItemVehicle ahead of ItemHandler and per-frame enforces meshTransform.localScale to track the intended world scale regardless of current parent state. Pocketing is picked up from the same path ItemHandler used.
  • Shrunken vehicles (ItemVehicle, ValuableArcticSnowBike) now drive at proportionally lower top speed instead of full speed on a tiny chassis. Scaling the transform but leaving maxSpeedKmh / bikeForwardSpeed at their full 100 / 10 values made a half-size car try to do 100 km/h with full-size forces. Felt like driving a brick on ice. Added vehicle speed-cap fields (maxSpeedKmh, softMaxSpeedKmh, maxForwardSpeed, maxReverseSpeed, hyperMaxSpeed, bikeForwardSpeed) to the existing reflection-based field-scaling pass; they're restored verbatim on expand. Vehicles stay pocketable via the same path they did under ItemHandler.

Bug fixes

  • Carts vanished on the second shrink. PocketHelper.CreateIconMaker was adding a SemiIconMaker component on an active GameObject, which fires OnEnable synchronously inside AddComponent, before we'd assigned iconCamera and renderTexture. OnEnable's if (renderTexture) branch skipped, renderTextureInstance stayed null, then the game's CreateIconFromRenderTexture NRE'd after teleporting the item to (-1000, -1000, -1000) for the render. The position-restore line never ran, the item fell out of world, the kill-zone destroyed it. The IconMaker is now created inactive, configured, then activated so OnEnable fires with everything in place.
  • Shrunken ItemVehicle.Semiscooter had no inventory icon (the small Semiscooter did). The icon-camera bounds calculation used item.GetComponentsInChildren<Renderer> on the vehicle root, which missed the deparented meshTransform and rendered an empty 5 KB PNG. Bounds now traverses meshTransform separately when it isn't a descendant of the root.
  • Vehicles could be pocketed while the player was shrunken (carts and items already blocked this). ShrunkEquipBlockPatch was checking CartHandler.State and ItemHandler.State for AddedEquippable but not VehicleHandler.State, vehicles fell through and the block didn't fire. Added the missing case.
  • Shrunken vehicles wouldn't steer. Throttle still applied, so they accelerated straight forward with no turn input response. ItemVehicle.UpdateSteering gates on DriverFullyMounted, which only becomes true once the player's tumble body comes within a hardcoded 0.05 world units of firstMountTransform. On a 0.4-scale vehicle that's basically inside the seat geometry; the player couldn't reach it, reachedFirstMount stayed false, steering clamped to 0. Postfixed the getter to return true when the vehicle has a seated player and is scaled.

0.5.0

Updated for R.E.P.O. v0.4

  • Rebuilt against the latest game release. 0.4.4 will not load on v0.4 (recompile is required because of internal type changes on the game side). All 22 patch points checked, 20 OK and 2 transpilers (PhysGrabCart.CartSteer, EnemyVision.Vision) verified intact.

Bug fixes

  • Map collapse messages, sirens, and the truck-arrival cascade ran way too fast in multiplayer. Every client was firing them locally; host-only now, so each fires exactly once and PUN broadcasts.
  • Final crush damage was stacking on every player in multiplayer. Every client was running the kill loop and PlayerHealth.Hurt routes through HurtRPC. Host-only now.

Improvements

  • Map collapse network sync moved from PhotonNetwork.RaiseEvent byte codes (198/199) to a [PunRPC] component piggybacked on PunManager, no more arbitrary 0-199 numbers that could collide with another mod.
  • Map collapse chat is more chaotic now: taxman reacts in emojis only, panic messages come from random players in the lobby, larger pool of lines, no immediate repeats.

0.4.4

Bug fixes

  • Fixed non-host clients running Dispatch methods (DispatchShrink, DispatchExpand, DispatchExpandNow), these are now gated behind a host/singleplayer check

0.4.3

New

  • MapCollapse is now public, other mods call MapCollapse.OnMapHit() to trigger the collapse event
  • ScaleController.ChallengeMode public property, implementations set this to enable challenge mode
  • Runtime SemiIconMaker generation for pocketed items, no more embedded PNGs, works for any item

Bug fixes

  • Fixed map collapse audio ignoring master volume (now routes through the game's SFX mixer group)
  • Fixed map collapse alarm doubling when the truck had no unique sounds
  • Fixed camera glitch effect not covering the full screen while shrunken
  • Fixed shrunken players getting crushed too early during map collapse (raycast distances now scale with player size)
  • Fixed pocketed item icons disappearing after level transitions
  • Map collapse crush sequence reworked, FOV slam, heavy shake, vignette, and a brief hold before death

Improvements

  • ScalerCore is now a pure library with no user-facing config entries
  • ShrinkChallengeMode and MapCollapse config removed, implementations own their settings
  • MapCollapseHitPatch removed, implementations provide their own hit detection
  • Map collapse enemy speed toned down (1.3x base, up to 1.8x, was 2.5x to 6.5x)
  • Map collapse no longer unshrinks everything when it starts
  • Map collapse FOV narrows during collapse for a claustrophobic feel
  • Embedded cart/cannon/laser icon PNGs replaced with runtime SemiIconMaker

0.4.2

Improvements

  • Added MapCollapse config option (Auto/On/Off). You'll know it when you see it. Turn it on and shoot the map, go ahead, I dare you.
  • No, Loom's arms still aren't fixed. I was busy with the super ultra important above feature for next year's april fools.
  • Removed Herobrine

0.4.1

New

  • Any non-pocketable item becomes pocketable while shrunken, carts, cart cannons, cart lasers, trackers. Press an inventory key to stash, shoot again to restore.
  • Shrunken players can't pocket shrunken items. If you get shrunk while carrying one, it drops automatically.

Bug fixes

  • Fixed shrunken players being able to wall-jump infinitely by touching walls
  • Fixed Shrink Challenge mode not working on clients (inverted re-shrink was blocked by debug key gate)
  • Fixed Shrink Challenge mode not working in singleplayer (was stuck waiting for voice chat)
  • Fixed Shrink Challenge mode firing in the lobby instead of waiting for the level to load
  • Fixed voice pitch not cleaning up when returning to lobby
  • Fixed camera occasionally clipping through walls at shrunken size
  • Pupil override priority and spring speed now match vanilla ranges
  • Items now stay shrunken permanently until shot again (was 5 minutes)
  • Enemy speed while shrunken is now 75% (was 65%)

Improvements

  • Shrink Challenge mode config changes apply instantly in the lobby
  • Shrunken items show as smaller dots on the map
  • Smoother pupil transition when expressions end
  • Embedded inventory icons for cart, cart cannon, and cart laser
  • Logging cleaned up, only warnings and errors in the console
  • InvertedMode synced to clients via RPC for proper multiplayer challenge mode

0.4.0

New

  • Added SuppressValueDropExpand option to ScaleOptions, valuables won't expand on damage while scaled. For cart mods where items bump into each other constantly.
  • Added PreserveMass option to ScaleOptions, rigidbody mass stays at its original value while scaled. For cart mods where items should weigh the same regardless of visual size.
  • Added ScaleManager.ApplyIfNotScaled(), scales only if not already scaled, no-op otherwise. Safe to call every frame from continuous triggers.
  • Added ScaleManager.GetController(), returns the ScaleController for a game object, resolving through PlayerShrinkLink.

Bug fixes

  • Fixed shrunken objects appearing full-size on non-host clients. The RPC was only sending the target vector without ScaleOptions fields, so clients had all-zero options and the animation never ran. Also broke late-join sync.
  • Fixed shrunken player mesh freezing in place on the host. AnimSpeedMultiplier wasn't synced to clients, so the client sent a zero-speed animation override back via RPC, killing the host's visual position interpolation.
  • Fixed players reverting to full size when another player jumped or tumbled into them. Bonk expand now only triggers when health actually decreases, not on zero-damage contact.

0.3.0

New

  • Shrink Challenge Mode: players start shrunken, guns temporarily grow you, damage shrinks you back
  • Shooting an already-shrunken target with the same factor toggles it back
  • Shooting an already-shrunken target with a different factor rescales it smoothly (no flash)

API changes

  • ScaleManager.Apply() now takes a ScaleOptions struct, per-call config replaces global ShrinkConfig
  • ScaleManager.Apply() without options uses ScaleOptions.Default
  • Added ScaleTargets flags enum for filtering what object types can be scaled
  • Added ScaleController.TargetType for mods to check what kind of object a controller manages
  • Removed ShrinkConfig and ScaleFactor (replaced by ScaleOptions)
  • Zero Factor/Speed in ScaleOptions falls back to defaults

Bug fixes

  • Fixed players in tumble/object mode (Q) not being shrinkable by guns
  • ScaleManager API now resolves PlayerShrinkLink when target GO doesn't have ScaleController directly
  • Fixed Tricycle (Bella) trike mesh not scaling, rider shrank but the bike stayed full size
  • Single doors now cleanly break off their hinges when shrunken instead of floating in place
  • Birthday Boy's balloons now shrink along with him
  • Enemies killed or despawned while shrunken no longer respawn at shrunken size
  • HeartHugger mesh and collision now align properly when shrunken (including when tipped)
  • HeartHugger gas pull distance scales with enemy size
  • Loom attack distance scales with enemy size
  • Fixed bonk expand not restoring visual scale on enemies with handler-owned scaling
  • Replaced most reflection with direct publicizer access for better performance
  • Fixed Loom (Shadow) NRE spam in EnemyShadow.HandLogic after unshrinking

0.2.0

Bug fixes

  • Pupils no longer stay huge after unshrinking
  • Animation speed resets properly on unshrink
  • Grab range actually scales down while shrunken now
  • Voice pitch no longer gets nuked by spewer/hourglass events
  • Menu preview shows big pupils while shrunken
  • Host now enforces grab stats for all shrunken players, not just local
  • Remote players see big pupils in the shop
  • Menu preview only shrinks for the shrunken player, not everyone
  • Non-host sees their own big pupils in menu preview
  • Non-host grab strength/range/throw restores properly after unshrinking
  • Big pupils yield to expressions while shrunken (no more bleeding through eyelids)
  • Cart pull distance no longer leaks the host's shrink state to other players
  • Shrunken enemies deal scaled damage across the board (mace swings, tumble impacts, instakills)
  • Enemies like Trudge whose mace has playerKill no longer instakill when shrunken
  • Damage scaling works even when the HurtCollider doesn't have enemyHost set
  • NavMesh agent radius scales with enemy size
  • Fixed Chef, Mentalist, Reaper, Trudge, and Elsa sinking into the ground when shrunken
  • Fixed Loom (Shadow) arms detaching from body when shrunken
  • Fixed AnimTarget discovery walking up to Enable container on enemies with renderers on the Rigidbody (caused double-scaling on Hearthugger and Loom)
  • Known: Hearthugger still has visual/grab misalignment when shrunken (cosmetic, gameplay unaffected)

Balance

  • Grab strength less punishing (1.5x scale factor, capped at 100% when shrunk)
  • Added MinimumStrength and MaximumStrength config options
  • Grab range and throw scale directly with size (no mercy bonus)
  • Enemy damage scales by shrink factor directly (was a flat 0.1x)
  • Enemy bonk immunity down from 5s to 3s
  • Items stay shrunk indefinitely (was 300s)

Improvements

  • Menu avatar animates smoothly when shrinking/unshrinking instead of snapping
  • Negative shrink durations from bad configs get clamped to 0
  • Version auto-stamped from csproj via BuildInfo

Internal

  • Assembly publicizer replaces all reflection in PlayerHandler
  • ItemHandler uses standard GetField instead of AccessTools
  • All enemy-to-player damage scaling lives in one patch now (KnockbackPatch)
  • Deduplicated grab strength formula into GetGrabFactors helper
  • Noisy item field logs downgraded to LogDebug
  • Dropped REPOLib dependency (wasn't actually used)
  • Updated Thunderstore description

0.1.0

Initial early access release.