Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion src/Entity/Misc/Mothership.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { ClientInputs } from "../../Client";
import { tps } from "../../config";
import { Color, Tank, Stat, ColorsHexCode, ClientBound, TeamFlags } from "../../Const/Enums";
import GameServer from "../../Game";
import ArenaEntity, { ArenaState } from "../../Native/Arena";
import { ArenaState } from "../../Native/Arena";
import { CameraEntity } from "../../Native/Camera";
import { AI, AIState, Inputs } from "../AI";
import Live from "../Live";
Expand Down
19 changes: 13 additions & 6 deletions src/Native/Manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,16 +179,23 @@ export default class EntityManager {
this.collisionManager.forEachCollisionPair(this.handleCollision);

for (let id = 0; id <= this.lastId; ++id) {
const entity = this.inner[id] as ObjectEntity;
const entity = this.inner[id];

// Alternatively, Entity.exists(entity), though this is probably faster.
if (!entity || entity.hash === 0) continue;

if (entity.isPhysical) {
entity.applyPhysics();
}

if (!entity.isChild) {
if (entity.cameraData) { // Cameras are ticked later
continue;
} else if (ObjectEntity.isObject(entity)) {
if (entity.isPhysical) {
entity.applyPhysics();
}

if (!entity.isChild) {
entity.tick(tick);
}
} else {
// Not an object entity (arena)
entity.tick(tick);
}
}
Expand Down
51 changes: 22 additions & 29 deletions src/Physics/HashGrid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default class HashGrid implements CollisionManager {
private hashMap: number[][] = [];
private gameLeftX: number = 0;
private gameTopY: number = 0
private collisionPairsSeen = new Uint32Array(MAX_ENTITY_COUNT * MAX_ENTITY_COUNT / 32)
private collisionPairsSeen = new Uint32Array(MAX_ENTITY_COUNT * (MAX_ENTITY_COUNT - 1) / 2 / 32)

public constructor(game: GameServer) {
this.game = game;
Expand All @@ -50,7 +50,7 @@ export default class HashGrid implements CollisionManager {
public preTick(tick: number): void {
const widthInCells = (this.game.arena.width + (CELL_SIZE - 1)) >> CELL_SHIFT;
const heightInCells = (this.game.arena.height + (CELL_SIZE - 1)) >> CELL_SHIFT;
this.hashMul = widthInCells >> CELL_SHIFT;
this.hashMul = widthInCells;
this.hashMap = Array(widthInCells * heightInCells);
this.queryIdMap.fill(0);
this.lastQueryId = 0;
Expand All @@ -65,7 +65,7 @@ export default class HashGrid implements CollisionManager {
}

public insert(entity: ObjectEntity) {
if (this.isLocked) throw new Error("HashGrid is locked! Cannot insert entity outside of tick");
if (this.isLocked) throw new Error("HashGrid is locked! Cannot insert() entity outside of tick");
const { sides, size, width } = entity.physicsData.values;
const { x, y } = entity.positionData.values;
const isLine = sides === 2;
Expand Down Expand Up @@ -98,7 +98,7 @@ export default class HashGrid implements CollisionManager {
halfWidth: number,
halfHeight: number
): PackedEntitySet {
if (this.isLocked) throw new Error("HashGrid is locked! Cannot insert entity outside of tick");
if (this.isLocked) throw new Error("HashGrid is locked! Cannot retrieve() entity outside of tick");
const result = this.resultSet;
result.clear();

Expand Down Expand Up @@ -140,7 +140,7 @@ export default class HashGrid implements CollisionManager {
halfHeight: number,
predicate: (entity: ObjectEntity) => boolean
): ObjectEntity | null {
if (this.isLocked) throw new Error("HashGrid is locked! Cannot insert entity outside of tick");
if (this.isLocked) throw new Error("HashGrid is locked! Cannot getFirstMatch() outside of tick");

const startX = (centerX - halfWidth - this.gameLeftX) >> CELL_SHIFT;
const startY = (centerY - halfHeight - this.gameTopY) >> CELL_SHIFT;
Expand Down Expand Up @@ -180,9 +180,9 @@ export default class HashGrid implements CollisionManager {

// No longer used
public retrieveEntitiesByEntity(entity: ObjectEntity): PackedEntitySet {
if (this.isLocked) throw new Error("HashGrid is locked! Cannot insert entity outside of tick");
if (this.isLocked) throw new Error("HashGrid is locked! Cannot retrieveEntitiesByEntity() outside of tick");
const { sides, size, width } = entity.physicsData.values;
const { x, y } = entity.positionData;
const { x, y } = entity.positionData.values;
const isLine = sides === 2;
const halfWidth = isLine ? size / 2 : size;
const halfHeight = isLine ? width / 2 : size;
Expand All @@ -192,7 +192,7 @@ export default class HashGrid implements CollisionManager {
public forEachCollisionPair(
callback: (entityA: ObjectEntity, entityB: ObjectEntity) => void
): void {
if (this.isLocked) throw new Error("HashGrid is locked! Cannot insert entity outside of tick");
if (this.isLocked) throw new Error("HashGrid is locked! Cannot forEachCollisionPair() entity outside of tick");
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message says "Cannot forEachCollisionPair() entity outside of tick" but it should probably be "Cannot call forEachCollisionPair() outside of tick" (without the word "entity") to match the pattern of the other error messages and be grammatically correct.

Suggested change
if (this.isLocked) throw new Error("HashGrid is locked! Cannot forEachCollisionPair() entity outside of tick");
if (this.isLocked) throw new Error("HashGrid is locked! Cannot call forEachCollisionPair() outside of tick");

Copilot uses AI. Check for mistakes.

const collisionsSeen = this.collisionPairsSeen;
collisionsSeen.fill(0);
Expand All @@ -213,27 +213,20 @@ export default class HashGrid implements CollisionManager {
const entityB = this.game.entities.inner[eidB] as ObjectEntity;
if (!entityB || entityB.hash === 0) continue;

if (eidA < eidB) {
// Prevent extra-cell duplicates
const pairHash = (eidA << MAX_ENTITY_ID_BITS) | eidB;
const pairHashIndex = pairHash >>> 5;
const pairHashBit = 1 << (pairHash & 31);
if ((collisionsSeen[pairHashIndex] & pairHashBit) !== 0) continue;
collisionsSeen[pairHashIndex] |= pairHashBit;

// Ensure (x, y) -> x.id < y.id
callback(entityA, entityB);
} else {
// Prevent extra-cell duplicates
const pairHash = (eidB << MAX_ENTITY_ID_BITS) | eidA;
const pairHashIndex = pairHash >>> 5;
const pairHashBit = 1 << (pairHash & 31);
if ((collisionsSeen[pairHashIndex] & pairHashBit) !== 0) continue;
collisionsSeen[pairHashIndex] |= pairHashBit;

// Ensure (x, y) -> x.id < y.id
callback(entityB, entityA);
}
// Ensure eidA < eidB for triangular matrix indexing
const [idA, idB] = eidA < eidB ? [eidA, eidB] : [eidB, eidA];
const [entA, entB] = eidA < eidB ? [entityA, entityB] : [entityB, entityA];

// Triangular matrix index: row * (row - 1) / 2 + col, where row > col
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says "row * (row - 1) / 2 + col" but the actual formula uses idB and idA. For clarity, consider updating the comment to match the variable names: "Triangular matrix index: idB * (idB - 1) / 2 + idA, where idB > idA".

Suggested change
// Triangular matrix index: row * (row - 1) / 2 + col, where row > col
// Triangular matrix index: idB * (idB - 1) / 2 + idA, where idB > idA

Copilot uses AI. Check for mistakes.
const triangularIndex = Math.floor(idB * (idB - 1) / 2) + idA;
const arrayIndex = triangularIndex >>> 5;
const bitIndex = triangularIndex & 31;
const bitMask = 1 << bitIndex;

if ((collisionsSeen[arrayIndex] & bitMask) !== 0) continue;
collisionsSeen[arrayIndex] |= bitMask;

callback(entA, entB);
}
}
}
Expand Down