Skip to content
Merged
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
6 changes: 3 additions & 3 deletions src/editor/configs/voidstrike.ts
Original file line number Diff line number Diff line change
Expand Up @@ -725,10 +725,10 @@ export const VOIDSTRIKE_PANELS: PanelConfig[] = [
{ id: 'bases', name: 'Bases', icon: '🏠', type: 'objects' },
{ id: 'objects', name: 'Obj', icon: '📦', type: 'objects' },
{ id: 'decorations', name: 'Decor', icon: '🌲', type: 'objects' },
{ id: 'selected', name: 'Edit', icon: '', type: 'selected' },
{ id: 'settings', name: 'Set', icon: '⚙', type: 'settings' },
{ id: 'selected', name: 'Edit', icon: '✏️', type: 'selected' },
{ id: 'settings', name: 'Set', icon: '⚙', type: 'settings' },
{ id: 'ai', name: 'AI', icon: '✨', type: 'ai' },
{ id: 'validate', name: 'Val', icon: '', type: 'validate' },
{ id: 'validate', name: 'Val', icon: '', type: 'validate' },
];

// ============================================
Expand Down
38 changes: 27 additions & 11 deletions src/editor/core/EditorCore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ export interface DetailedValidationResult {
connectedPairs: number;
blockedPairs: number;
};
navmeshStats?: {
navmeshGenerated: boolean;
pathsChecked: number;
pathsFound: number;
pathsBlocked: number;
blockedPaths?: Array<{ from: string; to: string }>;
};
timestamp?: number;
}

Expand Down Expand Up @@ -309,6 +316,24 @@ export function EditorCore({
if (dataProvider?.validateMap) {
const result = await dataProvider.validateMap(state.mapData);

// Extract extended result fields
const extendedResult = result as unknown as {
stats?: {
totalNodes: number;
totalEdges: number;
islandCount: number;
connectedPairs: number;
blockedPairs: number;
};
navmeshStats?: {
navmeshGenerated: boolean;
pathsChecked: number;
pathsFound: number;
pathsBlocked: number;
blockedPaths?: Array<{ from: string; to: string }>;
};
};

// Convert to detailed result format
setValidationResult({
valid: result.valid,
Expand All @@ -322,17 +347,8 @@ export function EditorCore({
issue as unknown as { suggestedFix?: { type: string; description: string } }
).suggestedFix,
})),
stats: (
result as unknown as {
stats?: {
totalNodes: number;
totalEdges: number;
islandCount: number;
connectedPairs: number;
blockedPairs: number;
};
}
).stats,
stats: extendedResult.stats,
navmeshStats: extendedResult.navmeshStats,
timestamp: Date.now(),
});
}
Expand Down
61 changes: 54 additions & 7 deletions src/editor/core/panels/SettingsPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useState } from 'react';
import { useState, useEffect, useRef } from 'react';
import type { EditorConfig, EditorState, EditorMapData, EditorObject } from '../../config/EditorConfig';
import { Section, ToggleSwitch } from './shared';
import {
Expand Down Expand Up @@ -40,7 +40,35 @@
// Border decoration state
const [borderStyle, setBorderStyle] = useState<BorderDecorationStyle>('rocks');
const [borderDensity, setBorderDensity] = useState(0.7);
const [borderScale, setBorderScale] = useState(2.0); // Average of scaleMin/scaleMax
const [isGenerating, setIsGenerating] = useState(false);
const isInitialMount = useRef(true);

// Auto-regenerate border decorations when style, density, or scale changes (if decorations exist)
useEffect(() => {
// Skip on initial mount
if (isInitialMount.current) {
isInitialMount.current = false;
return;
}

// Only auto-regenerate if there are existing border decorations
if (!state.mapData || !onUpdateObjects) return;
const currentCount = countBorderDecorations(state.mapData.objects);
if (currentCount === 0) return;

// Regenerate with new settings
const settings: BorderDecorationSettings = {
...DEFAULT_BORDER_SETTINGS,
style: borderStyle,
density: borderDensity,
scaleMin: borderScale * 0.6,
scaleMax: borderScale * 1.4,
};

const newObjects = generateBorderDecorations(state.mapData, settings);
onUpdateObjects(newObjects);
}, [borderStyle, borderDensity, borderScale]);

Check warning on line 71 in src/editor/core/panels/SettingsPanel.tsx

View workflow job for this annotation

GitHub Actions / lint

React Hook useEffect has missing dependencies: 'onUpdateObjects' and 'state.mapData'. Either include them or remove the dependency array. If 'onUpdateObjects' changes too often, find the parent component that defines it and wrap that definition in useCallback

if (!state.mapData) return null;

Expand All @@ -55,6 +83,8 @@
...DEFAULT_BORDER_SETTINGS,
style: borderStyle,
density: borderDensity,
scaleMin: borderScale * 0.6,
scaleMax: borderScale * 1.4,
};

const newObjects = generateBorderDecorations(state.mapData, settings);
Expand Down Expand Up @@ -196,15 +226,12 @@
<button
key={style}
onClick={() => setBorderStyle(style)}
className={`
px-2 py-1.5 rounded text-[11px] transition-all capitalize
${borderStyle === style ? 'ring-1' : 'hover:bg-white/5'}
`}
className="px-2 py-1.5 rounded text-[11px] transition-all capitalize hover:bg-white/5"
style={{
backgroundColor: borderStyle === style ? `${theme.primary}20` : theme.surface,
color: borderStyle === style ? theme.text.primary : theme.text.muted,
'--tw-ring-color': theme.primary,
} as React.CSSProperties}
border: borderStyle === style ? `1px solid ${theme.primary}` : '1px solid transparent',
}}
>
{style.replace('_', ' ')}
</button>
Expand Down Expand Up @@ -232,6 +259,26 @@
/>
</div>

<div>
<div className="flex justify-between items-center mb-1.5">
<label className="text-[10px] uppercase tracking-wider" style={{ color: theme.text.muted }}>
Scale
</label>
<span className="text-[10px] font-mono" style={{ color: theme.text.secondary }}>
{borderScale.toFixed(1)}x
</span>
</div>
<input
type="range"
min="0.5"
max="4"
step="0.5"
value={borderScale}
onChange={(e) => setBorderScale(parseFloat(e.target.value))}
className="w-full"
/>
</div>

<div className="flex gap-2">
<button
onClick={handleGenerateBorderDecorations}
Expand Down
56 changes: 43 additions & 13 deletions src/editor/core/panels/ValidatePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export function ValidatePanel({
const isValid = validationResult?.valid ?? true;
const issues = validationResult?.issues ?? [];
const stats = validationResult?.stats;
const navmeshStats = validationResult?.navmeshStats;

const errors = issues.filter(i => i.severity === 'error');
const warnings = issues.filter(i => i.severity === 'warning');
Expand All @@ -47,7 +48,7 @@ export function ValidatePanel({
{isValidating ? (
<>
<span className="animate-spin">⟳</span>
Validating...
Building Navmesh...
</>
) : (
<>
Expand All @@ -62,7 +63,7 @@ export function ValidatePanel({
className="text-[11px] leading-relaxed"
style={{ color: theme.text.muted }}
>
Checks that all bases are connected and expansions are reachable.
Uses Recast Navigation (same as game) to verify all bases are connected. Includes decoration obstacles.
</div>

{/* Results */}
Expand Down Expand Up @@ -95,13 +96,38 @@ export function ValidatePanel({
</div>
</div>

{/* Navmesh Status */}
{navmeshStats && (
<div
className="p-3 rounded-lg"
style={{
backgroundColor: navmeshStats.navmeshGenerated ? `${theme.success}10` : `${theme.warning}10`,
border: `1px solid ${navmeshStats.navmeshGenerated ? theme.success : theme.warning}20`,
}}
>
<div className="flex items-center gap-2 text-xs" style={{ color: theme.text.primary }}>
<span>{navmeshStats.navmeshGenerated ? '🗺️' : '⚠️'}</span>
<span className="font-medium">
{navmeshStats.navmeshGenerated ? 'Navmesh Generated' : 'Navmesh Failed'}
</span>
</div>
{navmeshStats.navmeshGenerated && (
<div className="mt-2 text-[10px]" style={{ color: theme.text.muted }}>
Paths checked: {navmeshStats.pathsChecked} |
Found: <span style={{ color: theme.success }}>{navmeshStats.pathsFound}</span> |
Blocked: <span style={{ color: navmeshStats.pathsBlocked > 0 ? theme.error : theme.text.muted }}>{navmeshStats.pathsBlocked}</span>
</div>
)}
</div>
)}

{/* Statistics */}
{stats && (
<Section title="Statistics" icon="📊" theme={theme} defaultOpen={false}>
<Section title="Path Statistics" icon="📊" theme={theme} defaultOpen={false}>
<div className="grid grid-cols-2 gap-2">
{[
{ label: 'Nodes', value: stats.totalNodes },
{ label: 'Islands', value: stats.islandCount, warn: stats.islandCount > 1 },
{ label: 'Bases', value: stats.totalNodes },
{ label: 'Paths Tested', value: stats.totalEdges },
{ label: 'Connected', value: stats.connectedPairs, success: true },
{ label: 'Blocked', value: stats.blockedPairs, error: stats.blockedPairs > 0 },
].map((stat) => (
Expand All @@ -118,8 +144,6 @@ export function ValidatePanel({
style={{
color: stat.error
? theme.error
: stat.warn
? theme.warning
: stat.success
? theme.success
: theme.text.primary,
Expand Down Expand Up @@ -205,7 +229,7 @@ export function ValidatePanel({
className="text-center py-4 text-sm"
style={{ color: theme.success }}
>
All connectivity checks passed!
All paths verified! Map is ready for play.
</div>
)}

Expand All @@ -229,25 +253,31 @@ export function ValidatePanel({
)}

{/* Help section */}
<Section title="Validation Checks" icon="ℹ️" theme={theme} defaultOpen={false}>
<Section title="How It Works" icon="ℹ️" theme={theme} defaultOpen={false}>
<ul className="space-y-1.5 text-[11px]" style={{ color: theme.text.muted }}>
<li className="flex items-start gap-2">
<span style={{ color: theme.success }}>✓</span>
<span>All main bases can reach each other</span>
<span>Builds navmesh using Recast Navigation (same as game)</span>
</li>
<li className="flex items-start gap-2">
<span style={{ color: theme.success }}>✓</span>
<span>Natural expansions are accessible</span>
<span>Includes terrain, ramps, and decoration obstacles</span>
</li>
<li className="flex items-start gap-2">
<span style={{ color: theme.success }}>✓</span>
<span>No important bases are isolated</span>
<span>Tests actual pathfinding between all bases</span>
</li>
<li className="flex items-start gap-2">
<span style={{ color: theme.success }}>✓</span>
<span>Ramps connect elevation differences</span>
<span>If validation passes, game pathfinding will work</span>
</li>
</ul>
<div
className="mt-3 p-2 rounded text-[10px]"
style={{ backgroundColor: `${theme.primary}15`, color: theme.primary }}
>
💡 This uses the exact same navigation system as the game, ensuring validation accuracy.
</div>
</Section>
</div>
);
Expand Down
Loading
Loading