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:

-
<constructor>, broadcast_message(), handle_transition(), kick(), print_client_list(), shutdown()
+
<constructor>, handle_transition(), kick(), print_client_list(), shutdown()

<constructor>

ba.ServerController(config: ServerConfig)

-
-

broadcast_message()

-

broadcast_message(self, message: str) -> None

- -

Broadcast a message to all connected clients.

-

handle_transition()

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())