Release beta 2.0.4.1023
This commit is contained in:
@@ -90,4 +90,4 @@ __all__ = [
|
||||
"render_feature_list",
|
||||
]
|
||||
|
||||
__version__ = "2.0.2b1+1021"
|
||||
__version__ = "2.0.4b1+1023"
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@ from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||
PROFILE = 'single-player-games'
|
||||
AUTHOR = 'jwebot'
|
||||
DESCRIPTION = '摩拉游戏'
|
||||
HELP_ENTRIES = ('/摩拉/原石 /抽奖 /彩票 /21点 /游戏帮助',)
|
||||
COMMANDS = ('/摩拉', '/金币', '/wallet', '/原石', '/抽奖', '/draw', '/彩票', '/买彩票', '/lottery', '/21点', '/二十一点', '/blackjack', '/游戏帮助', '/games')
|
||||
HELP_ENTRIES = ('/摩拉/原石 /许愿池 /彩票 /21点 /游戏帮助',)
|
||||
COMMANDS = ('/摩拉', '/金币', '/wallet', '/原石', '/许愿池', '/许愿', '/抽奖', '/draw', '/彩票', '/买彩票', '/lottery', '/21点', '/二十一点', '/blackjack', '/游戏帮助', '/games')
|
||||
ADAPTER = ServiceCommandAdapter(
|
||||
PROFILE,
|
||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||
|
||||
@@ -74,6 +74,8 @@ LOCAL_COMMAND_ALIASES = {
|
||||
"borrow": "loan",
|
||||
"还款": "repay",
|
||||
"repay": "repay",
|
||||
"许愿池": "prize_draw",
|
||||
"许愿": "prize_draw",
|
||||
"抽奖": "prize_draw",
|
||||
"draw": "prize_draw",
|
||||
"彩票": "lottery",
|
||||
@@ -177,7 +179,7 @@ LOCAL_EXTENSIONS = (
|
||||
author=JWEBOT_AUTHOR,
|
||||
description="摩拉游戏",
|
||||
help_entries=(
|
||||
"/摩拉/原石 /抽奖 /彩票 /21点 /游戏帮助",
|
||||
"/摩拉/原石 /许愿池 /彩票 /21点 /游戏帮助",
|
||||
),
|
||||
command_kinds=(
|
||||
"wallet",
|
||||
|
||||
+398
-52
@@ -9,15 +9,21 @@ from typing import Any
|
||||
|
||||
|
||||
INITIAL_COINS = 1000
|
||||
MIN_DRAW_BET = 2
|
||||
MAX_DRAW_BET = 100
|
||||
WISH_POOL_GAME_KEY = "wish_pool"
|
||||
WISH_POOL_UNIT = 1000
|
||||
MIN_WISH_POOL_AMOUNT = 1000
|
||||
MAX_WISH_POOL_AMOUNT = 10_000
|
||||
WISH_POOL_CONTRIBUTION = 200
|
||||
WISH_POOL_PITY_LIMIT = 8
|
||||
WISH_POOL_JACKPOT_PERCENT = 20
|
||||
WISH_POOL_JACKPOT_CAP = 20_000
|
||||
MIN_LOTTERY_BET = 1
|
||||
MAX_LOTTERY_BET = 500
|
||||
DEFAULT_LOTTERY_BET = 10
|
||||
MIN_BLACKJACK_BET = 1
|
||||
MAX_BLACKJACK_BET = 500
|
||||
MAX_BLACKJACK_BET = 2_000
|
||||
MIN_BLACKJACK_TABLE_BET = 1
|
||||
MAX_BLACKJACK_TABLE_BET = 10_000
|
||||
MAX_BLACKJACK_TABLE_BET = 2_000
|
||||
MAX_BLACKJACK_TABLE_PLAYERS = 8
|
||||
MIN_TRANSFER_AMOUNT = 1
|
||||
MAX_TRANSFER_AMOUNT = 100_000
|
||||
@@ -51,7 +57,7 @@ WISH_MORA_TABLE = (
|
||||
)
|
||||
WISH_SCAN_TICKET_PERCENT = 10
|
||||
GAME_LABELS = {
|
||||
"prize_draw": "抽奖",
|
||||
"prize_draw": "许愿池",
|
||||
"lottery": "彩票",
|
||||
"blackjack": "21点",
|
||||
}
|
||||
@@ -66,14 +72,14 @@ BLACKJACK_ACTIONS = {
|
||||
"end": "end",
|
||||
}
|
||||
BLACKJACK_START = re.compile(
|
||||
r"^(?:开始|start)(?P<bet>\d{1,3})?$"
|
||||
r"^(?:开始|start)(?P<bet>\d{1,4})?$"
|
||||
)
|
||||
BLACKJACK_COMBINED_ACTION = re.compile(
|
||||
r"^(?:开始|start)(?P<bet>\d{1,3})?"
|
||||
r"^(?:开始|start)(?P<bet>\d{1,4})?"
|
||||
r"(?:要牌|hit|停牌|stand)$"
|
||||
)
|
||||
BLACKJACK_TABLE_BET = re.compile(
|
||||
r"^(?:放|下注|bet)(?P<bet>\d{1,5})$",
|
||||
r"^(?:放|下注|bet)(?P<bet>\d{1,4})$",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
TRANSFER_MARKED = re.compile(
|
||||
@@ -232,6 +238,19 @@ def recent_primogem_event_lines(
|
||||
return lines
|
||||
|
||||
|
||||
def blackjack_outcome_stat(outcome: object) -> str | None:
|
||||
text = str(outcome or "")
|
||||
if not text or "取消" in text:
|
||||
return None
|
||||
if "平" in text:
|
||||
return "ties"
|
||||
if any(token in text for token in ("输", "认输", "失败", "爆")):
|
||||
return "losses"
|
||||
if "赢" in text or "获胜" in text:
|
||||
return "wins"
|
||||
return None
|
||||
|
||||
|
||||
def normalize_player_account(account: dict[str, Any]) -> dict[str, Any]:
|
||||
account.setdefault("balance", INITIAL_COINS)
|
||||
account.setdefault("game", None)
|
||||
@@ -245,6 +264,12 @@ def normalize_player_account(account: dict[str, Any]) -> dict[str, Any]:
|
||||
account["scan_boost_tickets"] = (
|
||||
scan_tickets if type(scan_tickets) is int and scan_tickets >= 0 else 0
|
||||
)
|
||||
wish_pool_pity = account.get("wish_pool_pity", 0)
|
||||
account["wish_pool_pity"] = (
|
||||
wish_pool_pity
|
||||
if type(wish_pool_pity) is int and wish_pool_pity >= 0
|
||||
else 0
|
||||
)
|
||||
blackjack_streak = account.get("blackjack_taffy_win_streak", 0)
|
||||
account["blackjack_taffy_win_streak"] = (
|
||||
blackjack_streak
|
||||
@@ -289,6 +314,45 @@ def normalize_player_account(account: dict[str, Any]) -> dict[str, Any]:
|
||||
account["game_history"] = [
|
||||
entry for entry in history if isinstance(entry, dict)
|
||||
][-GAME_HISTORY_LIMIT:]
|
||||
blackjack_stats = account.get("blackjack_stats")
|
||||
if not isinstance(blackjack_stats, dict):
|
||||
blackjack_stats = {
|
||||
"wins": 0,
|
||||
"losses": 0,
|
||||
"ties": 0,
|
||||
"stake": 0,
|
||||
"payout": 0,
|
||||
}
|
||||
for entry in account["game_history"]:
|
||||
if str(entry.get("kind", "")) != "blackjack":
|
||||
continue
|
||||
stat = blackjack_outcome_stat(entry.get("outcome"))
|
||||
if stat is None:
|
||||
continue
|
||||
try:
|
||||
stake = int(entry.get("stake", 0))
|
||||
payout = int(entry.get("payout", 0))
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
blackjack_stats[stat] += 1
|
||||
blackjack_stats["stake"] += max(0, stake)
|
||||
blackjack_stats["payout"] += max(0, payout)
|
||||
else:
|
||||
blackjack_stats = {
|
||||
key: (
|
||||
int(blackjack_stats.get(key, 0))
|
||||
if type(blackjack_stats.get(key, 0)) is int
|
||||
and int(blackjack_stats.get(key, 0)) >= 0
|
||||
else 0
|
||||
)
|
||||
for key in ("wins", "losses", "ties", "stake", "payout")
|
||||
}
|
||||
blackjack_stats["hands"] = (
|
||||
blackjack_stats["wins"]
|
||||
+ blackjack_stats["losses"]
|
||||
+ blackjack_stats["ties"]
|
||||
)
|
||||
account["blackjack_stats"] = blackjack_stats
|
||||
events = account.get("coin_events")
|
||||
if not isinstance(events, list):
|
||||
events = []
|
||||
@@ -364,6 +428,24 @@ def normalize_player_account(account: dict[str, Any]) -> dict[str, Any]:
|
||||
return account
|
||||
|
||||
|
||||
def record_blackjack_result(
|
||||
account: dict[str, Any],
|
||||
*,
|
||||
outcome: str,
|
||||
stake: int,
|
||||
payout: int,
|
||||
) -> None:
|
||||
stat = blackjack_outcome_stat(outcome)
|
||||
if stat is None:
|
||||
return
|
||||
normalize_player_account(account)
|
||||
stats = account["blackjack_stats"]
|
||||
stats[stat] += 1
|
||||
stats["hands"] += 1
|
||||
stats["stake"] += max(0, int(stake))
|
||||
stats["payout"] += max(0, int(payout))
|
||||
|
||||
|
||||
def next_game_index(account: dict[str, Any], kind: str) -> int:
|
||||
normalize_player_account(account)
|
||||
counts = account["game_counts"]
|
||||
@@ -525,6 +607,39 @@ class PlayerGames:
|
||||
self.display_label = display_label
|
||||
self.rng = random.SystemRandom()
|
||||
|
||||
@staticmethod
|
||||
def wish_pool_usage() -> str:
|
||||
return (
|
||||
"用法:/许愿池 1000~10000(兼容 /许愿、/抽奖、/draw);"
|
||||
"金额必须按1000递增,每1000摩拉算1愿"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def normalize_wish_pool_state(state: dict[str, Any]) -> dict[str, Any]:
|
||||
balance = state.get("balance", 0)
|
||||
state["balance"] = balance if type(balance) is int and balance >= 0 else 0
|
||||
total_wishes = state.get("total_wishes", 0)
|
||||
state["total_wishes"] = (
|
||||
total_wishes
|
||||
if type(total_wishes) is int and total_wishes >= 0
|
||||
else 0
|
||||
)
|
||||
return state
|
||||
|
||||
@staticmethod
|
||||
def parse_wish_pool_amount(arguments: str) -> int | None:
|
||||
value = arguments.strip()
|
||||
if not value:
|
||||
return 0
|
||||
match = re.fullmatch(
|
||||
r"(?P<amount>\d{1,5})(?:\s*(?:摩拉|金币|coin|coins))?",
|
||||
value,
|
||||
re.IGNORECASE,
|
||||
)
|
||||
if match is None:
|
||||
return None
|
||||
return int(match["amount"])
|
||||
|
||||
@staticmethod
|
||||
def rules() -> str:
|
||||
return (
|
||||
@@ -538,17 +653,20 @@ class PlayerGames:
|
||||
"(理论期望3600摩拉)并可能获得扫码概率券;"
|
||||
"/扫码加权 人名 在24小时内给TA整点扫码+5权重;"
|
||||
"/Taffy发话 内容 或 /Taffy发话 定时 HH:MM 内容。\n"
|
||||
"抽奖:/抽奖 [2~100],5%返5倍、20%返2倍,其余无返还。\n"
|
||||
"许愿池:/许愿池 1000~10000(兼容 /许愿、/抽奖、/draw),"
|
||||
"每1000摩拉1愿,可能获得摩拉回响、扫码概率券、"
|
||||
"Taffy发话额度或原石,8愿无道具后触发保底。\n"
|
||||
"多人彩票:/彩票 开始;/彩票 报名 3 4 7 [1~500];"
|
||||
"/彩票 当前状态;/彩票 开奖。三位号码顺序无关,省略金额默认10;"
|
||||
"命中1/2/3个返下注的50%/120%/300%。\n"
|
||||
"21点先用 /21点 开始 [1~500] 进场,金额是本局下注,默认50;空格可省略。\n"
|
||||
"21点先用 /21点 开始 [1~2000] 进场,金额是本局下注,默认50;空格可省略。\n"
|
||||
"看到牌后再单独发送 /21点 要牌|停牌|状态|结束"
|
||||
"(/blackjack hit|stand|status|end);"
|
||||
"/21点 胜率 查看本群所有玩家及Taffy的21点胜率和净收支;"
|
||||
"每人最多一局,10分钟无操作视为认输,结束不退下注。\n"
|
||||
"多人21点:/21点 多人 启动 创建牌桌;/21点 加入;"
|
||||
"主持人 /21点 多人 开始 后按提示 /21点 放200"
|
||||
"(1~10000);轮到自己 /21点 要牌|停牌。\n"
|
||||
"(1~2000);轮到自己 /21点 要牌|停牌。\n"
|
||||
"21点获胜返2倍、平局返下注、自然21点返2.5倍向下取整,庄家软17停牌。"
|
||||
"以上均含本金;虚拟摩拉不能充值或兑换。"
|
||||
)
|
||||
@@ -582,10 +700,11 @@ class PlayerGames:
|
||||
if kind == "wallet" and arguments:
|
||||
return "用法:/摩拉(兼容 /金币、/wallet),查看本人的虚拟摩拉;/游戏帮助 查看规则"
|
||||
if kind == "prize_draw":
|
||||
if arguments and not re.fullmatch(r"\d{1,3}", arguments):
|
||||
return "用法:/抽奖 [2~100摩拉](/draw),默认10摩拉"
|
||||
bet = int(arguments or 10)
|
||||
elif kind == "blackjack":
|
||||
return self.wish_pool(row, arguments)
|
||||
if kind == "blackjack":
|
||||
compacted = re.sub(r"\s+", "", arguments.casefold())
|
||||
if compacted in {"胜率", "战绩", "stats", "rate", "rates"}:
|
||||
return self.blackjack_win_rates(row)
|
||||
table_command = self.parse_blackjack_table_command(arguments)
|
||||
if table_command is not None:
|
||||
table_action, table_bet = table_command
|
||||
@@ -595,7 +714,7 @@ class PlayerGames:
|
||||
table_bet,
|
||||
arguments,
|
||||
)
|
||||
compacted = re.sub(r"\s+", "", arguments.casefold()) or "状态"
|
||||
compacted = compacted or "状态"
|
||||
start = BLACKJACK_START.fullmatch(compacted)
|
||||
if start is not None:
|
||||
action = "start"
|
||||
@@ -613,8 +732,9 @@ class PlayerGames:
|
||||
"/21点 要牌 或 /21点 停牌"
|
||||
)
|
||||
return (
|
||||
"用法:先 /21点 开始 [1~500](金额是本局下注,默认50);"
|
||||
"开局后再单独发送 /21点 要牌|停牌|状态|结束;空格可省略"
|
||||
"用法:先 /21点 开始 [1~2000](金额是本局下注,默认50);"
|
||||
"开局后再单独发送 /21点 要牌|停牌|状态|结束;"
|
||||
"/21点 胜率 查看群内战绩;空格可省略"
|
||||
)
|
||||
group = str(row["group_fingerprint"])
|
||||
if (
|
||||
@@ -640,14 +760,12 @@ class PlayerGames:
|
||||
)
|
||||
else:
|
||||
bet = 0
|
||||
if kind == "prize_draw" and not MIN_DRAW_BET <= bet <= MAX_DRAW_BET:
|
||||
return "抽奖下注必须为 2~100 的整数摩拉"
|
||||
if (
|
||||
kind == "blackjack"
|
||||
and action == "start"
|
||||
and not MIN_BLACKJACK_BET <= bet <= MAX_BLACKJACK_BET
|
||||
):
|
||||
return "21点下注必须为 1~500 的整数摩拉"
|
||||
return "21点下注必须为 1~2000 的整数摩拉"
|
||||
person = self.ledger.resolve_person(
|
||||
str(row["group_fingerprint"]),
|
||||
str(row["sender"]),
|
||||
@@ -684,6 +802,12 @@ class PlayerGames:
|
||||
stake=int(game["bet"]),
|
||||
payout=0,
|
||||
)
|
||||
record_blackjack_result(
|
||||
account,
|
||||
outcome="超时认输",
|
||||
stake=int(game["bet"]),
|
||||
payout=0,
|
||||
)
|
||||
account["game"] = None
|
||||
account["losses"] += 1
|
||||
account["blackjack_taffy_win_streak"] = 0
|
||||
@@ -743,39 +867,210 @@ class PlayerGames:
|
||||
index,
|
||||
)
|
||||
return player_reply(expired + self.show_hand(game), index)
|
||||
index = next_game_index(account, "prize_draw")
|
||||
roll = self.rng.randrange(100)
|
||||
payout = bet * (5 if roll < 5 else 2 if roll < 25 else 0)
|
||||
result = "大奖" if roll < 5 else "小奖" if roll < 25 else "未中奖"
|
||||
account["balance"] += payout
|
||||
account["wins" if payout > bet else "losses"] += 1
|
||||
append_game_record(
|
||||
account,
|
||||
kind="prize_draw",
|
||||
index=index,
|
||||
outcome=result,
|
||||
stake=bet,
|
||||
payout=payout,
|
||||
summary=f"抽奖结果 {result}",
|
||||
)
|
||||
append_game_coin_event(
|
||||
account,
|
||||
kind="prize_draw",
|
||||
outcome=result,
|
||||
stake=bet,
|
||||
payout=payout,
|
||||
)
|
||||
return player_reply(
|
||||
expired
|
||||
+ f"{result};下注 {bet},返还 {payout},"
|
||||
f"净变化 {payout - bet:+d};摩拉 {account['balance']}",
|
||||
index,
|
||||
)
|
||||
raise LocalCommandError("未知本地游戏指令")
|
||||
|
||||
return self.ledger.apply_player_action(
|
||||
str(row["group_fingerprint"]), str(row["sender"]),
|
||||
str(row["event_id"]), kind + ":" + arguments, execute)
|
||||
|
||||
def wish_pool(self, row: dict[str, Any], arguments: str) -> str:
|
||||
amount = self.parse_wish_pool_amount(arguments)
|
||||
if amount is None:
|
||||
return self.wish_pool_usage()
|
||||
if (
|
||||
amount != 0
|
||||
and (
|
||||
amount < MIN_WISH_POOL_AMOUNT
|
||||
or amount > MAX_WISH_POOL_AMOUNT
|
||||
or amount % WISH_POOL_UNIT != 0
|
||||
)
|
||||
):
|
||||
return (
|
||||
"许愿池投入必须为 1000~10000 的整数摩拉,"
|
||||
"且按 1000 递增"
|
||||
)
|
||||
group = str(row["group_fingerprint"])
|
||||
sender = str(row["sender"])
|
||||
event_id = str(row["event_id"])
|
||||
person = self.ledger.resolve_person(group, sender)
|
||||
player_label = self.display_label(person["display_name"])
|
||||
|
||||
def player_reply(text: str, index: int | None = None) -> str:
|
||||
suffix = f" #{index}" if index is not None else ""
|
||||
return f"{player_label}{suffix}:{text}"
|
||||
|
||||
def action(
|
||||
account: dict[str, Any],
|
||||
pool_state: dict[str, Any],
|
||||
) -> str:
|
||||
normalize_player_account(account)
|
||||
self.normalize_wish_pool_state(pool_state)
|
||||
if amount == 0:
|
||||
return (
|
||||
f"{self.wish_pool_usage()};当前池水 {pool_state['balance']};"
|
||||
f"你的保底 {account['wish_pool_pity']}/{WISH_POOL_PITY_LIMIT}"
|
||||
)
|
||||
if account.get("game"):
|
||||
return "你已有一局21点,请先 /21点 停牌 或 /21点 结束"
|
||||
if account["balance"] < amount:
|
||||
return f"摩拉不足:余额 {account['balance']},本次需要 {amount}"
|
||||
|
||||
wishes = amount // WISH_POOL_UNIT
|
||||
index = next_game_index(account, "prize_draw")
|
||||
account["balance"] -= amount
|
||||
pool_gain = 0
|
||||
mora_payout = 0
|
||||
jackpot_payout = 0
|
||||
scan_tickets = 0
|
||||
taffy_credits = 0
|
||||
primogems = 0
|
||||
dry_wishes = 0
|
||||
small_returns = 0
|
||||
medium_returns = 0
|
||||
big_returns = 0
|
||||
pity_hits = 0
|
||||
jackpot_hits = 0
|
||||
|
||||
for _ in range(wishes):
|
||||
pool_state["balance"] += WISH_POOL_CONTRIBUTION
|
||||
pool_state["total_wishes"] += 1
|
||||
pool_gain += WISH_POOL_CONTRIBUTION
|
||||
force_pity = account["wish_pool_pity"] >= WISH_POOL_PITY_LIMIT
|
||||
item_awarded = False
|
||||
if force_pity:
|
||||
pity_hits += 1
|
||||
gift_roll = self.rng.randrange(100)
|
||||
if gift_roll < 70:
|
||||
scan_tickets += 1
|
||||
elif gift_roll < 95:
|
||||
taffy_credits += 1
|
||||
else:
|
||||
primogems += 1
|
||||
item_awarded = True
|
||||
else:
|
||||
roll = self.rng.randrange(1000)
|
||||
if roll < 440:
|
||||
dry_wishes += 1
|
||||
elif roll < 660:
|
||||
small_returns += 1
|
||||
mora_payout += self.rng.randrange(200, 601)
|
||||
elif roll < 780:
|
||||
medium_returns += 1
|
||||
mora_payout += self.rng.randrange(800, 1401)
|
||||
elif roll < 860:
|
||||
scan_tickets += 1
|
||||
item_awarded = True
|
||||
elif roll < 920:
|
||||
taffy_credits += 1
|
||||
item_awarded = True
|
||||
elif roll < 950:
|
||||
primogems += 1
|
||||
item_awarded = True
|
||||
elif roll < 980:
|
||||
big_returns += 1
|
||||
mora_payout += self.rng.randrange(2000, 3501)
|
||||
elif roll < 995:
|
||||
scan_tickets += 1
|
||||
taffy_credits += 1
|
||||
item_awarded = True
|
||||
elif roll < 999:
|
||||
primogems += 2
|
||||
item_awarded = True
|
||||
else:
|
||||
primogems += 2
|
||||
jackpot = min(
|
||||
pool_state["balance"] * WISH_POOL_JACKPOT_PERCENT
|
||||
// 100,
|
||||
WISH_POOL_JACKPOT_CAP,
|
||||
)
|
||||
pool_state["balance"] -= jackpot
|
||||
jackpot_payout += jackpot
|
||||
jackpot_hits += 1
|
||||
item_awarded = True
|
||||
if item_awarded:
|
||||
account["wish_pool_pity"] = 0
|
||||
else:
|
||||
account["wish_pool_pity"] += 1
|
||||
|
||||
total_mora_payout = mora_payout + jackpot_payout
|
||||
account["balance"] += total_mora_payout
|
||||
account["scan_boost_tickets"] += scan_tickets
|
||||
account["taffy_say_credits"] += taffy_credits
|
||||
if primogems:
|
||||
account["primogems"] += primogems
|
||||
append_primogem_event(account, primogems, "许愿池")
|
||||
outcome_parts: list[str] = []
|
||||
if jackpot_hits:
|
||||
outcome_parts.append("池底大愿")
|
||||
if scan_tickets or taffy_credits or primogems or pity_hits:
|
||||
outcome_parts.append("祝福")
|
||||
if big_returns:
|
||||
outcome_parts.append("大回响")
|
||||
elif medium_returns or small_returns:
|
||||
outcome_parts.append("回响")
|
||||
if not outcome_parts:
|
||||
outcome_parts.append("沉底")
|
||||
outcome = "、".join(dict.fromkeys(outcome_parts))
|
||||
append_game_record(
|
||||
account,
|
||||
kind="prize_draw",
|
||||
index=index,
|
||||
outcome=outcome,
|
||||
stake=amount,
|
||||
payout=total_mora_payout,
|
||||
summary=(
|
||||
f"许愿池 {wishes}愿;池水+{pool_gain};"
|
||||
f"摩拉+{total_mora_payout};扫码券+{scan_tickets};"
|
||||
f"发话+{taffy_credits};原石+{primogems}"
|
||||
),
|
||||
)
|
||||
append_game_coin_event(
|
||||
account,
|
||||
kind="prize_draw",
|
||||
outcome=outcome,
|
||||
stake=amount,
|
||||
payout=total_mora_payout,
|
||||
)
|
||||
|
||||
result_parts: list[str] = []
|
||||
if total_mora_payout:
|
||||
mora_text = f"摩拉 +{total_mora_payout}"
|
||||
if jackpot_payout:
|
||||
mora_text += f"(含池底 {jackpot_payout})"
|
||||
result_parts.append(mora_text)
|
||||
if scan_tickets:
|
||||
result_parts.append(f"扫码概率券 +{scan_tickets} 张")
|
||||
if taffy_credits:
|
||||
result_parts.append(f"Taffy发话额度 +{taffy_credits} 次")
|
||||
if primogems:
|
||||
result_parts.append(f"原石 +{primogems} 个")
|
||||
if not result_parts:
|
||||
result_parts.append("池水起了点波纹,啥也没捞到")
|
||||
if pity_hits:
|
||||
result_parts.append(f"触发保底 {pity_hits} 次")
|
||||
return player_reply(
|
||||
(
|
||||
f"投入 {amount} 摩拉,向许愿池许了 {wishes} 愿;"
|
||||
f"结果:{','.join(result_parts)};"
|
||||
f"当前摩拉 {account['balance']},"
|
||||
f"原石 {account['primogems']} 个,"
|
||||
f"扫码概率券 {account['scan_boost_tickets']} 张,"
|
||||
f"Taffy发话额度 {account['taffy_say_credits']} 次;"
|
||||
f"池水 {pool_state['balance']};"
|
||||
f"保底 {account['wish_pool_pity']}/{WISH_POOL_PITY_LIMIT}"
|
||||
),
|
||||
index,
|
||||
)
|
||||
|
||||
return self.ledger.apply_player_local_game_action(
|
||||
group,
|
||||
sender,
|
||||
event_id,
|
||||
"prize_draw:" + str(amount or ""),
|
||||
WISH_POOL_GAME_KEY,
|
||||
action,
|
||||
)
|
||||
|
||||
def primogem_command(
|
||||
self,
|
||||
row: dict[str, Any],
|
||||
@@ -1055,6 +1350,45 @@ class PlayerGames:
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
@staticmethod
|
||||
def blackjack_net_text(net: int) -> str:
|
||||
if net > 0:
|
||||
return f"净赚 {net} 摩拉"
|
||||
if net < 0:
|
||||
return f"净亏 {-net} 摩拉"
|
||||
return "净赚净亏 0 摩拉"
|
||||
|
||||
def blackjack_win_rates(self, row: dict[str, Any]) -> str:
|
||||
stats = self.ledger.player_blackjack_stats(
|
||||
str(row["group_fingerprint"])
|
||||
)
|
||||
hands = int(stats["hands"])
|
||||
if hands == 0:
|
||||
return "当前群还没有已结算的21点记录"
|
||||
taffy_wins = int(stats["taffy_wins"])
|
||||
taffy_losses = int(stats["taffy_losses"])
|
||||
ties = int(stats["ties"])
|
||||
taffy_rate = taffy_wins / hands * 100
|
||||
lines = [
|
||||
f"21点胜率:本群已结算 {hands} 手",
|
||||
(
|
||||
f"Taffy:胜 {taffy_wins} / 负 {taffy_losses} / 平 {ties},"
|
||||
f"胜率 {taffy_rate:.2f}%;"
|
||||
f"{self.blackjack_net_text(int(stats['taffy_net']))}"
|
||||
),
|
||||
"玩家:",
|
||||
]
|
||||
for index, player in enumerate(stats["players"], start=1):
|
||||
player_hands = int(player["hands"])
|
||||
win_rate = int(player["wins"]) / player_hands * 100
|
||||
lines.append(
|
||||
f"{index}. {player['display_name']}:"
|
||||
f"胜 {player['wins']} / 负 {player['losses']} / "
|
||||
f"平 {player['ties']},胜率 {win_rate:.2f}%;"
|
||||
f"{self.blackjack_net_text(int(player['net']))}"
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
@staticmethod
|
||||
def parse_blackjack_table_command(
|
||||
arguments: str,
|
||||
@@ -1117,7 +1451,7 @@ class PlayerGames:
|
||||
def blackjack_prompt(player: dict[str, Any]) -> str:
|
||||
return (
|
||||
f"请{player['display_name']} 放自己的筹码\n"
|
||||
"输入/21点 放200(1~10000)"
|
||||
"输入/21点 放200(1~2000)"
|
||||
)
|
||||
|
||||
def group_blackjack(
|
||||
@@ -1135,7 +1469,7 @@ class PlayerGames:
|
||||
return (
|
||||
"多人21点用法:/21点 多人 启动;/21点 加入;"
|
||||
"主持人 /21点 多人 开始;按提示 /21点 放200"
|
||||
"(1~10000);轮到自己 /21点 要牌 或 /21点 停牌"
|
||||
"(1~2000);轮到自己 /21点 要牌 或 /21点 停牌"
|
||||
)
|
||||
if action == "start":
|
||||
return self.ledger.start_group_blackjack(
|
||||
@@ -1181,7 +1515,7 @@ class PlayerGames:
|
||||
)
|
||||
if action == "bet":
|
||||
if not MIN_BLACKJACK_TABLE_BET <= amount <= MAX_BLACKJACK_TABLE_BET:
|
||||
return "21点多人牌桌筹码必须为 1~10000 的整数摩拉"
|
||||
return "21点多人牌桌筹码必须为 1~2000 的整数摩拉"
|
||||
deck = list(range(52))
|
||||
self.rng.shuffle(deck)
|
||||
return self.ledger.place_group_blackjack_bet(
|
||||
@@ -1868,6 +2202,12 @@ class PlayerGames:
|
||||
stake=bet,
|
||||
payout=0,
|
||||
)
|
||||
record_blackjack_result(
|
||||
account,
|
||||
outcome="主动认输",
|
||||
stake=bet,
|
||||
payout=0,
|
||||
)
|
||||
account["game"] = None
|
||||
account["losses"] += 1
|
||||
account["blackjack_taffy_win_streak"] = 0
|
||||
@@ -1922,6 +2262,12 @@ class PlayerGames:
|
||||
stake=int(bet),
|
||||
payout=int(payout),
|
||||
)
|
||||
record_blackjack_result(
|
||||
account,
|
||||
outcome=outcome,
|
||||
stake=int(bet),
|
||||
payout=int(payout),
|
||||
)
|
||||
achievements = blackjack_achievement_lines(
|
||||
account,
|
||||
outcome=outcome,
|
||||
|
||||
Reference in New Issue
Block a user