VIP-система#
Плагин-пакет: VIP-статус из базы, бонусы на спавне и в уроне, команда выдачи, натив и форвард для Pawn-плагинов и переводы.
examples/plugins/vip/
├─ __init__.py Plugin и подключение модулей
├─ config.py cvar'ы
├─ models.py dataclass Vip
├─ repo.py хранилище SQLite
├─ state.py PlayerState(keep=True)
├─ perks.py загрузка VIP и бонусы
├─ commands.py /vip и amx_vip_add
├─ api.py натив и форвард для Pawn
└─ lang/ ru.toml, en.toml
"""VIP-система: бонусы на спавне и в уроне, натив и форвард для Pawn, хранение в SQLite."""
from amxpy import Plugin, events
from . import api, commands, perks, repo
from .config import cfg
plugin = Plugin("VIP", version="1.2.0", author="amxpy")
plugin.include(cfg, api.router, commands.router, perks.router)
@plugin.on(events.Init)
async def init(e: events.Init) -> None:
hot = e.hot_reload
await repo.migrate()
if hot:
plugin.log.info("reloaded on the fly, player state kept")
from amxpy import Router, events
from . import repo
from .api import vip_granted
from .config import cfg
from .state import vips
router = Router()
@router.on(events.Authorized) # SteamID уже известен
async def load(e: events.Authorized) -> None:
player = e.player
vip = await repo.find(player.steam_id) # кадр не ждёт; игрок ушёл — задача отменится
if vip is not None and vip.active:
vips[player] = vip
vip_granted.emit(player, vip.days_left)
@router.on(events.PlayerSpawn, post=True)
def spawn_bonus(e: events.PlayerSpawn) -> None:
if e.player in vips:
e.player.health += cfg.hp_bonus
@router.on(events.TakeDamage)
def more_damage(e: events.TakeDamage) -> None:
if e.attacker in vips:
e.damage *= cfg.damage_mult
from datetime import UTC, datetime, timedelta
from amxpy import Access, Player, Router
from . import repo
from .api import vip_granted
from .models import Vip
from .state import vips
router = Router()
@router.command("/vip", help="Статус VIP")
def status(player: Player) -> None:
match vips.get(player):
case None:
player.print(player.tr("VIP_NONE"))
case Vip(until=None):
player.print(player.tr("VIP_FOREVER"))
case vip:
player.print(player.tr("VIP_DAYS", days=vip.days_left))
@router.command("amx_vip_add", access=Access.RCON, help="Выдать VIP")
async def add(admin: Player, target: Player, days: int = 30) -> None:
vip = Vip(target.steam_id, datetime.now(UTC) + timedelta(days=days))
await repo.save(vip)
vips[target] = vip
vip_granted.emit(target, vip.days_left)
admin.console(f"VIP: {target.name}, {days} d.")
"""То, что видят Pawn-плагины: amxpyc генерирует из этого vip.inc."""
from amxpy import Player, Router
from .state import vips
router = Router()
vip_granted = router.forward("vip_granted", player=Player, days=int)
@router.native
def vip_is_user_vip(player: Player) -> bool:
"""Является ли игрок VIP."""
return player in vips
@router.native
def vip_days_left(player: Player) -> int:
"""Сколько дней VIP осталось: -1 — навсегда, 0 — не VIP."""
vip = vips.get(player)
return vip.days_left if vip else 0
from dataclasses import dataclass
from datetime import UTC, datetime, timedelta
from math import ceil
@dataclass
class Vip:
steam_id: str
until: datetime | None = None # None — навсегда
@property
def active(self) -> bool:
return self.until is None or self.until > datetime.now(UTC)
@property
def days_left(self) -> int:
"""Сколько дней осталось; -1 — навсегда."""
if self.until is None:
return -1
return max(0, ceil((self.until - datetime.now(UTC)) / timedelta(days=1)))
from amxpy.storage import Table
from .models import Vip
table = Table("vip", Vip, key="steam_id") # addons/amxpy/data/vip.db, запросы вне игрового потока
async def migrate() -> None:
await table.create()
async def find(steam_id: str) -> Vip | None:
return await table.get(steam_id)
async def save(vip: Vip) -> None:
await table.put(vip)
Как это работает#
__init__.pyсоздаёт единственныйPluginи подключает роутеры модулей и конфиг черезplugin.include(...); вInitготовит базу.state.py—vips: PlayerState[Vip]сkeep=True: статус игроков переживает горячую перезагрузку.perks.pyзагружает VIP вAuthorized(SteamID уже известен), даёт HP на спавне (post) и усиливает урон (pre).commands.py—/vipотвечает на языке игрока черезplayer.tr(...), аamx_vip_add <target> [days]выдаёт VIP и рассылает форвардvip_granted.api.py— нативvip_is_user_vip,vip_days_leftи форвардvip_granted: из них amxpyc генерируетvip.incдля Pawn-плагинов.