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 continue
event = event_queue.get() event = event_queue.get()
logger.info("broadcasting event %s", event.type) logger.debug("broadcasting %s event", event.type)
if client: if client:
event_task = client.loop.create_task(broadcast_event(event)) 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}" description = f"{event.client} is now playing as {event.character}"
else: else:
title = "Player Left" 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) player_embed = Embed(title=title, description=description)
return player_embed return player_embed

View File

@ -3,7 +3,7 @@ from collections import deque
from json import dumps, loads from json import dumps, loads
from logging import getLogger from logging import getLogger
from threading import Thread from threading import Thread
from typing import Literal from typing import Dict, Literal
from uuid import uuid4 from uuid import uuid4
import websockets import websockets
@ -27,6 +27,11 @@ logger = getLogger(__name__)
connected = set() connected = set()
recent_events = deque(maxlen=100) recent_events = deque(maxlen=100)
last_snapshot = None 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): async def handler(websocket):
@ -70,15 +75,40 @@ async def handler(websocket):
try: try:
# if this socket is attached to a character and that character's turn is active, wait for input # if this socket is attached to a character and that character's turn is active, wait for input
message = await websocket.recv() 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: try:
data = loads(message) data = loads(message)
message_type = data.get("type", None) message_type = data.get("type", None)
if message_type == "player": 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"] character_name = data["become"]
if has_player(character_name): 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 continue
# TODO: should this always remove? # TODO: should this always remove?
@ -96,18 +126,22 @@ async def handler(websocket):
) )
llm_agent = llm_agent.fallback_agent llm_agent = llm_agent.fallback_agent
# player_name = data["player"]
player = RemotePlayer( 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) 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 # swap out the LLM agent
set_actor_agent(actor.name, actor, player) set_actor_agent(actor.name, actor, player)
# notify all clients that this character is now active # notify all clients that this character is now active
player_event(character_name, id, "join") player_event(character_name, player_name, "join")
player_list() player_list()
elif message_type == "input": elif message_type == "input":
player = get_player(id) player = get_player(id)
@ -129,8 +163,9 @@ async def handler(websocket):
if player and isinstance(player, RemotePlayer): if player and isinstance(player, RemotePlayer):
remove_player(id) remove_player(id)
logger.info("Disconnecting player for %s", player.name) player_name = get_player_name(id)
player_event(player.name, id, "leave") logger.info("Disconnecting player %s from %s", player_name, player.name)
player_event(player.name, player_name, "leave")
player_list() player_list()
actor, _ = get_actor_agent_for_name(player.name) 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) logger.info("Restoring LLM agent for %s", player.name)
set_actor_agent(player.name, actor, player.fallback_agent) 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 socket_thread = None

View File

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

View File

@ -16,6 +16,12 @@
- [Reply Events](#reply-events) - [Reply Events](#reply-events)
- [Result Events](#result-events) - [Result Events](#result-events)
- [Status Events](#status-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 ## Event Types
@ -88,3 +94,53 @@ more frequent progress updates when generating with slow models.
### Result Events ### Result Events
### Status 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.