Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
|---|---|---|
| Feature | `feat/<slug>` | `feat/spectator-mode` |
| Fix | `fix/<slug>` | `fix/meteor-blast-radius` |
| Claude session | `claude/<slug>` | `claude/bot-pathfinding` |
| AI session | `ai/<slug>` | `ai/bot-pathfinding` |
| Chore | `chore/<slug>` | `chore/upgrade-rn-073` |

## Commit format
Expand Down
33 changes: 25 additions & 8 deletions app.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,37 @@
{
"name": "RunDown",
"displayName": "Run Down",
"displayName": "Hidden Cam Detector",
"expo": {
"name": "Run Down",
"name": "Hidden Cam Detector",
"slug": "run-down",
"version": "0.1.0",
"orientation": "landscape",
"version": "2.0.0",
"orientation": "portrait",
"ios": {
"bundleIdentifier": "com.claude.rundown",
"bundleIdentifier": "com.cloudygetty.hidcamdetector",
"infoPlist": {
"NSBluetoothAlwaysUsageDescription": "Used to scan for hidden Bluetooth cameras nearby.",
"NSBluetoothPeripheralUsageDescription": "Used to detect Bluetooth surveillance devices.",
"NSLocalNetworkUsageDescription": "Used to scan the local WiFi network for IP cameras.",
"NSCameraUsageDescription": "Used for IR lens detection via the device camera.",
"UIBackgroundModes": ["fetch", "processing"],
"BGTaskSchedulerPermittedIdentifiers": ["com.claude.rundown.refresh"]
"BGTaskSchedulerPermittedIdentifiers": ["com.cloudygetty.hidcamdetector.refresh"]
}
},
"android": {
"package": "com.claude.rundown"
}
"package": "com.cloudygetty.hidcamdetector",
"permissions": [
"BLUETOOTH",
"BLUETOOTH_ADMIN",
"BLUETOOTH_SCAN",
"BLUETOOTH_CONNECT",
"ACCESS_WIFI_STATE",
"CHANGE_WIFI_STATE",
"ACCESS_COARSE_LOCATION",
"ACCESS_FINE_LOCATION",
"CAMERA",
"VIBRATE"
]
},
"plugins": []
}
}
4 changes: 2 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AppRegistry } from 'react-native';
import App from './src/App';
import { DetectorApp } from './src/detector/DetectorApp';
import { name as appName } from './app.json';

AppRegistry.registerComponent(appName, () => App);
AppRegistry.registerComponent(appName, () => DetectorApp);
20 changes: 15 additions & 5 deletions src/components/HUD.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import { Player, Bombardment, WeaponType, GameState } from '../types';
import { Player, Bombardment, WeaponType } from '../types';

type Props = {
player: Player;
Expand Down Expand Up @@ -76,7 +76,6 @@ export const HUD: React.FC<Props> = ({
const phaseSeconds = Math.ceil(bombardment.timeUntilNextPhase / 1000);
const impactSeconds = Math.ceil(bombardment.timeUntilNextImpact / 1000);
const incomingMeteor = bombardment.timeUntilNextImpact < 2000;
const abilityCooldownPct = player.abilityChargeMs > 0 ? 1 : 0;
const abilityReady = player.abilityChargeMs === 0;
const isCorrupted = player.corruptionDps > 0;
const isBounty = bountyPlayerId === player.id;
Expand Down Expand Up @@ -124,7 +123,10 @@ export const HUD: React.FC<Props> = ({
<View style={styles.coreRow}>
<View style={[styles.coreChip, styles.coreChipActive]}>
<Text style={styles.coreChipText}>
CORE: {player.heldCoreEffect ? CORE_EFFECT_LABELS[player.heldCoreEffect] ?? player.heldCoreEffect : '?'}
CORE:{' '}
{player.heldCoreEffect
? CORE_EFFECT_LABELS[player.heldCoreEffect] ?? player.heldCoreEffect
: '?'}
{' '}
<Text style={styles.corruptText}>-{player.corruptionDps.toFixed(0)} HP/s</Text>
</Text>
Expand Down Expand Up @@ -266,7 +268,11 @@ const styles = StyleSheet.create({
paddingVertical: 4,
borderRadius: 12,
},
killsChipBounty: { backgroundColor: 'rgba(180,30,0,0.85)', borderWidth: 1, borderColor: '#ff4400' },
killsChipBounty: {
backgroundColor: 'rgba(180,30,0,0.85)',
borderWidth: 1,
borderColor: '#ff4400',
},
killsText: { color: '#ffcc00', fontWeight: 'bold', fontSize: 13 },
killsTextBounty: { color: '#ff6633' },

Expand Down Expand Up @@ -295,7 +301,11 @@ const styles = StyleSheet.create({
paddingVertical: 3,
alignSelf: 'flex-start',
},
coreChipActive: { backgroundColor: 'rgba(180,0,200,0.55)', borderWidth: 1, borderColor: '#cc44ff' },
coreChipActive: {
backgroundColor: 'rgba(180,0,200,0.55)',
borderWidth: 1,
borderColor: '#cc44ff',
},
coreChipText: { color: '#ee88ff', fontSize: 10, fontWeight: 'bold' },
corruptText: { color: '#ff4488' },

Expand Down
42 changes: 27 additions & 15 deletions src/core/characters/characters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ export const CHARACTERS: Character[] = [
},
ability: {
name: 'Titan Guard',
description: 'Deploys a frontal energy shield, absorbing all incoming damage for 6 seconds before releasing it as a shockwave.',
description:
'Deploys a frontal energy shield, absorbing all incoming damage for 6 seconds before releasing it as a shockwave.',
cooldownMs: 28000,
durationMs: 6000,
effectType: 'damage_immunity',
Expand All @@ -63,7 +64,7 @@ export const CHARACTERS: Character[] = [
name: 'Nyra Solis',
title: 'The Solar',
lore: 'Channelled solar energy into her biology. Warm to the touch. Lethal at range.',
meteorQuip: 'A core. Finally. Stay back — it\'s mine.',
meteorQuip: "A core. Finally. Stay back — it's mine.",
portraitSource: PORTRAITS.nyra,
accentColor: '#ffaa00',
passive: {
Expand All @@ -73,7 +74,8 @@ export const CHARACTERS: Character[] = [
},
ability: {
name: 'Solar Bloom',
description: 'Emits a healing flare — instantly restores 60 HP — while enemies within range are marked for +40% bonus damage for 5 seconds.',
description:
'Emits a healing flare — instantly restores 60 HP — while enemies within range are marked for +40% bonus damage for 5 seconds.',
cooldownMs: 22000,
durationMs: 5000,
effectType: 'damage_boost',
Expand All @@ -94,7 +96,8 @@ export const CHARACTERS: Character[] = [
},
ability: {
name: 'Trapline',
description: 'Activates hidden snares — marks all enemies and increases outgoing damage by 40% for 6 seconds.',
description:
'Activates hidden snares — marks all enemies and increases outgoing damage by 40% for 6 seconds.',
cooldownMs: 18000,
durationMs: 6000,
effectType: 'damage_boost',
Expand All @@ -115,7 +118,8 @@ export const CHARACTERS: Character[] = [
},
ability: {
name: 'Mind Fracture',
description: 'Deploys hallucinations — enemies cannot accurately target for 4 seconds, effectively granting damage immunity.',
description:
'Deploys hallucinations — enemies cannot accurately target for 4 seconds, effectively granting damage immunity.',
cooldownMs: 20000,
durationMs: 4000,
effectType: 'damage_immunity',
Expand All @@ -137,7 +141,8 @@ export const CHARACTERS: Character[] = [
},
ability: {
name: 'Smoke Reign',
description: 'Blankets the area — grants damage immunity for 3 seconds and doubles movement speed within the smoke.',
description:
'Blankets the area — grants damage immunity for 3 seconds and doubles movement speed within the smoke.',
cooldownMs: 16000,
durationMs: 3000,
effectType: 'damage_immunity',
Expand All @@ -158,7 +163,8 @@ export const CHARACTERS: Character[] = [
},
ability: {
name: 'Predator Leap',
description: 'Launches forward 200 units and marks the target, increasing all damage dealt to them by 50% for 5 seconds.',
description:
'Launches forward 200 units and marks the target, increasing all damage dealt to them by 50% for 5 seconds.',
cooldownMs: 15000,
durationMs: 5000,
effectType: 'damage_boost',
Expand Down Expand Up @@ -201,7 +207,8 @@ export const CHARACTERS: Character[] = [
},
ability: {
name: 'Shadow Bind',
description: 'Tethers enemies — damage dealt to any target is increased by 45% for 5 seconds as the bind amplifies all hits.',
description:
'Tethers enemies — damage dealt to any target is increased by 45% for 5 seconds as the bind amplifies all hits.',
cooldownMs: 18000,
durationMs: 5000,
effectType: 'damage_boost',
Expand All @@ -212,7 +219,7 @@ export const CHARACTERS: Character[] = [
name: 'Orin "Scrap" Dax',
title: 'The Salvager',
lore: 'Built his first weapon from a vending machine and a door hinge. Still uses it.',
meteorQuip: 'Free parts. I\'ll take it.',
meteorQuip: "Free parts. I'll take it.",
portraitSource: PORTRAITS.orin,
accentColor: '#cc8800',
passive: {
Expand Down Expand Up @@ -243,7 +250,8 @@ export const CHARACTERS: Character[] = [
},
ability: {
name: 'Sonic Crescendo',
description: 'Charges a focused sound blast — knocks back nearby enemies and deals +50% weapon damage for 5 seconds.',
description:
'Charges a focused sound blast — knocks back nearby enemies and deals +50% weapon damage for 5 seconds.',
cooldownMs: 20000,
durationMs: 5000,
effectType: 'damage_boost',
Expand All @@ -265,7 +273,8 @@ export const CHARACTERS: Character[] = [
},
ability: {
name: 'Gravity Well',
description: 'Pulls enemies toward a central point — all pulled targets take +50% weapon damage for 5 seconds.',
description:
'Pulls enemies toward a central point — all pulled targets take +50% weapon damage for 5 seconds.',
cooldownMs: 22000,
durationMs: 5000,
effectType: 'damage_boost',
Expand All @@ -286,7 +295,8 @@ export const CHARACTERS: Character[] = [
},
ability: {
name: 'Cryo Veil',
description: 'Freezes the surrounding ground — grants damage immunity for 4 seconds and creates ice barriers that block bullets.',
description:
'Freezes the surrounding ground — grants damage immunity for 4 seconds and creates ice barriers that block bullets.',
cooldownMs: 20000,
durationMs: 4000,
effectType: 'damage_immunity',
Expand All @@ -297,7 +307,7 @@ export const CHARACTERS: Character[] = [
name: 'Jax "Overclock" Renn',
title: 'The Overclocked',
lore: 'Neural augmentations pushed past rated limits. The tremor in his hands is from the speed, not the fear.',
meteorQuip: 'HA. Let\'s go again.',
meteorQuip: "HA. Let's go again.",
portraitSource: PORTRAITS.jax,
accentColor: '#ff4400',
passive: {
Expand All @@ -307,7 +317,8 @@ export const CHARACTERS: Character[] = [
},
ability: {
name: 'Adrenal Override',
description: 'Pushes all systems past the limit — doubles fire rate and movement speed for 5 seconds, but drains 5 HP per second during the effect.',
description:
'Pushes all systems past the limit — doubles fire rate and movement speed for 5 seconds, but drains 5 HP per second during the effect.',
cooldownMs: 20000,
durationMs: 5000,
effectType: 'rapid_fire',
Expand All @@ -329,7 +340,8 @@ export const CHARACTERS: Character[] = [
},
ability: {
name: 'Void Step',
description: 'Becomes intangible for 5 seconds — grants damage immunity and passes through all obstacles.',
description:
'Becomes intangible for 5 seconds — grants damage immunity and passes through all obstacles.',
cooldownMs: 25000,
durationMs: 5000,
effectType: 'damage_immunity',
Expand Down
27 changes: 13 additions & 14 deletions src/core/characters/portraits.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
/* eslint-disable @typescript-eslint/no-var-requires */
// Static require() calls so Metro can resolve portrait assets at bundle time.
// Swap any entry to null to fall back to the accent-color placeholder in LobbyScreen.

export const PORTRAITS: Record<string, number | null> = {
vex: require('../../../assets/portraits/vex.png'),
vex: require('../../../assets/portraits/vex.png'),
brutus: require('../../../assets/portraits/brutus.png'),
nyra: require('../../../assets/portraits/nyra.png'),
kade: require('../../../assets/portraits/kade.png'),
iris: require('../../../assets/portraits/iris.png'),
rook: require('../../../assets/portraits/rook.png'),
talon: require('../../../assets/portraits/talon.png'),
voss: require('../../../assets/portraits/voss.png'),
sable: require('../../../assets/portraits/sable.png'),
orin: require('../../../assets/portraits/orin.png'),
lyric: require('../../../assets/portraits/lyric.png'),
nyra: require('../../../assets/portraits/nyra.png'),
kade: require('../../../assets/portraits/kade.png'),
iris: require('../../../assets/portraits/iris.png'),
rook: require('../../../assets/portraits/rook.png'),
talon: require('../../../assets/portraits/talon.png'),
voss: require('../../../assets/portraits/voss.png'),
sable: require('../../../assets/portraits/sable.png'),
orin: require('../../../assets/portraits/orin.png'),
lyric: require('../../../assets/portraits/lyric.png'),
magnus: require('../../../assets/portraits/magnus.png'),
eira: require('../../../assets/portraits/eira.png'),
jax: require('../../../assets/portraits/jax.png'),
kael: require('../../../assets/portraits/kael.png'),
eira: require('../../../assets/portraits/eira.png'),
jax: require('../../../assets/portraits/jax.png'),
kael: require('../../../assets/portraits/kael.png'),
};
41 changes: 31 additions & 10 deletions src/core/gameEngine/GameEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ import {
Vector2,
FractureCore,
FractureCoreEffect,
GravityZone,
TimeEchoZone,
HelixRelay,
SupplyDrop,
WeaponType,
Rarity,
Expand Down Expand Up @@ -255,12 +252,20 @@ function tickHelixRelays(state: GameState, deltaMs: number): GameState {
{
id: `relay_loot_${relay.id}_${Date.now()}`,
position: {
x: relay.position.x + randomInRange(-HELIX_RELAY_REWARD_LOOT_RADIUS, HELIX_RELAY_REWARD_LOOT_RADIUS),
y: relay.position.y + randomInRange(-HELIX_RELAY_REWARD_LOOT_RADIUS, HELIX_RELAY_REWARD_LOOT_RADIUS),
x:
relay.position.x +
randomInRange(-HELIX_RELAY_REWARD_LOOT_RADIUS, HELIX_RELAY_REWARD_LOOT_RADIUS),
y:
relay.position.y +
randomInRange(-HELIX_RELAY_REWARD_LOOT_RADIUS, HELIX_RELAY_REWARD_LOOT_RADIUS),
},
weapon: null,
ammo: randomInt(60, 120),
materials: { wood: randomInt(40, 80), stone: randomInt(30, 60), metal: randomInt(20, 40) },
materials: {
wood: randomInt(40, 80),
stone: randomInt(30, 60),
metal: randomInt(20, 40),
},
shield: 100,
health: 50,
},
Expand Down Expand Up @@ -347,7 +352,14 @@ function tickSupplyDrops(state: GameState, deltaMs: number): GameState {
}

function spawnSupplyDrop(mapWidth: number, mapHeight: number): SupplyDrop {
const epicWeapons: WeaponType[] = ['sniper', 'heavy_sniper', 'rail_gun', 'minigun', 'rocket_launcher', 'heavy_ar'];
const epicWeapons: WeaponType[] = [
'sniper',
'heavy_sniper',
'rail_gun',
'minigun',
'rocket_launcher',
'heavy_ar',
];
const rarities: Rarity[] = ['epic', 'legendary'];
return {
id: `supply_${Date.now()}_${Math.random().toString(36).slice(2)}`,
Expand Down Expand Up @@ -408,7 +420,11 @@ function moveHumanPlayer(state: GameState, input: InputState, deltaMs: number):
const gravitySpeedMult = inGravityZone ? 0.6 : 1;

const speed =
PLAYER_SPEED * player.speedMult * abilitySpeedMult * gravitySpeedMult * (deltaMs / TICK_RATE_MS);
PLAYER_SPEED *
player.speedMult *
abilitySpeedMult *
gravitySpeedMult *
(deltaMs / TICK_RATE_MS);
const dir = normalize(input.moveVector);

const rawNext: Vector2 = {
Expand Down Expand Up @@ -487,7 +503,11 @@ function spawnMeteorEffects(state: GameState, newImpacts: MeteorImpact[]): GameS

for (const impact of newImpacts) {
if (impact.meteorType === 'explosive') {
const effects: FractureCoreEffect[] = ['cooldown_reduction', 'damage_amp', 'ability_mutation'];
const effects: FractureCoreEffect[] = [
'cooldown_reduction',
'damage_amp',
'ability_mutation',
];
fractureCores = [
...fractureCores,
{
Expand Down Expand Up @@ -602,7 +622,8 @@ export function fireShot(state: GameState, shooterId: string, targetPos: Vector2
}

// Outgoing multipliers: passive × ability × Fracture Core damage_amp
const abilityDamageMult = players[shooterIndex].activeAbilityEffect === 'damage_boost' ? 1.5 : 1;
const abilityDamageMult =
players[shooterIndex].activeAbilityEffect === 'damage_boost' ? 1.5 : 1;
const coreDamageMult = players[shooterIndex].heldCoreEffect === 'damage_amp' ? 1.4 : 1;
const rawDmg = Math.round(
weapon.damage * players[shooterIndex].damageMult * abilityDamageMult * coreDamageMult,
Expand Down
8 changes: 6 additions & 2 deletions src/core/meteor/MeteorManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,12 @@ function tickImpacts(impacts: MeteorImpact[], deltaMs: number): MeteorImpact[] {
// WHY: 5% echo, 15% gravity, 80% explosive — chaos is calibrated, not random.
function pickMeteorType(): MeteorType {
const roll = Math.random();
if (roll < 0.05) return 'echo';
if (roll < 0.20) return 'gravity';
if (roll < 0.05) {
return 'echo';
}
if (roll < 0.2) {
return 'gravity';
}
return 'explosive';
}

Expand Down
Loading