{"id":"khanpython/dispense-prop","name":"dispense-prop","scope":"khanpython","platform":"roblox","description":"System for physically dispensing and attracting in-game props towards a target.","version":"5.1.0","latest":"5.1.0","versions":["1.0.0","1.1.0","1.1.1","2.1.1","2.2.1","2.3.1","2.4.1","2.5.1","2.5.2","3.0.0","3.1.0","3.2.0","3.3.0","4.0.0","4.1.0","4.2.0","4.3.0","5.0.0","5.1.0"],"license":"MIT","licenseRating":"safe","licenseCaveats":[],"licenseVerified":true,"dependencies":{},"integrity":"dc2b301c47c73667e63bcacd685e3ff4e3ce98826c33d62e19f45756ec185c80","likes":0,"downloads":0,"install":"forest install khanpython/dispense-prop","url":"https://forest.dev/p/roblox/khanpython/dispense-prop","files":"https://api.forest.dev/ai/package/roblox/khanpython/dispense-prop/files","readme":"<div align=\"center\">\n\t<h1>Prop Dispenser</h1>\n    <p>System for physically dispensing and attracting in-game props towards a target.</p>\n     <img src=\"https://media.giphy.com/media/mt6Ct3gpcAOVzwk95i/giphy.gif\" width=\"350\" height=\"350\" alt=\"Prop Dispenser Demo\">\n</div>\n\n\n---\n\n### Features:\n\n- **Customizable Prop Spawning:** Spawn a specified number of props from a given origin with configurable settings.\n- **Automatic Prop Removal:** Optionally remove props automatically after a configurable duration.\n- **Sigmoid Attraction:** Props are attracted towards a target using a smooth sigmoid easing curve over a configurable duration.\n- **Dynamic Timing:** `AttractDelay` and `AttractDuration` accept a fixed number, a `Vector2` range for per-prop randomization, or a custom function.\n- **Batch Movement:** All attracted props are moved in a single `workspace:BulkMoveTo` call each frame for minimal overhead.\n- **SoA Data Layout:** Internal state uses flat, cache-friendly Structure of Arrays instead of nested dictionaries.\n- **Event Callbacks:** Attach custom logic to prop spawn, removal, and group completion events.\n\n---\n\n### Installation via Wally:\n\n1. Ensure you have the [Wally package manager](https://github.com/UpliftGames/wally) installed on your system.\n2. Add the following line to your `wally.toml` file under the `[dependencies]` section:\n   ```toml\n   dispense-prop = \"khanpython/dispense-prop@5.1.0\"\n   ```\n3. Run the Wally install command to download and integrate the package:\n    ```bash\n    wally install\n    ```\n4. The package will be placed in your Packages folder. Use the following code snippet to require it in your project:\n    ```lua\n    local PropManager = require(path-to-package)\n    ```\n\n---\n\n### Parameters:\n\n#### `PropManager:Start(amount, originCFrame, propTemplate, targetInstance, settings)`\n\n| Parameter        | Type            | Description                                                                 |\n|------------------|-----------------|-----------------------------------------------------------------------------|\n| `amount`         | `number`        | The number of props to spawn. Must be an integer >= 1.                     |\n| `originCFrame`   | `CFrame`        | The starting position for the props.                                       |\n| `propTemplate`   | `Model or BasePart`| A template instance to clone for each prop.                                |\n| `targetInstance` | `Model or BasePart`| The target instance towards which props may be attracted.                  |\n| `settings`       | `table`         | A table of configuration options for the props. See details below.         |\n\n#### `settings` Table\n\n`DynamicNumber` = `number | Vector2 | (propIndex: number) -> number`\n\n| Key               | Type             | Default      | Description                                                                 |\n|--------------------|------------------|--------------|-----------------------------------------------------------------------------|\n| `RemoveMagnitude` | `number?`        | `1`          | The distance within which a prop is removed (claimed).                      |\n| `AttractMagnitude`| `number?`        | `nil`        | The range within which props begin attraction. `nil` means attract from any distance. |\n| `AttractDuration` | `DynamicNumber?` | `1.5`        | How long (seconds) the sigmoid attraction takes to complete.                |\n| `AttractDelay`    | `DynamicNumber?` | `3`          | Time delay before a prop becomes live and can be attracted/claimed.         |\n| `AutoRemoveTime`  | `number?`        | `nil`        | Time in seconds before props are automatically removed.                     |\n| `CollisionGroup`  | `string?`        | `Props`      | The collision group assigned to props.                                      |\n| `OnSpawn`         | `function`       | required     | A function executed when a prop is spawned.                                 |\n| `OnRemoved`       | `function?`      | `nil`        | A function executed when a prop is removed. Receives `true` if forced (auto-remove/external destroy). |\n| `OnAllRemoved`    | `function?`      | `nil`        | A function executed when all props in a group are removed.                  |\n\n#### DynamicNumber\n\n`AttractDelay` and `AttractDuration` accept a `DynamicNumber`, which resolves per-prop at spawn time. This lets you stagger or randomize timing across a group.\n\n| Form | Behavior | Example |\n|------|----------|---------|\n| `number` | Same value for every prop | `AttractDelay = 2` |\n| `Vector2` | Random value in `[min, max]` (order doesn't matter) | `AttractDuration = Vector2.new(1, 3)` |\n| `function` | Called with the prop's 1-based index in the group | `AttractDelay = function(i) return i * 0.15 end` |\n\n```lua\n-- Fixed: all props attract after 2 seconds\nAttractDelay = 2\n\n-- Random: each prop gets a random delay between 1 and 4 seconds\nAttractDelay = Vector2.new(1, 4)\n\n-- Staggered: prop 1 waits 0.15s, prop 2 waits 0.3s, etc.\nAttractDelay = function(propIndex)\n    return propIndex * 0.15\nend\n```\n\n---\n\n### Example Usage:\n\n```lua\nlocal PropDispenser = require(path-to-package)\n\nlocal settings = {\n    RemoveMagnitude = 2,\n    AttractMagnitude = 20,\n    AttractDuration = Vector2.new(1, 2), -- random 1-2s per prop\n    AttractDelay = 3,\n    AutoRemoveTime = 30,\n    CollisionGroup = \"Props\",\n\n    --! Important: Avoid yielding any of the callbacks. `OnSpawn` could be an exception, though it may lead to unexpected results.\n    OnSpawn = function(prop)\n        print(\"Spawned prop:\", prop)\n\n        -- You must manually parent the prop\n        prop.Parent = workspace\n\n        local randomX = math.random(-10, 10)\n        local randomY = math.random(5, 10)\n        local randomZ = math.random(-10, 10)\n\n        -- Your `spill` logic\n        task.defer(function()\n            prop.PrimaryPart:ApplyImpulse(\n                Vector3.new(randomX, randomY, randomZ)\n            )\n        end)\n    end,\n    OnRemoved = function(wasForced)\n        print(\"Prop removed.\")\n\n        --[[\n            * Triggered whenever a single prop is removed.\n            * `wasForced` is `true` if the removal was due to auto-remove, external destruction,\n              or the target leaving. It is `nil`/falsy when the prop was claimed normally.\n        ]]\n    end,\n    OnAllRemoved = function()\n        print(\"All props removed.\")\n\n        --[[\n            * Triggered when every prop in the group has been removed,\n              regardless of how each individual removal occurred.\n        ]]\n    end,\n}\n\nPropDispenser:Start(\n    10, -- Number of props\n    CFrame.new(0, 10, 0), -- Origin position\n    workspace.PropTemplate, -- Template prop instance\n    workspace.Target, -- Target instance\n    settings -- Settings table\n)\n```\n---\n","readmeTruncated":false}