From 8d7ac504ca058bf25f8b554ea9c9d901c89139b3 Mon Sep 17 00:00:00 2001 From: Rene Arumetsa Date: Wed, 13 May 2026 22:43:34 +0300 Subject: [PATCH] Change exp leveling system --- commands/dev_member_commands.py | 8 +++---- core/economy.py | 12 ++++++---- core/member_sync.py | 41 +++++++++++++++++++++++++++------ 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/commands/dev_member_commands.py b/commands/dev_member_commands.py index aea4521..dc13dbe 100644 --- a/commands/dev_member_commands.py +++ b/commands/dev_member_commands.py @@ -9,7 +9,7 @@ from discord import app_commands from core import sheets import strings as S -from core.member_sync import announce_birthday, sync_member +from core.member_sync import announce_birthday, sync_member, today_local class BirthdayPages(discord.ui.View): @@ -44,7 +44,7 @@ def _build_birthday_pages( Returns (pages, start_index) where start_index is the current month. """ rows = sheets.get_cache() - today = datetime.date.today() + today = today_local() by_month: dict[int, list[tuple[int, str, int | None]]] = {m: [] for m in range(1, 13)} @@ -298,8 +298,8 @@ def register_dev_member_commands( for fmt in ["%d/%m/%Y", "%Y-%m-%d"]: try: bday = datetime.datetime.strptime(bday_str, fmt).date() - if 1920 <= bday.year <= datetime.date.today().year: - today = datetime.date.today() + if 1920 <= bday.year <= today_local().year: + today = today_local() age = today.year - bday.year - ((today.month, today.day) < (bday.month, bday.day)) embed.add_field(name=S.MEMBER_UI["age_field"], value=S.MEMBER_UI["age_val"].format(age=age), inline=True) break diff --git a/core/economy.py b/core/economy.py index ecfba15..30d28ea 100644 --- a/core/economy.py +++ b/core/economy.py @@ -317,16 +317,18 @@ LEVEL_ROLES: list[tuple[int, str]] = [ def get_level(exp: int) -> int: - """Level = max(1, floor(sqrt(exp/6))). - Level 5 @ 150 EXP, 10 @ 600, 20 @ 2400, 30 @ 5400.""" - return max(1, int(math.sqrt(max(0, exp) / 6))) + """Level = max(1, floor(sqrt(exp/10))). + Level 5 @ 250 EXP, 10 @ 1000, 20 @ 4000, 30 @ 9000.""" + return max(1, int(math.sqrt(max(0, exp) / 10))) def exp_for_level(level: int) -> int: - """Minimum cumulative EXP to reach this level. level^2 * 6.""" + """Minimum cumulative EXP to reach this level. + Recurrence: exp_for_level(L) = L*20 - 10 + exp_for_level(L-1), base 0. + Closed form: 10*level^2.""" if level <= 1: return 0 - return level * level * 6 + return 10 * level * level def level_role_name(level: int) -> str: diff --git a/core/member_sync.py b/core/member_sync.py index 9e28070..ff3dd69 100644 --- a/core/member_sync.py +++ b/core/member_sync.py @@ -2,9 +2,11 @@ from __future__ import annotations +import calendar import logging from datetime import datetime, date from dataclasses import dataclass, field +from zoneinfo import ZoneInfo import discord @@ -13,6 +15,20 @@ from . import sheets log = logging.getLogger(__name__) _PLACEHOLDER = {"-", "x", "n/a", "none", "ei"} +_TZ = ZoneInfo("Europe/Tallinn") + + +def today_local() -> date: + """Today's date in Europe/Tallinn — the bot's operational timezone, independent of host TZ.""" + return datetime.now(_TZ).date() + + +def _shift_year_safe(d: date, year: int) -> date: + """Move `d` to `year`; Feb 29 falls back to Feb 28 when the target year is non-leap.""" + try: + return d.replace(year=year) + except ValueError: + return d.replace(year=year, day=28) def _is_placeholder(val: str) -> bool: @@ -72,7 +88,7 @@ def _parse_birthday(raw: str) -> date | None: raw = str(raw).strip() if _is_placeholder(raw): return None - today = date.today() + today = today_local() for fmt, has_year in [("%d/%m/%Y", True), ("%Y-%m-%d", True), ("%m-%d", False)]: try: parsed = datetime.strptime(raw, fmt).date() @@ -90,21 +106,32 @@ def _is_birthday_soon(birthday_str: str, window_days: int | None = None) -> bool if bday is None: return False window = window_days or config.BIRTHDAY_WINDOW_DAYS - today = date.today() - this_year_bday = bday.replace(year=today.year) + today = today_local() + this_year_bday = _shift_year_safe(bday, today.year) if this_year_bday < today: - this_year_bday = bday.replace(year=today.year + 1) + this_year_bday = _shift_year_safe(bday, today.year + 1) delta = (this_year_bday - today).days return 0 <= delta <= window def is_birthday_today(birthday_str: str) -> bool: - """Return True if today is the member's birthday (any supported date format).""" + """Return True if today is the member's birthday (any supported date format). + + Feb 29 babies are observed on Feb 28 in non-leap years. + """ bday = _parse_birthday(birthday_str) if bday is None: return False - today = date.today() - return bday.month == today.month and bday.day == today.day + today = today_local() + if bday.month == today.month and bday.day == today.day: + return True + if ( + bday.month == 2 and bday.day == 29 + and today.month == 2 and today.day == 28 + and not calendar.isleap(today.year) + ): + return True + return False async def sync_member(