feat(api): add hp_regen_since column for persistent HP regeneration
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,12 @@ export class Character {
|
|||||||
@Column({ name: 'current_hp', type: 'integer' })
|
@Column({ name: 'current_hp', type: 'integer' })
|
||||||
currentHp!: number;
|
currentHp!: number;
|
||||||
|
|
||||||
|
// `current_hp` is only exact while this is null (regeneration paused, e.g.
|
||||||
|
// mid-combat). Otherwise it's the HP as of this timestamp -- read it
|
||||||
|
// through CharacterVitalsService.effectiveHp(), never directly.
|
||||||
|
@Column({ name: 'hp_regen_since', type: 'timestamptz', nullable: true })
|
||||||
|
hpRegenSince!: Date | null;
|
||||||
|
|
||||||
@Column({ name: 'current_location_id', type: 'uuid' })
|
@Column({ name: 'current_location_id', type: 'uuid' })
|
||||||
currentLocationId!: string;
|
currentLocationId!: string;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class AddHpRegeneration1792000000000 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
'ALTER TABLE "characters" ADD COLUMN "hp_regen_since" TIMESTAMP WITH TIME ZONE',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Existing characters start regenerating immediately from their current
|
||||||
|
// HP. A character whose fight is still ACTIVE keeps regeneration paused
|
||||||
|
// until that fight resolves, matching the "no combat-time regen" rule
|
||||||
|
// (persistent-hp-and-regeneration design, R4) -- this migration must not
|
||||||
|
// gift them free healing mid-fight.
|
||||||
|
await queryRunner.query('UPDATE "characters" SET "hp_regen_since" = now()');
|
||||||
|
await queryRunner.query(`UPDATE "characters" AS "character"
|
||||||
|
SET "hp_regen_since" = NULL
|
||||||
|
FROM "combats" AS "combat"
|
||||||
|
WHERE "combat"."character_id" = "character"."id"
|
||||||
|
AND "combat"."status" = 'ACTIVE'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query('ALTER TABLE "characters" DROP COLUMN "hp_regen_since"');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import 'reflect-metadata';
|
||||||
|
import { getMetadataArgsStorage } from 'typeorm';
|
||||||
|
import { Character } from '../../characters/entities/character.entity';
|
||||||
|
|
||||||
|
describe('characters.hp_regen_since schema', () => {
|
||||||
|
it('stores the regeneration anchor as a nullable timestamptz', () => {
|
||||||
|
const metadata = getMetadataArgsStorage();
|
||||||
|
const column = metadata.columns.find(
|
||||||
|
(candidate) =>
|
||||||
|
candidate.target === Character && candidate.propertyName === 'hpRegenSince',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(column).toBeDefined();
|
||||||
|
expect(column?.options.type).toBe('timestamptz');
|
||||||
|
expect(column?.options.nullable).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user