node-haxball
Version:
The most powerful and lightweight API that allows you to develop your original Haxball(www.haxball.com) host, client, and standalone applications both on node.js and browser environments and also includes every possible hack and functionality that you can
304 lines (261 loc) • 8.87 kB
JavaScript
module.exports = function (API) {
const {
OperationType,
VariableType,
ConnectionState,
AllowFlags,
Direction,
CollisionFlags,
CameraFollow,
BackgroundType,
GamePlayState,
Callback,
Utils,
Room,
Replay,
Query,
Library,
RoomConfig,
Plugin,
Renderer,
Errors,
Language,
Impl
} = API;
Object.setPrototypeOf(this, Plugin.prototype);
Plugin.call(this, "Rocket Mode", true, {
version: "0.1",
author: "Luks",
description: `Auto rocket mode / Manual rocket mode`,
allowFlags: AllowFlags.JoinRoom | AllowFlags.CreateRoom
});
this.defineVariable({
name: "autoRocketEnabled",
description: "Player will try to auto rocket",
type: VariableType.Boolean,
value: false,
});
this.defineVariable({
name: "rocketKey",
description: "Key to auto rocket yourself",
type: VariableType.Keys,
value: ["KeyC"],
});
this.defineVariable({
name: "checkFPS",
description: "FPS to run distance checks",
type: VariableType.Number,
value: 60,
range: {
min: 5,
step: 5
}
});
this.defineVariable({
name: "wallKickDistance",
description: "How far from wall to kick ball",
type: VariableType.Number,
value: 35,
range: {
min: 0,
step: 5
}
});
this.defineVariable({
name: "kickThreshold",
description: "How close is ball to player to kick",
type: VariableType.Number,
value: 15,
range: {
min: 0,
step: 1
}
});
this.defineVariable({
name: "firstKickInterval",
description: "How much time to pass in MS before kicking for the first time",
type: VariableType.Number,
value: 5,
range: {
min: 0,
step: 5
}
});
this.defineVariable({
name: "secondKickInterval",
description: "How much time to pass in MS before kicking second time",
type: VariableType.Number,
value: 115,
range: {
min: 0,
step: 5
}
});
var that = this;
let fps = that.checkFPS;
let interval = 1000 / fps;
let lastTime = Date.now();
let deltaTime = 0;
let gameLoopInterval = setInterval(gameLoop, interval);
let kicked = false;
function gameLoop() {
const now = Date.now();
deltaTime = now - lastTime;
if (deltaTime >= interval) {
update();
lastTime = now - (deltaTime % interval);
}
}
// Function to calculate the distance from a point to a line segment
function distanceToSegment(point, segA, segB) {
const x = point.x;
const y = point.y;
const x1 = segA.x;
const y1 = segA.y;
const x2 = segB.x;
const y2 = segB.y;
const A = x - x1;
const B = y - y1;
const C = x2 - x1;
const D = y2 - y1;
const dot = A * C + B * D;
const lenSq = C * C + D * D;
const param = lenSq !== 0 ? dot / lenSq : -1;
let xx, yy;
if (param < 0) {
xx = x1;
yy = y1;
} else if (param > 1) {
xx = x2;
yy = y2;
} else {
xx = x1 + param * C;
yy = y1 + param * D;
}
const dx = x - xx;
const dy = y - yy;
return Math.sqrt(dx * dx + dy * dy);
}
// Function to calculate the distance between two points
function distanceBetweenPoints(point1, point2) {
const dx = point1.x - point2.x;
const dy = point1.y - point2.y;
return Math.sqrt(dx * dx + dy * dy);
}
// Function to check if the player can kick the ball
function canPlayerKickBall(playerDisc, ballDisc, kickThreshold) {
const distance = distanceBetweenPoints(playerDisc.pos, ballDisc.pos);
const combinedRadius = playerDisc.radius + ballDisc.radius;
return distance <= combinedRadius + kickThreshold;
}
// Function to calculate the distance from a point to a plane
function distanceToPlane(point, planeNormal, planeDistance) {
// Distance from the point to the plane
return planeNormal.x * point.x + planeNormal.y * point.y - planeDistance;
}
// Function to calculate the distance before the ball collides with the plane
function distanceToPlaneWithBall(ballPos, ballRadius, planeNormal, planeDistance) {
const distanceToPlaneLine = distanceToPlane(ballPos, planeNormal, planeDistance);
const collisionDistance = Math.abs(distanceToPlaneLine) - ballRadius;
return Math.max(collisionDistance, 0);
}
function update() {
if(!that.active) return
if(!that.autoRocketEnabled) return
if(!that.room) return;
const physicsState = that.room.state?.gameState?.physicsState;
if (!physicsState) return;
const ball = physicsState.discs[0];
const ballPosition = ball.pos;
const ballRadius = ball.radius;
const ballCmask = ball.cMask;
const ballCgroup = ball.cGroup;
let closestObject = null;
let minDistance = Infinity;
let collidableObjectCount = 0;
// Check for segments
for (let i = 0; i < physicsState.segments.length; i++) {
const segment = physicsState.segments[i];
const pos1 = segment.v0.pos;
const pos2 = segment.v1.pos;
const segmentCmask = segment.cMask;
const segmentCgroup = segment.cGroup;
const canCollide = (ballCgroup & segmentCmask) !== 0 && (segmentCgroup & ballCmask) !== 0;
if (!canCollide) {
continue;
}
collidableObjectCount++;
// Calculate the distance to the segment
const distance = distanceToSegment(ballPosition, pos1, pos2);
if (distance < minDistance) {
minDistance = distance;
closestObject = { type: 'segment', object: segment };
}
}
// Check for planes
for (let i = 0; i < physicsState.planes.length; i++) {
const plane = physicsState.planes[i];
const planeNormal = plane.normal; // The normal vector of the plane
const planeDistance = plane.dist; // The distance from the origin to the plane
const planeCmask = plane.cMask;
const planeCgroup = plane.cGroup;
const canCollide = (ballCgroup & planeCmask) !== 0 && (planeCgroup & ballCmask) !== 0;
if (!canCollide) {
continue;
}
collidableObjectCount++;
// Calculate the distance to the plane
const distance = distanceToPlaneWithBall(ballPosition, ballRadius, planeNormal, planeDistance);
if (distance < minDistance) {
minDistance = distance;
closestObject = { type: 'plane', object: plane };
}
}
if (closestObject) {
if (minDistance < that.wallKickDistance) {
const playerDisc = that.room.currentPlayer.disc;
if (playerDisc) {
const kickThreshold = that.kickThreshold;
if (canPlayerKickBall(playerDisc, ball, kickThreshold) && !kicked) doubleKick();
}
}
}
}
function retrieveState() {
return Utils.reverseKeyState(that.room.getKeyState());
}
function doubleKick() {
if(kicked) return
//kicked = true;
setTimeout(function () {
let state = retrieveState()
const newState = Utils.keyState(state.dirX, state.dirY, true)
that.room.setKeyState(newState)
}, that.firstKickInterval);
setTimeout(function () {
let state = retrieveState()
const newState = Utils.keyState(state.dirX, state.dirY, true)
that.room.setKeyState(newState)
kicked = false
}, that.secondKickInterval);
}
this.onKeyDown = function(e){
if(!that.active) return
if(that.rocketKey.length == 0) return
if(that.rocketKey.includes(e.code)) doubleKick()
}
this.finalize = () => {
that = null;
clearInterval(gameLoopInterval);
gameLoopInterval = null;
};
this.onVariableValueChange = (addonObject, variableName, oldValue, newValue) => {
if (addonObject == that && variableName == "checkFPS") {
clearInterval(gameLoopInterval);
gameLoopInterval = null;
let fps = newValue;
let interval = 1000 / fps;
gameLoopInterval = setInterval(gameLoop, interval);
}
};
}