multiplayer 1.0.0__tar.gz → 1.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. {multiplayer-1.0.0 → multiplayer-1.1.0}/PKG-INFO +8 -6
  2. {multiplayer-1.0.0 → multiplayer-1.1.0}/README.md +6 -5
  3. {multiplayer-1.0.0 → multiplayer-1.1.0}/pyproject.toml +7 -1
  4. {multiplayer-1.0.0 → multiplayer-1.1.0}/src/multiplayer/__init__.py +1 -1
  5. {multiplayer-1.0.0 → multiplayer-1.1.0}/src/multiplayer/client.py +20 -12
  6. multiplayer-1.1.0/src/multiplayer/data/persistence.py +516 -0
  7. {multiplayer-1.0.0 → multiplayer-1.1.0}/src/multiplayer/game.py +72 -8
  8. {multiplayer-1.0.0 → multiplayer-1.1.0}/src/multiplayer/server.py +270 -101
  9. {multiplayer-1.0.0 → multiplayer-1.1.0}/src/multiplayer/utils.py +23 -0
  10. multiplayer-1.0.0/src/multiplayer/data/persistence.py +0 -280
  11. {multiplayer-1.0.0 → multiplayer-1.1.0}/LICENSE.md +0 -0
  12. {multiplayer-1.0.0 → multiplayer-1.1.0}/src/multiplayer/IPClogging/__init__.py +0 -0
  13. {multiplayer-1.0.0 → multiplayer-1.1.0}/src/multiplayer/IPClogging/echoing.py +0 -0
  14. {multiplayer-1.0.0 → multiplayer-1.1.0}/src/multiplayer/IPClogging/server.py +0 -0
  15. {multiplayer-1.0.0 → multiplayer-1.1.0}/src/multiplayer/IPClogging/test.py +0 -0
  16. {multiplayer-1.0.0 → multiplayer-1.1.0}/src/multiplayer/data/__init__.py +0 -0
  17. {multiplayer-1.0.0 → multiplayer-1.1.0}/src/multiplayer/data/cities.csv +0 -0
  18. {multiplayer-1.0.0 → multiplayer-1.1.0}/src/multiplayer/data/countries.csv +0 -0
  19. {multiplayer-1.0.0 → multiplayer-1.1.0}/src/multiplayer/data/egyptian_gods.csv +0 -0
  20. {multiplayer-1.0.0 → multiplayer-1.1.0}/src/multiplayer/data/european_kings.csv +0 -0
  21. {multiplayer-1.0.0 → multiplayer-1.1.0}/src/multiplayer/data/european_queens.csv +0 -0
  22. {multiplayer-1.0.0 → multiplayer-1.1.0}/src/multiplayer/data/greek_gods.csv +0 -0
  23. {multiplayer-1.0.0 → multiplayer-1.1.0}/src/multiplayer/data/planets_moons.csv +0 -0
  24. {multiplayer-1.0.0 → multiplayer-1.1.0}/src/multiplayer/data/rivers.csv +0 -0
  25. {multiplayer-1.0.0 → multiplayer-1.1.0}/src/multiplayer/data/roman_gods.csv +0 -0
  26. {multiplayer-1.0.0 → multiplayer-1.1.0}/src/multiplayer/data/seas_oceans.csv +0 -0
  27. {multiplayer-1.0.0 → multiplayer-1.1.0}/src/multiplayer/exceptions.py +0 -0
  28. {multiplayer-1.0.0 → multiplayer-1.1.0}/src/multiplayer/language/__init__.py +0 -0
  29. {multiplayer-1.0.0 → multiplayer-1.1.0}/src/multiplayer/language/language.py +0 -0
  30. {multiplayer-1.0.0 → multiplayer-1.1.0}/src/multiplayer/py.typed +0 -0
  31. {multiplayer-1.0.0 → multiplayer-1.1.0}/src/multiplayer/run_log_server.py +0 -0
  32. {multiplayer-1.0.0 → multiplayer-1.1.0}/src/multiplayer/run_server.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multiplayer
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: Library that allows you to manage multiple players, locally, on a network, or on the Internet.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE.md
@@ -13,6 +13,7 @@ Classifier: Programming Language :: Python :: 3.14
13
13
  Classifier: Topic :: Games/Entertainment
14
14
  Requires-Dist: colorlog
15
15
  Requires-Dist: cryptography
16
+ Requires-Dist: bcrypt
16
17
  Requires-Dist: pytest ; extra == 'dev'
17
18
  Requires-Dist: requests ; extra == 'dev'
18
19
  Requires-Dist: ruff ; extra == 'dev'
@@ -41,12 +42,13 @@ For a detailed technical description of all classes and functions, see the [API
41
42
  * **Observer Support:** Ability to add observers who can view the game state without participating as players.
42
43
  * **Administrator Role:** New `ServerAdmin` class to manage the server, kick players/observers, and monitor server status.
43
44
  * **Game Grouping:** Organize several game sessions within the same server using the `GameGroup` class.
44
- * **Multi-Layered Security:** Supports server passwords, admin passwords, and per-game passwords, with optional TLS v1.3 encryption. Passwords can be updated dynamically by administrators.
45
+ * **Multi-Layered Security:** Supports server passwords, admin passwords, and per-game passwords, with optional TLS v1.3 encryption. Passwords can be updated dynamically by administrators and are securely stored using `bcrypt` hashing.
45
46
  * **Automatic Server Discovery:** Clients can automatically find running servers on the local network.
46
47
  * **Extensible Name Suggestions:** Includes a utility function to suggest creative names for games and players.
47
48
  * **Multiple Games:** The server can manage multiple game sessions simultaneously. By default, the game list for standard clients is filtered to hide finished games, but administrators can access all sessions via specialized methods like `list_all_server_games()` or `list_all_group_games()`.
48
49
  * **Robust Error Handling:** A clear set of custom exceptions for both game logic and network issues.
49
- * **Persistent Player Accounts:** Ability for players to create an account with a password for repeated use across the server.
50
+ * **Persistent Player Accounts:** Ability for players to create an account with a password for repeated use across the server. All persistent passwords (players, groups, and server) are protected with `bcrypt`.
51
+ * **Data Persistence:** Optional support for persisting server configuration, player accounts, and game sessions to disk (e.g., in JSON format). Hash-based password storage ensures security even if the data file is compromised.
50
52
 
51
53
  ## Installation
52
54
 
@@ -60,9 +62,9 @@ pip install multiplayer
60
62
  ### 2. From a Wheel file (GitHub)
61
63
  Download the `.whl` file from the [Releases](https://github.com/devfred78/multiplayer/releases) page and run:
62
64
  ```sh
63
- pip install multiplayer-1.0.0-py3-none-any.whl
65
+ pip install multiplayer-1.1.0-py3-none-any.whl
64
66
  ```
65
- *Replace `multiplayer-1.0.0-py3-none-any.whl` with the actual name of the downloaded file.*
67
+ *Replace `multiplayer-1.1.0-py3-none-any.whl` with the actual name of the downloaded file.*
66
68
 
67
69
  ## Usage
68
70
 
@@ -103,7 +105,7 @@ game2 = Game("Match 2")
103
105
  group.add_game(game1)
104
106
  group.add_game(game2)
105
107
 
106
- print(f"Game 1 ID: {game1.ID}")
108
+ print(f"Game 1 ID: {game1.game_id}")
107
109
  # > Game 1 ID: 550e8400-e29b-41d4-a716-446655440000
108
110
 
109
111
  print(f"Group '{group.name}' has {len(group.games)} games.")
@@ -19,12 +19,13 @@ For a detailed technical description of all classes and functions, see the [API
19
19
  * **Observer Support:** Ability to add observers who can view the game state without participating as players.
20
20
  * **Administrator Role:** New `ServerAdmin` class to manage the server, kick players/observers, and monitor server status.
21
21
  * **Game Grouping:** Organize several game sessions within the same server using the `GameGroup` class.
22
- * **Multi-Layered Security:** Supports server passwords, admin passwords, and per-game passwords, with optional TLS v1.3 encryption. Passwords can be updated dynamically by administrators.
22
+ * **Multi-Layered Security:** Supports server passwords, admin passwords, and per-game passwords, with optional TLS v1.3 encryption. Passwords can be updated dynamically by administrators and are securely stored using `bcrypt` hashing.
23
23
  * **Automatic Server Discovery:** Clients can automatically find running servers on the local network.
24
24
  * **Extensible Name Suggestions:** Includes a utility function to suggest creative names for games and players.
25
25
  * **Multiple Games:** The server can manage multiple game sessions simultaneously. By default, the game list for standard clients is filtered to hide finished games, but administrators can access all sessions via specialized methods like `list_all_server_games()` or `list_all_group_games()`.
26
26
  * **Robust Error Handling:** A clear set of custom exceptions for both game logic and network issues.
27
- * **Persistent Player Accounts:** Ability for players to create an account with a password for repeated use across the server.
27
+ * **Persistent Player Accounts:** Ability for players to create an account with a password for repeated use across the server. All persistent passwords (players, groups, and server) are protected with `bcrypt`.
28
+ * **Data Persistence:** Optional support for persisting server configuration, player accounts, and game sessions to disk (e.g., in JSON format). Hash-based password storage ensures security even if the data file is compromised.
28
29
 
29
30
  ## Installation
30
31
 
@@ -38,9 +39,9 @@ pip install multiplayer
38
39
  ### 2. From a Wheel file (GitHub)
39
40
  Download the `.whl` file from the [Releases](https://github.com/devfred78/multiplayer/releases) page and run:
40
41
  ```sh
41
- pip install multiplayer-1.0.0-py3-none-any.whl
42
+ pip install multiplayer-1.1.0-py3-none-any.whl
42
43
  ```
43
- *Replace `multiplayer-1.0.0-py3-none-any.whl` with the actual name of the downloaded file.*
44
+ *Replace `multiplayer-1.1.0-py3-none-any.whl` with the actual name of the downloaded file.*
44
45
 
45
46
  ## Usage
46
47
 
@@ -81,7 +82,7 @@ game2 = Game("Match 2")
81
82
  group.add_game(game1)
82
83
  group.add_game(game2)
83
84
 
84
- print(f"Game 1 ID: {game1.ID}")
85
+ print(f"Game 1 ID: {game1.game_id}")
85
86
  # > Game 1 ID: 550e8400-e29b-41d4-a716-446655440000
86
87
 
87
88
  print(f"Group '{group.name}' has {len(group.games)} games.")
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "multiplayer"
3
- version = "1.0.0"
3
+ version = "1.1.0"
4
4
  description = "Library that allows you to manage multiple players, locally, on a network, or on the Internet."
5
5
  license = "MIT"
6
6
  license-files = ["LICENSE.md"]
@@ -19,6 +19,7 @@ classifiers = [
19
19
  dependencies = [
20
20
  "colorlog",
21
21
  "cryptography",
22
+ "bcrypt",
22
23
  ]
23
24
 
24
25
  [project.scripts]
@@ -40,3 +41,8 @@ build-backend = "uv_build"
40
41
  module-name = "multiplayer"
41
42
  module-root = "src"
42
43
  package-data = { "multiplayer" = ["data/*.csv"] }
44
+
45
+ [dependency-groups]
46
+ dev = [
47
+ "requests>=2.34.1",
48
+ ]
@@ -1,7 +1,7 @@
1
1
  """
2
2
  This package provides classes for managing a multiplayer game, both locally and over a network.
3
3
  """
4
- __version__ = "1.0.0"
4
+ __version__ = "1.1.0"
5
5
  from .game import Game, Player, Observer, GameState, GameGroup
6
6
  from .server import GameServer
7
7
  from .client import GameClient, RemoteGame, ServerAdmin, GroupAdmin
@@ -197,7 +197,7 @@ class GameClient:
197
197
  if group_id:
198
198
  params['group_id'] = group_id
199
199
  data = self._send_command('create_game', params)
200
- remote_game = RemoteGame(data['game_id'], self.host, self.port, self.password, self.use_tls, self.auth_user, self.auth_password)
200
+ remote_game = RemoteGame(data['game_id'], self.host, self.port, game_options.get('password', self.password), self.use_tls, self.auth_user, self.auth_password)
201
201
 
202
202
  # Propagate logging configuration if any
203
203
  for h in self._logger.handlers:
@@ -218,10 +218,14 @@ class GameClient:
218
218
  pass
219
219
  return games_data
220
220
 
221
+ def list_users(self):
222
+ """Retrieves a list of names of currently connected users."""
223
+ return self._send_command('list_users')
224
+
221
225
  def create_group(self, name, admin_password=None, **attributes):
222
226
  """Requests the server to create a new game group and returns a proxy to it."""
223
227
  data = self._send_command('create_group', {'name': name, 'admin_password': admin_password, 'attributes': attributes})
224
- remote_group = RemoteGroup(data['group_id'], self.host, self.port, self.password, self.use_tls, self.auth_user, self.auth_password)
228
+ remote_group = RemoteGroup(data['group_id'], self.host, self.port, admin_password or self.password, self.use_tls, self.auth_user, self.auth_password)
225
229
 
226
230
  # Propagate logging configuration if any
227
231
  for h in self._logger.handlers:
@@ -370,14 +374,7 @@ class ServerAdmin(GameClient):
370
374
 
371
375
  def list_all_server_games(self):
372
376
  """Retrieves a dictionary of all games (even those with GameState.FINISHED) organized by ID."""
373
- games_data = self._send_command('list_games', {'include_finished': True})
374
- for gid in games_data:
375
- if 'state' in games_data[gid]:
376
- try:
377
- games_data[gid]['state'] = GameState(games_data[gid]['state'])
378
- except ValueError:
379
- pass
380
- return games_data
377
+ return self._send_command('list_games', {'include_finished': True})
381
378
 
382
379
  def kick_player(self, game_id, player_id):
383
380
  """Kicks a player from a specific game."""
@@ -436,6 +433,12 @@ class ServerAdmin(GameClient):
436
433
  'hidden': hidden
437
434
  })
438
435
 
436
+ def set_server_name(self, name):
437
+ """Sets a new name for the server."""
438
+ return self._send_command('set_server_name', {
439
+ 'name': name
440
+ })
441
+
439
442
  def update_persistent_player(self, name, role=None, managed_groups=None, password=None, **attributes):
440
443
  """
441
444
  Updates a persistent player's information.
@@ -604,7 +607,12 @@ class RemoteGame:
604
607
  'game_password': password,
605
608
  }
606
609
  if isinstance(player, PersistentPlayer):
607
- params['persistent_player_password'] = player.password
610
+ # The server expects the raw password to verify against its hash
611
+ # PersistentPlayer.password is now a hash locally too if initialized with a string
612
+ # But the client test might be using the hash if it's not careful.
613
+ # However, if we want to support this, we need a way to get the raw password or
614
+ # ensure the client doesn't hash it.
615
+ params['persistent_player_password'] = getattr(player, '_raw_password', player.password)
608
616
 
609
617
  self._send_command('add_player', params)
610
618
 
@@ -635,7 +643,7 @@ class RemoteGame:
635
643
  'observer_password': password,
636
644
  }
637
645
  if isinstance(observer, PersistentPlayer):
638
- params['persistent_player_password'] = observer.password
646
+ params['persistent_player_password'] = getattr(observer, '_raw_password', observer.password)
639
647
 
640
648
  self._send_command('add_observer', params)
641
649