bunbot/plugins/bungame.py

197 lines
7.3 KiB
Python

import plugin, tasks, time, random, math
from bot import IRCLine
BOT=None
def respond(event,msg,generic=False):
if not BOT: return None
target = event.target
prefix = ""
if target.startswith("#"):
prefix += event.hostmask.nick
prefix += ": "
else:
target = event.hostmask.nick
if generic: prefix=""
BOT.socket.send(IRCLine("PRIVMSG",target,":"+prefix+msg).line)
pool = tasks.TaskPool()
bungame_data = plugin.DictData("bungame.json")
if "bun_active" not in bungame_data: bungame_data["bun_active"]=False
if "bun_time" not in bungame_data: bungame_data["bun_time"]=time.time()
if "buns" not in bungame_data: bungame_data["buns"]=dict()
if "score_cache" not in bungame_data: bungame_data["score_cache"]=dict()
if "association" not in bungame_data: bungame_data["association"]=dict()
def check_bun_active(channel):
"""Checks if a bun is active in channel.
Right now, this is a single-channel bot, but if I decide to make it a multichannel bot, this will make generalizing a lot easier."""
return bungame_data["bun_active"]
def activate_bun(channel):
"""Activates the bun in channel.
Again, just for generalizing if needed later."""
bungame_data["bun_time"]=time.time()
bungame_data["bun_active"]=True
def deactivate_bun(channel):
"""Deactivates the bun in channel.
Again, just for generalizing if needed later."""
bungame_data["bun_time"]=time.time()
bungame_data["bun_active"]=False
# Base is such that b^(1 hour in seconds) = 3.
# 2020-11-16 - A 2 hour bun awards 6,322,008.86 points, base is lowered from b^(1 hour in seconds) = 1000.
# 2020-11-17 - A 4:46:01.66 bun awards 3 billion and a 6:08:09.27 awards over a trillion. Base is lowered from 100.
# 2020-12-03 - After some fun with really long buns, I decide enough is enough and lower the base even more. Base lowered from 10.
# 2020-12-14 - Late Monday night I decide to lower the base slightly, from 5.
# 2021-06-04 - I decide to lower the base from 3, after almost 6 months of just leaving it as-is.
# This is probably too much and too easily abused but hell we'll give it a shot.
BASE = 2**(1/(60*60))
def bun_score(time_delta):
"""Generates the score for a bun."""
return BASE**time_delta
def get_bun_time(channel):
"""Returns the bun time of the channel."""
return bungame_data["bun_time"]
def get_bun_score(channel):
"""Gives the current score of the bun in channel."""
delta = time.time()-get_bun_time(channel)
return bun_score(delta), delta
def redo_score_cache():
score_cache = dict()
for account in bungame_data["buns"].keys():
buns = bungame_data["buns"][account]
score = 0
for bun in buns:
# time delta is stored up to 4 digits precision
# really you shouldn't need more than that
score+=bun_score(bun)
score_cache[account]=score
bungame_data["score_cache"]=score_cache
def on_privmsg(event):
# don't trigger on private messages
if not event.target.startswith("#"): return
# do association first, in case an error occurs elsewhere
if "account" in event.tags:
bungame_data["association"][event.hostmask.nick]=event.tags["account"]
bungame_data.save()
if not check_bun_active(event.target):
if random.random()>(3/4):
activate_bun(event.target)
respond(event,"A bun hops into the room. Hop, hop, hop, little bun!",True)
def on_befriend(event):
if not event.target.startswith("#"): return
if "account" not in event.tags:
respond(event,"You need a NickServ account to participate in the bun game! (/msg NickServ help register)")
return
if not check_bun_active(event.target):
respond(event,"You missed the bun!")
return
account = event.tags["account"]
score, final_delta = get_bun_score(event.target)
deactivate_bun(event.target)
first_bun = False
# add the bun to their account and regenerate the score cache
try:
bungame_data["buns"][account].append(final_delta)
except KeyError:
bungame_data["buns"][account]=[final_delta]
bungame_data.save()
redo_score_cache()
# now tell them about it
delta_r = round(final_delta,2)
score_r = round(score,2)
if first_bun:
respond(event,f"Congratulations on your first bun! This bun has waited {delta_r:,} second(s), and is therefore worth {score_r:.2e} point(s)!")
else:
respond(event,f"This bun has waited {delta_r:,} second(s), and is therefore worth {score_r:.2e} point(s)!")
def on_peek(event):
if not event.target.startswith("#"): return
if not check_bun_active(event.target):
respond(event,"There is no bun active in this channel!")
return
score, delta = get_bun_score(event.target)
delta_r=round(delta,2)
score_r=round(score,2)
respond(event,f"If you were to befriend the bun right now, it would have waited {delta_r:,} second(s), and would therefore be worth {score_r:.2e} point(s).")
average = lambda l: sum(l)/len(l)
def on_stats(event):
if not event.target.startswith("#"): return
if "account" not in event.tags:
respond(event,"You need a NickServ account to participate. (/msg NickServ help register)")
return
account = event.tags["account"]
buns = bungame_data["buns"].get(account)
if not buns:
respond(event,"You haven't befriended any buns!")
return
bunc = len(buns)
avg_bunt = average(buns) # *av*era*g*e *bun* *t*ime
stat_out = "You have befriended {:,} bun{}. Your average befriend time is {:,.02f}, and your current score is {:.2e}.".format(bunc,"s" if bunc!=1 else "",avg_bunt,bungame_data["score_cache"].get(account,0))
respond(event,stat_out)
def on_top10(event):
mode = "score"
if event.parts and event.parts[0] in "score count time".split():
mode = event.parts[0]
accounts = list(bungame_data["buns"].keys())
if mode == "score":
accounts.sort(key=lambda k: bungame_data["score_cache"].get(k,0),reverse=True)
accounts = [(bungame_data["association"].get(account,account),bungame_data["score_cache"][account]) for account in accounts[:10]]
elif mode == "count":
accounts.sort(key=lambda k: len(bungame_data["buns"].get(k,[])),reverse=True)
accounts = [(bungame_data["association"].get(account,account),len(bungame_data["buns"].get(account,[]))) for account in accounts[:10]]
elif mode == "time":
accounts.sort(key=lambda k: average(bungame_data["buns"].get(k,[0])),reverse=True)
accounts = [(bungame_data["association"].get(account,account),average(bungame_data["buns"].get(account,[0]))) for account in accounts[:10]]
out = f"Top 10 in {mode}: "
for account in accounts:
if mode == "count":
out += "{act[0]} ({act[1]:n})".format(act=account)
else:
out += "{act[0]} ({act[1]:.2e})".format(act=account)
out+=", "
out=out[:-2]
respond(event,out)
def admin_redocache(event):
redo_score_cache()
respond(event,"Score cache redone!")
def admin_merge(event):
try:
target, from_ = event.parts
except:
respond(event,"Syntax: admin merge <target> <from>")
return
bungame_data["buns"][target].extend(bungame_data["buns"][from_])
bungame_data.save()
del bungame_data["buns"][from_]
bungame_data.save()
redo_score_cache()
respond(event,"Should be merged now!")
def register(bot):
global BOT
BOT=bot
bot.event_manager.on("privmsg",on_privmsg)
bot.event_manager.on("command_befriend",on_befriend)
bot.event_manager.on("command_bef",on_befriend)
bot.event_manager.on("command_hug",on_befriend)
bot.event_manager.on("command_peek",on_peek)
bot.event_manager.on("command_stats",on_stats)
bot.event_manager.on("command_top10",on_top10)
bot.event_manager.on("admin_redocache",admin_redocache)
bot.event_manager.on("admin_merge",admin_merge)