@name metamount3
@inputs Vehicle:entity Camera:wirelink
@outputs GaitFrameAdvanced GaitFrameTime LinkArray:table Links
@persist [DestHeight JumpHeight MaxSpeed Acceleration BreakFactor]
@persist [CollisionRadius FallDampeningFactor] MoveDir:vector StepL StepH
@persist [Position]:vector [Angle]:angle [Speed Gravity MaxGravity] [Base Player]:entity
@persist GaitTable:table GaitFrameTime GaitFrameAdvanced WorldDownVec:vector TailBase:entity

@trigger none

if (first()) {

    #--------------
    #-- Settings
    
    DestHeight          = 25
    JumpHeight          = 50
    MaxSpeed            = 50
    MaxGravity          = 10
    Acceleration        = 5
    BreakFactor         = 0.8
    CollisionRadius     = 32
    FallDampeningFactor = 0.7
    
    #--------------
    #-- Setup
    
    runOnLast(1)
    rangerHitEntities(1)
    rangerFilter(Vehicle)
    rangerPersist(1)
    
    holoCreate(1)
    holoModel(1, "models/sprops/misc/origin.mdl")
    
    Base = holoEntity(1)
    
    if (Vehicle["Position", vector]) {
        Position = Vehicle["Position", vector]
        holoPos(1, Position)
    }
    else {
        Position = entity():pos() + vec(0, 0, DestHeight)
        holoPos(1, Position)
    }
    
    function setup() {
        Camera["Parent", entity]   = Base
        Camera["Position", vector] = vec(8, 0, 38)
        Camera["Distance", number] = 0
        
        rangerFilter(Vehicle)
    }
    
    function number lerp(A, B, X) {
        return A + (B - A) * X
    }
    
    setup()
    
    holoCreate(9,Base:pos())
    holoParent(9,1)

    WorldDownVec = vec(0,0,-1)

    function addGait( Name:string, Base:entity, RangerOffset:vector, CycleOffset, StepMaxL, StepMaxH ) {
        GaitTable[Name, table] = table(
            "cycleOffset" = CycleOffset,
            "stepMaxL"    = StepMaxL,
            "stepMaxH"    = StepMaxH,
            "baseEnt"     = Base,
            "rangerPosO"  = RangerOffset,
            "rangerMaxL"  = StepMaxH*5
        )
    }

    function runGaits( Rate, Debug ) {
        GaitFrameTime += Rate
        GaitFrameAdvanced = $GaitFrameTime != 0

        foreach (K, Gait:table = GaitTable) {
            Gait["stepCurL", number] = Gait["stepCurL", number] + (Gait["stepMaxL", number]*GaitFrameAdvanced - Gait["stepCurL", number])*0.5
            Gait["stepCurH", number] = Gait["stepCurH", number] + (Gait["stepMaxH", number]*GaitFrameAdvanced - Gait["stepCurH", number])*0.5

            # step foot pos
            Gait["outputPos", vector] = rangerOffset(Gait["rangerMaxL", number], Gait["baseEnt", entity]:toWorld(Gait["rangerPosO", vector] + vec(
                    sin(GaitFrameTime + Gait["cycleOffset", number])*Gait["stepCurL", number], 0, 0)), WorldDownVec):position() + vec(
                        0, 0, max(0, cos(GaitFrameTime + Gait["cycleOffset", number])*Gait["stepCurH", number]))

            # step foot ang
            Gait["outputAng", angle] = Gait["baseEnt", entity]:toWorld(ang(-sin(GaitFrameTime + Gait["cycleOffset", number])*Gait["stepCurL", number], 0, 0))

            # step debugging
            if (Debug) {
                if (!Gait["debugged", number]) {
                    local UniqueID = hash(K)
    
                    holoCreate(UniqueID, vec(), vec(1/2))
                    holoParent(UniqueID, Gait["baseEnt", entity])
                    holoModel(UniqueID, "sphere")
    
                    Gait["debugged", number] = UniqueID
                    continue
                }
    
                holoPos(Gait["debugged", number], Gait["outputPos", vector])
                holoAng(Gait["debugged", number], Gait["outputAng", angle])             
            }

        }
    }

    function vector getGaitPos( Name:string ) {
        return GaitTable[Name, table]["outputPos", vector]
    }

    function angle getGaitAng( Name:string ) {
        return GaitTable[Name, table]["outputAng", angle]
    }

    addGait( "fl", Base, vec(30, 15, 0), 0, 30, 15 )
    addGait( "fr", Base, vec(30, -15, 0), 45, 30, 15 )
    addGait( "rl", Base, vec(-30, 15, 0), 90, 30, 15 )
    addGait( "rr", Base, vec(-30, -15, 0), 135, 30, 15 )
    


    function table:newLink(Idx, Parent, Offset:vector, Holo:entity)    {
        Links++
        This[Links, table] = table(
            "Idx"  = Idx,
            "Holo" = Holo,
            "Parent" = holoEntity(Parent) ?: holoEntity(9),
            "Offset" = Offset,
            "Roll"   = 0
        )
    }
    
    function table:doLink()   {
        for (I = 1, Links) {
            local T = This[I, table]
            local Id = T["Idx", number]
            holoPos(Id, T["Parent", entity]:toWorld(T["Offset", vector]))
            holoAng(Id, (T["Parent", entity]:pos() - holoEntity(Id+1):pos()):toAngle())
        }
    }
    


    for (I = 20, 26)    {
        holoCreate(I)
        holoScaleUnits(I, vec(5,5,5) - (I-20)*0.25)
        holoModel(I, "sphere")

        LinkArray:newLink( I, I == 20 ? 9 : I - 1, I == 20 ? vec(-35,0,5) : vec(-10,0,0), holoEntity(I) )
    }
        
            
#[    addGait( "fl", Base, vec(30, 15, 0), 0, 30, 15 )
    addGait( "fr", Base, vec(30, -15, 0), 180, 30, 15 )
    addGait( "rl", Base, vec(-30, 15, 0), 90, 30, 15 )
    addGait( "rr", Base, vec(-30, -15, 0), 270, 30, 15 )]#
 
}

interval(90)


#--------------
#- Vehicle
Player = Vehicle:driver()

if (!Vehicle:parent()) {
    Vehicle:propFreeze(1)
    
    local Pos = holoEntity(9):toWorld(vec(0, 0, 0))
    local Ang = holoEntity(9):toWorld(ang(0, -90, 0))

    Vehicle:setPos(Pos)
    Vehicle:setAng(Ang)
    
    if ((Vehicle:pos() - Pos):length() < 0.1 & vec(Vehicle:angles() - Ang):length() < 0.1) {
        Vehicle:parentTo(holoEntity(9))
        
        setup()
    }
}
if (last()) { 
    Vehicle["Position", vector] = Position
    Vehicle:deparent()
}


if (Player) {

    #--------------
    #- Camera

    Camera["Activated", number] = 1
    if (Player:keyReload()) { Vehicle:ejectPod() }
    
    #--------------
    #- Directions
    
    local Trace = rangerOffsetHull(DestHeight*2, Position, vec(0, 0, -1), vec(24))
    
    local AimDir  = Vehicle:toLocalAxis(Player:eye()):setZ(0):normalized()
    local SideDir = AimDir:cross(vec(0, 0, 1))
    local Forward = Player:keyForward() - Player:keyBack()
    local Strafe  = Player:keyRight() - Player:keyLeft()
    
    if (Forward | Strafe) { Speed = min(Speed + Acceleration, MaxSpeed + Player:keySprint()*10) } else { Speed *= BreakFactor }

    local MoveDir = (AimDir*Forward + SideDir*Strafe):normalized()
    
    #--------------
    #- Gravity

    if (Trace:distance() <= DestHeight + 2) {
        Gravity *= FallDampeningFactor
        
        local Diff = Trace:distance() - DestHeight
        Position += Diff*vec(0, 0, -1)
    }
    elseif (Trace:distance() <= DestHeight + 20) {
        if (MoveDir:length() > 0) {
            local SlopeDot = MoveDir:dot(Trace:hitNormal())
            
            if (SlopeDot > 0.1) {
                local Diff = Trace:distance() - DestHeight
                Position += Diff*vec(0, 0, -1)
            }
            
        }
    }
    else { 
        Gravity = min(Gravity - 3, Gravity)
    }
    
    #--------------
    #- Movement
    
    Position += MoveDir*Speed + vec(0, 0, Gravity)
    
    if (MoveDir:length() > 0) {
        Angle = slerp(quat(MoveDir:toAngle()), quat(Angle), 0.5):toAngle()

        local Trace = rangerOffsetHull(CollisionRadius, Position, MoveDir, vec(CollisionRadius))
        
        local Dist = Trace:distance()
        if (Dist <= CollisionRadius) {
            local Normal = Trace:hitNormal()
            local Diff = -(Dist - CollisionRadius)
            local Approach = MoveDir * Speed
            
            local Reflection = -2 * (Approach:dot(Normal)) * Normal - Approach
            
            Position += Reflection:normalized() * Diff
        }
        
        #Anim += Speed*pi()/3
    } else {
        #Anim *= BreakFactor
    }
    
    holoPos(1, mix(Position, Base:pos(), 0.5))
    holoAng(1, Angle)
    
    
    #GaitTable["fl", table]["stepMaxL", number] = Speed
    #GaitTable["fr", table]["stepMaxL", number] = Speed
    #GaitTable["rl", table]["stepMaxL", number] = Speed
    #GaitTable["rr", table]["stepMaxL", number] = Speed
    
    
    runGaits( Speed*pi()/3*MoveDir:length(), 1 )
    
    
    local BreathS = sin( curtime()*270 )*(1 - MoveDir:length())*3
    local BreathC = cos( curtime()*270 )*(1 - MoveDir:length())*3
    
    holoPos(9, rangerOffset(DestHeight*2, Base:pos(), WorldDownVec):position() + vec(0, 0, BreathS + DestHeight + abs(cos(GaitFrameTime/2))*GaitTable["fl", table]["stepCurH", number]))
    holoAng(9, Base:toWorld(ang(BreathC + sin(-GaitFrameTime)*GaitTable["fl", table]["stepCurH", number], 0, -Base:toLocalAxis(MoveDir*Speed):y())))
    
 
    LinkArray:doLink()

}
else {
    Camera["Activated", number] = 0
}

