Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
e9d5391
factory test
c86ec23b-fef1-4979-b2fa-b9adc351b8cc Dec 13, 2025
5376660
Update Arena.ts
c86ec23b-fef1-4979-b2fa-b9adc351b8cc Dec 14, 2025
9f91b92
Fix boss death notif
c86ec23b-fef1-4979-b2fa-b9adc351b8cc Dec 14, 2025
838e2ac
broadcast msg function
c86ec23b-fef1-4979-b2fa-b9adc351b8cc Dec 14, 2025
8ebcf3a
whoops
c86ec23b-fef1-4979-b2fa-b9adc351b8cc Dec 15, 2025
a2f5668
typescript
c86ec23b-fef1-4979-b2fa-b9adc351b8cc Dec 15, 2025
2a9eb0d
safety
c86ec23b-fef1-4979-b2fa-b9adc351b8cc Dec 15, 2025
c8cc8ed
fix crash
c86ec23b-fef1-4979-b2fa-b9adc351b8cc Dec 15, 2025
2984146
update color properly
c86ec23b-fef1-4979-b2fa-b9adc351b8cc Dec 15, 2025
d190a07
admin fun cmd
c86ec23b-fef1-4979-b2fa-b9adc351b8cc Dec 15, 2025
255230b
tag cleanup
c86ec23b-fef1-4979-b2fa-b9adc351b8cc Dec 15, 2025
f3eb97e
Crash fix
c86ec23b-fef1-4979-b2fa-b9adc351b8cc Dec 20, 2025
569eca5
speedup AI slightly
c86ec23b-fef1-4979-b2fa-b9adc351b8cc Dec 27, 2025
b547697
move getRandomPosition to util
c86ec23b-fef1-4979-b2fa-b9adc351b8cc Dec 27, 2025
e100155
Fix maze wall team
c86ec23b-fef1-4979-b2fa-b9adc351b8cc Dec 28, 2025
e4eb26f
factory spawn chance tweak
c86ec23b-fef1-4979-b2fa-b9adc351b8cc Dec 29, 2025
d12f298
Fix spawns
c86ec23b-fef1-4979-b2fa-b9adc351b8cc Jan 4, 2026
2b08355
Fix 4 teams base locations
c86ec23b-fef1-4979-b2fa-b9adc351b8cc Jan 9, 2026
1d86e23
typo
c86ec23b-fef1-4979-b2fa-b9adc351b8cc Jan 13, 2026
1c269a1
fix randompos for circles
c86ec23b-fef1-4979-b2fa-b9adc351b8cc Jan 16, 2026
cd13863
Address code review feedback: remove unused imports, fix validation, …
Copilot Jan 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
51 changes: 40 additions & 11 deletions src/Const/Commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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",
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -194,23 +209,23 @@ export const commandCallbacks = {
game_set_level: (client: Client, levelArg: string) => {
const level = parseInt(levelArg);
const player = client.camera?.cameraData.player;
if (isNaN(level) || !Entity.exists(player) || !TankBody.isTank(player)) return;
if (!isFinite(level) || !Entity.exists(player) || !TankBody.isTank(player)) return;
const finalLevel = client.accessLevel == AccessLevel.FullAccess ? level : Math.min(maxPlayerLevel, level);
client.camera?.setLevel(finalLevel);
},
game_set_score: (client: Client, scoreArg: string) => {
const score = parseInt(scoreArg);
const camera = client.camera?.cameraData;
const player = client.camera?.cameraData.player;
if (isNaN(score) || score > Number.MAX_SAFE_INTEGER || score < Number.MIN_SAFE_INTEGER || !Entity.exists(player) || !TankBody.isTank(player) || !camera) return;
if (!isFinite(score) || score > Number.MAX_SAFE_INTEGER || score < Number.MIN_SAFE_INTEGER || !Entity.exists(player) || !TankBody.isTank(player) || !camera) return;
camera.score = score;
},
game_set_stat_max: (client: Client, statIdArg: string, statMaxArg: string) => {
const statId = StatCount - parseInt(statIdArg);
const statMax = parseInt(statMaxArg);
const camera = client.camera?.cameraData;
const player = client.camera?.cameraData.player;
if (statId < 0 || statId >= StatCount || isNaN(statId) || isNaN(statMax) || !Entity.exists(player) || !TankBody.isTank(player) || !camera) return;
if (statId < 0 || statId >= StatCount || !isFinite(statId) || !isFinite(statMax) || !Entity.exists(player) || !TankBody.isTank(player) || !camera) return;
const clampedStatMax = Math.max(statMax, 0);
camera.statLimits[statId as Stat] = clampedStatMax;
camera.statLevels[statId as Stat] = Math.min(camera.statLevels[statId as Stat], clampedStatMax);
Expand All @@ -220,22 +235,27 @@ export const commandCallbacks = {
const statPoints = parseInt(statPointsArg);
const camera = client.camera?.cameraData;
const player = client.camera?.cameraData.player;
if (statId < 0 || statId >= StatCount || isNaN(statId) || isNaN(statPoints) || !Entity.exists(player) || !TankBody.isTank(player) || !camera) return;
if (statId < 0 || statId >= StatCount || !isFinite(statId) || !isFinite(statPoints) || !Entity.exists(player) || !TankBody.isTank(player) || !camera) return;
camera.statLevels[statId as Stat] = statPoints;
},
game_add_upgrade_points: (client: Client, pointsArg: string) => {
const points = parseInt(pointsArg);
const camera = client.camera?.cameraData;
const player = client.camera?.cameraData.player;
if (isNaN(points) || points > Number.MAX_SAFE_INTEGER || points < Number.MIN_SAFE_INTEGER || !Entity.exists(player) || !TankBody.isTank(player) || !camera) return;
if (!isFinite(points) || points > Number.MAX_SAFE_INTEGER || points < Number.MIN_SAFE_INTEGER || !Entity.exists(player) || !TankBody.isTank(player) || !camera) return;
camera.statsAvailable += points;
},
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;

if (!isFinite(x) || !isFinite(y)) return;

player.positionData.x = x;
player.positionData.y = y;
player.setVelocity(0, 0);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -326,11 +355,11 @@ export const commandCallbacks = {
["Triangle", Triangle]
] as [string, typeof ObjectEntity][]).get(entityArg);

if (isNaN(count) || count < 0 || !game || !TEntity) return;
if (!isFinite(count) || count < 0 || !game || !TEntity) return;

for (let i = 0; i < count; ++i) {
const boss = new TEntity(game);
if (!isNaN(x) && !isNaN(y)) {
if (isFinite(x) && isFinite(y)) {
boss.positionData.x = x;
boss.positionData.y = y;
}
Expand Down
8 changes: 4 additions & 4 deletions src/Entity/AI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -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
Expand All @@ -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. */
Expand Down
6 changes: 2 additions & 4 deletions src/Entity/Boss/AbstractBoss.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
13 changes: 7 additions & 6 deletions src/Entity/Misc/MazeWall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,17 @@
along with this program. If not, see <https://www.gnu.org/licenses/>
*/

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,
Expand All @@ -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();

Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/Entity/Misc/Mothership.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
4 changes: 3 additions & 1 deletion src/Entity/Misc/TeamBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
/**
Expand All @@ -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;

Expand Down
18 changes: 18 additions & 0 deletions src/Entity/Misc/TeamEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}
}
}
10 changes: 7 additions & 3 deletions src/Entity/Object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down
Loading