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
90 changes: 83 additions & 7 deletions apps/agents-of-empire/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ interface GameInitializerProps {

function GameInitializer({ onReady }: GameInitializerProps) {
useEffect(() => {
const { initializeWorld, addStructure } = useGameStore.getState();
const { initializeWorld, addStructure, addQuest, addQuestline, updateQuest } = useGameStore.getState();

// Initialize terrain
initializeWorld(50, 50);
Expand All @@ -46,7 +46,7 @@ function GameInitializer({ onReady }: GameInitializerProps) {
});

// 2. CASTLE - Main goals (large, impressive)
addStructure({
const knowledgeCastle = addStructure({
type: "castle",
position: [40, 0, 10],
name: "Knowledge Castle",
Expand All @@ -55,15 +55,15 @@ function GameInitializer({ onReady }: GameInitializerProps) {
});

// 3. TOWER - Sub-goals (tall, watchtower style)
addStructure({
const scoutTower = addStructure({
type: "tower",
position: [8, 0, 8],
name: "Scout Tower",
description: "Sub-goal: Establish reconnaissance",
goalId: "sub-goal-scouting",
});

addStructure({
const watchtower = addStructure({
type: "tower",
position: [42, 0, 42],
name: "Watchtower",
Expand All @@ -72,14 +72,14 @@ function GameInitializer({ onReady }: GameInitializerProps) {
});

// 4. WORKSHOP - Tasks (building with work areas)
addStructure({
const codeWorkshop = addStructure({
type: "workshop",
position: [10, 0, 40],
name: "Code Workshop",
description: "Task: Craft agent solutions",
});

addStructure({
const researchLab = addStructure({
type: "workshop",
position: [40, 0, 40],
name: "Research Lab",
Expand All @@ -101,6 +101,81 @@ function GameInitializer({ onReady }: GameInitializerProps) {
description: "Agent rest and recovery point",
});

// ============================================================================
// Initialize Questline: "The Agent's Journey" (5-stage campaign)
// ============================================================================

const quest1 = addQuest({
title: "Establish Reconnaissance",
description: "Send agents to the Scout Tower",
status: "pending",
targetStructureId: scoutTower.id,
requiredAgents: 2,
assignedAgentIds: [],
rewards: ["+1 Agent Level"],
});

const quest2 = addQuest({
title: "Craft Agent Solutions",
description: "Assign agents to the Code Workshop",
status: "pending",
targetStructureId: codeWorkshop.id,
requiredAgents: 3,
assignedAgentIds: [],
rewards: ["+2 Agent Levels"],
prerequisiteQuestIds: [quest1.id],
});

const quest3 = addQuest({
title: "Analyze Data Patterns",
description: "Send agents to the Research Lab",
status: "pending",
targetStructureId: researchLab.id,
requiredAgents: 3,
assignedAgentIds: [],
rewards: ["+2 Agent Levels"],
prerequisiteQuestIds: [quest2.id],
});

const quest4 = addQuest({
title: "Defend the Perimeter",
description: "Send agents to the Watchtower",
status: "pending",
targetStructureId: watchtower.id,
requiredAgents: 4,
assignedAgentIds: [],
rewards: ["+3 Agent Levels"],
prerequisiteQuestIds: [quest3.id],
});

const quest5 = addQuest({
title: "Complete Research",
description: "Send agents to the Knowledge Castle",
status: "pending",
targetStructureId: knowledgeCastle.id,
requiredAgents: 5,
assignedAgentIds: [],
rewards: ["+5 Agent Levels", "Victory!"],
prerequisiteQuestIds: [quest4.id],
});

// Create the questline
const questline = addQuestline({
name: "The Agent's Journey",
description: "A comprehensive campaign to establish your agent empire",
status: "not_started",
questIds: [quest1.id, quest2.id, quest3.id, quest4.id, quest5.id],
currentQuestIndex: 0,
requiredCompletedQuests: 5,
});

// Link quests back to the questline
updateQuest(quest1.id, { questlineId: questline.id, position: 0 });
updateQuest(quest2.id, { questlineId: questline.id, position: 1 });
updateQuest(quest3.id, { questlineId: questline.id, position: 2 });
updateQuest(quest4.id, { questlineId: questline.id, position: 3 });
updateQuest(quest5.id, { questlineId: questline.id, position: 4 });

onReady();
}, [onReady]);

Expand Down Expand Up @@ -197,7 +272,8 @@ function GameScene() {
const handleStructureClick = useCallback(
(structureId: string, structure: Structure) => {
console.log("Structure clicked:", structure.name, structureId);
// Could show structure info panel here
// Set the selected structure to show info panel
useGameStore.getState().setSelectedStructure(structureId);
},
[]
);
Expand Down
121 changes: 121 additions & 0 deletions apps/agents-of-empire/src/store/gameStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ interface GameState {
selectionBox: { startX: number; startY: number; endX: number; endY: number; active: boolean } | null;
hoverAgentId: string | null;
hoverStructureId: string | null;
selectedStructureId: string | null;
contextMenuOpen: boolean;
contextMenuPosition: { x: number; y: number } | null;
contextMenuAgentId: string | null;
Expand Down Expand Up @@ -229,6 +230,12 @@ interface GameActions {
targetAgentId?: string,
duration?: number
) => void;
handleAgentCommunication: (
fromAgentId: string,
toAgentId: string,
message: string
) => void;
broadcastToParty: (partyId: string, fromAgentId: string, message: string) => void;

// Selection
selectAgent: (id: string) => void;
Expand Down Expand Up @@ -299,6 +306,7 @@ interface GameActions {
endSelectionBox: () => void;
setHoverAgent: (id: string | null) => void;
setHoveredStructure: (id: string | null) => void;
setSelectedStructure: (id: string | null) => void;
openContextMenu: (position: { x: number; y: number }, agentId: string) => void;
closeContextMenu: () => void;

Expand Down Expand Up @@ -347,6 +355,7 @@ export const useGameStore = create<GameStore>()(
selectionBox: null,
hoverAgentId: null,
hoverStructureId: null,
selectedStructureId: null,
contextMenuOpen: false,
contextMenuPosition: null,
contextMenuAgentId: null,
Expand Down Expand Up @@ -542,6 +551,64 @@ export const useGameStore = create<GameStore>()(
}, duration);
},

handleAgentCommunication: (fromAgentId, toAgentId, message) => {
const fromAgent = get().agents[fromAgentId];
const toAgent = get().agents[toAgentId];

if (!fromAgent || !toAgent) {
console.warn(`[handleAgentCommunication] One or both agents not found: ${fromAgentId}, ${toAgentId}`);
return;
}

// Set speech bubble on sender showing directed communication
get().setSpeechBubble(fromAgentId, message, toAgentId, 3000);

// If target agent is in the same party, show acknowledgment
if (fromAgent.partyId && fromAgent.partyId === toAgent.partyId) {
const acknowledgments = ["Got it!", "Understood!", "Copy that!", "On it!", "Roger!"];
setTimeout(() => {
get().setSpeechBubble(
toAgentId,
acknowledgments[Math.floor(Math.random() * acknowledgments.length)],
undefined,
2000
);
}, 1000);
}
},

broadcastToParty: (partyId, fromAgentId, message) => {
const party = get().parties[partyId];
const fromAgent = get().agents[fromAgentId];

if (!party || !fromAgent) {
console.warn(`[broadcastToParty] Party or agent not found: ${partyId}, ${fromAgentId}`);
return;
}

// Broadcast to all party members
party.memberIds.forEach((memberId, index) => {
if (memberId !== fromAgentId) {
const member = get().agents[memberId];
if (member) {
// Stagger responses for natural feel
setTimeout(() => {
const acknowledgments = ["Got it!", "Understood!", "Copy that!", "On it!", "Roger!", "Affirmative!"];
get().setSpeechBubble(
memberId,
acknowledgments[Math.floor(Math.random() * acknowledgments.length)],
undefined,
2000
);
}, 200 + index * 150);
}
}
});

// Sender shows broadcast message
get().setSpeechBubble(fromAgentId, message, undefined, 3000);
},

// Selection Actions
selectAgent: (id) => {
set((state) => {
Expand Down Expand Up @@ -926,10 +993,60 @@ export const useGameStore = create<GameStore>()(
},

assignQuestToAgents: (questId, agentIds) => {
// First update the quest metadata
get().updateQuest(questId, {
assignedAgentIds: agentIds,
status: "in_progress",
});

// Get the quest to find the target structure
const quest = get().quests[questId];
if (!quest || !quest.targetStructureId) {
console.warn(`[assignQuestToAgents] Quest ${questId} has no target structure`);
return;
}

// Get the target structure
const structure = get().structures[quest.targetStructureId];
if (!structure) {
console.warn(`[assignQuestToAgents] Structure ${quest.targetStructureId} not found`);
return;
}

// Calculate formation positions for agents around the structure
const structurePosition = structure.position;
const spacing = 2; // Distance between agents
const formationRadius = spacing * 2; // Radius of the formation circle

// Prepare batch updates for all assigned agents
const agentUpdates: Array<{ id: string; changes: Partial<GameAgent> }> = [];

agentIds.forEach((agentId, index) => {
const agent = get().agents[agentId];
if (!agent) return;

// Calculate position in circle formation around the structure
const angle = (index / agentIds.length) * Math.PI * 2;
const targetPosition: [number, number, number] = [
structurePosition[0] + Math.cos(angle) * formationRadius,
structurePosition[1],
structurePosition[2] + Math.sin(angle) * formationRadius,
];

agentUpdates.push({
id: agentId,
changes: {
targetPosition,
state: "MOVING",
currentTask: `Proceeding to ${structure.name}`,
},
});
});

// Apply all agent updates in a single batch
if (agentUpdates.length > 0) {
get().updateMultipleAgents(agentUpdates);
}
},

completeQuest: (id) => {
Expand Down Expand Up @@ -1081,6 +1198,10 @@ export const useGameStore = create<GameStore>()(
set({ hoverStructureId: id });
},

setSelectedStructure: (id) => {
set({ selectedStructureId: id });
},

openContextMenu: (position, agentId) => {
console.log("[gameStore] openContextMenu called", { position, agentId });
set({
Expand Down
23 changes: 22 additions & 1 deletion apps/agents-of-empire/src/ui/HUD.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { useCombat } from "../entities/Dragon";
import { ToolCard, ToolListItem, ToolIcon, RarityBadge, TOOL_TYPE_CONFIG, RARITY_CONFIG } from "./ToolCard";
import { screenToWorld } from "../core/CameraController";
import { useAgentBridge } from "../bridge/AgentBridge";
import { PartyPanel } from "./PartyPanel";
import { StructureInfoPanel } from "./StructureInfoPanel";

// ============================================================================
// Minimap Component
Expand Down Expand Up @@ -473,7 +475,7 @@ export function QuestTracker({ className = "" }: QuestTrackerProps) {
initial={{ opacity: 0, x: -50, y: -20 }}
animate={{ opacity: 1, x: 0, y: 0 }}
transition={{ duration: 0.5, ease: "easeOut" }}
className={`absolute top-4 left-4 bg-gray-900/95 border-2 border-empire-gold rounded-lg p-4 text-white w-80 shadow-lg shadow-empire-gold/20 ${className}`}
className={`absolute top-4 left-4 bg-gray-900/95 border-2 border-empire-gold rounded-lg p-4 text-white w-80 shadow-lg shadow-empire-gold/20 pointer-events-auto ${className}`}
>
{/* Classic RTS objectives header */}
<div className="flex items-center gap-2 mb-3 pb-2 border-b border-empire-gold/30">
Expand Down Expand Up @@ -729,6 +731,8 @@ export function HUD({ className = "" }: HUDProps) {
const contextMenuPosition = useGameStore((state) => state.contextMenuPosition);
const contextMenuAgentId = useGameStore((state) => state.contextMenuAgentId);
const closeContextMenu = useGameStore((state) => state.closeContextMenu);
const selectedStructureId = useGameStore((state) => state.selectedStructureId);
const setSelectedStructure = useGameStore((state) => state.setSelectedStructure);
const spawnDragon = useGameStore((state) => state.spawnDragon);
const agents = useAgentsShallow();
const bridge = useAgentBridge();
Expand Down Expand Up @@ -892,6 +896,23 @@ export function HUD({ className = "" }: HUDProps) {
{/* Agent panel - Classic RTS unit info (bottom-left) */}
<AgentPanel />

{/* Party panel - Bottom-right panel for party management */}
<div className="pointer-events-auto">
<PartyPanel />
</div>

{/* Structure Info Panel (has pointer events) */}
<AnimatePresence>
{selectedStructureId && (
<div className="pointer-events-auto">
<StructureInfoPanel
structureId={selectedStructureId}
onClose={() => setSelectedStructure(null)}
/>
</div>
)}
</AnimatePresence>

{/* Context menu (has pointer events) */}
<AnimatePresence>
{contextMenuOpen && contextMenuAgentId && contextMenuPosition && (
Expand Down
Loading
Loading