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
368 lines (329 loc) • 13.3 kB
JavaScript
module.exports = function (API) {
const { OperationType, VariableType, ConnectionState, AllowFlags, Direction, CollisionFlags, CameraFollow, BackgroundType, GamePlayState, BanEntryType, Callback, Utils, Room, Replay, Query, Library, RoomConfig, Plugin, Renderer, Errors, Language, EventFactory, Impl } = API;
Object.setPrototypeOf(this, Plugin.prototype);
Plugin.call(this, "joystick", true, { // "joystick" is plugin's name, "true" means "activated just after initialization". Every plugin should have a unique name.
version: "0.1",
author: "Bobboteck & abc & JerryOldson",
description: `This plugins allows player interact with the joystick.`,
allowFlags: AllowFlags.JoinRoom | AllowFlags.CreateRoom // We allow this plugin to be activated on both JoinRoom and CreateRoom.
});
this.defineVariable({
name: "opacity",
type: VariableType.Number,
value: 0.6,
range: {
min: 0,
max: 1,
step: 0.01
},
description: `Opacity of the gui that is shown on top of the canvas`
});
// This file is part of the JoyStick Project (https://github.com/bobboteck/JoyStick).
// Copyright (c) 2015 Roberto D'Amico (Bobboteck).
// ( Modified by abc )
var JoyStick = (function(container, parameters){
parameters = parameters || {};
var title = (typeof parameters.title === "undefined" ? "joystick" : parameters.title),
width = (typeof parameters.width === "undefined" ? 0 : parameters.width),
height = (typeof parameters.height === "undefined" ? 0 : parameters.height),
internalFillColor = (typeof parameters.internalFillColor === "undefined" ? "#00AA00" : parameters.internalFillColor),
internalLineWidth = (typeof parameters.internalLineWidth === "undefined" ? 2 : parameters.internalLineWidth),
internalStrokeColor = (typeof parameters.internalStrokeColor === "undefined" ? "#003300" : parameters.internalStrokeColor),
externalLineWidth = (typeof parameters.externalLineWidth === "undefined" ? 2 : parameters.externalLineWidth),
externalStrokeColor = (typeof parameters.externalStrokeColor === "undefined" ? "#008000" : parameters.externalStrokeColor),
autoReturnToCenter = (typeof parameters.autoReturnToCenter === "undefined" ? true : parameters.autoReturnToCenter);
// Create Canvas element and add it in the Container object
var objContainer = document.getElementById(container);
var canvas = document.createElement("canvas");
canvas.id = title;
if(width === 0) { width = objContainer.clientWidth; }
if(height === 0) { height = objContainer.clientHeight; }
canvas.width = width;
canvas.height = height;
objContainer.appendChild(canvas);
var context = canvas.getContext("2d");
var pressed = 0; // Bool - 1=Yes - 0=No
var circumference = 2 * Math.PI;
var internalRadius = (canvas.width-((canvas.width/2)+10))/2;
var maxMoveStick = internalRadius + 5;
var externalRadius = internalRadius + 30;
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
var directionHorizontalLimitPos = canvas.width / 10;
var directionHorizontalLimitNeg = directionHorizontalLimitPos * -1;
var directionVerticalLimitPos = canvas.height / 10;
var directionVerticalLimitNeg = directionVerticalLimitPos * -1;
// Used to save current position of stick
var movedX=centerX;
var movedY=centerY;
// Check if the device support the touch or not
if("ontouchstart" in document.documentElement){
canvas.addEventListener("touchstart", onTouchStart, false);
canvas.addEventListener("touchmove", onTouchMove, false);
canvas.addEventListener("touchend", onTouchEnd, false);
}
else{
canvas.addEventListener("mousedown", onMouseDown, false);
canvas.addEventListener("mousemove", onMouseMove, false);
canvas.addEventListener("mouseup", onMouseUp, false);
}
// Draw the object
drawExternal();
drawInternal();
/******************************************************
* Private methods
*****************************************************/
// Draw the external circle used as reference position
function drawExternal(){
context.beginPath();
context.arc(centerX, centerY, externalRadius, 0, circumference, false);
context.lineWidth = externalLineWidth;
context.strokeStyle = externalStrokeColor;
context.stroke();
}
// Draw the internal stick in the current position the user have moved it
function drawInternal(){
context.beginPath();
if(movedX<internalRadius) { movedX=maxMoveStick; }
if((movedX+internalRadius) > canvas.width) { movedX = canvas.width-(maxMoveStick); }
if(movedY<internalRadius) { movedY=maxMoveStick; }
if((movedY+internalRadius) > canvas.height) { movedY = canvas.height-(maxMoveStick); }
context.arc(movedX, movedY, internalRadius, 0, circumference, false);
// create radial gradient
var grd = context.createRadialGradient(centerX, centerY, 5, centerX, centerY, 200);
// Light color
grd.addColorStop(0, internalFillColor);
// Dark color
grd.addColorStop(1, internalStrokeColor);
context.fillStyle = grd;
context.fill();
context.lineWidth = internalLineWidth;
context.strokeStyle = internalStrokeColor;
context.stroke();
}
// Events for manage touch
function onTouchStart(event) {
pressed = 1;
}
function onTouchMove(event) {
// Prevent the browser from doing its default thing (scroll, zoom)
event.preventDefault();
if (pressed !== 1 || event.targetTouches[0].target !== canvas)
return;
// Calculate the position of the touch relative to the canvas
var rect = canvas.getBoundingClientRect();
var x = event.targetTouches[0].clientX - rect.left;
var y = event.targetTouches[0].clientY - rect.top;
// Make sure the joystick doesn't move beyond the maximum allowed radius (maxMoveStick)
var dx = x - centerX;
var dy = y - centerY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > maxMoveStick) {
x = centerX + dx / distance * maxMoveStick;
y = centerY + dy / distance * maxMoveStick;
}
// Update movedX and movedY
movedX = x;
movedY = y;
// Clear the canvas
context.clearRect(0, 0, canvas.width, canvas.height);
// Redraw the joystick
drawExternal();
drawInternal();
parameters.onChange && parameters.onChange();
}
function onTouchEnd(event){
pressed = 0;
// If required reset position store variable
if (autoReturnToCenter){
movedX = centerX;
movedY = centerY;
}
// Delete canvas
context.clearRect(0, 0, canvas.width, canvas.height);
// Redraw object
drawExternal();
drawInternal();
//canvas.unbind('touchmove');
parameters.onChange && parameters.onChange();
}
// Events for manage mouse
function onMouseDown(event) {
pressed = 1;
}
function onMouseMove(event) {
if (pressed !== 1)
return;
// Calculate the position of the mouse relative to the canvas
var rect = canvas.getBoundingClientRect();
var x = event.clientX - rect.left;
var y = event.clientY - rect.top;
// Make sure the joystick doesn't move beyond the maximum allowed radius (maxMoveStick)
var dx = x - centerX;
var dy = y - centerY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > maxMoveStick) {
x = centerX + dx / distance * maxMoveStick;
y = centerY + dy / distance * maxMoveStick;
}
// Update movedX and movedY
movedX = x;
movedY = y;
// Clear the canvas
context.clearRect(0, 0, canvas.width, canvas.height);
// Redraw the joystick
drawExternal();
drawInternal();
parameters.onChange && parameters.onChange();
}
function onMouseUp(event) {
pressed = 0;
// If required reset position store variable
if(autoReturnToCenter)
{
movedX = centerX;
movedY = centerY;
}
// Delete canvas
context.clearRect(0, 0, canvas.width, canvas.height);
// Redraw object
drawExternal();
drawInternal();
//canvas.unbind('mousemove');
parameters.onChange && parameters.onChange();
}
// Public methods
this.getX = function (){
return (movedX - centerX)/maxMoveStick;
};
this.getY = function (){
return (centerY - movedY)/maxMoveStick;
};
this.getDir = function () {
var horizontal = movedX - centerX, vertical = movedY - centerY;
return {
dirX: (horizontal < directionHorizontalLimitNeg) ? -1 : (horizontal > directionHorizontalLimitPos) ? 1 : 0,
dirY: (vertical < directionVerticalLimitNeg) ? -1 : (vertical > directionVerticalLimitPos) ? 1 : 0,
};
};
this.remove = function(){
objContainer = null;
canvas.remove();
canvas = null;
context = null;
};
});
var JoyButton = (function(container, parameters){
var objContainer = document.getElementById(container);
var btn = document.createElement("button");
btn.id = parameters.title || "joybutton";
btn.style = "background-color:#046DAA;border-width:10px;border-radius:50%;width:152px;height:152px;margin:24px";
btn.innerText = parameters.innerText || "";
objContainer.appendChild(btn);
var pressed = false;
function onBS(event){
pressed = true;
parameters.onChange && parameters.onChange(pressed);
}
function onBE(event){
pressed = false;
parameters.onChange && parameters.onChange(pressed);
}
if ("ontouchstart" in document.documentElement){
btn.addEventListener("touchstart", onBS, false);
btn.addEventListener("touchend", onBE, false);
}
else{
btn.addEventListener("mousedown", onBS, false);
btn.addEventListener("mouseup", onBE, false);
}
this.isPressed = function(){
return pressed;
};
this.remove = function(){
objContainer = null;
btn.remove();
btn = null;
};
});
var that = this;
var joyS, joyB, mainDiv, observer;
var lastValues = {dirX: 0, dirY: 0, kick: false};
function onInputChange(){
that.room.setKeyState(Utils.keyState(lastValues.dirX, lastValues.dirY, lastValues.kick));
}
function update(values){
var change = false;
if (typeof values.pressed != "undefined" && values.pressed != lastValues.kick){
lastValues.kick = values.pressed;
change = true;
}
if (typeof values.dirX != "undefined" && (values.dirX != lastValues.dirX || values.dirY != lastValues.dirY)){
lastValues.dirX = values.dirX;
lastValues.dirY = values.dirY;
change = true;
}
if (change)
onInputChange();
}
function appendDiv(){
var canvas = document.getElementsByClassName("canvas")[0];
if (!canvas || document.getElementById("joyDiv")!=null)
return;
canvas.parentElement.insertBefore(mainDiv, canvas);
joyS = new JoyStick("joyDiv", {
onChange: ()=>{
update(joyS.getDir());
}
});
joyB = new JoyButton("btnDiv", {
innerText: "Kick",
onChange: (pressed)=>{
update({pressed});
}
});
}
function removeDiv(){
joyS?.remove();
joyB?.remove();
joyS = null;
joyB = null;
}
function observerCallback(mutationList){
mutationList.forEach((x)=>{
if (x.addedNodes[0]?.className=="game-state-view")
appendDiv();
else if (x.removedNodes[0]?.className=="game-state-view")
removeDiv();
});
}
this.initialize = function(){
mainDiv = document.createElement("div");
mainDiv.style = "position:fixed;right:0px;bottom:calc(4% + 110px);display:inline-block;clear:both;width:100%;padding:0 20px;box-sizing:border-box;zIndex:9999;opacity:"+that.opacity;
mainDiv.innerHTML = `<div style="float:left;min-width:200px"><div id="btnDiv" style="width:200px;height:200px;"></div></div><div style="float:right;min-width:200px;"><div id="joyDiv" style="width:200px;height:200px;"></div></div>`;
appendDiv();
var f = ()=>{
var ts = document.getElementsByClassName("top-section")[0];
if (!ts){
setTimeout(f, 100);
return;
}
observer = new MutationObserver(observerCallback);
observer.observe(ts, { childList: true });
};
setTimeout(f, 1000);
};
this.finalize = function(){
observer?.disconnect();
removeDiv();
mainDiv.remove();
observer = null;
mainDiv = null;
};
this.onVariableValueChange = function(addonObject, variableName, oldValue, newValue){
if (that!=addonObject)
return;
if (variableName=="opacity")
mainDiv.style.opacity = newValue;
};
};