From c95357e3d0dcf24fb761edd70070a85c69b798c4 Mon Sep 17 00:00:00 2001
From: Eric Froemling
Date: Wed, 6 May 2020 00:50:18 -0700
Subject: [PATCH] Improved server chat and screenmessage functionality
---
.efrocachemap | 24 ++++++------
.idea/dictionaries/ericf.xml | 4 ++
assets/src/ba_data/python/_ba.py | 8 ++--
assets/src/ba_data/python/ba/_servermode.py | 33 ++++++++++-------
assets/src/ba_data/python/bastd/ui/party.py | 2 +-
assets/src/server/ballisticacore_server.py | 41 +++++++++++++++++----
docs/ba_module.md | 10 +----
tools/bacommon/servermanager.py | 15 ++++++--
tools/efrotools/__init__.py | 1 -
tools/snippets | 27 ++++++++++++++
10 files changed, 116 insertions(+), 49 deletions(-)
diff --git a/.efrocachemap b/.efrocachemap
index 3780919e..57ab8ece 100644
--- a/.efrocachemap
+++ b/.efrocachemap
@@ -4132,16 +4132,16 @@
"assets/build/windows/x64/python.exe": "https://files.ballistica.net/cache/ba1/25/a7/dc87c1be41605eb6fefd0145144c",
"assets/build/windows/x64/python37.dll": "https://files.ballistica.net/cache/ba1/b9/e4/d912f56e42e9991bcbb4c804cfcb",
"assets/build/windows/x64/pythonw.exe": "https://files.ballistica.net/cache/ba1/6c/bb/b6f52c306aa4e88061510e96cefe",
- "build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/8c/a0/002b38837d6d538912c7e3353d38",
- "build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/c8/af/0f496b474262aea9ee0f91145177",
- "build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/23/ff/2b90933ab022e851c9ee1885bd02",
- "build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/2f/ee/38fac529e6e1fc418334e3c5a96f",
- "build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/8d/99/1048ca7d9a3f8aabc7719399e996",
- "build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/81/b5/72bb876e9a7e88cbd2c828b7b1a9",
- "build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/3d/8b/ff95157b4e62b80210841a81594b",
- "build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/80/b7/ac34c7c602b40a1f4f095b2bd5df",
- "build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/c4/3b/8de9d3aa9d2bcfb7ad731b2243bc",
- "build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/b9/e3/5f34b93ba4b62facbfea0a8dfde4",
- "build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/a7/3e/65b9f907e53f753aa4178500cb2f",
- "build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/58/1f/3241179955da91425342cea435a0"
+ "build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/6a/56/e48e9306428b4ae7b58225fce76c",
+ "build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/dd/2d/bdd22cbd39a9bb27a75302ef837a",
+ "build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/61/f2/a679c5a3c9113c7b7599a59415fb",
+ "build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/6c/21/c80e03031e6c5959140588d439bf",
+ "build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/42/ff/6d9056500a768dae79ed44180796",
+ "build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/58/d4/3f6d3cb75988db046f22bfb5c2a5",
+ "build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/a8/07/edf6360c8e5b824633bf55c97ed9",
+ "build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/27/cc/0ec7cab492f111923320dc165427",
+ "build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/b2/b4/f68901c3dae4267a5640bf702d86",
+ "build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/ae/b7/0400bd1021d0fc0c40d1c2149364",
+ "build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/ee/6e/8f88fef729d85a07910cbedbac31",
+ "build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/e1/90/4877825fb2d779bd7ac9704a1a4c"
}
\ No newline at end of file
diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml
index faa11098..b847cba0 100644
--- a/.idea/dictionaries/ericf.xml
+++ b/.idea/dictionaries/ericf.xml
@@ -275,6 +275,7 @@
charmap
charname
charstr
+ chatmessage
checkarg
checkboxwidget
checkenv
@@ -316,6 +317,7 @@
cnode
codecsmodule
codefilenames
+ codehash
codeop
collapsable
collidemodel
@@ -800,6 +802,7 @@
halign
handlemessage
hant
+ hashfilename
hashlines
hashobj
hashopenssl
@@ -953,6 +956,7 @@
langs
langtarget
langval
+ lasthash
lastline
lastplayer
lastpoweruptype
diff --git a/assets/src/ba_data/python/_ba.py b/assets/src/ba_data/python/_ba.py
index c896bf5e..41acec04 100644
--- a/assets/src/ba_data/python/_ba.py
+++ b/assets/src/ba_data/python/_ba.py
@@ -34,7 +34,7 @@ NOTE: This file was autogenerated by gendummymodule; do not edit by hand.
"""
# (hash we can use to see if this file is out of date)
-# SOURCES_HASH=244232883360612053934546020264848527679
+# SOURCES_HASH=303806386736755003110818846527806768186
# I'm sorry Pylint. I know this file saddens you. Be strong.
# pylint: disable=useless-suppression
@@ -1504,8 +1504,10 @@ def charstr(char_id: ba.SpecialChar) -> str:
return str()
-def chat_message(message: Union[str, ba.Lstr]) -> None:
- """chat_message(message: Union[str, ba.Lstr]) -> None
+def chatmessage(message: Union[str, ba.Lstr],
+ clients: Sequence[int] = None) -> None:
+ """chatmessage(message: Union[str, ba.Lstr],
+ clients: Sequence[int] = None) -> None
(internal)
"""
diff --git a/assets/src/ba_data/python/ba/_servermode.py b/assets/src/ba_data/python/ba/_servermode.py
index 6fbf25dc..0ffd8fa8 100644
--- a/assets/src/ba_data/python/ba/_servermode.py
+++ b/assets/src/ba_data/python/ba/_servermode.py
@@ -31,8 +31,8 @@ from ba._freeforallsession import FreeForAllSession
from ba._dualteamsession import DualTeamSession
from bacommon.servermanager import (ServerCommand, StartServerModeCommand,
ShutdownCommand, ShutdownReason,
- BroadcastCommand, ClientListCommand,
- KickCommand)
+ ChatMessageCommand, ScreenMessageCommand,
+ ClientListCommand, KickCommand)
import _ba
if TYPE_CHECKING:
@@ -58,9 +58,20 @@ def _cmd(command_data: bytes) -> None:
immediate=command.immediate)
return
- if isinstance(command, BroadcastCommand):
+ if isinstance(command, ChatMessageCommand):
assert _ba.app.server is not None
- _ba.app.server.broadcast_message(command.message)
+ _ba.chatmessage(command.message, clients=command.clients)
+ return
+
+ if isinstance(command, ScreenMessageCommand):
+ assert _ba.app.server is not None
+ # Note: we have to do transient messages if
+ # clients is specified, so they won't show up
+ # in replays.
+ _ba.screenmessage(command.message,
+ color=command.color,
+ clients=command.clients,
+ transient=command.clients is not None)
return
if isinstance(command, ClientListCommand):
@@ -111,12 +122,6 @@ class ServerController:
timetype=TimeType.REAL,
repeat=True)
- def broadcast_message(self, message: str) -> None:
- """Broadcast a message to all connected clients."""
- # FIXME: Should add a proper call for this, which would allow
- # us to use Lstr values and colors and whatnot.
- _ba.chat_message(message)
-
def print_client_list(self) -> None:
"""Print info about all connected clients."""
import json
@@ -184,13 +189,13 @@ class ServerController:
self._executing_shutdown = True
timestrval = time.strftime('%c')
if self._shutdown_reason is ShutdownReason.RESTARTING:
- self.broadcast_message(
- Lstr(resource='internal.serverRestartingText').evaluate())
+ _ba.screenmessage(Lstr(resource='internal.serverRestartingText'),
+ color=(1, 0.5, 0.0))
print(f'{Clr.SBLU}Exiting for server-restart'
f' at {timestrval}{Clr.RST}')
else:
- self.broadcast_message(
- Lstr(resource='internal.serverShuttingDownText').evaluate())
+ _ba.screenmessage(Lstr(resource='internal.serverShuttingDownText'),
+ color=(1, 0.5, 0.0))
print(f'{Clr.SBLU}Exiting for server-shutdown'
f' at {timestrval}{Clr.RST}')
with _ba.Context('ui'):
diff --git a/assets/src/ba_data/python/bastd/ui/party.py b/assets/src/ba_data/python/bastd/ui/party.py
index 032e796b..a60787ab 100644
--- a/assets/src/ba_data/python/bastd/ui/party.py
+++ b/assets/src/ba_data/python/bastd/ui/party.py
@@ -412,7 +412,7 @@ class PartyWindow(ba.Window):
self._popup_party_member_is_host = is_host
def _send_chat_message(self) -> None:
- _ba.chat_message(cast(str, ba.textwidget(query=self._text_field)))
+ _ba.chatmessage(cast(str, ba.textwidget(query=self._text_field)))
ba.textwidget(edit=self._text_field, text='')
def close(self) -> None:
diff --git a/assets/src/server/ballisticacore_server.py b/assets/src/server/ballisticacore_server.py
index 8a8907d5..7ba69eb4 100755
--- a/assets/src/server/ballisticacore_server.py
+++ b/assets/src/server/ballisticacore_server.py
@@ -44,7 +44,7 @@ from efro.dataclassutils import dataclass_assign, dataclass_validate
from bacommon.servermanager import (ServerConfig, StartServerModeCommand)
if TYPE_CHECKING:
- from typing import Optional, List, Dict, Union
+ from typing import Optional, List, Dict, Union, Tuple
from types import FrameType
from bacommon.servermanager import ServerCommand
@@ -181,10 +181,31 @@ class ServerManagerApp:
# we'll hopefully still give it enough time to process/print.
time.sleep(0.1)
- def broadcast(self, message: str) -> None:
- """Broadcast a message to all connected clients."""
- from bacommon.servermanager import BroadcastCommand
- self._enqueue_server_command(BroadcastCommand(message=message))
+ def screenmessage(self,
+ message: str,
+ color: Optional[Tuple[float, float, float]] = None,
+ clients: Optional[List[int]] = None) -> None:
+ """Display a screen-message.
+
+ This will have no name attached and not show up in chat history.
+ They will show up in replays, however (unless clients is passed).
+ """
+ from bacommon.servermanager import ScreenMessageCommand
+ self._enqueue_server_command(
+ ScreenMessageCommand(message=message, color=color,
+ clients=clients))
+
+ def chatmessage(self,
+ message: str,
+ clients: Optional[List[int]] = None) -> None:
+ """Send a chat message from the server.
+
+ This will have the server's name attached and will be logged
+ in client chat windows, just like other chat messages.
+ """
+ from bacommon.servermanager import ChatMessageCommand
+ self._enqueue_server_command(
+ ChatMessageCommand(message=message, clients=clients))
def clientlist(self) -> None:
"""Print a list of connected clients."""
@@ -366,10 +387,16 @@ class ServerManagerApp:
# Watch for the process exiting.
code: Optional[int] = self._process.poll()
if code is not None:
- print(f'{Clr.CYN}Server process exited'
+ if code == 0:
+ clr = Clr.CYN
+ slp = 0.0
+ else:
+ clr = Clr.SRED
+ slp = 5.0 # Avoid super fast death loops.
+ print(f'{clr}Server child-process exited'
f' with code {code}.{Clr.RST}')
- time.sleep(1.0) # Keep things from moving too fast.
self._reset_process_vars()
+ time.sleep(slp)
break
time.sleep(0.25)
diff --git a/docs/ba_module.md b/docs/ba_module.md
index f03611d1..85139707 100644
--- a/docs/ba_module.md
+++ b/docs/ba_module.md
@@ -1,5 +1,5 @@
-last updated on 2020-05-05 for Ballistica version 1.5.0 build 20002
+last updated on 2020-05-06 for Ballistica version 1.5.0 build 20003
This page documents the Python classes and functions in the 'ba' module,
which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please let me know. Happy modding!
@@ -3988,17 +3988,11 @@ cause the powerup box to make a sound and disappear or whatnot.
Methods:
-
+
-
ba.ServerController(config: ServerConfig)
-
--
-
broadcast_message(self, message: str) -> None
-
-Broadcast a message to all connected clients.
-
-
handle_transition(self) -> bool
diff --git a/tools/bacommon/servermanager.py b/tools/bacommon/servermanager.py
index 6db74377..c00d2db8 100644
--- a/tools/bacommon/servermanager.py
+++ b/tools/bacommon/servermanager.py
@@ -26,7 +26,7 @@ from dataclasses import dataclass
from typing import TYPE_CHECKING
if TYPE_CHECKING:
- from typing import Optional
+ from typing import Optional, Tuple, List
@dataclass
@@ -129,9 +129,18 @@ class ShutdownCommand(ServerCommand):
@dataclass
-class BroadcastCommand(ServerCommand):
- """Broadcast a message to all clients."""
+class ChatMessageCommand(ServerCommand):
+ """Chat message from the server."""
message: str
+ clients: Optional[List[int]]
+
+
+@dataclass
+class ScreenMessageCommand(ServerCommand):
+ """Screen-message from the server."""
+ message: str
+ color: Optional[Tuple[float, float, float]]
+ clients: Optional[List[int]]
@dataclass
diff --git a/tools/efrotools/__init__.py b/tools/efrotools/__init__.py
index 9e0935ba..fc70e5ee 100644
--- a/tools/efrotools/__init__.py
+++ b/tools/efrotools/__init__.py
@@ -149,7 +149,6 @@ def run(cmd: str) -> None:
subprocess.run(cmd, shell=True, check=True)
-# 1
def get_files_hash(filenames: Sequence[Union[str, Path]],
extrahash: str = '',
int_only: bool = False,
diff --git a/tools/snippets b/tools/snippets
index af25fb0f..e90822c7 100755
--- a/tools/snippets
+++ b/tools/snippets
@@ -547,5 +547,32 @@ def printcolors() -> None:
f'{TerminalColor.RESET.value}')
+def lazy_increment_build() -> None:
+ """Increment build number only if C++ sources have changed.
+
+ This is convenient to place in automatic commit/push scripts.
+ """
+ import os
+ import subprocess
+ from efro.terminal import Clr
+ from efrotools import get_files_hash
+ from efrotools.code import get_code_filenames
+ codehash = get_files_hash(get_code_filenames(PROJROOT))
+ hashfilename = '.cache/lazy_increment_build'
+ try:
+ with open(hashfilename) as infile:
+ lasthash = infile.read()
+ except FileNotFoundError:
+ lasthash = ''
+ if codehash == lasthash:
+ pass
+ else:
+ print(f'{Clr.SMAG}Source(s) changed; incrementing build...{Clr.RST}')
+ subprocess.run(['tools/version_utils', 'incrementbuild'], check=True)
+ os.makedirs(os.path.dirname(hashfilename), exist_ok=True)
+ with open(hashfilename, 'w') as outfile:
+ outfile.write(codehash)
+
+
if __name__ == '__main__':
snippets_main(globals())