Compare commits
2 Commits
102e05c8ba
...
5885e33531
| Author | SHA1 | Date | |
|---|---|---|---|
| 5885e33531 | |||
| 626ca51041 |
@@ -1,4 +1,4 @@
|
|||||||
import { FlaskConical, Lock, Check, Play } from 'lucide-react';
|
import { FlaskConical, Lock, Check, Play, ListOrdered, X } from 'lucide-react';
|
||||||
import { TutorialHint } from '@/components/game/TutorialHint';
|
import { TutorialHint } from '@/components/game/TutorialHint';
|
||||||
import { useGameStore } from '@/store';
|
import { useGameStore } from '@/store';
|
||||||
import { formatDuration, formatPercent, formatNumber } from '@ai-tycoon/shared';
|
import { formatDuration, formatPercent, formatNumber } from '@ai-tycoon/shared';
|
||||||
@@ -24,13 +24,17 @@ const CATEGORY_LABELS: Record<string, string> = {
|
|||||||
export function ResearchPage() {
|
export function ResearchPage() {
|
||||||
const completedResearch = useGameStore((s) => s.research.completedResearch);
|
const completedResearch = useGameStore((s) => s.research.completedResearch);
|
||||||
const activeResearch = useGameStore((s) => s.research.activeResearch);
|
const activeResearch = useGameStore((s) => s.research.activeResearch);
|
||||||
|
const researchQueue = useGameStore((s) => s.research.researchQueue);
|
||||||
const researchPoints = useGameStore((s) => s.research.researchPoints);
|
const researchPoints = useGameStore((s) => s.research.researchPoints);
|
||||||
const startResearch = useGameStore((s) => s.startResearch);
|
const startResearch = useGameStore((s) => s.startResearch);
|
||||||
|
const queueResearch = useGameStore((s) => s.queueResearch);
|
||||||
|
const removeFromResearchQueue = useGameStore((s) => s.removeFromResearchQueue);
|
||||||
const era = useGameStore((s) => s.meta.currentEra);
|
const era = useGameStore((s) => s.meta.currentEra);
|
||||||
|
|
||||||
const state = useGameStore.getState();
|
const state = useGameStore.getState();
|
||||||
const available = getAvailableResearch(state);
|
const available = getAvailableResearch(state);
|
||||||
const availableIds = new Set(available.map(n => n.id));
|
const availableIds = new Set(available.map(n => n.id));
|
||||||
|
const queuedIds = new Set(researchQueue);
|
||||||
|
|
||||||
const handleStart = (node: ResearchNode) => {
|
const handleStart = (node: ResearchNode) => {
|
||||||
if (activeResearch) return;
|
if (activeResearch) return;
|
||||||
@@ -43,6 +47,10 @@ export function ResearchPage() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleQueue = (node: ResearchNode) => {
|
||||||
|
queueResearch(node.id);
|
||||||
|
};
|
||||||
|
|
||||||
const categories = [...new Set(TECH_TREE.map(n => n.category))];
|
const categories = [...new Set(TECH_TREE.map(n => n.category))];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -60,7 +68,7 @@ export function ResearchPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TutorialHint id="research-intro">
|
<TutorialHint id="research-intro">
|
||||||
Only one research project can run at a time. Complete prerequisites to unlock advanced technologies that improve your models and infrastructure.
|
Queue up multiple research projects to run in sequence. Complete prerequisites to unlock advanced technologies that improve your models and infrastructure.
|
||||||
</TutorialHint>
|
</TutorialHint>
|
||||||
|
|
||||||
{activeResearch && (
|
{activeResearch && (
|
||||||
@@ -88,6 +96,36 @@ export function ResearchPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{researchQueue.length > 0 && (
|
||||||
|
<div className="bg-surface-900 border border-surface-700 rounded-xl p-4">
|
||||||
|
<div className="flex items-center gap-2 mb-3">
|
||||||
|
<ListOrdered size={16} className="text-surface-400" />
|
||||||
|
<span className="text-sm font-medium text-surface-300">Queue ({researchQueue.length})</span>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{researchQueue.map((id, idx) => {
|
||||||
|
const node = TECH_TREE.find(n => n.id === id);
|
||||||
|
if (!node) return null;
|
||||||
|
return (
|
||||||
|
<div key={id} className="flex items-center justify-between bg-surface-800 rounded-lg px-3 py-2">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<span className="text-xs text-surface-500 font-mono w-5">{idx + 1}.</span>
|
||||||
|
<span className="text-sm">{node.name}</span>
|
||||||
|
<span className="text-xs text-surface-500">{formatDuration(node.cost.ticks)}</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => removeFromResearchQueue(id)}
|
||||||
|
className="text-surface-500 hover:text-red-400 transition-colors p-1"
|
||||||
|
>
|
||||||
|
<X size={14} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{categories.map(category => {
|
{categories.map(category => {
|
||||||
const nodes = TECH_TREE.filter(n => n.category === category);
|
const nodes = TECH_TREE.filter(n => n.category === category);
|
||||||
return (
|
return (
|
||||||
@@ -99,24 +137,29 @@ export function ResearchPage() {
|
|||||||
{nodes.map(node => {
|
{nodes.map(node => {
|
||||||
const isCompleted = completedResearch.includes(node.id);
|
const isCompleted = completedResearch.includes(node.id);
|
||||||
const isActive = activeResearch?.researchId === node.id;
|
const isActive = activeResearch?.researchId === node.id;
|
||||||
|
const isQueued = queuedIds.has(node.id);
|
||||||
const isAvailable = availableIds.has(node.id);
|
const isAvailable = availableIds.has(node.id);
|
||||||
const isLocked = !isCompleted && !isActive && !isAvailable;
|
const isLocked = !isCompleted && !isActive && !isAvailable && !isQueued;
|
||||||
|
const canStart = isAvailable && !activeResearch;
|
||||||
|
const canQueue = isAvailable && !!activeResearch;
|
||||||
|
const queuePosition = isQueued ? researchQueue.indexOf(node.id) + 1 : -1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={node.id}
|
key={node.id}
|
||||||
onClick={() => isAvailable && !activeResearch && handleStart(node)}
|
onClick={() => canStart ? handleStart(node) : canQueue ? handleQueue(node) : undefined}
|
||||||
className={`rounded-xl border p-4 transition-all ${
|
className={`rounded-xl border p-4 transition-all ${
|
||||||
isCompleted ? 'border-success/50 bg-success/5 opacity-70' :
|
isCompleted ? 'border-success/50 bg-success/5 opacity-70' :
|
||||||
isActive ? 'border-accent/50 bg-accent/5' :
|
isActive ? 'border-accent/50 bg-accent/5' :
|
||||||
isAvailable && !activeResearch ? `${CATEGORY_COLORS[category]} hover:brightness-110 cursor-pointer ring-1 ring-transparent hover:ring-accent/30` :
|
isQueued ? 'border-amber-500/50 bg-amber-500/5' :
|
||||||
isAvailable ? `${CATEGORY_COLORS[category]}` :
|
(canStart || canQueue) ? `${CATEGORY_COLORS[category]} hover:brightness-110 cursor-pointer ring-1 ring-transparent hover:ring-accent/30` :
|
||||||
'border-surface-700 bg-surface-900 opacity-50'
|
'border-surface-700 bg-surface-900 opacity-50'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between mb-2">
|
<div className="flex items-start justify-between mb-2">
|
||||||
<h4 className="font-medium text-sm">{node.name}</h4>
|
<h4 className="font-medium text-sm">{node.name}</h4>
|
||||||
{isCompleted && <Check size={16} className="text-success flex-shrink-0" />}
|
{isCompleted && <Check size={16} className="text-success flex-shrink-0" />}
|
||||||
|
{isQueued && <span className="text-xs text-amber-400 font-mono flex-shrink-0">#{queuePosition}</span>}
|
||||||
{isLocked && <Lock size={14} className="text-surface-500 flex-shrink-0" />}
|
{isLocked && <Lock size={14} className="text-surface-500 flex-shrink-0" />}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-surface-400 mb-3">{node.description}</p>
|
<p className="text-xs text-surface-400 mb-3">{node.description}</p>
|
||||||
@@ -125,17 +168,32 @@ export function ResearchPage() {
|
|||||||
{formatDuration(node.cost.ticks)} · {formatNumber(node.cost.compute)} compute
|
{formatDuration(node.cost.ticks)} · {formatNumber(node.cost.compute)} compute
|
||||||
{node.cost.researchPoints > 0 && ` · ${node.cost.researchPoints} RP`}
|
{node.cost.researchPoints > 0 && ` · ${node.cost.researchPoints} RP`}
|
||||||
</div>
|
</div>
|
||||||
{isAvailable && !activeResearch && (
|
{canStart && (
|
||||||
<button
|
<button
|
||||||
onClick={() => handleStart(node)}
|
onClick={(e) => { e.stopPropagation(); handleStart(node); }}
|
||||||
className="flex items-center gap-1 bg-accent hover:bg-accent-dark text-white rounded px-2 py-1 text-xs"
|
className="flex items-center gap-1 bg-accent hover:bg-accent-dark text-white rounded px-2 py-1 text-xs"
|
||||||
>
|
>
|
||||||
<Play size={12} />
|
<Play size={12} />
|
||||||
Start
|
Start
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{isAvailable && activeResearch && (
|
{canQueue && (
|
||||||
<span className="text-xs text-surface-500 italic">Queue after current</span>
|
<button
|
||||||
|
onClick={(e) => { e.stopPropagation(); handleQueue(node); }}
|
||||||
|
className="flex items-center gap-1 bg-amber-600 hover:bg-amber-700 text-white rounded px-2 py-1 text-xs"
|
||||||
|
>
|
||||||
|
<ListOrdered size={12} />
|
||||||
|
Queue
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{isQueued && (
|
||||||
|
<button
|
||||||
|
onClick={(e) => { e.stopPropagation(); removeFromResearchQueue(node.id); }}
|
||||||
|
className="flex items-center gap-1 bg-surface-700 hover:bg-red-900 text-surface-300 hover:text-red-300 rounded px-2 py-1 text-xs transition-colors"
|
||||||
|
>
|
||||||
|
<X size={12} />
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{node.prerequisites.length > 0 && isLocked && (
|
{node.prerequisites.length > 0 && isLocked && (
|
||||||
|
|||||||
@@ -138,6 +138,8 @@ interface Actions {
|
|||||||
setProductPricing: (productLineId: string, field: string, value: number) => void;
|
setProductPricing: (productLineId: string, field: string, value: number) => void;
|
||||||
toggleProductLine: (productLineId: string) => void;
|
toggleProductLine: (productLineId: string) => void;
|
||||||
startResearch: (research: ActiveResearch) => void;
|
startResearch: (research: ActiveResearch) => void;
|
||||||
|
queueResearch: (researchId: string) => void;
|
||||||
|
removeFromResearchQueue: (researchId: string) => void;
|
||||||
hireDepartment: (departmentId: string, count: number) => void;
|
hireDepartment: (departmentId: string, count: number) => void;
|
||||||
purchaseDataset: (dataset: OwnedDataset, cost: number) => void;
|
purchaseDataset: (dataset: OwnedDataset, cost: number) => void;
|
||||||
raiseFunding: (roundType: FundingRoundType) => void;
|
raiseFunding: (roundType: FundingRoundType) => void;
|
||||||
@@ -1170,6 +1172,35 @@ export const useGameStore = create<Store>()(
|
|||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
queueResearch: (researchId) => set((s) => {
|
||||||
|
if (s.research.researchQueue.includes(researchId)) return s;
|
||||||
|
const node = TECH_TREE.find(n => n.id === researchId);
|
||||||
|
if (!node) return s;
|
||||||
|
const rpCost = node.cost.researchPoints ?? 0;
|
||||||
|
if (rpCost > s.research.researchPoints) return s;
|
||||||
|
return {
|
||||||
|
research: {
|
||||||
|
...s.research,
|
||||||
|
researchQueue: [...s.research.researchQueue, researchId],
|
||||||
|
researchPoints: s.research.researchPoints - rpCost,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
|
||||||
|
removeFromResearchQueue: (researchId) => set((s) => {
|
||||||
|
const idx = s.research.researchQueue.indexOf(researchId);
|
||||||
|
if (idx === -1) return s;
|
||||||
|
const node = TECH_TREE.find(n => n.id === researchId);
|
||||||
|
const rpRefund = node?.cost.researchPoints ?? 0;
|
||||||
|
return {
|
||||||
|
research: {
|
||||||
|
...s.research,
|
||||||
|
researchQueue: s.research.researchQueue.filter(id => id !== researchId),
|
||||||
|
researchPoints: s.research.researchPoints + rpRefund,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
|
||||||
hireDepartment: (departmentId, count) => set((s) => {
|
hireDepartment: (departmentId, count) => set((s) => {
|
||||||
const costPerHire = 2000;
|
const costPerHire = 2000;
|
||||||
const totalCost = costPerHire * count;
|
const totalCost = costPerHire * count;
|
||||||
@@ -1450,7 +1481,12 @@ export const useGameStore = create<Store>()(
|
|||||||
infraNav: { level: 'clusters' },
|
infraNav: { level: 'clusters' },
|
||||||
} as unknown as Store;
|
} as unknown as Store;
|
||||||
}
|
}
|
||||||
return _persisted as Store;
|
const s = _persisted as Record<string, unknown>;
|
||||||
|
const research = s.research as Record<string, unknown>;
|
||||||
|
if (!research.researchQueue) {
|
||||||
|
s.research = { ...research, researchQueue: [] };
|
||||||
|
}
|
||||||
|
return s as unknown as Store;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ export function processDeveloperEcosystem(
|
|||||||
): DeveloperEcosystem {
|
): DeveloperEcosystem {
|
||||||
const updated = { ...eco };
|
const updated = { ...eco };
|
||||||
|
|
||||||
|
const eraCap = TAM_BASE_SIZES[era].developer;
|
||||||
|
|
||||||
const growthRate =
|
const growthRate =
|
||||||
BASE_DEV_GROWTH +
|
BASE_DEV_GROWTH +
|
||||||
apiFreeTierDevs * FREE_TIER_DEV_MULTIPLIER +
|
apiFreeTierDevs * FREE_TIER_DEV_MULTIPLIER +
|
||||||
@@ -29,8 +31,9 @@ export function processDeveloperEcosystem(
|
|||||||
updated.devRelSpending * DEV_REL_EFFECTIVENESS +
|
updated.devRelSpending * DEV_REL_EFFECTIVENESS +
|
||||||
updated.sdkCoverage * SDK_GROWTH_BONUS;
|
updated.sdkCoverage * SDK_GROWTH_BONUS;
|
||||||
|
|
||||||
updated.communityGrowthRate = growthRate;
|
const logisticDamping = Math.max(0, 1 - updated.communitySize / Math.max(1, eraCap));
|
||||||
updated.communitySize = Math.max(0, updated.communitySize + updated.communitySize * growthRate);
|
updated.communityGrowthRate = growthRate * logisticDamping;
|
||||||
|
updated.communitySize = Math.max(0, updated.communitySize + updated.communitySize * updated.communityGrowthRate);
|
||||||
|
|
||||||
if (updated.communitySize < 10 && apiTotalDevs > 0) {
|
if (updated.communitySize < 10 && apiTotalDevs > 0) {
|
||||||
updated.communitySize += 1 + apiTotalDevs * 0.1;
|
updated.communitySize += 1 + apiTotalDevs * 0.1;
|
||||||
@@ -47,7 +50,6 @@ export function processDeveloperEcosystem(
|
|||||||
updated.documentationQuality += (docTarget - updated.documentationQuality) * 0.003;
|
updated.documentationQuality += (docTarget - updated.documentationQuality) * 0.003;
|
||||||
updated.documentationQuality = Math.min(1, Math.max(0, updated.documentationQuality));
|
updated.documentationQuality = Math.min(1, Math.max(0, updated.documentationQuality));
|
||||||
|
|
||||||
const eraCap = TAM_BASE_SIZES[era].developer;
|
|
||||||
const communityNorm = Math.min(1, updated.communitySize / Math.max(1, eraCap * 0.1));
|
const communityNorm = Math.min(1, updated.communitySize / Math.max(1, eraCap * 0.1));
|
||||||
const activeRatio = updated.communitySize > 0
|
const activeRatio = updated.communitySize > 0
|
||||||
? Math.min(1, updated.activeDevelopers / updated.communitySize)
|
? Math.min(1, updated.activeDevelopers / updated.communitySize)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { GameState, ResearchState, ComputeState } from '@ai-tycoon/shared';
|
import type { GameState, ResearchState, ActiveResearch, ComputeState } from '@ai-tycoon/shared';
|
||||||
import { TECH_TREE } from '../data/techTree';
|
import { TECH_TREE } from '../data/techTree';
|
||||||
|
|
||||||
export interface ResearchTickResult {
|
export interface ResearchTickResult {
|
||||||
@@ -6,6 +6,42 @@ export interface ResearchTickResult {
|
|||||||
researchCompleted: string | null;
|
researchCompleted: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function promoteFromQueue(
|
||||||
|
research: ResearchState,
|
||||||
|
state: GameState,
|
||||||
|
): ResearchState {
|
||||||
|
const queue = research.researchQueue;
|
||||||
|
if (queue.length === 0) return research;
|
||||||
|
|
||||||
|
const eraOrder = ['startup', 'scaleup', 'bigtech', 'agi'];
|
||||||
|
const currentEraIdx = eraOrder.indexOf(state.meta.currentEra);
|
||||||
|
const completed = research.completedResearch;
|
||||||
|
|
||||||
|
for (let i = 0; i < queue.length; i++) {
|
||||||
|
const id = queue[i];
|
||||||
|
const node = TECH_TREE.find(n => n.id === id);
|
||||||
|
if (!node) continue;
|
||||||
|
if (eraOrder.indexOf(node.era) > currentEraIdx) continue;
|
||||||
|
if (node.prerequisites.some(p => !completed.includes(p))) continue;
|
||||||
|
|
||||||
|
const active: ActiveResearch = {
|
||||||
|
researchId: id,
|
||||||
|
progressTicks: 0,
|
||||||
|
totalTicks: node.cost.ticks,
|
||||||
|
allocatedResearchers: state.talent.departments.research.headcount,
|
||||||
|
allocatedCompute: node.cost.compute,
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...research,
|
||||||
|
activeResearch: active,
|
||||||
|
researchQueue: [...queue.slice(0, i), ...queue.slice(i + 1)],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return research;
|
||||||
|
}
|
||||||
|
|
||||||
export function processResearch(state: GameState, compute: ComputeState): ResearchTickResult {
|
export function processResearch(state: GameState, compute: ComputeState): ResearchTickResult {
|
||||||
const active = state.research.activeResearch;
|
const active = state.research.activeResearch;
|
||||||
if (!active) return { research: state.research, researchCompleted: null };
|
if (!active) return { research: state.research, researchCompleted: null };
|
||||||
@@ -17,13 +53,18 @@ export function processResearch(state: GameState, compute: ComputeState): Resear
|
|||||||
const newProgress = active.progressTicks + speedMultiplier;
|
const newProgress = active.progressTicks + speedMultiplier;
|
||||||
|
|
||||||
if (newProgress >= active.totalTicks) {
|
if (newProgress >= active.totalTicks) {
|
||||||
|
const completedResearch = {
|
||||||
|
...state.research,
|
||||||
|
completedResearch: [...state.research.completedResearch, active.researchId],
|
||||||
|
activeResearch: null,
|
||||||
|
researchPoints: state.research.researchPoints + 1,
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
research: {
|
research: promoteFromQueue(completedResearch, {
|
||||||
...state.research,
|
...state,
|
||||||
completedResearch: [...state.research.completedResearch, active.researchId],
|
research: completedResearch,
|
||||||
activeResearch: null,
|
}),
|
||||||
researchPoints: state.research.researchPoints + 1,
|
|
||||||
},
|
|
||||||
researchCompleted: active.researchId,
|
researchCompleted: active.researchId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -40,10 +81,12 @@ export function processResearch(state: GameState, compute: ComputeState): Resear
|
|||||||
export function getAvailableResearch(state: GameState): typeof TECH_TREE {
|
export function getAvailableResearch(state: GameState): typeof TECH_TREE {
|
||||||
const eraOrder = ['startup', 'scaleup', 'bigtech', 'agi'];
|
const eraOrder = ['startup', 'scaleup', 'bigtech', 'agi'];
|
||||||
const currentEraIdx = eraOrder.indexOf(state.meta.currentEra);
|
const currentEraIdx = eraOrder.indexOf(state.meta.currentEra);
|
||||||
|
const queuedIds = new Set(state.research.researchQueue);
|
||||||
|
|
||||||
return TECH_TREE.filter(node => {
|
return TECH_TREE.filter(node => {
|
||||||
if (state.research.completedResearch.includes(node.id)) return false;
|
if (state.research.completedResearch.includes(node.id)) return false;
|
||||||
if (state.research.activeResearch?.researchId === node.id) return false;
|
if (state.research.activeResearch?.researchId === node.id) return false;
|
||||||
|
if (queuedIds.has(node.id)) return false;
|
||||||
if (eraOrder.indexOf(node.era) > currentEraIdx) return false;
|
if (eraOrder.indexOf(node.era) > currentEraIdx) return false;
|
||||||
if (node.prerequisites.some(p => !state.research.completedResearch.includes(p))) return false;
|
if (node.prerequisites.some(p => !state.research.completedResearch.includes(p))) return false;
|
||||||
if (node.cost.researchPoints > state.research.researchPoints) return false;
|
if (node.cost.researchPoints > state.research.researchPoints) return false;
|
||||||
|
|||||||
@@ -2,14 +2,19 @@ import type { GameState, ActiveResearch } from '@ai-tycoon/shared';
|
|||||||
import { TECH_TREE } from '@ai-tycoon/game-engine';
|
import { TECH_TREE } from '@ai-tycoon/game-engine';
|
||||||
|
|
||||||
export function startResearch(state: GameState, research: ActiveResearch): boolean {
|
export function startResearch(state: GameState, research: ActiveResearch): boolean {
|
||||||
if (state.research.activeResearch) return false;
|
|
||||||
|
|
||||||
const node = TECH_TREE.find(n => n.id === research.researchId);
|
const node = TECH_TREE.find(n => n.id === research.researchId);
|
||||||
if (!node) return false;
|
if (!node) return false;
|
||||||
|
|
||||||
const rpCost = node.cost.researchPoints ?? 0;
|
const rpCost = node.cost.researchPoints ?? 0;
|
||||||
if (rpCost > state.research.researchPoints) return false;
|
if (rpCost > state.research.researchPoints) return false;
|
||||||
|
|
||||||
|
if (state.research.activeResearch) {
|
||||||
|
if (state.research.researchQueue.includes(research.researchId)) return false;
|
||||||
|
state.research.researchQueue.push(research.researchId);
|
||||||
|
state.research.researchPoints -= rpCost;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
state.research.activeResearch = research;
|
state.research.activeResearch = research;
|
||||||
state.research.researchPoints -= rpCost;
|
state.research.researchPoints -= rpCost;
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { Era } from './gameState';
|
|||||||
export interface ResearchState {
|
export interface ResearchState {
|
||||||
completedResearch: string[];
|
completedResearch: string[];
|
||||||
activeResearch: ActiveResearch | null;
|
activeResearch: ActiveResearch | null;
|
||||||
|
researchQueue: string[];
|
||||||
researchPoints: number;
|
researchPoints: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,5 +42,6 @@ export interface ResearchEffect {
|
|||||||
export const INITIAL_RESEARCH: ResearchState = {
|
export const INITIAL_RESEARCH: ResearchState = {
|
||||||
completedResearch: [],
|
completedResearch: [],
|
||||||
activeResearch: null,
|
activeResearch: null,
|
||||||
|
researchQueue: [],
|
||||||
researchPoints: 0,
|
researchPoints: 0,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user