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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

### Under the hood

- **SYSTEMS.EXE, the GM's own game systems in one window.** A SYSTEMS.EXE button under the game
system picker, in the GAME tab, opens it, for the main admin only. It lists every system the GM
has made or installed, with badges for the one running, its version, unpublished changes,
problems and whether it was installed from a file. Beside the one picked are its facts and
RENAME, DUPLICATE, EXPORT .CITYSYS and DELETE. A name already in use is refused right in the
rename box, which stays open to try another. The picker shows each change at once. Making a
new system and installing a file come next.

- **What the SYSTEMS.EXE window will say.** The badges on each system (RUNNING, PUBLISHED v3,
UNPUBLISHED CHANGES, a problem count, INSTALLED), the facts beside the one picked, and, for a file
about to be installed, what it holds and which buttons it offers: INSTALL, BRING IT BACK, UPDATE,
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import { useVehicleRoster } from './hooks/useVehicleRoster';
import { useEnemyVehicles } from './hooks/useEnemyVehicles';
import { hasVehicles } from './sheets/vehicleSystems';
import { NpcLibrary } from './components/NpcLibrary';
import { SystemsWindow } from './components/SystemsWindow';
import { NpcSheetWindow } from './components/NpcSheetWindow';
import { TvPortrait } from './components/TvPortrait';
import { headshotsForShape } from './headshots';
Expand Down Expand Up @@ -298,6 +299,8 @@ function App() {

const [isNpcLibraryOpen, setIsNpcLibraryOpen] = useState(false);
const [npcLibraryPos, setNpcLibraryPos] = useState(() => ({ x: window.innerWidth / 2 - 150, y: window.innerHeight / 2 - 200 }));
const [isSystemsOpen, setIsSystemsOpen] = useState(false);
const [systemsPos, setSystemsPos] = useState(() => ({ x: window.innerWidth / 2 - 310, y: window.innerHeight / 2 - 260 }));
const [openNpcSheet, setOpenNpcSheet] = useState<{ id: number; npc_label: string; token_shape?: string; locationId?: number } | null>(null);
// NPC sheet linked to the currently selected token (admin) - drives
// GENERATE_SHEET vs OPEN_SHEET on the token menu
Expand Down Expand Up @@ -2038,6 +2041,7 @@ function App() {
onGrantAccess={handleGrantAccess}
onRevokeAccess={handleRevokeAccess}
onOpenNpcLibrary={() => setIsNpcLibraryOpen(true)}
onOpenSystems={isPrimaryAdmin ? () => setIsSystemsOpen(true) : undefined}
/>
)}
{adminBankPlayer && (
Expand Down Expand Up @@ -2319,6 +2323,15 @@ function App() {
attachLocationId={selectedLocation && ['enemy_rhombus', 'friendly_rhombus'].includes(selectedLocation.shape) ? selectedLocation.id : null}
/>
)}
{isSystemsOpen && token && isPrimaryAdmin && (
<SystemsWindow
token={token}
running={gameSystem}
pos={systemsPos}
setPos={setSystemsPos}
onClose={() => setIsSystemsOpen(false)}
/>
)}
{openPlayerSheetUser && token && (
<NpcSheetWindow
token={token}
Expand Down
29 changes: 23 additions & 6 deletions frontend/src/components/AdminPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import { useParts, partOn, type PartLookup } from '../sheets/parts';
import { currenciesFor, isUploadedIcon, BUILT_IN_ICONS, type Currency } from '../sheets/currencies';
import { useCustomTemplate, refreshCustomTemplate } from '../sheets/customTemplates';
import { shortfallRule } from '../sheets/moneyText';
import { SYSTEMS_CHANGED_EVENT } from '../sheets/systemsLibrary';


// ─────────────────────────────────────────────────────────────────────────────
Expand All @@ -57,7 +58,7 @@ function BattleAdminPanel({
token, isDeployingEnemy, setIsDeployingEnemy, isDeployingFriendly, setIsDeployingFriendly,
tempBattleMapScale, setTempBattleMapScale, activeBattleMapData, locations, refreshLocations,
handleSaveDefault, handleLoadDefault, setIsAdminPayOpen, setIsAdminXpOpen, setIsCatalogueOpen, secureModeEnabled, onLogout,
globalSettings, fetchGlobalSettings, onOpenNpcLibrary, activeUsers,
globalSettings, fetchGlobalSettings, onOpenNpcLibrary, onOpenSystems, activeUsers,
}: any) {
const [tab, setTab] = useState<'battle_map' | 'game'>('battle_map');
// A custom system with the bank off has no pay or currency (3b2b).
Expand Down Expand Up @@ -141,7 +142,7 @@ function BattleAdminPanel({
</>}

{tab === 'game' && <>
<TTRPGSystemPanel token={token} onOpenNpcLibrary={onOpenNpcLibrary} activeUsers={activeUsers} />
<TTRPGSystemPanel token={token} onOpenNpcLibrary={onOpenNpcLibrary} onOpenSystems={onOpenSystems} activeUsers={activeUsers} />
{/* Money, which a custom system with the bank off does not have (3b2b). */}
{bankOn && <>
<CurrencyIconPanel token={token} globalSettings={globalSettings} fetchGlobalSettings={fetchGlobalSettings} system={globalSettings?.game_system} />
Expand Down Expand Up @@ -182,7 +183,7 @@ export function AdminPanel({
tempCityMapScale, setTempCityMapScale, globalSettings, fetchGlobalSettings, tempBattleMapScale, setTempBattleMapScale, activeBattleMapData, setIsAdminPayOpen, setIsAdminXpOpen, setIsCatalogueOpen,
secureModeEnabled, currentLocBattleMaps, enterBattleMap,
signs, fetchSigns, remoteFonts, setRemoteFonts, isPlacingSign, setIsPlacingSign, pendingSignPos, setPendingSignPos, selectedSignId, setSelectedSignId, signTransformMode, setSignTransformMode, signTransformActive, setSignTransformActive, handleUpdateSign, signMesh,
activeUsers, onGrantAccess, onRevokeAccess, onOpenNpcLibrary, onToggleHidden,
activeUsers, onGrantAccess, onRevokeAccess, onOpenNpcLibrary, onOpenSystems, onToggleHidden,
onExportPng, onStartRecording, onStopRecording, isRecording, isExporting, recordSecondsLeft,
gameSystem,
cityGenDrawMode, setCityGenDrawMode, genBoundaryTrail, setGenBoundaryTrail,
Expand All @@ -203,7 +204,7 @@ export function AdminPanel({
handleSaveDefault={handleSaveDefault} handleLoadDefault={handleLoadDefault}
setIsAdminPayOpen={setIsAdminPayOpen} setIsAdminXpOpen={setIsAdminXpOpen} setIsCatalogueOpen={setIsCatalogueOpen} secureModeEnabled={secureModeEnabled} onLogout={onLogout}
globalSettings={globalSettings} fetchGlobalSettings={fetchGlobalSettings}
onOpenNpcLibrary={onOpenNpcLibrary} activeUsers={activeUsers}
onOpenNpcLibrary={onOpenNpcLibrary} onOpenSystems={onOpenSystems} activeUsers={activeUsers}
/>
);
}
Expand Down Expand Up @@ -1045,7 +1046,7 @@ export function AdminPanel({

{adminTab === 'game' && (
<>
<TTRPGSystemPanel token={token} onOpenNpcLibrary={onOpenNpcLibrary} activeUsers={activeUsers} />
<TTRPGSystemPanel token={token} onOpenNpcLibrary={onOpenNpcLibrary} onOpenSystems={onOpenSystems} activeUsers={activeUsers} />
{/* Money, which a custom system with the bank off does not have (3b2b). */}
{bankOn && <>
<CurrencyIconPanel token={token} globalSettings={globalSettings} fetchGlobalSettings={fetchGlobalSettings} system={gameSystem} />
Expand Down Expand Up @@ -2297,7 +2298,11 @@ const SR6_HOUSE_RULES: HouseRuleDef[] = [
},
];

function TTRPGSystemPanel({ token, onOpenNpcLibrary, activeUsers }: { token: string; onOpenNpcLibrary?: () => void; activeUsers?: any[] }) {
function TTRPGSystemPanel({ token, onOpenNpcLibrary, onOpenSystems, activeUsers }: {
token: string; onOpenNpcLibrary?: () => void; activeUsers?: any[];
/** Opens SYSTEMS.EXE; the main admin's alone, so absent for a granted editor. */
onOpenSystems?: () => void;
}) {
const [open, setOpen] = useState(false);
const [system, setSystem] = useState<string>('generic');
const [systems, setSystems] = useState<PickerSystem[]>([]);
Expand All @@ -2315,6 +2320,12 @@ function TTRPGSystemPanel({ token, onOpenNpcLibrary, activeUsers }: { token: str
};

useEffect(() => { if (open) refresh(); }, [open]);
// SYSTEMS.EXE renamed, copied or deleted one: the picker shows it at once.
useEffect(() => {
const again = () => refresh();
window.addEventListener(SYSTEMS_CHANGED_EVENT, again);
return () => window.removeEventListener(SYSTEMS_CHANGED_EVENT, again);
}, []);

/** Switch the game. The picker has already asked; this says whether the server agreed. */
const selectSystem = async (id: string): Promise<boolean> => {
Expand All @@ -2339,6 +2350,12 @@ function TTRPGSystemPanel({ token, onOpenNpcLibrary, activeUsers }: { token: str
<p style={{ fontSize: '0.6rem', opacity: 0.6, margin: 0 }}>
Each system keeps its own characters, banks and token health; switching back restores them.
</p>
{onOpenSystems && <>
<button className="utility-btn" style={{ fontSize: '0.65rem' }} onClick={onOpenSystems}>SYSTEMS.EXE</button>
<p style={{ fontSize: '0.6rem', opacity: 0.6, margin: 0 }}>
Switching the game stays here, in the picker. SYSTEMS.EXE is for making and managing systems.
</p>
</>}
<HouseRulesPanel token={token} note={<ShortfallNote system={system} />} defs={[
...globalHouseRules(word, on, currenciesFor(system).length > 0),
...(system === 'cities_without_number' ? CWN_HOUSE_RULES : []),
Expand Down
Loading
Loading