Workspace - Everything about it

DISCLAIMER: THIS IS NOTHING TO DO WITH THE ROBLOX STUDIO WORKSPACE. I OWN AN EXPERIENCE CALLED WORKSPACE AND THIS IS FOR THAT.

No. I’m not the best scripter; I used AI for half of the code.
Yes this is technically a “tutorial” since I’m uhhhhh helping people make uh fighting games…

The actual experience isn’t uncopylocked, but you can download an outdated version here:
Workspace - Download the place file here! (2.2 MB)

If you wish to play the real game, you can play it here:
Workspace - Play the official game here!

Please do not reupload the game and claim ownership.
Nobody helped me with this except one person who did nothing except decorate the grocery store
They may add more in the future. And if they request, I will remove their grocery store build from the game.
Anybody else was just not developing the game, but instead giving me ideas (AS OF WRITING THIS).

The whole point of workspace is the legacy roblox style, not classic Roblox or new Roblox.

I’d rather people not use my work but… Hey, if it helps people make FPS and TPS games, sure, go for it.

I don’t care much about credit if you use something from the game, but you can credit me if you wish.

Workspace uses some customized classic/new meshes such as the Dynamic Man Head and some asset’s from The Mystery of Duvall Drive.

Workspace is an R6 experience about killing NPCS with very unfinished guns… Enemies are randomly chosen from a folder, while being easy to add/remove them, and change their code. if you wish to change an enemy’s size, put a script in their folder saying;

task.wait(0.1)
script.Parent:ScaleTo(2)

If you’re gonna resize an enemy, check the attacking script inside
game.ReplicatedStorage.Assets.Rigs.Enemy.
You must change the range for that enemy by giving an object in the enemy folder a specific name that the Attacking script can recognize, same with damage if you’re gonna do that.

Every Halloween, Christmas, and April Fools, the enemies will change their looks.

That’s all, please reply with reviews, or ideas for things to add.

The gun system system has 3 scripts, or 4 if you include the npc code.
I’ll paste the GunSystem ModuleScript below this line.

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CollectionService = game:GetService("CollectionService")
local TweenService = game:GetService("TweenService")
local Debris = game:GetService("Debris")
local Players = game:GetService("Players")

local GunSystem = {}

local BulletHoles = ReplicatedStorage:WaitForChild("Assets"):WaitForChild("BulletHoles")
local Bullets = ReplicatedStorage:WaitForChild("Assets"):WaitForChild("Bullets")
local Particles = ReplicatedStorage:WaitForChild("Assets"):WaitForChild("Particles")
local FireGun = ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("FireGun")

local BulletFolder = workspace:FindFirstChild("Bullets") or Instance.new("Folder")
BulletFolder.Name = "Bullets"
BulletFolder.Parent = workspace

local killstreaks = {}
local triggeredExplosives = {}
local lastShot = {}

local beam = nil

Players.PlayerAdded:Connect(function(player)
	killstreaks[player] = 0
	player.CharacterAdded:Connect(function(char)
		local hum = char:WaitForChild("Humanoid")
		killstreaks[player] = 0
		hum.Died:Connect(function()
			killstreaks[player] = 0
		end)
	end)
end)

Players.PlayerRemoving:Connect(function(player)
	killstreaks[player] = nil
end)

local function hasBlockingConstraint(part)
	for _, d in ipairs(part:GetDescendants()) do
		if d:IsA("BallSocketConstraint") then
			if not string.find(d.Name, "Ragdoll_Socket") then
				return true
			end
		elseif d:IsA("Constraint")
			and not d:IsA("NoCollisionConstraint")
			and not d:IsA("UIAspectRatioConstraint")
			and not d:IsA("UISizeConstraint")
			and not d:IsA("Motor6D")

			and not d:IsA("UITextSizeConstraint") then
			return true
		elseif d:IsA("Attachment") then
			if string.find(d.Name, "Custom_Attachment") or string.find(d.Name, "Tool_Attachment") then
				return true
			end
		end
	end
	return false
end

local function limb(hitPart)
	if math.random(1, 2) == 1 then return end

	local character = hitPart.Parent
	if not character then return end

	local clone = character:Clone()
	clone.Parent = workspace
	clone:PivotTo(hitPart.Parent:GetPivot())
	clone.PrimaryPart = nil

	for _, d in ipairs(clone:GetDescendants()) do
		if d:IsA("Script") or d:IsA("LocalScript") or d:IsA("ModuleScript") then
			d:Destroy()
		end
	end

	for _, name in ipairs({"HumanoidRootPart", "Head", "Torso"}) do
		local p = clone:FindFirstChild(name)
		if p then
			p:Destroy()
		end
	end

	for _, d in ipairs(clone:GetDescendants()) do
		if d:IsA("BasePart") then
			d.CanCollide = false
			d.Anchored = true
			d.CanQuery = false
			d.CanTouch = false
			d.Transparency = 1
		end
	end
	
	hitPart.CanQuery = false
	hitPart.CanTouch = false
	hitPart.Transparency = 1

	local limbClone = clone:FindFirstChild(hitPart.Name)
	if not limbClone or not limbClone:IsA("BasePart") then return end

	limbClone:BreakJoints()

	for _, d in ipairs(limbClone:GetChildren()) do
		if not d:IsA("Decal") and not d:IsA("Texture") and not d:IsA("SpecialMesh") then
			d:Destroy()
		end
	end

	limbClone.CanCollide = true
	limbClone.Transparency = 0
	limbClone.CanQuery = true
	limbClone.CanTouch = true
	limbClone.Anchored = false
	limbClone.CFrame = hitPart.CFrame
end

local function teamSafeRaycast(origin, direction, shooterModel)
	if not game:GetService("RunService"):IsServer() then return end
	local dir = direction.Magnitude > 0 and direction.Unit or Vector3.new(0,0,-1)
	local params = RaycastParams.new()
	params.FilterType = Enum.RaycastFilterType.Blacklist
	params.IgnoreWater = true
	local ignore = { shooterModel }

	for _ = 1, 8 do
		params.FilterDescendantsInstances = ignore
		local result = workspace:Raycast(origin, dir * 1999, params)
		if not result then
			return origin + dir * 1999, nil, nil
		end

		local hitModel = result.Instance:FindFirstAncestorOfClass("Model")
		if hitModel then
			local hum = hitModel:FindFirstChild("Humanoid")
			local shooter = Players:GetPlayerFromCharacter(shooterModel)
			local shooterTeam = shooter and shooter.Team
			local hitPlayer = Players:GetPlayerFromCharacter(hitModel)
			local hitTeam = hitPlayer and hitPlayer.Team
			local hitIsFriendly = CollectionService:HasTag(hitModel, "Friendly")
			local shooterIsEnemy = CollectionService:HasTag(shooterModel, "Enemy")

			if hum and shooterTeam and hitTeam and shooterTeam == hitTeam and shooterTeam.Name ~= "Human" then
				table.insert(ignore, hitModel)
				continue
			elseif hitIsFriendly and not shooterIsEnemy then
				table.insert(ignore, hitModel)
				continue
			end
		end

		return result.Position, result.Instance, result.Normal
	end

	return origin + dir * 1999, nil, nil
end

function GunSystem.Fire(user, originPos, direction, tool)
	if not game:GetService("RunService"):IsServer() then return end
	if game:GetService("RunService"):IsRunMode() then return end
	local shootFunc = tool:GetAttribute("ShootFunction")

	if shootFunc == "Ghost" then
		local character = user.Character
		if not character then return end
		local player = Players:GetPlayerFromCharacter(character)

		local handle = tool:FindFirstChild("Handle")
		if not handle then return end

		local projectile = Instance.new("Part")
		projectile.Name = "GhostProjectile"
		projectile.Size = Vector3.new(2.5,2.5,2.5)
		projectile.Shape = Enum.PartType.Ball
		projectile.CanCollide = false
		projectile.Transparency = 1
		projectile.Anchored = true
		projectile.CFrame = CFrame.new(handle.Position)
		projectile.Parent = workspace:FindFirstChild("BulletHoles") or workspace

		local fire = Instance.new("Fire")
		fire.Color = Color3.new(1,1,1)
		fire.SecondaryColor = Color3.new(0,0,0)
		fire.Heat = 2
		fire.Size = 3
		fire.Parent = projectile

		local creatorVal = Instance.new("ObjectValue")
		creatorVal.Name = "creator"
		creatorVal.Value = player
		creatorVal.Parent = projectile

		Debris:AddItem(projectile,25)

		local baseDir = (direction.Magnitude > 0 and direction.Unit or Vector3.new(0,0,1))
		local startPos = projectile.Position
		local timeElapsed = 0

		task.spawn(function()
			while projectile.Parent do
				task.wait(0.03)
				timeElapsed += 0.03
				local riseSpeed = math.clamp(timeElapsed*3,0,31)
				local targetPos = startPos + baseDir*345*timeElapsed + Vector3.new(0,riseSpeed*timeElapsed,0)
				local tween = TweenService:Create(projectile,TweenInfo.new(0.03,Enum.EasingStyle.Linear),{CFrame=CFrame.new(targetPos,targetPos+baseDir)})
				tween:Play()
			end
		end)

		projectile.Touched:Connect(function(hit)
			if not hit then return end
			local hum = hit.Parent:FindFirstChildOfClass("Humanoid")
			if hum and hum.Health > 0 and hum.Parent.Name ~= user.Name and not CollectionService:HasTag(hum.Parent, "Ghost") and not CollectionService:HasTag(hum.Parent, "Friendly") then
				
				projectile:Destroy()
				hum:TakeDamage(tool:GetAttribute("Damage") or 100)

				local hrp = hum.Parent:FindFirstChild("HumanoidRootPart")
				if hrp then
					local bv = Instance.new("BodyVelocity")
					bv.Velocity = Vector3.new(0,150,0)
					bv.MaxForce = Vector3.new(0,math.huge,0)
					bv.P = 1250
					bv.Parent = hrp
					Debris:AddItem(bv,0.3)
				end

				if hum.Health <= 1 then
					local ghost = game:GetService("ReplicatedStorage"):WaitForChild("Assets"):WaitForChild("Rigs"):WaitForChild("Ghost"):Clone()
					--ghost:AddTag("Ghost")
					ghost.Name = "Ghost"
					ghost.Parent = workspace:WaitForChild("NPCS")
					ghost:MoveTo(projectile.Position)
					ghost:ScaleTo(hit.Parent:GetScale() or 1)
					
					for i,v in ipairs(hit.Parent:GetChildren()) do
						if v:IsA("CharacterMesh") then
							local vclone=v:Clone()
							vclone.BaseTextureId=0
							vclone.OverlayTextureId=0
							vclone.Parent=ghost
						end
					end
					
					for i,v in ipairs(hit.Parent:WaitForChild("Head"):GetChildren()) do
						if v:IsA("Decal") then
							local xclone=v:Clone()
							xclone.Parent=ghost:WaitForChild("Head")
						end
					end
					
					for i,v in ipairs(hit.Parent:GetChildren()) do
						if v:IsA("Accessory") then
							local zclone=v:Clone()
							zclone.Parent=ghost
						end
					end
					
					for i,v in ipairs(ghost:GetChildren()) do
						if v:IsA("Accessory") then
							for i,z in ipairs(v:GetChildren()) do
								if z:IsA("BasePart") then
									z.Transparency=0.5
									z.CanCollide = false
									for i,n in ipairs(z:GetChildren()) do
										if n:IsA("SpecialMesh") then
											n.TextureId = ""
										end	
									end
								end
							end
						end
					end
					
					task.delay(25, function()
						local startScale = 1
						local endScale = 1.4
						local duration = 0.11
						local steps = 23
						local stepTime = duration / steps

						for i = 1, steps do
							local scale = startScale + (endScale - startScale) * (i / steps)
							ghost:ScaleTo(scale)
							task.wait(stepTime)
						end
						task.wait(0.1)
						ghost:Destroy()
					end)
				end
			end
		end)

		game:GetService("Debris"):AddItem(projectile, 30)
		return
	end
	
	if tool:FindFirstChild("End") and tool.End:FindFirstChild("End") then
		ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("FireParticle"):FireAllClients(tool)
	end

	if tool:FindFirstChild("End") and tool.End:FindFirstChild("End") then
		ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("BulletTrail"):FireAllClients(tool, hitPos, shooter, direction)
	end
	
	local character = user:IsA("Player") and user.Character or user
	if not character then return end

	local hitPos, hitPart, hitNormal = teamSafeRaycast(originPos, direction, character)

	if tool:FindFirstChild("Handle") and tool.Handle:FindFirstChild("Fire") then
		local fire = tool.Handle.Fire:Clone()
		fire.Parent = tool.Handle
		fire:Play()
		task.delay(fire.TimeLength + 0.4, function()
			fire:Destroy()
		end)
	end

	local damage = tool:GetAttribute("Damage") or 10

	if hitPart then
		local hitModel = hitPart:FindFirstAncestorOfClass("Model")
		local hum = hitModel and hitModel:FindFirstChild("Humanoid")

		local shooter = Players:GetPlayerFromCharacter(character)
		local shooterTeam = shooter and shooter.Team
		local hitPlayer = Players:GetPlayerFromCharacter(hitModel)
		local hitTeam = hitPlayer and hitPlayer.Team

		local hitIsFriendly = CollectionService:HasTag(hitModel, "Friendly")
		local shooterIsEnemy = CollectionService:HasTag(character, "Enemy")
		

		if hitIsFriendly and not shooterIsEnemy then return end
		if shooterTeam and hitTeam and shooterTeam == hitTeam and shooterTeam.Name ~= "Human" then return end
		
		local bulletRandom = math.random(1, 3)
		local realDamage = damage
		if shooterTeam and (shooterTeam.Name == "Resistance" or shooterTeam.Name == "Military") then
			realDamage *= 1.3
		end
		if character:FindFirstChild("OminousDragonHelmet") then
			realDamage *= 1.2
		end
		if character:FindFirstChild("hestaresinthedarkness") and character:FindFirstChild("hestaresinthedarkness"):FindFirstChildOfClass("Configuration") and character:FindFirstChild("hestaresinthedarkness"):IsA("Accessory") then
			realDamage *= 2
		end
		
		if shooter and shooter:IsA("Player") and character.Name == "DumpWanderer" or character.Name == "DumpWandererAlt" then
			realDamage += 5
		end
		
		if shooter and shooter:IsA("Player") then
			if shooter:IsFriendsWithAsync(6164835582) then
				realDamage += 1
			end
		end

		if math.random(1, 15000) == 1 then
			print("bro 1 in 15k chance the bullet doesnt do damage...")
		else
			if hum and hum.Health ~= 0 and not hitPart:HasTag("Unattached") then
				hum:TakeDamage(realDamage + bulletRandom)
			end
		end

		local hole
		local lifetime
		local dust
		
		
		if hitPart.MaterialVariant == "Leaves" then
			hole = BulletHoles.Leaves:Clone()
			hole.Decal.Color3 = hitPart.Color
			lifetime = 10
		elseif hum then
			hole = BulletHoles.Blood:Clone()
			CollectionService:AddTag(hole, "Blood")
			lifetime = 6.7
		elseif CollectionService:HasTag(hitPart, "OxygenTank") then
			if math.random(1, 5) == 1 then
				hole = BulletHoles.AirTank2:Clone()
			else
				hole = BulletHoles.AirTank1:Clone()
			end
			lifetime = 11
		elseif (hitPart.Material == Enum.Material.Glass and hitPart.Transparency ~= 0) or (hitPart.Transparency > 0.03 and hitPart.Transparency ~= 0) or hitPart.Name == "Glass" then
			hole = BulletHoles.Glass:Clone()
			hole.Decal.Color3 = hitPart.Color
			lifetime = 160
		else
			hole = BulletHoles.Concrete:Clone()
			if hitPart.Color ~= Color3.fromRGB(39,132,53)
				and hitPart.Color ~= Color3.fromRGB(255,140,1)
				and hitPart.Color ~= Color3.fromRGB(255,105,180)
				and hitPart.Color ~= Color3.fromRGB(34,139,34)
				and hitPart.Color ~= Color3.fromRGB(16,145,134) then
				hole.Transparency = 1
			end

			hole.Decal.Color3 = hitPart.Color
			lifetime = 190

			if hitPart.Material == Enum.Material.DiamondPlate or hitPart.Material == Enum.Material.Metal or hitPart.Material == Enum.Material.CorrodedMetal then
				dust = Particles.MetalHitDust:Clone()
			elseif hitPart.MaterialVariant == "Leaves" then
				dust = Particles:WaitForChild("LeavesHit"):Clone()
			else
				if math.random(1, 2) == 1 then
					dust = Particles.HitDustNew:Clone()
				else
					dust = Particles.HitDust:Clone()
				end
			end
			
			dust.Parent = hole
			dust.Enabled = true

			if hitPart.Color == Color3.fromRGB(165,166,166) then
				dust.Color = ColorSequence.new(Color3.fromRGB(81, 60, 46))
			elseif hitPart.Material == Enum.Material.DiamondPlate or hitPart.Material == Enum.Material.Metal or hitPart.Material == Enum.Material.CorrodedMetal then
				dust.Color = ColorSequence.new(Color3.fromRGB(255, 204, 0))
			else
				dust.Color = ColorSequence.new(hitPart.Color)
			end

			if hitPart.Material == Enum.Material.DiamondPlate or hitPart.Material == Enum.Material.Metal or hitPart.Material == Enum.Material.CorrodedMetal then
				task.delay(0.39, function()
					if dust then dust.Enabled = false end
				end)
			else
				task.delay(0.7, function()
					if dust then dust.Enabled = false end
				end)
			end

			Debris:AddItem(dust, 10)
		end

		if hitPart and CollectionService:HasTag(hitPart, "DestroyOnShoot") then
			hitPart:Destroy()
		elseif hitPart and CollectionService:HasTag(hitPart, "DestroyParentOnShoot") then
			if hitPart:HasTag("ExplodeOnShoot") then
				task.wait(0.1)
				hitPart.Parent:Destroy()
			else
				hitPart.Parent:Destroy()
			end
		elseif hitPart and CollectionService:HasTag(hitPart, "ExplodeOnShoot") then
			local pos = hitPart.Position

			local e = Instance.new("Explosion")
			e.BlastPressure = 5
			e.BlastRadius = 0
			e.DestroyJointRadiusPercent = 0
			e.Parent = workspace
			e.Position = hitPart.Position

			local radius = 25
			local maxDamage = 150

			for _, plr in pairs(Players:GetPlayers()) do
				local char = plr.Character
				if char then
					local hrp = char:FindFirstChild("HumanoidRootPart")
					local hum = char:FindFirstChild("Humanoid")
					if hrp and hum and hum.Health > 0 then
						local dist = (hrp.Position - pos).Magnitude
						if dist <= radius then
							if triggeredExplosives[hitPart] then return end
							triggeredExplosives[hitPart] = true

							local rayParams = RaycastParams.new()
							rayParams.FilterDescendantsInstances = {hitPart, char}
							rayParams.FilterType = Enum.RaycastFilterType.Exclude
							rayParams.IgnoreWater = true

							local result = workspace:Raycast(pos, hrp.Position - pos, rayParams)
							
							local chainRadius = 30

							for _, p in pairs(CollectionService:GetTagged("ExplodeOnShoot")) do
								if p ~= hitPart and p:IsA("BasePart") and p.Parent then
									if not triggeredExplosives[p] then
										if (p.Position - pos).Magnitude <= chainRadius then
											task.spawn(function()
												p:Destroy()
											end)
										end
									end
								end
							end

							local protectionMultiplier = 1

							if result then
								local mat = result.Instance.Material
								if mat == Enum.Material.Wood or mat == Enum.Material.WoodPlanks or mat == Enum.Material.Cardboard then
									protectionMultiplier = 0.85
								elseif mat == Enum.Material.CorrodedMetal then
									protectionMultiplier = 0.6
								elseif mat == Enum.Material.Metal then
									protectionMultiplier = 0.25
								elseif mat == Enum.Material.DiamondPlate then
									protectionMultiplier = 0.15
								end
							end

							local falloff = 1 - (dist / radius)
							local damage = math.clamp(maxDamage * falloff * protectionMultiplier, 0, maxDamage)

							if damage > 0 then
								hum:TakeDamage(damage)
							end
						end
					end
				end
			end
			task.wait()
			hitPart.Parent:Destroy()
		elseif hitPart and hitPart.Parent and CollectionService:HasTag(hitPart.Parent, "Target") then
			print(character.Name.. " hit a moving training dummy target")
			if Players:GetPlayerFromCharacter(character or shooter) then
				if not game:GetService("BadgeService"):UserHasBadgeAsync(Players:GetPlayerFromCharacter(character).UserId or shooter.UserId, 2511480778492588) then
					game:GetService("BadgeService"):AwardBadgeAsync(Players:GetPlayerFromCharacter(character or shooter).UserId, 2511480778492588)
				end
				ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("HitTarget"):FireClient(Players:GetPlayerFromCharacter(character or shooter))
			end
		elseif hitPart and hitPart.Parent and hitPart.Parent:HasTag("Enemy") and hitPart.Name == "Left Leg" and hitPart.Parent:FindFirstChild("Right Leg") and not hitPart.Parent:FindFirstChild("Right Leg"):HasTag("Unattached") and hitPart.Parent:FindFirstChildOfClass("Humanoid") and hitPart.Parent:FindFirstChildOfClass("Humanoid").Health ~= 0 and hitPart.Parent:GetScale() == 1 then
			limb(hitPart)
		elseif hitPart and hitPart.Parent and hitPart.Parent:HasTag("Enemy") and hitPart.Name == "Right Leg" and hitPart.Parent:FindFirstChild("Right Leg") and not hitPart.Parent:FindFirstChild("Left Leg"):HasTag("Unattached") and hitPart.Parent:FindFirstChildOfClass("Humanoid") and hitPart.Parent:FindFirstChildOfClass("Humanoid").Health ~= 0 and hitPart.Parent:GetScale() == 1 then
			limb(hitPart)
		elseif hitPart and hitPart.Parent and hitPart.Parent:HasTag("Enemy") and hitPart.Name == "Left Arm" and hitPart.Parent:FindFirstChild("Right Leg") and not hitPart.Parent:FindFirstChild("Right Arm"):HasTag("Unattached") and hitPart.Parent:FindFirstChildOfClass("Humanoid") and hitPart.Parent:FindFirstChildOfClass("Humanoid").Health ~= 0 and hitPart.Parent:GetScale() == 1 then
			limb(hitPart)
		elseif hitPart and hitPart.Parent and hitPart.Parent:HasTag("Enemy") and hitPart.Name == "Right Arm" and hitPart.Parent:FindFirstChild("Right Leg") and not hitPart.Parent:FindFirstChild("Left Arm"):HasTag("Unattached") and hitPart.Parent:FindFirstChildOfClass("Humanoid") and hitPart.Parent:FindFirstChildOfClass("Humanoid").Health ~= 0 and hitPart.Parent:GetScale() == 1 then
			limb(hitPart)
		end

		if hole then
			hole.CFrame = CFrame.lookAt(hitPos, hitPos + (hitNormal or Vector3.new(0,1,0)))
			if CollectionService:HasTag(hole, "Blood") then
				if hitPart and CollectionService:HasTag(hitPart.Parent.Parent, "Doll") then
					print("shot doll")
				else
					hole.Parent = hitPart or workspace:WaitForChild("BulletHoles", 5) or game:GetService("ServerStorage")
				end
			else
				hole.Parent = hitPart
			end

			local weld = Instance.new("WeldConstraint")
			weld.Part0 = hole
			weld.Part1 = hitPart
			weld.Parent = hole

			local decal = hole:FindFirstChildWhichIsA("Decal", true)
			if decal then
				decal.Transparency = 0
				task.wait(5)
				TweenService:Create(decal, TweenInfo.new(lifetime), {Transparency = 1}):Play()
			end

			Debris:AddItem(hole, lifetime)
		end
		
		--tool.Equipped:Connect(function()
		--	local leftarm = character:WaitForChild("Left Arm") or character:WaitForChild("LeftArm")
		--	local rightarm = character:WaitForChild("Right Arm") or character:WaitForChild("RightArm")
		--	local grip = rightarm:WaitForChild("RightGrip", 60) or rightarm:WaitForChild("Right Grip", 60) or nil
		--	grip.Part0 = rightarm
		--end)
	end
end

FireGun.OnServerEvent:Connect(function(player, originPos, direction, tool)
	if not tool then return end

	local now = os.clock()
	local cd = tool:GetAttribute("Cooldown") or 0.1
	local last = lastShot[player]

	if last and (now - last) < cd then
		return
	end

	lastShot[player] = now
	GunSystem.Fire(player, originPos, direction, tool)
end)

return GunSystem

It’s really a big system. Obviously theres 2 more scripts too, here’s the GunClient modulescript;

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local TweenService = game:GetService("TweenService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local module = {}
local activeSessions = {}
local REMOTES = ReplicatedStorage:WaitForChild("Remotes")
local ChangeGunSide = REMOTES:WaitForChild("ChangeGunSide")
local RequestGunSide = REMOTES:WaitForChild("RequestGunSide")

local windowFocused = true

function module.init(tool)
	if activeSessions[tool] then
		activeSessions[tool].cleanup()
	end

	local player = Players.LocalPlayer
	local camera = workspace.CurrentCamera
	local character = player.Character or player.CharacterAdded:Wait()
	local humanoid = character:WaitForChild("Humanoid")
	local root = character:WaitForChild("HumanoidRootPart")

	local MAX_LENGTH = 900000
	local ENABLED_OFFSET = Vector3.new(1.967, 0.67, 0.67)
	local CAMERA_TWEEN_TIME = 0.01
	local DEFAULT_FOV = 70
	local ZOOM_FOV = tool:GetAttribute("Zoom") or 67
	local ZOOM_SPEED = tool:GetAttribute("ZoomSpeed") or 18
	local zooming = false
	local active = false
	local frameMouselock = false
	local connections = {}
	local gunName = tool.Name
	local currentSide = 1

	local endTransparency = tool:WaitForChild("End").Transparency

	local leftArmTransparency = character:FindFirstChild("Left Arm").Transparency or character:WaitForChild("LeftArm", 3).Transparency
	local rightArmTransparency = character:FindFirstChild("Right Arm").Transparency or character:WaitForChild("RightArm", 3).Transparency
	
	UserInputService.WindowFocused:Connect(function()
		windowFocused = true
	end)

	UserInputService.WindowFocusReleased:Connect(function()
		windowFocused = false
	end)
	
	local function bind(signal, fn)
		local c = signal:Connect(fn)
		table.insert(connections, c)
		return c
	end

	local function cleanup()
		for _, c in connections do
			c:Disconnect()
		end
		connections = {}
		humanoid.CameraOffset = Vector3.zero
		camera.FieldOfView = DEFAULT_FOV
		UserInputService.MouseBehavior = Enum.MouseBehavior.Default
		UserInputService.MouseIconEnabled = true
	end

	activeSessions[tool] = { cleanup = cleanup }

	local function playerCamOffset()
		return Vector3.new(ENABLED_OFFSET.X * currentSide, ENABLED_OFFSET.Y, ENABLED_OFFSET.Z)
	end

	local realCamTweenInfo = TweenInfo.new(CAMERA_TWEEN_TIME, Enum.EasingStyle.Circular, Enum.EasingDirection.Out)

	if tool.Name == "Sniper" then
		local camTweenInfo1 = TweenInfo.new(CAMERA_TWEEN_TIME, Enum.EasingStyle.Quint, Enum.EasingDirection.Out)
		realCamTweenInfo = camTweenInfo1
	else
		local camTweenInfo2 = TweenInfo.new(CAMERA_TWEEN_TIME, Enum.EasingStyle.Circular, Enum.EasingDirection.Out)
		realCamTweenInfo = camTweenInfo2
	end

	local function TweenCameraOffset(targetOffset)
		TweenService:Create(humanoid, realCamTweenInfo, {CameraOffset = targetOffset}):Play()
	end

	local function UpdateCamera(dt)
		local lookVec = camera.CFrame.LookVector
		task.delay(0.01, function()
			if not character:FindFirstChildOfClass("Tool") then return end
			root.CFrame = CFrame.new(root.Position, root.Position + Vector3.new(lookVec.X * MAX_LENGTH, 0, lookVec.Z * MAX_LENGTH))
		end)
		if zooming then
			camera.FieldOfView += (ZOOM_FOV - camera.FieldOfView) * math.min(ZOOM_SPEED * dt, 1)
			if tool:GetAttribute("Scope") == true then
				player.PlayerGui.MainGui.Sniper.Visible = true
				UserInputService.MouseIconEnabled = false
				tool:WaitForChild("End").Transparency = 1
				local zoom = tonumber(tool:GetAttribute("Zoom"))
				local speed = tonumber(tool:GetAttribute("ZoomSpeed"))
				if zoom and speed
					and zoom <= 11
					and speed >= 13
					and game.ReplicatedStorage.Settings.IgnoreHideArmScopeStatements.Value == false then
					local leftArm = character:FindFirstChild("Left Arm") or character:FindFirstChild("LeftArm")
					if leftArm then
						leftArm.Transparency = 1
					end
					local rightArm = character:FindFirstChild("Right Arm") or character:FindFirstChild("RightArm")
					if rightArm then
						rightArm.Transparency = 1
					end
				end
			end
		else
			camera.FieldOfView += (DEFAULT_FOV - camera.FieldOfView) * math.min(ZOOM_SPEED * dt, 1)
			player.PlayerGui.MainGui.Sniper.Visible = false
			UserInputService.MouseIconEnabled = true
			tool:WaitForChild("End").Transparency = endTransparency
			local leftArm = character:FindFirstChild("Left Arm") or character:FindFirstChild("LeftArm")
			if leftArm then
				leftArm.Transparency = 0
			end
			local rightArm = character:FindFirstChild("Right Arm") or character:FindFirstChild("RightArm")
			if rightArm then
				rightArm.Transparency = 0
			end
		end
	end

	local function Enable()
		if active then return end
		active = true
		humanoid.AutoRotate = false
		TweenCameraOffset(playerCamOffset())
		if character:FindFirstChildOfClass("Tool") then
			UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
		end
		bind(RunService.RenderStepped, UpdateCamera)
		frameMouselock = true
		if tool:GetAttribute("MobileForceZoom") == true and UserInputService.TouchEnabled == true and not UserInputService.MouseEnabled then
			zooming = true
		end
	end

	local function Disable()
		active = false
		humanoid.AutoRotate = true
		TweenCameraOffset(Vector3.zero)
		cleanup()
		frameMouselock = false
		if UserInputService.TouchEnabled == true then
			zooming = false
		end
	end

	local function Shoot()
		local at = tool:GetAttribute("ShootFunction")
		if typeof(at) == "string" then
			at = at:lower()
			if at == "unzoom" then
				if UserInputService.TouchEnabled == false then
					zooming = false
				end
			elseif at == "die" then
				task.wait(tool:GetAttribute("DieFunctionWait"))
				local chance = tool:GetAttribute("DieChance") or 5
				if math.random(1, chance) == 1 then
					REMOTES:WaitForChild("KillPlayer"):FireServer()
				else
					REMOTES:WaitForChild("KillPlayer"):FireServer()
				end
			end
		end
	end

	RequestGunSide.OnClientEvent:Connect(function(gun, side)
		if gun == gunName then
			currentSide = side
			TweenCameraOffset(playerCamOffset())
		end
	end)

	bind(tool.Equipped, function()
		character = player.Character or player.CharacterAdded:Wait()
		humanoid = character:WaitForChild("Humanoid")
		root = character:WaitForChild("HumanoidRootPart")
		RequestGunSide:FireServer(gunName)
		Enable()
		local leftArm = character:FindFirstChild("Left Arm") or character:FindFirstChild("LeftArm")
		if leftArm then leftArm.Transparency = 0 end
		local rightArm = character:FindFirstChild("Right Arm") or character:FindFirstChild("RightArm")
		if rightArm then rightArm.Transparency = 0 end
	end)

	bind(tool.Unequipped, function()
		Disable()
		player.PlayerGui.MainGui.Sniper.Visible = false
		local leftArm = character:FindFirstChild("Left Arm") or character:FindFirstChild("LeftArm")
		if leftArm then leftArm.Transparency = 0 end
		local rightArm = character:FindFirstChild("Right Arm") or character:FindFirstChild("RightArm")
		if rightArm then rightArm.Transparency = 0 end
	end)

	bind(UserInputService.InputBegan, function(input, gpe)
		if gpe then return end
		if input.KeyCode == Enum.KeyCode.Q and active then
			--currentSide = -currentSide
			--TweenCameraOffset(playerCamOffset())
			--ChangeGunSide:FireServer(gunName, currentSide)
			
			if currentSide == -1 then
				-- left handed
				--game.ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("LeftSide"):FireServer()
			elseif currentSide == 1 then
				-- right handed
				--game.ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("RightSide"):FireServer()
			else
				-- unknown
				print("neither??")
				--player:Kick("please report this as a bug. tell the developers 'shiftlock was neither left or right'. thanks")
			end
		elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
			zooming = true
		elseif input.UserInputType == Enum.UserInputType.MouseButton1 then
			Shoot()
		end
	end)

	bind(UserInputService.InputEnded, function(input)
		if input.UserInputType == Enum.UserInputType.MouseButton2 then
			zooming = false
		end
	end)

	bind(humanoid.Died, function()
		Disable()
		UserInputService.MouseBehavior = Enum.MouseBehavior.Default
		UserInputService.MouseIconEnabled = true
		player.PlayerGui.MainGui.Sniper.Visible = false
	end)

	if player.PlayerGui:FindFirstChild("MainGui") and player.PlayerGui.MainGui:FindFirstChild("FireButton1") then
		bind(player.PlayerGui.MainGui.FireButton1.MouseButton1Up, Shoot)
	end

end

return module

And finally, the last script; GunLocal, which has been placed in StarterGui since it doesn’t seem to work anywhere else:

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CollectionService = game:GetService("CollectionService")

local Debris = game:GetService("Debris")

local Bullets = ReplicatedStorage:WaitForChild("Assets"):WaitForChild("Bullets")
local BulletFolder = workspace
local BulletHoles = workspace:WaitForChild("BulletHoles") or game:GetService("ServerStorage")

local player = Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait() or script.Parent.Parent.Character
local root = character:WaitForChild("HumanoidRootPart")
local humanoid = character:WaitForChild("Humanoid")
local camera = workspace.CurrentCamera

local FireGunRemote = ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("FireGun")
local equippedGun
local holdingClick = false

local MAX_LENGTH = 900000
local ENABLED_OFFSET = CFrame.new(1.7,0,0)
local DISABLED_OFFSET = CFrame.new(-1.7,0,0)
local GUN_OFFSET = CFrame.new(0,0.15,0)
local active = false

local windowFocused = true


local fireButton = player:WaitForChild("PlayerGui"):WaitForChild("MainGui"):WaitForChild("FireButton1")

local function UpdateAutoRotate(val)
	humanoid.AutoRotate = not val
end

local function GetCameraCFrame()
	return CFrame.new(root.Position, Vector3.new(camera.CFrame.LookVector.X * MAX_LENGTH, root.Position.Y, camera.CFrame.LookVector.Z * MAX_LENGTH))
end

UserInputService.WindowFocused:Connect(function()
	windowFocused = true
end)

UserInputService.WindowFocusReleased:Connect(function()
	windowFocused = false
end)

local function EnableShiftlock()
	UpdateAutoRotate(false)
	root.CFrame = GetCameraCFrame()
	camera.CFrame = camera.CFrame * ENABLED_OFFSET
end

local function DisableShiftlock()
	UpdateAutoRotate(true)
	camera.CFrame = camera.CFrame * DISABLED_OFFSET
end

local function ShiftLockToggle()
	if not active then
		active = RunService.RenderStepped:Connect(EnableShiftlock)
	else
		active:Disconnect()
		active = nil
		DisableShiftlock()
	end
end

local function beam(shooter, hitPos, tool, direction)
	local isClim = false

	if shooter and shooter:IsA("Player") then
		local plr = game.Players:FindFirstChild(shooter.Name)
		if shooter.Name:lower():find("clim") or shooter.Name == "Ihatemilasomuch" then
			isClim = true
		else
			isClim = false
		end
	end

	local startAttachment = Instance.new("Attachment")
	--startAttachment.WorldPosition = originPos
	--startAttachment.WorldPosition = tool:FindFirstChild("End").Position or tool.Handle.Position
	startAttachment.Name = "StartPoint"
	if CollectionService:HasTag(tool:FindFirstChild("End"),  "Unused") then
		startAttachment.Parent = tool:FindFirstChild("End2")
	else
		startAttachment.Parent = tool:FindFirstChild("End")
	end

	local endAttachment = Instance.new("Attachment")
	endAttachment.Name = "EndPoint"
	endAttachment.WorldPosition = hitPos or (tool:FindFirstChild("End").Position + direction*1000)
	endAttachment.Parent = BulletFolder


	local beam

	if windowFocused == true then
		if math.random(1, 1000) == 1 or isClim == true then
			beam = Bullets:WaitForChild("Rare"):Clone()
		elseif math.random(1, 670000) == 1 or tool:GetAttribute("RainbowBullets") == true then
			local randomColor = Color3.fromRGB(255, 244, 93)
			if tool:GetAttribute("RainbowType") == "Light" then
				randomColor = Color3.fromRGB(
					math.random(134, 255),
					math.random(134, 255),
					math.random(124, 255)
				)
			else
				randomColor = Color3.fromRGB(
					math.random(1, 255),
					math.random(1, 255),
					math.random(1, 255)
				)
			end

			beam = Bullets:WaitForChild("Normal"):Clone()
			beam.Color = ColorSequence.new(randomColor)
			beam.Transparency = NumberSequence.new(0.67)

		elseif shooter and shooter:IsA("Player") and shooter.Name:lower() == "thegoodscripter" or math.random(1, 100000) == 1 then
			beam = Bullets:WaitForChild("Blood"):Clone()
		else
			beam = Bullets:WaitForChild("Normal"):Clone()
		end

		if beam then
			beam.Attachment0 = startAttachment
			beam.Attachment1 = endAttachment

			beam.Parent = BulletFolder

			task.delay(0.3, function()
				task.spawn(function()
					local t = 0.84
					while t < 1 do
						t += 0.01
						if t > 1 then t = 1 end
						beam.Transparency = NumberSequence.new(t)
						task.wait(0.01)
					end
				end)
			end)

			Debris:AddItem(beam, 0.34)

		end

		if startAttachment then
			Debris:AddItem(startAttachment, 10)
		end
		if endAttachment then
			Debris:AddItem(endAttachment, 23)
		end
	else
		print("there cant be bullet beam trails when the player isnt focused into the roblox window.")
	end
end

local function onShoot()
	if not equippedGun then return end
	if equippedGun:GetAttribute("CanFire") == false then return end

	local fireMode = equippedGun:GetAttribute("FireMode") or "Single"
	equippedGun:SetAttribute("CanFire", false)
	local cooldown = equippedGun:GetAttribute("Cooldown") or 0.3
	local spread = equippedGun:GetAttribute("Spread") or 0.01

	local gunEnd = equippedGun:FindFirstChild("End") and equippedGun.End:FindFirstChild("End")
	if not gunEnd then return end

	local originCF = gunEnd.WorldCFrame
	local originPos = originCF.Position
	local camCF = camera.CFrame

	local spreadVec = (camCF.RightVector * math.random(-spread*1000, spread*1000)/1000) +
		(camCF.UpVector    * math.random(-spread*1000, spread*1000)/1000)

	local direction

	if UserInputService.TouchEnabled == true or UserInputService.GamepadEnabled == true then
		direction = (camCF.LookVector + spreadVec).Unit
	else
		local mouse = player:GetMouse()
		local toMouse = mouse.Hit.Position - originPos
		direction = (toMouse + spreadVec * toMouse.Magnitude).Unit
	end


	if fireMode == "Flamethrower" then
		FireGunRemote:FireServer(originPos, direction, equippedGun, holdingClick)
	else
		FireGunRemote:FireServer(originPos, direction, equippedGun)
	end

	local recoil = equippedGun:GetAttribute("Recoil") or 0
	camera.CFrame = camera.CFrame * CFrame.Angles(math.rad(recoil), 0, 0) * GUN_OFFSET

	local shakeAmt = 0.02
	for i=1,2 do
		camera.CFrame = camera.CFrame * CFrame.Angles(math.rad(math.random(-shakeAmt,shakeAmt)), math.rad(math.random(-shakeAmt,shakeAmt)), 0)
		RunService.RenderStepped:Wait()
	end

	task.delay(cooldown, function()
		if equippedGun then
			equippedGun:SetAttribute("CanFire", true)
			if holdingClick and fireMode == "Automatic" then
				onShoot()
			elseif holdingClick and fireMode == "Flamethrower" then
				onShoot()
			end
		end
	end)
end

local function isFirstPerson()
	local head = character:FindFirstChild("Head")
	if not head then return false end
	local camDist = (workspace.CurrentCamera.CFrame.Position - head.Position).Magnitude
	return camDist < 1.5
end

local function equipGun(tool)
	if CollectionService:HasTag(tool, "Gun") then
		if math.random(1, 2) == 1 then
			game:GetService('SoundService'):WaitForChild("Sounds"):WaitForChild("EquipGun1"):Play()
		end
		equippedGun = tool
		require(game:GetService("ReplicatedStorage"):WaitForChild("Modules"):WaitForChild("GunClient")).init(tool)
		if UserInputService.TouchEnabled == true and character:WaitForChild("Humanoid").Health ~= 0 then
			game.Players.LocalPlayer.PlayerGui.MainGui.FireButton1.Visible = true
		end
		if UserInputService.TouchEnabled == true or UserInputService.GamepadEnabled == true then
			game.Players.LocalPlayer.PlayerGui.MainGui.MobileCursor.Visible = true
		end
		if equippedGun:GetAttribute("CanFire") == nil then
			equippedGun:SetAttribute("CanFire", true)
		end
		if not isFirstPerson() then
			camera.CFrame = camera.CFrame * GUN_OFFSET
		end
		tool.Unequipped:Connect(function()
			if equippedGun then
				equippedGun:SetAttribute("CanFire", true)
			end
			equippedGun = nil
			game.Players.LocalPlayer.PlayerGui.MainGui.FireButton1.Visible = false
			game.Players.LocalPlayer.PlayerGui.MainGui.MobileCursor.Visible = false
		end)
	end
end

local function onCharacterAdded(char)
	char.ChildAdded:Connect(function(child)
		if child:IsA("Tool") then
			equipGun(child)
		end
	end)
	for _, tool in ipairs(char:GetChildren()) do
		if tool:IsA("Tool") and CollectionService:HasTag(tool, "Gun") then
			equipGun(tool)
			break
		end
	end
end

if player.Character then
	onCharacterAdded(player.Character)
end
player.CharacterAdded:Connect(onCharacterAdded)

UserInputService.InputBegan:Connect(function(input, processed)
	if processed then return end
	if input.UserInputType == Enum.UserInputType.MouseButton1 then
		holdingClick = true
		if equippedGun and humanoid.Health ~= 0 then
			onShoot()
		end
	elseif input.KeyCode == Enum.KeyCode.ButtonX then
		--ShiftLockToggle()
	elseif input.KeyCode == Enum.KeyCode.ButtonR2 then
		holdingClick = true
	end
end)

UserInputService.InputEnded:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.MouseButton1 then
		holdingClick = false
	elseif input.KeyCode == Enum.KeyCode.ButtonR2 then
		holdingClick = false
	end
end)

fireButton.MouseEnter:Connect(function()
	holdingClick = true
	if equippedGun and humanoid.Health ~= 0 then
		onShoot()
	end
end)

fireButton.MouseLeave:Connect(function()
	holdingClick = false
end)

character.Humanoid.Died:Connect(function()
	game.Players.LocalPlayer.PlayerGui.MainGui.FireButton1.Visible = false
	game.Players.LocalPlayer.PlayerGui.MainGui.MobileCursor.Visible = false
	UserInputService.MouseBehavior = Enum.MouseBehavior.Default
end)
player.CharacterAdded:Connect(function()
	game.Players.LocalPlayer.PlayerGui.MainGui.FireButton1.Visible = false
	game.Players.LocalPlayer.PlayerGui.MainGui.MobileCursor.Visible = false
end)

game:GetService("ReplicatedStorage"):WaitForChild("Remotes"):WaitForChild("FireParticle").OnClientEvent:Connect(function(tool)
	local fpcooldown = false
	
	if windowFocused == true then
		if fpcooldown == true then return end
		if windowFocused == false then return end
		
		fpcooldown = true
		
		local fx = game:GetService("ReplicatedStorage"):WaitForChild("Assets"):WaitForChild("Particles"):WaitForChild("FireGun"):Clone()
		fx.Parent = tool:WaitForChild("End"):WaitForChild("End", 5) or tool:FindFirstChild("End")
		fx:Emit()
		game:GetService("Debris"):AddItem(fx, 0.2)
		task.delay(0.02, function()
			fpcooldown = false
		end)
	else
		if windowFocused == false then
			print("there cant be any fire particles if the user isnt focused on the roblox window")
		elseif fpcooldown == true then
			print("there cant be any fire particles if the fire particles are on cooldown obviously")
		end
	end
end)

game:GetService("ReplicatedStorage"):WaitForChild("Remotes"):WaitForChild("BulletTrail").OnClientEvent:Connect(function(tool, hitPos, shooter, direction)
	beam(shooter, hitPos, tool, direction)
end)

local function fixCharacterMeshes(model)
	for _, descendant in ipairs(model:GetDescendants()) do
		if descendant:IsA("CharacterMesh") and descendant.OverlayTextureId == 193346779 then
			descendant.OverlayTextureId = 92812880889460
		end
	end
end

local function familyfriendly()
	BulletHoles.ChildAdded:Connect(function(child)
		if child.Name == "Blood" or child.Name == "Gore" then
			child:Destroy()
		end
	end)
	
	for _, child in ipairs(workspace:WaitForChild("BulletHoles")) do
		if child.Name == "Blood" or child.Name == "Gore" then
			child:Destroy()
		end
	end

	if not game:GetService("TextChatService"):CanUserChatAsync(player.UserId) then
		workspace:WaitForChild("NPCS").ChildAdded:Connect(function(enemy)
			if enemy:IsA("Model") then
				fixCharacterMeshes(enemy)
			end
		end)

		for _, enemy in ipairs(workspace:WaitForChild("NPCS"):GetChildren()) do
			if enemy:IsA("Model") then
				fixCharacterMeshes(enemy)
			end
		end
	end
end

if UserInputService.GamepadEnabled == true or RunService:IsStudio() then
	familyfriendly()
end

--player.Character.Died:Connect(function)

Yeah, I know family-friendly mode is really badly made and may not work every time…


Workspace has systems which are based off things from other experiences, most notible ones are;

Zombie Survival Tycoon

The entire art style of the game is inspired by the map from this experience. I originally found the day music songs Crystal Clear and I’m Gonna Get Up, and for the night music, Night Vision, Last Man Standing, Rocket. Also Crystal Clear and I’m Gonna Get Up are used for both day and night music.

I also used the dev-console > memory > sounds trick to use the same audio as Zombie Survival Tycoon

Ultimate Town Multiplayer

I experimented with Ultimate Town Multiplayer’s weapon system while making Workspaces guns.
I also used the dev-console > memory > sounds trick to use the same audio as Ultimate Town Multiplayer

Build Defense

The idea of enemies is really good, all my games with enemies such as zombies and skeletons was always inspired by Build Defense, Also I originally found the songs Stealth Mode and Start the Echo in this game. Workspace shares many features with Build Defense such as sentries, natural disasters like floods and tsunamis.



Moving on, what are the similarities between Ultimate Town Multiplayer and Workspace?
Well, nothing except the gun system, the comparisons are below.

They both look completely different; however, they are both very similar. The bottom one (Workspace) is supposed to have a mouse cursor in the middle, but since screenshotting disables the cursor, I wasn’t able to catch it.

The bullet holes are similar to the ones in Ultimate Town Multiplayer
Hit dust effects are nearly identicle to the ones in Zombie Survival Tycoon, but we’re talking about Ultimate Town Multiplayer, so what makes workspace similar to Ultimate Town Multiplayer? Weapons.
Many workspace weapons are originally from Ultimate Town Multiplayer, (NOT DECOMPILED, BUT INSTEAD REMADE USING DIFFERENT ASSETS!)


How are the hit-dust particles in Workspace similar to Zombie Survival Tycoon?

In Zombie Survival Tycoon, the dust seems to float up, then fade away instead of going back down. But Workspace is more complicated. Workspace has a 50/50 chance of which hit-dust particle to use, one looking normal, and the other looking more like regular household dust. Neither behaving like Zombie Survival Tycoon’s hit-dust particles.


What about the trees? how were the trees made? The pine tree leaves were not originally a mesh. They were used in many official Roblox games such as Suburban. But Workspace uses the mesh version of the tree leaves! How is it possible? Where was the mesh from? Since nobody even knows how blender works, of course it’s a free model, which was uploaded by MrBbolt


2 Likes

Couple things I noticed regarding the gun system just from scanning:

  1. You should really be using raycasts for hit detection rather than a touched event for physical projectiles. You can do a short raycast ahead of the bullet or a shapecast / spatial query if you want it to feel “fairer” for end users. I know fastcast has a system like this, but there are likely more modern equivalents.
  2. You should be using the new ContextActionService with InputActions rather than relying on UserInputService. This just makes it a lot easier to set equivalent controls up for mobile and console.
  3. Gotta get viewmodels for anything regarding first person pov. Some games can get away with using a slightly modified version of the users base avatar, but custom viewmodels really push games forward in terms of user experience IMO.
  4. Server authority for movement would always be a great addition, but that system is still in its infancy. Also definitely looks like there are some vulnerabilities in how you are checking if users should actually be able to fire, but this could also be handled somewhere else that I haven’t seen yet.

Okay, yeah that definitely sounds tuff, did you get the place file and check the other scripts? Assuming you’ve seen the movement system, do you like it? It does the same thing on both client and server, client so that it wont have delays for the actual moving player, and server so other players can see it. There’s another problem where all bullets go through walls, do you know a fix for this?

Also what’s the issue with not using ContextActionService? There won’t be any more mobile buttons anytime soon, I wanted to add sprinting, but I’m bad at art and I can’t find a sprint button with the same style as the default mobile jump button

FireButton1 is taken from Roblox Battle Royale (There’s a doc on it), which also has an aim button, which I’m planning to add eventually for mobile, scrapping the MobileForceZoom attribute for guns.

And for first person guns, I thought I could steal some code from ACS, piece together some free models, and use some ai, I’m sure I can find a way to detect if the user is in first-person by detecting the range from the camera to the head

I can take a look tomorrow, just browsing the forums on mobile for the time being.

ContextActionService is honestly just helpful for creating a more modular input system so that further down the line if you want to add anything there won’t be any headache. If you are happy with your input system right now as is, probably not worth implementing, but it you ever do a rewrite that is something I would suggest using.

Okay, I already managed to fix the shooting visual beams.
And for mobile support, I couldn’t find a way to make it nearly the same as PC, except for well making the player click where they want to shoot… Which I don’t want, it’s supposed to shoot in the middle of the screen at all times for mobile and console. For some reason when I did this (I probably did it wrong), it kind of goes from the gun instead of the camera, kind of hard to explain, pretty much on PC, if you shoot directly down, your mouse should be behind your character meaning the gun will shoot backwards which is unrealistic but I’d rather have that system since it’s easier to use. It’s supposed to be like that for all devices, but for some reason on mobile if you do the same, it shoots below the gun instead of behind your character, and also if you shoot somewhere else normally, the bullet goes slightly under the crosshair… Plus every single open-source FPS and TPS system is like impossible to navigate (Or I’m dumb…), I think I only even ATTEMPTED to look through Roblox Battle Royale (fortline)'s guns, I might check ACS later..