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 ") 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)