From 2f571408993d57fe8c4c3cc15b78ae650b8452b9 Mon Sep 17 00:00:00 2001 From: "azykov@mail.ru" Date: Fri, 4 Sep 2026 21:57:43 +0300 Subject: [PATCH] =?UTF-8?q?=D0=BA=D1=80=D0=BE=D0=B2=D0=B0=D0=B2=D1=8B?= =?UTF-8?q?=D0=B5=20=D0=BA=D1=80=D0=BE=D0=BB=D0=B8=D0=BA=D0=B8=20=D0=B6?= =?UTF-8?q?=D0=B8=D1=80=D0=BD=D0=B5=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/RabbitEntity.tsx | 5 ++--- src/game/GameStore.ts | 33 ++++++++++++++++++++++++++------- src/game/rabbits.ts | 4 ++++ 3 files changed, 32 insertions(+), 10 deletions(-) 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;