Skip to content

Commit 4ac56be

Browse files
Merge pull request #18 from tobiasmullett-svg/claude/game-sweep-improvements-itn70m
fix: implement missing pet perks and fix sweep-found bugs
2 parents 4fc15e9 + a5f0307 commit 4ac56be

8 files changed

Lines changed: 261 additions & 67 deletions

File tree

app/game.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ import ShopOverlay from '../components/game/ShopOverlay';
3131
import GameOverOverlay from '../components/game/GameOverOverlay';
3232
import Minimap from '../components/game/Minimap';
3333
import TutorialOverlay from '../components/game/TutorialOverlay';
34-
import { BOSS_WAVES } from '../engine/constants';
34+
import { BOSS_WAVES, WAVE_BASE_TIME } from '../engine/constants';
3535
import AsyncStorage from '@react-native-async-storage/async-storage';
3636

3737
const defaultHud: HudData = {
38-
hp: 100, maxHp: 100, armor: 0, waveNum: 1, waveTimer: 25, waveMaxTime: 25,
38+
hp: 100, maxHp: 100, armor: 0, waveNum: 1, waveTimer: WAVE_BASE_TIME, waveMaxTime: WAVE_BASE_TIME,
3939
materials: 0, level: 1, xp: 0, xpToNext: 10,
4040
abilityCd: 0, abilityMaxCd: 30, abilityEmoji: '\u2B50', phase: 'waveAnnounce',
4141
comboCount: 0, comboTimer: 0, comboMaxTime: 3.2, bestCombo: 0,

components/game/GameCanvas.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import React from 'react';
22
import { View, Text, StyleSheet } from 'react-native';
33
import type { GameState } from '../../engine/types';
44
import { ELITE_EMOJIS, ELITE_COLORS } from '../../engine/data';
5-
import { PICKUP_BASE_RANGE } from '../../engine/constants';
5+
import { CRIT_HIT_STOP } from '../../engine/constants';
6+
import { getPickupRange } from '../../engine/GameEngine';
67
import ArenaBackground from './arena/ArenaBackground';
78
import WeaponIcon, { hasWeaponIcon } from './WeaponIcon';
89

@@ -18,8 +19,9 @@ function DangerVignette({ frame }: { frame: number }) {
1819
);
1920
}
2021

21-
function CritFlash({ frame }: { frame: number }) {
22-
const opacity = Math.max(0, 1 - (frame % 20) * 0.15);
22+
function CritFlash({ hitStop }: { hitStop: number }) {
23+
// Fade with the remaining hit-stop so the flash decays deterministically.
24+
const opacity = Math.min(1, hitStop / CRIT_HIT_STOP);
2325
if (opacity <= 0) return null;
2426
return <View style={[s.critFlash, { opacity }]} />;
2527
}
@@ -44,8 +46,7 @@ export default function GameCanvas({ gameState, frame }: Props) {
4446
const visibleEffects = (effects ?? []).filter(fx => vis(fx.x, fx.y));
4547
const visibleDeathParticles = (deathParticles ?? []).filter(dp => vis(dp.x, dp.y));
4648
const visibleDmgNums = (dmgNums ?? []).filter(d => vis(d.x, d.y));
47-
const relicIds = new Set((state.relics ?? []).map(relic => relic.id));
48-
const effectivePickupRange = p.pickupRange * (relicIds.has('abyssalMagnet') ? 1.9 : 1) * (1 + p.luck * 0.01);
49+
const effectivePickupRange = getPickupRange(state);
4950
const crowded = state.wave.number >= 5 || visibleEnemies.length + visibleProjectiles.length > 45 || visibleEffects.length + visibleDmgNums.length > 35;
5051
const displayProjectiles = crowded ? visibleProjectiles.slice(0, 48) : visibleProjectiles;
5152
const displayEffects = crowded ? visibleEffects.filter(fx => fx.kind !== 'muzzle' && fx.kind !== 'spark').slice(-14) : visibleEffects;
@@ -709,7 +710,7 @@ export default function GameCanvas({ gameState, frame }: Props) {
709710
)}
710711
<OffScreenMarkers state={state} />
711712
{(p.hp / p.maxHp) < 0.32 && <DangerVignette frame={frame} />}
712-
{showCritFlash && <CritFlash frame={frame} />}
713+
{showCritFlash && <CritFlash hitStop={state.hitStop} />}
713714
{state.waveModifier === 'denseFog' && (
714715
<View style={s.fogOverlay} pointerEvents="none">
715716
<View style={s.fogVignette} />

components/game/Minimap.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export default function Minimap({ gameState, size = 120 }: Props) {
3737
))}
3838

3939
{/* Resource Nodes */}
40-
{resourceNodes.map(r => (
40+
{resourceNodes.filter(r => r.alive).map(r => (
4141
<View
4242
key={`res-${r.id}`}
4343
style={[

components/game/ShopOverlay.tsx

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { useState } from 'react';
22
import { View, Text, Pressable, StyleSheet, ScrollView } from 'react-native';
33
import type { GameState } from '../../engine/types';
44
import { RARITY_COLORS, WEAPON_EVOLVE_KILLS, WEAPON_EVOLVE_COST, WEAPON_MAX_LEVEL } from '../../engine/constants';
5-
import { buyShopItem, rerollShop, healPlayer, buyEgg, trainPets, fusePets, evolveWeapon, getPetSynergies, toggleShopSlotLock } from '../../engine/GameEngine';
5+
import { buyShopItem, rerollShop, healPlayer, buyEgg, trainPets, fusePets, evolveWeapon, getPetSynergies, getPetAttackStats, toggleShopSlotLock } from '../../engine/GameEngine';
66
import { WEAPONS, EVOLVED_WEAPONS, ITEM_DEFS } from '../../engine/data';
77
import WeaponIcon, { hasWeaponIcon } from './WeaponIcon';
88

@@ -34,17 +34,6 @@ const PET_DETAILS: Record<string, { upgrade: string; breed: string }> = {
3434
},
3535
};
3636

37-
function getPetCooldown(level: number, kind: string): string {
38-
const base = kind === 'snapper' ? 0.85 : kind === 'spark' ? 1.2 : 1.5;
39-
return (base * Math.max(0.62, 1 - (level - 1) * 0.035)).toFixed(2);
40-
}
41-
42-
function getPetRange(kind: string): number {
43-
if (kind === 'spark') return 180;
44-
if (kind === 'mender') return 145;
45-
return 130;
46-
}
47-
4837
export default function ShopOverlay({ gameState, onNextWave }: Props) {
4938
const [, force] = useState(0);
5039
const [broodNotice, setBroodNotice] = useState('');
@@ -54,6 +43,7 @@ export default function ShopOverlay({ gameState, onNextWave }: Props) {
5443
if (!state) return null;
5544
const { shopSlots, materials, player, wave, rerollCost, healCost } = state;
5645
const canHeal = player.hp < player.maxHp;
46+
const broodFull = state.pets.length + state.pendingHatches.length >= 5;
5747
const eggCost = 18 + state.pets.length * 6 + wave.number * 2;
5848
const trainCost = 10 + state.pets.reduce((sum, pet) => sum + pet.level * 4, 0);
5949
const canTrain = state.pets.length > 0 && state.pets.some(pet => pet.level < 9);
@@ -202,6 +192,7 @@ export default function ShopOverlay({ gameState, onNextWave }: Props) {
202192
<Text style={s.emptyPetText}>Find eggs in the arena or buy one here.</Text>
203193
) : state.pets.map(pet => {
204194
const detail = PET_DETAILS[pet.kind];
195+
const attackStats = getPetAttackStats(pet);
205196
const nextPower = pet.level >= 9 ? 'Max trained' : `Next: Pow ${Math.round(pet.damage + 1 + Math.floor((pet.level + 1) / 4))}`;
206197
const nextPerkLevel = [3, 5, 7].find(l => l > pet.level);
207198
return (
@@ -219,7 +210,7 @@ export default function ShopOverlay({ gameState, onNextWave }: Props) {
219210
<Text style={s.petName}>{pet.name}</Text>
220211
<Text style={[s.generationTag, { backgroundColor: pet.color ?? '#2DD4BF' }]}>Gen {pet.generation ?? 1}</Text>
221212
</View>
222-
<Text style={s.petLevel}>Lv.{pet.level} · Pow {Math.round(pet.damage)} · {getPetCooldown(pet.level, pet.kind)}s · R{getPetRange(pet.kind)}</Text>
213+
<Text style={s.petLevel}>Lv.{pet.level} · Pow {Math.round(pet.damage)} · {attackStats.cooldown.toFixed(2)}s · R{attackStats.range}</Text>
223214
<Text style={s.petTrait}>{pet.attackName}: {PET_TRAITS[pet.kind] ?? 'Companion'}</Text>
224215
{(pet.perks?.length ?? 0) > 0 && (
225216
<View style={s.perkRow}>
@@ -254,15 +245,14 @@ export default function ShopOverlay({ gameState, onNextWave }: Props) {
254245
<Pressable
255246
onPress={() => {
256247
if (buyEgg(state)) {
257-
const newest = state.pets[state.pets.length - 1];
258-
setBroodNotice(newest ? `${newest.name} joined the brood` : '+12 overflow materials');
248+
setBroodNotice('A new egg is hatching…');
259249
force(n => n + 1);
260250
}
261251
}}
262-
disabled={(materials ?? 0) < eggCost}
263-
style={[s.actionBtn, (materials ?? 0) < eggCost && s.disabled]}
252+
disabled={broodFull || (materials ?? 0) < eggCost}
253+
style={[s.actionBtn, (broodFull || (materials ?? 0) < eggCost) && s.disabled]}
264254
>
265-
<Text style={s.actionText}>🥚 Egg (🔩{eggCost})</Text>
255+
<Text style={s.actionText}>{broodFull ? '🥚 Brood Full' : `🥚 Egg (🔩${eggCost})`}</Text>
266256
</Pressable>
267257
<Pressable
268258
onPress={() => {

0 commit comments

Comments
 (0)