forked from sass/tipibot
Change exp leveling system
This commit is contained in:
@@ -9,7 +9,7 @@ from discord import app_commands
|
|||||||
|
|
||||||
from core import sheets
|
from core import sheets
|
||||||
import strings as S
|
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):
|
class BirthdayPages(discord.ui.View):
|
||||||
@@ -44,7 +44,7 @@ def _build_birthday_pages(
|
|||||||
Returns (pages, start_index) where start_index is the current month.
|
Returns (pages, start_index) where start_index is the current month.
|
||||||
"""
|
"""
|
||||||
rows = sheets.get_cache()
|
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)}
|
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"]:
|
for fmt in ["%d/%m/%Y", "%Y-%m-%d"]:
|
||||||
try:
|
try:
|
||||||
bday = datetime.datetime.strptime(bday_str, fmt).date()
|
bday = datetime.datetime.strptime(bday_str, fmt).date()
|
||||||
if 1920 <= bday.year <= datetime.date.today().year:
|
if 1920 <= bday.year <= today_local().year:
|
||||||
today = datetime.date.today()
|
today = today_local()
|
||||||
age = today.year - bday.year - ((today.month, today.day) < (bday.month, bday.day))
|
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)
|
embed.add_field(name=S.MEMBER_UI["age_field"], value=S.MEMBER_UI["age_val"].format(age=age), inline=True)
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -317,16 +317,18 @@ LEVEL_ROLES: list[tuple[int, str]] = [
|
|||||||
|
|
||||||
|
|
||||||
def get_level(exp: int) -> int:
|
def get_level(exp: int) -> int:
|
||||||
"""Level = max(1, floor(sqrt(exp/6))).
|
"""Level = max(1, floor(sqrt(exp/10))).
|
||||||
Level 5 @ 150 EXP, 10 @ 600, 20 @ 2400, 30 @ 5400."""
|
Level 5 @ 250 EXP, 10 @ 1000, 20 @ 4000, 30 @ 9000."""
|
||||||
return max(1, int(math.sqrt(max(0, exp) / 6)))
|
return max(1, int(math.sqrt(max(0, exp) / 10)))
|
||||||
|
|
||||||
|
|
||||||
def exp_for_level(level: int) -> int:
|
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:
|
if level <= 1:
|
||||||
return 0
|
return 0
|
||||||
return level * level * 6
|
return 10 * level * level
|
||||||
|
|
||||||
|
|
||||||
def level_role_name(level: int) -> str:
|
def level_role_name(level: int) -> str:
|
||||||
|
|||||||
@@ -2,9 +2,11 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import calendar
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, date
|
from datetime import datetime, date
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
@@ -13,6 +15,20 @@ from . import sheets
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
_PLACEHOLDER = {"-", "x", "n/a", "none", "ei"}
|
_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:
|
def _is_placeholder(val: str) -> bool:
|
||||||
@@ -72,7 +88,7 @@ def _parse_birthday(raw: str) -> date | None:
|
|||||||
raw = str(raw).strip()
|
raw = str(raw).strip()
|
||||||
if _is_placeholder(raw):
|
if _is_placeholder(raw):
|
||||||
return None
|
return None
|
||||||
today = date.today()
|
today = today_local()
|
||||||
for fmt, has_year in [("%d/%m/%Y", True), ("%Y-%m-%d", True), ("%m-%d", False)]:
|
for fmt, has_year in [("%d/%m/%Y", True), ("%Y-%m-%d", True), ("%m-%d", False)]:
|
||||||
try:
|
try:
|
||||||
parsed = datetime.strptime(raw, fmt).date()
|
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:
|
if bday is None:
|
||||||
return False
|
return False
|
||||||
window = window_days or config.BIRTHDAY_WINDOW_DAYS
|
window = window_days or config.BIRTHDAY_WINDOW_DAYS
|
||||||
today = date.today()
|
today = today_local()
|
||||||
this_year_bday = bday.replace(year=today.year)
|
this_year_bday = _shift_year_safe(bday, today.year)
|
||||||
if this_year_bday < today:
|
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
|
delta = (this_year_bday - today).days
|
||||||
return 0 <= delta <= window
|
return 0 <= delta <= window
|
||||||
|
|
||||||
|
|
||||||
def is_birthday_today(birthday_str: str) -> bool:
|
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)
|
bday = _parse_birthday(birthday_str)
|
||||||
if bday is None:
|
if bday is None:
|
||||||
return False
|
return False
|
||||||
today = date.today()
|
today = today_local()
|
||||||
return bday.month == today.month and bday.day == today.day
|
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(
|
async def sync_member(
|
||||||
|
|||||||
Reference in New Issue
Block a user