- Updated RobloxMCPPlugin with HTTP polling (auto-enables HttpService) - Added 20-weapon FPS game example (CoD-style) - Added Python studio-inject.py for command bar injection via Win32 API - Added auto-connect setup scripts (VBS + PowerShell) - Updated MCP server with all FPS game tools Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
384 lines
11 KiB
Lua
384 lines
11 KiB
Lua
-- ═══════════════════════════════════════════════════════════════════
|
|
-- MINI CALL OF DUTY - FPS Game Setup (Part 2: Weapon System)
|
|
-- ═══════════════════════════════════════════════════════════════════
|
|
|
|
local RS = game:GetService("ReplicatedStorage")
|
|
|
|
-- ═══════════════════════════════════════════════════════════════
|
|
-- WEAPON DATA MODULE
|
|
-- ═══════════════════════════════════════════════════════════════
|
|
local weaponData = Instance.new("ModuleScript")
|
|
weaponData.Name = "WeaponData"
|
|
weaponData.Parent = RS:FindFirstChild("Shared")
|
|
weaponData.Source = [[
|
|
local Weapons = {
|
|
M4A1 = {
|
|
name = "M4A1",
|
|
displayName = "M4A1 Carbine",
|
|
damage = 25,
|
|
fireRate = 0.09, -- seconds between shots
|
|
reloadTime = 2.2,
|
|
magSize = 30,
|
|
maxAmmo = 210,
|
|
range = 300,
|
|
headshotMult = 2.5,
|
|
recoil = {x = 0.8, y = 1.2},
|
|
spread = {hip = 3, ads = 0.5},
|
|
aimSpeed = 0.15,
|
|
moveSpeedMult = 0.95,
|
|
automatic = true,
|
|
adsFOV = 50,
|
|
},
|
|
AK47 = {
|
|
name = "AK-47",
|
|
displayName = "AK-47",
|
|
damage = 33,
|
|
fireRate = 0.1,
|
|
reloadTime = 2.5,
|
|
magSize = 30,
|
|
maxAmmo = 210,
|
|
range = 280,
|
|
headshotMult = 2.0,
|
|
recoil = {x = 1.2, y = 1.8},
|
|
spread = {hip = 4, ads = 0.8},
|
|
aimSpeed = 0.18,
|
|
moveSpeedMult = 0.92,
|
|
automatic = true,
|
|
adsFOV = 48,
|
|
},
|
|
Sniper = {
|
|
name = "AWP",
|
|
displayName = "AWP Sniper",
|
|
damage = 95,
|
|
fireRate = 1.2,
|
|
reloadTime = 3.5,
|
|
magSize = 5,
|
|
maxAmmo = 30,
|
|
range = 800,
|
|
headshotMult = 3.0,
|
|
recoil = {x = 3, y = 5},
|
|
spread = {hip = 8, ads = 0.1},
|
|
aimSpeed = 0.25,
|
|
moveSpeedMult = 0.85,
|
|
automatic = false,
|
|
adsFOV = 20,
|
|
},
|
|
Shotgun = {
|
|
name = "SPAS-12",
|
|
displayName = "SPAS-12 Shotgun",
|
|
damage = 15, -- per pellet (8 pellets)
|
|
fireRate = 0.7,
|
|
reloadTime = 3.0,
|
|
magSize = 8,
|
|
maxAmmo = 40,
|
|
range = 50,
|
|
headshotMult = 1.5,
|
|
recoil = {x = 4, y = 6},
|
|
spread = {hip = 12, ads = 8},
|
|
aimSpeed = 0.15,
|
|
moveSpeedMult = 0.88,
|
|
automatic = false,
|
|
pellets = 8,
|
|
adsFOV = 55,
|
|
},
|
|
}
|
|
return Weapons
|
|
]]
|
|
|
|
-- ═══════════════════════════════════════════════════════════════
|
|
-- CLIENT WEAPON CONTROLLER (LocalScript)
|
|
-- ═══════════════════════════════════════════════════════════════
|
|
local weaponClient = Instance.new("LocalScript")
|
|
weaponClient.Name = "WeaponClient"
|
|
weaponClient.Parent = game:GetService("StarterPlayer"):FindFirstChild("StarterPlayerScripts")
|
|
|
|
weaponClient.Source = [[
|
|
local Players = game:GetService("Players")
|
|
local RS = game:GetService("ReplicatedStorage")
|
|
local UIS = game:GetService("UserInputService")
|
|
local RunService = game:GetService("RunService")
|
|
local Events = RS:WaitForChild("Events")
|
|
|
|
local player = Players.LocalPlayer
|
|
local camera = workspace.CurrentCamera
|
|
local Weapons = require(RS:WaitForChild("Shared"):WaitForChild("WeaponData"))
|
|
|
|
-- State
|
|
local currentWeapon = "M4A1"
|
|
local weapon = Weapons[currentWeapon]
|
|
local ammo = weapon.magSize
|
|
local reserveAmmo = weapon.maxAmmo
|
|
local isReloading = false
|
|
local isADS = false
|
|
local isSprinting = false
|
|
local isFiring = false
|
|
local lastFireTime = 0
|
|
local canShoot = true
|
|
|
|
-- Recoil tracking
|
|
local recoilX = 0
|
|
local recoilY = 0
|
|
local recoilRecoverySpeed = 8
|
|
|
|
-- Functions
|
|
local function updateHUD()
|
|
local hud = player.PlayerGui:FindFirstChild("FPS_HUD")
|
|
if not hud then return end
|
|
local frame = hud:FindFirstChild("MainFrame")
|
|
if not frame then return end
|
|
|
|
local ammoText = frame:FindFirstChild("AmmoDisplay")
|
|
if ammoText then ammoText.Text = ammo .. " / " .. reserveAmmo end
|
|
|
|
local weaponText = frame:FindFirstChild("WeaponName")
|
|
if weaponText then weaponText.Text = weapon.displayName end
|
|
|
|
local healthBar = frame:FindFirstChild("HealthBar")
|
|
local healthFill = frame:FindFirstChild("HealthFill")
|
|
if healthBar and healthFill then
|
|
local char = player.Character
|
|
local hum = char and char:FindFirstChildOfClass("Humanoid")
|
|
if hum then
|
|
local pct = hum.Health / hum.MaxHealth
|
|
healthFill.Size = UDim2.new(pct * 0.18, 0, 0.025, 0)
|
|
if pct < 0.3 then
|
|
healthFill.BackgroundColor3 = Color3.fromRGB(200, 30, 30)
|
|
elseif pct < 0.6 then
|
|
healthFill.BackgroundColor3 = Color3.fromRGB(200, 180, 30)
|
|
else
|
|
healthFill.BackgroundColor3 = Color3.fromRGB(30, 200, 30)
|
|
end
|
|
end
|
|
end
|
|
|
|
local scoreText = frame:FindFirstChild("ScoreDisplay")
|
|
if scoreText then scoreText.Text = "KILLS: " .. tostring(player:GetAttribute("Kills") or 0) end
|
|
end
|
|
|
|
local function shoot()
|
|
if isReloading or ammo <= 0 or not canShoot then return end
|
|
if tick() - lastFireTime < weapon.fireRate then return end
|
|
|
|
lastFireTime = tick()
|
|
ammo = ammo - 1
|
|
canShoot = false
|
|
|
|
-- Fire raycast
|
|
local mousePos = UIS:GetMouseLocation()
|
|
local ray = camera:ViewportPointToRay(mousePos.X, mousePos.Y)
|
|
|
|
local spreadMult = isADS and weapon.spread.ads or weapon.spread.hip
|
|
local spread = CFrame.new(
|
|
math.random(-100, 100) / 100 * spreadMult,
|
|
math.random(-100, 100) / 100 * spreadMult,
|
|
math.random(-100, 100) / 100 * spreadMult
|
|
) * 0.01
|
|
local direction = (ray.Direction.Unit + spread.Position).Unit
|
|
|
|
local pellets = weapon.pellets or 1
|
|
for _ = 1, pellets do
|
|
local hitRay = RaycastParams.new()
|
|
hitRay.FilterDescendantsInstances = {player.Character or {}}
|
|
hitRay.FilterType = Enum.RaycastFilterType.Exclude
|
|
|
|
local result = workspace:Raycast(ray.Origin, direction * weapon.range, hitRay)
|
|
if result then
|
|
Events:FindFirstChild("ShootEvent"):FireServer({
|
|
origin = ray.Origin,
|
|
direction = direction * weapon.range,
|
|
hit = result.Instance,
|
|
hitPos = result.Position,
|
|
normal = result.Normal,
|
|
weapon = currentWeapon,
|
|
})
|
|
|
|
-- Muzzle flash visual
|
|
local flash = Instance.new("Part")
|
|
flash.Size = Vector3.new(0.3, 0.3, 0.3)
|
|
flash.Shape = Enum.PartType.Ball
|
|
flash.Color = Color3.fromRGB(255, 200, 50)
|
|
flash.Material = Enum.Material.Neon
|
|
flash.Anchored = true
|
|
flash.CanCollide = false
|
|
flash.Position = camera.CFrame.Position + camera.CFrame.LookVector * 3
|
|
flash.Parent = workspace
|
|
game:GetService("Debris"):AddItem(flash, 0.05)
|
|
|
|
-- Bullet trail
|
|
local trail = Instance.new("Part")
|
|
trail.Size = Vector3.new(0.1, 0.1, weapon.range)
|
|
trail.CFrame = CFrame.new(camera.CFrame.Position, result.Position) * CFrame.new(0, 0, -weapon.range/2)
|
|
trail.Anchored = true
|
|
trail.CanCollide = false
|
|
trail.Color = Color3.fromRGB(255, 220, 100)
|
|
trail.Material = Enum.Material.Neon
|
|
trail.Transparency = 0.5
|
|
trail.Parent = workspace
|
|
game:GetService("Debris"):AddItem(trail, 0.03)
|
|
|
|
-- Impact effect
|
|
local impact = Instance.new("Part")
|
|
impact.Size = Vector3.new(0.5, 0.5, 0.5)
|
|
impact.Shape = Enum.PartType.Ball
|
|
impact.Color = Color3.fromRGB(255, 150, 50)
|
|
impact.Material = Enum.Material.Neon
|
|
impact.Anchored = true
|
|
impact.CanCollide = false
|
|
impact.Position = result.Position
|
|
impact.Parent = workspace
|
|
game:GetService("Debris"):AddItem(impact, 0.1)
|
|
end
|
|
end
|
|
|
|
-- Apply recoil
|
|
if isADS then
|
|
recoilX = recoilX - weapon.recoil.x * 0.4
|
|
recoilY = recoilY + weapon.recoil.y * 0.4
|
|
else
|
|
recoilX = recoilX - weapon.recoil.x
|
|
recoilY = recoilY + weapon.recoil.y
|
|
end
|
|
|
|
-- Screen shake
|
|
local shake = isADS and 0.002 or 0.005
|
|
camera.CFrame = camera.CFrame * CFrame.new(
|
|
math.random(-100,100)/100 * shake,
|
|
math.random(-100,100)/100 * shake,
|
|
0
|
|
)
|
|
|
|
task.wait(weapon.fireRate)
|
|
canShoot = true
|
|
updateHUD()
|
|
|
|
if ammo <= 0 then
|
|
reload()
|
|
end
|
|
end
|
|
|
|
function reload()
|
|
if isReloading or reserveAmmo <= 0 then return end
|
|
isReloading = true
|
|
|
|
Events:FindFirstChild("ReloadEvent"):FireServer()
|
|
|
|
task.wait(weapon.reloadTime)
|
|
|
|
local needed = weapon.magSize - ammo
|
|
local available = math.min(needed, reserveAmmo)
|
|
ammo = ammo + available
|
|
reserveAmmo = reserveAmmo - available
|
|
isReloading = false
|
|
updateHUD()
|
|
end
|
|
|
|
-- Input handling
|
|
UIS.InputBegan:Connect(function(input, processed)
|
|
if processed then return end
|
|
|
|
if input.UserInputType == Enum.UserInputType.MouseButton2 then
|
|
isADS = true
|
|
if isSprinting then isSprinting = false end
|
|
end
|
|
|
|
if input.UserInputType == Enum.UserInputType.MouseButton1 then
|
|
isFiring = true
|
|
if isSprinting then isSprinting = false end
|
|
end
|
|
|
|
if input.KeyCode == Enum.KeyCode.LeftShift then
|
|
if not isADS then isSprinting = true end
|
|
end
|
|
|
|
if input.KeyCode == Enum.KeyCode.LeftControl then
|
|
local char = player.Character
|
|
if char then
|
|
local hum = char:FindFirstChildOfClass("Humanoid")
|
|
if hum then hum.WalkSpeed = 8 end
|
|
end
|
|
end
|
|
|
|
if input.KeyCode == Enum.KeyCode.R then
|
|
reload()
|
|
end
|
|
|
|
-- Weapon switch: 1-4
|
|
if input.KeyCode == Enum.KeyCode.One then currentWeapon = "M4A1" end
|
|
if input.KeyCode == Enum.KeyCode.Two then currentWeapon = "AK47" end
|
|
if input.KeyCode == Enum.KeyCode.Three then currentWeapon = "Sniper" end
|
|
if input.KeyCode == Enum.KeyCode.Four then currentWeapon = "Shotgun" end
|
|
|
|
if input.KeyCode >= Enum.KeyCode.One and input.KeyCode <= Enum.KeyCode.Four then
|
|
weapon = Weapons[currentWeapon]
|
|
ammo = weapon.magSize
|
|
reserveAmmo = weapon.maxAmmo
|
|
isReloading = false
|
|
updateHUD()
|
|
end
|
|
end)
|
|
|
|
UIS.InputEnded:Connect(function(input)
|
|
if input.UserInputType == Enum.UserInputType.MouseButton2 then
|
|
isADS = false
|
|
end
|
|
if input.UserInputType == Enum.UserInputType.MouseButton1 then
|
|
isFiring = false
|
|
end
|
|
if input.KeyCode == Enum.KeyCode.LeftShift then
|
|
isSprinting = false
|
|
end
|
|
if input.KeyCode == Enum.KeyCode.LeftControl then
|
|
local char = player.Character
|
|
if char then
|
|
local hum = char:FindFirstChildOfClass("Humanoid")
|
|
if hum then hum.WalkSpeed = 20 end
|
|
end
|
|
end
|
|
end)
|
|
|
|
-- Main loop
|
|
RunService.RenderStepped:Connect(function()
|
|
-- Camera FOV for ADS
|
|
local targetFOV = isADS and weapon.adsFOV or 70
|
|
camera.FieldOfView = camera.FieldOfView + (targetFOV - camera.FieldOfView) * 0.2
|
|
|
|
-- Sprint speed
|
|
local char = player.Character
|
|
if char then
|
|
local hum = char:FindFirstChildOfClass("Humanoid")
|
|
if hum and hum.MoveDirection.Magnitude > 0 then
|
|
if isSprinting then
|
|
hum.WalkSpeed = 30
|
|
elseif not UIS:IsKeyDown(Enum.KeyCode.LeftControl) then
|
|
hum.WalkSpeed = 20
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Auto-fire
|
|
if isFiring and weapon.automatic then
|
|
shoot()
|
|
end
|
|
|
|
-- Recoil recovery
|
|
recoilX = recoilX + (0 - recoilX) * math.clamp(recoilRecoverySpeed * 0.01, 0, 1)
|
|
recoilY = recoilY + (0 - recoilY) * math.clamp(recoilRecoverySpeed * 0.01, 0, 1)
|
|
|
|
updateHUD()
|
|
end)
|
|
|
|
-- Lock mouse for FPS
|
|
UIS.MouseIconEnabled = false
|
|
|
|
player.CharacterAdded:Connect(function()
|
|
ammo = weapon.magSize
|
|
reserveAmmo = weapon.maxAmmo
|
|
updateHUD()
|
|
end)
|
|
|
|
updateHUD()
|
|
print("[WeaponClient] Loaded - Controls: LMB=Shoot, RMB=ADS, Shift=Sprint, Ctrl=Crouch, R=Reload, 1-4=Switch weapon")
|
|
]]
|
|
|
|
print("[CoD FPS] Part 2/5 complete: Weapon system created.")
|