import { describe, expect, it } from 'vitest'; import { createUnitBodies } from './unitBodies'; import { createPhysics } from './physics/physics'; import { createUnits, type Unit, type UnitState } from './sim/units'; import { generateWorld } from './sim/world'; const world = generateWorld(1, 0); function place(state: UnitState, overrides: Partial): Unit { const unit: Unit = { id: state.nextId++, kind: 'soldier', faction: 'enemy', role: 'fighter', x: 0, z: 0, heading: 0, speed: 0, hp: 30, path: [], expires: 999, assigned: null, onStation: 0, cooldown: 0, elevation: 1.2, ...overrides, }; state.units.push(unit); return unit; } describe('running people over', () => { it('puts down someone you drive through at speed', async () => { const physics = await createPhysics(world); const contacts = createUnitBodies(physics); const units = createUnits(); const victim = place(units, { x: 1, z: 0 }); const events = contacts.sync(units, { x: 0, z: 0 }, 14, 1 / 60); expect(events.ranOver).toContain(victim); expect(victim.hp).toBe(0); }); it('does not hurt anyone you creep past', async () => { const physics = await createPhysics(world); const contacts = createUnitBodies(physics); const units = createUnits(); const bystander = place(units, { x: 1, z: 0 }); // Walking pace. Brushing past somebody is not running them over. const events = contacts.sync(units, { x: 0, z: 0 }, 1.5, 1 / 60); expect(events.ranOver).toHaveLength(0); expect(bystander.hp).toBe(30); }); it('leaves people you merely drove near alone', async () => { const physics = await createPhysics(world); const contacts = createUnitBodies(physics); const units = createUnits(); const bystander = place(units, { x: 9, z: 0 }); expect(contacts.sync(units, { x: 0, z: 0 }, 25, 1 / 60).ranOver).toHaveLength(0); expect(bystander.hp).toBe(30); }); it('gives people no collider, so the car is never hung up on a crowd', async () => { const physics = await createPhysics(world); const contacts = createUnitBodies(physics); const units = createUnits(); for (let i = 0; i < 12; i++) place(units, { x: i * 2, z: 4 }); contacts.sync(units, { x: 0, z: 0 }, 0, 1 / 60); expect(contacts.count).toBe(0); }); }); describe('hitting other vehicles', () => { const car = (state: UnitState, at: { x: number; z: number }) => place(state, { kind: 'car', role: 'traffic', faction: 'civilian', hp: 60, ...at }); it('gives nearby vehicles a body to collide with', async () => { const physics = await createPhysics(world); const contacts = createUnitBodies(physics); const units = createUnits(); car(units, { x: 12, z: 0 }); const before = physics.rapier.bodies.len(); contacts.sync(units, { x: 0, z: 0 }, 0, 1 / 60); expect(contacts.count).toBe(1); expect(physics.rapier.bodies.len()).toBe(before + 1); }); it('damages a vehicle you ram, and not one you pull up behind', async () => { const physics = await createPhysics(world); const contacts = createUnitBodies(physics); const units = createUnits(); const rammed = car(units, { x: 3, z: 0 }); const parked = car(units, { x: 60, z: 0 }); for (let i = 0; i < 30; i++) contacts.sync(units, { x: 0, z: 0 }, 20, 1 / 60); expect(rammed.hp).toBeLessThan(60); expect(parked.hp).toBe(60); }); it('does not damage a vehicle you are simply sitting next to', async () => { const physics = await createPhysics(world); const contacts = createUnitBodies(physics); const units = createUnits(); const neighbour = car(units, { x: 3, z: 0 }); for (let i = 0; i < 60; i++) contacts.sync(units, { x: 0, z: 0 }, 1, 1 / 60); expect(neighbour.hp).toBe(60); }); it('takes bodies away again as traffic drives out of range', async () => { const physics = await createPhysics(world); const contacts = createUnitBodies(physics); const units = createUnits(); const passing = car(units, { x: 20, z: 0 }); contacts.sync(units, { x: 0, z: 0 }, 0, 1 / 60); const withBody = physics.rapier.bodies.len(); expect(contacts.count).toBe(1); passing.x = 900; contacts.sync(units, { x: 0, z: 0 }, 0, 1 / 60); expect(contacts.count).toBe(0); expect(physics.rapier.bodies.len()).toBe(withBody - 1); }); it('does not leak bodies as traffic comes and goes', async () => { const physics = await createPhysics(world); const contacts = createUnitBodies(physics); const units = createUnits(); const base = physics.rapier.bodies.len(); for (let cycle = 0; cycle < 5; cycle++) { units.units = []; for (let i = 0; i < 6; i++) car(units, { x: 10 + i * 6, z: 0 }); contacts.sync(units, { x: 0, z: 0 }, 0, 1 / 60); expect(contacts.count).toBe(6); units.units = []; contacts.sync(units, { x: 0, z: 0 }, 0, 1 / 60); expect(physics.rapier.bodies.len()).toBe(base); } }); });