Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -51,7 +51,7 @@ PRAIRIE_LOCATIONS = {
|
|
51 |
"Wyoming Spring Creek": (41.6666, -106.6666)
|
52 |
}
|
53 |
|
54 |
-
# Directories
|
55 |
for d in ["chat_logs", "audio_logs"]:
|
56 |
os.makedirs(d, exist_ok=True)
|
57 |
|
@@ -59,6 +59,28 @@ CHAT_DIR = "chat_logs"
|
|
59 |
AUDIO_DIR = "audio_logs"
|
60 |
STATE_FILE = "user_state.txt"
|
61 |
CHAT_FILE = os.path.join(CHAT_DIR, "quest_log.md")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
62 |
|
63 |
# Helpers
|
64 |
def format_timestamp(username=""):
|
@@ -162,7 +184,8 @@ def init_session_state():
|
|
162 |
'speech_processed': False, 'players': {}, 'last_update': time.time(),
|
163 |
'update_interval': 1, 'x_pos': 0, 'z_pos': 0, 'move_left': False,
|
164 |
'move_right': False, 'move_up': False, 'move_down': False,
|
165 |
-
'prairie_players': {}, 'last_chat_update': 0
|
|
|
166 |
}
|
167 |
for k, v in defaults.items():
|
168 |
if k not in st.session_state:
|
@@ -175,6 +198,17 @@ def init_session_state():
|
|
175 |
st.session_state.username = random.choice(list(CHARACTERS.keys()))
|
176 |
asyncio.run(save_chat_entry(st.session_state.username, "πΊοΈ Begins the Rocky Mountain Quest!", CHARACTERS[st.session_state.username]["voice"]))
|
177 |
save_username(st.session_state.username)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
178 |
|
179 |
init_session_state()
|
180 |
|
@@ -187,6 +221,7 @@ async def websocket_handler(websocket, path):
|
|
187 |
st.session_state.active_connections[room_id][client_id] = websocket
|
188 |
username = st.session_state.username
|
189 |
|
|
|
190 |
if "prairie" in path:
|
191 |
st.session_state.prairie_players[client_id] = {
|
192 |
"username": username,
|
@@ -196,14 +231,16 @@ async def websocket_handler(websocket, path):
|
|
196 |
}
|
197 |
await broadcast_message(f"System|{username} joins the prairie!", room_id)
|
198 |
else:
|
199 |
-
|
200 |
-
"username
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
|
|
|
|
207 |
await broadcast_message(f"System|{username} joins the quest!", room_id)
|
208 |
|
209 |
try:
|
@@ -211,10 +248,30 @@ async def websocket_handler(websocket, path):
|
|
211 |
if '|' in message:
|
212 |
sender, content = message.split('|', 1)
|
213 |
voice = CHARACTERS.get(sender, {"voice": "en-US-AriaNeural"})["voice"]
|
|
|
214 |
if content.startswith("MOVE:"):
|
215 |
_, x, z = content.split(":")
|
216 |
-
|
217 |
-
st.session_state.players[client_id]["
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
218 |
elif content.startswith("PRAIRIE:"):
|
219 |
action, value = content.split(":", 1)
|
220 |
if action == "ANIMAL":
|
@@ -224,10 +281,6 @@ async def websocket_handler(websocket, path):
|
|
224 |
st.session_state.prairie_players[client_id]["location"] = target
|
225 |
action_msg = f"{sender} ({st.session_state.prairie_players[client_id]['animal']}) moves to {value}"
|
226 |
await save_chat_entry(sender, action_msg, voice)
|
227 |
-
elif content.startswith("SCORE:"):
|
228 |
-
st.session_state.players[client_id]["score"] = int(content.split(":")[1])
|
229 |
-
elif content.startswith("TREASURE:"):
|
230 |
-
st.session_state.players[client_id]["treasures"] = int(content.split(":")[1])
|
231 |
else:
|
232 |
await save_chat_entry(sender, content, voice)
|
233 |
await perform_arxiv_search(content, sender)
|
@@ -244,9 +297,10 @@ async def websocket_handler(websocket, path):
|
|
244 |
async def periodic_update():
|
245 |
while True:
|
246 |
if st.session_state.active_connections.get("quest"):
|
|
|
247 |
player_list = ", ".join([p["username"] for p in st.session_state.players.values()]) or "No adventurers yet!"
|
248 |
message = f"π’ Quest Update: Active Adventurers - {player_list}"
|
249 |
-
player_data = json.dumps(list(
|
250 |
await broadcast_message(f"MAP_UPDATE:{player_data}", "quest")
|
251 |
|
252 |
prairie_list = ", ".join([f"{p['username']} ({p['animal']})" for p in st.session_state.prairie_players.values()]) or "No animals yet!"
|
@@ -254,9 +308,9 @@ async def periodic_update():
|
|
254 |
prairie_data = json.dumps(list(st.session_state.prairie_players.values()))
|
255 |
await broadcast_message(f"PRAIRIE_UPDATE:{prairie_data}", "quest")
|
256 |
|
257 |
-
# Multicast chat update every second
|
258 |
chat_content = await load_chat()
|
259 |
await broadcast_message(f"CHAT_UPDATE:{json.dumps(chat_content[-10:])}", "quest")
|
|
|
260 |
await save_chat_entry("System", f"{message}\n{prairie_message}", "en-US-AriaNeural")
|
261 |
await asyncio.sleep(st.session_state.update_interval)
|
262 |
|
@@ -351,6 +405,7 @@ rocky_map_html = f"""
|
|
351 |
let players = {{}};
|
352 |
const playerMeshes = {{}};
|
353 |
let treasures = [];
|
|
|
354 |
let xPos = 0, zPos = 0, moveLeft = false, moveRight = false, moveUp = false, moveDown = false, collect = false;
|
355 |
let score = 0, treasureCount = 0;
|
356 |
|
@@ -363,22 +418,31 @@ rocky_map_html = f"""
|
|
363 |
scene.add(playerMesh);
|
364 |
players[playerName] = {{ mesh: playerMesh, score: 0, treasures: 0 }};
|
365 |
|
366 |
-
// Treasure
|
367 |
-
function
|
368 |
-
|
369 |
-
|
370 |
-
new THREE.
|
371 |
-
|
372 |
-
|
373 |
-
|
374 |
-
|
375 |
-
|
376 |
-
|
377 |
-
|
378 |
-
|
379 |
-
|
380 |
-
|
381 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
382 |
}}
|
383 |
|
384 |
// Controls
|
@@ -413,13 +477,14 @@ rocky_map_html = f"""
|
|
413 |
if (collect) {{
|
414 |
for (let i = treasures.length - 1; i >= 0; i--) {{
|
415 |
if (playerMesh.position.distanceTo(treasures[i].position) < 2) {{
|
|
|
416 |
scene.remove(treasures[i]);
|
417 |
treasures.splice(i, 1);
|
|
|
418 |
score += 50;
|
419 |
treasureCount += 1;
|
420 |
ws.send(`${{playerName}}|SCORE:${{score}}`);
|
421 |
ws.send(`${{playerName}}|TREASURE:${{treasureCount}}`);
|
422 |
-
spawnTreasure();
|
423 |
}}
|
424 |
}}
|
425 |
}}
|
@@ -428,13 +493,6 @@ rocky_map_html = f"""
|
|
428 |
camera.lookAt(xPos, 0, zPos);
|
429 |
}}
|
430 |
|
431 |
-
function updateTreasures(delta) {{
|
432 |
-
treasures.forEach(treasure => {{
|
433 |
-
treasure.rotation.y += delta;
|
434 |
-
}});
|
435 |
-
if (Math.random() < 0.01) spawnTreasure();
|
436 |
-
}}
|
437 |
-
|
438 |
function updatePlayers(playerData) {{
|
439 |
playerData.forEach(player => {{
|
440 |
if (!playerMeshes[player.username]) {{
|
@@ -446,6 +504,13 @@ rocky_map_html = f"""
|
|
446 |
const mesh = playerMeshes[player.username];
|
447 |
mesh.position.set(player.x, 1, player.z);
|
448 |
players[player.username] = {{ mesh: mesh, score: player.score, treasures: player.treasures }};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
449 |
}});
|
450 |
document.getElementById('players').textContent = `Players: ${{Object.keys(playerMeshes).length}}`;
|
451 |
document.getElementById('score').textContent = `Score: ${{score}}`;
|
@@ -462,6 +527,10 @@ rocky_map_html = f"""
|
|
462 |
const chatBox = document.getElementById('chatBox');
|
463 |
chatBox.innerHTML = chatData.map(line => `<p>${{line}}</p>`).join('');
|
464 |
chatBox.scrollTop = chatBox.scrollHeight;
|
|
|
|
|
|
|
|
|
465 |
}} else if (!data.startsWith('PRAIRIE_UPDATE:')) {{
|
466 |
const [sender, message] = data.split('|');
|
467 |
const chatBox = document.getElementById('chatBox');
|
@@ -478,12 +547,10 @@ rocky_map_html = f"""
|
|
478 |
lastTime = currentTime;
|
479 |
|
480 |
updatePlayer(delta);
|
481 |
-
|
482 |
renderer.render(scene, camera);
|
483 |
}}
|
484 |
|
485 |
-
// Initial spawn
|
486 |
-
for (let i = 0; i < 5; i++) spawnTreasure();
|
487 |
animate();
|
488 |
</script>
|
489 |
</body>
|
@@ -500,6 +567,9 @@ def main():
|
|
500 |
st.sidebar.write(f"π΅ Treasures: {st.session_state.treasures}")
|
501 |
st.sidebar.write(f"π₯ Players: {', '.join([p['username'] for p in st.session_state.players.values()]) or 'None'}")
|
502 |
|
|
|
|
|
|
|
503 |
new_username = st.sidebar.selectbox("π§ββοΈ Choose Your Hero", list(CHARACTERS.keys()), index=list(CHARACTERS.keys()).index(st.session_state.username))
|
504 |
if new_username != st.session_state.username:
|
505 |
asyncio.run(save_chat_entry(st.session_state.username, f"π Transforms into {new_username}!", CHARACTERS[st.session_state.username]["voice"]))
|
@@ -561,6 +631,7 @@ def main():
|
|
561 |
st.sidebar.markdown(f"β³ Next Update in: {int(remaining)}s")
|
562 |
if remaining <= 0:
|
563 |
st.session_state.last_update = time.time()
|
|
|
564 |
st.rerun()
|
565 |
|
566 |
if not st.session_state.get('server_running', False):
|
|
|
51 |
"Wyoming Spring Creek": (41.6666, -106.6666)
|
52 |
}
|
53 |
|
54 |
+
# Directories and Files
|
55 |
for d in ["chat_logs", "audio_logs"]:
|
56 |
os.makedirs(d, exist_ok=True)
|
57 |
|
|
|
59 |
AUDIO_DIR = "audio_logs"
|
60 |
STATE_FILE = "user_state.txt"
|
61 |
CHAT_FILE = os.path.join(CHAT_DIR, "quest_log.md")
|
62 |
+
GAME_STATE_FILE = "game_state.json"
|
63 |
+
|
64 |
+
# Cached Game State as "ML Model"
|
65 |
+
@st.cache_data
|
66 |
+
def load_game_state(_timestamp):
|
67 |
+
"""Load or initialize the game state, treated as a cached 'model'."""
|
68 |
+
if os.path.exists(GAME_STATE_FILE):
|
69 |
+
with open(GAME_STATE_FILE, 'r') as f:
|
70 |
+
state = json.load(f)
|
71 |
+
else:
|
72 |
+
state = {"players": {}, "treasures": []}
|
73 |
+
with open(GAME_STATE_FILE, 'w') as f:
|
74 |
+
json.dump(state, f)
|
75 |
+
return state
|
76 |
+
|
77 |
+
def update_game_state(state):
|
78 |
+
"""Update the game state and persist to file."""
|
79 |
+
with open(GAME_STATE_FILE, 'w') as f:
|
80 |
+
json.dump(state, f)
|
81 |
+
# Clear cache to force reload with new timestamp
|
82 |
+
load_game_state.clear()
|
83 |
+
return load_game_state(time.time())
|
84 |
|
85 |
# Helpers
|
86 |
def format_timestamp(username=""):
|
|
|
184 |
'speech_processed': False, 'players': {}, 'last_update': time.time(),
|
185 |
'update_interval': 1, 'x_pos': 0, 'z_pos': 0, 'move_left': False,
|
186 |
'move_right': False, 'move_up': False, 'move_down': False,
|
187 |
+
'prairie_players': {}, 'last_chat_update': 0,
|
188 |
+
'game_state_timestamp': time.time()
|
189 |
}
|
190 |
for k, v in defaults.items():
|
191 |
if k not in st.session_state:
|
|
|
198 |
st.session_state.username = random.choice(list(CHARACTERS.keys()))
|
199 |
asyncio.run(save_chat_entry(st.session_state.username, "πΊοΈ Begins the Rocky Mountain Quest!", CHARACTERS[st.session_state.username]["voice"]))
|
200 |
save_username(st.session_state.username)
|
201 |
+
# Initialize player in cached game state if new
|
202 |
+
game_state = load_game_state(st.session_state.game_state_timestamp)
|
203 |
+
if st.session_state.username not in game_state["players"]:
|
204 |
+
game_state["players"][st.session_state.username] = {
|
205 |
+
"x": random.uniform(-20, 20),
|
206 |
+
"z": random.uniform(-40, 40),
|
207 |
+
"color": CHARACTERS[st.session_state.username]["color"],
|
208 |
+
"score": 0,
|
209 |
+
"treasures": 0
|
210 |
+
}
|
211 |
+
update_game_state(game_state)
|
212 |
|
213 |
init_session_state()
|
214 |
|
|
|
221 |
st.session_state.active_connections[room_id][client_id] = websocket
|
222 |
username = st.session_state.username
|
223 |
|
224 |
+
game_state = load_game_state(st.session_state.game_state_timestamp)
|
225 |
if "prairie" in path:
|
226 |
st.session_state.prairie_players[client_id] = {
|
227 |
"username": username,
|
|
|
231 |
}
|
232 |
await broadcast_message(f"System|{username} joins the prairie!", room_id)
|
233 |
else:
|
234 |
+
if username not in game_state["players"]:
|
235 |
+
game_state["players"][username] = {
|
236 |
+
"x": random.uniform(-20, 20),
|
237 |
+
"z": random.uniform(-40, 40),
|
238 |
+
"color": CHARACTERS[username]["color"],
|
239 |
+
"score": 0,
|
240 |
+
"treasures": 0
|
241 |
+
}
|
242 |
+
update_game_state(game_state)
|
243 |
+
st.session_state.players[client_id] = game_state["players"][username]
|
244 |
await broadcast_message(f"System|{username} joins the quest!", room_id)
|
245 |
|
246 |
try:
|
|
|
248 |
if '|' in message:
|
249 |
sender, content = message.split('|', 1)
|
250 |
voice = CHARACTERS.get(sender, {"voice": "en-US-AriaNeural"})["voice"]
|
251 |
+
game_state = load_game_state(st.session_state.game_state_timestamp)
|
252 |
if content.startswith("MOVE:"):
|
253 |
_, x, z = content.split(":")
|
254 |
+
x, z = float(x), float(z)
|
255 |
+
st.session_state.players[client_id]["x"] = x
|
256 |
+
st.session_state.players[client_id]["z"] = z
|
257 |
+
game_state["players"][sender]["x"] = x
|
258 |
+
game_state["players"][sender]["z"] = z
|
259 |
+
update_game_state(game_state)
|
260 |
+
elif content.startswith("SCORE:"):
|
261 |
+
score = int(content.split(":")[1])
|
262 |
+
st.session_state.players[client_id]["score"] = score
|
263 |
+
game_state["players"][sender]["score"] = score
|
264 |
+
update_game_state(game_state)
|
265 |
+
elif content.startswith("TREASURE:"):
|
266 |
+
treasures = int(content.split(":")[1])
|
267 |
+
st.session_state.players[client_id]["treasures"] = treasures
|
268 |
+
game_state["players"][sender]["treasures"] = treasures
|
269 |
+
for i, t in enumerate(game_state["treasures"]):
|
270 |
+
if st.session_state.players[client_id]["x"] - 2 < t["x"] < st.session_state.players[client_id]["x"] + 2 and \
|
271 |
+
st.session_state.players[client_id]["z"] - 2 < t["z"] < st.session_state.players[client_id]["z"] + 2:
|
272 |
+
game_state["treasures"].pop(i)
|
273 |
+
break
|
274 |
+
update_game_state(game_state)
|
275 |
elif content.startswith("PRAIRIE:"):
|
276 |
action, value = content.split(":", 1)
|
277 |
if action == "ANIMAL":
|
|
|
281 |
st.session_state.prairie_players[client_id]["location"] = target
|
282 |
action_msg = f"{sender} ({st.session_state.prairie_players[client_id]['animal']}) moves to {value}"
|
283 |
await save_chat_entry(sender, action_msg, voice)
|
|
|
|
|
|
|
|
|
284 |
else:
|
285 |
await save_chat_entry(sender, content, voice)
|
286 |
await perform_arxiv_search(content, sender)
|
|
|
297 |
async def periodic_update():
|
298 |
while True:
|
299 |
if st.session_state.active_connections.get("quest"):
|
300 |
+
game_state = load_game_state(st.session_state.game_state_timestamp)
|
301 |
player_list = ", ".join([p["username"] for p in st.session_state.players.values()]) or "No adventurers yet!"
|
302 |
message = f"π’ Quest Update: Active Adventurers - {player_list}"
|
303 |
+
player_data = json.dumps(list(game_state["players"].values()))
|
304 |
await broadcast_message(f"MAP_UPDATE:{player_data}", "quest")
|
305 |
|
306 |
prairie_list = ", ".join([f"{p['username']} ({p['animal']})" for p in st.session_state.prairie_players.values()]) or "No animals yet!"
|
|
|
308 |
prairie_data = json.dumps(list(st.session_state.prairie_players.values()))
|
309 |
await broadcast_message(f"PRAIRIE_UPDATE:{prairie_data}", "quest")
|
310 |
|
|
|
311 |
chat_content = await load_chat()
|
312 |
await broadcast_message(f"CHAT_UPDATE:{json.dumps(chat_content[-10:])}", "quest")
|
313 |
+
await broadcast_message(f"GAME_STATE:{json.dumps(game_state)}", "quest")
|
314 |
await save_chat_entry("System", f"{message}\n{prairie_message}", "en-US-AriaNeural")
|
315 |
await asyncio.sleep(st.session_state.update_interval)
|
316 |
|
|
|
405 |
let players = {{}};
|
406 |
const playerMeshes = {{}};
|
407 |
let treasures = [];
|
408 |
+
const treasureMeshes = {{}};
|
409 |
let xPos = 0, zPos = 0, moveLeft = false, moveRight = false, moveUp = false, moveDown = false, collect = false;
|
410 |
let score = 0, treasureCount = 0;
|
411 |
|
|
|
418 |
scene.add(playerMesh);
|
419 |
players[playerName] = {{ mesh: playerMesh, score: 0, treasures: 0 }};
|
420 |
|
421 |
+
// Treasure updating
|
422 |
+
function updateTreasures(treasureData) {{
|
423 |
+
treasureData.forEach(t => {{
|
424 |
+
if (!treasureMeshes[t.id]) {{
|
425 |
+
const treasure = new THREE.Mesh(
|
426 |
+
new THREE.SphereGeometry(1, 8, 8),
|
427 |
+
new THREE.MeshPhongMaterial({{ color: 0xffff00 }})
|
428 |
+
);
|
429 |
+
treasure.position.set(t.x, 1, t.z);
|
430 |
+
treasure.castShadow = true;
|
431 |
+
treasureMeshes[t.id] = treasure;
|
432 |
+
treasures.push(treasure);
|
433 |
+
scene.add(treasure);
|
434 |
+
}} else {{
|
435 |
+
treasureMeshes[t.id].position.set(t.x, 1, t.z);
|
436 |
+
}}
|
437 |
+
}});
|
438 |
+
// Remove treasures not in the data
|
439 |
+
Object.keys(treasureMeshes).forEach(id => {{
|
440 |
+
if (!treasureData.some(t => t.id === id)) {{
|
441 |
+
scene.remove(treasureMeshes[id]);
|
442 |
+
treasures = treasures.filter(t => t !== treasureMeshes[id]);
|
443 |
+
delete treasureMeshes[id];
|
444 |
+
}}
|
445 |
+
}});
|
446 |
}}
|
447 |
|
448 |
// Controls
|
|
|
477 |
if (collect) {{
|
478 |
for (let i = treasures.length - 1; i >= 0; i--) {{
|
479 |
if (playerMesh.position.distanceTo(treasures[i].position) < 2) {{
|
480 |
+
const id = Object.keys(treasureMeshes).find(key => treasureMeshes[key] === treasures[i]);
|
481 |
scene.remove(treasures[i]);
|
482 |
treasures.splice(i, 1);
|
483 |
+
delete treasureMeshes[id];
|
484 |
score += 50;
|
485 |
treasureCount += 1;
|
486 |
ws.send(`${{playerName}}|SCORE:${{score}}`);
|
487 |
ws.send(`${{playerName}}|TREASURE:${{treasureCount}}`);
|
|
|
488 |
}}
|
489 |
}}
|
490 |
}}
|
|
|
493 |
camera.lookAt(xPos, 0, zPos);
|
494 |
}}
|
495 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
496 |
function updatePlayers(playerData) {{
|
497 |
playerData.forEach(player => {{
|
498 |
if (!playerMeshes[player.username]) {{
|
|
|
504 |
const mesh = playerMeshes[player.username];
|
505 |
mesh.position.set(player.x, 1, player.z);
|
506 |
players[player.username] = {{ mesh: mesh, score: player.score, treasures: player.treasures }};
|
507 |
+
if (player.username === playerName) {{
|
508 |
+
xPos = player.x;
|
509 |
+
zPos = player.z;
|
510 |
+
score = player.score;
|
511 |
+
treasureCount = player.treasures;
|
512 |
+
playerMesh.position.set(xPos, 1, zPos);
|
513 |
+
}}
|
514 |
}});
|
515 |
document.getElementById('players').textContent = `Players: ${{Object.keys(playerMeshes).length}}`;
|
516 |
document.getElementById('score').textContent = `Score: ${{score}}`;
|
|
|
527 |
const chatBox = document.getElementById('chatBox');
|
528 |
chatBox.innerHTML = chatData.map(line => `<p>${{line}}</p>`).join('');
|
529 |
chatBox.scrollTop = chatBox.scrollHeight;
|
530 |
+
}} else if (data.startsWith('GAME_STATE:')) {{
|
531 |
+
const gameState = JSON.parse(data.split('GAME_STATE:')[1]);
|
532 |
+
updatePlayers(gameState.players);
|
533 |
+
updateTreasures(gameState.treasures);
|
534 |
}} else if (!data.startsWith('PRAIRIE_UPDATE:')) {{
|
535 |
const [sender, message] = data.split('|');
|
536 |
const chatBox = document.getElementById('chatBox');
|
|
|
547 |
lastTime = currentTime;
|
548 |
|
549 |
updatePlayer(delta);
|
550 |
+
treasures.forEach(t => t.rotation.y += delta);
|
551 |
renderer.render(scene, camera);
|
552 |
}}
|
553 |
|
|
|
|
|
554 |
animate();
|
555 |
</script>
|
556 |
</body>
|
|
|
567 |
st.sidebar.write(f"π΅ Treasures: {st.session_state.treasures}")
|
568 |
st.sidebar.write(f"π₯ Players: {', '.join([p['username'] for p in st.session_state.players.values()]) or 'None'}")
|
569 |
|
570 |
+
# User-controlled refresh interval
|
571 |
+
st.session_state.update_interval = st.sidebar.slider("Refresh Interval (seconds)", 1, 10, 1, step=1)
|
572 |
+
|
573 |
new_username = st.sidebar.selectbox("π§ββοΈ Choose Your Hero", list(CHARACTERS.keys()), index=list(CHARACTERS.keys()).index(st.session_state.username))
|
574 |
if new_username != st.session_state.username:
|
575 |
asyncio.run(save_chat_entry(st.session_state.username, f"π Transforms into {new_username}!", CHARACTERS[st.session_state.username]["voice"]))
|
|
|
631 |
st.sidebar.markdown(f"β³ Next Update in: {int(remaining)}s")
|
632 |
if remaining <= 0:
|
633 |
st.session_state.last_update = time.time()
|
634 |
+
st.session_state.game_state_timestamp = time.time() # Update timestamp to refresh cache
|
635 |
st.rerun()
|
636 |
|
637 |
if not st.session_state.get('server_running', False):
|