Загрузка данных
local RunService = game:GetService("RunService")
local Toad = {}
Toad.__index = Toad
local FOLLOW_RADIUS = 8
local FOLLOW_TOLERANCE = 5
local AGGRO_RADIUS = 15
local LEASH_RADIUS = 20
local JUMP_HEIGHT = 5
local JUMP_TIME = 0.45
local ATTACK_DAMAGE = 10
local ATTACK_COOLDOWN = 0.5
local DECISION_COOLDOWN = 0.15
function Toad.new(model, owner, slotIndex)
local self = setmetatable({}, Toad)
self.model = model
self.owner = owner
self.state = "Follow"
self.target = nil
self.slotIndex = slotIndex or 0
self.lastAttackTime = 0
self.lastLandTime = 0
self.jumpId = 0
local root = model.PrimaryPart
if not root then
warn("У модели жабы не задан PrimaryPart:", model:GetFullName())
return self
end
-- Убираем лишние объекты, которые могут конфликтовать с ручным управлением моделью
local humanoid = model:FindFirstChildOfClass("Humanoid")
if humanoid then humanoid:Destroy() end
local animateScript = model:FindFirstChild("Animate")
if animateScript then animateScript:Destroy() end
-- Сварка всех частей к торсу + убираем физические коллизии/массу
for _, part in ipairs(model:GetDescendants()) do
if part:IsA("BasePart") then
part.CanCollide = false
part.Massless = true
part.Anchored = false
if part ~= root then
local weld = Instance.new("WeldConstraint")
weld.Part0 = root
weld.Part1 = part
weld.Parent = root
end
end
end
self.groundY = root.Position.Y
return self
end
function Toad:FindEnemy()
local enemiesFolder = workspace:FindFirstChild("Enemies")
if not enemiesFolder then return nil, nil end
local myPos = self.model.PrimaryPart.Position
local best, bestDist
for _, enemy in ipairs(enemiesFolder:GetChildren()) do
if enemy.PrimaryPart then
local hum = enemy:FindFirstChild("Humanoid")
if hum and hum.Health > 0 then
local d = (enemy.PrimaryPart.Position - myPos).Magnitude
if not bestDist or d < bestDist then
best, bestDist = enemy, d
end
end
end
end
return best, bestDist
end
function Toad:GetFollowSpot()
local ownerPos = self.owner.PrimaryPart.Position
local angle = (self.slotIndex * 47) % 360
local rad = math.rad(angle)
local radius = 4 + (self.slotIndex % 3)
local offset = Vector3.new(math.cos(rad) * radius, 0, math.sin(rad) * radius)
return ownerPos + offset
end
function Toad:Update()
if self.state == "Jumping" then return end
if not self.model.Parent then return end
if not self.model.PrimaryPart then return end
if not self.owner or not self.owner.Parent or not self.owner.PrimaryPart then return end
local ownerPos = self.owner.PrimaryPart.Position
local myPos = self.model.PrimaryPart.Position
if self.target then
if not self.target.Parent
or (self.target.PrimaryPart.Position - ownerPos).Magnitude > LEASH_RADIUS then
self.target = nil
else
local hum = self.target:FindFirstChild("Humanoid")
if hum and hum.Health <= 0 then
self.target = nil
end
end
end
if not self.target then
local enemy, dist = self:FindEnemy()
if enemy and dist <= AGGRO_RADIUS then
self.target = enemy
end
end
if self.target then
self:JumpTo(self.target.PrimaryPart.Position, true)
else
if tick() - self.lastLandTime < DECISION_COOLDOWN then return end
local followSpot = self:GetFollowSpot()
if (myPos - followSpot).Magnitude > FOLLOW_TOLERANCE then
self:JumpTo(followSpot, false)
end
end
end
function Toad:JumpTo(targetPosRaw, isAttack)
self.state = "Jumping"
self.jumpId = self.jumpId + 1
local myJumpId = self.jumpId
local root = self.model.PrimaryPart
local targetPos = Vector3.new(targetPosRaw.X, self.groundY, targetPosRaw.Z)
local start = root.Position
local t0 = tick()
local conn
conn = RunService.Heartbeat:Connect(function()
if self.jumpId ~= myJumpId or not self.model.Parent then
conn:Disconnect()
return
end
local a = (tick() - t0) / JUMP_TIME
if a >= 1 then
conn:Disconnect()
self.model:PivotTo(CFrame.new(targetPos) * self:GetFacingCFrame(start, targetPos))
if isAttack and self.target and self.target.Parent then
self:Hit(self.target)
end
self.state = "Follow"
self.lastLandTime = tick()
return
end
local flat = start:Lerp(targetPos, a)
local height = math.sin(a * math.pi) * JUMP_HEIGHT
local pos = flat + Vector3.new(0, height, 0)
local facing = self:GetFacingCFrame(start, targetPos)
local flip = CFrame.Angles(a * math.pi * 2, 0, 0)
self.model:PivotTo(CFrame.new(pos) * facing * flip)
end)
end
function Toad:GetFacingCFrame(fromPos, toPos)
local dir = toPos - fromPos
local flatDir = Vector3.new(dir.X, 0, dir.Z)
if flatDir.Magnitude < 0.1 then
return CFrame.new()
end
return CFrame.lookAt(Vector3.new(0,0,0), flatDir.Unit)
end
function Toad:Hit(enemy)
local now = tick()
if now - self.lastAttackTime < ATTACK_COOLDOWN then return end
self.lastAttackTime = now
local hum = enemy:FindFirstChild("Humanoid")
if hum then
hum:TakeDamage(ATTACK_DAMAGE)
end
end
function Toad:Destroy()
self.jumpId = self.jumpId + 1 -- останавливает активный Heartbeat-цикл прыжка
if self.model then
self.model:Destroy()
end
end
return Toad