Skip to content

Commit

Permalink
Merge branch 'development' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
1ForeverHD committed Aug 28, 2021
2 parents 650cf04 + 47b358d commit 9e7cc4b
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 41 deletions.
24 changes: 21 additions & 3 deletions docs/api/zone.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,16 @@

#### new
```lua
local zone = Zone.new(group)
local zone = Zone.new(container)
```
A group is used the define the boundaries of the zone. It can be any non-basepart instance (such as a Model, Folder, etc) that contain descendant baseparts. Alternatively a group can be a singular basepart instance, or a table containing an array of baseparts.
A container is used the define the boundaries of the zone. It can be any non-basepart instance (such as a Model, Folder, etc) that contain descendant baseparts. Alternatively a container can be a singular basepart instance, or a table containing an array of baseparts.

----
#### fromRegion
```lua
local zone = Zone.fromRegion(cframe, size)
```
Constructs a zone from the given CFrame and Size. Underneath the hood, it's creating a part (or multiple parts if any size coordinage exceeds 2024), parenting this to a folder (the container), constructing a zone with this container, calling ``:relocate()`` on that zone (which parents it outside of workspace), then finally returning the zone.

----

Expand Down Expand Up @@ -114,6 +121,13 @@ zone:setDetection(enumIdOrName)
```
Sets the precision of checks based upon the [Detection Enum]. Defaults to 'Automatic'.

----
#### relocate
```lua
zone:relocate()
```
Moves the zone outside of workspace into a separate WorldModel within ReplicatedStorage or ServerStorage. This action is irreversible - once called it cannot be undone.

----
#### destroy
```lua
Expand Down Expand Up @@ -262,12 +276,16 @@ When ``true``, will prevent the internal ``_update()`` from being called multipl
#### zoneParts
{read-only}

An array of baseparts, defined in the ``zoneGroup`` constructor parameter, that form the zone.
An array of baseparts, defined in the ``container`` constructor parameter, that form the zone.

----
#### region
{read-only}

----
#### volume
{read-only}

----
#### worldModel
{read-only}
20 changes: 20 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
## [3.1.0] - August 28 2021
### Added
- ``Zone.fromRegion(cframe, size)``
- ``zone:relocate()`` - Non-workspace zones are finally a possibility! Simply call this and the zones container will be moved into a WorldModel outside of Workspace.
- CollectiveWorldModel module
- ``zone.hasRelocated`` property
- ``zone.worldModel`` property
- ``zone.relocationContainer`` property
- ``CollectiveWorldModel.setupWorldModel(zone)``
- ``CollectiveWorldModel:GetPartBoundsInBox(cframe, size, overlapParams)``
- ``CollectiveWorldModel:GetPartBoundsInRadius(position, radius, overlapParams)``
- ``CollectiveWorldModel:GetPartsInPart(part, overlapParams)`

### Changed
- ``Zone.new(zoneGroup)`` to ``Zone.new(container)``
- ``zone.group`` property to ``zone.container``



--------
## [3.0.0] - August 27 2021
### Added
- ``Zone:trackItem(characterOrBasePart)``
Expand Down
6 changes: 3 additions & 3 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ Creating a zone is as simple as:
``` lua
-- Assuming we place ZonePlus in ReplicatedStorage
local Zone = require(game:GetService("ReplicatedStorage").Zone)
local zoneGroup = workspace.SafeZoneGroup
local zone = Zone.new(zoneGroup)
local container = workspace.SafeZoneContainer
local zone = Zone.new(container)
```

Zones take one argument: a **zoneGroup**. A zoneGroup can be any non-basepart instance (such as a Model, Folder, etc) that contain descendant [baseparts]. Alternatively a zoneGroup can be a singular basepart instance, or a table containing an array of baseparts.
Zones take one argument: a **container**. A container can be any non-basepart instance (such as a Model, Folder, etc) that contain descendant [baseparts]. Alternatively a container can be a singular basepart instance, or a table containing an array of baseparts.

!!! info
Zones are compatible with all basepart classes however it's recommended to use solely Blocks (i.e. Parts with Shape 'Block') when possible as these are better optimised (since only ``WorldRoot:GetPartBoundsInBox`` needs to be called instead of ``WorldRoot:GetPartsInPart``).
Expand Down
2 changes: 1 addition & 1 deletion src/Zone/VERSION.lua
Original file line number Diff line number Diff line change
@@ -1 +1 @@
-- v3.0.0
-- v3.1.0
47 changes: 47 additions & 0 deletions src/Zone/ZoneController/CollectiveWorldModel.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
local CollectiveWorldModel = {}
local worldModel
local runService = game:GetService("RunService")



-- FUNCTIONS
function CollectiveWorldModel.setupWorldModel(zone)
if worldModel then
return worldModel
end
local location = (runService:IsClient() and "ReplicatedStorage") or "ServerStorage"
worldModel = Instance.new("WorldModel")
worldModel.Name = "ZonePlusWorldModel"
worldModel.Parent = game:GetService(location)
return worldModel
end



-- METHODS
function CollectiveWorldModel:_getCombinedResults(methodName, ...)
local results = workspace[methodName](workspace, ...)
if worldModel then
local additionalResults = worldModel[methodName](worldModel, ...)
for _, result in pairs(additionalResults) do
table.insert(results, result)
end
end
return results
end

function CollectiveWorldModel:GetPartBoundsInBox(cframe, size, overlapParams)
return self:_getCombinedResults("GetPartBoundsInBox", cframe, size, overlapParams)
end

function CollectiveWorldModel:GetPartBoundsInRadius(position, radius, overlapParams)
return self:_getCombinedResults("GetPartBoundsInRadius", position, radius, overlapParams)
end

function CollectiveWorldModel:GetPartsInPart(part, overlapParams)
return self:_getCombinedResults("GetPartsInPart", part, overlapParams)
end



return CollectiveWorldModel
7 changes: 4 additions & 3 deletions src/Zone/ZoneController/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ local Janitor = require(script.Parent.Janitor)
local Enum_ = require(script.Parent.Enum)
local Signal = require(script.Parent.Signal)
local Tracker = require(script.Tracker)
local CollectiveWorldModel = require(script.CollectiveWorldModel)
local enum = Enum_.enums
local players = game:GetService("Players")
local activeZones = {}
Expand Down Expand Up @@ -323,7 +324,7 @@ function ZoneController._getZonesAndItems(trackerName, zonesDictToCheck, zoneCus
-- checks directly within each zone to determine players inside
for zone, _ in pairs(zonesDictToCheck) do
if not onlyActiveZones or zone.activeTriggers[trackerName] then
local result = workspace:GetPartBoundsInBox(zone.region.CFrame, zone.region.Size, tracker.whitelistParams)
local result = CollectiveWorldModel:GetPartBoundsInBox(zone.region.CFrame, zone.region.Size, tracker.whitelistParams)
local finalItemsDict = {}
for _, itemOrChild in pairs(result) do
local correspondingItem = tracker.partToItem[itemOrChild]
Expand Down Expand Up @@ -420,7 +421,7 @@ function ZoneController.getTouchingZones(item, onlyActiveZones, recommendedDetec
-- be the actual shape of the part.
local touchingPartsDictionary = {}
local zonesDict = {}
local boundParts = workspace:GetPartBoundsInBox(itemCFrame, itemSize, boundParams)
local boundParts = CollectiveWorldModel:GetPartBoundsInBox(itemCFrame, itemSize, boundParams)
local boundPartsThatRequirePreciseChecks = {}
for _, boundPart in pairs(boundParts) do
local correspondingZone = partToZoneDict[boundPart]
Expand Down Expand Up @@ -450,7 +451,7 @@ function ZoneController.getTouchingZones(item, onlyActiveZones, recommendedDetec
if not bodyPart:IsA("BasePart") or (itemIsCharacter and Tracker.bodyPartsToIgnore[bodyPart.Name]) then
continue
end
local preciseParts = workspace:GetPartsInPart(bodyPart, preciseParams)
local preciseParts = CollectiveWorldModel:GetPartsInPart(bodyPart, preciseParams)
for _, precisePart in pairs(preciseParts) do
if not touchingPartsDictionary[precisePart] then
local correspondingZone = partToZoneDict[precisePart]
Expand Down
114 changes: 83 additions & 31 deletions src/Zone/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ local ZonePlusReference = require(script.ZonePlusReference)
local referenceObject = ZonePlusReference.getObject()
local zoneControllerModule = script.ZoneController
local trackerModule = zoneControllerModule.Tracker
local collectiveWorldModelModule = zoneControllerModule.CollectiveWorldModel
local ZoneController = require(zoneControllerModule)
local referenceLocation = (game:GetService("RunService"):IsClient() and "Client") or "Server"
local referencePresent = referenceObject and referenceObject:FindFirstChild(referenceLocation)
Expand All @@ -29,15 +30,15 @@ Zone.enum = enum



-- CONSTRUCTOR
function Zone.new(group)
-- CONSTRUCTORS
function Zone.new(container)
local self = {}
setmetatable(self, Zone)

-- Validate group
local INVALID_TYPE_WARNING = "A zone group must be a model, folder, basepart or table!"
local groupType = typeof(group)
if not(groupType == "table" or groupType == "Instance") then
-- Validate container
local INVALID_TYPE_WARNING = "The zone container must be a model, folder, basepart or table!"
local containerType = typeof(container)
if not(containerType == "table" or containerType == "Instance") then
error(INVALID_TYPE_WARNING)
end

Expand All @@ -52,7 +53,7 @@ function Zone.new(group)
local janitor = Janitor.new()
self.janitor = janitor
self._updateConnections = janitor:add(Janitor.new(), "destroy")
self.group = group
self.container = container
self.zoneParts = {}
self.overlapParams = {}
self.region = nil
Expand All @@ -72,6 +73,7 @@ function Zone.new(group)
self.allZonePartsAreBlocks = true
self.trackedItems = {}
self.settingsGroupName = nil
self.worldModel = workspace

local checkerPart = janitor:add(Instance.new("Part"), "Destroy")
checkerPart.Size = Vector3.new(0.1, 0.1, 0.1)
Expand Down Expand Up @@ -149,6 +151,35 @@ function Zone.new(group)
return self
end

function Zone.fromRegion(cframe, size)
local MAX_PART_SIZE = 2024
local container = Instance.new("Model")
local function createCube(cubeCFrame, cubeSize)
if cubeSize.X > MAX_PART_SIZE or cubeSize.Y > MAX_PART_SIZE or cubeSize.Z > MAX_PART_SIZE then
local quarterSize = cubeSize * 0.25
local halfSize = cubeSize * 0.5
createCube(cubeCFrame * CFrame.new(-quarterSize.X, -quarterSize.Y, -quarterSize.Z), halfSize)
createCube(cubeCFrame * CFrame.new(-quarterSize.X, -quarterSize.Y, quarterSize.Z), halfSize)
createCube(cubeCFrame * CFrame.new(-quarterSize.X, quarterSize.Y, -quarterSize.Z), halfSize)
createCube(cubeCFrame * CFrame.new(-quarterSize.X, quarterSize.Y, quarterSize.Z), halfSize)
createCube(cubeCFrame * CFrame.new(quarterSize.X, -quarterSize.Y, -quarterSize.Z), halfSize)
createCube(cubeCFrame * CFrame.new(quarterSize.X, -quarterSize.Y, quarterSize.Z), halfSize)
createCube(cubeCFrame * CFrame.new(quarterSize.X, quarterSize.Y, -quarterSize.Z), halfSize)
createCube(cubeCFrame * CFrame.new(quarterSize.X, quarterSize.Y, quarterSize.Z), halfSize)
else
local part = Instance.new("Part")
part.CFrame = cubeCFrame
part.Size = cubeSize
part.Anchored = true
part.Parent = container
end
end
createCube(cframe, size)
local zone = Zone.new(container)
zone:relocate()
return zone
end



-- PRIVATE METHODS
Expand Down Expand Up @@ -238,30 +269,30 @@ function Zone:_displayBounds()
end

function Zone:_update()
local group = self.group
local container = self.container
local zoneParts = {}
local updateQueue = 0
self._updateConnections:clean()

local groupType = typeof(group)
local containers = {}
local INVALID_TYPE_WARNING = "A zone group must be a model, folder, basepart or table!"
if groupType == "table" then
for _, part in pairs(group) do
local containerType = typeof(container)
local holders = {}
local INVALID_TYPE_WARNING = "The zone container must be a model, folder, basepart or table!"
if containerType == "table" then
for _, part in pairs(container) do
if part:IsA("BasePart") then
table.insert(zoneParts, part)
end
end
elseif groupType == "Instance" then
if group:IsA("BasePart") then
table.insert(zoneParts, group)
elseif containerType == "Instance" then
if container:IsA("BasePart") then
table.insert(zoneParts, container)
else
table.insert(containers, group)
for _, part in pairs(group:GetDescendants()) do
table.insert(holders, container)
for _, part in pairs(container:GetDescendants()) do
if part:IsA("BasePart") then
table.insert(zoneParts, part)
else
table.insert(containers, part)
table.insert(holders, part)
end
end
end
Expand Down Expand Up @@ -289,8 +320,8 @@ function Zone:_update()
zonePartsIgnorelist.FilterDescendantsInstances = zoneParts
self.overlapParams.zonePartsIgnorelist = zonePartsIgnorelist

-- this will call update on the zone when a group parts size or position changes, and when a
-- child is removed or added from a container (anything which isn't a basepart)
-- this will call update on the zone when the container parts size or position changes, and when a
-- child is removed or added from a holder (anything which isn't a basepart)
local function update()
if self.autoUpdate then
local executeTime = os.clock()
Expand Down Expand Up @@ -318,10 +349,10 @@ function Zone:_update()
self._updateConnections:add(part:GetPropertyChangedSignal(prop):Connect(update), "Disconnect")
end
end
local groupEvents = {"ChildAdded", "ChildRemoved"}
for _, container in pairs(containers) do
for _, event in pairs(groupEvents) do
self._updateConnections:add(self.group[event]:Connect(function(child)
local containerEvents = {"ChildAdded", "ChildRemoved"}
for _, holder in pairs(holders) do
for _, event in pairs(containerEvents) do
self._updateConnections:add(self.container[event]:Connect(function(child)
if child:IsA("BasePart") then
update()
end
Expand All @@ -347,7 +378,7 @@ function Zone:_update()
-- and maxPartsAddition. This ultimately optimises region checks as they can be generated with
-- minimal MaxParts (i.e. recommendedMaxParts can be used instead of math.huge every time)
--[[
local result = workspace:FindPartsInRegion3(region, nil, math.huge)
local result = self.worldModel:FindPartsInRegion3(region, nil, math.huge)
local maxPartsBaseline = #result
self.recommendedMaxParts = maxPartsBaseline + self.maxPartsAddition
--]]
Expand Down Expand Up @@ -553,8 +584,8 @@ end

function Zone:findPart(part)
local methodName, args = self:_getRegionConstructor(part, self.overlapParams.zonePartsWhitelist)
local touchingZoneParts = workspace[methodName](workspace, unpack(args))
--local touchingZoneParts = workspace:GetPartsInPart(part, self.overlapParams.zonePartsWhitelist)
local touchingZoneParts = self.worldModel[methodName](self.worldModel, unpack(args))
--local touchingZoneParts = self.worldModel:GetPartsInPart(part, self.overlapParams.zonePartsWhitelist)
if #touchingZoneParts > 0 then
return true, touchingZoneParts
end
Expand All @@ -568,8 +599,8 @@ function Zone:findPoint(positionOrCFrame)
end
self.checkerPart.CFrame = cframe
local methodName, args = self:_getRegionConstructor(self.checkerPart, self.overlapParams.zonePartsWhitelist)
local touchingZoneParts = workspace[methodName](workspace, unpack(args))
--local touchingZoneParts = workspace:GetPartsInPart(self.checkerPart, self.overlapParams.zonePartsWhitelist)
local touchingZoneParts = self.worldModel[methodName](self.worldModel, unpack(args))
--local touchingZoneParts = self.worldModel:GetPartsInPart(self.checkerPart, self.overlapParams.zonePartsWhitelist)
if #touchingZoneParts > 0 then
return true, touchingZoneParts
end
Expand Down Expand Up @@ -609,7 +640,7 @@ function Zone:getParts()
end
return partsArray
end
local partsInRegion = workspace:GetPartBoundsInBox(self.region.CFrame, self.region.Size, self.overlapParams.zonePartsIgnorelist)
local partsInRegion = self.worldModel:GetPartBoundsInBox(self.region.CFrame, self.region.Size, self.overlapParams.zonePartsIgnorelist)
for _, part in pairs(partsInRegion) do
if self:findPart(part) then
table.insert(partsArray, part)
Expand Down Expand Up @@ -730,6 +761,27 @@ function Zone:unbindFromGroup()
end
end

function Zone:relocate()
if self.hasRelocated then
return
end

local CollectiveWorldModel = require(collectiveWorldModelModule)
local worldModel = CollectiveWorldModel.setupWorldModel(self)
self.worldModel = worldModel
self.hasRelocated = true

local relocationContainer = self.container
if typeof(relocationContainer) == "table" then
relocationContainer = Instance.new("Folder")
for _, zonePart in pairs(self.zoneParts) do
zonePart.Parent = relocationContainer
end
end
self.relocationContainer = self.janitor:add(relocationContainer, "Destroy", "RelocationContainer")
relocationContainer.Parent = worldModel
end

function Zone:destroy()
self:unbindFromGroup()
self.janitor:destroy()
Expand Down

0 comments on commit 9e7cc4b

Please sign in to comment.