diff --git a/diplomacy/client/notification_managers.py b/diplomacy/client/notification_managers.py index 05e6ab74c..98d17acd3 100644 --- a/diplomacy/client/notification_managers.py +++ b/diplomacy/client/notification_managers.py @@ -115,7 +115,8 @@ def on_game_message_received(game, notification): :type game: diplomacy.client.network_game.NetworkGame :type notification: diplomacy.communication.notifications.GameMessageReceived """ - Game.add_message(game, notification.message) + if notification.message.time_sent != 0: + Game.add_message(game, notification.message) def on_game_processed(game, notification): """ Manage notification GamePhaseUpdate (for omniscient and observer games). diff --git a/diplomacy/client/response_managers.py b/diplomacy/client/response_managers.py index 5464cc81b..c32ae5414 100644 --- a/diplomacy/client/response_managers.py +++ b/diplomacy/client/response_managers.py @@ -232,7 +232,10 @@ def on_send_game_message(context, response): request = context.request # type: requests.SendGameMessage message = request.message message.time_sent = response.data - Game.add_message(context.game, message) + if message.time_sent == 0: + return 0 + + return Game.add_message(context.game, message) def on_set_game_state(context, response): """ Manage response for request SetGameState. diff --git a/diplomacy/engine/game.py b/diplomacy/engine/game.py index 158694de2..1ba03b6d5 100644 --- a/diplomacy/engine/game.py +++ b/diplomacy/engine/game.py @@ -880,7 +880,7 @@ def new_log_data(self, body, recipient="OMNISCIENT"): assert self.is_player_game() return Log(phase=self.current_short_phase, sender=self.role, recipient=recipient, message=body) - def new_power_message(self, recipient, body): + def new_power_message(self, recipient, body, thread_idx=None): """ Create a undated (without timestamp) power message to be sent from a power to another via server. Server will answer with timestamp, and message will be updated and added to local game messages. @@ -893,7 +893,7 @@ def new_power_message(self, recipient, body): assert self.is_player_game() if not self.has_power(recipient): raise exceptions.MapPowerException(recipient) - return Message(phase=self.current_short_phase, sender=self.role, recipient=recipient, message=body) + return Message(phase=self.current_short_phase, sender=self.role, recipient=recipient, message=body, thread_idx=thread_idx) def new_global_message(self, body): """ Create an undated (without timestamp) global message to be sent from a power via server. @@ -932,6 +932,7 @@ def add_log(self, log): self.logs.put(log.time_sent, log) return log.time_sent + def add_message(self, message): """ Add message to current game data. Only a server game can add a message with no timestamp: @@ -957,6 +958,21 @@ def add_message(self, message): time.sleep(1e-6) message.time_sent = common.timestamp_microseconds() + if (message.thread_idx is not None) and (not self.is_player_game()): + def _is_current_thread(a, b): + if (a.phase != b.phase): return False + if (a.sender == b.sender) and (a.recipient == b.recipient): return True + if (a.sender == b.recipient) and (a.recipient == b.sender): return True + return False + + expected_thread_idx = len([msg for msg in self.messages.values() if _is_current_thread(msg, message)]) + if message.thread_idx != expected_thread_idx: + print('rejected', self.role, message) + return 0 + + if not self.is_player_game(): + print('accepted', self.role, message) + self.messages.put(message.time_sent, message) return message.time_sent diff --git a/diplomacy/engine/message.py b/diplomacy/engine/message.py index 8094bfeb7..720b9c753 100644 --- a/diplomacy/engine/message.py +++ b/diplomacy/engine/message.py @@ -65,13 +65,14 @@ class Message(Jsonable): are stored on server. Therefore, message timestamp is the time when server stores the message, not the time when message was sent by any client. """ - __slots__ = ['sender', 'recipient', 'time_sent', 'phase', 'message'] + __slots__ = ['sender', 'recipient', 'time_sent', 'phase', 'message', 'thread_idx'] model = { strings.SENDER: str, # either SYSTEM or a power name. strings.RECIPIENT: str, # either GLOBAL, OBSERVER, OMNISCIENT or a power name. strings.TIME_SENT: parsing.OptionalValueType(int), # given by server. strings.PHASE: str, # phase short name (e.g. 'S1901M' or 'COMPLETED') strings.MESSAGE: str, + strings.THREAD_IDX: parsing.OptionalValueType(int), } def __init__(self, **kwargs): @@ -80,10 +81,11 @@ def __init__(self, **kwargs): self.time_sent = None # type: int self.phase = None # type: str self.message = None # type: str + self.thread_idx = None # type: int super(Message, self).__init__(**kwargs) def __str__(self): - return '[%d/%s/%s->%s](%s)' % (self.time_sent or 0, self.phase, self.sender, self.recipient, self.message) + return '[%d/%s/%s->%s/tid=%d](%s)' % (self.time_sent or 0, self.phase, self.sender, self.recipient, self.thread_idx if self.thread_idx is not None else -1, self.message) def __hash__(self): return hash(self.time_sent) diff --git a/diplomacy/utils/strings.py b/diplomacy/utils/strings.py index 7fdfa985d..6e55c5c3b 100644 --- a/diplomacy/utils/strings.py +++ b/diplomacy/utils/strings.py @@ -192,6 +192,7 @@ STATES = 'states' STATUS = 'status' SUPPLY_CENTERS = 'supply_centers' +THREAD_IDX = 'thread_idx' TIME_SENT = 'time_sent' TIMESTAMP = 'timestamp' TIMESTAMP_CREATED = 'timestamp_created'