@name sShopKeeper2
@inputs Scr:wirelink [WalkSpace StandSpace Shield IdleSpace Radio]:entity
@persist [Items Config Meta]:table Status CurOffset
@persist User:entity Idle [Cursor Hold]:vector2 Using LastOffset Selection:string
@persist Walk [Pos LastPos]:vector Ang:angle Pending
@trigger Walk CurOffset

#include "lib/ct/shipmentmetadata"
Global=gTable("sKeeper2")

if (first() | dupefinished()) {
    
    ########### CONFIG ###########
    # Base
    Config["Interest",number]=1.15
    Config["GuiY",number]=360
    Config["MaxIdle",number]=30
    Config["IdleWarn",number]=15
    Config["WalkSpeed",number]=5.5
    Config["MaxOffset",number]=0.6
    Config["MaxDist",number]=150
    
    # Sounds
    Config["IdleWarning",string]="vo/npc/male01/doingsomething.wav"
    Config["DoorSound",string]="doors/door_latch3.wav"
    Config["ShieldSound",string]="doors/doormove2.wav"
    Config["Shutdown",string]="buttons/button11.wav"
    
    # Random Sound tables
    Config["Music",array]=array(
        "ambient/music/country_rock_am_radio_loop.wav",
        "ambient/music/cubanmusic1.wav",
        "ambient/music/flamenco.wav",
        "ambient/music/latin.wav",
        "ui/gamestartup21.mp3",
        "ui/gamestartup23.mp3"
    )
    Config["Greetings",array]=array(
        "vo/npc/male01/hi01.wav",
        "vo/npc/male01/hi02.wav",
        "vo/npc/male01/heydoc01.wav",
        "vo/npc/male01/heydoc02.wav"
    )
    Config["OnBuy",array]=array(
        "vo/npc/male01/fantastic01.wav"
    )
    Config["OnDeny",array]=array(
        "vo/npc/male01/getgoingsoon.wav"
    )
    Config["OnTimeout",array]=array(
        "vo/npc/male01/gethellout.wav"
    )
    Config["SelectSounds",array]=array(
        "vo/npc/male01/answer01.wav", # that's you all over
        "vo/npc/male01/answer02.wav", # i won't hold it against ya
        "vo/npc/male01/answer03.wav", # figures
        "vo/npc/male01/answer04.wav", # try not to dwell on it
        "vo/npc/male01/answer10.wav", # i wouldn't say that too louuuddd
        "vo/npc/male01/answer13.wav", # i'm with you
        "vo/npc/male01/answer14.wav", # you and me both
        "vo/npc/male01/answer22.wav", # you never know
        "vo/npc/male01/answer25.wav", # how bout that
        "vo/npc/male01/answer32.wav", # right on
        "vo/npc/male01/answer33.wav", # no argument there
        "vo/npc/male01/answer34.wav", # don't forget hawaii
        "vo/npc/male01/answer35.wav", # try not to let it get to ya
        "vo/npc/male01/answer40.wav", # there's a first time for everything
        "vo/npc/male01/evenodds.wav", # let's even the odds a little
        "vo/npc/male01/fantastic01.wav", # fantastic
        "vo/npc/male01/fantastic02.wav", # fantastic
        "vo/npc/male01/nice.wav" # nice
    )
    ##############################
    
    if (!WalkSpace | !StandSpace | !Shield | !Radio & !IdleSpace) {
        error("Not all entities are wired.")
    }
    
    Global["Vend",array]=array()
    
    # Base holos
    # Shopkeeper
    holoCreate(1)
    holoModel(1,"models/Humans/Group03/male_07.mdl")
    holoPos(1,IdleSpace:pos())
    holoAng(1,IdleSpace:angles())
    Pos=IdleSpace:pos()
    #stand=1, walk=10 
    
    # Shield
    holoCreate(2)
    holoModel(2,Shield:model())
    holoPos(2,Shield:pos())
    holoAng(2,Shield:angles())
    holoMaterial(2,Shield:getMaterial())
    Shield:setAlpha(0)
    
    # Offset Base
    holoCreate(3,Scr:egpToWorld(vec2(256,Config["GuiY",number])))
    holoAlpha(3,0)
    
    # Core Functions
    function string prettyInt(N:number) {
        #print(N)
        local Str=ceil(N):toString():reverse():explode("")
        local Count=Str:count()
        local Add=1
        for (I=0, Str:count(), 3) {
            if (I!=0 & I!=Count & Str[I+1,string]) {Str:insertString(I+Add,",") Add++}
        }
        return Str:concat():reverse()
    }
    function number pIR(Num:number) {
        local O=Cursor
        local Diff=Scr:egpSize(Num)/2
        local Pos=Scr:egpPos(Num)
        return inrange(O,Pos-Diff,Pos+Diff)
    }
    
    # Item Metadata
    local Holo=10
    foreach(I,V:table=ShipmentMetaData) {
        Items[I,table]=V
        Items[I,table]["Entity",entity]=noentity()
        Items[I,table]["Price",number]=ceil(V["Price",number]*Config["Interest",number])
        Items[I,table]["Holo",number]=Holo
        local Offset=256+((Holo-10)*64)
        Items[I,table]["Offset",number]=Offset
        Items["MaxOffset",number]=Offset+128
        Holo++
    }
    
    function buildUI() {
        foreach(I,V:table=ShipmentMetaData) {
            local Holo=V["Holo",number]
            local Offset=V["Offset",number]+CurOffset
            Scr:egpText(Holo,V["Name",string],vec2(Offset,Config["GuiY",number]-50))
            Scr:egpAlign(Holo,1,1)
            Scr:egpSize(Holo,10)
            
            local InStock=!V["Entity",entity]:isValid()
            Items[I,table]["CanBuy",number]=!InStock
            Scr:egpText(Holo+100,InStock ? "UNAVAILABLE" : "$"+prettyInt(V["Price",number]),vec2(Offset,Config["GuiY",number]-40))
            Scr:egpAlign(Holo+100,1,1)
            Scr:egpSize(Holo+100,10)
            Scr:egpColor(Holo+100,InStock ? vec(255,0,0) : vec(0,255,0))
            
            local Alpha=clamp(255-abs(256-(Offset+CurOffset)),0,255)
            Scr:egpAlpha(Holo,Alpha)
            Scr:egpAlpha(Holo+100,Alpha)
        }
        if (!Config["Tutorial",table][User:steamID(),number]) {
            Scr:egpText(200,"< Hold E and look left or right to navigate >",vec2(256,Config["GuiY",number]-100))
            Scr:egpText(201,"< Look down a bit and press E to purchase >",Scr:egpPos(200))
            Scr:egpAlpha(200,0)
            Scr:egpAlpha(201,0)
            Scr:egpAlign(200,1,1)
            Scr:egpAlign(201,1,1)
            Config["Tutorial",table][User:steamID(),number]=1
            Meta["TutoFade1",number]=0
            Meta["TutoFade2",number]=0
            timer("TutoFade1",1)
        }
        
    }
    
    function resetAll() {
        User=noentity()
        Status=0
        Pending=0
        foreach(I,V:table=Items) {
            holoDelete(V["Holo",number])
        }
        Scr:egpClear()
        soundPurge()
        Config["CalcShield",number]=1
        Config["ShieldGoal",number]=-0.1
        Shield:soundPlay(1,0,Config["ShieldSound",string])
    }
    
    # Misc Metadata
    Config["ShieldMin",vector]=Shield:pos()
    Config["ShieldMax",vector]=Shield:pos()+vec(0,0,48)
    
    local StandSize=StandSpace:aabbSize()
    Config["StandMin",vector]=StandSpace:toWorld(StandSize*vec(0.5,0.5,0))
    Config["StandMax",vector]=StandSpace:toWorld(StandSize*vec(-0.5,-0.5,30))
    
    local Size=(WalkSpace:aabbSize()/2)*Config["MaxOffset",number]
    local MinVec=WalkSpace:toWorld(Size)
    local MaxVec=WalkSpace:toWorld(-Size)
    Config["MaxPos",vector]=maxVec(MinVec,MaxVec)
    Config["MinPos",vector]=minVec(MinVec,MaxVec)
    
    Config["Blacklist",table]=table()
    Config["Tutorial",table]=table()
    
    Config["ShieldGoal",number]=-0.1
    
    printColor(vec(255,255,0),"[sKeeper2] ",vec(255),"sKeeper2 is now ",vec(0,255,0),"loaded",vec(255),".")
    resetAll()
}

Cursor=Scr:egpCursor(User)
if (User & !changed(Cursor)) {
    Idle+=0.1
    if (Idle==Config["IdleWarn",number]) {
        holoEntity(1):soundPlay(3,0,Config["IdleWarning",string])
    } elseif (Idle>=Config["MaxIdle",number]) {
        Config["Blacklist",table][User:steamID(),number]=curtime()+30
        resetAll()
    }
} else {Idle=0}

switch(Status){
    case 0,
        if (!User) {
            findInBox(Config["StandMin",vector],Config["StandMax",vector])
            findIncludeClass("player")
            local Plys=findToArray()
            foreach(I,V:entity=Plys) {
                if (V:isPlayer() & Config["Blacklist",table][V:steamID(),number]<curtime()) {
                    User=V
                    break
                }
            }
            if (Config["Door",number] & Pos:distance(IdleSpace:pos())>1) {
                Ang=(IdleSpace:pos()-Pos):toAngle()*ang(0,1,1)
                Pos=clamp(Pos+Ang:forward()*Config["WalkSpeed",number],Config["MinPos",vector],Config["MaxPos",vector])
                holoPos(1,Pos)
                holoAng(1,Ang)
                Walk=1
                if (Pos:distance(IdleSpace:pos())<Config["WalkSpeed",number]*2) {
                    Config["Door",number]=0
                    soundPlay(0,0,Config["DoorSound",string])
                    Walk=0
                }
            }
        } else {
            if (!Config["Door",number]) {
                soundPlay(0,0,Config["DoorSound",string])
                Config["Door",number]=1
            }
            Walk=1
            Ang=(User:pos()-Pos):toAngle()*ang(0,1,1)
            Pos=clamp(Pos+Ang:forward()*Config["WalkSpeed",number],Config["MinPos",vector],Config["MaxPos",vector])
            holoPos(1,Pos)
            holoAng(1,Ang)
            if ((Pos-LastPos):length()<1) {
                printColor(vec(255,255,0),"[sKeeper2] ",vec(255),"Player ",vec(0,255,0),User:name(),vec(255)," logged in.")
                Status=1
                Walk=0
                Shield:soundPlay(1,0,Config["ShieldSound",string])
                Config["ShieldGoal",number]=0.1
                Config["CalcShield",number]=1
                Radio:soundPlay(4,0,Config["Music",array][randint(Config["Music",array]:count()),string])
                timer("Greet",600)
            }
        }
    break
    case 1,
        if (clk("Greet")) {
            holoEntity(1):soundPlay(3,0,Config["Greetings",array][randint(Config["Greetings",array]:count()),string])
            Config["BaseAlpha",number]=0
            buildUI()
            timer("fade",100)
        }
        if (clk("fade") & Config["BaseAlpha",number]<1) {
            Config["BaseAlpha",number]=Config["BaseAlpha",number]+0.1
            timer("fade",100)
        }
        if (clk("TutoFade1")) {
            if (Meta["TutoFade1",number]<255) {
                Meta["TutoFade1",number]=Meta["TutoFade1",number]+255/10
                Scr:egpAlpha(200,Meta["TutoFade1",number])
            }
            if (!Meta["Used",number]) {
                timer("TutoFade1",100)
            } else {
                timer("TutoFade2",100)
            }
        }
        
        if (clk("TutoFade2")) {
            if (Meta["TutoFade2",number]<255) {
                Meta["TutoFade2",number]=Meta["TutoFade2",number]+255/10
                Scr:egpAlpha(201,Meta["TutoFade2",number])
                Scr:egpAlpha(200,255-Meta["TutoFade2",number])
                timer("TutoFade2",100)
            } else {
                timer("TutoFade3",5000)
            }
        }
        
        if (clk("TutoFade3")) {
            if (Meta["TutoFade3",number]<255) {
                Meta["TutoFade3",number]=Meta["TutoFade3",number]+255/10
                Scr:egpAlpha(201,255-Meta["TutoFade3",number])
                timer("TutoFade3",100)
            }
        }
        
        if (User:keyUse() & !Pending) {
            Meta["Used",number]=1
            if (Cursor:distance(vec2(256,Config["GuiY",number]))>20) {
                if (!Using) {
                    Hold=Cursor
                    LastOffset=CurOffset
                    Using=1
                }
                CurOffset=clamp(LastOffset+Cursor:x()-Hold:x(),-Items["MaxOffset",number]/2,0)
            } elseif (!Using & Items[Selection,table]["CanBuy",number]) {
                moneyRequest(User,
                Items[Selection,table]["Price",number],
                "sShopKeeper2 - Buying "+Items[Selection,table]["Name",string],
                10)
                Pending=1
            }
        } else {
            Using=0
            if (Config["SelectionChanged",number]) {
                holoEntity(1):soundPlay(3,0,Config["SelectSounds",array][randint(Config["SelectSounds",array]:count()),string])
                Config["SelectionChanged",number]=0
            }
            local Clamp=round(CurOffset/64)*64
            local Clamp2=CurOffset-(CurOffset-Clamp)/2
            CurOffset=round(clamp(Clamp2,-Items["MaxOffset",number]/2,0))
        }
        
        holoPos(3,Scr:egpToWorld(vec2(CurOffset,Config["GuiY",number])))
        local Rot=Scr:entity():angles()+ang(0,curtime()*15,0)
        local ZOffset=sin(curtime())*5
        foreach(I,V:table=Items) {
            local Offset=V["Offset",number]
            local Alpha=clamp(255-abs(256-(Offset+CurOffset)),0,255)*Config["BaseAlpha",number]
            if (abs(Alpha)>=255) {continue} # skip redundancies
            local ID=V["Holo",number]
            holoAlpha(ID,Alpha)
            Scr:egpAlpha(ID,Alpha)
            Scr:egpAlpha(ID+100,Alpha)
            holoAng(ID,Rot)
            local XPos=Offset+CurOffset
            Scr:egpPos(ID,vec2(XPos,Config["GuiY",number]-50))
            Scr:egpPos(ID+100,vec2(XPos,Config["GuiY",number]-40))
            Scr:egpColor(ID,vec(255))
            holoScale(ID,vec(0.5))
            if (Alpha>250) {
                Selection=I
                holoAlpha(ID,255)
                Scr:egpColor(ID,vec(0,172,0))
            }
        }
        
    break
}

local EyePos=User:attachmentPos("eyes")
local Yaw=-bearing(Pos,Ang,EyePos)
holoSetPose(1,"head_yaw",Yaw)

if (User & User:pos():distance(StandSpace:pos())>Config["MaxDist",number]) {
    resetAll()
}

if (Config["CalcShield",number]) {
    Config["ShieldCur",number]=clamp(Config["ShieldCur",number]+Config["ShieldGoal",number],0,1)
    ShieldPos=Config["ShieldCur",number]*(Config["ShieldMax",vector]-Config["ShieldMin",vector])+Config["ShieldMin",vector]
    holoPos(2,ShieldPos)
    if (Config["ShieldCur",number]==(Config["ShieldGoal",number]>0 ? 0 : 1)) {
        Config["CalcShield",number]=0
    }
}

if (changed(Walk) & Walk<2) {
    holoAnim(1,Walk ? 10 : 1)
}

if (chatClk(owner())) {
    local Command=owner():lastSaid():explode(" ")
    if (Command[1,string]==".add") {
        hideChat(1)
        local Ship=owner():aimEntity()
        if (Ship:isShipment()) {
            local Type=Ship:shipmentType()
            Items[Type,table]["Entity",entity]=Ship
            printColor(vec(255,255,0),"[sKeeper2] ",vec(255),"Added ",vec(0,255,0),Type,vec(255),".")
        } else {
            printColor(vec(255,255,0),"[sKeeper2] ",vec(255,0,0),"This is a not a valid shipment.")
        }
    }
}

if (moneyClk() | moneyNoClk() | clk("timeout")) {
    if (moneyClk()) {
        Global["Vend",array]:pushEntity(Items[Selection,table]["Entity",entity])
        holoEntity(1):soundPlay(3,0,Config["OnBuy",array][randint(Config["OnBuy",array]:count()),string])
        if (Items[Selection,table]["Entity",entity]:shipmentAmount()==1) {
            Items[Selection,table]["Entity",entity]=noentity()
            Items[Selection,table]["CanBuy",number]=0
            local ID=Items[Selection,table]["Holo",number]
            Scr:egpSetText(ID+100,"UNAVAILABLE")
            Scr:egpColor(ID+100,vec(255,0,0))
            printColor(vec(255,255,0),"[sKeeper2] ",vec(255),"The shipment ",vec(255,0,0),Selection,vec(255)," has expired.")
        }
    } elseif (moneyNoClk()) {
        holoEntity(1):soundPlay(3,0,Config["OnDeny",array][randint(Config["OnDeny",array]:count()),string])
    } elseif (moneyTimeout()) {
        holoEntity(1):soundPlay(3,0,Config["OnTimeout",array][randint(Config["OnTimeout",array]:count()),string])
    }
    Pending=0
    
}
LastPos=Pos
runOnChat(1)
interval(150)
