diff --git a/src/components/RabbitEntity.tsx b/src/components/RabbitEntity.tsx
index 17d181a..75f3d18 100644
--- a/src/components/RabbitEntity.tsx
+++ b/src/components/RabbitEntity.tsx
@@ -1,7 +1,6 @@
import { observer } from 'mobx-react-lite';
import type { GameStore } from '../game/GameStore';
import type { RabbitState } from '../game/types';
-import { RABBIT_MAX_HP } from '../game/rabbits';
import { RABBIT_NIGHT_SPRITE, RABBIT_SPRITE } from '../game/sprites';
import HealthBar from './HealthBar';
import PixelSprite from './PixelSprite';
@@ -24,8 +23,8 @@ const RabbitEntity = observer(function RabbitEntity({ rabbit, store }: { rabbit:
>
- {rabbit.hp < RABBIT_MAX_HP && (
-
+ {rabbit.hp < store.rabbitMaxHp && (
+
)}
);
diff --git a/src/game/GameStore.ts b/src/game/GameStore.ts
index 0b1038b..4af74cf 100644
--- a/src/game/GameStore.ts
+++ b/src/game/GameStore.ts
@@ -20,6 +20,7 @@ import {
import { BLOOD_MOON_BANNER_SECONDS, DAY_HOURS, isBloodMoonNight, isNightHour, NIGHT_SPEED, nightBelongsToDay } from './daynight';
import { clearSave, loadSave, writeSave } from './persistence';
import {
+ BLOOD_MOON_COMBAT_MULTIPLIER,
BLOOD_MOON_SIZE_MULTIPLIER,
RABBIT_ACTION_DURATION,
RABBIT_HEALTH_REGEN_INTERVAL,
@@ -233,6 +234,17 @@ export class GameStore {
return RABBIT_HIT_RADIUS * (this.isBloodMoon ? BLOOD_MOON_SIZE_MULTIPLIER : 1);
}
+ /** A rabbit's max hp — during a blood moon they're tougher, not just bigger. */
+ get rabbitMaxHp(): number {
+ return RABBIT_MAX_HP * (this.isBloodMoon ? BLOOD_MOON_COMBAT_MULTIPLIER : 1);
+ }
+
+ /** Damage a rabbit's strike deals to the character — during a blood moon they hit harder, not
+ * just bigger. */
+ get rabbitStrikeDamage(): number {
+ return RABBIT_STRIKE_DAMAGE * (this.isBloodMoon ? BLOOD_MOON_COMBAT_MULTIPLIER : 1);
+ }
+
/** Buys the next level of the given upgrade if there are enough coins. Returns whether it bought. */
buyUpgrade(id: UpgradeId): boolean {
const level = this.upgradeLevels[id];
@@ -289,7 +301,7 @@ export class GameStore {
// Recomputed from actionTime on the very next tick if actually mid-jump, so 0 is a safe
// starting value regardless.
z: 0,
- hp: rabbit.hp ?? RABBIT_MAX_HP,
+ hp: rabbit.hp ?? this.rabbitMaxHp,
// Purely a transient visual cue — always starts unflashed regardless of the save.
hitFlashTimer: 0,
// Transient accumulator — always starts fresh regardless of the save.
@@ -387,7 +399,7 @@ export class GameStore {
x: pos.x,
y: pos.y,
z: 0,
- hp: RABBIT_MAX_HP,
+ hp: this.rabbitMaxHp,
hitFlashTimer: 0,
healthRegenProgress: 0,
action: 'idle',
@@ -525,7 +537,8 @@ export class GameStore {
this.updateAutosave(deltaTime);
}
- /** Shows the "КРОВАВАЯ ЛУНА!" banner for a few seconds right as a blood moon night begins. */
+ /** Shows the "КРОВАВАЯ ЛУНА!" banner for a few seconds and heals every rabbit to full (their
+ * now-tripled max hp) right as a blood moon night begins. */
private updateBloodMoon(deltaTime: number) {
if (this.bloodMoonBannerTimer > 0)
this.bloodMoonBannerTimer = Math.max(0, this.bloodMoonBannerTimer - deltaTime);
@@ -537,6 +550,11 @@ export class GameStore {
return;
this.lastAnnouncedBloodMoonDay = nightDay;
this.bloodMoonBannerTimer = BLOOD_MOON_BANNER_SECONDS;
+ const maxHp = this.rabbitMaxHp;
+ for (const rabbit of this.rabbits) {
+ rabbit.hp = maxHp;
+ rabbit.healthRegenProgress = 0;
+ }
}
/** Hp regenerates a whole point at a time (same accumulate-then-spend cadence as chopProgress,
@@ -567,7 +585,7 @@ export class GameStore {
* out — the ~1s of "striking" afterward is just the rabbit committed to that action and unable
* to do anything else, not a wind-up the character could still escape. */
private landRabbitStrike() {
- this.characterHp = Math.max(0, this.characterHp - RABBIT_STRIKE_DAMAGE);
+ this.characterHp = Math.max(0, this.characterHp - this.rabbitStrikeDamage);
this.characterHitFlashTimer = HIT_FLASH_DURATION;
if (this.characterHp <= 0) {
this.isDead = true;
@@ -606,14 +624,15 @@ export class GameStore {
* character's own regen — unlike the character's, this isn't paused by anything (no upgrade for
* it either), so it ticks along even mid-fight. */
private updateRabbitHealthRegen(rabbit: RabbitState, deltaTime: number) {
- if (rabbit.hp >= RABBIT_MAX_HP) {
+ const maxHp = this.rabbitMaxHp;
+ if (rabbit.hp >= maxHp) {
rabbit.healthRegenProgress = 0;
return;
}
rabbit.healthRegenProgress += deltaTime;
- while (rabbit.healthRegenProgress >= RABBIT_HEALTH_REGEN_INTERVAL && rabbit.hp < RABBIT_MAX_HP) {
+ while (rabbit.healthRegenProgress >= RABBIT_HEALTH_REGEN_INTERVAL && rabbit.hp < maxHp) {
rabbit.healthRegenProgress -= RABBIT_HEALTH_REGEN_INTERVAL;
- rabbit.hp = Math.min(RABBIT_MAX_HP, rabbit.hp + 1);
+ rabbit.hp = Math.min(maxHp, rabbit.hp + 1);
}
}
diff --git a/src/game/rabbits.ts b/src/game/rabbits.ts
index fe257e1..9f9630a 100644
--- a/src/game/rabbits.ts
+++ b/src/game/rabbits.ts
@@ -39,6 +39,10 @@ export const RABBIT_NIGHT_JUMP_SPREAD_DEGREES = 30;
/** Rabbits (and their strike/hit ranges) are this much bigger during a blood moon. */
export const BLOOD_MOON_SIZE_MULTIPLIER = 2;
+/** Rabbits deal this much more strike damage and have this much more max hp during a blood moon —
+ * a bigger jump than their (purely visual/reach) size multiplier above. */
+export const BLOOD_MOON_COMBAT_MULTIPLIER = 3;
+
/** Seconds per hp of rabbit health regen (fixed — unlike the character's own regen, there's no
* upgrade for it). */
export const RABBIT_HEALTH_REGEN_INTERVAL = 3;