13
13
from collections import defaultdict
14
14
from math import nan , isnan
15
15
from time import sleep
16
- from typing import Iterator , Callable , Literal
16
+ from typing import Iterator , Callable , Literal , Iterable
17
17
18
18
from sty import fg , rs
19
19
import colorama
20
20
21
21
22
- VERSION = "v2.1. 2"
22
+ VERSION = "v2.2"
23
23
follow_mode = False # False -> analyze mode
24
24
25
25
dt_dict = {
@@ -51,6 +51,7 @@ class LogEnd(Exception):
51
51
52
52
class Constants :
53
53
NICKNAME = 'Net [Info]: name: '
54
+ SQUAD_MEMBER = 'loadout loader finished.'
54
55
HEIST_START = 'jobId=/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyFour'
55
56
HOST_MIGRATION = '"jobId" : "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyFour'
56
57
HEIST_ABORT = 'SetReturnToLobbyLevelArgs: '
@@ -72,6 +73,17 @@ def color(text: str, col: str) -> str:
72
73
return col + text + rs .fg
73
74
74
75
76
+ def oxfordcomma (collection : Iterable [str ]):
77
+ collection = list (collection )
78
+ if len (collection ) == 0 :
79
+ return ''
80
+ if len (collection ) == 1 :
81
+ return collection [0 ]
82
+ if len (collection ) == 2 :
83
+ return collection [0 ] + ' and ' + collection [1 ]
84
+ return ', ' .join (collection [:- 1 ]) + ', and ' + collection [- 1 ]
85
+
86
+
75
87
def time_str (seconds : float , format_ : Literal ['brackets' , 'units' ]) -> str :
76
88
if format_ == 'brackets' :
77
89
return f'[{ int (seconds / 60 )} :{ int (seconds % 60 ):02d} ]'
@@ -88,6 +100,7 @@ class RelRun:
88
100
def __init__ (self ,
89
101
run_nr : int ,
90
102
nickname : str ,
103
+ squad_members : set [str ],
91
104
pt_found : float ,
92
105
phase_durations : dict [int , float ],
93
106
shields : dict [float , list [tuple [str , float ]]],
@@ -96,6 +109,7 @@ def __init__(self,
96
109
pylon_dur : dict [int , float ]):
97
110
self .run_nr = run_nr
98
111
self .nickname = nickname
112
+ self .squad_members = squad_members
99
113
self .pt_found = pt_found
100
114
self .phase_durations = phase_durations
101
115
self .shields = shields
@@ -126,7 +140,8 @@ def pretty_print(self):
126
140
print (f'{ fg .white } { "-" * 72 } \n \n ' ) # footer
127
141
128
142
def pretty_print_run_summary (self ):
129
- run_info = f'{ fg .cyan } Profit-Taker Run #{ self .run_nr } by { fg .li_cyan } { self .nickname } { fg .cyan } cleared in ' \
143
+ players = oxfordcomma ([self .nickname ] + list (self .squad_members - {self .nickname }))
144
+ run_info = f'{ fg .cyan } Profit-Taker Run #{ self .run_nr } by { fg .li_cyan } { players } { fg .cyan } cleared in ' \
130
145
f'{ fg .li_cyan } { time_str (self .length (), "units" )} '
131
146
if self .best_run :
132
147
run_info += f'{ fg .white } - { fg .li_magenta } Best run!'
@@ -186,6 +201,7 @@ class AbsRun:
186
201
def __init__ (self , run_nr : int ):
187
202
self .run_nr = run_nr
188
203
self .nickname = ''
204
+ self .squad_members : set [str ] = set ()
189
205
self .heist_start = 0.0
190
206
self .pt_found = 0.0
191
207
self .shields : dict [float , list [tuple [str , float ]]] = defaultdict (list ) # phase -> list((type, absolute time))
@@ -239,7 +255,8 @@ def to_rel(self) -> RelRun:
239
255
# Set phase 3.5 shields
240
256
shields [3.5 ] = [(shield , nan ) for shield , _ in self .shields [3.5 ]]
241
257
242
- return RelRun (self .run_nr , self .nickname , pt_found , phase_durations , shields , legs , body_dur , pylon_dur )
258
+ return RelRun (self .run_nr , self .nickname , self .squad_members , pt_found ,
259
+ phase_durations , shields , legs , body_dur , pylon_dur )
243
260
244
261
245
262
def time_from_line (line : str ) -> float :
@@ -274,7 +291,8 @@ def register_phase(log: Iterator[str], run: AbsRun, phase: int):
274
291
lambda line : Constants .NICKNAME in line ,
275
292
lambda line : Constants .ELEVATOR_EXIT in line ,
276
293
lambda line : Constants .HEIST_START in line , # Functions as abort as well
277
- lambda line : Constants .HOST_MIGRATION in line ])
294
+ lambda line : Constants .HOST_MIGRATION in line ,
295
+ lambda line : Constants .SQUAD_MEMBER in line ])
278
296
if match == 0 : # Shield switch
279
297
# Shield_phase '3.5' is for when shields swap during the pylon phase in phase 3.
280
298
shield_phase = 3.5 if phase == 3 and 3 in run .pylon_start else phase
@@ -314,6 +332,8 @@ def register_phase(log: Iterator[str], run: AbsRun, phase: int):
314
332
raise RunAbort (require_heist_start = False )
315
333
elif match == 10 : # Host migration
316
334
raise RunAbort (require_heist_start = True )
335
+ elif match == 11 : # Squad member
336
+ run .squad_members .add (line .split ()[- 4 ])
317
337
318
338
319
339
def read_run (log : Iterator [str ], run_nr : int , require_heist_start = False ) -> AbsRun :
0 commit comments