add # 新增种子商店界面

fixed # 修复数据库目录错误的BUG
This commit is contained in:
2026-10-20 13:15:01 +08:00
parent 92a3944e43
commit 38f4b84cf0
15 changed files with 395 additions and 15 deletions
Binary file not shown.
+7
View File
@@ -6244,6 +6244,13 @@
30
]
},
"webgl/shop-board.png": {
"sha256": "88730b82d88cb33fae45c7180a88954fcc0241b2b4f5de095fc6b22b30296645",
"size": [
1024,
1536
]
},
"webgl/wither.png": {
"sha256": "1c87f05e0b7bd8287926351e07746bc9e36371a860afaef0e4b00c51536dc472",
"size": [
+30 -1
View File
@@ -4,6 +4,9 @@ import asyncio
import logging
from time import perf_counter
import time
import hashlib
from .shop import render_shop
logger = logging.getLogger(__name__)
@@ -60,13 +63,39 @@ class RenderService:
return await asyncio.shield(task)
async def _run(self, *args):
return await self._run_function(self.renderer.render, *args)
async def seed_shop(self, plants, account, page, pages):
if self._closed:
raise RuntimeError('Render service closed')
key = 'shop:' + hashlib.sha256(
repr((plants, account, page, pages)).encode(),
).hexdigest()
task = self._pending.get(key)
if task is None:
if len(self._pending) >= self._max_pending:
raise RuntimeError('Render queue full')
task = asyncio.create_task(self._run_function(
render_shop, self.renderer, plants, account, page, pages,
))
self._pending[key] = task
def finished(done):
self._pending.pop(key, None)
if not done.cancelled():
done.exception()
task.add_done_callback(finished)
return await asyncio.shield(task)
async def _run_function(self, function, *args):
started = perf_counter()
async with self._semaphore:
logger.info(
"farm render queue_ms=%.2f", (perf_counter()-started)*1000,
)
worker = asyncio.create_task(asyncio.to_thread(
self.renderer.render, *args,
function, *args,
))
try:
return await asyncio.shield(worker)
+93
View File
@@ -0,0 +1,93 @@
"""文字指令使用的木质种子价目板;全部价格来自离线目录。"""
import logging
import shlex
from PIL import Image
from .canvas import Canvas, font
logger = logging.getLogger(__name__)
PAGE_SIZE = 6
def shop_page(catalogue, page):
plants = sorted((p for p in catalogue if p.buyable),
key=lambda p: p.plant_id)
pages = max(1, (len(plants) + PAGE_SIZE - 1) // PAGE_SIZE)
if type(page) is not int or not 1 <= page <= pages:
raise ValueError('Invalid shop page')
start = (page - 1) * PAGE_SIZE
return tuple(plants[start:start + PAGE_SIZE]), pages
def duration(seconds):
hours, remainder = divmod(seconds, 3600)
minutes, seconds = divmod(remainder, 60)
return ''.join(f'{value}{unit}' for value, unit in (
(hours, '小时'), (minutes, '分'), (seconds, '秒'),
) if value) or '0秒'
def render_shop(renderer, plants, account, page, pages):
canvas = Canvas(renderer.assets.image('webgl/shop-board.png', (1024, 1536)))
def text(value, xy, size=28, color='#5b3015', width=850):
value = canvas.fit_text(value, renderer.font_path, size, width)
box = font(renderer.font_path, size).getbbox(value)
canvas.text((xy[0], xy[1]-box[1]), value,
renderer.font_path, size, color)
def centered(value, y, size, color='#5b3015'):
x = round((1024 - canvas.measure_text(
value, renderer.font_path, size,
)) / 2)
text(value, (x, y), size, color)
centered('种 子 商 店', 58, 54, '#fff5d3')
centered('种子价目板 · 发送文字指令购买', 178, 25, '#7a6439')
balances = ('未开通农场 · 可浏览种子,购买前请发送:开通农场'
if account is None else
f'Lv.{account.level} 农场币 {account.point:,} '
f'点券 {account.vip_point:,}')
text(balances, (142, 248), 29, width=745)
for index, plant in enumerate(plants):
y = 326 + index * 156
icon = f'plant/{plant.name}/icon.png'
if icon in renderer.assets.metadata['images']:
image = renderer.assets.image(icon)
image.thumbnail((104, 110), Image.Resampling.LANCZOS)
canvas.paste(image, (175-image.width//2, y+67-image.height//2))
else:
text('暂无图', (133, y+55), 23, '#8b8057', width=92)
logger.info('farm shop packaged icon unavailable id=%s',
plant.plant_id)
text(plant.name, (268, y+13), 34, width=490)
text(f'#{plant.plant_id}', (774, y+17), 26, '#69783e', width=132)
currency = '点券' if plant.is_vip else '农场币'
text(f'{plant.cost:,} {currency} / 粒', (268, y+62), 32,
'#b66522', width=480)
locked = account is not None and account.level < plant.level
text(f'{"需" if locked else "等级"} Lv.{plant.level}',
(774, y+69), 23, '#ac542d' if locked else '#66733d', width=132)
text(f'首熟 {duration(plant.growth_seconds)} · {plant.crop}季'
f' · 每季{plant.harvest}个', (268, y+108), 23, width=635)
if not plants:
text('暂无出售中的种子', (340, 690), 34)
example = plants[0] if plants else None
if example:
text(f'按编号:购买种子 #{example.plant_id} 5', (142, 1291), 27)
name = example.name
if any(c.isspace() for c in name) or any(c in name for c in '\"\''):
name = shlex.quote(name)
text(f'按名称:购买种子 {name} 5', (142, 1341), 27, width=750)
else:
text('购买种子 #编号 数量 或 购买种子 种子名 数量',
(142, 1316), 25, width=750)
navigation = f'第 {page} / {pages} 页'
if page < pages:
navigation += f' 下一页:种子商店 {page+1}'
elif page > 1:
navigation += f' 上一页:种子商店 {page-1}'
text(navigation, (263, 1420), 23, width=510)
return canvas.encode('PNG')
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB