// TODO: move cameraYaw into the PlayerCmd struct
        override public void Tick()
        {
            base.Tick();
            if (!hasAuthority)
            {
                return;
            }

            // TODO: this doesn't need to be done *every* frame
            CheckDespawn(300);

            if (pendingKill)
            {
                return;
            }

            // hacky spawn
            if (!active)
            {
                if (!WorldUtils.GetFirstSolidBlockDown(1000, ref spawnPosition))
                {
                    return;
                }
                SetActive(spawnPosition);
            }

            UpdateSilhouette();

            var head = go.GetChildComponent <MeshRenderer>("Head");

            if (head != null)
            {
                if (stunned)
                {
                    head.material.color = Color.yellow;
                }
                else if (stunInvincibilityTimer > 0)
                {
                    head.material.color = Color.gray;
                }
                else if (skidding)
                {
                    head.material.color = Color.cyan;
                }
                else
                {
                    head.material.color = Color.white;
                }
            }
        }
        public override void Tick()
        {
            base.Tick();
            if (!hasAuthority)
            {
                return;
            }

            if (!_hackDidFindGround)
            {
                if (WorldUtils.GetFirstSolidBlockDown(1000, ref position))
                {
                    position          += Vector3.up;
                    _hackDidFindGround = true;
                    go.SetActive(true);
                }
                else
                {
                    return;
                }
            }

            var dt = world.deltaTime;

            if (!inMotion)
            {
                if (velocity != Vector3.zero ||
                    !WorldUtils.IsSolidBlock(world.GetBlock(position)))
                {
                    inMotion = true;
                }
            }

            if (inMotion)
            {
                var newVel = velocity;
                {
                    bool onGround = WorldUtils.IsSolidBlock(world.GetBlock(position)) && velocity.y <= 0;
                    if (!onGround)
                    {
                        float gravity = -30f;
                        newVel.y += gravity * dt;
                    }
                    velocity = newVel;
                }

                var newPos = position;
                {
                    newPos.y += velocity.y * dt;
                    bool onGround = WorldUtils.IsSolidBlock(world.GetBlock(newPos)) && velocity.y <= 0;
                    if (onGround)
                    {
                        float bounceVel         = -5f;
                        float bounceCoefficient = 0.5f;
                        float friction          = 10f;
                        newPos.y = Mathf.Ceil(newPos.y);
                        if (velocity.y > bounceVel)
                        {
                            newVel = newVel - Mathf.Max(1f, dt * friction) * velocity;
                        }
                        else
                        {
                            newVel.y = -newVel.y * bounceCoefficient;
                        }
                        if (newVel.magnitude < 0.1f)
                        {
                            newVel = Vector3.zero;
                        }
                    }
                    velocity = newVel;
                    var moveXZ = new Vector3(velocity.x * dt, 0, velocity.z * dt);
                    if (!WorldUtils.IsSolidBlock(world.GetBlock(newPos + moveXZ)) && velocity.y != 0)
                    {
                        position = newPos + moveXZ;
                    }
                    else
                    {
                        var moveX = new Vector3(velocity.x * dt, 0, 0);
                        if (!WorldUtils.IsSolidBlock(world.GetBlock(newPos + moveX)) && velocity.y != 0)
                        {
                            position = newPos + moveX;
                        }
                        else
                        {
                            var moveZ = new Vector3(0, 0, velocity.z * dt);
                            if (!WorldUtils.IsSolidBlock(world.GetBlock(newPos + moveZ)) && velocity.y != 0)
                            {
                                position = newPos + moveZ;
                            }
                        }
                    }
                }
            }

            float yaw = 0;

            go.transform.SetPositionAndRotation(position, Quaternion.AngleAxis(yaw * Mathf.Rad2Deg, Vector3.up));

            if ((_marker == null) && ((data.mapMarker != null) && (data.mapMarker.Load() != null)))
            {
                // this is just horrible, normally we'd have the gamestate on the client from the World object
                // but this is all server code... so f**k it dude let's go bowling.
                if (GameManager.instance.clientWorld.gameState != null)
                {
                    _marker = AddGC(GameManager.instance.clientWorld.gameState.hud.CreateMapMarker(data.mapMarker.Load(), data.mapMarkerStyle));
                    _marker.SetAsFirstSibling();                     // always sort last.
                }
            }
            if (_marker != null)
            {
                _marker.worldPosition = new Vector2(position.x, position.z);
            }
        }
        public override void Tick()
        {
            if (!_addedToCamera && Client.Actors.ClientPlayerController.localPlayer != null && Client.Actors.ClientPlayerController.localPlayer.cameraController != null && rigidBody != null)
            {
                Client.Actors.ClientPlayerController.localPlayer.cameraController.AddTarget(this);
                _addedToCamera = true;
            }

            // kinda hacky here
            RecordInputDevices();

            base.Tick();
            if (!hasAuthority)
            {
                return;
            }

            // Hacky initial spawn
            if (!active)
            {
                if (WorldUtils.GetFirstSolidBlockDown(1000, ref spawnPoint))
                {
                    spawnPoint += Vector3.up * 2;
                    velocity    = Vector3.zero;
                    position    = spawnPoint;
                    Respawn();
                    active = true;

                    var horseData = CritterData.Get("horse");
                    var c         = gameMode.SpawnCritter(horseData, position + new Vector3(3, 0, 0), yaw, team);
                    c.SetActive(position + new Vector3(3, 0, 0));
                }
                else
                {
                    return;
                }
            }



            var head = go.GetChildComponent <MeshRenderer>("Head");

            if (head != null)
            {
                if (stunned)
                {
                    head.material.color = Color.yellow;
                }
                else if (stunInvincibilityTimer > 0)
                {
                    head.material.color = Color.gray;
                }
                else if (dodgeTimer > 0)
                {
                    head.material.color = Color.black;
                }
                else if (skidding)
                {
                    head.material.color = Color.cyan;
                }
                else
                {
                    head.material.color = Color.white;
                }
            }
        }