public void PackageAction(PlayerAction action) { PackagedActions.Enqueue(action); RecentActions.Add(action); IEntity temp = (IEntity)this.Clone(); this.Clone(NoCorrections); ExecuteAction(action.Type); NoCorrections = (IEntity)Match.CurrentState.GetEntity(ID).Clone(); this.Clone(temp); ExecuteAction(action.Type); }
static void DoAction(MatchState match, ICharacter champion, PlayerAction action) { switch (action.Type) { case PlayerActionType.MoveLeft: match.Move(champion.ID, HorizontalDirection.Left); champion.FacingLeft = true; break; case PlayerActionType.MoveRight: match.Move(champion.ID, HorizontalDirection.Right); champion.FacingLeft = false; break; case PlayerActionType.Jump: match.Jump(champion.ID); break; // Ignore the actions that are not related to movement case PlayerActionType.Idle: case PlayerActionType.Spell1: case PlayerActionType.Spell2: case PlayerActionType.Spell3: case PlayerActionType.Spell4: break; default: Debug.Fail("Invalid player action."); ILogger.Log("Invalid player action passed in a package: " + action.Type.ToString(), LogPriority.Warning); break; } }
void OnActionPackage(NetIncomingMessage message) { Debug.Assert(Clients.ContainsKey(message.SenderConnection)); try { while (message.Position < message.LengthBits) { ulong id = message.ReadUInt64(); float time = message.ReadFloat(); PlayerActionType type = (PlayerActionType)message.ReadByte(); Vec2 position = new Vec2(message.ReadFloat(), message.ReadFloat()); Vec2 target = ActionTypeHelper.IsSpell(type) ? new Vec2(message.ReadFloat(), message.ReadFloat()) : null; PlayerAction action = new PlayerAction(id, type, time, position, target); Clients[message.SenderConnection].ActionsPackage.Add(action); } } catch (Exception e) { ILogger.Log("Action package badly formatted: " + e.ToString(), LogPriority.Error); } }
static Vec2 ValidateActionPosition(IEntity player, PlayerAction action) { Vec2 position = action.Position; // If the position provided by the client seems legit, we take it. Otherwise, we ignore it // and log it (might be a hacker). if (Vec2.DistanceSquared(player.Position, position) >= MAX_TOLERATED_OFF_DISTANCE * MAX_TOLERATED_OFF_DISTANCE) { position = player.Position; } return position; }
static double ValidateActionTime(PlayerAction action, double currentTime) { double time = action.Time; // action time is too old? might be a hacker/extreme lag. Log it, keep it but clamp it double oldestAcceptedTime = currentTime - HISTORY_MAX_TIME_KEPT.TotalSeconds; if (action.Time < oldestAcceptedTime) { time = oldestAcceptedTime; ILogger.Log(String.Format("Action {0} seems a bit late. Accepting it, but might be a hacker/extreme lag. Given time: {1}, server time: {2}", action.ID, action.Time, currentTime), LogPriority.Warning); } // action time seems too recent? might be a hacker/time error. Log it, keep it but clamp it if (action.Time > currentTime + MAX_TIME_AHEAD) { time = currentTime; ILogger.Log(String.Format("Action {0} seems a bit too new. Accepting it, but might be a hacker/time error. Given time: {1}, server time: {2}", action.ID, action.Time, currentTime), LogPriority.Warning); } return time; }
void HandleMovementAction(ulong id, PlayerAction action) { double now = Server.Instance.GetTime().TotalSeconds; double time = action.Time; // Make sure we're not using weird times time = ValidateActionTime(action, now); // Go to the given action time if we have a state history if (!StateHistory.IsEmpty()) { // Go to the game snapshot before the action that we're simulating KeyValuePair<double, MatchState> stateBefore = StateHistory.GetSnapshotBefore(time); KeyValuePair<double, MatchState> state = new KeyValuePair<double, MatchState>( stateBefore.Key, stateBefore.Value.Clone() as MatchState); // Simulate from our previous snapshot to our current action to be up-to-date if (state.Value.ContainsEntity(id)) { var player = (ICharacter)state.Value.GetEntity(id); float deltaT = (float)(time - state.Key); if (deltaT > 0f) { // if we have something to simulate... state.Value.ApplyPhysicsUpdate(id, deltaT); } // Make sure we're not using hacked positions player.Position = ValidateActionPosition(player, action); // Actually execute the action on our currently simulated state DoAction(state.Value, player, action); // Store our intermediate state at the action time. state = StateHistory.AddSnapshot(state.Value, time); } // Resimulate all the states up to now so that they are affected // by the player's action. var nextState = StateHistory.GetNext(state); while (nextState.HasValue) { // get how much time we have to simulate for next state float timeUntilNextState = (float)(nextState.Value.Key - time); Debug.Assert(timeUntilNextState >= 0f); // simulate the next state if (nextState.Value.Value.ContainsEntity(id)) { nextState.Value.Value.GetEntity(id).Clone(state.Value.GetEntity(id)); if (timeUntilNextState > 0f) { nextState.Value.Value.ApplyPhysicsUpdate(id, timeUntilNextState); } } // switch to the next state state = nextState.Value; time = state.Key; nextState = StateHistory.GetNext(state); } // Modify our current game state to apply our simulation modifications. var last = StateHistory.GetLast(); if (Match.CurrentState.ContainsEntity(id) && last.Value.ContainsEntity(id)) { Match.CurrentState.GetEntity(id).Clone(last.Value.GetEntity(id)); } } }
const double RADIANS_BETWEEN_PROJECTILES = Math.PI / 36.0; // ~5 degrees void CastChampionSpell(ICharacter champ, PlayerAction action) { Debug.Assert(action.Target != null); // aim in the direction of the spell champ.FacingLeft = action.Target.X < champ.Position.X + champ.CollisionWidth / 2f; SpellTypes type = ChampionTypesHelper.GetSpellFromAction(champ.Type, action.Type); int projectiles = SpellsHelper.Info(type).Projectiles; Vec2 spawn = champ.GetHandsPosition(); double angle = 0.0; if (action.Target != null) { Vec2 dir = action.Target - spawn; angle = Math.Atan2(dir.Y, dir.X); // current angle double completeArc = RADIANS_BETWEEN_PROJECTILES * (projectiles - 1); // complete arc that we'll cover angle -= completeArc / 2f; // start from the lowest angle } for (int i = 0; i < projectiles; ++i) { Vec2 dir = Vec2.Zero; if (action.Target != null) { double current = angle + i * RADIANS_BETWEEN_PROJECTILES; dir = new Vec2((float)Math.Cos(current), (float)Math.Sin(current)); } LinearSpell spell = new LinearSpell( IDGenerator.GenerateID(), champ.Team, spawn, spawn + dir, type, champ); CastSpell(spell, action.Target); } }
void HandleAction(ServerClient client, PlayerAction action) { if (ActionTypeHelper.IsSpell(action.Type)) { var spell = ChampionTypesHelper.GetSpellFromAction(client.Champion.Type, action.Type); if (client.ChampStats.Alive && !client.ChampStats.IsOnCooldown(spell)) { // we're not dead and the spell is not on cooldown CastChampionSpell(client.Champion, action); client.ChampStats.UsedSpell(spell); } } else if (action.Type != PlayerActionType.Idle) { ILogger.Log("Unknown player action type: " + action.Type); } }