- 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>
543 lines
16 KiB
Lua
543 lines
16 KiB
Lua
-- ═══════════════════════════════════════════════════════════════════
|
|
-- MINI CALL OF DUTY - FPS Game Setup (Part 3: Enemy AI + Server Handler)
|
|
-- ═══════════════════════════════════════════════════════════════════
|
|
|
|
local SSS = game:GetService("ServerScriptService")
|
|
local RS = game:GetService("ReplicatedStorage")
|
|
|
|
-- ═══════════════════════════════════════════════════════════════
|
|
-- SERVER GAME HANDLER
|
|
-- ═══════════════════════════════════════════════════════════════
|
|
local serverScript = Instance.new("Script")
|
|
serverScript.Name = "GameServer"
|
|
serverScript.Parent = SSS
|
|
serverScript.Source = [[
|
|
local Players = game:GetService("Players")
|
|
local RS = game:GetService("ReplicatedStorage")
|
|
local Events = RS:WaitForChild("Events")
|
|
local Shared = RS:WaitForChild("Shared")
|
|
local WeaponData = require(Shared:WaitForChild("WeaponData"))
|
|
|
|
local scores = {}
|
|
local killFeed = {}
|
|
|
|
-- Player setup
|
|
Players.PlayerAdded:Connect(function(player)
|
|
scores[player.UserId] = {kills = 0, deaths = 0, streak = 0}
|
|
|
|
player.CharacterAdded:Connect(function(char)
|
|
task.wait(0.5)
|
|
local hum = char:WaitForChild("Humanoid")
|
|
hum.MaxHealth = 100
|
|
hum.Health = 100
|
|
hum.WalkSpeed = 20
|
|
|
|
-- Give starter weapon visual
|
|
local tool = Instance.new("Tool")
|
|
tool.Name = "M4A1"
|
|
tool.RequiresHandle = true
|
|
tool.CanBeDropped = false
|
|
|
|
local handle = Instance.new("Part")
|
|
handle.Name = "Handle"
|
|
handle.Size = Vector3.new(0.5, 0.5, 3)
|
|
handle.Color = Color3.fromRGB(40, 40, 40)
|
|
handle.Material = Enum.Material.SmoothPlastic
|
|
handle.CanCollide = false
|
|
handle.Anchored = false
|
|
handle.Parent = tool
|
|
tool.Parent = player.Backpack
|
|
|
|
-- Gun body
|
|
local barrel = Instance.new("Part")
|
|
barrel.Name = "Barrel"
|
|
barrel.Size = Vector3.new(0.2, 0.2, 2)
|
|
barrel.Color = Color3.fromRGB(30, 30, 30)
|
|
barrel.Material = Enum.Material.Metal
|
|
barrel.CanCollide = false
|
|
barrel.Anchored = false
|
|
local weld = Instance.new("WeldConstraint")
|
|
weld.Part0 = handle
|
|
weld.Part1 = barrel
|
|
weld.Parent = barrel
|
|
barrel.CFrame = handle.CFrame * CFrame.new(0, 0.1, -2)
|
|
barrel.Parent = tool
|
|
|
|
-- Magazine
|
|
local mag = Instance.new("Part")
|
|
mag.Name = "Magazine"
|
|
mag.Size = Vector3.new(0.3, 0.8, 0.4)
|
|
mag.Color = Color3.fromRGB(35, 35, 35)
|
|
mag.Material = Enum.Material.SmoothPlastic
|
|
mag.CanCollide = false
|
|
mag.Anchored = false
|
|
local weld2 = Instance.new("WeldConstraint")
|
|
weld2.Part0 = handle
|
|
weld2.Part1 = mag
|
|
weld2.Parent = mag
|
|
mag.CFrame = handle.CFrame * CFrame.new(0, -0.5, -0.5)
|
|
mag.Parent = tool
|
|
|
|
-- Health regeneration
|
|
task.spawn(function()
|
|
while hum and hum.Health > 0 do
|
|
task.wait(3)
|
|
if hum.Health < hum.MaxHealth and hum.Health > 0 then
|
|
hum.Health = math.min(hum.MaxHealth, hum.Health + 5)
|
|
end
|
|
end
|
|
end)
|
|
|
|
-- Death handler
|
|
hum.Died:Connect(function()
|
|
scores[player.UserId].deaths = scores[player.UserId].deaths + 1
|
|
scores[player.UserId].streak = 0
|
|
|
|
-- Respawn after 5 seconds
|
|
task.delay(5, function()
|
|
if player then
|
|
player:LoadCharacter()
|
|
end
|
|
end)
|
|
end)
|
|
end)
|
|
end)
|
|
|
|
-- Handle hit events from clients
|
|
Events:WaitForChild("ShootEvent").OnServerEvent:Connect(function(player, data)
|
|
-- Validate the shot
|
|
if not data or not data.hit then return end
|
|
|
|
local hitObj = data.hit
|
|
local weaponName = data.weapon or "M4A1"
|
|
local weapon = WeaponData[weaponName]
|
|
if not weapon then return end
|
|
|
|
-- Check range
|
|
local char = player.Character
|
|
if not char then return end
|
|
local dist = (data.hitPos - char.Head.Position).Magnitude
|
|
if dist > weapon.range then return end
|
|
|
|
-- Find the humanoid of what was hit
|
|
local targetHum = nil
|
|
local isHeadshot = false
|
|
|
|
if hitObj:IsA("Model") then
|
|
targetHum = hitObj:FindFirstChildOfClass("Humanoid")
|
|
elseif hitObj.Parent and hitObj.Parent:IsA("Model") then
|
|
targetHum = hitObj.Parent:FindFirstChildOfClass("Humanoid")
|
|
elseif hitObj.Parent and hitObj.Parent.Parent and hitObj.Parent.Parent:IsA("Model") then
|
|
targetHum = hitObj.Parent.Parent:FindFirstChildOfClass("Humanoid")
|
|
end
|
|
|
|
-- Check headshot
|
|
if hitObj.Name == "Head" and targetHum then
|
|
isHeadshot = true
|
|
end
|
|
|
|
-- Apply damage
|
|
if targetHum then
|
|
local dmg = weapon.damage
|
|
if isHeadshot then dmg = dmg * weapon.headshotMult end
|
|
|
|
targetHum:TakeDamage(dmg)
|
|
|
|
-- Check if killed
|
|
if targetHum.Health <= 0 then
|
|
local victim = nil
|
|
for _, p in ipairs(Players:GetPlayers()) do
|
|
if p.Character and p.Character:FindFirstChildOfClass("Humanoid") == targetHum then
|
|
victim = p
|
|
break
|
|
end
|
|
end
|
|
|
|
if victim then
|
|
scores[player.UserId].kills = scores[player.UserId].kills + 1
|
|
scores[player.UserId].streak = scores[player.UserId].streak + 1
|
|
end
|
|
|
|
-- Fire kill event
|
|
Events:WaitForChild("KillEvent"):FireAllClients({
|
|
killer = player.Name,
|
|
victim = victim and victim.Name or "Enemy",
|
|
weapon = weaponName,
|
|
headshot = isHeadshot,
|
|
streak = scores[player.UserId].streak,
|
|
})
|
|
end
|
|
|
|
-- Fire damage indicator
|
|
Events:WaitForChild("DamageEvent"):FireClient(player, {
|
|
hit = true,
|
|
headshot = isHeadshot,
|
|
damage = dmg,
|
|
})
|
|
end
|
|
end)
|
|
|
|
-- Game data request
|
|
Events:WaitForChild("GetGameData").OnServerInvoke = function(player)
|
|
return {
|
|
scores = scores,
|
|
killFeed = killFeed,
|
|
}
|
|
end
|
|
|
|
-- Kill feed relay
|
|
Events:WaitForChild("KillEvent").OnServerEvent:Connect(function(player, data)
|
|
table.insert(killFeed, 1, data)
|
|
if #killFeed > 5 then table.remove(killFeed) end
|
|
end)
|
|
|
|
-- ═══════════════════════════════════════════════════════════════
|
|
-- ENEMY AI SYSTEM
|
|
-- ═══════════════════════════════════════════════════════════════
|
|
local enemySpawns = {
|
|
Vector3.new(60, 2, 80), Vector3.new(-60, 2, 80),
|
|
Vector3.new(70, 2, -80), Vector3.new(-70, 2, -80),
|
|
Vector3.new(0, 2, 100), Vector3.new(0, 2, -80),
|
|
Vector3.new(50, 2, 0), Vector3.new(-50, 2, 0),
|
|
Vector3.new(-40, 2, 50), Vector3.new(40, 2, -50),
|
|
Vector3.new(-80, 2, 30), Vector3.new(80, 2, -30),
|
|
}
|
|
|
|
local enemies = {}
|
|
local MAX_ENEMIES = 10
|
|
local SPAWN_INTERVAL = 8
|
|
|
|
local function createEnemy(pos)
|
|
local model = Instance.new("Model")
|
|
model.Name = "Enemy_" .. tostring(#enemies + 1)
|
|
|
|
-- Humanoid
|
|
local hum = Instance.new("Humanoid")
|
|
hum.MaxHealth = 80
|
|
hum.Health = 80
|
|
hum.WalkSpeed = 14
|
|
hum.Parent = model
|
|
|
|
-- Head
|
|
local head = Instance.new("Part")
|
|
head.Name = "Head"
|
|
head.Size = Vector3.new(1.5, 1.5, 1.5)
|
|
head.Color = Color3.fromRGB(180, 140, 110)
|
|
head.Material = Enum.Material.SmoothPlastic
|
|
head.CanCollide = true
|
|
head.Parent = model
|
|
local headMesh = Instance.new("SpecialMesh")
|
|
headMesh.MeshType = Enum.MeshType.Head
|
|
headMesh.Scale = Vector3.new(1.25, 1.25, 1.25)
|
|
headMesh.Parent = head
|
|
|
|
-- Torso
|
|
local torso = Instance.new("Part")
|
|
torso.Name = "HumanoidRootPart"
|
|
torso.Size = Vector3.new(2, 2, 1)
|
|
torso.Color = Color3.fromRGB(60, 80, 40) -- Military green
|
|
torso.Material = Enum.Material.SmoothPlastic
|
|
torso.CanCollide = true
|
|
torso.Parent = model
|
|
|
|
-- Legs
|
|
local lleg = Instance.new("Part")
|
|
lleg.Name = "Left Leg"
|
|
lleg.Size = Vector3.new(1, 2, 1)
|
|
lleg.Color = Color3.fromRGB(50, 55, 45)
|
|
lleg.Material = Enum.Material.SmoothPlastic
|
|
lleg.Parent = model
|
|
|
|
local rleg = Instance.new("Part")
|
|
rleg.Name = "Right Leg"
|
|
rleg.Size = Vector3.new(1, 2, 1)
|
|
rleg.Color = Color3.fromRGB(50, 55, 45)
|
|
rleg.Material = Enum.Material.SmoothPlastic
|
|
rleg.Parent = model
|
|
|
|
-- Arms
|
|
local larm = Instance.new("Part")
|
|
larm.Name = "Left Arm"
|
|
larm.Size = Vector3.new(1, 2, 1)
|
|
larm.Color = Color3.fromRGB(60, 80, 40)
|
|
larm.Material = Enum.Material.SmoothPlastic
|
|
larm.Parent = model
|
|
|
|
local rarm = Instance.new("Part")
|
|
rarm.Name = "Right Arm"
|
|
rarm.Size = Vector3.new(1, 2, 1)
|
|
rarm.Color = Color3.fromRGB(60, 80, 40)
|
|
rarm.Material = Enum.Material.SmoothPlastic
|
|
rarm.Parent = model
|
|
|
|
-- Motor6D connections
|
|
local function weld(part0, part1, c0, c1)
|
|
local m = Instance.new("Motor6D")
|
|
m.Part0 = part0
|
|
m.Part1 = part1
|
|
if c0 then m.C0 = c0 end
|
|
if c1 then m.C1 = c1 end
|
|
m.Parent = part0
|
|
end
|
|
|
|
weld(torso, head, CFrame.new(0, 1.5, 0), CFrame.new(0, 0, 0))
|
|
weld(torso, larm, CFrame.new(-1.5, 0, 0), CFrame.new(0.5, 0, 0))
|
|
weld(torso, rarm, CFrame.new(1.5, 0, 0), CFrame.new(-0.5, 0, 0))
|
|
weld(torso, lleg, CFrame.new(-0.5, -2, 0), CFrame.new(0, 1, 0))
|
|
weld(torso, rleg, CFrame.new(0.5, -2, 0), CFrame.new(0, 1, 0))
|
|
|
|
-- Beret/hat
|
|
local hat = Instance.new("Part")
|
|
hat.Name = "Hat"
|
|
hat.Size = Vector3.new(1.8, 0.5, 1.8)
|
|
hat.Color = Color3.fromRGB(40, 50, 30)
|
|
hat.Material = Enum.Material.SmoothPlastic
|
|
hat.CanCollide = false
|
|
hat.Parent = model
|
|
local hatWeld = Instance.new("WeldConstraint")
|
|
hatWeld.Part0 = head
|
|
hatWeld.Part1 = hat
|
|
hatWeld.Parent = hat
|
|
hat.CFrame = head.CFrame * CFrame.new(0, 0.9, 0)
|
|
|
|
-- Health bar above head
|
|
local billboard = Instance.new("BillboardGui")
|
|
billboard.Name = "HealthBar"
|
|
billboard.Size = UDim2.new(3, 0, 0.4, 0)
|
|
billboard.StudsOffset = Vector3.new(0, 3.5, 0)
|
|
billboard.AlwaysOnTop = true
|
|
billboard.Parent = head
|
|
|
|
local bg = Instance.new("Frame")
|
|
bg.Size = UDim2.new(1, 0, 1, 0)
|
|
bg.BackgroundColor3 = Color3.fromRGB(40, 40, 40)
|
|
bg.BorderSizePixel = 0
|
|
bg.Parent = billboard
|
|
|
|
local fill = Instance.new("Frame")
|
|
fill.Name = "Fill"
|
|
fill.Size = UDim2.new(1, 0, 1, 0)
|
|
fill.BackgroundColor3 = Color3.fromRGB(200, 30, 30)
|
|
fill.BorderSizePixel = 0
|
|
fill.Parent = bg
|
|
|
|
-- AI Script
|
|
local aiScript = Instance.new("Script")
|
|
aiScript.Source = [[
|
|
local humanoid = script.Parent:FindFirstChildOfClass("Humanoid")
|
|
local rootPart = script.Parent:FindFirstChild("HumanoidRootPart")
|
|
local head = script.Parent:FindFirstChild("Head")
|
|
local healthFill = script.Parent:FindFirstChild("Head"):FindFirstChild("HealthBar"):FindFirstChild("Frame"):FindFirstChild("Fill")
|
|
|
|
if not humanoid or not rootPart then return end
|
|
|
|
local state = "patrol"
|
|
local target = nil
|
|
local lastShot = 0
|
|
local fireRate = 1.2
|
|
local damage = 12
|
|
local detectionRange = 80
|
|
local attackRange = 60
|
|
local patrolPoints = {}
|
|
local currentPatrolIndex = 1
|
|
|
|
-- Generate patrol points
|
|
for i = 1, 6 do
|
|
local angle = math.rad(math.random(360))
|
|
local dist = math.random(15, 60)
|
|
table.insert(patrolPoints, rootPart.Position + Vector3.new(math.cos(angle)*dist, 0, math.sin(angle)*dist))
|
|
end
|
|
|
|
-- Find closest player
|
|
local function findTarget()
|
|
local closest = nil
|
|
local closestDist = detectionRange
|
|
for _, player in ipairs(game:GetService("Players"):GetPlayers()) do
|
|
if player.Character then
|
|
local hum = player.Character:FindFirstChildOfClass("Humanoid")
|
|
if hum and hum.Health > 0 then
|
|
local dist = (player.Character.Head.Position - rootPart.Position).Magnitude
|
|
if dist < closestDist then
|
|
closest = player
|
|
closestDist = dist
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return closest, closestDist
|
|
end
|
|
|
|
-- Update health bar
|
|
humanoid.HealthChanged:Connect(function(health)
|
|
local pct = health / humanoid.MaxHealth
|
|
healthFill.Size = UDim2.new(pct, 0, 1, 0)
|
|
if pct < 0.3 then
|
|
healthFill.BackgroundColor3 = Color3.fromRGB(200, 30, 30)
|
|
else
|
|
healthFill.BackgroundColor3 = Color3.fromRGB(200, 60, 30)
|
|
end
|
|
end)
|
|
|
|
-- Main AI loop
|
|
while humanoid and humanoid.Health > 0 do
|
|
task.wait(0.3)
|
|
|
|
target, _ = findTarget()
|
|
|
|
if target and target.Character then
|
|
local targetHum = target.Character:FindFirstChildOfClass("Humanoid")
|
|
local targetHead = target.Character:FindFirstChild("Head")
|
|
|
|
if targetHum and targetHum.Health > 0 and targetHead then
|
|
local dist = (targetHead.Position - rootPart.Position).Magnitude
|
|
|
|
-- Face target
|
|
rootPart.CFrame = CFrame.new(rootPart.Position, Vector3.new(targetHead.Position.X, rootPart.Position.Y, targetHead.Position.Z))
|
|
|
|
if dist <= attackRange then
|
|
state = "attack"
|
|
-- Shoot at player
|
|
if tick() - lastShot > fireRate then
|
|
lastShot = tick()
|
|
|
|
-- Raycast to player
|
|
local direction = (targetHead.Position - head.Position).Unit
|
|
local spread = Vector3.new(math.random()-0.5, math.random()-0.5, math.random()-0.5) * 2
|
|
local hitResult = workspace:Raycast(head.Position, (direction + spread) * attackRange)
|
|
|
|
if hitResult then
|
|
-- Muzzle flash
|
|
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 = head.Position + direction * 2
|
|
flash.Parent = workspace
|
|
game:GetService("Debris"):AddItem(flash, 0.08)
|
|
|
|
-- Bullet trail
|
|
local trail = Instance.new("Part")
|
|
trail.Size = Vector3.new(0.1, 0.1, (head.Position - hitResult.Position).Magnitude)
|
|
trail.CFrame = CFrame.new(head.Position, hitResult.Position) * CFrame.new(0, 0, -trail.Size.Z/2)
|
|
trail.Anchored = true
|
|
trail.CanCollide = false
|
|
trail.Color = Color3.fromRGB(255, 200, 100)
|
|
trail.Material = Enum.Material.Neon
|
|
trail.Transparency = 0.3
|
|
trail.Parent = workspace
|
|
game:GetService("Debris"):AddItem(trail, 0.1)
|
|
|
|
-- Check if hit player
|
|
local hitChar = hitResult.Instance
|
|
if hitChar then
|
|
local hitHum = nil
|
|
if hitChar.Parent and hitChar.Parent:FindFirstChildOfClass("Humanoid") then
|
|
hitHum = hitChar.Parent:FindFirstChildOfClass("Humanoid")
|
|
elseif hitChar.Parent and hitChar.Parent.Parent and hitChar.Parent.Parent:FindFirstChildOfClass("Humanoid") then
|
|
hitHum = hitChar.Parent.Parent:FindFirstChildOfClass("Humanoid")
|
|
end
|
|
|
|
if hitHum and hitHum ~= humanoid then
|
|
hitHum:TakeDamage(damage)
|
|
end
|
|
end
|
|
|
|
-- 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 = hitResult.Position
|
|
impact.Parent = workspace
|
|
game:GetService("Debris"):AddItem(impact, 0.15)
|
|
end
|
|
end
|
|
else
|
|
-- Move toward target
|
|
state = "chase"
|
|
humanoid:MoveTo(targetHead.Position)
|
|
end
|
|
end
|
|
else
|
|
-- Patrol
|
|
state = "patrol"
|
|
if #patrolPoints > 0 then
|
|
humanoid:MoveTo(patrolPoints[currentPatrolIndex])
|
|
humanoid.MoveToFinished:Connect(function(reached)
|
|
if reached then
|
|
currentPatrolIndex = (currentPatrolIndex % #patrolPoints) + 1
|
|
end
|
|
end)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Death effect
|
|
local root = script.Parent:FindFirstChild("HumanoidRootPart")
|
|
if root then
|
|
for _, v in ipairs(script.Parent:GetDescendants()) do
|
|
if v:IsA("BasePart") then
|
|
v.Anchored = false
|
|
v.BrickColor = BrickColor.new("Dark stone grey")
|
|
local bf = Instance.new("BodyForce")
|
|
bf.Force = Vector3.new(math.random(-50,50), 100, math.random(-50,50))
|
|
bf.Parent = v
|
|
game:GetService("Debris"):AddItem(v, 3)
|
|
end
|
|
end
|
|
end
|
|
|
|
task.delay(3, function()
|
|
if script.Parent then script.Parent:Destroy() end
|
|
end)
|
|
]]
|
|
aiScript.Parent = model
|
|
|
|
-- Position
|
|
local primary = torso
|
|
model.PrimaryPart = primary
|
|
primary.Position = pos
|
|
head.Position = pos + Vector3.new(0, 2.5, 0)
|
|
lleg.Position = pos + Vector3.new(-0.5, -1, 0)
|
|
rleg.Position = pos + Vector3.new(0.5, -1, 0)
|
|
larm.Position = pos + Vector3.new(-1.5, 0, 0)
|
|
rarm.Position = pos + Vector3.new(1.5, 0, 0)
|
|
|
|
model.Parent = workspace
|
|
table.insert(enemies, model)
|
|
return model
|
|
end
|
|
|
|
-- Enemy spawner loop
|
|
task.spawn(function()
|
|
task.wait(5) -- Initial delay
|
|
while true do
|
|
-- Remove dead enemies
|
|
for i = #enemies, 1, -1 do
|
|
if not enemies[i] or not enemies[i]:FindFirstChildOfClass("Humanoid")
|
|
or enemies[i]:FindFirstChildOfClass("Humanoid").Health <= 0 then
|
|
table.remove(enemies, i)
|
|
end
|
|
end
|
|
|
|
-- Spawn new enemies
|
|
if #enemies < MAX_ENEMIES then
|
|
local pos = enemySpawns[math.random(#enemySpawns)]
|
|
createEnemy(pos + Vector3.new(math.random(-5,5), 0, math.random(-5,5)))
|
|
end
|
|
|
|
task.wait(SPAWN_INTERVAL)
|
|
end
|
|
end)
|
|
|
|
print("[GameServer] Enemy AI system active. Spawning " .. MAX_ENEMIES .. " enemies.")
|
|
]]
|
|
|
|
print("[CoD FPS] Part 3/5 complete: Server handler + Enemy AI created.")
|