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
272 lines (253 loc) • 9.27 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, "flappyKirby", true, {
version: "0.1",
author: "abc",
description: `This plugin lets you host a flappy kirby game. You also probably need to activate the flappy kriby renderer.`,
allowFlags: AllowFlags.CreateRoom
});
this.defineVariable({
name: "maxScore",
description: "Maximum score that a player can have before the stadium is randomized.",
type: VariableType.Integer,
value: 3,
range: {
min: 1,
max: Infinity,
step: 1
}
});
var that = this, scores = null;
const groundPlaneId = 1, finishPlaneId = 3;
function generateStadium(mapWidth, mapHeight, n, cr, ch, d, kickback, vx, g, onError){ // n: number of columns, ch: column height, cr: column radius
var w = mapWidth/2, h = mapHeight/2;
if (w<60 || h<60){
onError("Width and height must be > 120");
return null;
}
var map = {
"name" : "abc\'s Flappy Kirby {w:"+mapWidth+", h:"+mapHeight+", n:"+n+", cr:"+cr+", ch:"+ch+", d:"+d+", kb:"+kickback+", vx:"+vx+", g:"+g+"}",
"width" : w,
"height" : h,
"bg" : { "type" : "", "width" : 1, "height" : 1, "color" : "454C5E" },
"vertexes" : [
/* 0 */ { "x" : w-30, "y" : 30-h },
/* 1 */ { "x" : 30-w, "y" : 30-h },
/* 2 */ { "x" : 30-w, "y" : h-30 },
/* 3 */ { "x" : w-30, "y" : h-30 },
/* 4 */ { "x" : 30-w, "y" : 30-h },
/* 5 */ { "x" : 30-w, "y" : h-30 },
/* 6 */ { "x" : w-30, "y" : 30-h },
/* 7 */ { "x" : w-30, "y" : h-30 }
],
"segments" : [
{ "v0" : 0, "v1" : 1, "curve" : 0, "trait": "customPlane" },
{ "v0" : 2, "v1" : 3, "curve" : 0, "trait": "customPlane" },
{ "v0" : 4, "v1" : 5, "curve" : 0, "trait": "customPlane" },
{ "v0" : 6, "v1" : 7, "curve" : 0, "trait": "customPlane" }
],
"planes" : [
{ "normal" : [0,1], "dist" : 30-h, "trait": "customPlane" },
{ "normal" : [0,-1], "dist" : 30-h, "trait": "customPlane" },
{ "normal" : [1,0], "dist" : 30-w, "trait": "customPlane" },
{ "normal" : [-1,0], "dist" : 30-w, "trait": "customPlane" }
],
"goals" : [],
"discs" : [
{ "radius" : 20000000, "invMass" : 0, "pos" : [0,10000000], "color" : "transparent", "cMask" : ["none"], "cGroup" : ["kick"] }
],
"playerPhysics" : {
"kickStrength" : 0,
"acceleration": 0,
"xspeed": vx,
"kickingAcceleration" : 0,
"damping" : 1,
"kickingDamping" : 1,
"kickback" : kickback,
"gravity" : [0, g]
},
"ballPhysics" : "disc0",
"spawnDistance" : h-60,
"traits" : {
"customPlane": {
"color": "a3acc2",
"bCoef" : 0,
"bias": 0,
"bCoef" : 0,
"cMask": ["c0"],
"cGroup": ["wall"]
},
"colliderWall": {
"color": "eb8934",
"bias": 0,
"bCoef" : 0,
"cMask": ["c0"],
"cGroup": ["wall"]
}
},
"joints" : [],
"redSpawnPoints" : [[60-w, 60-h]],
"blueSpawnPoints" : [[60-w, 60-h]],
"canBeStored" : true,
"cameraFollow" : "player"
};
w-=60;
var x = (d-n*(d+2*cr))/2
for (var i=0;i<n;i++,x+=2*cr+d){
var currentVertexIndex = map.vertexes.length;
if (i%2==0){
map.vertexes.push({ "x" : x, "y" : h-30 });// A
map.vertexes.push({ "x" : x+2*cr, "y" : h-30 });// B
map.vertexes.push({ "x" : x, "y" : h-30-ch });// C
map.vertexes.push({ "x" : x+2*cr, "y" : h-30-ch });// D
map.segments.push({ "v0" : currentVertexIndex, "v1" : currentVertexIndex+2, "curve" : 0, "trait": "colliderWall" });
map.segments.push({ "v0" : currentVertexIndex+2, "v1" : currentVertexIndex+3, "curve" : 180, "trait": "colliderWall" });
map.segments.push({ "v0" : currentVertexIndex+3, "v1" : currentVertexIndex+1, "curve" : 0, "trait": "colliderWall" });
}
else{
map.vertexes.push({ "x" : x, "y" : 30-h });// A
map.vertexes.push({ "x" : x+2*cr, "y" : 30-h });// B
map.vertexes.push({ "x" : x, "y" : 30-h+ch });// C
map.vertexes.push({ "x" : x+2*cr, "y" : 30-h+ch });// D
map.segments.push({ "v0" : currentVertexIndex, "v1" : currentVertexIndex+2, "curve" : 0, "trait": "colliderWall" });
map.segments.push({ "v0" : currentVertexIndex+2, "v1" : currentVertexIndex+3, "curve" : -180, "trait": "colliderWall" });
map.segments.push({ "v0" : currentVertexIndex+3, "v1" : currentVertexIndex+1, "curve" : 0, "trait": "colliderWall" });
}
}
var s;
try{
s = Utils.parseStadium(JSON.stringify(map));
}catch(e){}
if (s)
s.initialXVelocity = vx;
return s;
}
function generateRandomStadium(onError){
const offsetDist = 120;
var w, h, n, cr, ch, d, kickback, vx, g;
w = (1000+Math.random()*5000)|0;
h = (200+Math.random()*800)|0;
cr = (h/100+Math.random()*(h/100))|0;
ch = (h/2.5+Math.random()*(h/10))|0;
d = (60+2*cr+Math.random()*(w/100))|0;
kickback = ((3+(ch/h)*3+Math.random()*15)*0.6)|0;
vx = (1+0.0001*w*Math.random())|0;
if ((2*ch-h)>0)
vx/=8*(2*ch-h);
else
vx*=1.2;
g = (((h/7500+(ch/h)/1000+Math.random()*0.075)*100)|0)/100;
var nmin=((3*w/5-60+d)/(d+2*cr))|0, nmax = (w+d-60-2*offsetDist)/(d+2*cr)|0;
//console.log(nmin, nmax);
n = (nmin+Math.random()*(nmax-nmin))|0;
return generateStadium(w, h, n, cr, ch, d, kickback, vx, g, onError);
}
var stadium, initializeScheduled = false;
function updatePlayerDiscs(){
setTimeout(()=>{
that.room.state.players.forEach((player)=>{
if (player.disc!=null){
that.room.setPlayerDiscProperties(player.id, {
x: 60-stadium.width,
y: 60-stadium.height,
xspeed: stadium.initialXVelocity,
yspeed: 0,
cGroup: 1<<CollisionFlags.c0
});
}
});
}, 100);
}
function resetScores(){
scores = {};
that.room.hostPing = 0;
}
function resetCoordinateLater(playerId){
Utils.runAfterGameTick(()=>{
if (stadium)
that.room.setPlayerDiscProperties(playerId, {
x: 60-stadium.width,
y: 60-stadium.height,
xspeed: stadium.initialXVelocity,
yspeed: 0
});
});
}
this.initialize = function(){
setTimeout(()=>{
do{
stadium = generateRandomStadium(console.log);
} while(!stadium);
resetScores();
that.room.stopGame();
that.room.setCurrentStadium(stadium);
that.room.startGame();
updatePlayerDiscs();
}, 100);
};
this.finalize = function(){
scores = null;
};
this.onCollisionDiscVsPlane = function(discId, discPlayerId, planeId){
if (planeId!=finishPlaneId){
if (planeId!=groundPlaneId)
return;
resetCoordinateLater(discPlayerId);
return;
}
var x=(scores[discPlayerId]|0)+1;
scores[discPlayerId]=x;
if (discPlayerId==0)
that.room.hostPing=x|0;
if (!initializeScheduled && x>=that.maxScore){
initializeScheduled = true;
var name = that.room.players.find((x)=>x.id==discPlayerId)?.name||"?";
setTimeout(()=>{
initializeScheduled = false;
that.room.sendAnnouncement("Congratulations, "+name+"!", null, 0x15cf31, 4, 2);
that.initialize();
}, 100);
}
else
resetCoordinateLater(discPlayerId);
};
this.onCollisionDiscVsSegment = function(discId, discPlayerId, segmentId){
if (discPlayerId==null)
return;
resetCoordinateLater(discPlayerId);
};
this.onGameStart = function(){
updatePlayerDiscs();
};
this.onGameStop = function(){
resetScores();
};
this.onPlayerTeamChange = function(id, teamId, byId){
setTimeout(()=>{
var player = that.room.state.players.find((x)=>x.id==id);
if (player?.disc!=null){
that.room.setPlayerDiscProperties(id, {
x: 60-stadium.width,
y: 60-stadium.height,
xspeed: stadium.initialXVelocity,
yspeed: 0,
cGroup: 1<<CollisionFlags.c0
});
}
}, 100);
};
this.onPlayerJoin = function(playerObj){
if (scores){
scores[playerObj.id] = 0;
that.room.setPlayerTeam(playerObj.id, 1);
}
};
this.onPlayerLeave = function(playerObj, reason, isBanned, byId){
if (scores)
delete scores[playerObj?.id];
};
this.modifyPlayerPing = function(playerId, ping){
return scores ? (scores[playerId] | 0) : 0;
};
}