Skip to content

Commit

Permalink
refactor+fix: move some hitbox code around and fix import cycle
Browse files Browse the repository at this point in the history
  • Loading branch information
leia-uwu committed Jan 8, 2025
1 parent 5f1f6c8 commit 85f37d2
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 126 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,5 @@ jobs:
- name: Install Packages
run: bun install --frozen-lockfile

- name: Tests
- name: Format and Lint
run: bun lint:ci
2 changes: 1 addition & 1 deletion client/src/game/bullet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export class ClientBullet extends BaseBullet {
let tint = 0xff0000;
if (wall) {
tint = wall.color;
} else if (entity && entity.__type == EntityType.Obstacle) {
} else if (entity && entity.__type === EntityType.Obstacle) {
const def = ObstacleDefs.typeToDef((entity as Obstacle).type);
tint = def.img.tint ?? tint;
}
Expand Down
2 changes: 1 addition & 1 deletion common/src/net.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ export class GameBitStream extends BitStream {
* @param length The amount of bytes to copy
*/
writeBytes(src: GameBitStream, offset: number, length: number): void {
assert(this.index % 8 == 0, "WriteBytes: stream must be byte aligned");
assert(this.index % 8 === 0, "WriteBytes: stream must be byte aligned");

const data = new Uint8Array(src.view.view.buffer, offset, length);
this.view.view.set(data, this.index / 8);
Expand Down
77 changes: 77 additions & 0 deletions common/src/utils/collisionHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { BaseGameMap, BaseMapObject, MapObjectType } from "../baseMap";
import { EntityType } from "../constants";
import { Hitbox } from "./hitbox";
import { Vec2, Vector } from "./vector";

interface Entity {
__type: EntityType;
id: number;
position: Vector;
hitbox: Hitbox;
}

interface LineOfSightResponse {
entity?: Entity;
wall?: BaseMapObject;
normal?: Vector;
position: Vector;
distance: number;
originalDistance: number;
}

export const CollisionHelpers = {
lineOfSightCheck(
entities: Iterable<Entity>,
map: BaseGameMap,
pointA: Vector,
pointB: Vector,
entitesToCollide: EntityType[],
entityId = 0,
): LineOfSightResponse {
let originalDist = Vec2.distanceSqrt(pointA, pointB);

let res: LineOfSightResponse = {
position: pointB,
distance: originalDist,
originalDistance: originalDist,
};

const objects = map.intersectLineSegment(pointA, pointB);

for (const wall of objects) {
if (wall.type !== MapObjectType.Wall) continue;

const intersection = wall.hitbox.intersectsLine(pointA, pointB);
if (intersection) {
const intersectionDist = Vec2.distanceSqrt(pointA, intersection.point);
if (intersectionDist < res.distance) {
res.normal = intersection.normal;
res.position = intersection.point;
res.distance = intersectionDist;
res.wall = wall;
}
}
}

for (const entity of entities) {
if (!entitesToCollide.includes(entity.__type)) continue;
if (entity.id === entityId) continue;

const intersection = entity.hitbox.intersectsLine(pointA, pointB);
if (intersection) {
const intersectionDist = Vec2.distanceSqrt(pointA, intersection.point);
if (intersectionDist < res.distance) {
res.normal = intersection.normal;
res.position = intersection.point;
res.distance = intersectionDist;
res.wall = undefined;
res.entity = entity;
}
}
}

res.distance = Math.sqrt(res.distance);

return res;
},
};
170 changes: 48 additions & 122 deletions common/src/utils/hitbox.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { type BaseGameMap, type BaseMapObject, MapObjectType } from "../baseMap";
import type { EntityType } from "../constants";
import { Collision, type IntersectionResponse, type LineIntersection } from "./collision";
import { MathUtils } from "./math";
import { assert } from "./util";
Expand Down Expand Up @@ -57,7 +55,11 @@ export abstract class BaseHitbox<T extends HitboxType = HitboxType> {
* @return `true` if both {@link Hitbox}es collide
*/
collidesWith(that: Hitbox): boolean {
return CollisionHelpers.hitboxCheck(this as unknown as Hitbox, that);
const collisionFn = checkFunctions[this.type][that.type];
assert(collisionFn, `${this.type} doesn't support check with ${that.type}`);
return collisionFn.reverse
? collisionFn.fn(that, this as unknown as Hitbox)
: collisionFn.fn(this as unknown as Hitbox, that);
}

/**
Expand All @@ -67,7 +69,19 @@ export abstract class BaseHitbox<T extends HitboxType = HitboxType> {
* @return The intersection response with direction normal and penetration depth
*/
getIntersection(that: Hitbox): IntersectionResponse {
return CollisionHelpers.hitboxIntersection(this as unknown as Hitbox, that);
const collisionFn = intersectionFunctions[this.type][that.type];
assert(
collisionFn,
`${this.type} doesn't support intersection with ${that.type}`,
);

let response = collisionFn.reverse
? collisionFn.fn(that, this as unknown as Hitbox)
: collisionFn.fn(this as unknown as Hitbox, that);
if (response && collisionFn.reverse) {
response.normal = Vec2.neg(response.normal);
}
return response;
}

/**
Expand All @@ -77,7 +91,9 @@ export abstract class BaseHitbox<T extends HitboxType = HitboxType> {
* @return An intersection response containing the intersection position and normal
*/
intersectsLine(a: Vector, b: Vector): LineIntersection {
return CollisionHelpers.lineHitboxIntersection(this as unknown as Hitbox, a, b);
const intersectionFn = lineIntersectionFunctions[this.type];
assert(intersectionFn, `Hitbox ${this.type} doens't support line intersection`);
return intersectionFn(this as unknown as Hitbox, a, b);
}

/**
Expand Down Expand Up @@ -350,25 +366,30 @@ const checkFunctions: Array<
Array<{ fn: (a: Hitbox, b: Hitbox) => boolean; reverse: boolean }>
> = [];

function setCheckFn<A extends HitBoxCtr, B extends HitBoxCtr>(
function setCheckFn<
A extends HitBoxCtr,
B extends HitBoxCtr,
AInst = Hitbox & { type: A["type"] },
BInst = Hitbox & { type: B["type"] },
>(
hitboxA: A,
hitboxB: B,
fn: (a: Hitbox & { type: A["type"] }, b: Hitbox & { type: B["type"] }) => boolean,
fn: (a: AInst, b: BInst) => boolean,
) {
const setFunction = (
typeA: HitboxType,
typeB: HitboxType,
fn: (a: Hitbox & { type: A["type"] }, b: Hitbox & { type: B["type"] }) => boolean,
fn: (a: AInst, b: BInst) => boolean,
reverse: boolean,
) => {
checkFunctions[typeA] = checkFunctions[typeA] || [];
checkFunctions[typeA] ??= [];
checkFunctions[typeA][typeB] = {
fn,
fn: fn as (a: Hitbox, B: Hitbox) => boolean,
reverse,
};
};
setFunction(hitboxA.type, hitboxB.type, fn, false);
if (hitboxA.type != hitboxB.type) {
if (hitboxA.type !== hitboxB.type) {
setFunction(hitboxB.type, hitboxA.type, fn, true);
}
}
Expand All @@ -390,34 +411,42 @@ setCheckFn(PolygonHitbox, PolygonHitbox, (a, b) => {
});

const intersectionFunctions: Array<
Array<{ fn: (a: Hitbox, b: Hitbox) => IntersectionResponse; reverse: boolean }>
Array<{
fn: (a: Hitbox, b: Hitbox) => IntersectionResponse;
reverse: boolean;
}>
> = [];

function setIntersectionFn<A extends HitBoxCtr, B extends HitBoxCtr>(
function setIntersectionFn<
A extends HitBoxCtr,
B extends HitBoxCtr,
AInst = Hitbox & { type: A["type"] },
BInst = Hitbox & { type: B["type"] },
>(
hitboxA: A,
hitboxB: B,
fn: (
a: Hitbox & { type: A["type"] },
b: Hitbox & { type: B["type"] },
a: AInst,
b: BInst,
) => IntersectionResponse,
) {
const setFunction = (
typeA: HitboxType,
typeB: HitboxType,
fn: (
a: Hitbox & { type: A["type"] },
b: Hitbox & { type: B["type"] },
a: AInst,
b: BInst,
) => IntersectionResponse,
reverse: boolean,
) => {
intersectionFunctions[typeA] = intersectionFunctions[typeA] || [];
intersectionFunctions[typeA] ??= [];
intersectionFunctions[typeA][typeB] = {
fn: fn as (a: Hitbox, B: Hitbox) => IntersectionResponse,
reverse,
};
};
setFunction(hitboxA.type, hitboxB.type, fn, false);
if (hitboxA.type != hitboxB.type) {
if (hitboxA.type !== hitboxB.type) {
setFunction(hitboxB.type, hitboxA.type, fn, true);
}
}
Expand Down Expand Up @@ -477,106 +506,3 @@ setLineIntersectionFn(RectHitbox, (hitbox, a, b) => {
setLineIntersectionFn(PolygonHitbox, (hitbox, a, b) => {
return Collision.lineIntersectsPolygon(a, b, hitbox.verts);
});

interface Entity {
__type: EntityType;
id: number;
position: Vector;
hitbox: Hitbox;
}

interface LineOfSightResponse {
entity?: Entity;
wall?: BaseMapObject;
normal?: Vector;
position: Vector;
distance: number;
originalDistance: number;
}

export const CollisionHelpers = {
hitboxCheck(hitboxA: Hitbox, hitboxB: Hitbox): boolean {
const collisionFn = checkFunctions[hitboxA.type][hitboxB.type];
assert(collisionFn, `${hitboxA.type} doesn't support check with ${hitboxB.type}`);
return collisionFn.reverse
? collisionFn.fn(hitboxB, hitboxA)
: collisionFn.fn(hitboxA, hitboxB);
},

hitboxIntersection(hitboxA: Hitbox, hitboxB: Hitbox): IntersectionResponse {
const collisionFn = intersectionFunctions[hitboxA.type][hitboxB.type];
assert(
collisionFn,
`${hitboxA.type} doesn't support intersection with ${hitboxB.type}`,
);

let response = collisionFn.reverse
? collisionFn.fn(hitboxB, hitboxA)
: collisionFn.fn(hitboxA, hitboxB);
if (response && collisionFn.reverse) {
response.normal = Vec2.neg(response.normal);
}
return response;
},

lineHitboxIntersection(hitbox: Hitbox, a: Vector, b: Vector): LineIntersection {
const intersectionFn = lineIntersectionFunctions[hitbox.type];
assert(intersectionFn, `Hitbox ${hitbox.type} doens't support line intersection`);
return intersectionFn(hitbox, a, b);
},

lineOfSightCheck(
entities: Iterable<Entity>,
map: BaseGameMap,
pointA: Vector,
pointB: Vector,
entitesToCollide: EntityType[],
entityId = 0,
): LineOfSightResponse {
let originalDist = Vec2.distanceSqrt(pointA, pointB);

let res: LineOfSightResponse = {
position: pointB,
distance: originalDist,
originalDistance: originalDist,
};

const objects = map.intersectLineSegment(pointA, pointB);

for (const wall of objects) {
if (wall.type !== MapObjectType.Wall) continue;

const intersection = wall.hitbox.intersectsLine(pointA, pointB);
if (intersection) {
const intersectionDist = Vec2.distanceSqrt(pointA, intersection.point);
if (intersectionDist < res.distance) {
res.normal = intersection.normal;
res.position = intersection.point;
res.distance = intersectionDist;
res.wall = wall;
}
}
}

for (const entity of entities) {
if (!entitesToCollide.includes(entity.__type)) continue;
if (entity.id === entityId) continue;

const intersection = entity.hitbox.intersectsLine(pointA, pointB);
if (intersection) {
const intersectionDist = Vec2.distanceSqrt(pointA, intersection.point);
if (intersectionDist < res.distance) {
res.normal = intersection.normal;
res.position = intersection.point;
res.distance = intersectionDist;
res.wall = undefined;
res.entity = entity;
}
}
}

res.distance = Math.sqrt(res.distance);

return res;
},
};
3 changes: 2 additions & 1 deletion server/src/explosion.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { EntityType } from "../../common/src/constants";
import { type ExplosionDefKey, ExplosionDefs } from "../../common/src/defs/explosionDefs";
import { CircleHitbox, CollisionHelpers } from "../../common/src/utils/hitbox";
import { CollisionHelpers } from "../../common/src/utils/collisionHelpers";
import { CircleHitbox } from "../../common/src/utils/hitbox";
import { MathUtils } from "../../common/src/utils/math";
import { Vec2, type Vector } from "../../common/src/utils/vector";
import type { Player } from "./entities/player";
Expand Down

0 comments on commit 85f37d2

Please sign in to comment.