mirror of
https://github.com/logsol/chuck.js.git
synced 2026-05-11 18:47:35 +00:00
Replace cheat-detection teleport with server reconciliation
The old PUNKBUSTER check compared client-reported position to server position and snapped the player back when latency made them diverge, which felt like getting teleported under any real network conditions. Replaces that with proper client-side prediction + reconciliation: client tags each input with a sequence number and keeps an input buffer; server tracks the last processed sequence and reports its authoritative position via a per-user inputAck alongside each worldUpdate. The client only corrects when the actual disagreement exceeds what the unacked input time can explain — so steady-state movement runs purely on local physics, and only genuine unexpected events (collisions, being hit) trigger a smooth blend toward the server state. Includes adaptive threshold scaling so high-latency sessions don't false-positive corrections during normal running.
This commit is contained in:
parent
e6089687ed
commit
71e4b4e847
9 changed files with 162 additions and 149 deletions
|
|
@ -1,24 +1,24 @@
|
|||
define([
|
||||
"Game/Core/Control/PlayerController",
|
||||
"Lib/Utilities/NotificationCenter",
|
||||
"Lib/Utilities/Protocol/Parser",
|
||||
"Game/Config/Settings"
|
||||
"Lib/Utilities/Protocol/Parser"
|
||||
],
|
||||
|
||||
function(Parent, nc, Parser, Settings) {
|
||||
|
||||
function(Parent, nc, Parser) {
|
||||
|
||||
"use strict";
|
||||
|
||||
function PlayerController(player) {
|
||||
|
||||
Parent.call(this, player);
|
||||
this._lastProcessedSeq = 0;
|
||||
}
|
||||
|
||||
PlayerController.prototype = Object.create(Parent.prototype);
|
||||
|
||||
/*
|
||||
* retrieves move (and other) commands from client and executes them at the server
|
||||
*/
|
||||
/*
|
||||
* retrieves move (and other) commands from client and executes them at the server
|
||||
*/
|
||||
PlayerController.prototype.applyCommand = function(options) {
|
||||
// FIXME: remove this function and use ProtocolHelper.applyCommand() instead
|
||||
// Don't forget to change the function names to on...
|
||||
|
|
@ -28,9 +28,16 @@ function(Parent, nc, Parser, Settings) {
|
|||
} else {
|
||||
message = options;
|
||||
}
|
||||
|
||||
|
||||
for (var command in message) {
|
||||
this[command].call(this, message[command]);
|
||||
var commandOptions = message[command];
|
||||
|
||||
// Track sequence number from client input commands
|
||||
if (commandOptions && typeof commandOptions === 'object' && commandOptions._seq !== undefined) {
|
||||
this._lastProcessedSeq = commandOptions._seq;
|
||||
}
|
||||
|
||||
this[command].call(this, commandOptions);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -45,36 +52,6 @@ function(Parent, nc, Parser, Settings) {
|
|||
this.player.suicide();
|
||||
};
|
||||
|
||||
PlayerController.prototype.mePositionStateOverride = function(update) {
|
||||
|
||||
if(!this.player.isSpawned()) {
|
||||
// if someone still falls but is dead on the server already
|
||||
return;
|
||||
}
|
||||
|
||||
var difference = {
|
||||
x: Math.abs(update.p.x - this.player.doll.body.GetPosition().x),
|
||||
y: Math.abs(update.p.y - this.player.doll.body.GetPosition().y)
|
||||
};
|
||||
|
||||
if(difference.x < Settings.PUNKBUSTER_DIFFERENCE_METERS &&
|
||||
difference.y < Settings.PUNKBUSTER_DIFFERENCE_METERS) {
|
||||
this.player.doll.updatePositionState(update);
|
||||
} else {
|
||||
// HARD UPDATE FOR SELF
|
||||
console.log(this.player.user.options.nickname + " is cheating.");
|
||||
|
||||
var body = this.player.doll.body;
|
||||
|
||||
var options = {
|
||||
p: body.GetPosition(),
|
||||
lv: body.GetLinearVelocity()
|
||||
};
|
||||
|
||||
nc.trigger(nc.ns.channel.to.client.user.gameCommand.send + this.player.id, "positionStateReset", options);
|
||||
}
|
||||
};
|
||||
|
||||
return PlayerController;
|
||||
|
||||
});
|
||||
|
|
@ -113,13 +113,30 @@ function (Parent, PhysicsEngine, Settings, requestAnimFrame, nc, Box2D, Player,
|
|||
};
|
||||
|
||||
GameController.prototype.updateWorld = function () {
|
||||
|
||||
|
||||
var update = this.getWorldUpdateObject(false);
|
||||
|
||||
if(Object.getOwnPropertyNames(update).length > 0) {
|
||||
nc.trigger(nc.ns.channel.to.client.gameCommand.broadcast, "worldUpdate", update);
|
||||
}
|
||||
|
||||
// Send per-user input acknowledgments for server reconciliation
|
||||
for (var id in this.players) {
|
||||
var player = this.players[id];
|
||||
if (player.isSpawned() && player.playerController._lastProcessedSeq > 0) {
|
||||
var body = player.doll.body;
|
||||
nc.trigger(
|
||||
nc.ns.channel.to.client.user.gameCommand.send + id,
|
||||
"inputAck",
|
||||
{
|
||||
seq: player.playerController._lastProcessedSeq,
|
||||
p: { x: body.GetPosition().x, y: body.GetPosition().y },
|
||||
lv: { x: body.GetLinearVelocity().x, y: body.GetLinearVelocity().y }
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.worldUpdateTimeout = setTimeout(this.updateWorld.bind(this), Settings.NETWORK_UPDATE_INTERVAL);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,10 @@ define([
|
|||
"Game/Core/GameObjects/Doll",
|
||||
"Game/Channel/GameObjects/Item",
|
||||
"Lib/Vendor/Box2D",
|
||||
"Lib/Utilities/NotificationCenter",
|
||||
"Lib/Utilities/Assert"
|
||||
"Lib/Utilities/NotificationCenter"
|
||||
],
|
||||
|
||||
function (Parent, Item, Box2D, nc, Assert) {
|
||||
|
||||
function (Parent, Item, Box2D, nc) {
|
||||
|
||||
"use strict";
|
||||
|
||||
|
|
@ -93,16 +92,6 @@ function (Parent, Item, Box2D, nc, Assert) {
|
|||
}
|
||||
};
|
||||
|
||||
Doll.prototype.updatePositionState = function(update) {
|
||||
if(!this.isAnotherPlayerNearby()) {
|
||||
Assert.number(update.p.x, update.p.y);
|
||||
Assert.number(update.lv.x, update.lv.y);
|
||||
this.body.SetAwake(true);
|
||||
this.body.SetPosition(update.p);
|
||||
this.body.SetLinearVelocity(update.lv);
|
||||
}
|
||||
};
|
||||
|
||||
Doll.prototype.getUpdateData = function(getSleeping) {
|
||||
|
||||
var updateData = Parent.prototype.getUpdateData.call(this, getSleeping);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue