{"id":"breezy1214/zone2d","name":"zone2d","scope":"breezy1214","platform":"roblox","description":"Lightweight part-footprint zones with Entered/Exited signals, an optional height cap, and above-only mode.","version":"0.1.0","latest":"0.1.0","versions":["0.1.0"],"license":"MIT","licenseRating":"safe","licenseCaveats":[],"licenseVerified":true,"dependencies":{"howmanysmall/janitor":{"version":"^1.18.3","alias":"Janitor"},"sleitnick/signal":{"version":"^2.0.1","alias":"Signal"}},"integrity":"a642509ff6e295aeb38ce30c22c233418391f971efeda45363c0a9268e6b6a92","likes":0,"downloads":0,"install":"forest install breezy1214/zone2d","url":"https://forest.dev/p/roblox/breezy1214/zone2d","files":"https://api.forest.dev/ai/package/roblox/breezy1214/zone2d/files","readme":"# Zone2D\n\n[Documentation](https://breezy1214.github.io/Zone2D/)\n\nZone2D is a lightweight zone module for Roblox. It turns a `BasePart` into a zone by checking whether a point falls inside the part's X/Z footprint, measured in the part's own space so rotated parts work. By default the zone extends infinitely up and down. You can cap it with `MaxHeight` or restrict it to the space above the part with `AboveOnly`.\n\nTracked models are checked once per `Heartbeat` against their `PrimaryPart` position, and `Entered`/`Exited` fire on transitions. It does not use touch events or spatial queries.\n\n## Installation\n\nAdd the package to your [Wally](https://wally.run) manifest:\n\n```toml\n[dependencies]\nZone2D = \"breezy1214/zone2d@0.1.0\"\n```\n\n## Quick Start\n\n```lua\nlocal Zone2D = require(Path.To.Zone2D)\n\nlocal zone = Zone2D.new(workspace.Zones.Spawn, { MaxHeight = 50 })\nzone:TrackPlayers()\n\nzone.Entered:Connect(function(character: Model)\n\tprint(character.Name, \"entered\")\nend)\n\nzone.Exited:Connect(function(character: Model)\n\tprint(character.Name, \"left\")\nend)\n\n-- Later\nzone:Destroy()\n```\n\nZones are Janitor/Trove friendly because they expose `Destroy`:\n\n```lua\nlocal zone = janitor:Add(Zone2D.new(part))\n```\n\n## Options\n\n```lua\nexport type ZoneOptions = {\n\tMaxHeight: number?,\n\tAboveOnly: boolean?,\n}\n```\n\n| Option | Default | Effect |\n| --- | --- | --- |\n| `MaxHeight` | `nil` (unbounded) | Maximum vertical distance from the part's center, in the part's local Y. |\n| `AboveOnly` | `false` | Points below the part's center are outside. Combined with `MaxHeight`, the zone spans `0..MaxHeight` above the center. |\n\n## API\n\n### `Zone2D.new(part: BasePart, options: ZoneOptions?): Zone2D`\n\nCreates a zone from `part` and starts the per-`Heartbeat` update. The part's size is read once at construction. While the part is not a descendant of `workspace`, updates are paused.\n\n### `Zone2D.IsPointInside(part: BasePart, position: Vector3, options: ZoneOptions?): boolean`\n\nA static check that doesn't create a zone. Use it for one-off tests.\n\n### `zone:IsInside(position: Vector3): boolean`\n\nReturns whether `position` is inside this zone, using its options.\n\n### `zone:TrackModel(model: Model)`\n\nStarts tracking `model`. Detection begins once the model has a `PrimaryPart`. A model that leaves `workspace` is untracked automatically, and `Exited` fires if it was inside.\n\n### `zone:UntrackModel(model: Model)`\n\nStops tracking `model`. Fires `Exited` if it was inside.\n\n### `zone:TrackPlayers()`\n\nTracks every current and future player character, and untracks characters on `CharacterRemoving` or `PlayerRemoving`.\n\n### `zone:GetModelsInside(): { Model }`\n\nReturns the tracked models that are currently inside.\n\n### `zone.Entered: Signal<Model>` / `zone.Exited: Signal<Model>`\n\nThese fire when a tracked model's `PrimaryPart` crosses into or out of the zone.\n\n### `zone:Destroy()`\n\nDisconnects everything, destroys the signals, and clears tracked state. It is safe to call more than once.\n\n## License\n\nMIT\n","readmeTruncated":false}