<p align="center">
    <img src="../../images/mascot.png" alt="Lobstar logo" width="256" />
</p>

> [!WARNING]
> This is an early prototype and the API is subject to change and bugs are expected.

A lightweight peer-to-peer multiplayer game session and lobby manager for web games.

[![npm version](https://img.shields.io/npm/v/@lobstar/core.svg)](https://www.npmjs.com/package/@lobstar/core)
[![license](https://img.shields.io/npm/l/@lobstar/core.svg)](https://github.com/jamsinclair/@lobstar/core/blob/main/LICENSE.md)
[![bundlephobia minzipped size](https://badgen.net/bundlephobia/minzip/@lobstar/core)](https://bundlephobia.com/package/@lobstar/core)

## Features

- Simple peer-to-peer networking with WebRTC (via [PeerJS](https://peerjs.com/))
- Host/client architecture with configurable lobby settings
- Player management (joining, leaving, kicking, ready states)
- Game state lifecycle (lobby, playing, game over)
- Event-based messaging system
- Robust error handling and state management

## Installation

```bash
npm install @lobstar/core
```

## Basic Usage

### Creating a Game Session

```typescript
import { GameSessionManager } from '@lobstar/core';

// Create a session manager with default options
const gameSession = new GameSessionManager({
  maxPlayers: 4,                // Maximum number of players allowed (default: 8)
  requireReadyBeforeStart: true, // Require all players to be ready before starting (default: true)
  debug: true                   // Enable debug logging (default: false)
});

// Listen for state changes
gameSession.on('stateChange', ({ state, previousState }) => {
  console.log(`Game state changed from ${previousState} to ${state}`);
});

// Host a new game
async function hostGame() {
  try {
    const lobbyId = await gameSession.host('HostPlayer');
    console.log(`Game hosted with lobby ID: ${lobbyId}`);
    
    // Share this lobby ID with other players so they can join
  } catch (error) {
    console.error('Failed to host game:', error);
  }
}

// Join an existing game
async function joinGame(lobbyId) {
  try {
    await gameSession.join(lobbyId, 'GuestPlayer');
    console.log('Successfully joined the game!');
  } catch (error) {
    console.error('Failed to join game:', error);
  }
}
```

### Managing Players and Game State

```typescript
// Set player ready state
gameSession.setReady(true);

// Start the game (host only)
if (gameSession.isHost()) {
  gameSession.startGame();
}

// Get current players
const players = gameSession.getPlayers();
console.log('Current players:', players);

// Kick a player (host only)
if (gameSession.isHost()) {
  gameSession.kickPlayer('player-id-to-kick');
}

// End the game (host only)
if (gameSession.isHost()) {
  gameSession.endGame();
}

// Leave the game
gameSession.leave();
```

### Custom Communication

The session manager provides a custom message system that allows you to send and receive messages between players.

This allows you to
- Extend the lobby experience with chat, character selection, etc.
- Send and receive game-specific messages between players on game start

> [!NOTE]
> The host will operate like a server, they can send messages to all players and receive messages from all players.
>
> The clients can only send messages and receive messages from the host.

```typescript
// Send a custom message to the host
gameSession.sendMessageToHost({
  type: 'move', 
  x: 100, 
  y: 200 
});

// Broadcast a message to all players
gameSession.broadcastMessage({
  type: 'itemCollected',
  playerId: 'player-id-123',
  itemId: 'power-up-22'
});

// Send a custom message to a specific player from the host
gameSession.sendMessage('player-id-123', {
  type: 'secretObjective',
  objective: 'Find the hidden gold necklace'
});

// Listen for custom messages
gameSession.on('customMessage', ({ data, peerId }) => {
  console.log(`Received message from ${peerId}:`, data);
});
```

## API Reference

### Constructor

```typescript
new GameSessionManager(options?: GameSessionOptions)
```

**Options:**
- `maxPlayers`: Maximum number of players allowed (default: 8)
- `requireReadyBeforeStart`: Require all players to be ready before starting (default: true)
- `debug`: Enable debug logging (default: false)
- `peerOptions`: Options to pass to the underlying PeerJS instance
- `playerJoinTimeoutMs`: Timeout for player join (default: 10000)

### Methods

#### Session Management
- `host(playerName: string, lobbyId?: string): Promise<string>` - Host a new game session
- `join(lobbyId: string, playerName: string): Promise<void>` - Join an existing game session
- `leave(): void` - Leave the current session

#### Game Flow
- `setReady(isReady: boolean): void` - Set player ready state
- `startGame(): void` - Start the game (host only)
- `endGame(): void` - End the game (host only)

#### Player Management
- `kickPlayer(playerId: string): void` - Kick a player (host only)
- `getPlayers(): PlayerMap` - Get all players
- `getPlayer(id: string): Player | null` - Get a specific player
- `getSelf(): Player | null` - Get the current player
- `getHost(): Player | null` - Get the host player

#### Communication
- `sendMessage(peerId: string, data: unknown): void` - Send a message to a specific player. Only the host can send messages to other players.
  Clients can only send messages to the host's `peerId`.
- `sendMessageToHost(data: unknown): void` - Send a message to the host.
- `broadcastMessage(data: unknown, excludeSelf: boolean = true): void` - Broadcast a message to all players.
Only the host can broadcast messages.
#### Status
- `isHost(): boolean` - Check if the current player is the host
- `getState(): SessionState` - Get the current session state
- `getMaxPlayers(): number` - Get the maximum allowed players

### Events

Listen to events using the `on` method:

```typescript
gameSession.on('eventName', (eventData) => {
  // Handle the event
});
```

Available events:
- `stateChange`: Fired when the session state changes
- `playersUpdate`: Fired when the players list is updated
- `customMessage`: Fired when a custom message is received
- `error`: Fired when an error occurs
- `kicked`: Fired when the player is kicked from the session

## Error Handling

The session manager provides detailed error information through the `error` event:

```typescript
gameSession.on('error', ({ code, message, context, originalError }) => {
  console.error(`Error [${code}]: ${message} (${context})`);
  if (originalError) {
    console.error('Original error:', originalError);
  }
});
```

Error codes are available in the `ERROR_CODES` constant.

## License

MIT
