Skip to content

Commit

Permalink
refactor(experience): move xp logic to GuildUser object (#64)
Browse files Browse the repository at this point in the history
* feat: add chargen enums

* refactor(experience): move add/spend/fetch experience to `GuildUser` object
  • Loading branch information
natelandau committed Sep 27, 2023
1 parent 00a3503 commit 3a1dd79
Show file tree
Hide file tree
Showing 8 changed files with 391 additions and 76 deletions.
2 changes: 1 addition & 1 deletion src/valentina/cogs/characters.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ async def list_characters(
text += f"**{character.name}**\n"
text += "```\n"
text += f"Class: {character.char_class.name:<20} Created On: {character.created.split(' ')[0]}\n"
text += f"Alive: {alive:<20} Active: {character.is_active}\n"
text += f"Alive: {alive:<20} Active: {bool(character.is_active)}\n"
text += f"Owner: {character.owned_by.data['display_name']:<20}\n"
text += "```\n"

Expand Down
91 changes: 25 additions & 66 deletions src/valentina/cogs/experience.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,32 +64,21 @@ async def xp_add(
)
return

campaign = self.bot.campaign_svc.fetch_active(ctx).id

campaign_xp = user.data.get(f"{campaign}_experience", 0)
campaign_total_xp = user.data.get(f"{campaign}_total_experience", 0)
lifetime_xp = user.data.get("lifetime_experience", 0)

new_xp = campaign_xp + amount
new_total_xp = campaign_total_xp + amount
new_lifetime_xp = lifetime_xp + amount
campaign = self.bot.campaign_svc.fetch_active(ctx)

title = f"Add `{amount}` xp to `{user.data['display_name']}`"
is_confirmed, confirmation_response_msg = await confirm_action(ctx, title, hidden=hidden)

description = "View experience with `/user_info`"
is_confirmed, confirmation_response_msg = await confirm_action(
ctx, title, description=description, hidden=hidden
)
if not is_confirmed:
return

await self.bot.user_svc.update_or_add(
ctx,
user=user,
data={
f"{campaign}_experience": new_xp,
f"{campaign}_total_experience": new_total_xp,
"lifetime_experience": new_lifetime_xp,
},
)
# Make the database updates
user.add_experience(campaign.id, amount)
self.bot.user_svc.purge_cache(ctx)

# Send the confirmation message
await self.bot.guild_svc.send_to_audit_log(ctx, title)
await confirmation_response_msg

Expand All @@ -110,7 +99,7 @@ async def cp_add(
default=False,
),
) -> None:
"""Add experience to a user."""
"""Add cool points to a user."""
if not user:
user = await self.bot.user_svc.fetch_user(ctx)
else:
Expand All @@ -126,40 +115,24 @@ async def cp_add(
)
return

campaign = self.bot.campaign_svc.fetch_active(ctx).id

campaign_xp = user.data.get(f"{campaign}_experience", 0)
campaign_total_xp = user.data.get(f"{campaign}_total_experience", 0)
campaign_total_cp = user.data.get(f"{campaign}_total_cool_points", 0)
lifetime_xp = user.data.get("lifetime_experience", 0)
lifetime_cp = user.data.get("lifetime_cool_points", 0)

xp_amount = amount * COOL_POINT_VALUE
new_xp = campaign_xp + xp_amount
new_total_xp = campaign_total_xp + xp_amount
new_lifetime_xp = lifetime_xp + xp_amount
new_lifetime_cp = lifetime_cp + amount
campaign = self.bot.campaign_svc.fetch_active(ctx)

title = (
f"Add `{amount}` cool {p.plural_noun('point', amount)} to `{user.data['display_name']}`"
)
is_confirmed, confirmation_response_msg = await confirm_action(ctx, title, hidden=hidden)

description = "View cool points with `/user_info`"
is_confirmed, confirmation_response_msg = await confirm_action(
ctx, title, description=description, hidden=hidden
)
if not is_confirmed:
return

await self.bot.user_svc.update_or_add(
ctx,
user=user,
data={
f"{campaign}_experience": new_xp,
f"{campaign}_total_experience": new_total_xp,
f"{campaign}_total_cool_points": campaign_total_cp + amount,
"lifetime_cool_points": new_lifetime_cp,
"lifetime_experience": new_lifetime_xp,
},
)
# Make the database updates
user.add_cool_points(campaign.id, amount)
user.add_experience(campaign.id, amount * COOL_POINT_VALUE)
self.bot.user_svc.purge_cache(ctx)

# Send the confirmation message
await self.bot.guild_svc.send_to_audit_log(ctx, title)
await confirmation_response_msg

Expand All @@ -186,7 +159,7 @@ async def xp_spend(
),
) -> None:
"""Spend experience points."""
campaign = self.bot.campaign_svc.fetch_active(ctx).id
campaign = self.bot.campaign_svc.fetch_active(ctx)
old_trait_value = character.get_trait_value(trait)
category = trait.category.name

Expand Down Expand Up @@ -214,34 +187,20 @@ async def xp_spend(
)
return

current_xp = character.owned_by.data.get(f"{campaign}_experience", 0)
remaining_xp = current_xp - upgrade_cost
new_trait_value = old_trait_value + 1
new_experience = current_xp - upgrade_cost

if remaining_xp < 0:
await present_embed(
ctx,
title="Error: Not enough XP",
description=f"**{trait.name}** upgrade cost is `{upgrade_cost}` xp. You only have `{current_xp}` xp.",
level="error",
ephemeral=True,
)
return

title = f"Upgrade `{trait.name}` from `{old_trait_value}` {p.plural_noun('dot', old_trait_value)} to `{new_trait_value}` {p.plural_noun('dot', new_trait_value)} for `{upgrade_cost}` xp"
is_confirmed, confirmation_response_msg = await confirm_action(ctx, title, hidden=hidden)
if not is_confirmed:
return

# Make the database updates
user = character.owned_by
user.spend_experience(campaign.id, upgrade_cost)
character.set_trait_value(trait, new_trait_value)
await self.bot.user_svc.update_or_add(
ctx,
user=character.owned_by,
data={f"{campaign}_experience": new_experience},
)
self.bot.user_svc.purge_cache(ctx)

# Send the confirmation message
await self.bot.guild_svc.send_to_audit_log(ctx, title)
await confirmation_response_msg

Expand Down
19 changes: 10 additions & 9 deletions src/valentina/cogs/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ async def user_info(
"""View information about a user."""
target = user or ctx.author
db_user = await self.bot.user_svc.fetch_user(ctx=ctx, user=target)

campaign = self.bot.campaign_svc.fetch_active(ctx)
# Variables for embed
num_characters = (
Character.select()
Expand All @@ -167,12 +167,13 @@ async def user_info(
creation_date = ((target.id >> 22) + 1420070400000) // 1000
roles = ", ".join(r.mention for r in target.roles[::-1][:-1]) or "_Member has no roles_"
roll_stats = Statistics(ctx, user=target)
lifetime_xp = db_user.data.get("lifetime_experience", 0)
lifetime_cp = db_user.data.get("lifetime_cool_points", 0)
campaign = self.bot.campaign_svc.fetch_active(ctx)
campaign_xp = db_user.data.get(f"{campaign.id}_experience", 0)
campaign_total_xp = db_user.data.get(f"{campaign.id}_total_experience", 0)
campaign_cp = db_user.data.get(f"{campaign.id}_total_cool_points", 0)
(
campaign_xp,
campaign_total_xp,
lifetime_xp,
campaign_cp,
lifetime_cp,
) = db_user.fetch_experience(campaign.id)

# Build Embed
description = (
Expand All @@ -181,11 +182,11 @@ async def user_info(
f"**Account Created :** <t:{creation_date}:R> on <t:{creation_date}:D>",
f"**Joined Server{SPACER * 7}:** <t:{int(target.joined_at.timestamp())}:R> on <t:{int(target.joined_at.timestamp())}:D>",
f"**Roles{SPACER * 24}:** {roles}",
"### __Campaign Information__",
f"### __{campaign.name}__ (Active campaign)",
f"Available Experience{SPACER * 2}: `{campaign_xp}`",
f"Total Experience{SPACER * 10}: `{campaign_total_xp}`",
f"Cool Points{SPACER * 20}: `{campaign_cp}`",
"### __Experience Information__",
"### __Lifetime Experience__",
f"Lifetime Experience{SPACER * 3}: `{lifetime_xp}`",
f"Lifetime Cool Points{SPACER * 2}: `{lifetime_cp}`",
"### __Gameplay Information__",
Expand Down
44 changes: 44 additions & 0 deletions src/valentina/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,50 @@ class XPMultiplier(Enum):
### DISCORD SETTINGS ###


class CharGenClass(Enum):
"""Enum for RNG character generation classes."""

HUMAN = range(0, 60)
VAMPIRE = range(61, 66)
WEREWOLF = range(67, 72)
MAGE = range(73, 78)
GHOUL = range(79, 84)
CHANGELING = range(85, 90)
CHANGELING_BREED = range(91, 96)
OTHER = range(97, 100)


class CharGenHumans(Enum):
"""Enum for RNG character generation of humans."""

CIVILIAN = range(0, 40)
HUNTER = range(41, 50)
CHANGELING = range(51, 53)
GHOUL = range(54, 60)
WATCHER = range(61, 70)
HUNTER_CLASS = range(71, 80)
SORCERER = range(81, 90)
NUMINOUS = range(91, 100)


class CharGenConcept(Enum):
"""Enum for RNG character generation of concepts."""

BERSERKER = range(0, 8)
PERFORMER = range(9, 16)
HEALER = range(17, 24)
SHAMAN = range(25, 32)
SOLDIER = range(33, 40)
ASCETIC = range(41, 48)
CRUSADER = range(49, 56)
URBAN_TRACKER = range(57, 64)
UNDER_WORLDER = range(65, 72)
SCIENTIST = range(73, 80)
TRADESMAN = range(81, 88)
BUSINESSMAN = range(89, 96)
CHOOSE = range(97, 100)


# CHANNEL_PERMISSIONS: Dictionary containing a mapping of channel permissions.
# Format:
# default role permission,
Expand Down
97 changes: 97 additions & 0 deletions src/valentina/models/db_tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,103 @@ def __str__(self) -> str:
"""Return the string representation of the model."""
return f"[{self.data['id']}] {self.data['display_name']}"

def fetch_experience(self, campaign_id: int) -> tuple[int, int, int, int, int]:
"""Fetch the experience for a user for a specific campaign.
Args:
campaign_id (int): The campaign ID to fetch experience for.
Returns:
tuple[int, int, int, int, int]: The campaign experience, campaign total experience, lifetime experience, campaign total cool points, and lifetime cool points.
"""
campaign_xp = self.data.get(f"{campaign_id}_experience", 0)
campaign_total_xp = self.data.get(f"{campaign_id}_total_experience", 0)
campaign_total_cp = self.data.get(f"{campaign_id}_total_cool_points", 0)
lifetime_xp = self.data.get("lifetime_experience", 0)
lifetime_cp = self.data.get("lifetime_cool_points", 0)

return campaign_xp, campaign_total_xp, lifetime_xp, campaign_total_cp, lifetime_cp

def add_experience(self, campaign_id: int, amount: int) -> tuple[int, int, int]:
"""Set the experience for a user for a specific campaign.
Args:
campaign_id (int): The campaign ID to set experience for.
amount (int): The amount of experience to set.
Returns:
tuple[int, int, int]: The campaign experience, campaign total experience, and lifetime experience.
"""
(
campaign_xp,
campaign_total_xp,
lifetime_xp,
_,
_,
) = self.fetch_experience(campaign_id)

new_xp = self.data[f"{campaign_id}_experience"] = campaign_xp + amount
new_total = self.data[f"{campaign_id}_total_experience"] = campaign_total_xp + amount
new_lifetime_total = self.data["lifetime_experience"] = lifetime_xp + amount

self.save()

return new_xp, new_total, new_lifetime_total

def add_cool_points(self, campaign_id: int, amount: int) -> tuple[int, int]:
"""Set the cool points for a user for a specific campaign.
Args:
campaign_id (int): The campaign ID to set cool points for.
amount (int): The amount of cool points to set.
Returns:
tuple[int, int]: The campaign total cool points and lifetime cool points.
"""
(
_,
_,
_,
campaign_total_cp,
lifetime_cp,
) = self.fetch_experience(campaign_id)

new_total = self.data[f"{campaign_id}_total_cool_points"] = campaign_total_cp + amount
new_lifetime = self.data["lifetime_cool_points"] = lifetime_cp + amount

self.save()

return new_total, new_lifetime

def spend_experience(self, campaign_id: int, amount: int) -> int:
"""Spend experience for a user for a specific campaign.
Args:
campaign_id (int): The campaign ID to spend experience for.
amount (int): The amount of experience to spend.
Returns:
int: The new campaign experience.
"""
(
campaign_xp,
_,
_,
_,
_,
) = self.fetch_experience(campaign_id)

new_xp = self.data[f"{campaign_id}_experience"] = campaign_xp - amount

if new_xp < 0:
raise errors.NotEnoughExperienceError(
f"Can not spend {amount} xp with only {campaign_xp} available"
)

self.save()

return new_xp

def set_default_data_values(self) -> GuildUser:
"""Verify that the GuildUser's JSONField defaults are set. If any keys are missing, they are added to the data column with default values.
Expand Down
1 change: 1 addition & 0 deletions src/valentina/models/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def _handle_known_exceptions( # noqa: C901
| errors.ValidationError
| errors.NoMatchingItemsError
| errors.NoActiveCharacterError
| errors.NotEnoughExperienceError
| errors.URLNotAvailableError
| errors.ServiceDisabledError
| errors.S3ObjectExistsError,
Expand Down
18 changes: 18 additions & 0 deletions src/valentina/utils/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,24 @@ def __init__(
super().__init__(msg, *args, **kwargs)


class NotEnoughExperienceError(DiscordException):
"""Raised when a user does not have enough experience to perform an action."""

def __init__(
self,
msg: str | None = None,
e: Exception | None = None,
*args: str | int,
**kwargs: int | str | bool,
):
if not msg:
msg = "Not enough experience to perform this action."
if e:
msg += f"\nRaised from: {e.__class__.__name__}: {e}"

super().__init__(msg, *args, **kwargs)


class S3ObjectExistsError(Exception):
"""Raised when an S3 object already exists."""

Expand Down
Loading

0 comments on commit 3a1dd79

Please sign in to comment.