diff --git a/Getting Started/Modifiers/index.html b/Getting Started/Modifiers/index.html index c727bb7..ebfadfd 100755 --- a/Getting Started/Modifiers/index.html +++ b/Getting Started/Modifiers/index.html @@ -589,20 +589,23 @@

Example

local ProjectileModifier; if Character:GetAttribute("ExtraPenetration") then ProjectileModifier = { - Power = 200 - } - end - - local Time = workspace:GetServerTimeNow() - - --> Replicate to the server - SimulateEvent:FireServer(Origin, Direction, Time) + Power = 200, + Extra = { + Weapon = "M16" + } + } + end + + local Time = workspace:GetServerTimeNow() - --> Cast the projectile within our own simulation - SecureCast.Cast(Player, "Bullet", Origin, Direction, Time, nil, ProjectileModifier) -end) - -... + --> Replicate to the server + SimulateEvent:FireServer(Origin, Direction, Time) + + --> Cast the projectile within our own simulation + SecureCast.Cast(Player, "Bullet", Origin, Direction, Time, nil, ProjectileModifier) +end) + +...

Example server simulation with modifiers
 1
  2
@@ -621,7 +624,10 @@ 

Example

15 16 17 -18
...
+18
+19
+20
+21
...
 
 ReplicatedStorage.Events.Simulate.OnServerEvent:Connect(function(Player: Player, Origin: Vector3, Direction: Vector3, Timestamp: number)
     ...
@@ -630,15 +636,18 @@ 

Example

if Character:GetAttribute("ExtraPenetration") then ProjectileModifier = { Power = 200 - } - end - - --> Replicate the projectile to all other clients - SimulateEvent:FireAllClients(Player, "Bullet", Origin, Direction, nil, ProjectileModifier) + Extra = { + Weapon = "M16" + } + } + end - --> Cast the projectile within our own simulation - SecureCast.Cast(Player, "Bullet", Origin, Direction, Time - Latency - Interpolation, nil, ProjectileModifier) -end) + --> Replicate the projectile to all other clients + SimulateEvent:FireAllClients(Player, "Bullet", Origin, Direction, nil, ProjectileModifier) + + --> Cast the projectile within our own simulation + SecureCast.Cast(Player, "Bullet", Origin, Direction, Time - Latency - Interpolation, nil, ProjectileModifier) +end)
diff --git a/search/search_index.json b/search/search_index.json index f48a52f..7c76487 100755 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Secure-Cast","text":"

A server-authoritative projectile system for ROBLOX.

"},{"location":"#what-is-it","title":"What is it?","text":"

SecureCast is aimed at providing an easy solution to server authoritative projectile using the ROBLOX humanoid system.

"},{"location":"#features","title":"Features","text":""},{"location":"#getting-started","title":"Getting Started","text":"

Head over to the installation page to get started with SecureCast.

"},{"location":"#license","title":"License","text":"

SecureCast is licensed under the MIT License.

"},{"location":"API/definitions/","title":"Definitions","text":""},{"location":"API/definitions/#definitions","title":"Definitions","text":"

This page contains all types defined and used by SecureCast.

type Modifier ( number Loss, number Power, number Angle, number Gravity, number Velocity, number Lifetime, BindableEvent? OnImpact, BindableEvent? OnDestroyed, BindableEvent? OnIntersection, RaycastParams? RaycastFilter, {[string]: any}? Extra, )

Modifiers are a powerful tool that allows you to define per cast functionality seperate from the base projectile definition.

Custom event handling
local Bindable = Instance.new(\"BindableEvent\")\nBindable.Event:Connect(function(Type: string, Event: string, ...)\n    if Event == \"OnDestroyed\" then\n        --> No need to keep a reference to the connection \n        --> since destroy will take care of it for us\n        Bindable:Destroy()\n    elseif Event == \"OnImpact\" then\n\n    elseif Event == \"OnIntersection\" then\n\n    end\nend)\n\nlocal Modifier = {\n    OnImpact = Bindable,\n    OnDestroyed = Bindable,\n    OnIntersection = Bindable,\n}\n\nSecureCast.Cast(Player, \"Bullet\", Origin, Direction, Time, nil, Modifier)\n

Danger

When using Modifiers make sure that all clients and the server use the same modifier, improper modifier usage can result in simulation desync.

type Definition ( number Loss, number Power, number Angle, number Gravity, number Velocity, number Lifetime, BindableEvent? Output, RaycastParams? RaycastFilter, void OnImpact ( Player Caster, Vector3 Direction, Instance Instance, Vector3 Normal, Vector3 Position, Enum.Material Material {[string]: any}? Extra, \u2003), void OnDestroyed ( Player Caster, Vector3 Position {[string]: any}? Extra, \u2003), void OnIntersection ( Player Caster, Vector3 Direction, string Part, Player? Victim, Vector3 Position Model? VictimCharacter, {[string]: any}? Extra, \u2003), )

This type represents the way that projectiles should be defined within the system. Refer to the template bullet included within the GitHub repository for an example.

type Record ( {CFrame} Parts, Vector3 Position, )

type Snapshot ( number Time, Voxels.Grid Grid, {[Player]: Record} Records, )

"},{"location":"API/securecast/","title":"SecureCast","text":""},{"location":"API/securecast/#securecast","title":"SecureCast","text":"

SecureCast is the top-level interface for interacting with the system. It is returned by require(SecureCast).

"},{"location":"API/securecast/#members","title":"Members","text":"

Utility Snapshots

The snapshots utility used by the simulation, you can use this to retrieve player positions back in time for lag compensation. The API for this utility is available here.

Warning

Snapshots can only be used on the server, calling any of the methods within the utility will throw an error when called from the client!

"},{"location":"API/securecast/#methods","title":"Methods","text":"

void Initialize ( )

Initialize the simulation for the current context.

Warning

Initialize can only be called once per context, subsequent calls will result in an error.

void Cast ( Player Caster, string Type, Vector3 Origin Vector3 Direction number Timestamp PVInstance? PVInstance Modifier? Modifier )

Casts a new projectile. A Modifier can be used to modify the behaviour of a projectile on a per cast basis. A PVInstance must exist on the client in order for the projectile to be rendered.

Danger

When using a Modifier make sure that all clients and the server use the same modifier, improper modifier usage can result in simulation desync.

"},{"location":"API/settings/","title":"Settings","text":""},{"location":"API/settings/#settings","title":"Settings","text":"

This page contains all settings used by SecureCast. These can be found in the Settings module under the SecureCast module.

number VoxelSize

The size of each voxel in studs.

Vector3 VoxelGridSize

The size of the voxel grid in studs.

Danger

Be careful when editing this value, anything smaller than the playable area will result in players being missed by the server raycasts.

number SnapshotLifetime

The lifetime of snapshots in seconds.

Danger

Be careful when editing this value, very small values will result in players with high ping not being able to land shots but high values may result in players being hit behind cover long after they have gone behind it.

Instance Definitions

The container for projectile definitions modules.

{[Enum.Material]: number} SurfaceHardness

An array of each materials hardness. The needed penetration power can be calculate with the following formula: Power = SurfaceDepth * SurfaceHardness

number RicochetHardness

The minimum surface hardness needed for a projectile to ricochet off of something. This is ignored when a projectile has a ricochet angle set to math.pi * 2.

{string} Parts

An array containing the names of every hitbox in a players character, ordered from most to least damage.

{Vector3} PartsSizes

An array containing the halved sizes of every hitbox in a players character, in the same order as the Parts array.

Vector3 HitboxSize

The maximum halved size of a players character, it needs to contain the character at it's maximum arm span.

"},{"location":"API/snapshots/","title":"Snapshots","text":""},{"location":"API/snapshots/#snapshots","title":"Snapshots","text":"

The snapshots utility is used by the simulation to take \"snapshots\" of player positions in the past for lag compensation.

Note

Times must be retrieved using workspace:GetServerTimeNow()

"},{"location":"API/snapshots/#methods","title":"Methods","text":"

{[string]: CFrame}? GetPlayerAtTime ( Player Player number Time )

Returns an array containing the CFrame of each of the player's hitboxes in the past, returns nothing when no snapshots containing the player can be found.

{[Player]: {[string]: CFrame}} GetPlayersAtTime ( number Time )

Returns an array containing the CFrame of each of the hitboxes of every player in the past.

Snapshot?, Snapshot?, number? GetSnapshotsAtTime ( number Time )

Returns a tuple containing the previous snapshot, next snapshot and the fraction used for lerping between them, returns nothing if no snapshots can be found for the given time.

void CreatePlayersSnapshot ( number Time )

Creates a snapshot of every players hitbox at the given time.

Danger

This method should not be called or it may result in undefined behaviour, this is already called by SecureCast internally.

"},{"location":"Getting%20Started/Installation/","title":"Installation","text":"

Installing the module into your place is simple, we will cover the different methods of installation down below.

"},{"location":"Getting%20Started/Installation/#with-wally","title":"With Wally","text":"

SecureCast is available as a Wally package. Navigate to your projects wally.toml file and add the following dependancy

securecast = \"1axen/secure-cast\"\n
After adding SecureCast to your dependencies you will need to install it by running
wally install\n

"},{"location":"Getting%20Started/Installation/#with-git","title":"With git","text":"

SecureCast can be directly cloned from GitHub

git clone https://github.com/1Axen/Secure-Cast.git\n

Warning

The master branch of the GitHub repository contains the latest development version and may not be stable.

"},{"location":"Getting%20Started/Installation/#with-github-releases","title":"With GitHub releases","text":"

The latest stable version of the module can be downloaded from https://github.com/1Axen/Secure-Cast/releases After downloading the RBXM file insert it into ReplicatedStorage or any other shared container of your choice.

"},{"location":"Getting%20Started/Modifiers/","title":"Modifiers","text":"

Modifiers are a powerful tool that allows you to define per cast functionality seperate from the base projectile definition. They can be used to alter already existing projectile definitions for example: Giving a grenade extra velocity for jump/run throws.

Danger

When using Modifiers make sure that all clients and the server use the same modifier, improper modifier usage can result in simulation desync.

"},{"location":"Getting%20Started/Modifiers/#example","title":"Example","text":"

I will be using the simulation setup from the previous page, if you haven't setup your simulaton yet refer to the previous page. In this example we will create a modifier that gives a bullet extra penetrative power, this modifier will be controlled by an attribute \"ExtraPenetration\" in the players character. Example client simulation with modifiers

...\n\nUserInputService.InputBegan:Connect(function(Input, GPE)\n    ...\n\n    local ProjectileModifier;\n    if Character:GetAttribute(\"ExtraPenetration\") then\n        ProjectileModifier = {\n            Power = 200\n        }\n    end\n\n    local Time = workspace:GetServerTimeNow()\n\n    --> Replicate to the server\n    SimulateEvent:FireServer(Origin, Direction, Time)\n\n    --> Cast the projectile within our own simulation\n    SecureCast.Cast(Player, \"Bullet\", Origin, Direction, Time, nil, ProjectileModifier)\nend)\n\n...\n

Example server simulation with modifiers
...\n\nReplicatedStorage.Events.Simulate.OnServerEvent:Connect(function(Player: Player, Origin: Vector3, Direction: Vector3, Timestamp: number)\n    ...\n\n    local ProjectileModifier;\n    if Character:GetAttribute(\"ExtraPenetration\") then\n        ProjectileModifier = {\n            Power = 200\n        }\n    end\n\n    --> Replicate the projectile to all other clients\n    SimulateEvent:FireAllClients(Player, \"Bullet\", Origin, Direction, nil, ProjectileModifier)\n\n    --> Cast the projectile within our own simulation\n    SecureCast.Cast(Player, \"Bullet\", Origin, Direction, Time - Latency - Interpolation, nil, ProjectileModifier)\nend)\n
"},{"location":"Getting%20Started/Simulation/","title":"Simulation","text":"

In this section we will explore setting up a basic simulation along side the different things we must account for in order to create a working simulation.

"},{"location":"Getting%20Started/Simulation/#setting-up-your-workspace","title":"Setting up your workspace","text":"

SecureCast requires a Map and Characters folder placed within workspace to function correctly. All parts of the map must be a descendant of the Map folder. All player characters must be paranted to the Characters folder.

We will also need an Events folder within ReplicatedStorage. Add a RemoteEvent named Simulation under the Events folder.

"},{"location":"Getting%20Started/Simulation/#setting-up-your-client","title":"Setting up your client","text":"

Create a LocalScript and place it under StarterPlayerScripts. Copy and paste the following code into the script you just created: Example client simulation

--!strict\n\nlocal Players = game:GetService(\"Players\")\nlocal ReplicatedStorage = game:GetService(\"ReplicatedStorage\")\nlocal UserInputService = game:GetService(\"UserInputService\")\n\nlocal SecureCast = require(ReplicatedStorage.SecureCast)\n\nlocal Player = Players.LocalPlayer\nlocal Mouse = Player:GetMouse()\n\nlocal Events = ReplicatedStorage.Events\nlocal SimulateEvent = Events.Simulate\n\n--> Only call once per context\nSecureCast.Initialize()\n\nUserInputService.InputBegan:Connect(function(Input, GPE)\n    if GPE or Input.UserInputType ~= Enum.UserInputType.MouseButton1 then\n        return\n    end\n\n    local Character = Player.Character\n    local Head = Character and Character:FindFirstChild(\"Head\")\n    if not Head then\n        return\n    end\n\n    local Origin = Head.Position\n    local Direction = (Mouse.Hit.Position - Origin).Unit\n\n    local Time = workspace:GetServerTimeNow()\n\n    --> Replicate to the server\n    SimulateEvent:FireServer(Origin, Direction, Time)\n\n    --> Cast the projectile within our own simulation\n    SecureCast.Cast(Player, \"Bullet\", Origin, Direction, Time)\nend)\n\nSimulateEvent.OnClientEvent:Connect(function(Caster: Player, Type: string, Origin: Vector3, Direction: Vector3, PVInstance: PVInstance?, Modifer)\n    if Caster ~= Player then\n        SecureCast.Cast(Caster, Type, Origin, Direction, workspace:GetServerTimeNow(), PVInstance, Modifer)\n    end\nend)\n

"},{"location":"Getting%20Started/Simulation/#setting-up-your-server","title":"Setting up your server","text":"

Create a Script and place it under ServerScriptService. Copy and paste the following code into the script you just created: Example server simulation

--!strict\n\nlocal Players = game:GetService(\"Players\")\nlocal ReplicatedStorage = game:GetService(\"ReplicatedStorage\")\nlocal RunService = game:GetService(\"RunService\")\n\nlocal SecureCast = require(ReplicatedStorage.SecureCast)\n\nlocal MAXIMUM_LATENCY = 0.8 -- 800 ms\n\nlocal Events = ReplicatedStorage.Events\nlocal SimulateEvent = Events.Simulate\n\n--> Only call once per context\nSecureCast.Initialize()\n\nPlayers.PlayerAdded:Connect(function(Player: Player)\n    --> We must parent all characters to the Characters folder within workspace\n    Player.CharacterAdded:Connect(function(Character)\n        RunService.PostSimulation:Wait()\n        Character.Parent = workspace.Characters\n    end)\n\n    --> Disable raycast interactions with accessories\n    Player.CharacterAppearanceLoaded:Connect(function(Character)\n        for _, Child in Character:GetChildren() do\n            if not Child:IsA(\"Accessory\") then\n                continue\n            end\n\n            local Handle: BasePart? = Child:FindFirstChild(\"Handle\") :: BasePart\n            if Handle then\n                Handle.CanQuery = false\n            end\n        end\n    end)\nend)\n\nReplicatedStorage.Events.Simulate.OnServerEvent:Connect(function(Player: Player, Origin: Vector3, Direction: Vector3, Timestamp: number)\n    --> It is best to have calculate these values at the top\n    --> We can have the most accurate latency values this way\n    --> Calculating them further down may result in skewed results\n\n    --> We must take into account character interpolation\n    --> The best estimate for this value available is (PLAYER_PING + 48 ms)\n    --> If we do not factor in interpolation we will end up with inaccurate lag compensation\n\n    local Latency = (workspace:GetServerTimeNow() - Timestamp)\n    local Interpolation = (Player:GetNetworkPing() + SecureCast.Settings.Interpolation)\n\n    --> Validate the latency and avoid players with very slow connections\n    if (Latency < 0) or (Latency > MAXIMUM_LATENCY) then\n        return\n    end\n\n    --> Validate the projectile origin\n    local Character = Player.Character\n    local Head: BasePart? = Character and Character:FindFirstChild(\"Head\") :: BasePart\n    if not Head then\n        return\n    end\n\n    local Distance = (Origin - Head.Position).Magnitude\n    if Distance > 5 then\n        warn(`{Player} is too far from the projectile origin.`)\n        return\n    end\n\n    --> Replicate the projectile to all other clients\n    SimulateEvent:FireAllClients(Player, \"Bullet\", Origin, Direction)\n\n    --> Cast the projectile within our own simulation\n    SecureCast.Cast(Player, \"Bullet\", Origin, Direction, Timestamp - Interpolation)\nend)\n

"},{"location":"Getting%20Started/Simulation/#test-your-simulation","title":"Test your simulation","text":"

You can test if your simulation is working properly by going into a Local Test Server.

Note

Make sure to create 2 seperate teams or the players will be ignored due to being on the same team.

"}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Secure-Cast","text":"

A server-authoritative projectile system for ROBLOX.

"},{"location":"#what-is-it","title":"What is it?","text":"

SecureCast is aimed at providing an easy solution to server authoritative projectile using the ROBLOX humanoid system.

"},{"location":"#features","title":"Features","text":""},{"location":"#getting-started","title":"Getting Started","text":"

Head over to the installation page to get started with SecureCast.

"},{"location":"#license","title":"License","text":"

SecureCast is licensed under the MIT License.

"},{"location":"API/definitions/","title":"Definitions","text":""},{"location":"API/definitions/#definitions","title":"Definitions","text":"

This page contains all types defined and used by SecureCast.

type Modifier ( number Loss, number Power, number Angle, number Gravity, number Velocity, number Lifetime, BindableEvent? OnImpact, BindableEvent? OnDestroyed, BindableEvent? OnIntersection, RaycastParams? RaycastFilter, {[string]: any}? Extra, )

Modifiers are a powerful tool that allows you to define per cast functionality seperate from the base projectile definition.

Custom event handling
local Bindable = Instance.new(\"BindableEvent\")\nBindable.Event:Connect(function(Type: string, Event: string, ...)\n    if Event == \"OnDestroyed\" then\n        --> No need to keep a reference to the connection \n        --> since destroy will take care of it for us\n        Bindable:Destroy()\n    elseif Event == \"OnImpact\" then\n\n    elseif Event == \"OnIntersection\" then\n\n    end\nend)\n\nlocal Modifier = {\n    OnImpact = Bindable,\n    OnDestroyed = Bindable,\n    OnIntersection = Bindable,\n}\n\nSecureCast.Cast(Player, \"Bullet\", Origin, Direction, Time, nil, Modifier)\n

Danger

When using Modifiers make sure that all clients and the server use the same modifier, improper modifier usage can result in simulation desync.

type Definition ( number Loss, number Power, number Angle, number Gravity, number Velocity, number Lifetime, BindableEvent? Output, RaycastParams? RaycastFilter, void OnImpact ( Player Caster, Vector3 Direction, Instance Instance, Vector3 Normal, Vector3 Position, Enum.Material Material {[string]: any}? Extra, \u2003), void OnDestroyed ( Player Caster, Vector3 Position {[string]: any}? Extra, \u2003), void OnIntersection ( Player Caster, Vector3 Direction, string Part, Player? Victim, Vector3 Position Model? VictimCharacter, {[string]: any}? Extra, \u2003), )

This type represents the way that projectiles should be defined within the system. Refer to the template bullet included within the GitHub repository for an example.

type Record ( {CFrame} Parts, Vector3 Position, )

type Snapshot ( number Time, Voxels.Grid Grid, {[Player]: Record} Records, )

"},{"location":"API/securecast/","title":"SecureCast","text":""},{"location":"API/securecast/#securecast","title":"SecureCast","text":"

SecureCast is the top-level interface for interacting with the system. It is returned by require(SecureCast).

"},{"location":"API/securecast/#members","title":"Members","text":"

Utility Snapshots

The snapshots utility used by the simulation, you can use this to retrieve player positions back in time for lag compensation. The API for this utility is available here.

Warning

Snapshots can only be used on the server, calling any of the methods within the utility will throw an error when called from the client!

"},{"location":"API/securecast/#methods","title":"Methods","text":"

void Initialize ( )

Initialize the simulation for the current context.

Warning

Initialize can only be called once per context, subsequent calls will result in an error.

void Cast ( Player Caster, string Type, Vector3 Origin Vector3 Direction number Timestamp PVInstance? PVInstance Modifier? Modifier )

Casts a new projectile. A Modifier can be used to modify the behaviour of a projectile on a per cast basis. A PVInstance must exist on the client in order for the projectile to be rendered.

Danger

When using a Modifier make sure that all clients and the server use the same modifier, improper modifier usage can result in simulation desync.

"},{"location":"API/settings/","title":"Settings","text":""},{"location":"API/settings/#settings","title":"Settings","text":"

This page contains all settings used by SecureCast. These can be found in the Settings module under the SecureCast module.

number VoxelSize

The size of each voxel in studs.

Vector3 VoxelGridSize

The size of the voxel grid in studs.

Danger

Be careful when editing this value, anything smaller than the playable area will result in players being missed by the server raycasts.

number SnapshotLifetime

The lifetime of snapshots in seconds.

Danger

Be careful when editing this value, very small values will result in players with high ping not being able to land shots but high values may result in players being hit behind cover long after they have gone behind it.

Instance Definitions

The container for projectile definitions modules.

{[Enum.Material]: number} SurfaceHardness

An array of each materials hardness. The needed penetration power can be calculate with the following formula: Power = SurfaceDepth * SurfaceHardness

number RicochetHardness

The minimum surface hardness needed for a projectile to ricochet off of something. This is ignored when a projectile has a ricochet angle set to math.pi * 2.

{string} Parts

An array containing the names of every hitbox in a players character, ordered from most to least damage.

{Vector3} PartsSizes

An array containing the halved sizes of every hitbox in a players character, in the same order as the Parts array.

Vector3 HitboxSize

The maximum halved size of a players character, it needs to contain the character at it's maximum arm span.

"},{"location":"API/snapshots/","title":"Snapshots","text":""},{"location":"API/snapshots/#snapshots","title":"Snapshots","text":"

The snapshots utility is used by the simulation to take \"snapshots\" of player positions in the past for lag compensation.

Note

Times must be retrieved using workspace:GetServerTimeNow()

"},{"location":"API/snapshots/#methods","title":"Methods","text":"

{[string]: CFrame}? GetPlayerAtTime ( Player Player number Time )

Returns an array containing the CFrame of each of the player's hitboxes in the past, returns nothing when no snapshots containing the player can be found.

{[Player]: {[string]: CFrame}} GetPlayersAtTime ( number Time )

Returns an array containing the CFrame of each of the hitboxes of every player in the past.

Snapshot?, Snapshot?, number? GetSnapshotsAtTime ( number Time )

Returns a tuple containing the previous snapshot, next snapshot and the fraction used for lerping between them, returns nothing if no snapshots can be found for the given time.

void CreatePlayersSnapshot ( number Time )

Creates a snapshot of every players hitbox at the given time.

Danger

This method should not be called or it may result in undefined behaviour, this is already called by SecureCast internally.

"},{"location":"Getting%20Started/Installation/","title":"Installation","text":"

Installing the module into your place is simple, we will cover the different methods of installation down below.

"},{"location":"Getting%20Started/Installation/#with-wally","title":"With Wally","text":"

SecureCast is available as a Wally package. Navigate to your projects wally.toml file and add the following dependancy

securecast = \"1axen/secure-cast\"\n
After adding SecureCast to your dependencies you will need to install it by running
wally install\n

"},{"location":"Getting%20Started/Installation/#with-git","title":"With git","text":"

SecureCast can be directly cloned from GitHub

git clone https://github.com/1Axen/Secure-Cast.git\n

Warning

The master branch of the GitHub repository contains the latest development version and may not be stable.

"},{"location":"Getting%20Started/Installation/#with-github-releases","title":"With GitHub releases","text":"

The latest stable version of the module can be downloaded from https://github.com/1Axen/Secure-Cast/releases After downloading the RBXM file insert it into ReplicatedStorage or any other shared container of your choice.

"},{"location":"Getting%20Started/Modifiers/","title":"Modifiers","text":"

Modifiers are a powerful tool that allows you to define per cast functionality seperate from the base projectile definition. They can be used to alter already existing projectile definitions for example: Giving a grenade extra velocity for jump/run throws.

Danger

When using Modifiers make sure that all clients and the server use the same modifier, improper modifier usage can result in simulation desync.

"},{"location":"Getting%20Started/Modifiers/#example","title":"Example","text":"

I will be using the simulation setup from the previous page, if you haven't setup your simulaton yet refer to the previous page. In this example we will create a modifier that gives a bullet extra penetrative power, this modifier will be controlled by an attribute \"ExtraPenetration\" in the players character. Example client simulation with modifiers

...\n\nUserInputService.InputBegan:Connect(function(Input, GPE)\n    ...\n\n    local ProjectileModifier;\n    if Character:GetAttribute(\"ExtraPenetration\") then\n        ProjectileModifier = {\n            Power = 200,\n            Extra = {\n                Weapon = \"M16\"\n            }\n        }\n    end\n\n    local Time = workspace:GetServerTimeNow()\n\n    --> Replicate to the server\n    SimulateEvent:FireServer(Origin, Direction, Time)\n\n    --> Cast the projectile within our own simulation\n    SecureCast.Cast(Player, \"Bullet\", Origin, Direction, Time, nil, ProjectileModifier)\nend)\n\n...\n

Example server simulation with modifiers
...\n\nReplicatedStorage.Events.Simulate.OnServerEvent:Connect(function(Player: Player, Origin: Vector3, Direction: Vector3, Timestamp: number)\n    ...\n\n    local ProjectileModifier;\n    if Character:GetAttribute(\"ExtraPenetration\") then\n        ProjectileModifier = {\n            Power = 200\n            Extra = {\n                Weapon = \"M16\"\n            }\n        }\n    end\n\n    --> Replicate the projectile to all other clients\n    SimulateEvent:FireAllClients(Player, \"Bullet\", Origin, Direction, nil, ProjectileModifier)\n\n    --> Cast the projectile within our own simulation\n    SecureCast.Cast(Player, \"Bullet\", Origin, Direction, Time - Latency - Interpolation, nil, ProjectileModifier)\nend)\n
"},{"location":"Getting%20Started/Simulation/","title":"Simulation","text":"

In this section we will explore setting up a basic simulation along side the different things we must account for in order to create a working simulation.

"},{"location":"Getting%20Started/Simulation/#setting-up-your-workspace","title":"Setting up your workspace","text":"

SecureCast requires a Map and Characters folder placed within workspace to function correctly. All parts of the map must be a descendant of the Map folder. All player characters must be paranted to the Characters folder.

We will also need an Events folder within ReplicatedStorage. Add a RemoteEvent named Simulation under the Events folder.

"},{"location":"Getting%20Started/Simulation/#setting-up-your-client","title":"Setting up your client","text":"

Create a LocalScript and place it under StarterPlayerScripts. Copy and paste the following code into the script you just created: Example client simulation

--!strict\n\nlocal Players = game:GetService(\"Players\")\nlocal ReplicatedStorage = game:GetService(\"ReplicatedStorage\")\nlocal UserInputService = game:GetService(\"UserInputService\")\n\nlocal SecureCast = require(ReplicatedStorage.SecureCast)\n\nlocal Player = Players.LocalPlayer\nlocal Mouse = Player:GetMouse()\n\nlocal Events = ReplicatedStorage.Events\nlocal SimulateEvent = Events.Simulate\n\n--> Only call once per context\nSecureCast.Initialize()\n\nUserInputService.InputBegan:Connect(function(Input, GPE)\n    if GPE or Input.UserInputType ~= Enum.UserInputType.MouseButton1 then\n        return\n    end\n\n    local Character = Player.Character\n    local Head = Character and Character:FindFirstChild(\"Head\")\n    if not Head then\n        return\n    end\n\n    local Origin = Head.Position\n    local Direction = (Mouse.Hit.Position - Origin).Unit\n\n    local Time = workspace:GetServerTimeNow()\n\n    --> Replicate to the server\n    SimulateEvent:FireServer(Origin, Direction, Time)\n\n    --> Cast the projectile within our own simulation\n    SecureCast.Cast(Player, \"Bullet\", Origin, Direction, Time)\nend)\n\nSimulateEvent.OnClientEvent:Connect(function(Caster: Player, Type: string, Origin: Vector3, Direction: Vector3, PVInstance: PVInstance?, Modifer)\n    if Caster ~= Player then\n        SecureCast.Cast(Caster, Type, Origin, Direction, workspace:GetServerTimeNow(), PVInstance, Modifer)\n    end\nend)\n

"},{"location":"Getting%20Started/Simulation/#setting-up-your-server","title":"Setting up your server","text":"

Create a Script and place it under ServerScriptService. Copy and paste the following code into the script you just created: Example server simulation

--!strict\n\nlocal Players = game:GetService(\"Players\")\nlocal ReplicatedStorage = game:GetService(\"ReplicatedStorage\")\nlocal RunService = game:GetService(\"RunService\")\n\nlocal SecureCast = require(ReplicatedStorage.SecureCast)\n\nlocal MAXIMUM_LATENCY = 0.8 -- 800 ms\n\nlocal Events = ReplicatedStorage.Events\nlocal SimulateEvent = Events.Simulate\n\n--> Only call once per context\nSecureCast.Initialize()\n\nPlayers.PlayerAdded:Connect(function(Player: Player)\n    --> We must parent all characters to the Characters folder within workspace\n    Player.CharacterAdded:Connect(function(Character)\n        RunService.PostSimulation:Wait()\n        Character.Parent = workspace.Characters\n    end)\n\n    --> Disable raycast interactions with accessories\n    Player.CharacterAppearanceLoaded:Connect(function(Character)\n        for _, Child in Character:GetChildren() do\n            if not Child:IsA(\"Accessory\") then\n                continue\n            end\n\n            local Handle: BasePart? = Child:FindFirstChild(\"Handle\") :: BasePart\n            if Handle then\n                Handle.CanQuery = false\n            end\n        end\n    end)\nend)\n\nReplicatedStorage.Events.Simulate.OnServerEvent:Connect(function(Player: Player, Origin: Vector3, Direction: Vector3, Timestamp: number)\n    --> It is best to have calculate these values at the top\n    --> We can have the most accurate latency values this way\n    --> Calculating them further down may result in skewed results\n\n    --> We must take into account character interpolation\n    --> The best estimate for this value available is (PLAYER_PING + 48 ms)\n    --> If we do not factor in interpolation we will end up with inaccurate lag compensation\n\n    local Latency = (workspace:GetServerTimeNow() - Timestamp)\n    local Interpolation = (Player:GetNetworkPing() + SecureCast.Settings.Interpolation)\n\n    --> Validate the latency and avoid players with very slow connections\n    if (Latency < 0) or (Latency > MAXIMUM_LATENCY) then\n        return\n    end\n\n    --> Validate the projectile origin\n    local Character = Player.Character\n    local Head: BasePart? = Character and Character:FindFirstChild(\"Head\") :: BasePart\n    if not Head then\n        return\n    end\n\n    local Distance = (Origin - Head.Position).Magnitude\n    if Distance > 5 then\n        warn(`{Player} is too far from the projectile origin.`)\n        return\n    end\n\n    --> Replicate the projectile to all other clients\n    SimulateEvent:FireAllClients(Player, \"Bullet\", Origin, Direction)\n\n    --> Cast the projectile within our own simulation\n    SecureCast.Cast(Player, \"Bullet\", Origin, Direction, Timestamp - Interpolation)\nend)\n

"},{"location":"Getting%20Started/Simulation/#test-your-simulation","title":"Test your simulation","text":"

You can test if your simulation is working properly by going into a Local Test Server.

Note

Make sure to create 2 seperate teams or the players will be ignored due to being on the same team.

"}]} \ No newline at end of file