1
0
Fork 0

add player nicknames for socket clients

This commit is contained in:
Sean Sube 2024-05-11 05:17:03 -05:00
parent 1b84edfd50
commit 416b3cc5d2
Signed by: ssube
GPG Key ID: 3EED7B957D362AF1
4 changed files with 163 additions and 39 deletions

View File

@ -221,7 +221,7 @@ def launch_bot():
continue
event = event_queue.get()
logger.info("broadcasting event %s", event.type)
logger.debug("broadcasting %s event", event.type)
if client:
event_task = client.loop.create_task(broadcast_event(event))
@ -343,7 +343,7 @@ def embed_from_player(event: PlayerEvent):
description = f"{event.client} is now playing as {event.character}"
else:
title = "Player Left"
description = f"{event.client} has left the game, {event.character} will be played by the AI"
description = f"{event.client} has left the game. {event.character} is now controlled by an LLM"
player_embed = Embed(title=title, description=description)
return player_embed

View File

@ -3,7 +3,7 @@ from collections import deque
from json import dumps, loads
from logging import getLogger
from threading import Thread
from typing import Literal
from typing import Dict, Literal
from uuid import uuid4
import websockets
@ -27,6 +27,11 @@ logger = getLogger(__name__)
connected = set()
recent_events = deque(maxlen=100)
last_snapshot = None
player_names: Dict[str, str] = {}
def get_player_name(client_id: str) -> str:
return player_names.get(client_id, client_id)
async def handler(websocket):
@ -70,15 +75,40 @@ async def handler(websocket):
try:
# if this socket is attached to a character and that character's turn is active, wait for input
message = await websocket.recv()
logger.info(f"Received message for {id}: {message}")
player_name = get_player_name(id)
logger.info(f"Received message for {player_name}: {message}")
try:
data = loads(message)
message_type = data.get("type", None)
if message_type == "player":
if "name" in data:
new_player_name = data["name"]
existing_id = next(
(
k
for k, v in player_names.items()
if v == new_player_name
),
None,
)
if existing_id is not None:
logger.error(
f"Name {new_player_name} is already in use by {existing_id}"
)
continue
logger.info(
f"changing player name for {id} to {new_player_name}"
)
player_names[id] = new_player_name
elif "become" in data:
character_name = data["become"]
if has_player(character_name):
logger.error(f"Character {character_name} is already in use")
logger.error(
f"Character {character_name} is already in use"
)
continue
# TODO: should this always remove?
@ -96,18 +126,22 @@ async def handler(websocket):
)
llm_agent = llm_agent.fallback_agent
# player_name = data["player"]
player = RemotePlayer(
actor.name, actor.backstory, sync_turn, fallback_agent=llm_agent
actor.name,
actor.backstory,
sync_turn,
fallback_agent=llm_agent,
)
set_player(id, player)
logger.info(f"Client {id} is now character {character_name}")
logger.info(
f"Client {player_name} is now character {character_name}"
)
# swap out the LLM agent
set_actor_agent(actor.name, actor, player)
# notify all clients that this character is now active
player_event(character_name, id, "join")
player_event(character_name, player_name, "join")
player_list()
elif message_type == "input":
player = get_player(id)
@ -129,8 +163,9 @@ async def handler(websocket):
if player and isinstance(player, RemotePlayer):
remove_player(id)
logger.info("Disconnecting player for %s", player.name)
player_event(player.name, id, "leave")
player_name = get_player_name(id)
logger.info("Disconnecting player %s from %s", player_name, player.name)
player_event(player.name, player_name, "leave")
player_list()
actor, _ = get_actor_agent_for_name(player.name)
@ -138,7 +173,7 @@ async def handler(websocket):
logger.info("Restoring LLM agent for %s", player.name)
set_actor_agent(player.name, actor, player.fallback_agent)
logger.info("Client disconnected: %s", id)
logger.info("Client disconnected: %s", player_name)
socket_thread = None

View File

@ -20,6 +20,8 @@ import {
Switch,
FormGroup,
FormControlLabel,
TextField,
IconButton,
} from '@mui/material';
import { Allotment } from 'allotment';
@ -30,6 +32,8 @@ import { PlayerPanel } from './player.js';
import 'allotment/dist/style.css';
import './main.css';
import { HistoryPanel } from './history.js';
import { set } from 'lodash';
import { CheckBox, Save } from '@mui/icons-material';
const useWebSocket = (useWebSocketModule as any).default;
@ -74,15 +78,23 @@ export function DetailDialog(props: { setDetails: SetDetails; details: Maybe<Ite
}
export function App(props: AppProps) {
// client state - slice 1
const [ activeTurn, setActiveTurn ] = useState<boolean>(false);
const [ autoScroll, setAutoScroll ] = useState<boolean>(true);
const [ themeMode, setThemeMode ] = useState('light');
// client identity - slice 2
const [ clientId, setClientId ] = useState<string>('');
const [ clientName, setClientName ] = useState<string>('');
// world state - slice 3
const [ detailEntity, setDetailEntity ] = useState<Maybe<Item | Actor | Room>>(undefined);
const [ character, setCharacter ] = useState<Maybe<Actor>>(undefined);
const [ clientId, setClientId ] = useState<string>('');
const [ world, setWorld ] = useState<Maybe<World>>(undefined);
const [ themeMode, setThemeMode ] = useState('light');
const [ history, setHistory ] = useState<Array<string>>([]);
const [ players, setPlayers ] = useState<Record<string, string>>({});
// socket stuff
const [ history, setHistory ] = useState<Array<string>>([]);
const { lastMessage, readyState, sendMessage } = useWebSocket(props.socketUrl);
function setPlayer(actor: Maybe<Actor>) {
@ -99,6 +111,11 @@ export function App(props: AppProps) {
}
}
function sendName(name: string) {
sendMessage(JSON.stringify({ type: 'player', name: clientName }));
setClientName(name);
}
const theme = createTheme({
palette: {
mode: themeMode as PaletteMode,
@ -175,6 +192,22 @@ export function App(props: AppProps) {
sx={{ marginLeft: 'auto' }}
/>} label="Auto Scroll" />
</FormGroup>
<FormGroup row>
<TextField
label="Player Name"
value={clientName}
onChange={(e) => setClientName(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
sendName(clientName);
}
}}
sx={{ marginLeft: 'auto' }}
/>
<IconButton onClick={() => sendName(clientName)}>
<Save />
</IconButton>
</FormGroup>
</Stack>
</Alert>
<Stack direction="row" spacing={2}>

View File

@ -16,6 +16,12 @@
- [Reply Events](#reply-events)
- [Result Events](#result-events)
- [Status Events](#status-events)
- [Server-specific Events](#server-specific-events)
- [Websocket Server Events](#websocket-server-events)
- [Websocket New Client](#websocket-new-client)
- [Websocket Player Become Character](#websocket-player-become-character)
- [Websocket Player Input](#websocket-player-input)
- [Websocket Player Name](#websocket-player-name)
## Event Types
@ -88,3 +94,53 @@ more frequent progress updates when generating with slow models.
### Result Events
### Status Events
## Server-specific Events
### Websocket Server Events
The websocket server has a few unique message types that it uses to communicate metadata with socket clients.
#### Websocket New Client
Notify a new client of its unique ID.
```yaml
type: "id"
id: str
```
This is an outgoing event from the server to clients.
#### Websocket Player Become Character
A socket client wants to play as a character in the world.
```yaml
type: "player"
become: str
```
This is an incoming event from clients to the server.
#### Websocket Player Input
A socket client has sent some input, usually in response to a prompt.
```yaml
type: "input"
input: str
```
This is an incoming event from clients to the server.
#### Websocket Player Name
Update the player name attached to a socket client.
```yaml
type: "player"
name: str
```
This is an incoming event from clients to the server.