--[[
    implementation of linedef 461 in SRB2K v1.3
    
    v1_0_RC1
        Using underscores instead of periods in version for better SLADE compatibility
        Cleaned up code for release
        Removed unnecessary global-to-local caching
        Added distinguishment to script warnings. Now highlighted magenta
        Commented out code for getting object name. Don't need the memory wasted
        Fixed dummy spawnpoint caching to be more robust
        Added protection against freeslot famine
    
    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

--object must be freeslotted
if freeslot("MT_DUMMYSP") == nil then
    return
end
local MT_DUMMYSP = MT_DUMMYSP

local function scriptWarn(...)
    --local info = debug.getinfo(2, "Sl")
    --local prefix = string.format("\129WARNING:\128 %s:%d: ", info.short_src, info.currentline)
    local scriptName = "Linedef461Ex v1.0"
    local prefix = string.format("\129WARNING:\128: %s: ", scriptName)
    local items = {...}
    if items[1] then
        items[1] = string.format("%s%s", prefix, tostring($))
    else
        items[1] = prefix
    end
    print(unpack(items))
end

--local MF_NOSECTOR = MF_NOSECTOR
--local MF_NOBLOCKMAP = MF_NOBLOCKMAP
--local MF_NOGRAVITY = MF_NOGRAVITY
--local MF_NOCLIP = MF_NOCLIP
--local MF_NOTHINK = MF_NOTHINK
--local MF_NOCLIPHEIGHT = MF_NOCLIPHEIGHT
--local MF_NOCLIPTHING = MF_NOCLIPTHING
local ML_NOCLIMB = ML_NOCLIMB
local FRACBITS = FRACBITS
local ML_EFFECT1 = ML_EFFECT1
local ML_EFFECT2 = ML_EFFECT2

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 dummySpCacheDefrosting = false
local dummySpCache = {}

local function doesDummySpCacheNeedDefrost()
    if not leveltime then
        if not dummySpCacheDefrosting then
            dummySpCacheDefrosting = true
        end
    else
        dummySpCacheDefrosting = false
    end
    return dummySpCacheDefrosting
end

--[[
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
        scriptWarn(string.format("Dummy spawnpoint %d has a duplicate!", spId))
        return
    end
    dummySpCache[spId] = sp
end

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

local function tryCollectDummySp(spId)
    for thing in mapthings.iterate do
        if isValidDummySp(thing) and thing.angle == spId then
            dummySpCache[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()
end

local function checkDummySpCacheDefrostReset()
    if doesDummySpCacheNeedDefrost() then
        resetDummySpCache()
    end
end

local function asapAfterMapLoad()
    checkDummySpCacheDefrostReset()
end

addHook("ThinkFrame", asapAfterMapLoad)

local function linedefType461(line)
    checkDummySpCacheDefrostReset()
    
    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
                        scriptWarn(string.format(
                            "Line %d attempted to use dummy spawnpoint %d for object with no associated mapthing (%s)!",
                            #line,
                            line.tag,
                            objName
                        ))
                        return
                    end
                    --line tag specifies dummy spawnpoint ID
                    local sp = dummySpCache[line.tag]
                    if not sp or not sp.valid then
                        --try to find spawnpoint
                        sp = tryCollectDummySp(line.tag)
                        if not sp then
                            scriptWarn(string.format(
                                "Line %d attempted to use dummy spawnpoint %d when it does not exist!",
                                #line,
                                line.tag
                            ))
                            return
                        end
                    end
                    if sp.mobj and sp.mobj.type ~= MT_DUMMYSP then
                        scriptWarn(string.format(
                            "Line %d attempted to use dummy spawnpoint %d after an object was already spawned there!",
                            #line,
                            line.tag
                        ))
                        return
                    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
                scriptWarn(string.format(
                    "Line %d uses %s as the object to spawn, but it does not exist!",
                    #line,
                    objName
                ))
            end
        else
            scriptWarn(string.format(
                "\133LINE %d DID NOT SET UP ITS TEXT FIELD! THIS SHOULDN'T HAPPEN!!",
                #line
            ))
        end
    else
        scriptWarn(string.format(
            "Line %d needs a front and back sector for complete minimal object spawning info!",
            #line
        ))
    end
end

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

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

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