Esempio n. 1
0
        partial void UpdateNetPlayerPositionProjSpecific(float deltaTime, float lowestSubPos)
        {
            if (character != GameMain.Client.Character || !character.AllowInput)
            {
                //remove states without a timestamp (there may still be ID-based states
                //in the list when the controlled character switches to timestamp-based interpolation)
                character.MemState.RemoveAll(m => m.Timestamp == 0.0f);

                //use simple interpolation for other players' characters and characters that can't move
                if (character.MemState.Count > 0)
                {
                    CharacterStateInfo serverPos = character.MemState.Last();
                    if (!character.isSynced)
                    {
                        SetPosition(serverPos.Position, false);
                        Collider.LinearVelocity = Vector2.Zero;
                        character.MemLocalState.Clear();
                        character.LastNetworkUpdateID = serverPos.ID;
                        character.isSynced            = true;
                        return;
                    }

                    if (character.MemState[0].SelectedCharacter == null || character.MemState[0].SelectedCharacter.Removed)
                    {
                        character.DeselectCharacter();
                    }
                    else if (character.MemState[0].SelectedCharacter != null)
                    {
                        character.SelectCharacter(character.MemState[0].SelectedCharacter);
                    }

                    if (character.MemState[0].SelectedItem == null || character.MemState[0].SelectedItem.Removed)
                    {
                        character.SelectedConstruction = null;
                    }
                    else
                    {
                        if (character.SelectedConstruction != character.MemState[0].SelectedItem)
                        {
                            foreach (var ic in character.MemState[0].SelectedItem.Components)
                            {
                                if (ic.CanBeSelected)
                                {
                                    ic.Select(character);
                                }
                            }
                        }
                        character.SelectedConstruction = character.MemState[0].SelectedItem;
                    }

                    if (character.MemState[0].Animation == AnimController.Animation.CPR)
                    {
                        character.AnimController.Anim = AnimController.Animation.CPR;
                    }
                    else if (character.AnimController.Anim == AnimController.Animation.CPR)
                    {
                        character.AnimController.Anim = AnimController.Animation.None;
                    }

                    Vector2 newVelocity        = Collider.LinearVelocity;
                    Vector2 newPosition        = Collider.SimPosition;
                    float   newRotation        = Collider.Rotation;
                    float   newAngularVelocity = Collider.AngularVelocity;
                    Collider.CorrectPosition(character.MemState, out newPosition, out newVelocity, out newRotation, out newAngularVelocity);

                    newVelocity = newVelocity.ClampLength(100.0f);
                    if (!MathUtils.IsValid(newVelocity))
                    {
                        newVelocity = Vector2.Zero;
                    }
                    overrideTargetMovement = newVelocity.LengthSquared() > 0.01f ? newVelocity : Vector2.Zero;

                    Collider.LinearVelocity  = newVelocity;
                    Collider.AngularVelocity = newAngularVelocity;

                    float distSqrd       = Vector2.DistanceSquared(newPosition, Collider.SimPosition);
                    float errorTolerance = character.AllowInput ? 0.01f : 0.2f;
                    if (distSqrd > errorTolerance)
                    {
                        if (distSqrd > 10.0f || !character.AllowInput)
                        {
                            Collider.TargetRotation = newRotation;
                            SetPosition(newPosition, lerp: distSqrd < 5.0f);
                        }
                        else
                        {
                            Collider.TargetRotation = newRotation;
                            Collider.TargetPosition = newPosition;
                            Collider.MoveToTargetPosition(true);
                        }
                    }

                    //unconscious/dead characters can't correct their position using AnimController movement
                    // -> we need to correct it manually
                    if (!character.AllowInput)
                    {
                        float mainLimbDistSqrd       = Vector2.DistanceSquared(MainLimb.PullJointWorldAnchorA, Collider.SimPosition);
                        float mainLimbErrorTolerance = 0.1f;
                        //if the main limb is roughly at the correct position and the collider isn't moving (much at least),
                        //don't attempt to correct the position.
                        if (mainLimbDistSqrd > mainLimbErrorTolerance || Collider.LinearVelocity.LengthSquared() > 0.05f)
                        {
                            MainLimb.PullJointWorldAnchorB = Collider.SimPosition;
                            MainLimb.PullJointEnabled      = true;
                        }
                    }
                }
                character.MemLocalState.Clear();
            }
            else
            {
                //remove states with a timestamp (there may still timestamp-based states
                //in the list if the controlled character switches from timestamp-based interpolation to ID-based)
                character.MemState.RemoveAll(m => m.Timestamp > 0.0f);

                for (int i = 0; i < character.MemLocalState.Count; i++)
                {
                    if (character.Submarine == null)
                    {
                        //transform in-sub coordinates to outside coordinates
                        if (character.MemLocalState[i].Position.Y > lowestSubPos)
                        {
                            character.MemLocalState[i].TransformInToOutside();
                        }
                    }
                    else if (currentHull?.Submarine != null)
                    {
                        //transform outside coordinates to in-sub coordinates
                        if (character.MemLocalState[i].Position.Y < lowestSubPos)
                        {
                            character.MemLocalState[i].TransformOutToInside(currentHull.Submarine);
                        }
                    }
                }

                if (character.MemState.Count < 1)
                {
                    return;
                }

                overrideTargetMovement = Vector2.Zero;

                CharacterStateInfo serverPos = character.MemState.Last();

                if (!character.isSynced)
                {
                    SetPosition(serverPos.Position, false);
                    Collider.LinearVelocity = Vector2.Zero;
                    character.MemLocalState.Clear();
                    character.LastNetworkUpdateID = serverPos.ID;
                    character.isSynced            = true;
                    return;
                }

                int localPosIndex = character.MemLocalState.FindIndex(m => m.ID == serverPos.ID);
                if (localPosIndex > -1)
                {
                    CharacterStateInfo localPos = character.MemLocalState[localPosIndex];

                    //the entity we're interacting with doesn't match the server's
                    if (localPos.SelectedCharacter != serverPos.SelectedCharacter)
                    {
                        if (serverPos.SelectedCharacter == null || serverPos.SelectedCharacter.Removed)
                        {
                            character.DeselectCharacter();
                        }
                        else if (serverPos.SelectedCharacter != null)
                        {
                            character.SelectCharacter(serverPos.SelectedCharacter);
                        }
                    }
                    if (localPos.SelectedItem != serverPos.SelectedItem)
                    {
                        if (serverPos.SelectedItem == null || serverPos.SelectedItem.Removed)
                        {
                            character.SelectedConstruction = null;
                        }
                        else if (serverPos.SelectedItem != null)
                        {
                            if (character.SelectedConstruction != serverPos.SelectedItem)
                            {
                                serverPos.SelectedItem.TryInteract(character, true, true);
                            }
                            character.SelectedConstruction = serverPos.SelectedItem;
                        }
                    }

                    if (localPos.Animation != serverPos.Animation)
                    {
                        if (serverPos.Animation == AnimController.Animation.CPR)
                        {
                            character.AnimController.Anim = AnimController.Animation.CPR;
                        }
                        else if (character.AnimController.Anim == AnimController.Animation.CPR)
                        {
                            character.AnimController.Anim = AnimController.Animation.None;
                        }
                    }

                    Hull serverHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(serverPos.Position), character.CurrentHull, serverPos.Position.Y < lowestSubPos);
                    Hull clientHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(localPos.Position), serverHull, localPos.Position.Y < lowestSubPos);

                    if (serverHull != null && clientHull != null && serverHull.Submarine != clientHull.Submarine)
                    {
                        //hull subs don't match => teleport the camera to the other sub
                        character.Submarine   = serverHull.Submarine;
                        character.CurrentHull = currentHull = serverHull;
                        SetPosition(serverPos.Position);
                        character.MemLocalState.Clear();
                    }
                    else
                    {
                        Vector2 positionError = serverPos.Position - localPos.Position;
                        float   rotationError = serverPos.Rotation.HasValue && localPos.Rotation.HasValue ?
                                                serverPos.Rotation.Value - localPos.Rotation.Value :
                                                0.0f;

                        for (int i = localPosIndex; i < character.MemLocalState.Count; i++)
                        {
                            Hull pointHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(character.MemLocalState[i].Position), clientHull, character.MemLocalState[i].Position.Y < lowestSubPos);
                            if (pointHull != clientHull && ((pointHull == null) || (clientHull == null) || (pointHull.Submarine == clientHull.Submarine)))
                            {
                                break;
                            }
                            character.MemLocalState[i].Translate(positionError, rotationError);
                        }

                        float errorMagnitude = positionError.Length();
                        if (errorMagnitude > 0.01f)
                        {
                            Collider.TargetPosition = Collider.SimPosition + positionError;
                            Collider.TargetRotation = Collider.Rotation + rotationError;
                            Collider.MoveToTargetPosition(lerp: true);
                            if (errorMagnitude > 0.5f)
                            {
                                character.MemLocalState.Clear();
                                foreach (Limb limb in Limbs)
                                {
                                    limb.body.TargetPosition = limb.body.SimPosition + positionError;
                                    limb.body.MoveToTargetPosition(lerp: true);
                                }
                            }
                        }
                    }
                }

                if (character.MemLocalState.Count > 120)
                {
                    character.MemLocalState.RemoveRange(0, character.MemLocalState.Count - 120);
                }
                character.MemState.Clear();
            }
        }
        public virtual void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
        {
            if (GameMain.Server != null)
            {
                return;
            }

            switch (type)
            {
            case ServerNetObject.ENTITY_POSITION:
                bool facingRight = AnimController.Dir > 0.0f;

                lastRecvPositionUpdateTime = (float)NetTime.Now;

                AnimController.Frozen = false;
                Enabled = true;

                UInt16 networkUpdateID = 0;
                if (msg.ReadBoolean())
                {
                    networkUpdateID = msg.ReadUInt16();
                }
                else
                {
                    bool aimInput = msg.ReadBoolean();
                    keys[(int)InputType.Aim].Held = aimInput;
                    keys[(int)InputType.Aim].SetState(false, aimInput);

                    bool useInput = msg.ReadBoolean();
                    keys[(int)InputType.Use].Held = useInput;
                    keys[(int)InputType.Use].SetState(false, useInput);

                    if (AnimController is HumanoidAnimController)
                    {
                        bool crouching = msg.ReadBoolean();
                        keys[(int)InputType.Crouch].Held = crouching;
                        keys[(int)InputType.Crouch].SetState(false, crouching);
                        AnimController.GrabLimb = (LimbType)msg.ReadByte();
                    }

                    bool hasAttackLimb = msg.ReadBoolean();
                    if (hasAttackLimb)
                    {
                        bool attackInput = msg.ReadBoolean();
                        keys[(int)InputType.Attack].Held = attackInput;
                        keys[(int)InputType.Attack].SetState(false, attackInput);
                    }

                    if (aimInput)
                    {
                        double aimAngle = ((double)msg.ReadUInt16() / 65535.0) * 2.0 * Math.PI;
                        cursorPosition = (ViewTarget == null ? AnimController.AimSourcePos : ViewTarget.Position)
                                         + new Vector2((float)Math.Cos(aimAngle), (float)Math.Sin(aimAngle)) * 60.0f;

                        TransformCursorPos();
                    }
                    bool ragdollInput = msg.ReadBoolean();
                    keys[(int)InputType.Ragdoll].Held = ragdollInput;
                    keys[(int)InputType.Ragdoll].SetState(false, ragdollInput);

                    facingRight = msg.ReadBoolean();
                }

                bool   entitySelected = msg.ReadBoolean();
                Entity selectedEntity = null;

                AnimController.Animation animation = AnimController.Animation.None;
                if (entitySelected)
                {
                    ushort entityID = msg.ReadUInt16();
                    selectedEntity = FindEntityByID(entityID);
                    if (selectedEntity is Character)
                    {
                        bool doingCpr = msg.ReadBoolean();
                        if (doingCpr && SelectedCharacter != null)
                        {
                            animation = AnimController.Animation.CPR;
                        }
                    }
                }

                Vector2 pos = new Vector2(
                    msg.ReadFloat(),
                    msg.ReadFloat());

                float rotation = msg.ReadFloat();

                ReadStatus(msg);

                msg.ReadPadBits();

                int index = 0;
                if (GameMain.NetworkMember.Character == this)
                {
                    var posInfo = new CharacterStateInfo(pos, rotation, networkUpdateID, facingRight ? Direction.Right : Direction.Left, selectedEntity, animation);
                    while (index < memState.Count && NetIdUtils.IdMoreRecent(posInfo.ID, memState[index].ID))
                    {
                        index++;
                    }

                    memState.Insert(index, posInfo);
                }
                else
                {
                    var posInfo = new CharacterStateInfo(pos, rotation, sendingTime, facingRight ? Direction.Right : Direction.Left, selectedEntity, animation);
                    while (index < memState.Count && posInfo.Timestamp > memState[index].Timestamp)
                    {
                        index++;
                    }

                    memState.Insert(index, posInfo);
                }

                break;

            case ServerNetObject.ENTITY_EVENT:

                int eventType = msg.ReadRangedInteger(0, 2);
                switch (eventType)
                {
                case 0:
                    inventory.ClientRead(type, msg, sendingTime);
                    break;

                case 1:
                    byte ownerID = msg.ReadByte();
                    ResetNetState();
                    if (ownerID == GameMain.Client.ID)
                    {
                        if (controlled != null)
                        {
                            LastNetworkUpdateID = controlled.LastNetworkUpdateID;
                        }

                        controlled                       = this;
                        IsRemotePlayer                   = false;
                        GameMain.Client.Character        = this;
                        GameMain.LightManager.LosEnabled = true;
                    }
                    else if (controlled == this)
                    {
                        controlled     = null;
                        IsRemotePlayer = ownerID > 0;
                    }
                    break;

                case 2:
                    ReadStatus(msg);
                    break;
                }
                msg.ReadPadBits();
                break;
            }
        }
        partial void UpdateNetInput()
        {
            if (GameMain.Client != null)
            {
                if (this != Controlled)
                {
                    if (GameMain.Client.EndCinematic != null &&
                        GameMain.Client.EndCinematic.Running) // Freezes the characters during the ending cinematic
                    {
                        AnimController.Frozen = true;
                        memState.Clear();
                        return;
                    }

                    //freeze AI characters if more than x seconds have passed since last update from the server
                    if (lastRecvPositionUpdateTime < Lidgren.Network.NetTime.Now - NetConfig.FreezeCharacterIfPositionDataMissingDelay)
                    {
                        AnimController.Frozen = true;
                        memState.Clear();
                        //hide after y seconds
                        if (lastRecvPositionUpdateTime < Lidgren.Network.NetTime.Now - NetConfig.DisableCharacterIfPositionDataMissingDelay)
                        {
                            Enabled = false;
                            return;
                        }
                    }
                }
                else
                {
                    var posInfo = new CharacterStateInfo(
                        SimPosition,
                        AnimController.Collider.Rotation,
                        LastNetworkUpdateID,
                        AnimController.TargetDir,
                        SelectedCharacter,
                        SelectedConstruction,
                        AnimController.Anim);

                    memLocalState.Add(posInfo);

                    InputNetFlags newInput = InputNetFlags.None;
                    if (IsKeyDown(InputType.Left))
                    {
                        newInput |= InputNetFlags.Left;
                    }
                    if (IsKeyDown(InputType.Right))
                    {
                        newInput |= InputNetFlags.Right;
                    }
                    if (IsKeyDown(InputType.Up))
                    {
                        newInput |= InputNetFlags.Up;
                    }
                    if (IsKeyDown(InputType.Down))
                    {
                        newInput |= InputNetFlags.Down;
                    }
                    if (IsKeyDown(InputType.Run))
                    {
                        newInput |= InputNetFlags.Run;
                    }
                    if (IsKeyDown(InputType.Crouch))
                    {
                        newInput |= InputNetFlags.Crouch;
                    }
                    if (IsKeyHit(InputType.Select))
                    {
                        newInput |= InputNetFlags.Select;                             //TODO: clean up the way this input is registered
                    }
                    if (IsKeyHit(InputType.Deselect))
                    {
                        newInput |= InputNetFlags.Deselect;
                    }
                    if (IsKeyHit(InputType.Health))
                    {
                        newInput |= InputNetFlags.Health;
                    }
                    if (IsKeyHit(InputType.Grab))
                    {
                        newInput |= InputNetFlags.Grab;
                    }
                    if (IsKeyDown(InputType.Use))
                    {
                        newInput |= InputNetFlags.Use;
                    }
                    if (IsKeyDown(InputType.Aim))
                    {
                        newInput |= InputNetFlags.Aim;
                    }
                    if (IsKeyDown(InputType.Shoot))
                    {
                        newInput |= InputNetFlags.Shoot;
                    }
                    if (IsKeyDown(InputType.Attack))
                    {
                        newInput |= InputNetFlags.Attack;
                    }
                    if (IsKeyDown(InputType.Ragdoll))
                    {
                        newInput |= InputNetFlags.Ragdoll;
                    }

                    if (AnimController.TargetDir == Direction.Left)
                    {
                        newInput |= InputNetFlags.FacingLeft;
                    }

                    Vector2 relativeCursorPos = cursorPosition - AimRefPosition;
                    relativeCursorPos.Normalize();
                    UInt16 intAngle = (UInt16)(65535.0 * Math.Atan2(relativeCursorPos.Y, relativeCursorPos.X) / (2.0 * Math.PI));

                    NetInputMem newMem = new NetInputMem
                    {
                        states = newInput,
                        intAim = intAngle
                    };

                    if (FocusedCharacter != null &&
                        FocusedCharacter.CampaignInteractionType != CampaignMode.InteractionType.None &&
                        newMem.states.HasFlag(InputNetFlags.Use))
                    {
                        newMem.interact = FocusedCharacter.ID;
                    }
                    else if (newMem.states.HasFlag(InputNetFlags.Use) && (FocusedCharacter?.IsPet ?? false))
                    {
                        newMem.interact = FocusedCharacter.ID;
                    }
                    else if (focusedItem != null && !CharacterInventory.DraggingItemToWorld &&
                             !newMem.states.HasFlag(InputNetFlags.Grab) && !newMem.states.HasFlag(InputNetFlags.Health))
                    {
                        newMem.interact = focusedItem.ID;
                    }
                    else if (FocusedCharacter != null)
                    {
                        newMem.interact = FocusedCharacter.ID;
                    }

                    memInput.Insert(0, newMem);
                    LastNetworkUpdateID++;
                    if (memInput.Count > 60)
                    {
                        memInput.RemoveRange(60, memInput.Count - 60);
                    }
                }
            }
            else //this == Character.Controlled && GameMain.Client == null
            {
                AnimController.Frozen = false;
            }
        }
        public virtual void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
        {
            switch (type)
            {
            case ServerNetObject.ENTITY_POSITION:
                bool facingRight = AnimController.Dir > 0.0f;

                lastRecvPositionUpdateTime = (float)Lidgren.Network.NetTime.Now;

                AnimController.Frozen = false;
                Enabled = true;

                UInt16 networkUpdateID = 0;
                if (msg.ReadBoolean())
                {
                    networkUpdateID = msg.ReadUInt16();
                }
                else
                {
                    bool aimInput = msg.ReadBoolean();
                    keys[(int)InputType.Aim].Held = aimInput;
                    keys[(int)InputType.Aim].SetState(false, aimInput);

                    bool shootInput = msg.ReadBoolean();
                    keys[(int)InputType.Shoot].Held = shootInput;
                    keys[(int)InputType.Shoot].SetState(false, shootInput);

                    bool useInput = msg.ReadBoolean();
                    keys[(int)InputType.Use].Held = useInput;
                    keys[(int)InputType.Use].SetState(false, useInput);

                    if (AnimController is HumanoidAnimController)
                    {
                        bool crouching = msg.ReadBoolean();
                        keys[(int)InputType.Crouch].Held = crouching;
                        keys[(int)InputType.Crouch].SetState(false, crouching);
                    }

                    bool attackInput = msg.ReadBoolean();
                    keys[(int)InputType.Attack].Held = attackInput;
                    keys[(int)InputType.Attack].SetState(false, attackInput);

                    double aimAngle = msg.ReadUInt16() / 65535.0 * 2.0 * Math.PI;
                    cursorPosition = AimRefPosition + new Vector2((float)Math.Cos(aimAngle), (float)Math.Sin(aimAngle)) * 500.0f;
                    TransformCursorPos();

                    bool ragdollInput = msg.ReadBoolean();
                    keys[(int)InputType.Ragdoll].Held = ragdollInput;
                    keys[(int)InputType.Ragdoll].SetState(false, ragdollInput);

                    facingRight = msg.ReadBoolean();
                }

                bool      entitySelected    = msg.ReadBoolean();
                Character selectedCharacter = null;
                Item      selectedItem      = null;

                AnimController.Animation animation = AnimController.Animation.None;
                if (entitySelected)
                {
                    ushort characterID = msg.ReadUInt16();
                    ushort itemID      = msg.ReadUInt16();
                    selectedCharacter = FindEntityByID(characterID) as Character;
                    selectedItem      = FindEntityByID(itemID) as Item;
                    if (characterID != NullEntityID)
                    {
                        bool doingCpr = msg.ReadBoolean();
                        if (doingCpr && SelectedCharacter != null)
                        {
                            animation = AnimController.Animation.CPR;
                        }
                    }
                }

                Vector2 pos = new Vector2(
                    msg.ReadSingle(),
                    msg.ReadSingle());
                float   MaxVel         = NetConfig.MaxPhysicsBodyVelocity;
                Vector2 linearVelocity = new Vector2(
                    msg.ReadRangedSingle(-MaxVel, MaxVel, 12),
                    msg.ReadRangedSingle(-MaxVel, MaxVel, 12));
                linearVelocity = NetConfig.Quantize(linearVelocity, -MaxVel, MaxVel, 12);

                bool  fixedRotation   = msg.ReadBoolean();
                float?rotation        = null;
                float?angularVelocity = null;
                if (!fixedRotation)
                {
                    rotation = msg.ReadSingle();
                    float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity;
                    angularVelocity = msg.ReadRangedSingle(-MaxAngularVel, MaxAngularVel, 8);
                    angularVelocity = NetConfig.Quantize(angularVelocity.Value, -MaxAngularVel, MaxAngularVel, 8);
                }

                bool readStatus = msg.ReadBoolean();
                if (readStatus)
                {
                    ReadStatus(msg);
                    (AIController as EnemyAIController)?.PetBehavior?.ClientRead(msg);
                }

                msg.ReadPadBits();

                int index = 0;
                if (GameMain.Client.Character == this && CanMove)
                {
                    var posInfo = new CharacterStateInfo(
                        pos, rotation,
                        networkUpdateID,
                        facingRight ? Direction.Right : Direction.Left,
                        selectedCharacter, selectedItem, animation);

                    while (index < memState.Count && NetIdUtils.IdMoreRecent(posInfo.ID, memState[index].ID))
                    {
                        index++;
                    }
                    memState.Insert(index, posInfo);
                }
                else
                {
                    var posInfo = new CharacterStateInfo(
                        pos, rotation,
                        linearVelocity, angularVelocity,
                        sendingTime, facingRight ? Direction.Right : Direction.Left,
                        selectedCharacter, selectedItem, animation);

                    while (index < memState.Count && posInfo.Timestamp > memState[index].Timestamp)
                    {
                        index++;
                    }
                    memState.Insert(index, posInfo);
                }

                break;

            case ServerNetObject.ENTITY_EVENT:

                int eventType = msg.ReadRangedInteger(0, 5);
                switch (eventType)
                {
                case 0:         //NetEntityEvent.Type.InventoryState
                    if (Inventory == null)
                    {
                        string errorMsg = "Received an inventory update message for an entity with no inventory (" + Name + ", removed: " + Removed + ")";
                        DebugConsole.ThrowError(errorMsg);
                        GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ClientRead:NoInventory" + ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);

                        //read anyway to prevent messing up reading the rest of the message
                        UInt16 lastEventID = msg.ReadUInt16();
                        byte   itemCount   = msg.ReadByte();
                        for (int i = 0; i < itemCount; i++)
                        {
                            msg.ReadUInt16();
                        }
                    }
                    else
                    {
                        Inventory.ClientRead(type, msg, sendingTime);
                    }
                    break;

                case 1:         //NetEntityEvent.Type.Control
                    byte ownerID = msg.ReadByte();
                    ResetNetState();
                    if (ownerID == GameMain.Client.ID)
                    {
                        if (controlled != null)
                        {
                            LastNetworkUpdateID = controlled.LastNetworkUpdateID;
                        }

                        if (!IsDead)
                        {
                            Controlled = this;
                        }
                        IsRemotePlayer                   = false;
                        GameMain.Client.HasSpawned       = true;
                        GameMain.Client.Character        = this;
                        GameMain.LightManager.LosEnabled = true;
                    }
                    else
                    {
                        if (controlled == this)
                        {
                            Controlled     = null;
                            IsRemotePlayer = ownerID > 0;
                        }
                    }
                    break;

                case 2:         //NetEntityEvent.Type.Status
                    ReadStatus(msg);
                    break;

                case 3:         //NetEntityEvent.Type.UpdateSkills
                    int skillCount = msg.ReadByte();
                    for (int i = 0; i < skillCount; i++)
                    {
                        string skillIdentifier = msg.ReadString();
                        float  skillLevel      = msg.ReadSingle();
                        info?.SetSkillLevel(skillIdentifier, skillLevel, WorldPosition + Vector2.UnitY * 150.0f);
                    }
                    break;

                case 4:         //NetEntityEvent.Type.ExecuteAttack
                    int    attackLimbIndex = msg.ReadByte();
                    UInt16 targetEntityID  = msg.ReadUInt16();
                    int    targetLimbIndex = msg.ReadByte();

                    //255 = entity already removed, no need to do anything
                    if (attackLimbIndex == 255 || Removed)
                    {
                        break;
                    }

                    if (attackLimbIndex >= AnimController.Limbs.Length)
                    {
                        DebugConsole.ThrowError($"Received invalid ExecuteAttack message. Limb index out of bounds (character: {Name}, limb index: {attackLimbIndex}, limb count: {AnimController.Limbs.Length})");
                        break;
                    }
                    Limb attackLimb = AnimController.Limbs[attackLimbIndex];
                    Limb targetLimb = null;
                    if (!(FindEntityByID(targetEntityID) is IDamageable targetEntity))
                    {
                        DebugConsole.ThrowError($"Received invalid ExecuteAttack message. Target entity not found (ID {targetEntityID})");
                        break;
                    }
                    if (targetEntity is Character targetCharacter)
                    {
                        if (targetLimbIndex >= targetCharacter.AnimController.Limbs.Length)
                        {
                            DebugConsole.ThrowError($"Received invalid ExecuteAttack message. Target limb index out of bounds (target character: {targetCharacter.Name}, limb index: {targetLimbIndex}, limb count: {targetCharacter.AnimController.Limbs.Length})");
                            break;
                        }
                        targetLimb = targetCharacter.AnimController.Limbs[targetLimbIndex];
                    }
                    if (attackLimb?.attack != null)
                    {
                        attackLimb.ExecuteAttack(targetEntity, targetLimb, out _);
                    }
                    break;

                case 5:         //NetEntityEvent.Type.AssignCampaignInteraction
                    byte campaignInteractionType = msg.ReadByte();
                    (GameMain.GameSession?.GameMode as CampaignMode)?.AssignNPCMenuInteraction(this, (CampaignMode.InteractionType)campaignInteractionType);
                    break;
                }
                msg.ReadPadBits();
                break;
            }
        }
Esempio n. 5
0
        private void UpdateNetInput()
        {
            if (this != Character.Controlled)
            {
                if (GameMain.Client != null)
                {
                    //freeze AI characters if more than 1 seconds have passed since last update from the server
                    if (lastRecvPositionUpdateTime < NetTime.Now - 1.0f)
                    {
                        AnimController.Frozen = true;
                        memState.Clear();
                        //hide after 2 seconds
                        if (lastRecvPositionUpdateTime < NetTime.Now - 2.0f)
                        {
                            Enabled = false;
                            return;
                        }
                    }
                }
                else if (GameMain.Server != null && (!(this is AICharacter) || IsRemotePlayer))
                {
                    if (!AllowInput)
                    {
                        AnimController.Frozen = false;
                        if (memInput.Count > 0)
                        {
                            prevDequeuedInput = dequeuedInput;
                            dequeuedInput     = memInput[memInput.Count - 1].states;
                            memInput.RemoveAt(memInput.Count - 1);
                        }
                    }
                    else if (memInput.Count == 0)
                    {
                        AnimController.Frozen = true;
                    }
                    else
                    {
                        AnimController.Frozen = false;
                        prevDequeuedInput     = dequeuedInput;

                        LastProcessedID = memInput[memInput.Count - 1].networkUpdateID;
                        dequeuedInput   = memInput[memInput.Count - 1].states;

                        double aimAngle = ((double)memInput[memInput.Count - 1].intAim / 65535.0) * 2.0 * Math.PI;
                        cursorPosition = (ViewTarget == null ? AnimController.AimSourcePos : ViewTarget.Position)
                                         + new Vector2((float)Math.Cos(aimAngle), (float)Math.Sin(aimAngle)) * 60.0f;

                        //reset focus when attempting to use/select something
                        if (memInput[memInput.Count - 1].states.HasFlag(InputNetFlags.Use) ||
                            memInput[memInput.Count - 1].states.HasFlag(InputNetFlags.Select))
                        {
                            focusedItem      = null;
                            focusedCharacter = null;
                        }
                        var closestEntity = FindEntityByID(memInput[memInput.Count - 1].interact);
                        if (closestEntity is Item)
                        {
                            if (CanInteractWith((Item)closestEntity))
                            {
                                focusedItem      = (Item)closestEntity;
                                focusedCharacter = null;
                            }
                        }
                        else if (closestEntity is Character)
                        {
                            if (CanInteractWith((Character)closestEntity))
                            {
                                focusedCharacter = (Character)closestEntity;
                                focusedItem      = null;
                            }
                        }

                        memInput.RemoveAt(memInput.Count - 1);

                        TransformCursorPos();

                        if ((dequeuedInput == InputNetFlags.None || dequeuedInput == InputNetFlags.FacingLeft) && Math.Abs(AnimController.Collider.LinearVelocity.X) < 0.005f && Math.Abs(AnimController.Collider.LinearVelocity.Y) < 0.2f)
                        {
                            while (memInput.Count > 5 && memInput[memInput.Count - 1].states == dequeuedInput)
                            {
                                //remove inputs where the player is not moving at all
                                //helps the server catch up, shouldn't affect final position
                                LastProcessedID = memInput[memInput.Count - 1].networkUpdateID;
                                memInput.RemoveAt(memInput.Count - 1);
                            }
                        }
                    }
                }
            }
            else if (GameMain.Client != null)
            {
                var posInfo = new CharacterStateInfo(
                    SimPosition,
                    AnimController.Collider.Rotation,
                    LastNetworkUpdateID,
                    AnimController.TargetDir,
                    SelectedCharacter == null ? (Entity)selectedConstruction : (Entity)SelectedCharacter,
                    AnimController.Anim);

                memLocalState.Add(posInfo);

                InputNetFlags newInput = InputNetFlags.None;
                if (IsKeyDown(InputType.Left))
                {
                    newInput |= InputNetFlags.Left;
                }
                if (IsKeyDown(InputType.Right))
                {
                    newInput |= InputNetFlags.Right;
                }
                if (IsKeyDown(InputType.Up))
                {
                    newInput |= InputNetFlags.Up;
                }
                if (IsKeyDown(InputType.Down))
                {
                    newInput |= InputNetFlags.Down;
                }
                if (IsKeyDown(InputType.Run))
                {
                    newInput |= InputNetFlags.Run;
                }
                if (IsKeyDown(InputType.Crouch))
                {
                    newInput |= InputNetFlags.Crouch;
                }
                if (IsKeyHit(InputType.Select))
                {
                    newInput |= InputNetFlags.Select;                                 //TODO: clean up the way this input is registered
                }
                if (IsKeyDown(InputType.Use))
                {
                    newInput |= InputNetFlags.Use;
                }
                if (IsKeyDown(InputType.Aim))
                {
                    newInput |= InputNetFlags.Aim;
                }
                if (IsKeyDown(InputType.Attack))
                {
                    newInput |= InputNetFlags.Attack;
                }
                if (IsKeyDown(InputType.Ragdoll))
                {
                    newInput |= InputNetFlags.Ragdoll;
                }

                if (AnimController.TargetDir == Direction.Left)
                {
                    newInput |= InputNetFlags.FacingLeft;
                }

                Vector2 relativeCursorPos = cursorPosition - (ViewTarget == null ? AnimController.AimSourcePos : ViewTarget.Position);
                relativeCursorPos.Normalize();
                UInt16 intAngle = (UInt16)(65535.0 * Math.Atan2(relativeCursorPos.Y, relativeCursorPos.X) / (2.0 * Math.PI));

                NetInputMem newMem = new NetInputMem();
                newMem.states = newInput;
                newMem.intAim = intAngle;
                if (focusedItem != null)
                {
                    newMem.interact = focusedItem.ID;
                }
                else if (focusedCharacter != null)
                {
                    newMem.interact = focusedCharacter.ID;
                }

                memInput.Insert(0, newMem);
                LastNetworkUpdateID++;
                if (memInput.Count > 60)
                {
                    memInput.RemoveRange(60, memInput.Count - 60);
                }
            }
            else //this == Character.Controlled && GameMain.Client == null
            {
                AnimController.Frozen = false;
            }

            if (networkUpdateSent)
            {
                foreach (Key key in keys)
                {
                    key.DequeueHit();
                    key.DequeueHeld();
                }

                networkUpdateSent = false;
            }
        }
        public virtual void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
        {
            switch (type)
            {
            case ServerNetObject.ENTITY_POSITION:
                bool facingRight = AnimController.Dir > 0.0f;

                lastRecvPositionUpdateTime = (float)Lidgren.Network.NetTime.Now;

                AnimController.Frozen = false;
                Enabled = true;

                UInt16 networkUpdateID = 0;
                if (msg.ReadBoolean())
                {
                    networkUpdateID = msg.ReadUInt16();
                }
                else
                {
                    bool aimInput = msg.ReadBoolean();
                    keys[(int)InputType.Aim].Held = aimInput;
                    keys[(int)InputType.Aim].SetState(false, aimInput);

                    bool shootInput = msg.ReadBoolean();
                    keys[(int)InputType.Shoot].Held = shootInput;
                    keys[(int)InputType.Shoot].SetState(false, shootInput);

                    bool useInput = msg.ReadBoolean();
                    keys[(int)InputType.Use].Held = useInput;
                    keys[(int)InputType.Use].SetState(false, useInput);

                    if (AnimController is HumanoidAnimController)
                    {
                        bool crouching = msg.ReadBoolean();
                        keys[(int)InputType.Crouch].Held = crouching;
                        keys[(int)InputType.Crouch].SetState(false, crouching);
                    }

                    bool attackInput = msg.ReadBoolean();
                    keys[(int)InputType.Attack].Held = attackInput;
                    keys[(int)InputType.Attack].SetState(false, attackInput);

                    double aimAngle = msg.ReadUInt16() / 65535.0 * 2.0 * Math.PI;
                    cursorPosition = AimRefPosition + new Vector2((float)Math.Cos(aimAngle), (float)Math.Sin(aimAngle)) * 500.0f;
                    TransformCursorPos();

                    bool ragdollInput = msg.ReadBoolean();
                    keys[(int)InputType.Ragdoll].Held = ragdollInput;
                    keys[(int)InputType.Ragdoll].SetState(false, ragdollInput);

                    facingRight = msg.ReadBoolean();
                }

                bool      entitySelected    = msg.ReadBoolean();
                Character selectedCharacter = null;
                Item      selectedItem      = null;

                AnimController.Animation animation = AnimController.Animation.None;
                if (entitySelected)
                {
                    ushort characterID = msg.ReadUInt16();
                    ushort itemID      = msg.ReadUInt16();
                    selectedCharacter = FindEntityByID(characterID) as Character;
                    selectedItem      = FindEntityByID(itemID) as Item;
                    if (characterID != NullEntityID)
                    {
                        bool doingCpr = msg.ReadBoolean();
                        if (doingCpr && SelectedCharacter != null)
                        {
                            animation = AnimController.Animation.CPR;
                        }
                    }
                }

                Vector2 pos = new Vector2(
                    msg.ReadSingle(),
                    msg.ReadSingle());
                float   MaxVel         = NetConfig.MaxPhysicsBodyVelocity;
                Vector2 linearVelocity = new Vector2(
                    msg.ReadRangedSingle(-MaxVel, MaxVel, 12),
                    msg.ReadRangedSingle(-MaxVel, MaxVel, 12));
                linearVelocity = NetConfig.Quantize(linearVelocity, -MaxVel, MaxVel, 12);

                bool  fixedRotation   = msg.ReadBoolean();
                float?rotation        = null;
                float?angularVelocity = null;
                if (!fixedRotation)
                {
                    rotation = msg.ReadSingle();
                    float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity;
                    angularVelocity = msg.ReadRangedSingle(-MaxAngularVel, MaxAngularVel, 8);
                    angularVelocity = NetConfig.Quantize(angularVelocity.Value, -MaxAngularVel, MaxAngularVel, 8);
                }

                bool readStatus = msg.ReadBoolean();
                if (readStatus)
                {
                    ReadStatus(msg);
                }

                msg.ReadPadBits();

                int index = 0;
                if (GameMain.Client.Character == this && AllowInput)
                {
                    var posInfo = new CharacterStateInfo(
                        pos, rotation,
                        networkUpdateID,
                        facingRight ? Direction.Right : Direction.Left,
                        selectedCharacter, selectedItem, animation);

                    while (index < memState.Count && NetIdUtils.IdMoreRecent(posInfo.ID, memState[index].ID))
                    {
                        index++;
                    }
                    memState.Insert(index, posInfo);
                }
                else
                {
                    var posInfo = new CharacterStateInfo(
                        pos, rotation,
                        linearVelocity, angularVelocity,
                        sendingTime, facingRight ? Direction.Right : Direction.Left,
                        selectedCharacter, selectedItem, animation);

                    while (index < memState.Count && posInfo.Timestamp > memState[index].Timestamp)
                    {
                        index++;
                    }
                    memState.Insert(index, posInfo);
                }

                break;

            case ServerNetObject.ENTITY_EVENT:

                int eventType = msg.ReadRangedInteger(0, 3);
                switch (eventType)
                {
                case 0:
                    if (Inventory == null)
                    {
                        string errorMsg = "Received an inventory update message for an entity with no inventory (" + Name + ", removed: " + Removed + ")";
                        DebugConsole.ThrowError(errorMsg);
                        GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ClientRead:NoInventory" + ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);

                        //read anyway to prevent messing up reading the rest of the message
                        byte itemCount = msg.ReadByte();
                        for (int i = 0; i < itemCount; i++)
                        {
                            msg.ReadUInt16();
                        }
                    }
                    else
                    {
                        Inventory.ClientRead(type, msg, sendingTime);
                    }
                    break;

                case 1:
                    byte ownerID = msg.ReadByte();
                    ResetNetState();
                    if (ownerID == GameMain.Client.ID)
                    {
                        if (controlled != null)
                        {
                            LastNetworkUpdateID = controlled.LastNetworkUpdateID;
                        }

                        Controlled                       = this;
                        IsRemotePlayer                   = false;
                        GameMain.Client.HasSpawned       = true;
                        GameMain.Client.Character        = this;
                        GameMain.LightManager.LosEnabled = true;
                    }
                    else
                    {
                        if (controlled == this)
                        {
                            Controlled     = null;
                            IsRemotePlayer = ownerID > 0;
                        }
                    }
                    break;

                case 2:
                    ReadStatus(msg);
                    break;

                case 3:
                    int skillCount = msg.ReadByte();
                    for (int i = 0; i < skillCount; i++)
                    {
                        string skillIdentifier = msg.ReadString();
                        float  skillLevel      = msg.ReadSingle();
                        info?.SetSkillLevel(skillIdentifier, skillLevel, WorldPosition + Vector2.UnitY * 150.0f);
                    }
                    break;
                }
                msg.ReadPadBits();
                break;
            }
        }
Esempio n. 7
0
        public virtual void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
        {
            if (GameMain.Server != null)
            {
                return;
            }

            switch (type)
            {
            case ServerNetObject.ENTITY_POSITION:
                bool facingRight = AnimController.Dir > 0.0f;

                lastRecvPositionUpdateTime = (float)NetTime.Now;

                AnimController.Frozen = false;
                Enabled = true;

                UInt16 networkUpdateID = 0;
                if (msg.ReadBoolean())
                {
                    networkUpdateID = msg.ReadUInt16();
                }
                else
                {
                    bool aimInput = msg.ReadBoolean();
                    keys[(int)InputType.Aim].Held = aimInput;
                    keys[(int)InputType.Aim].SetState(false, aimInput);

                    bool useInput = msg.ReadBoolean();
                    keys[(int)InputType.Use].Held = useInput;
                    keys[(int)InputType.Use].SetState(false, useInput);

                    if (AnimController is HumanoidAnimController)
                    {
                        bool crouching = msg.ReadBoolean();
                        keys[(int)InputType.Crouch].Held = crouching;
                        keys[(int)InputType.Crouch].SetState(false, crouching);
                    }

                    bool hasAttackLimb = msg.ReadBoolean();
                    if (hasAttackLimb)
                    {
                        bool attackInput = msg.ReadBoolean();
                        keys[(int)InputType.Attack].Held = attackInput;
                        keys[(int)InputType.Attack].SetState(false, attackInput);
                    }

                    if (aimInput)
                    {
                        double aimAngle = ((double)msg.ReadUInt16() / 65535.0) * 2.0 * Math.PI;
                        cursorPosition = AimRefPosition + new Vector2((float)Math.Cos(aimAngle), (float)Math.Sin(aimAngle)) * 60.0f;
                        TransformCursorPos();
                    }
                    bool ragdollInput = msg.ReadBoolean();
                    keys[(int)InputType.Ragdoll].Held = ragdollInput;
                    keys[(int)InputType.Ragdoll].SetState(false, ragdollInput);

                    facingRight = msg.ReadBoolean();
                }

                bool   entitySelected = msg.ReadBoolean();
                Entity selectedEntity = null;

                AnimController.Animation animation = AnimController.Animation.None;
                if (entitySelected)
                {
                    ushort entityID = msg.ReadUInt16();
                    selectedEntity = FindEntityByID(entityID);
                    if (selectedEntity is Character)
                    {
                        bool doingCpr = msg.ReadBoolean();
                        if (doingCpr && SelectedCharacter != null)
                        {
                            animation = AnimController.Animation.CPR;
                        }
                    }
                }

                Vector2 pos = new Vector2(
                    msg.ReadFloat(),
                    msg.ReadFloat());

                float rotation = msg.ReadFloat();

                ReadStatus(msg);

                msg.ReadPadBits();

                int index = 0;
                if (GameMain.NetworkMember.Character == this && AllowInput)
                {
                    var posInfo = new CharacterStateInfo(pos, rotation, networkUpdateID, facingRight ? Direction.Right : Direction.Left, selectedEntity, animation);
                    while (index < memState.Count && NetIdUtils.IdMoreRecent(posInfo.ID, memState[index].ID))
                    {
                        index++;
                    }

                    memState.Insert(index, posInfo);
                }
                else
                {
                    var posInfo = new CharacterStateInfo(pos, rotation, sendingTime, facingRight ? Direction.Right : Direction.Left, selectedEntity, animation);
                    while (index < memState.Count && posInfo.Timestamp > memState[index].Timestamp)
                    {
                        index++;
                    }

                    memState.Insert(index, posInfo);
                }

                break;

            case ServerNetObject.ENTITY_EVENT:

                int eventType = msg.ReadRangedInteger(0, 3);
                switch (eventType)
                {
                case 0:
                    if (Inventory == null)
                    {
                        string errorMsg = "Received an inventory update message for an entity with no inventory (" + Name + ")";
                        DebugConsole.ThrowError(errorMsg);
                        GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ClientRead:NoInventory" + ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
                    }
                    else
                    {
                        Inventory.ClientRead(type, msg, sendingTime);
                    }
                    break;

                case 1:
                    byte ownerID = msg.ReadByte();
                    ResetNetState();
                    if (ownerID == GameMain.Client.ID)
                    {
                        if (controlled != null)
                        {
                            LastNetworkUpdateID = controlled.LastNetworkUpdateID;
                        }

                        Controlled                       = this;
                        IsRemotePlayer                   = false;
                        GameMain.Client.HasSpawned       = true;
                        GameMain.Client.Character        = this;
                        GameMain.LightManager.LosEnabled = true;
                    }
                    else if (controlled == this)
                    {
                        Controlled     = null;
                        IsRemotePlayer = ownerID > 0;
                    }
                    break;

                case 2:
                    ReadStatus(msg);
                    break;

                case 3:
                    int skillCount = msg.ReadByte();
                    for (int i = 0; i < skillCount; i++)
                    {
                        string skillIdentifier = msg.ReadString();
                        float  skillLevel      = msg.ReadSingle();
                        info?.SetSkillLevel(skillIdentifier, skillLevel, WorldPosition + Vector2.UnitY * 150.0f);
                    }
                    break;
                }
                msg.ReadPadBits();
                break;
            }
        }
Esempio n. 8
0
        partial void UpdateNetInput()
        {
            if (GameMain.Client != null)
            {
                if (this != Controlled)
                {
                    //freeze AI characters if more than 1 seconds have passed since last update from the server
                    if (lastRecvPositionUpdateTime < NetTime.Now - 1.0f)
                    {
                        AnimController.Frozen = true;
                        memState.Clear();
                        //hide after 2 seconds
                        if (lastRecvPositionUpdateTime < NetTime.Now - 2.0f)
                        {
                            Enabled = false;
                            return;
                        }
                    }
                }
                else
                {
                    var posInfo = new CharacterStateInfo(
                        SimPosition,
                        AnimController.Collider.Rotation,
                        LastNetworkUpdateID,
                        AnimController.TargetDir,
                        SelectedCharacter == null ? (Entity)SelectedConstruction : (Entity)SelectedCharacter,
                        AnimController.Anim);

                    memLocalState.Add(posInfo);

                    InputNetFlags newInput = InputNetFlags.None;
                    if (IsKeyDown(InputType.Left))
                    {
                        newInput |= InputNetFlags.Left;
                    }
                    if (IsKeyDown(InputType.Right))
                    {
                        newInput |= InputNetFlags.Right;
                    }
                    if (IsKeyDown(InputType.Up))
                    {
                        newInput |= InputNetFlags.Up;
                    }
                    if (IsKeyDown(InputType.Down))
                    {
                        newInput |= InputNetFlags.Down;
                    }
                    if (IsKeyDown(InputType.Run))
                    {
                        newInput |= InputNetFlags.Run;
                    }
                    if (IsKeyDown(InputType.Crouch))
                    {
                        newInput |= InputNetFlags.Crouch;
                    }
                    if (IsKeyHit(InputType.Select))
                    {
                        newInput |= InputNetFlags.Select;                             //TODO: clean up the way this input is registered
                    }
                    if (IsKeyHit(InputType.Health))
                    {
                        newInput |= InputNetFlags.Health;
                    }
                    if (IsKeyHit(InputType.Grab))
                    {
                        newInput |= InputNetFlags.Grab;
                    }
                    if (IsKeyDown(InputType.Use))
                    {
                        newInput |= InputNetFlags.Use;
                    }
                    if (IsKeyDown(InputType.Aim))
                    {
                        newInput |= InputNetFlags.Aim;
                    }
                    if (IsKeyDown(InputType.Attack))
                    {
                        newInput |= InputNetFlags.Attack;
                    }
                    if (IsKeyDown(InputType.Ragdoll))
                    {
                        newInput |= InputNetFlags.Ragdoll;
                    }

                    if (AnimController.TargetDir == Direction.Left)
                    {
                        newInput |= InputNetFlags.FacingLeft;
                    }

                    Vector2 relativeCursorPos = cursorPosition - AimRefPosition;
                    relativeCursorPos.Normalize();
                    UInt16 intAngle = (UInt16)(65535.0 * Math.Atan2(relativeCursorPos.Y, relativeCursorPos.X) / (2.0 * Math.PI));

                    NetInputMem newMem = new NetInputMem
                    {
                        states = newInput,
                        intAim = intAngle
                    };
                    if (focusedItem != null && (!newMem.states.HasFlag(InputNetFlags.Grab) && !newMem.states.HasFlag(InputNetFlags.Health)))
                    {
                        newMem.interact = focusedItem.ID;
                    }
                    else if (focusedCharacter != null)
                    {
                        newMem.interact = focusedCharacter.ID;
                    }

                    memInput.Insert(0, newMem);
                    LastNetworkUpdateID++;
                    if (memInput.Count > 60)
                    {
                        memInput.RemoveRange(60, memInput.Count - 60);
                    }
                }
            }
            else //this == Character.Controlled && GameMain.Client == null
            {
                AnimController.Frozen = false;
            }
        }
        public virtual void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
        {
            switch (type)
            {
            case ServerNetObject.ENTITY_POSITION:
                bool facingRight = AnimController.Dir > 0.0f;

                lastRecvPositionUpdateTime = (float)Lidgren.Network.NetTime.Now;

                AnimController.Frozen = false;
                Enabled = true;

                UInt16 networkUpdateID = 0;
                if (msg.ReadBoolean())
                {
                    networkUpdateID = msg.ReadUInt16();
                }
                else
                {
                    bool aimInput = msg.ReadBoolean();
                    keys[(int)InputType.Aim].Held = aimInput;
                    keys[(int)InputType.Aim].SetState(false, aimInput);

                    bool shootInput = msg.ReadBoolean();
                    keys[(int)InputType.Shoot].Held = shootInput;
                    keys[(int)InputType.Shoot].SetState(false, shootInput);

                    bool useInput = msg.ReadBoolean();
                    keys[(int)InputType.Use].Held = useInput;
                    keys[(int)InputType.Use].SetState(false, useInput);

                    if (AnimController is HumanoidAnimController)
                    {
                        bool crouching = msg.ReadBoolean();
                        keys[(int)InputType.Crouch].Held = crouching;
                        keys[(int)InputType.Crouch].SetState(false, crouching);
                    }

                    bool attackInput = msg.ReadBoolean();
                    keys[(int)InputType.Attack].Held = attackInput;
                    keys[(int)InputType.Attack].SetState(false, attackInput);

                    double aimAngle = msg.ReadUInt16() / 65535.0 * 2.0 * Math.PI;
                    cursorPosition = AimRefPosition + new Vector2((float)Math.Cos(aimAngle), (float)Math.Sin(aimAngle)) * 500.0f;
                    TransformCursorPos();

                    bool ragdollInput = msg.ReadBoolean();
                    keys[(int)InputType.Ragdoll].Held = ragdollInput;
                    keys[(int)InputType.Ragdoll].SetState(false, ragdollInput);

                    facingRight = msg.ReadBoolean();
                }

                bool      entitySelected    = msg.ReadBoolean();
                Character selectedCharacter = null;
                Item      selectedItem      = null;

                AnimController.Animation animation = AnimController.Animation.None;
                if (entitySelected)
                {
                    ushort characterID = msg.ReadUInt16();
                    ushort itemID      = msg.ReadUInt16();
                    selectedCharacter = FindEntityByID(characterID) as Character;
                    selectedItem      = FindEntityByID(itemID) as Item;
                    if (characterID != NullEntityID)
                    {
                        bool doingCpr = msg.ReadBoolean();
                        if (doingCpr && SelectedCharacter != null)
                        {
                            animation = AnimController.Animation.CPR;
                        }
                    }
                }

                Vector2 pos = new Vector2(
                    msg.ReadSingle(),
                    msg.ReadSingle());
                float   MaxVel         = NetConfig.MaxPhysicsBodyVelocity;
                Vector2 linearVelocity = new Vector2(
                    msg.ReadRangedSingle(-MaxVel, MaxVel, 12),
                    msg.ReadRangedSingle(-MaxVel, MaxVel, 12));
                linearVelocity = NetConfig.Quantize(linearVelocity, -MaxVel, MaxVel, 12);

                bool  fixedRotation   = msg.ReadBoolean();
                float?rotation        = null;
                float?angularVelocity = null;
                if (!fixedRotation)
                {
                    rotation = msg.ReadSingle();
                    float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity;
                    angularVelocity = msg.ReadRangedSingle(-MaxAngularVel, MaxAngularVel, 8);
                    angularVelocity = NetConfig.Quantize(angularVelocity.Value, -MaxAngularVel, MaxAngularVel, 8);
                }

                bool readStatus = msg.ReadBoolean();
                if (readStatus)
                {
                    ReadStatus(msg);
                    AIController?.ClientRead(msg);
                }

                msg.ReadPadBits();

                int index = 0;
                if (GameMain.Client.Character == this && CanMove)
                {
                    var posInfo = new CharacterStateInfo(
                        pos, rotation,
                        networkUpdateID,
                        facingRight ? Direction.Right : Direction.Left,
                        selectedCharacter, selectedItem, animation);

                    while (index < memState.Count && NetIdUtils.IdMoreRecent(posInfo.ID, memState[index].ID))
                    {
                        index++;
                    }
                    memState.Insert(index, posInfo);
                }
                else
                {
                    var posInfo = new CharacterStateInfo(
                        pos, rotation,
                        linearVelocity, angularVelocity,
                        sendingTime, facingRight ? Direction.Right : Direction.Left,
                        selectedCharacter, selectedItem, animation);

                    while (index < memState.Count && posInfo.Timestamp > memState[index].Timestamp)
                    {
                        index++;
                    }
                    memState.Insert(index, posInfo);
                }

                break;

            case ServerNetObject.ENTITY_EVENT:
                int eventType = msg.ReadRangedInteger(0, 13);
                switch (eventType)
                {
                case 0:         //NetEntityEvent.Type.InventoryState
                    if (Inventory == null)
                    {
                        string errorMsg = "Received an inventory update message for an entity with no inventory (" + Name + ", removed: " + Removed + ")";
                        DebugConsole.ThrowError(errorMsg);
                        GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ClientRead:NoInventory" + ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);

                        //read anyway to prevent messing up reading the rest of the message
                        _ = msg.ReadUInt16();
                        byte inventoryItemCount = msg.ReadByte();
                        for (int i = 0; i < inventoryItemCount; i++)
                        {
                            msg.ReadUInt16();
                        }
                    }
                    else
                    {
                        Inventory.ClientRead(type, msg, sendingTime);
                    }
                    break;

                case 1:         //NetEntityEvent.Type.Control
                    byte ownerID = msg.ReadByte();
                    ResetNetState();
                    if (ownerID == GameMain.Client.ID)
                    {
                        if (controlled != null)
                        {
                            LastNetworkUpdateID = controlled.LastNetworkUpdateID;
                        }

                        if (!IsDead)
                        {
                            Controlled = this;
                        }
                        IsRemotePlayer                          = false;
                        GameMain.Client.HasSpawned              = true;
                        GameMain.Client.Character               = this;
                        GameMain.LightManager.LosEnabled        = true;
                        GameMain.LightManager.LosAlpha          = 1f;
                        GameMain.Client.WaitForNextRoundRespawn = null;
                    }
                    else
                    {
                        if (controlled == this)
                        {
                            Controlled     = null;
                            IsRemotePlayer = ownerID > 0;
                        }
                    }
                    break;

                case 2:         //NetEntityEvent.Type.Status
                    ReadStatus(msg);
                    break;

                case 3:         //NetEntityEvent.Type.UpdateSkills
                    int skillCount = msg.ReadByte();
                    for (int i = 0; i < skillCount; i++)
                    {
                        string skillIdentifier = msg.ReadString();
                        float  skillLevel      = msg.ReadSingle();
                        info?.SetSkillLevel(skillIdentifier, skillLevel);
                    }
                    break;

                case 4:         // NetEntityEvent.Type.SetAttackTarget
                case 5:         //NetEntityEvent.Type.ExecuteAttack
                    int     attackLimbIndex = msg.ReadByte();
                    UInt16  targetEntityID  = msg.ReadUInt16();
                    int     targetLimbIndex = msg.ReadByte();
                    Vector2 targetSimPos    = new Vector2(msg.ReadSingle(), msg.ReadSingle());
                    //255 = entity already removed, no need to do anything
                    if (attackLimbIndex == 255 || Removed)
                    {
                        break;
                    }
                    if (attackLimbIndex >= AnimController.Limbs.Length)
                    {
                        DebugConsole.ThrowError($"Received invalid SetAttack/ExecuteAttack message. Limb index out of bounds (character: {Name}, limb index: {attackLimbIndex}, limb count: {AnimController.Limbs.Length})");
                        break;
                    }
                    Limb attackLimb = AnimController.Limbs[attackLimbIndex];
                    Limb targetLimb = null;
                    if (!(FindEntityByID(targetEntityID) is IDamageable targetEntity))
                    {
                        DebugConsole.ThrowError($"Received invalid SetAttack/ExecuteAttack message. Target entity not found (ID {targetEntityID})");
                        break;
                    }
                    if (targetEntity is Character targetCharacter)
                    {
                        if (targetLimbIndex >= targetCharacter.AnimController.Limbs.Length)
                        {
                            DebugConsole.ThrowError($"Received invalid SetAttack/ExecuteAttack message. Target limb index out of bounds (target character: {targetCharacter.Name}, limb index: {targetLimbIndex}, limb count: {targetCharacter.AnimController.Limbs.Length})");
                            break;
                        }
                        targetLimb = targetCharacter.AnimController.Limbs[targetLimbIndex];
                    }
                    if (attackLimb?.attack != null && Controlled != this)
                    {
                        if (eventType == 4)
                        {
                            SetAttackTarget(attackLimb, targetEntity, targetSimPos);
                            PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3);
                        }
                        else
                        {
                            attackLimb.ExecuteAttack(targetEntity, targetLimb, out _);
                        }
                    }
                    break;

                case 6:         //NetEntityEvent.Type.AssignCampaignInteraction
                    byte campaignInteractionType = msg.ReadByte();
                    bool requireConsciousness    = msg.ReadBoolean();
                    (GameMain.GameSession?.GameMode as CampaignMode)?.AssignNPCMenuInteraction(this, (CampaignMode.InteractionType)campaignInteractionType);
                    RequireConsciousnessForCustomInteract = requireConsciousness;
                    break;

                case 7:         //NetEntityEvent.Type.ObjectiveManagerState
                    // 1 = order, 2 = objective
                    int msgType = msg.ReadRangedInteger(0, 2);
                    if (msgType == 0)
                    {
                        break;
                    }
                    bool validData = msg.ReadBoolean();
                    if (!validData)
                    {
                        break;
                    }
                    if (msgType == 1)
                    {
                        int    orderIndex  = msg.ReadRangedInteger(0, Order.PrefabList.Count);
                        var    orderPrefab = Order.PrefabList[orderIndex];
                        string option      = null;
                        if (orderPrefab.HasOptions)
                        {
                            int optionIndex = msg.ReadRangedInteger(-1, orderPrefab.AllOptions.Length);
                            if (optionIndex > -1)
                            {
                                option = orderPrefab.AllOptions[optionIndex];
                            }
                        }
                        GameMain.GameSession?.CrewManager?.SetOrderHighlight(this, orderPrefab.Identifier, option);
                    }
                    else if (msgType == 2)
                    {
                        string identifier = msg.ReadString();
                        string option     = msg.ReadString();
                        ushort objectiveTargetEntityId = msg.ReadUInt16();
                        var    objectiveTargetEntity   = FindEntityByID(objectiveTargetEntityId);
                        GameMain.GameSession?.CrewManager?.CreateObjectiveIcon(this, identifier, option, objectiveTargetEntity);
                    }
                    break;

                case 8:         //NetEntityEvent.Type.TeamChange
                    byte newTeamId = msg.ReadByte();
                    ChangeTeam((CharacterTeamType)newTeamId);
                    break;

                case 9:         //NetEntityEvent.Type.AddToCrew
                    GameMain.GameSession.CrewManager.AddCharacter(this);
                    CharacterTeamType teamID    = (CharacterTeamType)msg.ReadByte();
                    ushort            itemCount = msg.ReadUInt16();
                    for (int i = 0; i < itemCount; i++)
                    {
                        ushort itemID = msg.ReadUInt16();
                        if (!(Entity.FindEntityByID(itemID) is Item item))
                        {
                            continue;
                        }
                        item.AllowStealing = true;
                        var wifiComponent = item.GetComponent <WifiComponent>();
                        if (wifiComponent != null)
                        {
                            wifiComponent.TeamID = teamID;
                        }
                        var idCard = item.GetComponent <IdCard>();
                        if (idCard != null)
                        {
                            idCard.TeamID = teamID;
                            idCard.SubmarineSpecificID = 0;
                        }
                    }
                    break;

                case 10:         //NetEntityEvent.Type.UpdateExperience
                    int experienceAmount = msg.ReadInt32();
                    info?.SetExperience(experienceAmount);
                    break;

                case 11:         //NetEntityEvent.Type.UpdateTalents:
                    ushort talentCount = msg.ReadUInt16();
                    for (int i = 0; i < talentCount; i++)
                    {
                        bool   addedThisRound   = msg.ReadBoolean();
                        UInt32 talentIdentifier = msg.ReadUInt32();
                        GiveTalent(talentIdentifier, addedThisRound);
                    }
                    break;

                case 12:         //NetEntityEvent.Type.UpdateMoney:
                    int moneyAmount = msg.ReadInt32();
                    SetMoney(moneyAmount);
                    break;

                case 13:         //NetEntityEvent.Type.UpdatePermanentStats:
                    byte      savedStatValueCount = msg.ReadByte();
                    StatTypes statType            = (StatTypes)msg.ReadByte();
                    info?.ClearSavedStatValues(statType);
                    for (int i = 0; i < savedStatValueCount; i++)
                    {
                        string statIdentifier = msg.ReadString();
                        float  statValue      = msg.ReadSingle();
                        bool   removeOnDeath  = msg.ReadBoolean();
                        info?.ChangeSavedStatValue(statType, statValue, statIdentifier, removeOnDeath, setValue: true);
                    }
                    break;
                }
                msg.ReadPadBits();
                break;
            }
        }