rushnotes: major update, show villain ranges in HUD + other tweaks
This commit is contained in:
parent
96275f9f1e
commit
ae7f983591
|
@ -26,16 +26,14 @@ The existing notes file will be altered by this function
|
||||||
|
|
||||||
########################################################################
|
########################################################################
|
||||||
|
|
||||||
##########for each hand processed, attempts to update hero.xml player notes for FullTilt
|
##########for each hand processed, attempts to create update for player notes in FullTilt
|
||||||
##########based upon the AW howto notes written by Ray E. Barker (nutomatic) at fpdb.sourceforge.net
|
##########based upon the AW howto notes written by Ray E. Barker (nutomatic) at fpdb.sourceforge.net
|
||||||
|
##########Huge thanks to Ray for his guidance and encouragement to create this !!
|
||||||
|
|
||||||
#to do
|
#
|
||||||
### think about seeding
|
|
||||||
### multiple huds firing at the same xml
|
|
||||||
### same player / two levels / only one xml
|
|
||||||
### http://www.faqs.org/docs/diveintopython/kgp_search.html
|
|
||||||
|
|
||||||
#debugmode will write logfiles for the __init__ and update_data methods
|
#debugmode will write logfiles for the __init__ and update_data methods
|
||||||
|
# writes into ./pyfpdb/~Rushdebug.*
|
||||||
|
#
|
||||||
debugmode = False
|
debugmode = False
|
||||||
|
|
||||||
# Standard Library modules
|
# Standard Library modules
|
||||||
|
@ -50,9 +48,10 @@ from Mucked import Aux_Window
|
||||||
from Mucked import Seat_Window
|
from Mucked import Seat_Window
|
||||||
from Mucked import Aux_Seats
|
from Mucked import Aux_Seats
|
||||||
import Stats
|
import Stats
|
||||||
|
import Card
|
||||||
|
|
||||||
#
|
#
|
||||||
# overload minidom methods to fix bug where \n is parsed as " ".
|
# overload minidom methods to fix bug where \n is parsed as " ".
|
||||||
# described here: http://bugs.python.org/issue7139
|
# described here: http://bugs.python.org/issue7139
|
||||||
#
|
#
|
||||||
|
|
||||||
|
@ -110,7 +109,8 @@ class RushNotes(Aux_Window):
|
||||||
notepath = site_params_dict['site_path'] # this is a temporary hijack of site-path
|
notepath = site_params_dict['site_path'] # this is a temporary hijack of site-path
|
||||||
self.heroid = self.hud.db_connection.get_player_id(self.config, sitename, heroname)
|
self.heroid = self.hud.db_connection.get_player_id(self.config, sitename, heroname)
|
||||||
self.notefile = notepath + "/" + heroname + ".xml"
|
self.notefile = notepath + "/" + heroname + ".xml"
|
||||||
|
self.rushtables = ("Mach 10", "Lightning", "Celerity", "Flash", "Zoom")
|
||||||
|
|
||||||
if not os.path.isfile(self.notefile):
|
if not os.path.isfile(self.notefile):
|
||||||
self.active = False
|
self.active = False
|
||||||
return
|
return
|
||||||
|
@ -129,28 +129,27 @@ class RushNotes(Aux_Window):
|
||||||
outputfile.close()
|
outputfile.close()
|
||||||
xmlnotefile.unlink
|
xmlnotefile.unlink
|
||||||
|
|
||||||
|
#
|
||||||
# Create a fresh queue file with skeleton XML
|
# Create a fresh queue file with skeleton XML
|
||||||
#
|
#
|
||||||
self.queuefile = self.notefile + ".queue"
|
self.queuefile = self.notefile + ".queue"
|
||||||
queuedom = minidom.Document()
|
queuedom = minidom.Document()
|
||||||
# Create the minidom document
|
|
||||||
|
|
||||||
# Create the <wml> base element
|
|
||||||
pld=queuedom.createElement("PLAYERDATA")
|
pld=queuedom.createElement("PLAYERDATA")
|
||||||
queuedom.appendChild(pld)
|
queuedom.appendChild(pld)
|
||||||
|
|
||||||
nts=queuedom.createElement("NOTES")
|
nts=queuedom.createElement("NOTES")
|
||||||
pld.appendChild(nts)
|
pld.appendChild(nts)
|
||||||
|
|
||||||
nte = queuedom.createElement("NOTE")
|
nte = queuedom.createElement("NOTE")
|
||||||
nte = queuedom.createTextNode("\n")
|
nte = queuedom.createTextNode("\n")
|
||||||
nts.insertBefore(nte,None)
|
nts.insertBefore(nte,None)
|
||||||
|
|
||||||
outputfile = open(self.queuefile, 'w')
|
outputfile = open(self.queuefile, 'w')
|
||||||
queuedom.writexml(outputfile)
|
queuedom.writexml(outputfile)
|
||||||
outputfile.close()
|
outputfile.close()
|
||||||
queuedom.unlink
|
queuedom.unlink
|
||||||
|
|
||||||
if (debugmode):
|
if (debugmode):
|
||||||
#initialise logfiles
|
#initialise logfiles
|
||||||
debugfile=open("~Rushdebug.init", "w")
|
debugfile=open("~Rushdebug.init", "w")
|
||||||
|
@ -159,19 +158,19 @@ class RushNotes(Aux_Window):
|
||||||
debugfile.write("para="+str(params)+"\n")
|
debugfile.write("para="+str(params)+"\n")
|
||||||
debugfile.write("hero="+heroname+" "+str(self.heroid)+"\n")
|
debugfile.write("hero="+heroname+" "+str(self.heroid)+"\n")
|
||||||
debugfile.write("back="+notefilebackup+"\n")
|
debugfile.write("back="+notefilebackup+"\n")
|
||||||
debugfile.write("queu="+self.queuefile+"\n")
|
debugfile.write("queu="+self.queuefile+"\n")
|
||||||
debugfile.close()
|
debugfile.close()
|
||||||
|
|
||||||
open("~Rushdebug.data", "w").close()
|
open("~Rushdebug.data", "w").close()
|
||||||
|
|
||||||
|
|
||||||
def update_data(self, new_hand_id, db_connection):
|
def update_data(self, new_hand_id, db_connection):
|
||||||
#this method called once for every hand processed
|
#this method called once for every hand processed
|
||||||
# self.hud.stat_dict contains the stats information for this hand
|
# self.hud.stat_dict contains the stats information for this hand
|
||||||
|
|
||||||
if not self.active:
|
if not self.active:
|
||||||
return
|
return
|
||||||
|
|
||||||
if (debugmode):
|
if (debugmode):
|
||||||
debugfile=open("~Rushdebug.data", "a")
|
debugfile=open("~Rushdebug.data", "a")
|
||||||
debugfile.write(new_hand_id+"\n")
|
debugfile.write(new_hand_id+"\n")
|
||||||
|
@ -179,10 +178,11 @@ class RushNotes(Aux_Window):
|
||||||
debugfile.write(now.strftime("%Y%m%d%H%M%S")+ " update_data begins"+ "\n")
|
debugfile.write(now.strftime("%Y%m%d%H%M%S")+ " update_data begins"+ "\n")
|
||||||
debugfile.write("hero="+str(self.heroid)+"\n")
|
debugfile.write("hero="+str(self.heroid)+"\n")
|
||||||
#debugfile.write(str(self.hud.stat_dict)+"\n")
|
#debugfile.write(str(self.hud.stat_dict)+"\n")
|
||||||
debugfile.write(self.hud.table.name+"\n")
|
debugfile.write("table="+self.hud.table.name+"\n")
|
||||||
debugfile.write(str(self.hud.stat_dict.keys())+"\n")
|
debugfile.write("players="+str(self.hud.stat_dict.keys())+"\n")
|
||||||
|
debugfile.write("db="+str(db_connection)+"\n")
|
||||||
|
|
||||||
if self.hud.table.name not in ("Mach 10", "Lightning", "Celerity", "Flash", "Zoom"):
|
if self.hud.table.name not in self.rushtables:
|
||||||
return
|
return
|
||||||
#
|
#
|
||||||
# Grab a list of player id's
|
# Grab a list of player id's
|
||||||
|
@ -212,18 +212,74 @@ class RushNotes(Aux_Window):
|
||||||
agg_freq=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'agg_freq')[3] + " ")
|
agg_freq=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'agg_freq')[3] + " ")
|
||||||
BBper100=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'BBper100')[3])
|
BBper100=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'BBper100')[3])
|
||||||
if BBper100[6] == "-": BBper100=BBper100[0:6] + "(" + BBper100[7:] + ")"
|
if BBper100[6] == "-": BBper100=BBper100[0:6] + "(" + BBper100[7:] + ")"
|
||||||
|
|
||||||
xmlqueuedict[playername] = ("~fpdb~" + "\n" +
|
|
||||||
n + vpip + pfr + three_B + fbbsteal + "\n" +
|
#
|
||||||
steal + cbet + ffreq1 + "\n" +
|
# grab villain known starting hands
|
||||||
agg_freq + BBper100 + "\n" +
|
# only those where they VPIP'd, so limp in the BB will not be shown
|
||||||
|
# sort by hand strength. Output will show position too,
|
||||||
|
# so KK.1 is KK from late posn etc.
|
||||||
|
# ignore non-rush hands (check against known rushtablenames)
|
||||||
|
# cards decoding is hard-coded for holdem, so that's tuff atm
|
||||||
|
# three categories of known hands are shown:
|
||||||
|
# agression preflop hands
|
||||||
|
# non-aggression preflop hands
|
||||||
|
# bigblind called to defend hands
|
||||||
|
#
|
||||||
|
# This isn't perfect, but it isn't too bad a starting point
|
||||||
|
#
|
||||||
|
|
||||||
|
PFcall="PFcall"
|
||||||
|
PFaggr="PFaggr"
|
||||||
|
PFdefend="PFdefend"
|
||||||
|
|
||||||
|
c = db_connection.get_cursor()
|
||||||
|
c.execute(("SELECT handId, position, startCards, street0Aggr, tableName " +
|
||||||
|
"FROM hands, handsPlayers " +
|
||||||
|
"WHERE handsplayers.handId = hands.id " +
|
||||||
|
"AND street0VPI = 1 " +
|
||||||
|
"AND startCards > 0 " +
|
||||||
|
"AND playerId = %d " +
|
||||||
|
"ORDER BY startCards DESC " +
|
||||||
|
";")
|
||||||
|
% int(playerid))
|
||||||
|
|
||||||
|
for (qid, qposition, qstartcards, qstreet0Aggr, qtablename) in c.fetchall():
|
||||||
|
if (debugmode):
|
||||||
|
debugfile.write("pid, hid, pos, cards, aggr, table player"+
|
||||||
|
str(playerid)+"/"+str(qid)+"/"+str(qposition)+"/"+
|
||||||
|
str(qstartcards)+"/"+str(qstreet0Aggr)+"/"+
|
||||||
|
str(qtablename)+"/"+str(playername)+
|
||||||
|
"\n")
|
||||||
|
|
||||||
|
humancards = Card.decodeStartHandValue("holdem", qstartcards)
|
||||||
|
|
||||||
|
if qtablename not in self.rushtables:
|
||||||
|
pass
|
||||||
|
elif qposition == "B" and qstreet0Aggr == False:
|
||||||
|
PFdefend=PFdefend+"/"+humancards
|
||||||
|
elif qstreet0Aggr == True:
|
||||||
|
PFaggr=PFaggr+"/"+humancards+"."+qposition
|
||||||
|
else:
|
||||||
|
PFcall=PFcall+"/"+humancards+"."+qposition
|
||||||
|
c.close
|
||||||
|
|
||||||
|
#
|
||||||
|
# build up final text package (top/tail with ~fpdb~ ~ends~
|
||||||
|
# for later search/replace by Merge module
|
||||||
|
#
|
||||||
|
xmlqueuedict[playername] = ("~fpdb~" + "\n" +
|
||||||
|
n + vpip + pfr + three_B + fbbsteal + "\n" +
|
||||||
|
steal + cbet + ffreq1 + "\n" +
|
||||||
|
agg_freq + BBper100 + "\n" +
|
||||||
|
PFcall+"\n"+PFaggr+"\n"+PFdefend +"\n"
|
||||||
"~ends~")
|
"~ends~")
|
||||||
|
|
||||||
if (debugmode):
|
if (debugmode):
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
debugfile.write(now.strftime("%Y%m%d%H%M%S")+" villain data has been processed" + "\n")
|
debugfile.write(now.strftime("%Y%m%d%H%M%S")+" villain data has been processed" + "\n")
|
||||||
debugfile.write(str(xmlqueuedict)+"\n")
|
debugfile.write(str(xmlqueuedict)+"\n")
|
||||||
|
|
||||||
#
|
#
|
||||||
# delaying processing of xml until now. Grab current queuefile contents and
|
# delaying processing of xml until now. Grab current queuefile contents and
|
||||||
# read each existing NOTE element in turn, if matched to a player in xmlqueuedict
|
# read each existing NOTE element in turn, if matched to a player in xmlqueuedict
|
||||||
|
@ -244,26 +300,26 @@ class RushNotes(Aux_Window):
|
||||||
if len(xmlqueuedict) > 0:
|
if len(xmlqueuedict) > 0:
|
||||||
playerdata=xmlnotefile.lastChild #move to the PLAYERDATA node (assume last one in the list)
|
playerdata=xmlnotefile.lastChild #move to the PLAYERDATA node (assume last one in the list)
|
||||||
notesnode=playerdata.childNodes[0] #Find NOTES node
|
notesnode=playerdata.childNodes[0] #Find NOTES node
|
||||||
|
|
||||||
for newplayer in xmlqueuedict:
|
for newplayer in xmlqueuedict:
|
||||||
newentry = xmlnotefile.createElement("NOTE")
|
newentry = xmlnotefile.createElement("NOTE")
|
||||||
newentry.setAttribute("PlayerId", newplayer)
|
newentry.setAttribute("PlayerId", newplayer)
|
||||||
newentry.setAttribute("Text", xmlqueuedict[newplayer])
|
newentry.setAttribute("Text", xmlqueuedict[newplayer])
|
||||||
notesnode.insertBefore(newentry,None)
|
notesnode.insertBefore(newentry,None)
|
||||||
newentry = xmlnotefile.createTextNode("\n")
|
newentry = xmlnotefile.createTextNode("\n")
|
||||||
notesnode.insertBefore(newentry,None)
|
notesnode.insertBefore(newentry,None)
|
||||||
|
|
||||||
if (debugmode):
|
if (debugmode):
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
debugfile.write(now.strftime("%Y%m%d%H%M%S")+" xml pre-processing complete"+ "\n")
|
debugfile.write(now.strftime("%Y%m%d%H%M%S")+" xml pre-processing complete"+ "\n")
|
||||||
|
|
||||||
#
|
#
|
||||||
# OverWrite existing xml file with updated DOM and cleanup
|
# OverWrite existing xml file with updated DOM and cleanup
|
||||||
#
|
#
|
||||||
updatednotes = open(self.queuefile, 'w')
|
updatednotes = open(self.queuefile, 'w')
|
||||||
xmlnotefile.writexml(updatednotes)
|
xmlnotefile.writexml(updatednotes)
|
||||||
updatednotes.close()
|
updatednotes.close()
|
||||||
|
|
||||||
xmlnotefile.unlink
|
xmlnotefile.unlink
|
||||||
|
|
||||||
if (debugmode):
|
if (debugmode):
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
Merge .queue file with hero's note to generate fresh .merge file
|
Merge .queue file with hero's note to generate fresh .merge file
|
||||||
|
|
||||||
normal usage
|
normal usage
|
||||||
$> ./pyfpdb/RushNotesMerge.py "/home/steve/.wine/drive_c/Program Files/Full Tilt Poker/heroname.xml"
|
$> ./pyfpdb/RushNotesMerge.py "/home/foo/.wine/drive_c/Program Files/Full Tilt Poker/heroname.xml"
|
||||||
|
|
||||||
The generated file can then replace heroname.xml (if all is well).
|
The generated file can then replace heroname.xml (if all is well).
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ import sys
|
||||||
from xml.dom import minidom
|
from xml.dom import minidom
|
||||||
|
|
||||||
#
|
#
|
||||||
# overload minidom methods to fix bug where \n is parsed as " ".
|
# overload minidom methods to fix bug where \n is parsed as " ".
|
||||||
# described here: http://bugs.python.org/issue7139
|
# described here: http://bugs.python.org/issue7139
|
||||||
#
|
#
|
||||||
|
|
||||||
|
@ -92,14 +92,20 @@ try:
|
||||||
sys.argv[1] <> ""
|
sys.argv[1] <> ""
|
||||||
except:
|
except:
|
||||||
print "A parameter is required, quitting now"
|
print "A parameter is required, quitting now"
|
||||||
|
print "normal usage is something like:"
|
||||||
|
print '$> ./pyfpdb/RushNotesMerge.py "/home/foo/.wine/drive_c/Program Files/Full Tilt Poker/myhero.xml"'
|
||||||
quit()
|
quit()
|
||||||
|
|
||||||
if not os.path.isfile(sys.argv[1]):
|
if not os.path.isfile(sys.argv[1]):
|
||||||
print "Hero notes file not found, quitting"
|
print "Hero notes file not found, quitting"
|
||||||
|
print "normal usage is something like:"
|
||||||
|
print '$> ./pyfpdb/RushNotesMerge.py "/home/foo/.wine/drive_c/Program Files/Full Tilt Poker/myhero.xml"'
|
||||||
quit()
|
quit()
|
||||||
|
|
||||||
if not os.path.isfile((sys.argv[1]+".queue")):
|
if not os.path.isfile((sys.argv[1]+".queue")):
|
||||||
print "Nothing queued, quitting"
|
print "Nothing found to merge, quitting"
|
||||||
|
print "Did the HUD not get started during the last session?"
|
||||||
|
print "Has the HUD been stopped and started without merging?"
|
||||||
quit()
|
quit()
|
||||||
|
|
||||||
print "***************************************************************"
|
print "***************************************************************"
|
||||||
|
|
|
@ -12,24 +12,40 @@ RushNotesMerge - stand alone process to merge the existing ftp notes, together w
|
||||||
the output file can then be renamed to become the new ftp notes file
|
the output file can then be renamed to become the new ftp notes file
|
||||||
|
|
||||||
Important info:
|
Important info:
|
||||||
The Merge process can only be run when ftp client is shutdown - otherwise ftp overwrites the xml on exit.
|
The Merge process can only be run when ftp client is shutdown
|
||||||
|
- otherwise ftp overwrites the xml on exit.
|
||||||
|
|
||||||
Restarting the autoimport will empty the notes"queue" so avoid restarting autoimport until the previous
|
Restarting the autoimport will empty the notes"queue" so avoid restarting
|
||||||
notes "queue" has been merged.
|
autoimport until the previous notes "queue" has been merged. You will
|
||||||
|
lose all the queued notes, but these will be regenerated the next time
|
||||||
|
the villian is at your table, so it isn't the end of the world.
|
||||||
|
|
||||||
Existing ftp notes _SHOULD_ be preserved, but this isn't guaranteed, you have been warned
|
Existing ftp notes _SHOULD_ be preserved, but this isn't guaranteed,
|
||||||
Existing colour codings should be preserved, this process should not change colourcodings.
|
you have been warned!
|
||||||
|
|
||||||
|
Existing colour codings should be preserved,
|
||||||
|
this process does not change or set colourcodings.
|
||||||
|
|
||||||
Copies of the live ftp notes file are preserved everytime RushNotesAux is started, just in case.
|
Copies of the live ftp notes file should be preserved everytime
|
||||||
|
RushNotesAux (i.e. the HUD is started)
|
||||||
|
|
||||||
The AW is hard-coded with just the table names of Micro Rush Poker, and should ignore all other hands.
|
The AW is hard-coded with just the table names of Micro Rush Poker,
|
||||||
|
and should ignore all other hands.
|
||||||
|
|
||||||
|
What might not work?
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
This isn't tested with Windows, and probably won't work, feedback welcome.
|
||||||
|
Hasn't been tested for co-existance with other sites, feedback welcome.
|
||||||
|
Whenever FTP change their notes file format, this will all break rather spectacularly,
|
||||||
|
you have been warned!
|
||||||
|
|
||||||
Getting started:
|
Getting started:
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
1. Set the Hero aggregation to alltime. hero_stat_range="A"
|
1. Set the Hero aggregation to alltime. hero_stat_range="A"
|
||||||
This overcomes a sqlite "bug" which has nothing to do with auxillary windows - not doing this
|
This overcomes a sqlite "bug" which has nothing to do with auxillary windows
|
||||||
will slow processing down to about 1 hand per minute.
|
not doing this will slow processing down to about 1 hand per minute.
|
||||||
|
|
||||||
2. Set the site_path to be the folder containing the FTP notes xml file
|
2. Set the site_path to be the folder containing the FTP notes xml file
|
||||||
(on wine this is normally site_path="/home/blah/.wine/Program Files/Full Tilt Poker/")
|
(on wine this is normally site_path="/home/blah/.wine/Program Files/Full Tilt Poker/")
|
||||||
|
@ -43,12 +59,17 @@ Wire-up the aux process:
|
||||||
|
|
||||||
or whatever works for you.
|
or whatever works for you.
|
||||||
|
|
||||||
Start Autoimport, and rearrange the on-screen stats out of the way
|
|
||||||
(killing the HUD kills the AW updates)
|
|
||||||
|
|
||||||
Play some poker
|
Play some poker
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Start Autoimport, and rearrange the on-screen stats out of the way
|
||||||
|
(the full HUD must run, killing the HUD kills the AW updates)
|
||||||
|
|
||||||
|
Play whatever you want
|
||||||
|
|
||||||
Stop the autoimport
|
Stop the autoimport
|
||||||
|
|
||||||
Exit the Full tilt poker client (ensure it has fully stopped with ps -A)
|
Exit the Full tilt poker client (ensure it has fully stopped with ps -A)
|
||||||
|
|
||||||
execute the following:
|
execute the following:
|
||||||
|
@ -59,14 +80,20 @@ A revised notes file (blah.merge) should automagically appear in the full tilt r
|
||||||
If you are happy with it, replace the existing (myname.xml file)
|
If you are happy with it, replace the existing (myname.xml file)
|
||||||
|
|
||||||
|
|
||||||
|
Since the updates aren't real time, it would be ok to play the rush
|
||||||
|
session with fpdb inactive, but before quitting any of the tables,
|
||||||
|
start the HUD and wait for it to catch-up processing all the hands played.
|
||||||
|
|
||||||
|
|
||||||
Summary
|
Summary
|
||||||
------
|
-------
|
||||||
|
|
||||||
This is very rough and ready, but it does what I set-out to achieve.
|
This is very rough and ready, but it does what I set-out to achieve.
|
||||||
|
|
||||||
All feedback welcome, and if this is useful as a basis for general notes processing, then thats great.
|
All feedback welcome, and if this is useful as a basis for general notes
|
||||||
|
processing in future, then thats great.
|
||||||
|
|
||||||
As I find bugs and make improvements, I will push to the git branch.
|
As I find bugs and make improvements, I will push to git.
|
||||||
|
|
||||||
|
|
||||||
Much more information below:
|
Much more information below:
|
||||||
|
@ -136,12 +163,13 @@ It is hoped that due to the relatively large hand volume and relatively small
|
||||||
although there will obviously be a number of players with no fpdb note.
|
although there will obviously be a number of players with no fpdb note.
|
||||||
|
|
||||||
The aggregation parameters used for the notes are based upon the HUD parameters.
|
The aggregation parameters used for the notes are based upon the HUD parameters.
|
||||||
|
(with the exception of the hand-ranges, which uses its' own criteria (see source)
|
||||||
|
|
||||||
Stopping and starting the HUD will erase the previously created notes holding file.
|
Stopping and starting the HUD will erase the previously created notes queue file.
|
||||||
|
|
||||||
The HUD must run, so the individual player popups need to be manually relocated.
|
The HUD must run, so the individual player popups need to be manually relocated.
|
||||||
|
|
||||||
Although hard-coded for micro RUSH tablenames, the auxilliary window will
|
Although hard-coded for micro RUSH tablenames, the auxilliary window could
|
||||||
probably happily update notes of all cash and tournament players.
|
probably happily update notes of all cash and tournament players.
|
||||||
|
|
||||||
Process overview
|
Process overview
|
||||||
|
|
Loading…
Reference in New Issue
Block a user