--[[
 -- @Description:
 -- Hello , DiaoSi ~ ( or not ? ) ,glad you saw my comment
 -- If you think my mod is okay , you can sponsor my breakfast by a tiny amount donation  ($_$)
 -- paypal:td1madao@163.com     alipay:td1madao@163.com
 -- If any question ,Please email me
 -- What's more ,don't modify and republish my mods . 3Q for cooperation
 -- What's more ,don't modify and republish my mods . 3Q for cooperation
 -- What's more ,don't modify and republish my mods . 3Q for cooperation
 -- @version 1.0.0
 -- @author td1madao
 -- @email td1madao@163.com
 -- @qq 360810498
 -- @date 16/8/6
 ]]
local AUTHOR = "td1madao"
local NAMESPACE = "rivendst"
local PARENT_FN_LINE = 3
local function createTable(elem, child)
    if not elem or not child or elem[child] then
        return
    end
    elem[child] = {}
end

local td1madao_try_class = Class(function(self) end)
function td1madao_try_class:__call(tryfun)
    local success = true
    GLOBAL.xpcall(tryfun, function() success = false end)
    return success
end

local function createStrParam(...)
    local argNum = select("#", ...)
    local str = ""
    for i = 1, argNum do
        str = str .. tostring(select(i, ...)) .. ","
    end
    return str
end

local td1madao_try = td1madao_try_class()
_G.td1madao_try = td1madao_try
if not td1madao_try(function()
    local a = _G.TD
end) then
    _G.TD = {}
end
TD = _G.TD
local TD = _G.TD
createTable(TD, AUTHOR)
createTable(TD[AUTHOR], NAMESPACE)
local modField = TD.td1madao.rivendst
if not modField.createFn then
    modField.createFn = function(name, fn)
        if not name or not fn then
            return
        end
        if modField[name] then
            return
        end
        modField[name] = fn
    end
end
-- yeah ,these functions used for my personal test to optimization my code
modField.START_LOGGER = true
modField.START_MONITOR = false
modField.DEBUG_KEY = _G.KEY_F12
modField.NAMESPACE = "rivendst"
modField.ACTION = "td1madao_rivendst_all"
modField.createFn("log", function(...)
    if not modField.START_LOGGER then
        return
    end
    local printStr = string.format("eye>player={%s}", tostring(_G.ThePlayer))
    local info = debug.getinfo(2, "lS")
    if info then
        local filePath = info.source:sub(2) or "nil"
        local fileArr = string.td1madao_split(filePath, "/")
        printStr = printStr .. string.format(",file={%s},line={%s}", tostring(fileArr[#fileArr]), tostring(info.currentline))
    end
    local argNum = select("#", ...)
    if argNum > 0 then
        print(string.format("%s,param={%s}", printStr, createStrParam(...)))
    else
        print(printStr)
    end
end)
modField.createFn("see", function(...)
    if not modField.START_MONITOR then
        return
    end
    local argNum = select("#", ...)
    local counter = 1
    local info = debug.getinfo(2, "nlS")
    local printStr
    local formatter = "\t\tdebug>player={%s},fn={%s},file={%s},line={%s},param={%s}"
    if info then
        local stringBuilder = ""
        for i = 1, math.huge do
            local k, v = debug.getlocal(2, i)
            if k and k ~= "(*temporary)" then
                stringBuilder = stringBuilder .. string.format("%s=%s,", tostring(k), tostring(v))
            else
                break
            end
        end
        local filePath = info.source:sub(2) or "nil"
        local fileArr = string.td1madao_split(filePath, "/")
        printStr = string.format(formatter, tostring(_G.ThePlayer), tostring(info.name), tostring(fileArr[#fileArr]), tostring(info.currentline), tostring(stringBuilder))
    else
        local trace = ""
        modField.try(function()
            trace = debug.traceback()
        end)
        for line in string.gmatch(trace, "[^\r\n]+") do
            if counter == PARENT_FN_LINE then
                printStr = string.format(formatter, tostring(_G.ThePlayer), tostring(line), "nil", "nil", "nil")
                break
            end
            counter = counter + 1
        end
    end
    if argNum > 0 then
        print(string.format("%s,param=2{%s}", printStr, createStrParam(...)))
    else
        print(printStr)
    end
end)
modField.createFn("spawn", function(name)
    local elem
    if _G.Prefabs[name] then
        elem = _G.SpawnPrefab(name)
    else
        elem = _G.SpawnPrefab("rivendstnil")
    end
    return elem
end)
modField.createFn("remove", function(inst)
    if not inst then
        return
    end
    if inst.Remove then
        inst:Remove()
    end
end)
modField.createFn("canMoveTo", function(player, xn, yn, zn)
    if not player or not xn or not zn then
        return
    end
    return modField.moveableterrian(player, xn, 0, zn)
end)
modField.createFn("moveableterrian", function(a, bx, by, bz)
    local ax, ay, az = a:GetPosition():Get()
    local ground = _G.TheWorld
    local btile = ground.Map:GetTileAtPoint(bx, by, bz)
    return modField.similarterrian(ground.Map:GetTileAtPoint(ax, 0, az), btile)
end)
modField.createFn("similarterrian", function(atile, btile)
    return not (atile == _G.GROUND.IMPASSABLE or btile == _G.GROUND.IMPASSABLE)
end)
modField.createFn("try", function(tryfun)
    return td1madao_try(tryfun)
end)
modField.createFn("play", function(v, frame, forcePlay)
    local player = _G.ThePlayer
    if player.td1madao_forbid_anim_interrupt and not forcePlay then
        return
    end
    if v and v.sg and v.sg.sg and v.sg.sg.states and v.sg.sg.states[frame] and modField.live(v) then
        if v == player then
            v.sg:GoToState("idle")
        end
        modField.latter(v, 0.01, function()
            v.sg:GoToState(frame)
        end)
    end
end)
modField.createFn("live", function(v)
    return v and v.components.health and v.components.health.IsDead and not v.components.health:IsDead()
end)
modField.createFn("die", function(v)
    return v and v.components.health and v.components.health.IsDead and v.components.health:IsDead()
end)
modField.createFn("latter", function(inst, time, task)
    if not inst then
        return
    end
    return inst:DoTaskInTime(time, task)
end)

modField.createFn("anim", function(inst, time, anim, fn, clean, notIdle)
    if not inst or not time then
        return
    end
    inst:DoTaskInTime(time, function()
        if inst and anim then
            inst.AnimState:PlayAnimation(anim)
        end
        if fn then
            fn()
        end
        if clean then
            if inst.Physics.ClearMotorVelOverride then
                inst.Physics:ClearMotorVelOverride()
            end
            if not notIdle and inst.sg and inst.sg.sg and inst.sg.sg.states and inst.sg.sg.states.idle then
                inst.sg:GoToState("idle")
            end
            if not notIdle and inst.components.playercontroller then
                inst.components.playercontroller:Enable(true)
            end
        end
    end)
end)

modField.createFn("removeTask", function(inst, task, fun)
    if inst and inst[task] ~= nil then
        inst[task]:Cancel()
        inst[task] = nil
    end
    if fun then
        fun()
    end
end)
modField.createFn("flush", function(player, time)
    if not player then
        player = _G.ThePlayer
    end
    modField.removeTask(player, "td1madaoPreventFromStuck")
    player.td1madaoPreventFromStuck = player:DoTaskInTime(time or 0.2, function()
        modField.play(player, "idle")
    end)
end)
modField.createFn("dist", function(x1, x2, z1, z2)
    if not x1 or not x2 or not z1 or not z2 then
        return 65535
    end
    local dist = math.sqrt((x1 - x2) * (x1 - x2) + (z1 - z2) * (z1 - z2))
    return dist
end)
modField.createFn("distBetween", function(target1, target2)
    if not target2 then
        target2 = _G.ThePlayer
    end
    if not target1 or not target2 or not target1.Transform or not target2.Transform then
        return 65535
    end
    local x1, y1, z1 = target1.Transform:GetWorldPosition()
    local x2, y2, z2 = target2.Transform:GetWorldPosition()
    return modField.dist(x1, x2, z1, z2)
end)
modField.createFn("below", function(inst, itemName)
    if not inst then
        return
    end
    local tmp = modField.spawn(itemName)
    if tmp and tmp.Transform and tmp.Transform.SetPosition and inst and inst.GetPosition then
        tmp.Transform:SetPosition(inst:GetPosition():Get())
    end
    if tmp and tmp.Show then
        tmp:Show()
    end
    return tmp
end)

modField.createFn("color", function(item, r, g, b)
    if not item then
        return
    end
    if item and item.AnimState and item.AnimState.SetMultColour then
        item.AnimState:SetMultColour(r, g, b, 1)
    else
        if not item.components.highlight then
            item:AddComponent("highlight")
        end
        local frozenColor = Vector3(r, g, b)
        local r, g, b = frozenColor.x, frozenColor.y, frozenColor.z
        item.components.highlight:SetAddColour(Vector3(r, g, b))
    end
    return item
end)

modField.createFn("player_init", function(inst)
    if not inst:HasTag("playerghost") then
        inst.replica.health:SetIsDead(false)
    end
    inst.replica.combat._attackrange:set(2)
end)
modField.createFn("stopMove", function(inst)
    if not inst then
        return
    end
    if inst.components.playercontroller then
        inst.components.playercontroller:Enable(false)
    end
    if inst.components.locomotor then
        inst.components.locomotor:Stop()
        inst.components.locomotor:Clear()
    end
    inst:ClearBufferedAction()
    ------------------------------
end)
modField.createFn("child", function(inst, itemName)
    if not inst then
        return
    end
    local tmp = modField.spawn(itemName)
    if tmp and tmp.entity and inst.entity then
        tmp.entity:SetParent(inst.entity)
    end
    if tmp and tmp.Show then
        tmp:Show()
    end
    return tmp
end)
modField.createFn("resetY", function(inst)
    if not inst then
        return
    end
    local x, y, z = inst:GetPosition():Get()
    inst.Transform:SetPosition(x, 0, z)
end)
modField.createFn("find", function(target, dist)
    local x2, y2, z2 = target.Transform:GetWorldPosition()
    local ents = TheSim:FindEntities(x2, y2, z2, dist)
    return ents
end)

modField.createFn("server_attack", function(player, ents, effect, addition, ruinStruct, otherCondition)
    local player = player or _G.ThePlayer
    if not addition then
        addition = 1
    end
    local syn = modField.getC(player, "rivendst_common_synchronizer")
    local equip = player.components.inventory:GetEquippedItem(EQUIPSLOTS.HANDS)
    local hasDestory = false
    local dmg = syn.server_dmg or 10
    local multi = modField.getC(player, "rivendst_common_synchronizer").server_damagemultiplierDefault or 1
    local attackDmg = dmg * multi
    local time = GetTime()
    for k, v in pairs(ents) do
        if not otherCondition or (otherCondition and otherCondition(v)) then
            if modField.server_canAttack(player, v) then
                local additionLocal = addition
                if v:HasTag("player") then
                    additionLocal = 0.75
                end
                v.components.combat:GetAttacked(player, attackDmg * additionLocal)
                if math.random() < addition then
                    equip.components.weapon:OnAttack(player, v)
                end
                if effect then
                    effect(v)
                end
                if not v:HasTag("player") then
                    v.components.combat:SetTarget(player)
                end
                v.components.combat.laststartattacktime = time
                if modField.live(v) then
                    modField.goto(v, "hit")
                end
            elseif not modField.live(v) and ruinStruct and equip and equip.components.tool and
                    v and v.components.workable and v.components.workable.workleft > 0
                    and equip.components.tool.actions and equip.components.tool.actions[v.components.workable.action]
                    and player and player.components.hunger and not hasDestory then
                hasDestory = true
                player.components.hunger:DoDelta(-10, false, true)
                if equip and equip.components.finiteuses and equip.components.finiteuses.consumption[v.components.workable.action] then
                    local cost = equip.components.finiteuses.consumption[v.components.workable.action] * v.components.workable.workleft
                    if equip.components.finiteuses.current > cost then
                        equip.components.finiteuses:Use(cost)
                    else
                        equip.components.finiteuses.current = 1
                    end
                end
                modField.below(v, "collapse_small")
                v.components.workable:Destroy(player)
            end
        end
    end
end)
modField.createFn("blewUp", function(v, player)
    if v and v.sg and not v.components.health:IsDead() then
        if v.brain then
            v.brain:Stop()
        end
        if v:HasTag("player") then
            if v.components.playercontroller then
                v.components.playercontroller:Enable(false)
            end
        end
        local x2, y2, z2 = v:GetPosition():Get()
        local sign = 1
        modField.period(v, nil, function()
            local x3, y3, z3 = v:GetPosition():Get()
            v.Transform:SetPosition(x2, y3 + 0.7 * sign, z2)
        end, 0.05, 0.6, function()
            v.Transform:SetPosition(x2, 0, z2)
        end)
        v:DoTaskInTime(0.3, function()
            sign = -1
        end)
        v.components.combat:BlankOutAttacks(1)
        if v.sg and v.sg.sg and v.sg.sg.states and v.sg.sg.states.hit and not v.components.health:IsDead() then
            modField.goto(v, "hit")
        end
        v:DoTaskInTime(1, function()
            if v.brain then
                v.brain:Start()
            end
            if v:HasTag("player") then
                if v.components.playercontroller then
                    v.components.playercontroller:Enable(true)
                end
            end
        end)
        v:DoTaskInTime(1.5, function()
            if v.components.combat and not v:HasTag("player") then
                v.components.combat:SetTarget(player or _G.ThePlayer)
            end
            v.components.combat.laststartattacktime = GetTime()
        end)
    end
end)
modField.createFn("dizzy", function(v, player)
    if v and v.sg and not v.components.health:IsDead() then
        if v.brain then
            v.brain:Stop()
        end
        if v:HasTag("player") then
            if v.components.playercontroller then
                v.components.playercontroller:Enable(false)
            end
        end
        if v.components.locomotor then
            v.components.locomotor:Stop()
        end
        modField.child(v, "rivendizzy").Transform:SetPosition(0, 1.5, 0)
        v:DoTaskInTime(1.5, function()
            if v.brain then
                v.brain:Start()
                if v:HasTag("player") then
                    if v.components.playercontroller then
                        v.components.playercontroller:Enable(true)
                    end
                end
                if v.components.combat and not v:HasTag("player") then
                    v.components.combat:SetTarget(player or _G.ThePlayer)
                end
                v.components.combat.laststartattacktime = GetTime()
            end
        end)
    end
end)
modField.createFn("period", function(inst, name, fun, period, clean, cleanFn)
    if not name then
        if not inst.td1madao_doPeriodicTask_task_adder then
            inst.td1madao_doPeriodicTask_task_adder = 0
        end
        name = "td1madao_doPeriodicTask_task_adder" .. tostring(inst.td1madao_doPeriodicTask_task_adder)
        inst.td1madao_doPeriodicTask_task_adder = inst.td1madao_doPeriodicTask_task_adder + 1
    end
    if inst and name then
        modField.removeTask(inst, name)
        modField.removeTask(inst, name .. "Cleaner")
        inst[name] = inst:DoPeriodicTask(period or 1, fun)
        inst[name .. "Cleaner"] = inst:DoTaskInTime(clean or 1, function()
            if cleanFn then
                cleanFn()
            end
            modField.removeTask(inst, name)
            modField.removeTask(inst, name .. "Cleaner")
        end)
    end
end)
modField.createFn("goto", function(v, frame)
    if v and v.sg and v.sg.sg and v.sg.sg.states and v.sg.sg.states[frame] and modField.live(v) then
        v.sg:GoToState(frame)
    end
end)
modField.createFn("server_canAttack", function(player, v)
    local player = player or _G.ThePlayer
    local inst = player
    if v and v == player then
        return false
    elseif TheNet:GetPVPEnabled() and modField.live(v) and v.HasTag and v:HasTag("player") then
        return true
    elseif (v and v.components.container and v.components.container.canbeopened) or (v and v.components.follower and v.components.follower.leader == inst) or (player.components.combat and not player.components.combat:CanTarget(v)) then
        return false
    elseif v and v.components.inventoryitem and v.components.inventoryitem.owner then
        return false
    elseif v and v:HasTag("wall") then
        return false
    elseif v and player and player.components.inventory and player.components.inventory:GetActiveItem() == v then
        return false
    elseif v and modField.live(v) and v.components.combat and not v:HasTag("player") then
        return true
    end
    return false
end)
modField.createFn("fly", function(inst, world)
    if not inst then
        inst = _G.ThePlayer
    end
    inst.Physics:ClearMotorVelOverride()
    inst.Physics:SetCollisionGroup(COLLISION.CHARACTERS)
    inst.Physics:ClearCollisionMask()
    inst.Physics:CollidesWith(COLLISION.GROUND)
    if world then
        inst.Physics:CollidesWith(COLLISION.WORLD)
    end
end)
modField.createFn("stopFly", function(inst)
    if not inst then
        inst = _G.ThePlayer
    end
    inst.Physics:ClearMotorVelOverride()
    inst.Physics:SetCollisionGroup(COLLISION.CHARACTERS)
    inst.Physics:ClearCollisionMask()
    inst.Physics:CollidesWith(COLLISION.WORLD)
    inst.Physics:CollidesWith(COLLISION.OBSTACLES)
    inst.Physics:CollidesWith(COLLISION.CHARACTERS)
    inst.Physics:CollidesWith(COLLISION.ITEMS)
    inst.Physics:CollidesWith(COLLISION.GROUND)
end)
modField.createFn("player", function(inst, fun, notPlayerFn)
    if not inst or not fun then
        return
    end
    local player = _G.ThePlayer
    if player ~= nil then
        if player == inst then
            fun()
        elseif notPlayerFn and type(notPlayerFn) == "function" then
            notPlayerFn()
        end
    else
        local taskName = "td1madao_check_if_inst_is_player"
        modField.period(inst, taskName, function()
            local player = _G.ThePlayer
            if player ~= nil then
                if player == inst then
                    fun()
                elseif notPlayerFn and type(notPlayerFn) == "function" then
                    notPlayerFn()
                end
                modField.removeTask(inst, taskName)
                modField.removeTask(inst, taskName .. "Cleaner")
            end
        end, 0.25, 3)
    end
end)
modField.createFn("isPlayer", function(inst)
    local player = _G.ThePlayer
    return player and inst and player == inst and inst:HasTag("rivendst")
end)
modField.createFn("server", function(fun, notServerFn)
    if TheNet:GetIsServer() then
        fun()
    elseif notServerFn and type(notServerFn) == "function" then
        notServerFn()
    end
end)
modField.createFn("isServer", function(fun, notServerFn)
    return TheNet:GetIsServer()
end)
modField.createFn("common", function(inst, fun)
    modField.server(fun, function()
        modField.player(inst, fun)
    end)
end)
modField.createFn("addC", function(var1, var2)
    local inst
    local name
    if var1 and type(var1) == "string" and var2 == nil then
        inst = _G.ThePlayer
        name = var1
    else
        inst = var1
        name = var2
    end
    if not inst or not name then
        return
    end
    if not inst.components[name] then
        inst:AddComponent(name)
    end
end)
modField.createFn("getC", function(var1, var2)
    local inst
    local name
    if var1 and type(var1) == "string" and var2 == nil then
        inst = _G.ThePlayer
        name = var1
    else
        inst = var1
        name = var2
    end
    if not inst or not name then
        return
    end
    modField.addC(inst, name)
    return inst.components[name]
end)
modField.createFn("callC", function(var1, var2, var3)
    local inst
    local name
    local fun
    if var1 and var2 and type(var1) == "string" and type(var2) == "function" and var3 == nil then
        inst = _G.ThePlayer
        name = var1
        fun = var2
    else
        inst = var1
        name = var2
        fun = var3
    end
    if not inst or not name or not fun then
        return
    end
    if not inst.components[name] then
        return
    end
    local component = inst.components[name]
    fun(component)
end)
modField.createFn("action", function(actionName, tagStr, action, effect)
    local localAction = action or _G.Action({ priority = 10, rmb = true, distance = 36, mount_valid = true })
    localAction.str = tagStr or _G.TUNING.TD_STR_1
    localAction.id = string.upper(actionName or "hehe")
    localAction.fn = function(act)
        local player = act.doer
        if modField.isPlayer(player) and effect then
            return effect(act)
        end
        return false
    end
    AddAction(localAction)
    AddStategraphActionHandler("wilson", ActionHandler(localAction, "td1madao_rivendst_accepter"))
    AddStategraphActionHandler("wilson_client", ActionHandler(localAction, "td1madao_rivendst_accepter"))
    return localAction
end)
modField.createFn("sg", function(state)
    if not state then
        return
    end
    modField.server(function()
        AddStategraphState("wilson", state)
    end)
end)
modField.createFn("smart", function()
    return TUNING.TD1MADAO_RIVENDST_SMART_CAST == 1
end)
modField.createFn("empty", function() end)
modField.createFn("translate", function() 
    STRINGS.CHARACTERS.RIVENDST = STRINGS.CHARACTERS.WENDY
end)
