# This will controll characters.

import os
import bge
from Scripts import Opt
import math
import json
import time
import mathutils
import random


from Scripts import Reuse
from Scripts.Common import *
from Scripts import Vehicle
from Scripts import Multiplayer_Shared

controls = {

    "forward" :keycodes["W"],
    "backward":keycodes["S"],
    "left"    :keycodes["A"],
    "right"   :keycodes["D"],   
    "jump"    :keycodes["LCtrl"],    
    "getcar"  :keycodes["E"],  
    "swimdown":keycodes["LShift"],
} 

if "chr_controls" not in bge.logic.globalDict:
    bge.logic.globalDict["chr_controls"] = controls
controls = bge.logic.globalDict["chr_controls"]

# Loading animation data.
def LoadAnimations():

    # Loading Json animation data files.
    
    folder = bge.logic.expandPath("//animdata/chr/")

    if "character-anim-data" not in bge.logic.globalDict:
        animations = {}

        for f in os.listdir(folder):
            if f.endswith(".json"):
                print("Reading", f, "...")
                with open(folder+f) as fl:
                    data = json.load(fl)

                name = f.replace(".json", "")

                animations[name] = data
        bge.logic.globalDict["character-anim-data"] = animations

    return bge.logic.globalDict["character-anim-data"]

animations = LoadAnimations()

def DaniUpdate():
    
    # This function is specifically meant for Dani.
    # Since he is the main character. But if you
    # want to modify the game, so somebody else is
    # the main character. Use this function on that
    # character.
    
    cont = bge.logic.getCurrentController()
    scene = bge.logic.getCurrentScene()
    dani = cont.owner
    
    keys = bge.logic.globalDict.get("keys", [])


        
    
    if not dani["driving"]:

        # Jump animation logic
        onground = OnGround(dani)
        if dani.get("not-onground", 0) > 20 and onground and "Water" not in str(onground):
            dani["landing"] = 13
            
        if onground:
            dani["not-onground"] = 0
        else:
            try: dani["not-onground"] += 1
            except: pass

        if not onground or dani.get("grab"):
            GrabEdge(dani)
            
        dani["onground"] = onground

        if dani.get("landing"):
            ApplyAnimation(dani, "Land")
            WalkForward(dani)
            
            device = bge.logic.globalDict["SoundDevice"]
            sound  = bge.logic.globalDict["sounds"]["dani_land"]
            if not sound["play"] or not sound["play"].status:
                sound["play"] = device.play(sound["sound"])

            StopWalkSound(dani)
                
            dani["landing"] -= 1
        elif not onground and not dani.get("underwater"):
            if not dani.get("grab"):
                ApplyAnimation(dani, "Fly")
            else:
                ApplyAnimation(dani, "Hang")
            StopWalkSound(dani)

            
        # Moving back and forth
        if controls["forward"] in keys:
            WalkForward(dani)                     
        elif controls["backward"] in keys:
            WalkBackward(dani)
            
        # Moving left to right
        if controls["left"] in keys:
            WalkLeft(dani)
        elif controls["right"] in keys:
            WalkRight(dani)
            
        # Jumping
        if controls["jump"] in keys:
            Jump(dani)
        else: dani["jumping"] = False
            
        # Swiming down
        if controls["swimdown"] in keys:
            GoDown(dani)
        
        if not dani.get("swimming_down"):
            WaterBoyancy(dani)
            
            
        # Standing still animation
        if not keys and not dani["walkingtowardcar"]:
            ApplyAnimation(dani, "Stand")
            StopWalkSound(dani)
        #AutoStand(dani)
        
        # If getting car
        if controls["getcar"] in keys:
            getIntoCar(dani)
            
        elif dani["walkingtowardcar"]:
            walkTowardCar(dani)
            
            if any(controls[x] in keys for x in ["forward", "backward", "left", "right"]):
                getOutCar(dani)
        
    else:
        
        ApplyAnimation(dani, "Drive")
        
        # If getting out of car
        if controls["getcar"] in keys:
            getOutCar(dani)
    
    
    
            
    # Executing camera changes
    ExecuteChangeCameraTarget()
    CameraRuleOfThirds(dani)
    
    # Reverting swimming
    dani["swimming_down"] = False
    
def OnGround(character):

    # Returns whether the car
    # in on the ground or not.

    # Getting the relative position of the ground
    down   = Vehicle.RelativePoint(character, [0,0,-0.6])
    center = Vehicle.RelativePoint(character, [0,0, 0.6])

    # See if there is grown under the car
    ray = character.rayCast(down, center,  poly=True)

    # If nothing is sensed, we return None
    if not ray[1]:
        return None

    # Else we return the material of the object underneith
    # Useful to see on what kind of ground the car is.
    else:
        try: return ray[3].material
        # If there is no material data it will game the name
        # of the object
        except: return ray[0].name

def WalkForward(character):
    
    toFPS = Opt.ToFPS()
    character.applyMovement([0,-0.05*toFPS,0], True)
    
    # Facing correctly
    rig = getRig(character)
    rot = rig.localOrientation.to_euler()
    target = 0
    rot[2] = rot[2] + (( target - rot[2] ) / 5 )   
    rig.localOrientation = rot
    
    if not character.get("underwater"):
        ApplyAnimation(character, "Walk")
    else:
        ApplyAnimation(character, "Swim")

    WalkSound(character)
    
def WalkBackward(character):
    
    toFPS = Opt.ToFPS()
    character.applyMovement([0,0.05*toFPS,0], True)
    
    # Facing correctly
    rig = getRig(character)
    rot = rig.localOrientation.to_euler()
    target = math.pi
    if rot[2] < 0: target -= math.pi*2
    rot[2] = rot[2] + (( target - rot[2] ) / 5 ) 
    rig.localOrientation = rot
    
    if not character.get("underwater"):
        ApplyAnimation(character, "Walk")
    else:
        ApplyAnimation(character, "Swim")

    WalkSound(character)

def WalkLeft(character):
    
    toFPS = Opt.ToFPS()
    character.applyMovement([0.05*toFPS,0,0], True)
    
    # Facing correctly
    if not character.get("grab"):
        rig = getRig(character)
        rot = rig.localOrientation.to_euler()
        target = math.pi/2
        if rot[2] < -(math.pi/2): rot[2] += math.pi*2
        rot[2] = rot[2] + (( target - rot[2] ) / 5 ) 
        rig.localOrientation = rot
    
        
    
    if not character.get("underwater"):
        ApplyAnimation(character, "Walk")
    else:
        ApplyAnimation(character, "Swim")

    WalkSound(character)
    
def WalkRight(character):
    
    toFPS = Opt.ToFPS()
    character.applyMovement([-0.05*toFPS,0,0], True)
    
    # Facing correctly
    if not character.get("grab"):
        rig = getRig(character)
        rot = rig.localOrientation.to_euler()
        target =  -(math.pi/2)
        if rot[2] > math.pi/2: rot[2] -= math.pi*2
        rot[2] = rot[2] + (( target - rot[2] ) / 5 ) 
        rig.localOrientation = rot
    
    if not character.get("underwater"):
        ApplyAnimation(character, "Walk")
    else:
        ApplyAnimation(character, "Swim")

    WalkSound(character)
    
def Jump(character):
    
    toFPS = Opt.ToFPS()
    character.applyMovement([0, 0, 0.07*toFPS], False)

    character["jumping"] = True
    
    if "Dani" in character.name:

        device = bge.logic.globalDict["SoundDevice"]
        sound  = bge.logic.globalDict["sounds"]["dani_jump"]
        
        if OnGround(character) and ( not sound["play"] or not sound["play"].status ) :
            
            sound["play"] = device.play(sound["sound"])
    
def GoDown(character):
    
    toFPS = Opt.ToFPS()
    character.applyMovement([0, 0, -0.1*toFPS], False)
    character["swimming_down"] = False
    
def WaterBoyancy(character):
    
    # Creating water boyancy underwater
    
    toFPS = Opt.ToFPS()
    scene = bge.logic.getCurrentScene()

    
    if character["underwater"] and character.position[2] < -10.5 and not character["driving"]:

        #character.worldLinearVelocity.z /= 10
        #if round(character.worldLinearVelocity.z, 1) == 0.0:
        character.worldLinearVelocity.z = 0.5
        
        #character.applyForce([0,0,character.mass*9.8*-min(0, character.worldLinearVelocity[2])]) #*min(1.1, (character.position[2]+9.5)*-10)
        scene.objects["DanisRig"]["underwater"] = True
    elif character.position[2] > -9.5:
        character["underwater"] = False
        scene.objects["DanisRig"]["underwater"] = False

def getIntoCar(character, car=None, passanger=False):
    
    # This function will run when any character wants to get into
    # any car
    
    # If character already in a car or walking to another car
    # we give this up.
    if character.get("driving") or character.get("walkingtowardcar") or character.get("last_change",0) > time.time()-1:
        return

    
    if not car:
        # If a car is not specified we are going to choose the
        # closest car within 10 meters away from Dani.
        
        closest = 10
        car = None
        
        for trycar in bge.logic.globalDict["allcars"]:
            distance = trycar.getDistanceTo(character)
            if distance < closest:
                car = trycar    
                closest = distance
    if not car:
        
        # There is no car within this distance we give up
        return
        
    # Then we want to choose the closest car target.
    
    dani_stand_target = car.children["Dani_Stand_Target"]
    try: other_stand_target = car.children["Other_Stand_Target"]
    except: other_stand_target = dani_stand_target
            
    # Now we want to activate the movement.
        
    character["walkingtowardcar"] = car
    if not passanger:
        character["walkingtowardcartarget"] = dani_stand_target
    else:
        character["walkingtowardcartarget"] = other_stand_target
    character["becomepassanger"] = passanger
    
    # Readjust the camera
    
    # if character.name == "Dani_Box":
    #     car.children["CarCamTarget"].orientation = [0.2,0,0]
    #     zoom = car["specs"].get("cameraZoom", 3)
    #     ChangeCameraTarget(car.children["CarCamTarget"], 100, [zoom,zoom,zoom])
    pass
        
def walkToward(character, point):
    
    # Function used when character needs to walk somewhere
    
    walkVector = character.getVectTo(point)
    
    # We want the character to walk only to the strongest
    # vector. So he will not end up flapping arround all
    # the time.

    fro = character.position.copy()
    fro.z += 1
    
    ray = Vehicle.BeautyRayCast(character, "walk",
                                point,
                                fro)
    if ray[0] and ray[0].position != point and ray[0] != point and ray[0].visible:

        obstacleVector = character.getVectTo(ray[1])

        # Instead of trying to walk toward the target we will instead walk
        # around the obstacle
        
        WalkForward(character)
        if obstacleVector[2][0] > 0:            
            character.alignAxisToVect(obstacleVector[1], 0, 0.5)
        else:
            character.alignAxisToVect(-obstacleVector[1], 0, 0.5)
        character.alignAxisToVect( (0,0,1), 2, 1.0 )
        
        
        return walkVector
        
    else:
        walkVectorIs = walkVector[2]
    
    if walkVectorIs[1] < 0:
        WalkForward(character)
    elif walkVectorIs[1] > 0:
        WalkBackward(character)
    
    if walkVectorIs[0] > 0:
        character.applyRotation([0,0,walkVectorIs[0]/5], False)
        #WalkLeft(character)
    elif walkVectorIs[0] < 0:
        #WalkRight(character)
        character.applyRotation([0,0,walkVectorIs[0]/5], False)
        
    if walkVectorIs[2] > 0.1:
        Jump(character)
    elif walkVectorIs[2] < -0.1:
        GoDown(character)
        
        
    return walkVector

def ChangeCameraTarget(targetobject, delay, scaling=None, callable=None, rotate=True):
    
    # This function changes dani's camera target
    # useful for when getting into cars and the
    # mounting point of the camera is changed.
    # And because I can, it is also delayed.
    
    scene = bge.logic.getCurrentScene()
    
    bge.logic.globalDict["CameraTargetChange"] = {"delay":delay,
                                                  "remaining":delay,
                                                  "target":targetobject,
                                                  "targetscale":scaling,
                                                  "callable":callable,
                                                  "rotate":rotate}
    
    cam_parent = scene.objects["DaniCam_Parent"]
    cam_parent.removeParent()
    
    cam_parent["startingPosition"] = cam_parent.position.copy()
    cam_parent["startingOrientation"] = mathutils.Vector(cam_parent.orientation.to_euler())

    # This fixes the spins that are more than 360 degrees
    targetRotation = mathutils.Vector(targetobject.orientation.to_euler())
    for i in range(3):
        d = targetRotation[i] - cam_parent["startingOrientation"][i]
        if d > math.pi:
              cam_parent["startingOrientation"][i] += 2 * math.pi
        elif d < -math.pi:
              cam_parent["startingOrientation"][i] -= 2 * math.pi
    
    cam_parent["startingScaling"] = cam_parent.scaling.copy()
    
def CameraRuleOfThirds(dani):
    
    # This function implements the rule of thirds.
    # The rule of thirds is a technique to compose
    # pretty images. Where the main subject in the
    # frame is positioned on in the center of the
    # frame, but rather slightly to the side.
    # Imagine dividing the frame in 3. And putting
    # your subject on one of those division lines.
    # This is rule of thirds.
        
    # One more think to keep in mind. We want more empty
    # frame toward the direction where the main character
    # is looking.
    
    scene = bge.logic.getCurrentScene()
    cam = scene.objects["DaniCam"]
    
    # We do that only for the walking.
    if not dani["driving"]:
        
        # We want to get the z rotation of Dani's rig
        # which is determening where he is looking.
        # We are going to use local space orientation
        # since it is in relation to the parent, which
        # rotates in sinc with the camera.
        
        daniZrot = getRig(dani).localOrientation.to_euler()[2]
        
        # Finding the multiplication fraction of the effect
        # When dani points in the same direction as the camera
        # we want the effect to be 0. The same as if we look
        # directly at him from the front. But looking at him
        # from the side should give 100% effect.
        
        frac = math.cos(daniZrot * 2) - 1
        
        # Limiting the effect to be between 0 and 1
        frac = min(0, max(-1, frac))
        
        # Multiplying the effectiveness of the effect
        frac = frac ** 0.6
        if type(frac) == complex: frac = float(frac.real)
        
        
        # Flipping the effect when he looks right, versus left.
        if daniZrot > 0: frac *= -1
        
        # Applying the math to the camera
        cam.localOrientation = [math.pi/2,    # X - Move camera up, so it is not looking at the floor
                                -frac/3,      # Y - Add a dutch angle since it's cool and cool is cool
                                math.pi+frac] # Z - Add the rule of thirds.

    else:
        # Applying the math to the camera
        cam.localOrientation = [math.pi/2,    # X - Move camera up, so it is not looking at the floor
                                0,
                                math.pi]
        
def ExecuteChangeCameraTarget():
    
    # This function will execute the actual camera movement itself.
    
    
    scene = bge.logic.getCurrentScene()
    
    if "CameraTargetChange" not in bge.logic.globalDict:
        return
    
    cam_parent = scene.objects["DaniCam_Parent"]
    
    
    targetObject = bge.logic.globalDict["CameraTargetChange"]["target"]
    delayFull = bge.logic.globalDict["CameraTargetChange"]["delay"]
    delayRemaining = bge.logic.globalDict["CameraTargetChange"]["remaining"]
    ToRotate = bge.logic.globalDict["CameraTargetChange"]["rotate"]
    
    
    # Counting down frames of the delay
    bge.logic.globalDict["CameraTargetChange"]["remaining"] -= 1
            
    # Getting a fraction of how much from that other target the character walked
    try:
        camFactor = ( delayRemaining / delayFull ) ** 2
    except:
        camFactor = 1
    camFactor = camFactor * -1 + 1
    camFactor = min(1, max(0, camFactor))
        
        
    # Applying camera location and rotation based on this factor
        
    targetObject.orientation = [0.2,0,0]
        
    target_position = targetObject.position
    target_orientation = mathutils.Vector(targetObject.orientation.to_euler())
    
    
    if bge.logic.globalDict["CameraTargetChange"]["targetscale"]:
        target_scale = mathutils.Vector(bge.logic.globalDict["CameraTargetChange"]["targetscale"])
    
    if delayRemaining <= 0:
        
        
        if ToRotate:
            cam_parent.orientation = target_orientation
        else:
            vect = cam_parent.getVectTo(target_position)
            vect[1].z = min(-0.1, vect[1].z)
            cam_parent.alignAxisToVect(-vect[1], 1, 0.1)
            

        cam_parent.position = target_position
            
        if bge.logic.globalDict["CameraTargetChange"]["targetscale"]:
            cam_parent.scaling = target_scale
        cam_parent.setParent(targetObject, 0, 0)
        
        if bge.logic.globalDict["CameraTargetChange"]["callable"]:
            bge.logic.globalDict["CameraTargetChange"]["callable"]()
        
        del bge.logic.globalDict["CameraTargetChange"]
        
        return
        
    cam_parent.position = ( cam_parent["startingPosition"] + ( target_position - cam_parent["startingPosition"] ) * camFactor )
    if ToRotate:
        cam_parent.orientation = ( cam_parent["startingOrientation"] + ( target_orientation - cam_parent["startingOrientation"] ) * camFactor )
    if bge.logic.globalDict["CameraTargetChange"]["targetscale"]:
        cam_parent.scaling = ( cam_parent["startingScaling"] + ( target_scale - cam_parent["startingScaling"] ) * camFactor )

def walkTowardCar(character):
    
    # This function is running while the character is getting into car

    settings = bge.logic.globalDict["settings"]
    
    walkVector = walkToward(character, character["walkingtowardcartarget"])
    
    scene = bge.logic.getCurrentScene()
    car = character["walkingtowardcar"]
    finaltarget = car.children["Dani_Stand_Target"]
    passanger = character["becomepassanger"]

    if passanger:
        try: finaltarget = car.children["Other_Stand_Target"]
        except: pass
        
    # Change to final target
        
    if walkVector[0] < 1 and character["walkingtowardcartarget"] != finaltarget:
        character["walkingtowardcartarget"] = finaltarget
        
    # Change to sitting inside of the car
    
    elif walkVector[0] < 1 and character["walkingtowardcartarget"] == finaltarget:
        
        StopWalkSound(character)
        character["walkingtowardcar"] = False
        if character.name == "Dani_Box":
            car["active"] = True

        character["driving"] = car
        
        if not passanger:    
            car["driver"] = character
        else:
            if "passangers" not in car:
                car["passangers"] = []
            car["passangers"].append(character)
            
        car["npc"] = ""
        car["enemy"] = ""
        car["shake"] = 0.0
        
        rig = getRig(character)
        rig["driving"] = True
        target = car.children["Dani_Sit_Target"]
        
        if passanger:
            try: target = car.children["Other_Sit_Target"]
            except: pass
            
        rig.removeParent()
        pos = target.worldPosition
        rig.position = [pos[0],pos[1],pos[2]]
        pos = target.worldOrientation
        rig.orientation = [pos[0],pos[1],pos[2]]
        rig.setParent(target, False, False)

        character.suspendPhysics(True)
        character.setParent(car, False, False)

                
        #car_rig = car.children[car.get("rig","RIG")]
        #car_rig.playAction(car.get("rigaction", "NeonspeedsterRIGAction"), 10,20)
        
        if character.name == "Dani_Box":
            scene.objects["Gage"].visible = True
            scene.objects["Gage_Arrow"].visible = True
            scene.objects["Gage_Health"].visible = True
            scene.objects["Gage_Nitro"].visible = True
            scene.objects["Gage_Tachometer"].visible = True
            scene.objects["Gage_Gear"].visible = True
            scene.objects["Gage_Redline"].visible = True
            if settings.get("veh_mouse"):
                
                if settings.get("veh_mouse_guides"):
                    scene.objects["Mouse_Turn_UI_Piece"].visible = True
                    scene.objects["Mouse_Turn_UI"].visible = True
                    scene.objects["Mouse_Accelation_UI_Piece"].visible = True
                    scene.objects["Mouse_Accelation_UI"].visible = True

                if settings.get("veh_mouse_cursor"):
                    bge.render.showMouse(True)


            Vehicle.RemoveNetId(car)

        print(consoleForm(character), "in car:", consoleForm(car))
        
        
            
        # Sound device for sounds.
        device = bge.logic.globalDict["SoundDevice"]
        
        sound = bge.logic.globalDict["sounds"]["door"]
        sound["play"] =  device.play(sound["sound"])
        sound["play"].location = car.worldPosition
        sound["play"].velocity = car.getLinearVelocity()
        sound["play"].relative = False
        sound["play"].distance_maximum = 200
        sound["play"].distance_reference = 1
        sound["play"].attenuation = 1
        sound["play"].volume = 1.5

        
        # Readjust the camera
            
        if character.name == "Dani_Box":
            car.children["CarCamTarget"].orientation = [0.2,0,0]
            zoom = car["specs"].get("cameraZoom", 3)
            ChangeCameraTarget(car.children["CarCamTarget"], 20, [zoom,zoom,zoom])
    
def getOutCar(character):
    
    # Fucntion to get out of car
    
    scene = bge.logic.getCurrentScene()
    
    # If character is not in the car
    if character["walkingtowardcar"]:    
        car = character["walkingtowardcar"]
        soundDo = False
    elif character["driving"]:
        car = character["driving"]
        soundDo = True
    else:
        return
    
    rig = getRig(character)

    if character.name == "Dani_Box":
        car["active"] = False

        # Unfucking the camera
        cam = bge.logic.getCurrentScene().active_camera
        cam.shift_x = 0
        cam.shift_y = 0
        cam.lens    = 20
        
    if character == car.get("driver"):
        car["driver"] = None
    if character in car.get("passangers",[]):
        car["passangers"].remove(character)
    
    rig["driving"] = False
    
    character["last_change"] = int(time.time())
    
    character.restorePhysics()
    character.removeParent()
    character.localLinearVelocity = [0,0,0]
    if -car.localLinearVelocity[1] > 10 and not character["walkingtowardcar"]:
        character["landing"] = 13
    
    pos = car.worldOrientation.to_euler()
    character.orientation = [0,0,float(pos[2])]

    if not character.get("walkingtowardcar"):
        character.position[2] += 1.5

    character["driving"] = False
    
    
    rig.removeParent()
    pos = character.worldPosition
    rig.position = [pos[0],pos[1],pos[2]]
    pos = character.worldOrientation
    rig.orientation = [pos[0],pos[1],pos[2]]
    rig.setParent(character, False, False)
    
    # Readjust camera    
    if character.name == "Dani_Box":
        character.children["Dani_cam_Target"].orientation = [0,0,0]
        ChangeCameraTarget(character.children["Dani_cam_Target"], 20, [1,1,1])
        
        # if soundDo:
        #     rig = car.children["RIG"]
        #     rig.playAction(car.get("rigaction", "NeonspeedsterRIGAction"), 10,20)
        
        scene.objects["Gage"].visible = False
        scene.objects["Gage_Arrow"].visible = False
        scene.objects["Gage_Health"].visible = False
        scene.objects["Gage_Nitro"].visible = False
        scene.objects["Gage_Tachometer"].visible = False
        scene.objects["Gage_Gear"].visible = False
        scene.objects["Gage_Redline"].visible = False
        scene.objects["Mouse_Turn_UI_Piece"].visible = False
        scene.objects["Mouse_Turn_UI"].visible = False
        scene.objects["Mouse_Accelation_UI_Piece"].visible = False
        scene.objects["Mouse_Accelation_UI"].visible = False

        bge.render.showMouse(False)
        
            
        if not character.get("walkingtowardcar"):
            device = bge.logic.globalDict["SoundDevice"]
            sound = bge.logic.globalDict["sounds"]["door"]
            sound["play"] =  device.play(sound["sound"])
            sound["play"].location = car.worldPosition
            sound["play"].velocity = car.getLinearVelocity()
            sound["play"].relative = False
            sound["play"].distance_maximum = 200
            sound["play"].distance_reference = 1
            sound["play"].attenuation = 1
            sound["play"].volume = 1.5
            
    character["walkingtowardcar"] = False

    print(consoleForm(character), "got out of:", consoleForm(car))

def getRig(character):
    
    scene = bge.logic.getCurrentScene()
    
    if type(character.get("rig", "")) == str and character.get("rig", ""):
        rig = scene.objects[character.get("rig", "")]
    elif character.get("rig"):
        rig = character.get("rig")
        
    return rig
        
def ApplyAnimation(character, animation, keep=False, **kwargs):

    # Optimization
    cam = bge.logic.getCurrentScene().active_camera
    if character in Reuse.reuse.get(character.name, [])\
       or cam.getDistanceTo(character) > 150:
        return
    
    # Exctracting data request
    rig        = getRig(character)   
    action     = rig.get("Action") 
    startframe = animation[0]
    endframe   = animation[1]

    # Failsafe
    if not action:
        return
    
    # If we are reading animation from the database
    if type(animation) == str and animation in animations.get(character.name, {}):
        
        data       = animations[character.name][animation]
        action     = data.get("Action", action)
        keep       = data.get("Keep", keep)
        startframe = data["Frames"][0]
        endframe   = data["Frames"][1]
        kwargs     = data.get("kwargs", kwargs)
    
    # Sometimes we might want to keep the animation at it's
    # last frame when it ends.
    if keep and rig.getActionFrame(kwargs.get("layer", 0)) == endframe:
        startframe = endframe
    
    
    # Executing the animation
    rig.playAction(action, startframe, endframe, **kwargs)

    # To activate the stand animation we want to know if anything is playing
    if kwargs.get("layer", 0) == 0:

        # Recording the animation
        rig["animation"] = animation

def AutoStand(character):

    # This function will automatically play the stand animation if
    # nothing else is playing.

  
    rig = getRig(character)
    
    if not rig.get("animation"):
        ApplyAnimation(character, "Stand")

    else:

        animation = rig.get("animation")
        
        startframe = animation[0]
        endframe   = animation[1]
        
        # If we are reading animation from the database
        if type(animation) == str and animation in animations.get(character.name, {}):
        
            data       = animations[character.name][animation]
            keep       = data.get("Keep", None)
            startframe = data["Frames"][0]
            endframe   = data["Frames"][1]
            kwargs     = data.get("kwargs", {})

        if rig.getActionFrame(0) == endframe:
            rig["animation"] = None

def GrabEdge(character):

    rig = getRig(character)
    
    chest     = Vehicle.RelativePoint(rig, [0,0,0.7])
    fromchest = Vehicle.RelativePoint(rig, [0,-1,0.7])

    chestray = Vehicle.BeautyRayCast(character, "chest", fromchest, chest)

    face     = Vehicle.RelativePoint(rig, [0,0,1.5])
    fromface = Vehicle.RelativePoint(rig, [0,-1,1.5])

    faceray = Vehicle.BeautyRayCast(character, "face", fromface, face)

    try:    facedist  = math.dist(face, faceray[1])
    except: facedist = 1
    try:    chestdist = math.dist(chest, chestray[1])
    except: chestdist = 1
    
    if chestdist < facedist:
        
        
        if not character.get("jumping"):

            dist =   chestdist+0.01
            toedge =  Vehicle.RelativePoint(rig, [0,-dist,1.0])
            fromedge = Vehicle.RelativePoint(rig, [0,-dist,1.5])
            edgeray = Vehicle.BeautyRayCast(character, "edge", toedge, fromedge)
            if edgeray[1]:
                character["grab"] = float(edgeray[1].z-1.2)
            else:
                character["grab"] = float(character.position.z)                            
            character.position.z = character["grab"]
            character.worldLinearVelocity = [0,0,0]

            
            
        else:
            
            character["grab"] = character.position.z


        # Ratate character to face the point.
        vect = character.getVectTo(chestray[2])
        character.alignAxisToVect(chestray[2], 1, 1)
        character.alignAxisToVect((0,0,1), 2, 1)

        vect = rig.getVectTo(chestray[2])
        rig.alignAxisToVect(chestray[2], 1, 1)
        rig.alignAxisToVect((0,0,1), 2, 1)

        character.applyMovement((0,-0.01,0), True)
        
        
    else:
        character["grab"] = None


            
### SOUND STUFF ####
            
def WalkSound(character):

    if "Dani" in character.name and character.get("onground") and not character.get("landing"):
        device = bge.logic.globalDict["SoundDevice"]
        sound  = bge.logic.globalDict["sounds"]["dani_walk"]
        if not sound["play"] or not sound["play"].status:
            sound["play"] = device.play(sound["sound"])
def StopWalkSound(character):

    try: bge.logic.globalDict["sounds"]["dani_walk"]["play"].stop()
    except: pass
    
            
##### STORY RELATED FUNCTIONS ####
            
def StoryCharacter(character):

     # It may run from an actuator, so we want to make sure we get the object
    # and not the actuator.
    scene, character = Vehicle.GetSceneAndCar(character)
    
    
    if character.get("walkingtowardcar"):
            walkTowardCar(character)


    if not character.get("driving"):
        
        AutoStand(character)
        
    else:
        
        ApplyAnimation(character, "Drive")

    ReactionsToDaniDriving(character)

def ReactionsToDaniDriving(character):

    # This function will make characters react to dani's driving
    # when they are sitting in the same car.

    scene = bge.logic.getCurrentScene()
    dani = scene.objects["Dani_Box"]

    # First we need to make sure that the character is in the
    # car as dani.

    if not character.get("driving") == dani.get("driving") or not character.get("driving"):
        return

    # Then we want to get data about what if going on with the car.

    car = character.get("driving")
    crash = car.get("crash", 0.0)
    near  = car.get("near", 0.0)
    speed = -car.localLinearVelocity[1]

    car["crash"] = 0.0
    car["near"]  = 0.0

    react = None
    
    if crash > 0.1:
        react = "crash"

    if speed > 50:
        react = "speed"
        
    if near >= 0.4:
        react = "near"

    

    device = bge.logic.globalDict["SoundDevice"]

    try:
        voices = bge.logic.globalDict[character.name+"Voices"]
    except:
        return
    
    if ( not character.get("saying") or not character.get("saying").status ) and react:
        sound = random.choice(voices[react])
        while character.get("lastsaid") == sound:
            sound = random.choice(voices[react])
        character["lastsaid"] = sound
        sound["play"] = device.play(sound["sound"])
        character["saying"] = sound["play"]

        sound["play"].volume = 2

        
        ApplyAnimation(character, "Talk")


##### NETWORK RELATE FUNCTIONS ####

def Encode(character):

    data = {
        "ID"          :id(character),
        "type"        :"chr",
        "name"        :character.name,
        "netId"       :character.get("netId"),
        "position"    :list(character.position),
        "orientation" :list(character.orientation.to_euler())
    }

    character["netId"] = data["netId"]

    netObjects = bge.logic.globalDict["netObjects"]
    if data["ID"] not in netObjects["pythonId"]:
        netObjects["pythonId"][data["ID"]] = character
    
    return data

def Decode(data, network=False):

    # Decoding character data

    netObjects = bge.logic.globalDict["netObjects"]

    if data.get("netId") not in netObjects["netId"]:

        # Making the character
        try:
            character = Reuse.Create(data.get("name"))
        except:
            character = Reuse.Create("MoriaBox")

        netObjects["netId"][data.get("netId")] = character

    character = netObjects["netId"][data.get("netId")]
    character["netId"] = data.get("netId")

    character.position    = data.get("position", (0,0,0))
    character.orientation = data.get("orientation", (0,0,0))