--[[
    implementation of linedef 461
    
    v0.3.2.1
        Fixed formatting in already-given-object dummy spawnpoint error message
        Removed trying to get object name in error messages. Turns out it's not possible
    
    v0.3.2:
        Removed ability to give spawnpoint to objects with no associated mapthing
            In kind, network resync no longer required, and has been removed
        Fixed setting dummy spawnpoint not working (again; typed "ANG_1" as "ANG1")
        Adjusted whitespace formatting to make Zone Builder stop being a baby. Cry some more
        Fixed error message formatting for nonexistent dummy spawnpoint
        Fixed error message erroring (oops) for already-given-object dummy spawnpoint
    
    v0.3.1.1:
        Fixed setting object angle from linedef not working
        Fixed setting dummy spawnpoint not working
        (both related to mistyping "spawnedObj" as "spawnObj")
    
    v0.3.1:
        Changed spawnpoint angle increments control from back sector tag to back sector floor height
    
    v0.3:
        added option to refer to dummy mapthing for objects requiring a spawnpoint
            Eldog: im about to write my own implementation right now and we will become bitter
                       enemies forever
                   like uhh ford and chevrolet
        added global to prevent multiple loads in case this is used in a multiple packs
    
    v0.2:
        adjusted implementation to use backside instead of tag proxying to get object
    
    v0.1.3:
        added additional error check for when the sidedef text property is nil
    
    v0.1.2.1:
        fixed the comment listed too many linedef types upon further research
        updated the changelog
    
    v0.1.2:
        fixed a typo
        discovered quirks about sidedef.text; added a comment specifying the need for linedef type
    
    v0.1.1:
        fixed z coordinated not being bit-shifted
    
    v0.1:
        the first, the OG. People gonna test this and it's gonna DIE
]]

--don't load this twice
if Ld461LuaLoaded then return end

local error = error
--YEEHAW
local _G = _G

local mobjinfo = mobjinfo
local mapthings = mapthings

--local freeslot = freeslot
local addHook = addHook

local ML_NOCLIMB = ML_NOCLIMB
local FRACBITS = FRACBITS
local ML_EFFECT1 = ML_EFFECT1
local ML_EFFECT2 = ML_EFFECT2

local P_SpawnMobj = P_SpawnMobj
local P_RandomRange = P_RandomRange

freeslot("MT_DUMMYSP")
local MT_DUMMYSP = MT_DUMMYSP

local MT_DUMMYSP_NUM = 3378
mobjinfo[MT_DUMMYSP] = {
        doomednum = MT_DUMMYSP_NUM,
        flags = MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_NOTHINK|MF_NOCLIPHEIGHT|MF_NOCLIPTHING
}

local dummySpCache = {}
--due to possible early executors, here's a temp cache
local dummySpTempCache = {}

local function getObjName(typeId)
    --WHY HAVE YOU FORSAKEN US
    for key, val in pairs(_G) do
        if
            type(key) == "string" and
            key:sub(1, 3) == "MT_" and
            type(val) == "number" and
            val == typeId
        then
            return key
        end
    end
    return "UNKNOWN MOBJ TYPE"
end

local function cacheDummySp(sp)
    local spId = sp.angle
    if dummySpCache[spId] then
        error("Dummy spawnpoint " .. spId .. " has a duplicate!")
    end
    dummySpCache[spId] = sp
end

local function isValidDummySp(thing)
    return thing.valid and thing.type == MT_DUMMYSP_NUM
end

local function tmpCacheDummySp(spId)
    for thing in mapthings.iterate do
        if isValidDummySp(thing) and thing.angle == spId then
            dummySpBufCache[spId] = thing
            return thing
        end
    end
end

local function collectDummySps()
    for thing in mapthings.iterate do
        if isValidDummySp(thing) then
            cacheDummySp(thing)
        end
    end
end

local function resetDummySpCache()
    dummySpCache = {}
    collectDummySps()
    --empty the temp cache since the cache exists properly now
    dummySpTempCache = {}
end

addHook("MapLoad", resetDummySpCache)

local function linedefType461(line)
    local frontSec = line.frontsector
    local backSec = line.backsector
    if frontSec and backSec then
        local backSide = line.backside
        --make sure the object is legal. oh god, this looks really cursed
        local objName = backSide.text
        if objName then
            local objType = _G[objName]
            if objType ~= nil then
                local frontSide = line.frontside
                local lineFlags = line.flags
                local spawnX, spawnY, spawnZ
                --flag to randomize spawn location
                if lineFlags & ML_NOCLIMB then
                    --randomized location requires back sector
                    spawnX = P_RandomRange(
                            frontSide.textureoffset,
                            backSide.textureoffset
                        ) << FRACBITS
                    spawnY = P_RandomRange(
                            frontSide.rowoffset,
                            backSide.rowoffset
                        ) << FRACBITS
                    spawnZ = P_RandomRange(
                            frontSec.floorheight,
                            frontSec.ceilingheight
                        ) << FRACBITS
                else
                    spawnX = frontSide.textureoffset
                    spawnY = frontSide.rowoffset
                    spawnZ = frontSec.floorheight
                end
                local spawnedObj = P_SpawnMobj(spawnX, spawnY, spawnZ, objType)
                if lineFlags & ML_EFFECT1 then
                    local v1 = line.v1
                    local v2 = line.v2
                    spawnedObj.angle = R_PointToAngle2(v1.x, v1.y, v2.x, v2.y)
                end
                --my addition: specify a MT_DUMMYSP as the spawnpoint for the spawned object
                if lineFlags & ML_EFFECT2 then
                    --do not give spawnpoints to objects which normally would never have one
                    if spawnedObj.info.doomednum == -1 then
                        error(
                            "Line " .. #line .. " attempted to use dummy spawnpoint " ..
                            line.tag .. " for object with no associated mapthing (" ..
                            objName .. ")!"
                        )
                    end
                    --line tag specifies dummy spawnpoint ID
                    local sp = dummySpCache[line.tag]
                    if not sp or not sp.valid then
                        --race condition: might have executed before map load caching
                        --check for previous execution
                        sp = dummySpBufCache[line.tag]
                        if not sp or not sp.valid then
                            sp = tmpCacheDummySp(line.tag)
                            if not sp then
                                error(
                                    "Line " .. #line .. " attempted to use dummy spawnpoint " .. 
                                    line.tag .. " when it does not exist!"
                                )
                            end
                        end
                    end
                    if sp.mobj and sp.mobj.type ~= MT_DUMMYSP then
                        error(
                            "Line " .. #line .. " attempted to use dummy spawnpoint " .. line.tag ..
                            " after an object was already spawned there!"
                        )
                    end
                    --spawnpoint angle needs to be overwritten with object angle
                    --also support angle overflow with back sector floor height
                    sp.angle = spawnedObj.angle / ANG1 + 360 * (backSec.floorheight >> FRACBITS)
                    spawnedObj.spawnpoint = sp
                    sp.mobj = spawnedObj
                end
            else
                error(
                    "Line " .. #line .. " uses " .. objName ..
                    " as the object to spawn, but it does not exist!"
                )
            end
        else
            error(
                "\133LINE " .. #line .. " DID NOT SET UP ITS TEXT FIELD! THIS SHOULDN'T HAPPEN!!"
            )
        end
    else
        error(
            "Line " .. #line ..
            " needs a front and back sector for complete minimal object spawning info!"
        )
    end
end

addHook("LinedefExecute", linedefType461, "SPAWNOBJ")

local function syncWork(network)
    dummySpCache = network(dummySpCache)
    dummySpTempCache = network(dummySpTempCache)
end

--don't load this twice
rawset(_G, "Ld461LuaLoaded", true)