Medieval RTS | Auto Fishing, Enemy Ship Detect
MEDIEVAL RTS [⚔️BIGGER ARMIES]Description
Medieval RTS | AutoFish Automate your fishing economy while protecting your Fishing Boats from enemy Attack Ships! AutoFish V3 is an automated fishing script for Roblox Medieval RTS. It manages your Fishing Boats, sends them to fishing spots, detects nearby enemy Attack Ships, and attempts to retreat to safety before returning to fishing. Designed for players who want to focus on building their kingdom and controlling their army instead of constantly managing Fishing Boats. Features 🎣 Automatic Fishing Automatically detects your Fishing Boats. Sends boats to available fishing spots. Starts fishing when boats reach their destination. Automatically manages newly spawned Fishing Boats. 🚢 Enemy Attack Ship Detection Monitors nearby enemy Attack Ships. Detects threats within approximately 110 studs. Stops fishing before issuing escape commands. Sends threatened boats toward safer locations. 🛡️ Automatic Escape & Return Prioritizes escaping when an enemy Attack Ship approaches. Waits until the boat is safely away from threats. Automatically returns to a suitable fishing spot. Resumes fishing when the area is safe. ⚙️ Smart Movement System Detects boats that stop making progress. Retries movement commands when necessary. Avoids constantly spamming movement requests. Distributes Fishing Boats across available spots. Avoids fishing locations near detected enemy Attack Ships. 🔄 Automatic Boat Management Continues working after a Fishing Boat is destroyed. Automatically detects and manages replacement boats. Doesn't require manually restarting the script for every new boat. How to Use Join Medieval RTS. Execute the script. Spawn a Fishing Boat. The script will automatically assign it to a fishing spot. If an enemy Attack Ship approaches, the boat will attempt to escape and return once safe. Important Notes The script does not automatically purchase Fishing Boats. No GUI required, runs automatically after execution.
Script source Luau
-- [[ BloxScripts Risk Notice ]]
-- This script has not been verified by BloxScripts.fun. Deal with caution.
--
-- Stay safe:
-- • Never log in on unofficial Roblox sites or lookalike domains.
-- • Real Roblox links use roblox.com (check the .com ending).
-- • Treat fake Roblox login / "claim reward" pages as phishing.
-- [[ End BloxScripts Risk Notice ]]
print("AUTOFISH V3 - ESCAPE FIX")
local Players = game:GetService("Players")
local RS = game:GetService("ReplicatedStorage")
local lp = Players.LocalPlayer
local playerFolder = workspace.Game.PlayerFolder
local myUnits = playerFolder:WaitForChild(lp.Name):WaitForChild("Units")
local fishingFolder = workspace.Game
:WaitForChild("Map")
:WaitForChild("Map")
:WaitForChild("Fishing")
local fishingRemote = RS:WaitForChild("Fishing")
local moveRemote = RS:WaitForChild("SendUnitGoals")
-- SETTINGS
local DANGER_RANGE = 110
local SAFE_RANGE = 155
local SAFE_WAIT = 6
local ARRIVAL_RANGE = 6
local COMMAND_DELAY = 1.5
local MOVE_RETRY = 8
local FISH_RETRY = 8
local STOP_RETRY = 7
local UPDATE_DELAY = 0.5
local states = {}
local sending = false
local lastGlobalMove = -math.huge
-- Disable previous V3 instance if rerun
local old = lp.PlayerGui:FindFirstChild("AutoFishV3Active")
if old then
old.Value = false
old:Destroy()
end
local active = Instance.new("BoolValue")
active.Name = "AutoFishV3Active"
active.Value = true
active.Parent = lp.PlayerGui
local function log(...)
print("[AUTOFISH V3]", ...)
end
local function getPosition(model)
if not model or not model.Parent then
return nil
end
local hitbox = model:FindFirstChild("Hitbox", true)
if hitbox and hitbox:IsA("BasePart") then
return hitbox.Position
end
return nil
end
local function alive(model)
if not model or not model.Parent then
return false
end
if model:GetAttribute("Destroyed") == true then
return false
end
local health = model:GetAttribute("Health")
if type(health) == "number" and health <= 0 then
return false
end
return getPosition(model) ~= nil
end
local function isFishing(boat)
return boat:FindFirstChild("Fishing") ~= nil
end
local function isAttackShip(unit)
local name = unit.Name:lower()
:gsub("%s+", "")
:gsub("%-", "")
return name:find("attackship", 1, true) ~= nil
end
local function getEnemyShips()
local ships = {}
for _, opponent in ipairs(playerFolder:GetChildren()) do
if opponent.Name ~= lp.Name then
local folder = opponent:FindFirstChild("Units")
if folder then
for _, unit in ipairs(folder:GetChildren()) do
if isAttackShip(unit) and alive(unit) then
local position = getPosition(unit)
if position then
table.insert(ships, position)
end
end
end
end
end
end
return ships
end
local function nearestThreat(position, ships)
local closest = nil
local bestDistance = math.huge
for _, shipPosition in ipairs(ships) do
local distance =
(position - shipPosition).Magnitude
if distance < bestDistance then
bestDistance = distance
closest = shipPosition
end
end
return closest, bestDistance
end
local function getState(boat)
if not states[boat] then
states[boat] = {
mode = "FISH",
spot = nil,
destination = nil,
lastMove = -math.huge,
lastFishing = -math.huge,
lastStop = -math.huge,
lastPosition = nil,
lastProgress = os.clock(),
safeSince = nil
}
end
return states[boat]
end
local function getFishingSpots()
local spots = {}
for _, spot in ipairs(fishingFolder:GetChildren()) do
local radius = spot:FindFirstChild("Radius")
if radius and radius:IsA("BasePart") then
table.insert(spots, spot)
end
end
return spots
end
local function chooseFishingSpot(
boat,
position,
ships
)
local bestSpot = nil
local bestScore = math.huge
for _, spot in ipairs(getFishingSpots()) do
local spotPosition = spot.Radius.Position
local _, threatDistance =
nearestThreat(spotPosition, ships)
-- Only fish somewhere clear of attack ships
if threatDistance >= SAFE_RANGE then
local distance =
(position - spotPosition).Magnitude
local occupied = 0
for otherBoat, state in pairs(states) do
if otherBoat ~= boat
and alive(otherBoat)
and state.spot == spot then
occupied = occupied + 1
end
end
local score = distance + occupied * 70
if score < bestScore then
bestScore = score
bestSpot = spot
end
end
end
return bestSpot
end
local function getEscapePosition(position, threat)
if not threat then
return nil
end
local away = position - threat
local flat = Vector3.new(away.X, 0, away.Z)
if flat.Magnitude < 0.1 then
flat = Vector3.new(1, 0, 0)
end
return position + flat.Unit * 140
end
-- NEW: Attempts to toggle fishing OFF before fleeing
local function stopFishing(boat, state)
if not isFishing(boat) then
return true
end
local now = os.clock()
if now - state.lastStop >= STOP_RETRY then
state.lastStop = now
log("STOPPING FISHING:", boat.Name)
local success, err = pcall(function()
fishingRemote:FireServer(boat)
end)
if not success then
warn("[AUTOFISH] Stop fishing error:", err)
end
end
-- Wait until the Fishing marker is removed
return false
end
local function startFishing(boat, state)
if isFishing(boat) then
return
end
local now = os.clock()
if now - state.lastFishing < FISH_RETRY then
return
end
state.lastFishing = now
log("STARTING FISHING:", boat.Name)
local success, err = pcall(function()
fishingRemote:FireServer(boat)
end)
if not success then
warn("[AUTOFISH] Start fishing error:", err)
end
end
local function sendMove(boat, destination, state)
if not destination or not alive(boat) then
return
end
if sending then
return
end
local now = os.clock()
if now - lastGlobalMove < COMMAND_DELAY then
return
end
local current = getPosition(boat)
if not current then
return
end
if (current - destination).Magnitude <= ARRIVAL_RANGE then
return
end
local targetChanged =
state.destination == nil
or (
state.destination - destination
).Magnitude > 10
local stuck =
now - state.lastProgress >= MOVE_RETRY
if not targetChanged and not stuck then
return
end
if now - state.lastMove < COMMAND_DELAY then
return
end
sending = true
lastGlobalMove = now
state.lastMove = now
state.destination = destination
task.spawn(function()
local success, result = pcall(function()
return moveRemote:InvokeServer({
{boat, destination}
}, false)
end)
if not success then
warn("[AUTOFISH] Move error:", result)
elseif result == false then
warn("[AUTOFISH] Move rejected:", boat.Name)
else
log("MOVE ORDER:", boat.Name)
end
sending = false
end)
end
local function updateBoat(boat, ships)
if not alive(boat) then
return
end
local state = getState(boat)
local now = os.clock()
local position = getPosition(boat)
-- Track actual progress, not just issued orders
if not state.lastPosition
or (
position - state.lastPosition
).Magnitude >= 2 then
state.lastPosition = position
state.lastProgress = now
end
local threat, distance =
nearestThreat(position, ships)
-- ENTER ESCAPE MODE
if distance <= DANGER_RANGE then
if state.mode ~= "FLEE" then
state.mode = "FLEE"
state.spot = nil
state.destination = nil
state.safeSince = nil
log("DANGER! ATTACK SHIP NEAR", boat.Name)
end
end
if state.mode == "FLEE" then
-- STEP 1: STOP FISHING FIRST
if isFishing(boat) then
stopFishing(boat, state)
return
end
-- STEP 2: WAIT UNTIL AREA STAYS SAFE
if distance >= SAFE_RANGE then
state.safeSince = state.safeSince or now
if now - state.safeSince >= SAFE_WAIT then
state.mode = "FISH"
state.spot = nil
state.destination = nil
state.lastProgress = -math.huge
state.safeSince = nil
log("SAFE AGAIN:", boat.Name)
end
else
state.safeSince = nil
end
-- STEP 3: RUN FROM ATTACK SHIPS
if state.mode == "FLEE" then
local safeSpot =
chooseFishingSpot(boat, position, ships)
local destination = nil
if safeSpot then
destination = safeSpot.Radius.Position
else
-- Preserve an escape destination
-- instead of changing it every update
if state.destination
and (
position - state.destination
).Magnitude > ARRIVAL_RANGE then
destination = state.destination
else
destination =
getEscapePosition(position, threat)
end
end
if destination then
sendMove(boat, destination, state)
end
return
end
end
-- NORMAL FISHING MODE
-- Respect your manual selection unless escaping
if boat:FindFirstChild("SelectionPart", true) then
return
end
if isFishing(boat) then
state.destination = nil
return
end
if not state.spot or not state.spot.Parent then
state.spot =
chooseFishingSpot(boat, position, ships)
state.destination = nil
end
local spot = state.spot
if not spot then
return
end
local destination = spot.Radius.Position
local _, spotDanger =
nearestThreat(destination, ships)
if spotDanger < SAFE_RANGE then
state.spot = nil
state.destination = nil
return
end
local distanceToSpot =
(position - destination).Magnitude
if distanceToSpot <= ARRIVAL_RANGE then
startFishing(boat, state)
else
sendMove(boat, destination, state)
end
end
log("ACTIVE - AUTOMATIC ESCAPE ENABLED")
while active.Parent and active.Value do
local success, err = pcall(function()
local ships = getEnemyShips()
for _, boat in ipairs(myUnits:GetChildren()) do
if boat.Name == "Fishing Boat" and alive(boat) then
updateBoat(boat, ships)
end
end
-- Forget destroyed boats; replacements get
-- their own fresh state automatically
for boat in pairs(states) do
if not alive(boat) then
states[boat] = nil
end
end
end)
if not success then
warn("[AUTOFISH] Update error:", err)
end
task.wait(UPDATE_DELAY)
end
log("STOPPED")Review the source before using it. Executions count successful copies, capped at 10 per user and network per script in 24 hours.


Your community, in one place