Permalink
Browse files

Modified the token-system.

Tokens now last forever, but you can have at most 15 at once (by default).

You can now choose to give out points instead of tokens for completed
quests.
  • Loading branch information...
1 parent 6d94309 commit eed0584a91662192f1d8f8f96e7c9d5f5fb6304b @pajlada committed May 26, 2016
View
@@ -1,6 +1,10 @@
# Change Log
## [Unreleased]
+### Changed
+- Quests can now reward points and any variable amount of tokens.
+- Tokens no longer expire after 3 streams. You instead have a maximum amount of tokens.
+
### Added
- New API endpoint: /api/v1/pleblist/top - lists the top pleblist songs
- Reasons to most timeouts
@@ -392,7 +392,7 @@ def run(self, bot, source, message, event={}, args={}, whisper=False):
if self.tokens_cost > 0 and not source.can_afford_with_tokens(self.tokens_cost):
if self.notify_on_error:
- bot.whisper(source.username, 'You do not have the required {} tokens to execute this command. (You have {} tokens)'.format(self.tokens_cost, source.get_tokens()))
+ bot.whisper(source.username, 'You do not have the required {} tokens to execute this command. (You have {} tokens)'.format(self.tokens_cost, source.tokens))
# User does not have enough tokens to use the command
return False
@@ -414,11 +414,6 @@ def run_action(self, bot, source, message, event, args):
if self.data is not None:
self.data.num_uses += 1
self.data.last_date_used = datetime.datetime.now()
- if self.tokens_cost > 0:
- if not source.spend_tokens(self.tokens_cost):
- # The user does not have enough tokens to spend!
- log.warning('{0} used tokens he does not have.'.format(source.username))
- return False
# TODO: Will this be an issue?
self.last_run = cur_time
View
@@ -12,7 +12,6 @@
from pajbot.exc import FailedCommand
from pajbot.managers.db import Base
from pajbot.managers.db import DBManager
-from pajbot.managers.handler import HandlerManager
from pajbot.managers.redis import RedisManager
from pajbot.managers.schedule import ScheduleManager
from pajbot.managers.time import TimeManager
@@ -247,6 +246,7 @@ def duel_stats(self, value):
class UserRedis:
SS_KEYS = [
'num_lines',
+ 'tokens',
]
HASH_KEYS = [
'last_seen',
@@ -261,6 +261,7 @@ class UserRedis:
SS_DEFAULTS = {
'num_lines': 0,
+ 'tokens': 0,
}
HASH_DEFAULTS = {
'last_seen': None,
@@ -353,6 +354,26 @@ def num_lines(self, value):
self.redis.zrem('{streamer}:users:num_lines'.format(streamer=StreamHelper.get_streamer()), self.username)
@property
+ def tokens(self):
+ if self.save_to_redis:
+ self.redis_load()
+ return self.values['tokens']
+ else:
+ return self.values.get('tokens', 0)
+
+ @tokens.setter
+ def tokens(self, value):
+ # Set cached value
+ self.values['tokens'] = value
+
+ if self.save_to_redis:
+ # Set redis value
+ if value != 0:
+ self.redis.zadd('{streamer}:users:tokens'.format(streamer=StreamHelper.get_streamer()), self.username, value)
+ else:
+ self.redis.zrem('{streamer}:users:tokens'.format(streamer=StreamHelper.get_streamer()), self.username)
+
+ @property
def num_lines_rank(self):
key = '{streamer}:users:num_lines'.format(streamer=StreamHelper.get_streamer())
rank = self.redis.zrevrank(key, self.username)
@@ -607,28 +628,35 @@ def timeout(self, timeout_length, warning_module=None, use_warnings=True):
def spend_currency_context(self, points_to_spend, tokens_to_spend):
# TODO: After the token storage rewrite, use tokens here too
try:
- self.spend_points(points_to_spend)
+ self._spend_points(points_to_spend)
+ self._spend_tokens(tokens_to_spend)
yield
except FailedCommand:
log.debug('Returning {} points to {}'.format(points_to_spend, self.username_raw))
self.points += points_to_spend
+ self.tokens += tokens_to_spend
except:
# An error occured, return the users points!
log.exception('XXXX')
log.debug('Returning {} points to {}'.format(points_to_spend, self.username_raw))
self.points += points_to_spend
- def spend(self, points_to_spend):
- # XXX: Remove all usages of spend() and use spend_points() instead
- return self.spend_points(points_to_spend)
-
- def spend_points(self, points_to_spend):
+ def _spend_points(self, points_to_spend):
+ """ Returns true if points were spent, otherwise return False """
if points_to_spend <= self.points:
self.points -= points_to_spend
return True
return False
+ def _spend_tokens(self, tokens_to_spend):
+ """ Returns true if tokens were spent, otherwise return False """
+ if tokens_to_spend <= self.tokens:
+ self.tokens -= tokens_to_spend
+ return True
+
+ return False
+
def remove_debt(self, debt):
try:
self.debts.remove(debt)
@@ -651,80 +679,5 @@ def can_afford(self, points_to_spend):
def __eq__(self, other):
return self.username == other.username
- # TODO: rewrite this token code shit
def can_afford_with_tokens(self, cost):
- num_tokens = self.get_tokens()
- return num_tokens >= cost
-
- def spend_tokens(self, tokens_to_spend, redis=None):
- if redis is None:
- redis = RedisManager.get()
-
- user_token_key = '{streamer}:{username}:tokens'.format(
- streamer=StreamHelper.get_streamer(), username=self.username)
-
- token_dict = redis.hgetall(user_token_key)
-
- for stream_id in token_dict:
- try:
- num_tokens = int(token_dict[stream_id])
- except (TypeError, ValueError):
- continue
-
- if num_tokens == 0:
- continue
-
- decrease_by = min(tokens_to_spend, num_tokens)
- tokens_to_spend -= decrease_by
- num_tokens -= decrease_by
-
- redis.hset(user_token_key, stream_id, num_tokens)
-
- if tokens_to_spend == 0:
- return True
-
- return False
-
- def award_tokens(self, tokens, redis=None, force=False):
- """ Returns True if tokens were awarded properly.
- Returns False if not.
- Tokens can only be rewarded once per stream ID.
- """
-
- streamer = StreamHelper.get_streamer()
- stream_id = StreamHelper.get_current_stream_id()
-
- if stream_id is False:
- return False
-
- if redis is None:
- redis = RedisManager.get()
-
- key = '{streamer}:{username}:tokens'.format(
- streamer=streamer, username=self.username)
-
- if force:
- res = True
- redis.hset(key, stream_id, tokens)
- else:
- res = True if redis.hsetnx(key, stream_id, tokens) == 1 else False
- if res is True:
- HandlerManager.trigger('on_user_gain_tokens', self, tokens)
- return res
-
- def get_tokens(self, redis=None):
- streamer = StreamHelper.get_streamer()
- if redis is None:
- redis = RedisManager.get()
-
- tokens = redis.hgetall('{streamer}:{username}:tokens'.format(
- streamer=streamer, username=self.username))
-
- num_tokens = 0
- for token_value in tokens.values():
- try:
- num_tokens += int(token_value)
- except (TypeError, ValueError):
- log.warn('Invalid value for tokens, user {}'.format(self.username))
-
- return num_tokens
+ return self.tokens >= cost
@@ -91,7 +91,7 @@ def debug_user(self, **options):
pass
data['ignored'] = user.ignored
data['banned'] = user.banned
- data['tokens'] = user.get_tokens()
+ data['tokens'] = user.tokens
bot.whisper(source.username, ', '.join(['%s=%s' % (key, value) for (key, value) in data.items()]))
else:
@@ -43,6 +43,33 @@ class QuestModule(BaseModule):
'me',
'reply',
]),
+ ModuleSetting(
+ key='reward_type',
+ label='Reward type',
+ type='options',
+ required=True,
+ default='tokens',
+ options=[
+ 'tokens',
+ 'points',
+ ]),
+ ModuleSetting(
+ key='reward_amount',
+ label='Reward amount',
+ type='number',
+ required=True,
+ default=5,
+ ),
+ ModuleSetting(
+ key='max_tokens',
+ label='Max tokens',
+ type='number',
+ required=True,
+ default=15,
+ constraints={
+ 'min_value': 1,
+ 'max_value': 5000,
+ }),
]
def __init__(self):
@@ -92,7 +119,7 @@ def get_user_tokens(self, **options):
event = options['event']
source = options['source']
- message_tokens = '{0}, you have {1} tokens.'.format(source.username_raw, source.get_tokens())
+ message_tokens = '{0}, you have {1} tokens.'.format(source.username_raw, source.tokens)
if self.settings['action_tokens'] == 'say':
bot.say(message_tokens)
@@ -135,6 +162,7 @@ def on_stream_start(self):
return False
self.current_quest = random.choice(available_quests)
+ self.current_quest.quest_module = self
self.current_quest.start_quest()
redis = RedisManager.get()
@@ -164,21 +192,6 @@ def on_stream_stop(self):
# No last stream ID found. why?
return False
- # XXX: Should we use a pipeline for any of this?
- # Go through user tokens and remove any from more than 2 streams ago
- for key in redis.keys('{streamer}:*:tokens'.format(streamer=StreamHelper.get_streamer())):
- all_tokens = redis.hgetall(key)
- for stream_id_str in all_tokens:
- try:
- stream_id = int(stream_id_str)
- except (TypeError, ValueError):
- log.error('Invalid stream id in tokens by {}'.format(key))
- continue
-
- if last_stream_id - stream_id > 1:
- log.info('Removing tokens for stream {}'.format(stream_id))
- redis.hdel(key, stream_id)
-
def on_loaded(self):
if self.bot:
self.current_quest_key = '{streamer}:current_quest'.format(streamer=self.bot.streamer)
@@ -1,5 +1,7 @@
+import json
import logging
+import pajbot.managers
from pajbot.managers.redis import RedisManager
from pajbot.modules import BaseModule
from pajbot.streamhelper import StreamHelper
@@ -14,6 +16,44 @@ def __init__(self):
super().__init__()
self.progress = {}
self.progress_key = '{streamer}:current_quest_progress'.format(streamer=StreamHelper.get_streamer())
+ self.quest_finished_key = '{streamer}:quests:finished'.format(streamer=StreamHelper.get_streamer())
+
+ def finish_quest(self, redis, user):
+ stream_id = StreamHelper.get_current_stream_id()
+
+ # Load user's finished quest status
+ val = redis.hget(self.quest_finished_key, user.username)
+ if val:
+ quests_finished = json.loads(val)
+ else:
+ quests_finished = []
+
+ if stream_id in quests_finished:
+ # User has already completed this quest
+ return
+
+ # Mark the current stream ID has finished
+ quests_finished.append(stream_id)
+ redis.hset(self.quest_finished_key, user.username, json.dumps(quests_finished, separators=(',', ':')))
+
+ # Award the user appropriately
+ reward_type = self.quest_module.settings['reward_type']
+ reward_amount = self.quest_module.settings['reward_amount']
+ if reward_type == 'tokens':
+ user.tokens += reward_amount
+ else:
+ user.points += reward_amount
+
+ # Make sure the user doesn't have more tokens than allowed
+ if user.tokens > self.quest_module.settings['max_tokens']:
+ user.tokens = self.quest_module.settings['max_tokens']
+
+ # Notify the user that they've finished today's quest
+ message = 'You finished todays quest! You have been awarded with {} {}.'.format(reward_amount, reward_type)
+ pajbot.managers.handler.HandlerManager.trigger('send_whisper', user.username, message)
+
+ # XXX: this can safely be removed once points are moved to redis
+ user.save()
def start_quest(self):
""" This method is triggered by either the stream starting, or the bot loading up
@@ -32,8 +32,6 @@ class TypeEmoteQuestModule(BaseQuest):
}),
]
- REWARD = 5
-
def __init__(self):
super().__init__()
self.current_emote_key = '{streamer}:current_quest_emote'.format(streamer=StreamHelper.get_streamer())
@@ -56,7 +54,7 @@ def on_message(self, source, message, emotes, whisper, urls, event):
redis = RedisManager.get()
if user_progress == self.get_limit():
- source.award_tokens(self.REWARD, redis=redis)
+ self.finish_quest(redis, source)
self.set_user_progress(source.username, user_progress, redis=redis)
return
@@ -41,8 +41,6 @@ class TypeMeMessageQuestModule(BaseQuest):
}),
]
- REWARD = 5
-
def get_limit(self):
return self.settings['quest_limit']
@@ -62,7 +60,7 @@ def on_message(self, source, message, emotes, whisper, urls, event):
redis = RedisManager.get()
if user_progress == self.get_limit():
- source.award_tokens(self.REWARD, redis=redis)
+ self.finish_quest(redis, source)
self.set_user_progress(source.username, user_progress, redis=redis)
@@ -44,7 +44,6 @@ class WinDuelPointsQuestModule(BaseQuest):
]
LIMIT = 1
- REWARD = 5
def __init__(self):
super().__init__()
@@ -74,7 +73,7 @@ def on_duel_complete(self, winner, loser, points_won, points_bet):
if total_points_won >= self.points_required:
# Reward the user with some tokens
- winner.award_tokens(self.REWARD, redis=redis)
+ self.finish_quest(redis, winner)
# Save the users "points won" progress
self.set_user_progress(winner.username, total_points_won, redis=redis)
Oops, something went wrong.

0 comments on commit eed0584

Please sign in to comment.