local r=3 local P=50 local v=.25 local f=10 local Q=13 local b={[-1]="\078\111\110\101",[0]="\067\117\115\116\111\109",[1]="\071\114\097\115\115";[2]="\083\119\097\109\112";[3]="\080\117\109\112\107\105\110\115";[4]="\087\097\116\101\114";[5]="\082\111\099\107";[6]="\082\111\097\100";[7]="\072\111\117\115\101"}local A=game:GetService("\087\111\114\107\115\112\097\099\101")local j=game:GetService("\080\108\097\121\101\114\115")local Y=game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101")local u=require((Y:WaitForChild("\080\114\111\106\101\099\116")):WaitForChild("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101"))local R=u:GetResource("\071\097\109\101\068\097\116\097\046\071\101\110\101\114\097\116\105\111\110\046\067\117\115\116\111\109\083\116\114\117\099\116\117\114\101\115")local N=u:GetResource("\071\097\109\101\068\097\116\097\046\071\101\110\101\114\097\116\105\111\110\046\077\097\112\067\101\108\108\115")local M=u:GetResource("\071\097\109\101\068\097\116\097\046\071\101\110\101\114\097\116\105\111\110\046\078\080\067\076\111\099\097\116\105\111\110\115")local B=u:GetResource("\069\120\116\101\114\110\097\108\085\116\105\108\046\078\101\120\117\115\073\110\115\116\097\110\099\101\046\078\101\120\117\115\079\098\106\101\099\116")local U=u:GetResource("\071\097\109\101\082\101\112\108\105\099\097\116\105\111\110\046\068\117\110\103\101\111\110\082\101\112\108\105\099\097\116\105\111\110\046\083\116\097\114\116\066\108\111\120\104\105\108\100\097\068\117\110\103\101\111\110")local o=(u:GetResource("\083\116\097\116\101\046\081\117\101\115\116\115")).GetQuests(j.LocalPlayer)local S=u:GetResource("\066\108\111\120\104\105\108\100\097\080\111\114\116\097\108")local e={BaseGrass=u:GetResource("\071\101\110\101\114\097\116\105\111\110\046\067\101\108\108\046\066\097\115\101\071\114\097\115\115\067\101\108\108"),Grass=u:GetResource("\071\101\110\101\114\097\116\105\111\110\046\067\101\108\108\046\071\114\097\115\115\067\101\108\108"),Swamp=u:GetResource("\071\101\110\101\114\097\116\105\111\110\046\067\101\108\108\046\083\119\097\109\112\067\101\108\108"),Pumpkins=u:GetResource("\071\101\110\101\114\097\116\105\111\110\046\067\101\108\108\046\080\117\109\112\107\105\110\115\067\101\108\108");Water=u:GetResource("\071\101\110\101\114\097\116\105\111\110\046\067\101\108\108\046\087\097\116\101\114\067\101\108\108"),Rock=u:GetResource("\071\101\110\101\114\097\116\105\111\110\046\067\101\108\108\046\082\111\099\107\067\101\108\108"),Road=u:GetResource("\071\101\110\101\114\097\116\105\111\110\046\067\101\108\108\046\082\111\097\100\067\101\108\108");House=u:GetResource("\071\101\110\101\114\097\116\105\111\110\046\067\101\108\108\046\072\111\117\115\101\067\101\108\108")}local k=B:Extend()k:SetClassName("\077\097\112\071\101\110\101\114\097\116\111\114")function k.__new(r)r:InitializeSuper()r.LastX=0 r.LastY=0 r.QueuedAction={}r.TotalQueuedCreateActions=0 r.Cells={}end function k.GenerateCustomCells(r)for P,v in pairs(R.Cells)do local f,Q=v[1],v[2]e[v[3]].new(f,Q,r:GetCellType(f,Q+1),r:GetCellType(f,Q-1),r:GetCellType(f-1,Q),r:GetCellType(f+1,Q))end for r,P in pairs(R.Structures)do local v=(u:GetResource(r)):Clone()v.PrimaryPart=v:WaitForChild("\067\101\110\116\101\114")v:SetPrimaryPartCFrame(CFrame.new(P[1]*100,-0.5,P[2]*100))v.Parent=A if P[3]then P[3]()end end for r,P in pairs(M)do(coroutine.wrap(function()local v=(u:GetResource("\078\080\067\077\111\100\101\108\115\046"..tostring(r))):Clone()local f,Q=v:WaitForChild("\072\117\109\097\110\111\105\100"),v:WaitForChild("\072\117\109\097\110\111\105\100\082\111\111\116\080\097\114\116")v.PrimaryPart=Q local b=f.HipHeight+Q.Size.Y/2 local Y=(CFrame.new(P.CellX*100,0,P.CellY*100)*P.OffsetCFrame)*CFrame.new(0,b,0)v:SetPrimaryPartCFrame(Y)v.Parent=A local R=Instance.new("\080\097\114\116")R.Size=Vector3.new(5,b*2,5)R.CFrame=Y R.Transparency=1 R.Anchored=true R.CanCollide=false R.Parent=v local N=Instance.new("\067\108\105\099\107\068\101\116\101\099\116\111\114")N.MaxActivationDistance=20 N.Parent=R local M=false N.MouseClick:Connect(function()(((j.LocalPlayer:WaitForChild("\080\108\097\121\101\114\071\117\105")):WaitForChild("\068\105\097\108\111\103\086\105\101\119")):WaitForChild("\083\116\097\114\116\068\105\097\108\111\103")):Fire(r)end)R.Touched:Connect(function(P)local v=j.LocalPlayer.Character if not M and(v and P:IsDescendantOf(v))then M=true;(((j.LocalPlayer:WaitForChild("\080\108\097\121\101\114\071\117\105")):WaitForChild("\068\105\097\108\111\103\086\105\101\119")):WaitForChild("\083\116\097\114\116\068\105\097\108\111\103")):Fire(r)while(P.Position-R.Position).Magnitude<6 do wait()end M=false end end)local B=Instance.new("\065\110\105\109\097\116\105\111\110")B.AnimationId="\114\098\120\097\115\115\101\116\105\100\058\047\047"..tostring(P.IdleAnimationId)local U=f:LoadAnimation(B)U.DidLoop:Connect(function()U:Play()end)U:Play()end))()end local function P()if A:FindFirstChild("\066\108\111\120\104\105\108\100\097\080\111\114\116\097\108")then return end if not o:QuestConditonValid("\069\110\100 \111\102 \068\097\121\115","\084\117\114\110\101\100\073\110")then return end local r=S:Clone()r.PrimaryPart=r:WaitForChild("\067\101\110\116\101\114")r:SetPrimaryPartCFrame((CFrame.new(2400,0,900)*CFrame.new(-35,0,30))*CFrame.Angles(0,-math.rad(10),0))r.Parent=A local P=true;(r:WaitForChild("\080\111\114\116\097\108")).Touched:Connect(function(r)if P then P=false if r.Parent==j.LocalPlayer.Character then U:FireServer()end wait()P=true end end)end P()o.QuestsChanged:Connect(P)end function k.CellInLastCall(v,r,P)return math.abs(r-v.LastX)<Q and math.abs(P-v.LastY)<Q end function k.GetCellTypeId(v,r,P)if not N[r]then return-1 end return N[r][P]or-1 end function k.GetCellType(v,r,P)return b[v:GetCellTypeId(r,P)]end function k.GenerateCell(v,r,P)local f=tostring(r)..("\095"..tostring(P))if v.Cells[f]then return end local Q=v:GetCellType(r,P)if e[Q]then v.Cells[f]=e[Q].new(r,P,v:GetCellType(r,P+1),v:GetCellType(r,P-1),v:GetCellType(r-1,P),v:GetCellType(r+1,P))end end function k.UpdateQueuedCells(f)local Q=r if f.TotalQueuedCreateActions>=P then Q=math.ceil(f.TotalQueuedCreateActions*v)end local b={}local A=0 for r,P in pairs(f.QueuedAction)do if P[1]=="\067\082\069\065\084\069"and not f:CellInLastCall(P[2],P[3])then P[1]="\073\071\078\079\082\069"f.TotalQueuedCreateActions=f.TotalQueuedCreateActions-1 end table.insert(b,r)if P[1]=="\067\082\069\065\084\069"then A=A+1 end if A>=Q then break end end for r,P in pairs(b)do local v=f.QueuedAction[P]if v[1]=="\067\082\069\065\084\069"then f:GenerateCell(v[2],v[3])f.TotalQueuedCreateActions=f.TotalQueuedCreateActions-1 elseif v[1]=="\068\069\076\069\084\069"then f.Cells[P]:Destroy()f.Cells[P]=nil end f.QueuedAction[P]=nil end end function k.QueueGenerateCell(v,r,P)local f=tostring(r)..("\095"..tostring(P))if not v.Cells[f]and(not v.QueuedAction[f]and v:GetCellTypeId(r,P)>=1)then v.QueuedAction[f]={"\067\082\069\065\084\069";r,P}v.TotalQueuedCreateActions=v.TotalQueuedCreateActions+1 end end function k.QueueDeleteCell(v,r,P)local f=tostring(r)..("\095"..tostring(P))if v.Cells[f]and not v.QueuedAction[f]then v.QueuedAction[f]={"\068\069\076\069\084\069";r;P}end end function k.GenerateCells(v,r,P)if r==v.LastX and P==v.LastY then return end v.LastX,v.LastY=r,P v:GenerateCell(r,P)for r=r-f,r+f,1 do for P=P-f,P+f,1 do v:QueueGenerateCell(r,P)end end for r,P in pairs(v.Cells)do if not v:CellInLastCall(P.X,P.Y)then v:QueueDeleteCell(P.X,P.Y)end end end return k
--[[
TheNexusAvenger

Generates the map from the map data.
--]]

local NORMAL_CELLS_PER_PASS = 3
local ACCELERATED_CELL_CREATION_THRESHOLD = 50
local ACCELERATED_CELLS_PER_PASS_MULTIPLIER = 0.25
local MAP_RADIUS = 10
local CLEAR_MAP_RADIUS = 13
local ID_TO_CELL_TYPE = {
    [-1] = "None",
    [0] = "Custom",
    [1] = "Grass",
    [2] = "Swamp",
    [3] = "Pumpkins",
    [4] = "Water",
    [5] = "Rock",
    [6] = "Road",
    [7] = "House",
}



local Workspace = game:GetService("Workspace")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local ReplicatedStorageProject = require(ReplicatedStorage:WaitForChild("Project"):WaitForChild("ReplicatedStorage"))

local CustomStructures = ReplicatedStorageProject:GetResource("GameData.Generation.CustomStructures")
local MapCellData = ReplicatedStorageProject:GetResource("GameData.Generation.MapCells")
local NPCLocations = ReplicatedStorageProject:GetResource("GameData.Generation.NPCLocations")
local NexusObject = ReplicatedStorageProject:GetResource("ExternalUtil.NexusInstance.NexusObject")
local StartBloxhildaDungeon = ReplicatedStorageProject:GetResource("GameReplication.DungeonReplication.StartBloxhildaDungeon")
local Quests = ReplicatedStorageProject:GetResource("State.Quests").GetQuests(Players.LocalPlayer)
local BloxhildaPortal = ReplicatedStorageProject:GetResource("BloxhildaPortal")

local CellGenerators = {
    BaseGrass = ReplicatedStorageProject:GetResource("Generation.Cell.BaseGrassCell"),
    Grass = ReplicatedStorageProject:GetResource("Generation.Cell.GrassCell"),
    Swamp = ReplicatedStorageProject:GetResource("Generation.Cell.SwampCell"),
    Pumpkins = ReplicatedStorageProject:GetResource("Generation.Cell.PumpkinsCell"),
    Water = ReplicatedStorageProject:GetResource("Generation.Cell.WaterCell"),
    Rock = ReplicatedStorageProject:GetResource("Generation.Cell.RockCell"),
    Road = ReplicatedStorageProject:GetResource("Generation.Cell.RoadCell"),
    House = ReplicatedStorageProject:GetResource("Generation.Cell.HouseCell"),
}

local MapGenerator = NexusObject:Extend()
MapGenerator:SetClassName("MapGenerator")



--[[
Creates the map generator.
--]]
function MapGenerator:__new()
    self:InitializeSuper()

    self.LastX = 0
    self.LastY = 0
    self.QueuedAction = {}
    self.TotalQueuedCreateActions = 0
    self.Cells = {}
end

--[[
Generates the custom cells.
--]]
function MapGenerator:GenerateCustomCells()
    --Generate the cells.
    for _,CellData in pairs(CustomStructures.Cells) do
        local X,Y = CellData[1],CellData[2]
        CellGenerators[CellData[3]].new(X,Y,self:GetCellType(X,Y+1),self:GetCellType(X,Y-1),self:GetCellType(X-1,Y),self:GetCellType(X+1,Y))
    end

    --Generate the structures.
    for StructurePath,Location in pairs(CustomStructures.Structures) do
        local NewStructureModel = ReplicatedStorageProject:GetResource(StructurePath):Clone()
        NewStructureModel.PrimaryPart = NewStructureModel:WaitForChild("Center")
        NewStructureModel:SetPrimaryPartCFrame(CFrame.new(Location[1] * 100,-0.5,Location[2] * 100))
        NewStructureModel.Parent = Workspace

        if Location[3] then
            Location[3]()
        end
    end

    --Generate the NPCs.
    for NPCName,NPCData in pairs(NPCLocations) do
        coroutine.wrap(function()
            --Clone and set up the model.
            local NPCModel = ReplicatedStorageProject:GetResource("NPCModels."..tostring(NPCName)):Clone()
            local Humanoid,HumnoidRootPart = NPCModel:WaitForChild("Humanoid"),NPCModel:WaitForChild("HumanoidRootPart")
            NPCModel.PrimaryPart = HumnoidRootPart

            --Determine the center CFrame.
            local Height = Humanoid.HipHeight + HumnoidRootPart.Size.Y/2
            local Center = CFrame.new(NPCData.CellX * 100,0,NPCData.CellY * 100) * NPCData.OffsetCFrame * CFrame.new(0,Height,0)

            --Move the model.
            NPCModel:SetPrimaryPartCFrame(Center)
            NPCModel.Parent = Workspace

            --Add the bounding box.
            local BoundingBox = Instance.new("Part")
            BoundingBox.Size = Vector3.new(5,Height * 2,5)
            BoundingBox.CFrame = Center
            BoundingBox.Transparency = 1
            BoundingBox.Anchored = true
            BoundingBox.CanCollide = false
            BoundingBox.Parent = NPCModel

            local ClickDetector = Instance.new("ClickDetector")
            ClickDetector.MaxActivationDistance = 20
            ClickDetector.Parent = BoundingBox

            --Set up interactions.
            local TouchInRange = false
            ClickDetector.MouseClick:Connect(function()
                Players.LocalPlayer:WaitForChild("PlayerGui"):WaitForChild("DialogView"):WaitForChild("StartDialog"):Fire(NPCName)
            end)
            BoundingBox.Touched:Connect(function(TouchPart)
                local Character = Players.LocalPlayer.Character
                if not TouchInRange and Character and TouchPart:IsDescendantOf(Character) then
                    --Start the dialog.
                    TouchInRange = true
                    Players.LocalPlayer:WaitForChild("PlayerGui"):WaitForChild("DialogView"):WaitForChild("StartDialog"):Fire(NPCName)

                    --Wait for the touch part to leave.
                    while (TouchPart.Position - BoundingBox.Position).Magnitude < 6 do
                        wait()
                    end
                    TouchInRange = false
                end
            end)

            --Start the idle animation.
            local Animation = Instance.new("Animation")
            Animation.AnimationId = "rbxassetid://"..tostring(NPCData.IdleAnimationId)
            local AnimationTrack = Humanoid:LoadAnimation(Animation)
            AnimationTrack.DidLoop:Connect(function()
                AnimationTrack:Play()
            end)
            AnimationTrack:Play()
        end)()
    end

    --[[
    Generates the Bloxhilda portal.
    --]]
    local function CreateBloxhildaPortal()
        --Return if the portal exists.
        if Workspace:FindFirstChild("BloxhildaPortal") then
            return
        end

        --Return if the final quest wasn't completed.
        if not Quests:QuestConditonValid("End of Days","TurnedIn") then
            return
        end

        --Create the portal.
        local Portal = BloxhildaPortal:Clone()
        Portal.PrimaryPart = Portal:WaitForChild("Center")
        Portal:SetPrimaryPartCFrame(CFrame.new(24 * 100,0,9 * 100) * CFrame.new(-35,0,30) * CFrame.Angles(0,-math.rad(10),0))
        Portal.Parent = Workspace

        --Connect the portal.
        local DB = true
        Portal:WaitForChild("Portal").Touched:Connect(function(TouchPart)
            if DB then
                DB = false
                if TouchPart.Parent == Players.LocalPlayer.Character then
                    --Start the Bloxhilda dungeon.
                    StartBloxhildaDungeon:FireServer()
                end
                wait()
                DB = true
            end
        end)
    end

    --Create the portal.
    CreateBloxhildaPortal()
    Quests.QuestsChanged:Connect(CreateBloxhildaPortal)
end

--[[
Returns if a cell position is in the range
of the last call.
--]]
function MapGenerator:CellInLastCall(X,Y)
    return math.abs(X - self.LastX) < CLEAR_MAP_RADIUS and math.abs(Y - self.LastY) < CLEAR_MAP_RADIUS
end

--[[
Returns the cell id at a given coordinate.
--]]
function MapGenerator:GetCellTypeId(X,Y)
    if not MapCellData[X] then
        return -1
    end
    return MapCellData[X][Y] or -1
end

--[[
Returns the cell name at a given coordinate.
--]]
function MapGenerator:GetCellType(X,Y)
    return ID_TO_CELL_TYPE[self:GetCellTypeId(X,Y)]
end

--[[
Generates a cell.
--]]
function MapGenerator:GenerateCell(X,Y)
    --Return if the cell exists.
    local CellId = tostring(X).."_"..tostring(Y)
    if self.Cells[CellId] then
        return
    end

    --Create and store the cell.
    local CellType = self:GetCellType(X,Y)
    if CellGenerators[CellType] then
        self.Cells[CellId] = CellGenerators[CellType].new(X,Y,self:GetCellType(X,Y+1),self:GetCellType(X,Y-1),self:GetCellType(X-1,Y),self:GetCellType(X+1,Y))
    end
end

--[[
Performs a pass on the queued cells.
--]]
function MapGenerator:UpdateQueuedCells()
    --Determine how many cells to create.
    local MaxCellsToCreate = NORMAL_CELLS_PER_PASS
    if self.TotalQueuedCreateActions >= ACCELERATED_CELL_CREATION_THRESHOLD then
        MaxCellsToCreate = math.ceil(self.TotalQueuedCreateActions * ACCELERATED_CELLS_PER_PASS_MULTIPLIER)
    end

    --Fetch the cells to update.
    local CellsToUpdate = {}
    local CreateCellCalls = 0
    for CellId,Action in pairs(self.QueuedAction) do
        --Ignore the cell if it shouldn't be created.
        if Action[1] == "CREATE" and not self:CellInLastCall(Action[2],Action[3]) then
            Action[1] = "IGNORE"
            self.TotalQueuedCreateActions = self.TotalQueuedCreateActions - 1
        end

        --Add the cell to update.
        table.insert(CellsToUpdate,CellId)
        if Action[1] == "CREATE" then
            CreateCellCalls = CreateCellCalls + 1
        end
        if CreateCellCalls >= MaxCellsToCreate then
            break
        end
    end

    --Generate the cells.
    for _,CellId in pairs(CellsToUpdate) do
        local Action = self.QueuedAction[CellId]
        if Action[1] == "CREATE" then
            self:GenerateCell(Action[2],Action[3])
            self.TotalQueuedCreateActions = self.TotalQueuedCreateActions - 1
        elseif Action[1] == "DELETE" then
            self.Cells[CellId]:Destroy()
            self.Cells[CellId] = nil
        end
        self.QueuedAction[CellId] = nil
    end
end

--[[
Queues generating a cell.
--]]
function MapGenerator:QueueGenerateCell(X,Y)
    local CellId = tostring(X).."_"..tostring(Y)
    if not self.Cells[CellId] and not self.QueuedAction[CellId] and self:GetCellTypeId(X,Y) >= 1 then
        self.QueuedAction[CellId] = {"CREATE",X,Y}
        self.TotalQueuedCreateActions = self.TotalQueuedCreateActions + 1
    end
end

--[[
Queues deleting a cell.
--]]
function MapGenerator:QueueDeleteCell(X,Y)
    local CellId = tostring(X).."_"..tostring(Y)
    if self.Cells[CellId] and not self.QueuedAction[CellId] then
        self.QueuedAction[CellId] = {"DELETE",X,Y}
    end
end

--[[
Generates the cells centered around a region.
--]]
function MapGenerator:GenerateCells(CenterX,CenterY)
    --Return if the position didn't change.
    if CenterX == self.LastX and CenterY == self.LastY then
        return
    end
    self.LastX,self.LastY = CenterX,CenterY

    --Generate the cell the player is in.
    self:GenerateCell(CenterX,CenterY)

    --Queue generating the cells.
    for X = CenterX - MAP_RADIUS,CenterX + MAP_RADIUS do
        for Y = CenterY - MAP_RADIUS,CenterY + MAP_RADIUS do
            self:QueueGenerateCell(X,Y)
        end
    end

    --Queue deleting cells.
    for _,Cell in pairs(self.Cells) do
        if not self:CellInLastCall(Cell.X,Cell.Y) then
            self:QueueDeleteCell(Cell.X,Cell.Y)
        end
    end
end



return MapGenerator