diff --git a/src/Client.ts b/src/Client.ts
index 237da40..08df2ad 100644
--- a/src/Client.ts
+++ b/src/Client.ts
@@ -324,11 +324,11 @@ export default class Client {
this.setHasCheated(true);
player.destroy();
- player.onKill(player);
player.onDeath(player);
+ player.onKill(player);
}
}
-
+
return;
}
case ServerBound.Spawn: {
diff --git a/src/Const/Commands.ts b/src/Const/Commands.ts
index a153b08..72aae7e 100644
--- a/src/Const/Commands.ts
+++ b/src/Const/Commands.ts
@@ -18,28 +18,36 @@
import Client from "../Client"
import { AccessLevel, maxPlayerLevel } from "../config";
+
+import ObjectEntity from "../Entity/Object";
+import LivingEntity from "../Entity/Live";
+
import AbstractBoss from "../Entity/Boss/AbstractBoss";
import Defender from "../Entity/Boss/Defender";
import FallenBooster from "../Entity/Boss/FallenBooster";
import FallenOverlord from "../Entity/Boss/FallenOverlord";
import Guardian from "../Entity/Boss/Guardian";
import Summoner from "../Entity/Boss/Summoner";
-import LivingEntity from "../Entity/Live";
+
import ArenaCloser from "../Entity/Misc/ArenaCloser";
import FallenAC from "../Entity/Misc/Boss/FallenAC";
-import Mothership from "../Entity/Misc/Mothership";
import FallenSpike from "../Entity/Misc/Boss/FallenSpike";
import FallenMegaTrapper from "../Entity/Misc/Boss/FallenMegaTrapper";
+
+import Mothership from "../Entity/Misc/Mothership";
import Dominator from "../Entity/Misc/Dominator";
-import ObjectEntity from "../Entity/Object";
+
import AbstractShape from "../Entity/Shape/AbstractShape";
import Crasher from "../Entity/Shape/Crasher";
import Pentagon from "../Entity/Shape/Pentagon";
import Square from "../Entity/Shape/Square";
import Triangle from "../Entity/Shape/Triangle";
+
import AutoTurret from "../Entity/Tank/AutoTurret";
import Bullet from "../Entity/Tank/Projectile/Bullet";
import TankBody from "../Entity/Tank/TankBody";
+
+import { TeamEntity } from "../Entity/Misc/TeamEntity";
import { AIState } from "../Entity/AI";
import { Entity, EntityStateFlags } from "../Native/Entity";
import { saveToVLog } from "../util";
@@ -60,6 +68,7 @@ export const enum CommandID {
gameGodmode = "game_godmode",
gameAnnounce = "game_announce",
gameGoldenName = "game_golden_name",
+ gameNeutral = "game_neutral",
adminSummon = "admin_summon",
adminKillAll = "admin_kill_all",
adminKillEntity = "admin_kill_entity",
@@ -154,6 +163,12 @@ export const commandDefinitions = {
description: "Toggles the golden nickname color that appears upon using cheats",
permissionLevel: AccessLevel.FullAccess,
isCheat: false
+ },
+ game_neutral: {
+ id: CommandID.gameNeutral,
+ description: "Sets your tank's team to the neutral team",
+ permissionLevel: AccessLevel.FullAccess,
+ isCheat: false
},
admin_summon: {
id: CommandID.adminSummon,
@@ -233,9 +248,14 @@ export const commandCallbacks = {
game_teleport: (client: Client, xArg: string, yArg: string) => {
const player = client.camera?.cameraData.player;
if (!Entity.exists(player) || !ObjectEntity.isObject(player)) return;
+
+ if (!xArg || !yArg) return;
+
const x = xArg.match(RELATIVE_POS_REGEX) ? player.positionData.x + parseInt(xArg.slice(1) || "0", 10) : parseInt(xArg, 10);
const y = yArg.match(RELATIVE_POS_REGEX) ? player.positionData.y + parseInt(yArg.slice(1) || "0", 10) : parseInt(yArg, 10);
+
if (isNaN(x) || isNaN(y)) return;
+
player.positionData.x = x;
player.positionData.y = y;
player.setVelocity(0, 0);
@@ -293,6 +313,15 @@ export const commandCallbacks = {
game_golden_name: (client: Client, activeArg?: string) => {
client.setHasCheated(!client.hasCheated());
},
+ game_neutral: (client: Client) => {
+ const team = client.camera?.game.arena;
+ const player = client.camera?.cameraData.values.player;
+
+ if (!team || !player) return;
+ if (!ObjectEntity.isObject(player)) return;
+
+ TeamEntity.setTeam(team, player);
+ },
admin_summon: (client: Client, entityArg: string, countArg?: string, xArg?: string, yArg?: string) => {
const count = countArg ? parseInt(countArg) : 1;
let x = parseInt(xArg || "0", 10);
diff --git a/src/Entity/AI.ts b/src/Entity/AI.ts
index 8850ec2..97d760e 100644
--- a/src/Entity/AI.ts
+++ b/src/Entity/AI.ts
@@ -88,7 +88,7 @@ export class AI {
/** The current game. */
public game: GameServer;
/** The AI's target. */
- public target: Entity & { positionData: PositionGroup, physicsData: PhysicsGroup, relationsData: RelationsGroup, velocity: Vector } | null = null;
+ public target: ObjectEntity | null = null;
/** The speed at which the ai's owner can move. */
public movementSpeed = 1;
/** The speed at which the ai can reach the target. */
@@ -168,9 +168,9 @@ export class AI {
chunk ^= bitValue;
const id = 32 * i + bitIdx;
- const entity = this.game.entities.inner[id] as ObjectEntity;
+ const entity = this.game.entities.inner[id];
if (!entity || entity.hash === 0) continue;
- if (!entity.positionData || !entity.relationsData || !entity.physicsData) continue;
+ if (!ObjectEntity.isObject(entity)) continue;
if (!entity.isPhysical) continue;
// Check if the target is living
@@ -197,7 +197,7 @@ export class AI {
}
}
- return this.target = closestEntity as Entity & { positionData: PositionGroup, physicsData: PhysicsGroup, relationsData: RelationsGroup, velocity: Vector };
+ return this.target = closestEntity;
}
/** Aims and predicts at the target. */
diff --git a/src/Entity/Boss/AbstractBoss.ts b/src/Entity/Boss/AbstractBoss.ts
index 7bb3b5c..914dfef 100644
--- a/src/Entity/Boss/AbstractBoss.ts
+++ b/src/Entity/Boss/AbstractBoss.ts
@@ -163,12 +163,10 @@ export default class AbstractBoss extends LivingEntity {
* Will set game.arena.boss to null, so that the next boss can spawn
*/
public onDeath(killer: LivingEntity) {
- let killerName: string;
+ let killerName: string = "an unnamed tank";
if (TankBody.isTank(killer) || AbstractBoss.isBoss(killer)) {
- killerName = killer.nameData.values.name;
- } else {
- killerName = "an unnamed tank";
+ killerName = killer.nameData.values.name || "an unnamed tank";
}
this.game.broadcast()
diff --git a/src/Entity/Misc/MazeWall.ts b/src/Entity/Misc/MazeWall.ts
index bd36b2d..c4f2348 100644
--- a/src/Entity/Misc/MazeWall.ts
+++ b/src/Entity/Misc/MazeWall.ts
@@ -16,16 +16,17 @@
along with this program. If not, see
*/
-import GameServer from "../../Game";
+import ArenaEntity from "../../Native/Arena";
import ObjectEntity from "../Object";
import { PhysicsFlags, Color } from "../../Const/Enums";
+
/**
* Only used for maze walls and nothing else.
*/
export default class MazeWall extends ObjectEntity {
public static newFromBounds(
- game: GameServer,
+ arena: ArenaEntity,
minX: number,
minY: number,
maxX: number,
@@ -40,11 +41,11 @@ export default class MazeWall extends ObjectEntity {
const centerX = (minX + maxX) / 2;
const centerY = (minY + maxY) / 2;
- return new MazeWall(game, centerX, centerY, width, height);
+ return new MazeWall(arena, centerX, centerY, width, height);
}
- public constructor(game: GameServer, x: number, y: number, width: number, height: number) {
- super(game);
+ public constructor(arena: ArenaEntity, x: number, y: number, width: number, height: number) {
+ super(arena.game);
this.setGlobalEntity();
@@ -58,7 +59,7 @@ export default class MazeWall extends ObjectEntity {
this.physicsData.values.pushFactor = 2;
this.physicsData.values.absorbtionFactor = 0;
- this.relationsData.values.team = this.game.arena;
+ this.relationsData.values.team = arena;
this.styleData.values.borderWidth = 10;
this.styleData.values.color = Color.Box;
diff --git a/src/Entity/Misc/Mothership.ts b/src/Entity/Misc/Mothership.ts
index 8151955..145cf33 100644
--- a/src/Entity/Misc/Mothership.ts
+++ b/src/Entity/Misc/Mothership.ts
@@ -89,7 +89,7 @@ export default class Mothership extends TankBody {
this.game.broadcast()
.u8(ClientBound.Notification)
// If mothership has a team name, use it, otherwise just say has destroyed a mothership
- .stringNT(`${killerTeamIsATeam ? (killerTeam.teamName || "a mysterious group") : (killer.nameData?.values.name || "an unnamed tank")} has destroyed ${teamIsATeam ? team.teamName + "'s" : "a"} Mothership!`)
+ .stringNT(`${killerTeamIsATeam ? (killerTeam.teamName || "a mysterious group") : (killer.nameData?.values.name || "an unnamed tank")} has destroyed ${teamIsATeam ? (team.teamName || "a mysterious group") + "'s" : "a"} Mothership!`)
.u32(killerTeamIsATeam ? ColorsHexCode[killerTeam.teamData.values.teamColor] : 0x000000)
.float(-1)
.stringNT("").send();
diff --git a/src/Entity/Misc/TeamBase.ts b/src/Entity/Misc/TeamBase.ts
index 9b4304f..7437f17 100644
--- a/src/Entity/Misc/TeamBase.ts
+++ b/src/Entity/Misc/TeamBase.ts
@@ -19,7 +19,7 @@
import GameServer from "../../Game";
import { HealthFlags, PhysicsFlags, StyleFlags } from "../../Const/Enums";
-import { TeamGroupEntity } from "./TeamEntity";
+import { TeamEntity, TeamGroupEntity } from "./TeamEntity";
import LivingEntity from "../Live";
import BaseDrones from "./BaseDrones";
/**
@@ -34,6 +34,8 @@ export default class TeamBase extends LivingEntity {
this.relationsData.values.team = team;
+ if (team instanceof TeamEntity) team.base = this;
+
this.positionData.values.x = x;
this.positionData.values.y = y;
diff --git a/src/Entity/Misc/TeamEntity.ts b/src/Entity/Misc/TeamEntity.ts
index 23f5e9b..0a460ab 100644
--- a/src/Entity/Misc/TeamEntity.ts
+++ b/src/Entity/Misc/TeamEntity.ts
@@ -17,6 +17,9 @@
*/
import GameServer from "../../Game";
+import TeamBase from "./TeamBase";
+import ObjectEntity from "../Object";
+import TankBody from "../Tank/TankBody";
import { Color } from "../../Const/Enums";
import { Entity } from "../../Native/Entity";
@@ -51,6 +54,9 @@ export class TeamEntity extends Entity implements TeamGroupEntity {
/** Used for notifications in team based gamemodes */
public teamName: string;
+
+ /** The team's spawn base. */
+ public base: TeamBase | null = null;
public constructor(game: GameServer, color: Color, name: string = ColorsTeamName[color] || "UNKNOWN") {
super(game);
@@ -64,4 +70,16 @@ export class TeamEntity extends Entity implements TeamGroupEntity {
return !!entity.teamData;
}
+
+ public static setTeam(team: TeamGroupEntity, entity: ObjectEntity) {
+ if (!Entity.exists(entity)) return;
+
+ entity.relationsData.values.team = team;
+
+ entity.styleData.color = team.teamData.values.teamColor;
+
+ if (TankBody.isTank(entity)) {
+ entity.cameraEntity.relationsData.values.team = team;
+ }
+ }
}
diff --git a/src/Entity/Object.ts b/src/Entity/Object.ts
index 071a82c..44c45c1 100644
--- a/src/Entity/Object.ts
+++ b/src/Entity/Object.ts
@@ -18,7 +18,7 @@
import * as util from "../util";
import GameServer from "../Game";
-import Vector from "../Physics/Vector";
+import Vector, { VectorAbstract } from "../Physics/Vector";
import { PhysicsGroup, PositionGroup, RelationsGroup, StyleGroup } from "../Native/FieldGroups";
import { Entity } from "../Native/Entity";
@@ -95,11 +95,11 @@ export default class ObjectEntity extends Entity {
/** Used to determine the parent of all parents. */
public rootParent: ObjectEntity = this;
- /** Entity tags. */
+ /** Entity bit flag tags. */
public entityTags: number = 0;
/** Entity type ID. */
- public arenaMobID: string = ""
+ public arenaMobID: string | null = null;
/** Velocity used for physics. */
public velocity = new Vector();
@@ -231,7 +231,11 @@ export default class ObjectEntity extends Entity {
if (this.physicsData.values.flags & PhysicsFlags.showsOnMap) {
const globalEntities = this.game.entities.globalEntities;
- util.removeFast(globalEntities, globalEntities.indexOf(this.id));
+ const id = this.id;
+
+ if (!globalEntities.includes(id)) return;
+
+ util.removeFast(globalEntities, globalEntities.indexOf(id));
}
super.delete();
diff --git a/src/Entity/Tank/Barrel.ts b/src/Entity/Tank/Barrel.ts
index 792fdc2..d160d6a 100644
--- a/src/Entity/Tank/Barrel.ts
+++ b/src/Entity/Tank/Barrel.ts
@@ -1,5 +1,5 @@
/*
- DiepCustom - custom tank game server that shares diep.io's WebSocket protocol
+ DiepCustom - custom tank game server that shares diep.io"s WebSocket protocol
Copyright (C) 2022 ABCxFF (github.com/ABCxFF)
This program is free software: you can redistribute it and/or modify
@@ -38,7 +38,7 @@ import { BarrelAddon, BarrelAddonById } from "./BarrelAddons";
import { Swarm } from "./Projectile/Swarm";
import NecromancerSquare from "./Projectile/NecromancerSquare";
/**
- * Class that determines when barrels can shoot, and when they can't.
+ * Class that determines when barrels can shoot, and when they can"t.
*/
export class ShootCycle {
/** The barrel this cycle is keeping track of. */
@@ -61,7 +61,7 @@ export class ShootCycle {
this.reloadTime = this.barrelEntity.barrelData.reloadTime = reloadTime;
}
- const alwaysShoot = (this.barrelEntity.definition.forceFire) || (this.barrelEntity.definition.bullet.type === 'drone') || (this.barrelEntity.definition.bullet.type === 'minion');
+ const alwaysShoot = (this.barrelEntity.definition.forceFire) || (this.barrelEntity.definition.bullet.type === "drone") || (this.barrelEntity.definition.bullet.type === "minion");
if (this.pos >= reloadTime) {
// When its not shooting dont shoot, unless its a drone
@@ -70,7 +70,7 @@ export class ShootCycle {
return;
}
// When it runs out of drones, dont shoot
- if (typeof this.barrelEntity.definition.droneCount === 'number' && this.barrelEntity.droneCount >= this.barrelEntity.definition.droneCount) {
+ if (typeof this.barrelEntity.definition.droneCount === "number" && this.barrelEntity.droneCount >= this.barrelEntity.definition.droneCount) {
this.pos = reloadTime;
return;
}
@@ -105,7 +105,7 @@ export default class Barrel extends ObjectEntity {
/** Number of drones that this barrel shot that are still alive. */
public droneCount = 0;
- /** The barrel's addons */
+ /** The barrel"s addons */
public addons: BarrelAddon[] = [];
/** Always existant barrel field group, present on all barrels. */
@@ -169,32 +169,32 @@ export default class Barrel extends ObjectEntity {
case "rocket":
new Rocket(this, this.tank, tankDefinition, angle);
break;
- case 'bullet': {
+ case "bullet": {
projectile = new Bullet(this, this.tank, tankDefinition, angle);
if (tankDefinition && (tankDefinition.id === Tank.ArenaCloser || tankDefinition.id === DevTank.Squirrel)) projectile.positionData.flags |= PositionFlags.canMoveThroughWalls;
break;
}
- case 'trap':
+ case "trap":
projectile = new Trap(this, this.tank, tankDefinition, angle);
break;
- case 'drone':
+ case "drone":
projectile = new Drone(this, this.tank, tankDefinition, angle);
break;
- case 'necrodrone':
+ case "necrodrone":
projectile = new NecromancerSquare(this, this.tank, tankDefinition, angle);
break;
- case 'swarm':
+ case "swarm":
projectile = new Swarm(this, this.tank, tankDefinition, angle);
break;
- case 'minion':
+ case "minion":
projectile = new Minion(this, this.tank, tankDefinition, angle);
break;
- case 'flame':
+ case "flame":
projectile = new Flame(this, this.tank, tankDefinition, angle);
break;
- case 'wall': {
- const w = projectile = new MazeWall(this.game, Math.round(this.tank.inputs.mouse.x / 50) * 50, Math.round(this.tank.inputs.mouse.y / 50) * 50, 250, 250);
+ case "wall": {
+ const w = projectile = new MazeWall(this.game.arena, Math.round(this.tank.inputs.mouse.x / 50) * 50, Math.round(this.tank.inputs.mouse.y / 50) * 50, 250, 250);
setTimeout(() => {
w.delete();
}, 60 * 1000);
@@ -204,7 +204,7 @@ export default class Barrel extends ObjectEntity {
projectile = new CrocSkimmer(this, this.tank, tankDefinition, angle);
break;
default:
- util.log('Ignoring attempt to spawn projectile of type ' + this.definition.bullet.type);
+ util.log("Ignoring attempt to spawn projectile of type " + this.definition.bullet.type);
break;
}
diff --git a/src/Entity/Tank/Projectile/Bullet.ts b/src/Entity/Tank/Projectile/Bullet.ts
index ffc3a29..be587d7 100644
--- a/src/Entity/Tank/Projectile/Bullet.ts
+++ b/src/Entity/Tank/Projectile/Bullet.ts
@@ -73,7 +73,7 @@ export default class Bullet extends LivingEntity {
this.physicsData.values.sides = 1;
this.physicsData.values.flags |= PhysicsFlags.noOwnTeamCollision | PhysicsFlags.canEscapeArena;
- if (tank.positionData.values.flags & PositionFlags.canMoveThroughWalls || this.relationsData.values.team === this.game.arena) this.positionData.values.flags |= PositionFlags.canMoveThroughWalls
+ if (tank.positionData.values.flags & PositionFlags.canMoveThroughWalls) this.positionData.values.flags |= PositionFlags.canMoveThroughWalls
this.physicsData.values.size = (barrel.physicsData.values.width / 2) * bulletDefinition.sizeRatio;
this.styleData.values.color = tank.rootParent.styleData.values.color;
this.styleData.values.flags |= StyleFlags.hasNoDmgIndicator;
diff --git a/src/Game.ts b/src/Game.ts
index 0772e38..646c608 100644
--- a/src/Game.ts
+++ b/src/Game.ts
@@ -147,7 +147,7 @@ export default class GameServer {
this.arena = new ArenaClass(this);
this._tickInterval = setInterval(() => {
- if (this.clients.size) this.tickLoop();
+ if (this.clients.size) this.tickLoop(); // Don't tick empty games
}, config.mspt);
}
@@ -159,6 +159,10 @@ export default class GameServer {
public broadcastPlayerCount() {
this.broadcast().vu(ClientBound.PlayerCount).vu(GameServer.globalPlayerCount).send();
}
+ /** Sends a notification to all clients connected to this game server. */
+ public broadcastMessage(text: string, color = 0x000000, time = 5000, id = "") {
+ this.broadcast().u8(ClientBound.Notification).stringNT(text).u32(color).float(time).stringNT(id).send();
+ }
/** Ends the game instance. */
public end() {
diff --git a/src/Gamemodes/Domination.ts b/src/Gamemodes/Domination.ts
index 7596d20..ae0a452 100644
--- a/src/Gamemodes/Domination.ts
+++ b/src/Gamemodes/Domination.ts
@@ -18,6 +18,7 @@
import Client from "../Client";
import { Color, ColorsHexCode, ArenaFlags, ValidScoreboardIndex, ClientBound } from "../Const/Enums";
+import ObjectEntity from "../Entity/Object";
import Dominator from "../Entity/Misc/Dominator";
import TeamBase from "../Entity/Misc/TeamBase";
import { TeamEntity } from "../Entity/Misc/TeamEntity";
@@ -25,7 +26,7 @@ import TankBody from "../Entity/Tank/TankBody";
import GameServer from "../Game";
import ArenaEntity, { ArenaState } from "../Native/Arena";
import { Entity } from "../Native/Entity";
-import { randomFrom } from "../util";
+import { randomFrom, getRandomPosition } from "../util";
const arenaSize = 11150;
const baseSize = arenaSize / (3 + 1/3); // 3345, must scale with arena size
@@ -81,26 +82,27 @@ export default class DominationArena extends ArenaEntity {
NE.prefix = "NE ";
this.dominators.push(SE, SW, NW, NE);
}
-
- public spawnPlayer(tank: TankBody, client: Client) {
- tank.positionData.values.y = arenaSize * Math.random() - arenaSize;
-
- const xOffset = (Math.random() - 0.5) * baseSize,
- yOffset = (Math.random() - 0.5) * baseSize;
-
- const team = this.playerTeamMap.get(client) || randomFrom(this.teams);
- const teamBase: TeamBase = this.game.entities.inner.find((entity) => entity instanceof TeamBase && entity.relationsData.values.team === team) as TeamBase;
-
- tank.relationsData.values.team = teamBase.relationsData.values.team;
- tank.styleData.values.color = teamBase.styleData.values.color;
- tank.positionData.values.x = teamBase.positionData.values.x + xOffset;
- tank.positionData.values.y = teamBase.positionData.values.y + yOffset;
+
+ public decideTeam(client: Client): TeamEntity {
+ const team = this.playerTeamMap.get(client) || randomFrom(this.teams);
this.playerTeamMap.set(client, team);
- if (client.camera) client.camera.relationsData.team = tank.relationsData.values.team;
+ return team;
+ }
+
+ public spawnPlayer(tank: TankBody, client: Client) {
+ const team = this.decideTeam(client);
+ TeamEntity.setTeam(team, tank);
+
+ const teamBase = team.base;
+ if (!teamBase) return super.spawnPlayer(tank, client);
+
+ const pos = getRandomPosition(teamBase);
+ tank.positionData.values.x = pos.x;
+ tank.positionData.values.y = pos.y;
}
- public getTeamDominatorCount(team: TeamEntity) {
+ public getTeamDominatorCount(team: TeamEntity): number {
let doms: number = 0;
for (const dominator of this.dominators) {
if (dominator.relationsData.values.team === team) doms++;
diff --git a/src/Gamemodes/Mothership.ts b/src/Gamemodes/Mothership.ts
index b830ff1..373cf72 100644
--- a/src/Gamemodes/Mothership.ts
+++ b/src/Gamemodes/Mothership.ts
@@ -37,14 +37,18 @@ export default class MothershipArena extends ArenaEntity {
/** All team entities in game */
public teams: TeamEntity[] = [];
+
/** Motherships in game */
public motherships: Mothership[] = [];
- /** Maps clients to their mothership */
- public playerTeamMotMap: WeakMap = new WeakMap();
+ /** Maps clients to their team */
+ public playerTeamMap: WeakMap = new WeakMap();
public constructor(game: GameServer) {
super(game);
+
+ this.updateBounds(arenaSize * 2, arenaSize * 2);
+
this.shapeScoreRewardMultiplier = 3.0;
this.arenaData.values.flags |= ArenaFlags.hiddenScores;
@@ -59,41 +63,34 @@ export default class MothershipArena extends ArenaEntity {
mot.relationsData.values.team = team;
mot.styleData.values.color = team.teamData.values.teamColor;
- mot.positionData.values.x = Math.cos(randAngle) * arenaSize * 0.75;
- mot.positionData.values.y = Math.sin(randAngle) * arenaSize * 0.75;
+ mot.positionData.values.x = Math.cos(randAngle) * arenaSize * 0.8;
+ mot.positionData.values.y = Math.sin(randAngle) * arenaSize * 0.8;
randAngle += PI2 / TEAM_COLORS.length;
}
+ }
+
+ public decideTeam(client: Client): TeamEntity {
+ const team = this.playerTeamMap.get(client) || randomFrom(this.teams);
+ this.playerTeamMap.set(client, team);
- this.updateBounds(arenaSize * 2, arenaSize * 2);
+ return team;
}
public spawnPlayer(tank: TankBody, client: Client) {
- if (!this.motherships.length && !this.playerTeamMotMap.has(client)) {
- const team = randomFrom(this.teams);
- const { x, y } = this.findPlayerSpawnLocation();
-
- tank.positionData.values.x = x;
- tank.positionData.values.y = y;
- tank.relationsData.values.team = team;
- tank.styleData.values.color = team.teamData.teamColor;
- return;
- }
-
- const mothership = this.playerTeamMotMap.get(client) || randomFrom(this.motherships);
- this.playerTeamMotMap.set(client, mothership);
+ const team = this.decideTeam(client);
+ TeamEntity.setTeam(team, tank);
- tank.relationsData.values.team = mothership.relationsData.values.team;
- tank.styleData.values.color = mothership.styleData.values.color;
+ const success = this.attemptFactorySpawn(tank);
+ if (success) return; // This player was spawned from a factory instead
// TODO: Possess mothership if its unpossessed
const { x, y } = this.findPlayerSpawnLocation();
tank.positionData.values.x = x;
tank.positionData.values.y = y;
-
- if (client.camera) client.camera.relationsData.team = tank.relationsData.values.team;
}
+
public updateScoreboard() {
this.motherships.sort((m1, m2) => m2.healthData.values.health - m1.healthData.values.health);
@@ -113,6 +110,7 @@ export default class MothershipArena extends ArenaEntity {
this.arenaData.scoreboardAmount = length;
}
+
public updateArenaState() {
// backwards to preserve
for (let i = this.motherships.length; i --> 0;) {
diff --git a/src/Gamemodes/Survival.ts b/src/Gamemodes/Survival.ts
index e1669fc..6103350 100644
--- a/src/Gamemodes/Survival.ts
+++ b/src/Gamemodes/Survival.ts
@@ -24,7 +24,7 @@ import TankBody from "../Entity/Tank/TankBody";
import ShapeManager from "../Misc/ShapeManager";
import { ArenaFlags, ClientBound } from "../Const/Enums";
-import { tps, countdownTicks, scoreboardUpdateInterval } from "../config";
+import { tps, countdownDuration, scoreboardUpdateInterval } from "../config";
const MIN_PLAYERS = 4; // 6 in Diep.io
@@ -104,7 +104,7 @@ export default class SurvivalArena extends ArenaEntity {
if (this.arenaData.values.playersNeeded <= 0) {
this.arenaData.flags |= ArenaFlags.gameReadyStart;
} else {
- this.arenaData.ticksUntilStart = countdownTicks; // Reset countdown
+ this.arenaData.ticksUntilStart = countdownDuration; // Reset countdown
if (this.arenaData.flags & ArenaFlags.gameReadyStart) this.arenaData.flags &= ~ArenaFlags.gameReadyStart;
}
}
diff --git a/src/Gamemodes/Tag.ts b/src/Gamemodes/Tag.ts
index 0726507..9133f9b 100644
--- a/src/Gamemodes/Tag.ts
+++ b/src/Gamemodes/Tag.ts
@@ -48,7 +48,8 @@ const ENABLE_DOMINATOR = false;
*/
export class TagShapeManager extends ShapeManager {
protected get wantedShapes() {
- const ratio = Math.ceil(Math.pow(this.game.arena.width / 2500, 2));
+ const size = (this.game.arena.width + this.game.arena.height) / 2;
+ const ratio = Math.ceil(Math.pow(size / 2500, 2));
return Math.floor(12.5 * ratio);
}
@@ -61,9 +62,12 @@ export default class TagArena extends ArenaEntity {
static override GAMEMODE_ID: string = "tag";
protected shapes: ShapeManager = new TagShapeManager(this);
-
+
/** All team entities in game */
public teams: TeamEntity[] = [];
+
+ /** Maps teams to their total score. */
+ public teamScoreMap: Map = new Map();
/** Maps clients to their team */
public playerTeamMap: WeakMap = new WeakMap();
@@ -75,6 +79,7 @@ export default class TagArena extends ArenaEntity {
this.arenaData.values.flags |= ArenaFlags.hiddenScores;
const teamOrder = TEAM_COLORS.slice();
shuffleArray(teamOrder);
+
for (const teamColor of teamOrder) {
const team = new TeamEntity(this.game, teamColor);
this.teams.push(team);
@@ -87,6 +92,13 @@ export default class TagArena extends ArenaEntity {
this.updateBounds(ARENA_SIZE * 2, ARENA_SIZE * 2);
}
+
+ public decideTeam(client: Client): TeamEntity {
+ const team = this.playerTeamMap.get(client) || (this.getAlivePlayers().length <= MIN_PLAYERS ? this.teams[this.teams.length - 1] : this.teams[0]); // If there are not enough players to start the game, choose the team with least players. Otherwise choose the one with highest player count
+ this.playerTeamMap.set(client, team);
+
+ return team;
+ }
public spawnPlayer(tank: TankBody, client: Client) {
const deathMixin = tank.onDeath.bind(tank);
@@ -105,42 +117,27 @@ export default class TagArena extends ArenaEntity {
else this.playerTeamMap.set(client, team);
}
- if (!this.playerTeamMap.has(client)) {
- const team = this.getAlivePlayers().length <= MIN_PLAYERS ? this.teams[this.teams.length - 1] :
- this.teams[0]; // If there are not enough players to start the game, choose the team with least players. Otherwise choose the one with highest player count
- const { x, y } = this.findPlayerSpawnLocation();
+ const team = this.decideTeam(client);
+ TeamEntity.setTeam(team, tank);
- tank.positionData.values.x = x;
- tank.positionData.values.y = y;
- tank.relationsData.values.team = team;
- tank.styleData.values.color = team.teamData.teamColor;
+ this.updateTeamScores(); // update team counts
- this.playerTeamMap.set(client, team)
- return;
- }
-
- const team = this.playerTeamMap.get(client) || this.teams[0];
-
- this.playerTeamMap.set(client, team);
-
- tank.relationsData.values.team = team;
- tank.styleData.values.color = team.teamData.values.teamColor;
+ const success = this.attemptFactorySpawn(tank);
+ if (success) return; // This player was spawned from a factory instead
const { x, y } = this.findPlayerSpawnLocation();
tank.positionData.values.x = x;
tank.positionData.values.y = y;
-
- if (client.camera) client.camera.relationsData.team = tank.relationsData.values.team;
}
public updateScoreboard() {
const length = Math.min(10, this.teams.length);
for (let i = 0; i < length; ++i) {
const team = this.teams[i];
- const playerCount = this.getTeamPlayers(team).length;
- if (team.teamData.values.teamColor === Color.Tank) this.arenaData.values.scoreboardColors[i as ValidScoreboardIndex] = Color.ScoreboardBar;
- else this.arenaData.values.scoreboardColors[i as ValidScoreboardIndex] = team.teamData.values.teamColor;
+ const playerCount = this.getTeamScore(team);
+
+ this.arenaData.values.scoreboardColors[i as ValidScoreboardIndex] = team.teamData.values.teamColor;
this.arenaData.values.scoreboardNames[i as ValidScoreboardIndex] = team.teamName;
this.arenaData.values.scoreboardTanks[i as ValidScoreboardIndex] = -1;
this.arenaData.values.scoreboardScores[i as ValidScoreboardIndex] = playerCount;
@@ -149,24 +146,38 @@ export default class TagArena extends ArenaEntity {
this.arenaData.scoreboardAmount = Math.min(10, length);
}
+
+ public getTeamScore(team: TeamEntity): number {
+ return this.teamScoreMap.get(team) || 0;
+ }
+
+ public updateTeamScores() {
+ for (let i = 0; i < this.teams.length; ++i) {
+ const team = this.teams[i];
+
+ this.teamScoreMap.set(team, this.getTeamPlayers(team).length);
+ }
+ this.teams.sort((t1, t2) => this.getTeamScore(t2) - this.getTeamScore(t1));
+ }
public updateArenaState() {
- this.teams.sort((t1, t2) => this.getTeamPlayers(t2).length - this.getTeamPlayers(t1).length);
+ this.updateTeamScores();
+
const length = Math.min(10, this.teams.length);
const arenaPlayerCount = this.getAlivePlayers().length; // Only count alive players for win condition
- const leaderTeam = this.teams[0]; // Most players are in this team
+ const leaderTeam = this.teams[0]; // Most players are on this team
+
for (let i = 0; i < length; ++i) {
const team = this.teams[i];
if (this.getTeamPlayers(leaderTeam).length === arenaPlayerCount && arenaPlayerCount >= MIN_PLAYERS) { // If all alive players are in the leading team, it has won since all other team's players have died
if (this.state === ArenaState.OPEN) {
- this.game.broadcast()
- .u8(ClientBound.Notification)
- .stringNT(`${leaderTeam.teamName} HAS WON THE GAME!`)
- .u32(ColorsHexCode[leaderTeam.teamData.values.teamColor])
- .float(-1)
- .stringNT("").send();
-
+ this.game.broadcastMessage(
+ `${leaderTeam.teamName} HAS WON THE GAME!`,
+ ColorsHexCode[leaderTeam.teamData.values.teamColor],
+ -1
+ )
+
this.state = ArenaState.OVER;
setTimeout(() => {
this.close();
@@ -188,8 +199,12 @@ export default class TagArena extends ArenaEntity {
if ((this.game.tick % scoreboardUpdateInterval) === 0) {
this.updateScoreboard();
}
+ }
+
+ public tick(tick: number) {
+ super.tick(tick);
- if ((this.game.tick % SHRINK_INTERVAL) === 0 && this.width > MIN_SIZE) {
+ if ((tick % SHRINK_INTERVAL) === 0 && this.width > MIN_SIZE) {
this.updateBounds(this.width - SHRINK_AMOUNT, this.height - SHRINK_AMOUNT);
}
}
diff --git a/src/Gamemodes/Team2.ts b/src/Gamemodes/Team2.ts
index 0e0b736..ff2179f 100644
--- a/src/Gamemodes/Team2.ts
+++ b/src/Gamemodes/Team2.ts
@@ -20,12 +20,13 @@ import GameServer from "../Game";
import ArenaEntity from "../Native/Arena";
import Client from "../Client";
+import ObjectEntity from "../Entity/Object";
import TeamBase from "../Entity/Misc/TeamBase";
import TankBody from "../Entity/Tank/TankBody";
import { TeamEntity } from "../Entity/Misc/TeamEntity";
import { Color } from "../Const/Enums";
-import { randomFrom } from "../util";
+import { randomFrom, getRandomPosition } from "../util";
const arenaSize = 11150;
const baseWidth = arenaSize / (3 + 1/3) * 0.6; // 2007
@@ -37,30 +38,42 @@ export default class Teams2Arena extends ArenaEntity {
static override GAMEMODE_ID: string = "teams";
/** Blue Team entity */
- public blueTeamBase: TeamBase;
+ public blueTeamEntity: TeamEntity;
/** Red Team entity */
- public redTeamBase: TeamBase;
+ public redTeamEntity: TeamEntity;
/** Maps clients to their teams */
- public playerTeamMap: WeakMap = new WeakMap();
+ public playerTeamMap: WeakMap = new WeakMap();
public constructor(game: GameServer) {
super(game);
this.updateBounds(arenaSize * 2, arenaSize * 2);
- this.blueTeamBase = new TeamBase(game, new TeamEntity(this.game, Color.TeamBlue), -arenaSize + baseWidth / 2, 0, arenaSize * 2, baseWidth, true, 12, 2);
- this.redTeamBase = new TeamBase(game, new TeamEntity(this.game, Color.TeamRed), arenaSize - baseWidth / 2, 0, arenaSize * 2, baseWidth, true, 12, 2);
+
+ this.blueTeamEntity = new TeamEntity(this.game, Color.TeamBlue);
+ this.redTeamEntity = new TeamEntity(this.game, Color.TeamRed);
+
+ new TeamBase(game, this.blueTeamEntity, -arenaSize + baseWidth / 2, 0, arenaSize * 2, baseWidth, true, 12, 2);
+ new TeamBase(game, this.redTeamEntity, arenaSize - baseWidth / 2, 0, arenaSize * 2, baseWidth, true, 12, 2);
}
- public spawnPlayer(tank: TankBody, client: Client) {
- tank.positionData.values.y = 2 * arenaSize * Math.random() - arenaSize;
+ public decideTeam(client: Client): TeamEntity {
+ const team = this.playerTeamMap.get(client) || randomFrom([this.blueTeamEntity, this.redTeamEntity]);
+ this.playerTeamMap.set(client, team);
- const xOffset = (Math.random() - 0.5) * baseWidth;
+ return team;
+ }
+
+ public spawnPlayer(tank: TankBody, client: Client) {
+ const team = this.decideTeam(client);
+ TeamEntity.setTeam(team, tank);
- const base = this.playerTeamMap.get(client) || randomFrom([this.blueTeamBase, this.redTeamBase]);
- tank.relationsData.values.team = base.relationsData.values.team;
- tank.styleData.values.color = base.styleData.values.color;
- tank.positionData.values.x = base.positionData.values.x + xOffset;
- this.playerTeamMap.set(client, base);
+ const success = this.attemptFactorySpawn(tank);
+ if (success) return; // This player was spawned from a factory instead
+
+ const base = team.base;
+ if (!base) return super.spawnPlayer(tank, client);
- if (client.camera) client.camera.relationsData.team = tank.relationsData.values.team;
+ const pos = getRandomPosition(base);
+ tank.positionData.x = pos.x;
+ tank.positionData.y = pos.y;
}
}
\ No newline at end of file
diff --git a/src/Gamemodes/Team4.ts b/src/Gamemodes/Team4.ts
index 3e15a2a..b0ce55c 100644
--- a/src/Gamemodes/Team4.ts
+++ b/src/Gamemodes/Team4.ts
@@ -20,12 +20,13 @@ import GameServer from "../Game";
import ArenaEntity from "../Native/Arena";
import Client from "../Client";
+import ObjectEntity from "../Entity/Object";
import TeamBase from "../Entity/Misc/TeamBase";
import TankBody from "../Entity/Tank/TankBody";
import { TeamEntity } from "../Entity/Misc/TeamEntity";
import { Color } from "../Const/Enums";
-import { randomFrom } from "../util";
+import { randomFrom, getRandomPosition } from "../util";
const arenaSize = 11150;
const baseSize = arenaSize / (3 + 1/3); // 3345
@@ -36,41 +37,52 @@ const baseSize = arenaSize / (3 + 1/3); // 3345
export default class Teams4Arena extends ArenaEntity {
static override GAMEMODE_ID: string = "4teams";
- /** Blue TeamBASEentity */
- public blueTeamBase: TeamBase;
+ /** Blue Team entity */
+ public blueTeamEntity: TeamEntity;
/** Red TeamBASE entity */
- public redTeamBase: TeamBase;
+ public redTeamEntity: TeamEntity;
/** Green TeamBASE entity */
- public greenTeamBase: TeamBase;
+ public greenTeamEntity: TeamEntity;
/** Purple TeamBASE entity */
- public purpleTeamBase: TeamBase;
-
+ public purpleTeamEntity: TeamEntity;
/** Maps clients to their teams */
- public playerTeamMap: WeakMap = new WeakMap();
+ public playerTeamMap: WeakMap = new WeakMap();
public constructor(game: GameServer) {
super(game);
+
this.updateBounds(arenaSize * 2, arenaSize * 2);
- this.blueTeamBase = new TeamBase(game, new TeamEntity(this.game, Color.TeamBlue), -arenaSize + baseSize / 2, -arenaSize + baseSize / 2, baseSize, baseSize);
- this.redTeamBase = new TeamBase(game, new TeamEntity(this.game, Color.TeamRed), arenaSize - baseSize / 2, arenaSize - baseSize / 2, baseSize, baseSize);
- this.greenTeamBase = new TeamBase(game, new TeamEntity(this.game, Color.TeamGreen), -arenaSize + baseSize / 2, arenaSize - baseSize / 2, baseSize, baseSize);
- this.purpleTeamBase = new TeamBase(game, new TeamEntity(this.game, Color.TeamPurple), arenaSize - baseSize / 2, -arenaSize + baseSize / 2, baseSize, baseSize);
+ this.blueTeamEntity = new TeamEntity(this.game, Color.TeamBlue);
+ this.redTeamEntity = new TeamEntity(this.game, Color.TeamRed);
+ this.purpleTeamEntity = new TeamEntity(this.game, Color.TeamPurple);
+ this.greenTeamEntity = new TeamEntity(this.game, Color.TeamGreen);
+
+ new TeamBase(game, this.blueTeamEntity, -arenaSize + baseSize / 2, -arenaSize + baseSize / 2, baseSize, baseSize);
+ new TeamBase(game, this.redTeamEntity, arenaSize - baseSize / 2, arenaSize - baseSize / 2, baseSize, baseSize);
+ new TeamBase(game, this.purpleTeamEntity, arenaSize - baseSize / 2, -arenaSize + baseSize / 2, baseSize, baseSize);
+ new TeamBase(game, this.greenTeamEntity, -arenaSize + baseSize / 2, arenaSize - baseSize / 2, baseSize, baseSize);
}
- public spawnPlayer(tank: TankBody, client: Client) {
- tank.positionData.values.y = arenaSize * Math.random() - arenaSize;
+ public decideTeam(client: Client): TeamEntity {
+ const team = this.playerTeamMap.get(client) || randomFrom([this.blueTeamEntity, this.redTeamEntity, this.purpleTeamEntity, this.greenTeamEntity]);
+ this.playerTeamMap.set(client, team);
- const xOffset = (Math.random() - 0.5) * baseSize,
- yOffset = (Math.random() - 0.5) * baseSize;
+ return team;
+ }
+
+ public spawnPlayer(tank: TankBody, client: Client) {
+ const team = this.decideTeam(client);
+ TeamEntity.setTeam(team, tank);
- const base = this.playerTeamMap.get(client) || randomFrom([this.blueTeamBase, this.redTeamBase, this.greenTeamBase, this.purpleTeamBase]);
- tank.relationsData.values.team = base.relationsData.values.team;
- tank.styleData.values.color = base.styleData.values.color;
- tank.positionData.values.x = base.positionData.values.x + xOffset;
- tank.positionData.values.y = base.positionData.values.y + yOffset;
- this.playerTeamMap.set(client, base);
-
- if (client.camera) client.camera.relationsData.team = tank.relationsData.values.team;
+ const success = this.attemptFactorySpawn(tank);
+ if (success) return; // This player was spawned from a factory instead
+
+ const base = team.base;
+ if (!base) return super.spawnPlayer(tank, client);
+
+ const pos = getRandomPosition(base);
+ tank.positionData.x = pos.x;
+ tank.positionData.y = pos.y;
}
}
diff --git a/src/Misc/MazeGenerator.ts b/src/Misc/MazeGenerator.ts
index aae11c3..69d86b3 100644
--- a/src/Misc/MazeGenerator.ts
+++ b/src/Misc/MazeGenerator.ts
@@ -239,7 +239,7 @@ export default class MazeGenerator {
): MazeWall {
const { x: minX, y: minY } = this.scaleGridToArenaPosition(arena, gridX, gridY);
const { x: maxX, y: maxY } = this.scaleGridToArenaPosition(arena, gridX + gridW, gridY + gridH);
- return MazeWall.newFromBounds(arena.game, minX, minY, maxX, maxY);
+ return MazeWall.newFromBounds(arena, minX, minY, maxX, maxY);
}
/** Allows for easier (x, y) based getting of maze cells */
diff --git a/src/Native/Arena.ts b/src/Native/Arena.ts
index 80f4ff4..32f4950 100644
--- a/src/Native/Arena.ts
+++ b/src/Native/Arena.ts
@@ -26,13 +26,13 @@ import AbstractBoss from "../Entity/Boss/AbstractBoss";
import { VectorAbstract } from "../Physics/Vector";
import { ArenaGroup, TeamGroup } from "./FieldGroups";
import { Entity } from "./Entity";
-import { Color, ArenaFlags, CameraFlags, ValidScoreboardIndex } from "../Const/Enums";
-import { PI2, saveToLog } from "../util";
-import { TeamGroupEntity } from "../Entity/Misc/TeamEntity";
+import { Color, ArenaFlags, CameraFlags, Tank, ValidScoreboardIndex } from "../Const/Enums";
+import { PI2, randomFrom, saveToLog } from "../util";
+import { TeamEntity, TeamGroupEntity } from "../Entity/Misc/TeamEntity";
import Client from "../Client";
-import { countdownTicks, bossSpawningInterval, scoreboardUpdateInterval } from "../config";
+import { countdownDuration, bossSpawningInterval, factorySpawnChance, scoreboardUpdateInterval } from "../config";
export const enum ArenaState {
/** Countdown, waiting for players screen */
@@ -95,7 +95,7 @@ export default class ArenaEntity extends Entity implements TeamGroupEntity {
this.arenaData.values.flags = ArenaFlags.gameReadyStart;
this.arenaData.values.playersNeeded = 0;
- this.arenaData.values.ticksUntilStart = countdownTicks;
+ this.arenaData.values.ticksUntilStart = countdownDuration;
this.teamData.values.teamColor = Color.Neutral;
}
@@ -143,7 +143,7 @@ export default class ArenaEntity extends Entity implements TeamGroupEntity {
// If there is any tank within 1000 units, find a new position
const entity = this.game.entities.collisionManager.getFirstMatch(pos.x, pos.y, 1000, 1000, (entity) => {
- if (!TankBody.isTank(entity) || !AbstractBoss.isBoss(entity)) return false;
+ if (!(TankBody.isTank(entity) || AbstractBoss.isBoss(entity))) return false;
const dX = entity.positionData.values.x - pos.x;
const dY = entity.positionData.values.y - pos.y;
@@ -305,11 +305,44 @@ export default class ArenaEntity extends Entity implements TeamGroupEntity {
* Allows the arena to decide how players are spawned into the game.
*/
public spawnPlayer(tank: TankBody, client: Client) {
+ const success = this.attemptFactorySpawn(tank);
+ if (success) return; // This player was spawned from a factory instead
+
const { x, y } = this.findPlayerSpawnLocation();
tank.positionData.values.x = x;
tank.positionData.values.y = y;
}
+
+ public attemptFactorySpawn(tank: TankBody): boolean {
+ if (Math.random() > factorySpawnChance) return false;
+
+ const team = tank.relationsData.values.team;
+ if (!TeamEntity.isTeam(team)) return false;
+
+ const teammates = this.getTeamPlayers(team);
+ const factories: TankBody[] = [];
+
+ for (const teammate of teammates) {
+ if (teammate.currentTank === Tank.Factory && !teammate.deletionAnimation) {
+ factories.push(teammate);
+ }
+ }
+
+ if (factories.length === 0) return false; // No available factories on this team, spawn as usual
+
+ const factory = randomFrom(factories);
+
+ const { x, y } = factory.getWorldPosition();
+ const barrel = factory.barrels[0];
+ const shootAngle = barrel.definition.angle + factory.positionData.values.angle;
+
+ tank.positionData.values.x = x + (Math.cos(shootAngle) * barrel.physicsData.values.size) - Math.sin(shootAngle) * barrel.definition.offset * factory.sizeFactor;
+ tank.positionData.values.y = y + (Math.sin(shootAngle) * barrel.physicsData.values.size) + Math.cos(shootAngle) * barrel.definition.offset * factory.sizeFactor;
+ tank.addVelocity(shootAngle, 25);
+
+ return true;
+ }
/**
* Closes the arena.
diff --git a/src/Native/Camera.ts b/src/Native/Camera.ts
index fbb72a0..364934d 100644
--- a/src/Native/Camera.ts
+++ b/src/Native/Camera.ts
@@ -37,6 +37,9 @@ import { maxPlayerLevel } from "../config";
export class CameraEntity extends Entity {
/** Always existant camera field group. Present in all GUI/camera entities. */
public cameraData: CameraGroup = new CameraGroup(this);
+
+ /** Always existant relations field group. Present in all GUI/camera entities. */
+ public relationsData: RelationsGroup = new RelationsGroup(this);
/** The current size of the tank the camera is in charge of. Calculated with level stuff */
public sizeFactor: number = 1;
@@ -122,9 +125,6 @@ export default class ClientCamera extends CameraEntity {
/** All entities in the view of the camera. Represented by id. */
private view: Entity[] = [];
- /** Always existant relations field group. Present in all GUI/camera entities. */
- public relationsData: RelationsGroup = new RelationsGroup(this);
-
/** Calculates the amount of stats available at a specific level. */
public static calculateStatCount(level: number) {
if (level <= 0) return 0;
diff --git a/src/config.ts b/src/config.ts
index 0d44cdb..faffafd 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -44,11 +44,14 @@ export const host: string = process.env.SERVER_INFO || "unknown";
export const mode: string = process.env.NODE_ENV || "production";
/** How long the countdown should last until the game is started. By default it is 10 seconds. Set to 0 if you wish to disable this. */
-export const countdownTicks = 10 * tps;
+export const countdownDuration = 10 * tps;
-/** Chance for a shape to spawn as shiny (green) */
+/** Chance for a shape to spawn as shiny (green). */
export const shinyChance: number = 1 / 1_000_000;
+/** Chance for a player to spawn out of an allied factory. */
+export const factorySpawnChance: number = 0.05;
+
/** Is hosting a rest api */
export const enableApi: boolean = true;
diff --git a/src/util.ts b/src/util.ts
index 1fc1042..4270ba0 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -19,6 +19,8 @@
import chalk from "chalk";
import { inspect } from "util";
import { doVerboseLogs } from "./config";
+import { VectorAbstract } from "./Physics/Vector";
+import ObjectEntity from "./Entity/Object";
/** Logs data prefixed with the Date. */
export const log = (...args: any[]) => {
@@ -71,16 +73,41 @@ export const constrain = (value: number, min: number, max: number): number => {
return Math.max(min, Math.min(max, value));
}
-/** 2π */
+/** 2PI / TAU */
export const PI2 = Math.PI * 2;
/**
- * Normalize angle (ex: 4π-> 0π, 3π -> 1π)
+ * Normalize angle (ex: 4PI-> 0PI, 3PI -> 1PI)
*/
export const normalizeAngle = (angle: number): number => {
return ((angle % PI2) + PI2) % PI2;
}
+/**
+ * Returns a random position inside the given entity. Might not work correctly with attach entities
+ */
+export const getRandomPosition = (entity: ObjectEntity): VectorAbstract => {
+ const pos = entity.getWorldPosition();
+
+ const isRect = entity.physicsData.values.sides === 2;
+
+ if (isRect) { // Rectangular hitbox
+ const xOffset = (Math.random() - 0.5) * entity.physicsData.values.size,
+ yOffset = (Math.random() - 0.5) * entity.physicsData.values.width;
+
+ pos.x += xOffset;
+ pos.y += yOffset;
+ } else { // Circular hitbox
+ const radius = Math.random() * entity.physicsData.values.size;
+ const angle = Math.random() * PI2;
+
+ pos.x += Math.cos(angle) * radius;
+ pos.y += Math.sin(angle) * radius;
+ }
+
+ return pos;
+}
+
/**
* Logs - Used to have a webhook log here
*/