# -*- coding: utf-8 -*-
"""
Created on Mon Jan 31 15:42:10 2022
@author: AisiYidingbai
"""
# init
import os
import re
import json
from math import e
import numpy as np
import pandas as pd
from datetime import datetime
file = "pointinator.txt"
bak1 = "pointinator.bak1.txt"
bak2 = "pointinator.bak2.txt"
bak3 = "pointinator.bak3.txt"
drill_file = "drill.txt"
drill_bak1 = "drill.bak1.txt"
drill_bak2 = "drill.bak2.txt"
drill_bak3 = "drill.bak3.txt"
paramsfile = "params.txt"
#%% I/O Functions
# load the sheet
def loadsheet():
data = pd.read_csv(file)
data = data[{'Participant', 'Value', 'Type', 'Date'}]
return data
# save the sheet
def savesheet(data):
if(os.path.exists(bak3)):
os.remove(bak3) # remove backup 3 if it exists
if(os.path.exists(bak2)):
os.rename(bak2, bak3) # make backup 2 to backup 3 if it exists
if(os.path.exists(bak1)):
os.rename(bak1, bak2) # make backup 1 to backup 2 if it exists
if(os.path.exists(file)):
os.rename(file, bak1) # make the previous sheet to backup 1
data.to_csv(file) # save the current sheet
# undo the last change
def undosheet():
if(os.path.exists(bak1)):
os.remove(file) # delete the current sheet
os.rename(bak1, file) # reinstate backup 1 as the current sheet
if(os.path.exists(bak2)):
os.rename(bak2, bak1) # reinstate backup 2 as backup 1 if it exists
if(os.path.exists(bak3)):
os.rename(bak3, bak2) # reinstate backup 3 as backup 2 if it exists
data = loadsheet()
lastmodified = max(data['Date'])
res1 = "OK, reverting to sheet last saved on " + str(lastmodified)
res2 = gettierlist(data)
return [res1, res2]
else:
res1 = "Cannot undo. No more undos available." # if there are no more backups, refuse to undo
res2 = gettierlist(data)
return [res1, res2]
#%% Sheet Functions
# get user from regex
def getuser(string, data): # collect a string to match to the sheet
userlist = getuserlist(data) # get the list of participants already in the sheet
founduser = None
for user in userlist:
if(string == user): # first look for a direct match
founduser = user
break
if(founduser is None):
for user in userlist:
if(re.search("^" + string, user, re.IGNORECASE)): # then look for a wildcard* match
founduser = user
break
if(founduser is None):
for user in userlist:
if(re.search(string + "$", user, re.IGNORECASE)): # then look for a *wildcard match
founduser = user
break
if(founduser is None):
for user in userlist:
if(re.search(re.sub("(.)", ".*\\1", string) + ".*", user, re.IGNORECASE)): # then look for an abbreviated match
founduser = user
break
return founduser
# add points
def addpoints(string, points, datatype, data):
data = data.loc[data['Participant'] != "No-one yet"] # delete the initialising entry for new sheets if it is there
founduser = getuser(string, data) # call getuser()
if(founduser):
user = founduser # if a participant was found, use it
res2 = False # return the flag res2, True if a new participant was added when adding points
else:
user = string # if not, add a new participant
res2 = True
date = datetime.now().strftime("%Y-%m-%dT%H:%M:%S") # get the current date and time
res1 = data.append({'Participant':user, 'Value':points, 'Type':str(datatype), 'Date':date}, ignore_index=True) # add an entry to the sheet
return [res1, res2]
# reset the sheet
def reset():
data = pd.DataFrame([], columns = ['Participant', 'Value', 'Type', 'Date'])
date = datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
data = data.append({'Participant':"No-one yet", 'Value':0, 'Type': "point", 'Date':date}, ignore_index=True)
savesheet(data)
# get userlist
def getuserlist(data):
userlist = list(set(data["Participant"])) # find all participants currently in the list
return userlist
# pivot sheet
def pivotsheet(data, datatype):
tierlist = data.loc[data['Type'] == str(datatype)]
tierlist = tierlist.groupby('Participant').sum('Value') # pivot the sheet to get points per participant
tierlist['LogPoints'] = np.log(1 + tierlist['Value']) # calculate log(points + 1)
return tierlist
# get logcurrentmax
def getlogcurrentmax(tierlist):
loghighscore = max(tierlist['LogPoints']) # determine max tier requirement by calculating log(highscore + 1) and log(pseudomax)
pseudomax = np.power(int(params['cap']), (int(params['tcap'])-1)/(int(params['tcap'])-2)) # calculate the pseudomax from the cap (think upper end of T10, a.k.a. T11)
logpseudomax = np.log(pseudomax)
logcurrentmax = min(loghighscore, logpseudomax) # use the minimum of the two values. max tier requirement rises with the highscore until it reaches the cap
return logcurrentmax
# get tierlist
def gettierlist(data):
tierlist = pivotsheet(data, "point") # pivot the sheet for points
logcurrentmax = getlogcurrentmax(tierlist) # calculate the log current max ("T11")
tierlist['Tier'] = np.ceil(tierlist['LogPoints'] / logcurrentmax * (params['tcap']-1) + 1) # calculate the current tiers
offsets = pivotsheet(data, "tier")['Value'] # pivot the sheet for tiers
tierlist = tierlist.join(offsets, on = "Participant", how = "left", rsuffix = ".tier") # join tiers to point sheet
tierlist['Value.tier'][np.isnan(tierlist['Value.tier'])] = 0
tierlist['Tier'] = np.minimum(np.minimum(tierlist['Tier'], params['tcap']) + tierlist['Value.tier'], params['thardcap']) # don't let the tier exceed the max
tierlist = tierlist.sort_values(['Value'], ascending = False) # sort the sheet by descending points
cols = ['Value', 'Tier']
tierlist = tierlist[cols]
return tierlist
# calculate tiers
def calculatetiers(data):
tierlist = pivotsheet(data, "point") # pivot the sheet
logcurrentmax = getlogcurrentmax(tierlist) # calculate the log current max ("T11")
currenttiers = list()
tierpoints = list()
for i in range((int(params['tcap'])-2), -2, -1):
currenttiers.append("T" + str(i+2))
tierpoints.append(np.ceil(np.power(np.power(e,logcurrentmax),(i/(params['tcap']-1))))) # use meth to determine the T1-10 requirements
tiers = pd.DataFrame({'Tier':currenttiers, 'Value':tierpoints})
tiers = tiers.set_index('Tier')
return tiers
def findparam(key):
found = None
paramlist = list(params.keys())
for p in paramlist:
if(key == p): # first look for a direct match
found = p
break
if(found is None):
for p in paramlist:
if(re.search("^" + key, p, re.IGNORECASE)): # then look for a wildcard* match
found = p
break
if(found is None):
for p in paramlist:
if(re.search(key + "$", p, re.IGNORECASE)): # then look for a *wildcard match
found = p
break
if(found is None):
for p in paramlist:
if(re.search(re.sub("(.)", ".*\\1", key) + ".*", p, re.IGNORECASE)): # then look for an abbreviated match
found = p
break
return(found)
#%% Message Functions
def msg_add(participant, points): # routine to add points
data = loadsheet()
points = np.round(points, 1) # round the number of points to 1 d.p.
if(isinstance(participant, str)): # routine to deal with a single participant
ret = addpoints(participant, points, "point", data)
data = ret[0]
if(ret[1]):
res1 = "OK, adding " + str(points) + " points for the new participant " + str(participant) + "."
else:
res1 = "OK, adding " + str(points) + " points for " + str(getuser(participant, data)) + "."
res2 = gettierlist(data)
savesheet(data)
return [res1, res2]
else: # routine to deal with multiple participants at once
u = ""
for p in participant:
data = addpoints(p, points, "point", data)[0]
u = u + ", " + str(getuser(str(p), data))
res1 = "OK, adding " + str(points) + " points for the following participants: " + re.sub("^, ", "", u)
res2 = gettierlist(data)
savesheet(data)
return [res1, res2]
def msg_offset(participant, tiers): # routine to add tiers
data = loadsheet()
tiers = np.round(tiers, 0) # round the number of tiers to whole tiers
ret = addpoints(participant, tiers, "tier", data)
data = ret[0]
if(ret[1]):
res1 = "OK, adding " + str(tiers) + " tiers for the new participant " + str(participant) + "."
else:
res1 = "OK, adding " + str(tiers) + " tiers for " + str(getuser(participant, data)) + "."
res2 = gettierlist(data)
savesheet(data)
return [res1, res2]
def msg_distribute(participant, points): # routine to divide points among multiple participants
if(isinstance(participant, str)):
res = msg_add(participant, points) # call regular add routine if only one participant
else:
pointseach = points / len(participant) # calculate points each and then call add routine if more than one participant
res = msg_add(participant, pointseach)
res[0] = res[0] + "; for a total of " + str(points) + " points."
return [res[0], res[1]]
def msg_show(): # routine to show the current sheet
data = loadsheet()
tierlist = gettierlist(data)
res1 = "Here is the current sheet:"
res2 = tierlist
return [res1, res2]
def msg_new(participant): # routine to add a new participant
data = loadsheet()
data = addpoints(participant, 0, "point", data)[0]
res1 = "OK, adding " + str(participant) + " as a new participant."
res2 = gettierlist(data)
savesheet(data)
return [res1, res2]
def msg_tiers(): # routine to show the current point requirements per tier
data = loadsheet()
tiers = calculatetiers(data)
res1 = "Here are the current point requirements per tier:"
res2 = tiers
return [res1, res2]
def msg_whois(participant): # routine to test participant abbreviations
data = loadsheet()
founduser = getuser(participant, data)
if(founduser is not None):
res = "I recognised \"" + participant + "\" as " + getuser(participant, data) + "."
else:
res = "I could not recognise anyone by \"" + participant + "\"."
return res
def msg_getcap(): # routine to show the cap
res = "The cap is " + str(params['cap']) + ". Participants scoring higher than this are guaranteed a T" + str(params['tcap']) + " payout."
return res
def msg_setcap(value): # routine to change the cap
params['cap'] = value
res = "OK, changing the cap to " + str(params['cap']) + ". Participants scoring higher than this are guaranteed a T" + str(params['tcap']) + " payout."
with open(paramsfile, 'w') as f: # save to file
json.dump(params, f, indent = 4)
return res
def msg_setparam(key, value): # routine to change parameters
found = findparam(key)
if(found is not None):
old = params[found]
params[found] = value
res = "OK, changing the value of " + str(found) + " from " + str(old) + " to " + str(value) + "."
with open(paramsfile, 'w') as f: # save to file
json.dump(params, f, indent = 4)
else:
res = "I couldn't find any parameter called " + str(key) + "."
return res
def msg_getparam(key): # routine to view parameters
found = findparam(key)
if(found is not None):
res = "The value of " + str(found) + " is " + str(params[found]) + "."
else:
res = "I couldn't find any parameter called " + str(key) + "."
return res
def msg_audit(lines):
data = loadsheet()
sli = data.iloc[-int(lines):]
cols = ['Participant', 'Value', 'Type', 'Date']
res1 = "Here are the last " + str(lines) + " lines of the sheet:"
res2 = sli[cols]
return [res1, res2]
def msg_edit(row, col, newvalue):
data = loadsheet()
row = int(row)
if(row > len(data)-1):
res = "Error point-editing the sheet. Row number out of bounds"
return res
elif(col != "Participant" and col != "Date" and col != "Value" and col != "Type"):
res = "Error point-editing the sheet. Column name must be one of: Participant, Date, Value, Type"
return res
else:
old = data.at[row,col]
data.at[row,col] = newvalue
savesheet(data)
sli = data.iloc[row:row+1]
res1 = "OK, successfully point-edited the " + col + " at line " + str(row) + " from " + str(old) + " to " + str(newvalue) + "."
res2 = sli
return [res1, res2]
def msg_reset(confirm): # routine to wipe the sheet. use reset confirm to confirm
if(confirm == "confirm"):
reset()
res = "OK, I've wiped the sheet."
else:
res = "Are you sure? This will wipe the sheet. Type `reset confirm` to confirm you want to do this."
return res
def msg_params():
res1 = "Here are the current Pointinator parameters:"
res2 = pd.DataFrame.from_dict(params, orient='index')
return [res1, res2]
def msg_help():
res = """Possible Pointinator commands:
`add`, `split`, `offset`, `show`, `undo`, `new`, `tiers`, `whois`, `set`, `params`, `reset`, `audit`, `points`, `help`, `info`.\n
Possible Pointinator guild drill commands:
`drill add`, `drill show`, `drill summary`, `drill progress`, `drill undo`, `drill audit`, `drill reset`.\n
Issue `help` for detailed usage instructions.
"""
return res
def msg_man():
res = """Usage:
\t`add <p1 p2 ...> <n>`: Award *n* points to one or more participants *p*
\t`split <p1 p2 ...> <n>`: Divide *n* points between participants *p*
\t`offset <p> <n>`: Add *n* tiers to a participant *p*
\t`show`: Show the current sheet
\t`undo`: Undo the last change
\t`new <p>`: Add a new participant *p*
\t`tiers`: Show the current point requirements per tier
\t`whois <name>`: See if I can recognise a participant *name*
\t`set <param> <value>`: Change a Pointinator *param*eter to *value*
\t`params`: Show all Pointinator parameters
\t`reset`: Wipe the sheet
\t`audit <n>`: Show the last *n* sheet actions
\t`points`: Show point values
\t`help`: Show this help
\t`info`: Show info on Pointinator\n
Drill commands:
\t`drill add <p> <n> <mat>`: Record *n* drill *mat*erials for a participant *p*
\t`drill show`: Show a comprehensive report of drill materials
\t`drill summary`: Show a summary of drill materials
\t`drill progress`: Show a summary of drill progress
\t`drill undo`: Undo the last change to the drill sheet
\t`drill audit <n>`: Show the last *n* sheet actions
\t`drill reset`: Wipe the drill sheet
"""
return res
def msg_points():
res = """Point values:
\t**S guild missions**: 1 point
\t**M guild missions**: 2 points
\t**L guild missions**: 3 points
\t**XL guild missions**: 4 points
\t**3k anymob missions**: 3 points
\t**5k anymob missions**: 4 points
\t**7k anymob missions**: 5 points
\t**10k anymob missions**: 6 points
\t**Mudster boss subjugation missions**: 2 points
\t**Ferrid boss subjugation missions**: 2 points
\t**Puturum boss subjugation missions**: 6 points
\t**Attending guild events**: 2 points
\t**Depositing a [Guild] Steel Candidum Shell**: 16 points
\t**Depositing [Guild] Drill materials**: see `drill` commands
"""
return res
def msg_info():
res = """**Pointinator** helps keep track of guild points. It was developed by Yidingbai.
*Using Pointinator*
Type Pointinator commands into the chat box in the #points channel. Type `help` to see all of my functions.
*Functions*
I can add and split points to one or more participants at once. Once a participant has been added, I will try to recognise nicknames for that participant using partial and wildcard matches. If you make a mistake, you can undo up to three times in a row.
*Service status*
Pointinator should be available as long as I am online on Discord. Check my online status in the members list. Please note that I undergo nightly maintenance at 4:00 UTC+0 for up to an hour.
*Terms*
Use of Pointinator is subject to terms of service <https://aisiydb.ddns.net/pointinator-terms-of-service/> and privacy policy <https://aisiydb.ddns.net/pointinator-privacy-policy/>. Pointinator is open-source software and the source code can be found here: <https://aisiydb.ddns.net/pointinator-source-code/>.
"""
return res
#%% Main Routine
def on_command(command): # check the contents of the command
parsed = re.split(" ", command)
keyword = parsed[0].lower()
if(keyword == "add" or keyword == "give"): # add command:
if(len(parsed) < 3): # fail if less than 2 arguments
ret = msg_help()
elif(not re.search("^-*\d*[\.,]*\d*$", parsed[-1])): # fail if points arg is not a number
ret = msg_help()
elif(len(parsed) == 3): # accept if one participant
ret = msg_add(parsed[1], float(parsed[2]))
else: # accept if more than one participant
ret = msg_add(parsed[1:len(parsed)-1], float(re.sub(",", ".", parsed[-1])))
elif(keyword == "offset"):
if(len(parsed) < 3):
ret = msg_help()
elif(not re.search("^-*\d*[\.,]*\d*$", parsed[2])): # fail if points arg is not a number
ret = msg_help()
else:
ret = msg_offset(parsed[1], float(parsed[2]))
elif(keyword == "distribute" or keyword == "split"): # distribute command:
if(len(parsed) < 3): # fail if less than 2 arguments
ret = msg_help()
elif(len(parsed) == 3): # if only one participant, call add instead
ret = msg_add(parsed[1], float(parsed[2]))
else: # call distribute if more than one participant
ret = msg_distribute(parsed[1:len(parsed)-1], float(re.sub(",", ".", parsed[-1])))
elif(keyword == "undo"):
ret = undosheet()
elif(keyword == "show"):
ret = msg_show()
elif(keyword == "new"):
ret = msg_new(parsed[1])
elif(keyword == "tiers"):
ret = msg_tiers()
elif(keyword == "whois"):
if(len(parsed) < 2):
ret = msg_help()
else:
ret = msg_whois(parsed[1])
elif(keyword == "getcap"):
ret = msg_getcap()
elif(keyword == "setcap"):
if(len(parsed) < 2):
ret = msg_help()
elif(not parsed[1].isnumeric()):
ret = msg_help()
else:
ret = msg_setcap(int(parsed[1]))
elif(keyword == "setparam" or keyword == "set"):
if(len(parsed) < 3):
ret = msg_help()
elif(not re.search("^-*\d*[\.,]*\d*$", parsed[2])): # fail if points arg is not a number
ret = msg_help()
else:
ret = msg_setparam(' '.join(parsed[1:len(parsed)-1]), float(re.sub(",", ".", parsed[-1])))
elif(keyword == "getparam"):
if(len(parsed) < 2):
ret = msg_help()
else:
ret = msg_getparam(' '.join(parsed[1:]))
elif(keyword == "params"):
ret = msg_params()
elif(keyword == "audit"):
if(len(parsed) < 2):
ret = msg_help()
elif(not re.search("^\d+$", parsed[1])): # fail if points arg is not a number
ret = msg_help()
else:
ret = msg_audit(parsed[1])
elif(keyword == "edit"):
if(len(parsed) < 4):
ret = msg_help()
elif(not re.search("^\d+$", parsed[1])):
ret = msg_help()
else:
ret = msg_edit(parsed[1], parsed[2], parsed[3])
elif(keyword == "reset"):
if(len(parsed) < 2):
ret = msg_reset(False)
else:
ret = msg_reset(parsed[1])
elif(keyword == "points"):
ret = msg_points()
elif(keyword == "help"):
ret = msg_man()
elif(keyword == "info"):
ret = msg_info()
elif(keyword == "drill"):
if(len(parsed) < 2):
ret = msg_help()
else:
ret = drill_on_command(command) # send to drill functions
elif(keyword == "chat"):
return None
else:
ret = msg_help()
return(ret)
#%% Drill Functions
# load the sheet
def drill_loadsheet():
drill_data = pd.read_csv(drill_file)
drill_data = drill_data[{'Participant', 'Item', 'Amount', 'Date'}]
return drill_data
# save the sheet
def drill_savesheet(drill_data):
if(os.path.exists(drill_bak3)):
os.remove(drill_bak3) # remove backup 3 if it exists
if(os.path.exists(drill_bak2)):
os.rename(drill_bak2, drill_bak3) # make backup 2 to backup 3 if it exists
if(os.path.exists(drill_bak1)):
os.rename(drill_bak1, drill_bak2) # make backup 1 to backup 2 if it exists
if(os.path.exists(drill_file)):
os.rename(drill_file, drill_bak1) # make the previous sheet to backup 1
drill_data.to_csv(drill_file) # save the current sheet
# undo the last change
def drill_undosheet():
if(os.path.exists(drill_bak1)):
os.remove(drill_file) # delete the current sheet
os.rename(drill_bak1, drill_file) # reinstate backup 1 as the current sheet
if(os.path.exists(drill_bak2)):
os.rename(drill_bak2, drill_bak1) # reinstate backup 2 as backup 1 if it exists
if(os.path.exists(drill_bak3)):
os.rename(drill_bak3, drill_bak2) # reinstate backup 3 as backup 2 if it exists
drill_data = drill_loadsheet()
lastmodified = max(drill_data['Date'])
res1 = "OK, reverting to sheet last saved on " + str(lastmodified)
res2 = drill_summary()
return [res1, res2]
else:
res1 = "Cannot undo. No more undos available." # if there are no more backups, refuse to undo
res2 = drill_summary()
return [res1, res2]
# reset the sheet
def drill_reset(): # reset the sheet
date = datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
data = {'Participant':["dummy"] * 15,
'Item':['Old Tree\'s Tear',
'Elder Tree Plywood',
'Palm Plywood',
'Standardized Timber Square',
'Black Stone Powder',
'Earth\'s Tear',
'Steel',
'Vanadium Ingot',
'Log',
'Bronze Ingot',
'Black Stone (Weapon)',
'Black Stone (Armor)',
'Powder of Flame',
'Fire Horn',
'Coal'],
'Amount':[0] * 15,
'Date':[date] * 15}
drill_data = pd.DataFrame(data)
drill_data = drill_data.append({'Participant':"No-one yet", 'Item':"Nothing", 'Amount':0, 'Date':date}, ignore_index = True)
drill_savesheet(drill_data)
# get the list of participants or materials
def drill_getlist(drill_data, datatype):
userlist = list(set(drill_data[datatype])) # datatype can either be Participant or Item
userlist = sorted(userlist, key = len)
return userlist
# get user or mat from regex
def drill_getname(string, drill_data, datatype): # collect a string to match to the sheet
userlist = drill_getlist(drill_data, datatype) # get the list of participants already in the sheet
founduser = None
for user in userlist:
if(string == user): # first look for a direct match
founduser = user
break
if(founduser is None):
for user in userlist:
if(re.search("^" + string, user, re.IGNORECASE)): # then look for a wildcard* match
founduser = user
break
if(founduser is None):
for user in userlist:
if(re.search(string + "$", user, re.IGNORECASE)): # then look for a *wildcard match
founduser = user
break
if(founduser is None):
for user in userlist:
if(re.search(re.sub("(.)", ".*\\1", string) + ".*", user, re.IGNORECASE)): # then look for an abbreviated match
founduser = user
break
return founduser
# add mat
def drill_addpoints(string, amount, material, drill_data):
drill_data = drill_data.loc[drill_data['Participant'] != "No-one yet"] # delete the initialising entry for new sheets if it is there
founduser = drill_getname(string, drill_data, "Participant") # call getuser()
if(founduser):
user = founduser # if a participant was found, use it
res2 = False # return the flag res2, True if a new participant was added when adding points
else:
user = string # if not, add a new participant
res2 = True
foundmat = drill_getname(material, drill_data, "Item")
if(foundmat):
mat = foundmat
else:
foundmat = material
date = datetime.now().strftime("%Y-%m-%dT%H:%M:%S") # get the current date and time
res1 = drill_data.append({'Participant':user, 'Amount':amount, 'Item':mat, 'Date':date}, ignore_index=True) # add an entry to the sheet
return [res1, res2]
# show summary
def drill_summary():
drill_data = drill_show()
drill_data = drill_data.reset_index()
drill_data['Points'] = drill_data['Silver (M)'] / 2
drill_data['Points'] = drill_data['Silver (M)'] / 2
drill_data['Points'] = np.round(drill_data['Points'], 1)
drill_data = drill_data.groupby('Participant').sum(['Silver', 'Points'])
drill_data = drill_data.sort_values('Silver (M)', ascending = False)
return drill_data
# show full sheet
def drill_show():
drill_data = drill_loadsheet()
drill_data = drill_data.loc[drill_data['Participant'] != "dummy"] # data has dummy rows with all mats but zero amount and assigned to no-one. remove
drill_data = drill_data.groupby(['Participant', 'Item']).sum('Amount') # group by participant and item, show a sum of the amount
drill_data = drill_data.reset_index()
silver = [] # calculate silver
for i in range(len(drill_data)):
item = drill_data.iloc[i,1]
amount = drill_data.iloc[i,2]
silver.append(amount * params[item] / 1000000) # using the parameters file, and then calculate millions of silver
drill_data['Silver (M)'] = silver
drill_data['Silver (M)'] = np.round(drill_data['Silver (M)'], 1) # round to 1 d.p.
drill_data = drill_data.groupby(['Participant', 'Item']).sum(['Silver'])
return drill_data
# show totals
def drill_totals():
drill_data = drill_loadsheet()
drill_pivoted = drill_data.groupby(['Item']).sum('Amount') # group by item only, show a sum of the total amount contributed so far
drill_pivoted = drill_pivoted.reset_index()
drill_totals = [ # make an array of mats and requirements
['(multiple)','Old Tree\'s Tear',200],
['Support','Elder Tree Plywood',100],
['Support','Palm Plywood',100],
['Support','Standardized Timber Square',100],
['(multiple)','Black Stone Powder',4000],
['(multiple)','Earth\'s Tear',200],
['(multiple)','Steel',200],
['Shaft','Vanadium Ingot',100],
['Grip','Log',100],
['Grip','Bronze Ingot',100],
['Pin','Titanium Ingot',100],
['Fuel','Black Stone (Weapon)',5000],
['Fuel','Black Stone (Armor)',5000],
['Fuel','Powder of Flame',5000],
['Fuel','Fire Horn',5000],
['Fuel','Coal',5000]
]
drill_totals = pd.DataFrame(drill_totals, columns = ['Part', 'Item', 'Required'])
drill_pivoted = drill_pivoted.merge(drill_totals, on = "Item", how = "outer")# merge arrays
drill_pivoted = drill_pivoted.sort_values("Part")
drill_pivoted['Amount'] = drill_pivoted['Amount'].fillna(0)
drill_pivoted = drill_pivoted.set_index(['Part','Item'])
return drill_pivoted
#%% Drill Message Functions
# routine to add items
def msg_drill_add(participant, amount, material): # routine to add mats to the sheet
drill_data = drill_loadsheet()
ret = drill_addpoints(participant, amount, material, drill_data)
drill_data = ret[0]
if(ret[1]):
res1 = "OK, adding " + str(amount) + " " + str(drill_getname(material, drill_data, "Item")) + " for the new participant " + str(participant) + "."
else:
res1 = "OK, adding " + str(amount) + " " + str(drill_getname(material, drill_data, "Item")) + " for " + str(drill_getname(participant, drill_data, "Participant")) + "."
drill_savesheet(drill_data)
res2 = drill_summary()
return [res1, res2]
def msg_drill_summary(): # routine to show per participant summary
drill_data = drill_summary()
res1 = "Here is the current sheet:"
res2 = drill_data
return [res1, res2]
def msg_drill_show(): # routine to show the a comprehensive report
drill_report = drill_show()
res1 = "Here is the current sheet:"
res2 = drill_report
return [res1, res2]
def msg_drill_totals(): # routine to show a per mat summary
totals = drill_totals()
res1 = "Here's the drill progress:"
res2 = totals
return [res1, res2]
def msg_drill_audit(lines):
drill_data = drill_loadsheet()
drill_data = drill_data.iloc[-int(lines):]
cols = ['Participant', 'Amount', 'Item', 'Date']
res1 = "Here are the last " + str(lines) + " lines of the sheet:"
res2 = drill_data[cols]
return [res1, res2]
def msg_drill_reset(confirm): # routine to wipe the sheet. use reset confirm to confirm
if(confirm == "confirm"):
drill_reset()
res = "OK, I've wiped the drill sheet."
else:
res = "Are you sure? This will wipe the drill sheet. Type `drill reset confirm` to confirm you want to do this."
return res
#%% Drill Main Routine
# main drill routine
def drill_on_command(command):
parsed = re.split(" ", command)
keyword = parsed[1].lower()
if(keyword == "add"): # detect add
if(len(parsed) < 5): # fail if fewer than 3 args
ret = msg_help()
elif(not re.search("^-*\d+$", parsed[3])): # fail if points arg is not a number
ret = msg_help()
elif(drill_getname(' '.join(parsed[4:]), drill_loadsheet(), "Item") is None):# fail if no item of that name (arg 3) can be found
ret = msg_help()
else:
ret = msg_drill_add(parsed[2], int(parsed[3]), ' '.join(parsed[4:]))# accept multi-word argument in arg 3
elif(keyword == "show"): # detect show
ret = msg_drill_show()
elif(keyword == "summary"): # detect summary
ret = msg_drill_summary()
elif(keyword == "progress"): # detect progress
ret = msg_drill_totals()
elif(keyword == "undo"): # detect undo
ret = drill_undosheet()
elif(keyword == "audit"):
if(len(parsed) < 3):
ret = msg_help()
elif(not re.search("^\d+$", parsed[2])): # fail if arg is not a number
ret = msg_help()
else:
ret = msg_drill_audit(parsed[2])
elif(keyword == "reset"): # detect reset
if(len(parsed) < 3):
ret = msg_drill_reset(False)
else:
ret = msg_drill_reset(parsed[2])
else:
ret = msg_help()
return ret
#%% Discord
import discord
client = discord.Client()
@client.event
async def on_ready():
print('We have logged in as {0.user}'.format(client)) # inform ready in console
#@client.event # deprecated text-only messaging
#async def on_message(message):
# if(message.author.bot):
# return # don't respond to self
# elif(str(message.channel) != "points"):
# return # don't chat in the wrong channels
# else:
# command = message.content # get the discord message and interpret it
# ret = on_command(command)
# await message.channel.send(ret)
@client.event
async def on_message(message): # with embeds
if(message.author.bot):
return # don't respond to self
elif(str(message.channel) != "points"):
return # don't chat in the wrong channels
else:
command = message.content # get the discord message and interpret it
ret = on_command(command)
commandecho = "*Your command: *`" + command + "`" + "\n"
if(isinstance(re.match("drill", command), re.Match)):
colour = discord.Colour.dark_gold()
else:
colour = discord.Colour.teal()
if(isinstance(ret, str)):
ret = commandecho + ret
await message.channel.send(ret)
elif(isinstance(ret, list)):
ret[0] = commandecho + ret[0]
embed = discord.Embed(title = ret[0],
description = "```" + str(ret[1]) + "```",
color = colour)
embed.set_footer(text = "by Yidingbai :)")
await message.channel.send(embed = embed)
else:
return None
#%% Execution
if(os.path.exists(paramsfile)):
with open(paramsfile) as r: # parameters are stored as json. load it on run
params = json.load(r)
else:
params = {
'cap':150, # number of points to guarantee a max tier payout
'tcap':10, # maximum tier attainable by scoring points only
'thardcap':10, # maximum tier attainable after accounting for offsets
'Old Tree\'s Tear':0,
'Elder Tree Plywood':0,
'Palm Plywood':0,
'Standardized Timber Square':0,
'Black Stone Powder':0,
'Earth\'s Tear':0,
'Steel':0,
'Vanadium Ingot':0,
'Log':0,
'Bronze Ingot':0,
'Titanium Ingot':0,
'Black Stone (Weapon)':0,
'Black Stone (Armor)':0,
'Powder of Flame':0,
'Fire Horn':0,
'Coal':0
}
with open(paramsfile, 'w') as f:
json.dump(params, f, indent = 4)
if(not os.path.exists(file)): # make a new sheet if it doesn't exist
reset()
if(not os.path.exists(drill_file)): # make a new drill sheet if it doesn't exist
drill_reset()
client.run('')