示例#1
0
        private void InputLogic(InputBasic2d action, SnapHistory <NentBasic2d, NentStaticBasic2d> h, byte pid, float delta)
        {
            // process the inputs for this action
            h.Shots[h.CurrentIndex].XVel = RMathF.Clamp(action.Horizontal, -1f, 1f) * PlayerSpeed;
            h.Shots[h.CurrentIndex].YVel = RMathF.Clamp(action.Vertical, -1f, 1f) * PlayerSpeed;

            // set our rotation, but only if we're not mid-dash
            if (h.Shots[h.CurrentIndex].Free1 <= 0)
            {
                h.Shots[h.CurrentIndex].Rot = action.Rotation;
            }

            // dash action
            if ((action.Inputs & InputBasic2d.INPUT_A) != 0 &&
                h.Shots[h.CurrentIndex].Free1 == 0)
            {
                // if input A is pressed, dash forward according to rotation
                // we use Free1 to store the dash timer. We can only begin a
                // dash if Free1 is equal to 0 (e.g. dash is over).
                h.Shots[h.CurrentIndex].Free1 = DashTimerMax;

                // we don't need to set XVel/YVel here because this is done
                // in AdvanceLogic
            }

            // finally, do AdvanceLogic over the delta window
            AdvanceLogic(h, delta);
        }
示例#2
0
        public void InputLogic(AdvancerConfigBasic2d config, InputBasic2d action, SnapHistory <NentBasic2d, NentStaticBasic2d> h, ref NentBasic2d snap, byte pid, float delta)
        {
            // process the inputs for this action
            snap.XVel = RMathF.Clamp(action.Horizontal, -1f, 1f) * config.PlayerSpeed;
            snap.YVel = RMathF.Clamp(action.Vertical, -1f, 1f) * config.PlayerSpeed;

            // set our rotation, but only if we're not mid-dash
            if (snap.Free1 <= 0)
            {
                snap.Rot = action.Rotation;
            }

            // dash action
            if ((action.Inputs & InputBasic2d.INPUT_A) != 0 &&
                snap.Free1 == 0)
            {
                // if input A is pressed, dash forward according to rotation
                // we use Free1 to store the dash timer. We can only begin a
                // dash if Free1 is equal to 0 (e.g. dash is over).
                snap.Free1 = config.DashTimerMax;

                // we don't need to set XVel/YVel here because this is done
                // in AdvanceLogic
            }

            // finally, do AdvanceLogic over the delta window
            AdvanceLogic(config, h, ref snap, delta);
        }
示例#3
0
        public void ServerAdvance()
        {
            // handle inputs

            /*for (int i = 0; i < Server.PlayerInfos.Count; i++)
             * {
             *  byte pid = Server.PlayerInfos.Values[i].PlayerId;
             *  if (pid == 0)
             *      continue; // server doesn't have inputs
             *
             *  InputChecker(pid);
             * }*/

            for (int d = 0; d < NentDatas.Length; d++)
            {
                ReArrayIdPool <SnapHistory <NentBasic2d, NentStaticBasic2d> > data = NentDatas[d];
                for (int i = 0; i < data.Count; i++)
                {
                    SnapHistory <NentBasic2d, NentStaticBasic2d> h = data.Values[i];

                    if (h.PrevFlag != SnapHistory <NentBasic2d, NentStaticBasic2d> .FlagGold)
                    {
                        continue; // if the prevflag is not gold,
                    }
                    // the entity does not exist at this timestamp
                    // so we don't bother simulating it yet

                    // now we advance the snapshot forward to the current point in
                    // time, and then save it
                    if (h.StaticData.Id2 == NENT_PLAYEROBJ)
                    {
                        // store the player entities so we know which entity belongs
                        // to which player
                        PlayerEntityIds[h.StaticData.Id1] = (byte)h.EntityId;

                        // if we have no inputs, do regular advancement
                        h.Shots[h.CurrentIndex] = h.Shots[h.PrevIndex];
                        if (!InputChecker(h, h.StaticData.Id1))
                        {
                            AdvanceLogic(h, NetSnapper.TickMSTarget);
                        }
                    }
                    else
                    {
                        // if this were client, we'd check the flag of the next.
                        // as it is, just create the next from the current
                        h.Shots[h.CurrentIndex] = h.Shots[h.PrevIndex];
                        AdvanceLogic(h, NetSnapper.TickMSTarget);
                    }

                    Nents.ServerSaveSimIntoCurrent(h);
                }
            }
        }
示例#4
0
        private void AdvanceLogic(SnapHistory <NentBasic2d, NentStaticBasic2d> h, float delta)
        {
            if (h.StaticData.Id2 == NENT_PLAYEROBJ)
            {
                // some special considerations for the playerobject
                if (h.Shots[h.CurrentIndex].Free1 > 0)
                {
                    // if Free1 is over 0, we're in the middle of a dash
                    // this means we're moving quickly in the direction
                    // of our rotation

                    // we're expecting the client to provide rotation in
                    // radians, for the record
                    h.Shots[h.CurrentIndex].XVel = DashSpeed * RMathF.Cos(h.Shots[h.CurrentIndex].Rot);
                    h.Shots[h.CurrentIndex].YVel = DashSpeed * RMathF.Sin(h.Shots[h.CurrentIndex].Rot);

                    // reduce the dash timer
                    h.Shots[h.CurrentIndex].Free1 -= delta;
                    // if we cross 0, set up the cooldown timer
                    if (h.Shots[h.CurrentIndex].Free1 <= 0)
                    {
                        h.Shots[h.CurrentIndex].Free1 = -DashCooldownMax;
                    }

                    // note: something we're not really handling here
                    // is that on the tick that the dash ends, we may
                    // get a few ms of "extra" dash b/c if it has say
                    // 10ms remaining and we have 16ms in the tick,
                    // you're dashing for the full 16ms even though
                    // you should only have 10ms of dash.

                    // you could probably fix this issue if precision
                    // matters in your use case. but for this demo
                    // I think it's outside the scope
                }
                else if (h.Shots[h.CurrentIndex].Free1 < 0)
                {
                    // if we're negative, we're on cooldown
                    // count back up to 0, when we get to 0
                    // the dash is available again.
                    h.Shots[h.CurrentIndex].Free1 += delta;
                    if (h.Shots[h.CurrentIndex].Free1 >= 0)
                    {
                        h.Shots[h.CurrentIndex].Free1 = 0;
                    }
                }
            }

            h.Shots[h.CurrentIndex].X += h.Shots[h.CurrentIndex].XVel * delta;
            h.Shots[h.CurrentIndex].Y += h.Shots[h.CurrentIndex].YVel * delta;
        }
示例#5
0
        private void InterpolateLogic(SnapHistory <NentBasic2d, NentStaticBasic2d> h, float delta)
        {
            // interpolate from Current forward delta ms
            float tickpercent    = delta / NetSnapper.TickMSTarget;
            float invtickpercent = 1.0f - tickpercent;

            // we don't adjust IDs at all

            // pos/vel is simple, just blend
            h.Shots[h.CurrentIndex].X    = (h.Shots[h.CurrentIndex].X * invtickpercent) + (h.Shots[h.NextIndex].X * tickpercent);
            h.Shots[h.CurrentIndex].Y    = (h.Shots[h.CurrentIndex].Y * invtickpercent) + (h.Shots[h.NextIndex].Y * tickpercent);
            h.Shots[h.CurrentIndex].XVel = (h.Shots[h.CurrentIndex].XVel * invtickpercent) + (h.Shots[h.NextIndex].XVel * tickpercent);
            h.Shots[h.CurrentIndex].YVel = (h.Shots[h.CurrentIndex].YVel * invtickpercent) + (h.Shots[h.NextIndex].YVel * tickpercent);

            // it makes sense to blend Free1 as well since we just
            // use it as a timer, but in other cases this might not
            // be appropriate
            h.Shots[h.CurrentIndex].Free1 = (h.Shots[h.CurrentIndex].Free1 * invtickpercent) + (h.Shots[h.NextIndex].Free1 * tickpercent);

            // rotation is more complicated to blend
            h.Shots[h.CurrentIndex].Rot = RMathF.AngleBlend(h.Shots[h.CurrentIndex].Rot, h.Shots[h.NextIndex].Rot, tickpercent);
        }
示例#6
0
        public void BlendLogic(SnapHistory <NentBasic2d, NentStaticBasic2d> h,
                               ref NentBasic2d shot, NentBasic2d blendTarget, float factor)
        {
            // interpolate from Current forward delta ms
            float invfactor = 1.0f - factor;

            // we don't adjust IDs at all

            // pos/vel is simple, just blend
            shot.X    = (shot.X * invfactor) + (blendTarget.X * factor);
            shot.Y    = (shot.Y * invfactor) + (blendTarget.Y * factor);
            shot.XVel = (shot.XVel * invfactor) + (blendTarget.XVel * factor);
            shot.YVel = (shot.YVel * invfactor) + (blendTarget.YVel * factor);

            // it makes sense to blend Free1 as well since we just
            // use it as a timer, but in other cases this might not
            // be appropriate
            shot.Free1 = (shot.Free1 * invfactor) + (blendTarget.Free1 * factor);

            // rotation is more complicated to blend
            shot.Rot = RMathF.AngleBlend(shot.Rot, blendTarget.Rot, factor);
        }
示例#7
0
        private void InterpolateSnapLogic(SnapHistory <NentBasic2d, NentStaticBasic2d> h)
        {
            // interpolates from Prev to Next to create Current
            h.Shots[h.CurrentIndex] = h.Shots[h.PrevIndex];

            // always inherit ids from previous
            //h.Prev.Id1 = h.Prev.Id1;
            //h.Prev.Id2 = h.Prev.Id2;

            // pos/vel is easy, just average them
            h.Shots[h.CurrentIndex].X    = (h.Shots[h.NextIndex].X + h.Shots[h.CurrentIndex].X) / 2f;
            h.Shots[h.CurrentIndex].Y    = (h.Shots[h.NextIndex].Y + h.Shots[h.CurrentIndex].Y) / 2f;
            h.Shots[h.CurrentIndex].XVel = (h.Shots[h.NextIndex].XVel + h.Shots[h.CurrentIndex].XVel) / 2f;
            h.Shots[h.CurrentIndex].YVel = (h.Shots[h.NextIndex].YVel + h.Shots[h.CurrentIndex].YVel) / 2f;

            // in our case, we use Free1 as a timer, so it makes
            // sense to average this as well. May not be the case
            // if Free1 is used for a different kind of value
            h.Shots[h.CurrentIndex].Free1 = (h.Shots[h.NextIndex].Free1 + h.Shots[h.CurrentIndex].Free1) / 2f;

            // rotation is more complicated to find the midpoint
            h.Shots[h.CurrentIndex].Rot = RMathF.AngleMidpoint(h.Shots[h.CurrentIndex].Rot, h.Shots[h.NextIndex].Rot);
        }
示例#8
0
        private void RepeatTestLogic(int clients, int amount, int runs)
        {
            SnapBasic2dEnvironment senv = new SnapBasic2dEnvironment(clients);

            senv.Activate();
            senv.FastTick();

            // have the server ghost some new entities
            List <byte> eids = new List <byte>();

            for (int i = 0; i < amount; i++)
            {
                senv.AddEntityFirst(new NentBasic2d()
                {
                    X    = 10f + i,
                    Y    = 5f + i,
                    XVel = 3f
                },
                                    new NentStaticBasic2d()
                {
                    Id1 = 0,
                    Id2 = 0
                }, out byte eid);

                eids.Add(eid);
            }

            ushort timeAdded = senv.NetSnappers[0].CurrentTime;

            senv.FastTick();

            ushort nextTimeChecked = 0;
            ushort timeChecked     = senv.NetSnappers[0].CurrentTime;
            Snapper <NentBasic2d, NentStaticBasic2d, PackerBasic2d, PackInfoBasic2d> checkNent =
                senv.Nents[0];

            for (int r = 0; r < runs; r++)
            {
                nextTimeChecked = senv.NetSnappers[0].CurrentTime;
                senv.FastTick(); // allow extra time to be sure the clients
                                 // have these timestamps

                // verify that the client has the entities
                for (int c = 1; c < senv.NetSnappers.Count; c++)
                {
                    NetExecutorSnapper ns = senv.NetSnappers[c];
                    Snapper <NentBasic2d, NentStaticBasic2d, PackerBasic2d, PackInfoBasic2d> nent = senv.Nents[c];

                    for (int i = 0; i < eids.Count; i++)
                    {
                        byte eid = eids[i];

                        // pull the entity from the source so we can compare
                        SnapHistory <NentBasic2d, NentStaticBasic2d> checkH = checkNent.GetFirstEntity(eid);
                        Assert.AreNotEqual(null, checkH);

                        // does this ent exist?
                        SnapHistory <NentBasic2d, NentStaticBasic2d> h = nent.GetFirstEntity(eid);
                        Assert.AreNotEqual(null, h);

                        // do we have snapshots for the latest timestamp?
                        int checkIndex = h.FindIndex(timeChecked);
                        Assert.IsTrue(checkIndex != -1);
                        int checkAgainstIndex = checkH.FindIndex(timeChecked);
                        Assert.IsTrue(checkAgainstIndex != -1);

                        // are the values in it correct?
                        Assert.AreEqual(checkH.Shots[checkAgainstIndex].X, h.Shots[checkIndex].X);
                        Assert.AreEqual(5f + i, h.Shots[checkIndex].Y);
                        Assert.AreEqual(3f, h.Shots[checkIndex].XVel);
                    }
                }

                timeChecked = nextTimeChecked;
            }
        }
示例#9
0
        // Advance Methods
        public void ClientAdvance(int times, float tickms)
        {
            for (int t = 0; t < times; t++)
            {
                for (int d = 0; d < NentDatas.Length; d++)
                {
                    ReArrayIdPool <SnapHistory <NentBasic2d, NentStaticBasic2d> > data = NentDatas[d];
                    for (int i = 0; i < data.Count; i++)
                    {
                        SnapHistory <NentBasic2d, NentStaticBasic2d> h = data.Values[i];

                        if (h.PrevFlag == SnapHistory <NentBasic2d, NentStaticBasic2d> .FlagEmpty ||
                            h.PrevFlag == SnapHistory <NentBasic2d, NentStaticBasic2d> .FlagDeghosted)
                        {
                            continue; // if the prev flag is empty, this entity does
                        }
                        // not exist yet

                        // check to see if this is one of our player objects,
                        // if so, we resimulate it according to our inputs
                        bool skipAdvancement = false;
                        if (h.StaticData.Id2 == NENT_PLAYEROBJ)
                        {
                            // store the player entities so we know which entity belongs
                            // to which player
                            PlayerEntityIds[h.StaticData.Id1] = (byte)h.EntityId;

                            // we'll only have inputs if it is our player object
                            // since we don't receive inputs for other players
                            if (h.StaticData.Id1 == Server.OurPlayerId)
                            {
                                // if we have no inputs, skip advancement will be false
                                // and we'll do regular simulation on this object (see below)
                                h.Shots[h.CurrentIndex] = h.Shots[h.PrevIndex];
                                skipAdvancement         = InputChecker(h, (byte)h.EntityId);
                            }
                        }

                        if (!skipAdvancement)
                        {
                            if (h.CurrentFlag == SnapHistory <NentBasic2d, NentStaticBasic2d> .FlagGold)
                            {
                                continue; // if we already have a gold from the server,
                            }
                            // there is no purpose in resimulating.

                            // we know the prev snapshot must be gold or silver, or we
                            // wouldn't be here right now. So now we just need to see
                            // what the next snapshot is.

                            // if we have a gold snapshot ahead of us, we can just
                            // interpolate between Prev and Next, which is less costly.
                            // if not, we need to Advance from Prev to Current

                            // check Next
                            if (h.NextFlag != SnapHistory <NentBasic2d, NentStaticBasic2d> .FlagGold)
                            {
                                // we don't have gold ahead of us, so we must
                                // simulate the current snapshot from previous
                                h.Shots[h.CurrentIndex] = h.Shots[h.PrevIndex];
                                AdvanceLogic(h, NetSnapper.TickMSTarget);
                            }
                            else
                            {
                                // we can interpolate since the next flag is gold
                                InterpolateSnapLogic(h);
                            }
                        }

                        // if we have gold we would have already `continue;`d by now
                        // so it's safe to save as silver here
                        Nents.ClientSaveSimIntoCurrent(h);
                    }
                }

                // load next
                NetSnapper.AdvanceSimulateTimestamp();
            }

            // now advance to TickMS
            for (int d = 0; d < NentDatas.Length; d++)
            {
                ReArrayIdPool <SnapHistory <NentBasic2d, NentStaticBasic2d> > data = NentDatas[d];
                for (int i = 0; i < data.Count; i++)
                {
                    SnapHistory <NentBasic2d, NentStaticBasic2d> h = data.Values[i];

                    if (h.PrevFlag == SnapHistory <NentBasic2d, NentStaticBasic2d> .FlagEmpty ||
                        h.PrevFlag == SnapHistory <NentBasic2d, NentStaticBasic2d> .FlagDeghosted)
                    {
                        continue; // if the prev flag is empty, this entity does
                    }
                    // not exist yet

                    if (h.CurrentFlag == SnapHistory <NentBasic2d, NentStaticBasic2d> .FlagGold)
                    {
                        // we have a gold flag, so we can interpolate
                        InterpolateLogic(h, tickms);
                    }
                    else
                    {
                        // otherwise we must simulate
                        AdvanceLogic(h, tickms);
                    }
                }
            }

            // now Current is populated with the latest snapshots
            // adjusted forward to meet the current tickMS
            // this can be rendered by the client
        }
示例#10
0
        // returns false if we have no inputs
        private bool InputChecker(SnapHistory <NentBasic2d, NentStaticBasic2d> h, byte pid)
        {
            InputBasic2d[] actions = Input.GetPlayerInputs(pid,
                                                           out int index, out int count,
                                                           out ushort[] timestamps, out float[] tickms);

            if (count == 0)
            {
                return(false);
            }

            ushort simstamp = NetSnapper.SimulateTimestamp;

            // timestamps are ordered from oldest --> latest
            // loop through seeking our current timestamp
            float        lastms        = 0;
            InputBasic2d lastAction    = new InputBasic2d();
            bool         hasLastAction = false;

            for (int i = 0; i < count; i++)
            {
                // if timestamps[index] is later than our timestamp, stop
                // if timestamps[index] is earlier than our timestamp, keep going
                // if timestamps[index] is equal to our timestamp, process it
                if (timestamps[index] == simstamp)
                {
                    // process it
                    if (hasLastAction)
                    {
                        InputLogic(lastAction, h, pid, tickms[index] - lastms);
                    }
                    hasLastAction = true;
                    lastms        = tickms[index];
                    lastAction    = actions[index];
                }
                else if (simstamp >= ushort.MaxValue / 2)
                {
                    if (timestamps[index] > simstamp ||
                        timestamps[index] < simstamp - (ushort.MaxValue / 2))
                    {
                        // later
                        break;
                    }
                }
                else
                {
                    if (timestamps[index] > simstamp &&
                        timestamps[index] < simstamp + (ushort.MaxValue / 2))
                    {
                        // later
                        break;
                    }
                }

                index++;
                if (index >= actions.Length)
                {
                    index -= actions.Length;
                }
            }

            // process the last action, now that we're done
            if (hasLastAction)
            {
                // we use the tickrate here bbecause this action stretches until the end
                // of the tick, since there are no subsequent actions in this tick
                InputLogic(lastAction, h, pid, NetSnapper.TickMSTarget - lastms);
            }

            return(true);
        }