1.优化抽奖券的抽奖逻辑
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
"poolType": "Pool Type",
|
"poolType": "Pool Type",
|
||||||
"placeholderPoolType": "Please select pool type",
|
"placeholderPoolType": "Please select pool type",
|
||||||
"poolTypeNormal": "Normal",
|
"poolTypeNormal": "Normal",
|
||||||
|
"poolTypeFree": "Free",
|
||||||
"poolTypeKill": "Kill",
|
"poolTypeKill": "Kill",
|
||||||
"poolTypeT1": "T1 High",
|
"poolTypeT1": "T1 High",
|
||||||
"safetyLine": "Safety Line",
|
"safetyLine": "Safety Line",
|
||||||
@@ -25,7 +26,7 @@
|
|||||||
"realtime": "Live",
|
"realtime": "Live",
|
||||||
"profitCalcHint": "Accumulated on name=default (Normal) pool: paid += win_coin − paid_amount (ante×1); free += win_coin. Compared with safety line to decide paid-draw kill switch. Refreshes every 2s while open.",
|
"profitCalcHint": "Accumulated on name=default (Normal) pool: paid += win_coin − paid_amount (ante×1); free += win_coin. Compared with safety line to decide paid-draw kill switch. Refreshes every 2s while open.",
|
||||||
"tierRuleTitle": "Paid draw tier rule",
|
"tierRuleTitle": "Paid draw tier rule",
|
||||||
"tierRuleContent": "Compares default pool profit_amount (not per-player profit). Below safety line or kill off: use player T*_weight; at or above safety line with kill on: use killScore pool T*_weight. Free draws always use killScore weights (safety line N/A).",
|
"tierRuleContent": "Compares default pool profit_amount (not per-player profit). Below safety line or kill off: paid uses player T*_weight; at/above safety line with kill on: paid uses killScore pool. Free draws always use channel name=free pool weights (fallback default if missing); safety line N/A.",
|
||||||
"enableKillScore": "Enable kill score",
|
"enableKillScore": "Enable kill score",
|
||||||
"killScoreWeights": "Kill weights (killScore)",
|
"killScoreWeights": "Kill weights (killScore)",
|
||||||
"killWeightNote": "Edit killScore (Force Kill) row in the list for kill weights. This dialog only configures default pool safety line and kill switch.",
|
"killWeightNote": "Edit killScore (Force Kill) row in the list for kill weights. This dialog only configures default pool safety line and kill switch.",
|
||||||
@@ -57,6 +58,7 @@
|
|||||||
"placeholderName": "Please enter name",
|
"placeholderName": "Please enter name",
|
||||||
"placeholderPoolType": "Please select pool type",
|
"placeholderPoolType": "Please select pool type",
|
||||||
"poolTypeNormal": "Normal",
|
"poolTypeNormal": "Normal",
|
||||||
|
"poolTypeFree": "Free",
|
||||||
"poolTypeKill": "Force Kill",
|
"poolTypeKill": "Force Kill",
|
||||||
"poolTypeT1": "T1 High Rate"
|
"poolTypeT1": "T1 High Rate"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -54,11 +54,14 @@
|
|||||||
"weightTest": {
|
"weightTest": {
|
||||||
"title": "One-Click Weight Test",
|
"title": "One-Click Weight Test",
|
||||||
"alertTitle": "Bonus pool logic",
|
"alertTitle": "Bonus pool logic",
|
||||||
"alertBody": "Test mode is non-kill by default. You can enable kill mode below with switch + safety line: once simulated player cumulative profit reaches the line, paid draws switch to killScore.",
|
"alertBody": "Test mode is non-kill by default. When test kill mode is enabled, rules match production: compare default pool profit_amount to the safety line, with kill_enabled on and a killScore config present.",
|
||||||
"chainModeHint": "Simulation: set paid spin counts only (CW/CCW). If a paid draw hits “play again” (or T5), the next draw is free with the same ante, lottery type free, paid amount 0. Free-draw tier odds are configured below (including chained free plays).",
|
"chainModeHint": "Simulation: set paid spin counts only (CW/CCW). If a paid draw hits “play again” (or T5), the next draw is free with the same ante, lottery type free, paid amount 0. Free-draw tier odds are configured below (including chained free plays).",
|
||||||
"killModeHint": "When test kill mode is enabled: use simulated player cumulative profit as trigger; once cumulative profit >= safety line, subsequent paid draws use killScore. Free draws still follow the configured free settings.",
|
"killModeHint": "When test kill mode is on: start from default pool profit_amount and accumulate each spin (paid: win_coin - paid_amount; free: win_coin). Once profit >= safety line and kill_enabled is on, subsequent paid draws use killScore; free draws still use the name=free pool.",
|
||||||
"labelKillModeEnabled": "Enable test kill mode",
|
"labelKillModeEnabled": "Enable test kill mode",
|
||||||
"labelTestSafetyLine": "Test safety line",
|
"labelTestSafetyLine": "default safety line",
|
||||||
|
"killEnabledOn": "kill on",
|
||||||
|
"killEnabledOff": "kill off",
|
||||||
|
"poolProfitPrefix": "pool profit ",
|
||||||
"sectionPaid": "Paid draws",
|
"sectionPaid": "Paid draws",
|
||||||
"sectionFreeAfterPlayAgain": "Free draw tier odds (after play-again)",
|
"sectionFreeAfterPlayAgain": "Free draw tier odds (after play-again)",
|
||||||
"tierProbHintFreeChain": "When using custom tier odds: T1–T5 below apply when a free draw runs (tier roll; combined with dice_reward row weights).",
|
"tierProbHintFreeChain": "When using custom tier odds: T1–T5 below apply when a free draw runs (tier roll; combined with dice_reward row weights).",
|
||||||
@@ -69,7 +72,7 @@
|
|||||||
"labelAnte": "Ante",
|
"labelAnte": "Ante",
|
||||||
"placeholderAnte": "Select ante config",
|
"placeholderAnte": "Select ante config",
|
||||||
"placeholderPaidPool": "Leave empty for custom tier odds below (default: default)",
|
"placeholderPaidPool": "Leave empty for custom tier odds below (default: default)",
|
||||||
"placeholderFreePool": "Leave empty for custom tier odds below (default: killScore)",
|
"placeholderFreePool": "Leave empty for custom tier odds below (default: free pool)",
|
||||||
"tierProbHint": "Custom tier odds (T1–T5), each 0–100%, sum of five must not exceed 100%",
|
"tierProbHint": "Custom tier odds (T1–T5), each 0–100%, sum of five must not exceed 100%",
|
||||||
"tierFieldLabel": "Tier {tier} (%)",
|
"tierFieldLabel": "Tier {tier} (%)",
|
||||||
"tierSumError": "Current sum of five tiers is {sum}%, cannot exceed 100%",
|
"tierSumError": "Current sum of five tiers is {sum}%, cannot exceed 100%",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"poolType": "奖池类型",
|
"poolType": "奖池类型",
|
||||||
"placeholderPoolType": "请选择奖池类型",
|
"placeholderPoolType": "请选择奖池类型",
|
||||||
"poolTypeNormal": "正常",
|
"poolTypeNormal": "正常",
|
||||||
|
"poolTypeFree": "免费",
|
||||||
"poolTypeKill": "强制杀猪",
|
"poolTypeKill": "强制杀猪",
|
||||||
"poolTypeT1": "T1高倍率",
|
"poolTypeT1": "T1高倍率",
|
||||||
"safetyLine": "安全线",
|
"safetyLine": "安全线",
|
||||||
@@ -25,7 +26,7 @@
|
|||||||
"realtime": "实时",
|
"realtime": "实时",
|
||||||
"profitCalcHint": "累计在 name=default(正常)奖池上:付费每局 += win_coin − paid_amount(ante×1);免费每局 += win_coin。用于与安全线比较,判定付费抽奖是否切换杀分。弹窗打开期间每 2 秒自动刷新。",
|
"profitCalcHint": "累计在 name=default(正常)奖池上:付费每局 += win_coin − paid_amount(ante×1);免费每局 += win_coin。用于与安全线比较,判定付费抽奖是否切换杀分。弹窗打开期间每 2 秒自动刷新。",
|
||||||
"tierRuleTitle": "付费抽奖档位规则",
|
"tierRuleTitle": "付费抽奖档位规则",
|
||||||
"tierRuleContent": "比较对象为 default 奖池的 profit_amount(非单个玩家盈利)。当 profit_amount 低于安全线或未开启杀分时,按玩家 T*_weight 抽档;当 profit_amount 高于或等于安全线且已开启杀分时,按 killScore 奖池的 T*_weight 抽档。免费抽奖始终按 killScore 权重(与安全线无关)。",
|
"tierRuleContent": "比较对象为 default 奖池的 profit_amount(非单个玩家盈利)。当 profit_amount 低于安全线或未开启杀分时,付费按玩家 T*_weight 抽档;当 profit_amount 高于或等于安全线且已开启杀分时,付费按 killScore 奖池抽档。免费抽奖始终按本渠道 name=free 奖池权重(无 free 时回退 default),与安全线无关。",
|
||||||
"enableKillScore": "开启杀分",
|
"enableKillScore": "开启杀分",
|
||||||
"killScoreWeights": "杀分权重(killScore)",
|
"killScoreWeights": "杀分权重(killScore)",
|
||||||
"killWeightNote": "杀分权重请在列表中编辑 name=killScore(强制杀猪)记录;本弹窗仅配置 default 奖池的安全线与杀分开关。",
|
"killWeightNote": "杀分权重请在列表中编辑 name=killScore(强制杀猪)记录;本弹窗仅配置 default 奖池的安全线与杀分开关。",
|
||||||
@@ -57,6 +58,7 @@
|
|||||||
"placeholderName": "请输入名称",
|
"placeholderName": "请输入名称",
|
||||||
"placeholderPoolType": "请选择奖池类型",
|
"placeholderPoolType": "请选择奖池类型",
|
||||||
"poolTypeNormal": "正常",
|
"poolTypeNormal": "正常",
|
||||||
|
"poolTypeFree": "免费",
|
||||||
"poolTypeKill": "强制杀猪",
|
"poolTypeKill": "强制杀猪",
|
||||||
"poolTypeT1": "T1高倍率"
|
"poolTypeT1": "T1高倍率"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -54,11 +54,14 @@
|
|||||||
"weightTest": {
|
"weightTest": {
|
||||||
"title": "一键测试权重",
|
"title": "一键测试权重",
|
||||||
"alertTitle": "彩金池逻辑说明",
|
"alertTitle": "彩金池逻辑说明",
|
||||||
"alertBody": "测试模式默认不启用杀分切换;可通过下方“杀分开关 + 安全线”在测试内启用:当模拟玩家累计盈利达到安全线后,付费抽奖切换到 killScore。",
|
"alertBody": "测试模式默认不启用杀分切换;开启「测试内杀分」后,判定规则与线上一致:以 default 彩金池累计盈利(profit_amount)对比安全线,且需 kill_enabled=开启、存在 killScore 配置。",
|
||||||
"chainModeHint": "模拟方式:只配置付费抽奖次数(顺/逆时针)。付费抽到「再来一次」或 T5 时,下一局自动为免费抽奖,底注与触发局相同,抽奖类型记为免费、付费金额记为 0。免费抽奖的档位概率由下方「免费抽奖」配置决定(含通过再来一次触发的后续免费局)。",
|
"chainModeHint": "模拟方式:只配置付费抽奖次数(顺/逆时针)。付费抽到「再来一次」或 T5 时,下一局自动为免费抽奖,底注与触发局相同,抽奖类型记为免费、付费金额记为 0。免费抽奖的档位概率由下方「免费抽奖」配置决定(含通过再来一次触发的后续免费局)。",
|
||||||
"killModeHint": "杀分开关开启后:以“模拟玩家累计盈利”作为判定值;当累计盈利 >= 安全线时,后续付费抽奖按 killScore 配置抽取;免费抽奖仍按“免费抽奖配置”执行。",
|
"killModeHint": "杀分开关开启后:从 default 奖池当前 profit_amount 起步,测试内逐局累加(付费=win_coin-paid_amount,免费=win_coin);当累计盈利 ≥ 安全线且 kill_enabled 开启时,后续付费抽奖切 killScore;免费抽奖仍走 name=free 奖池。",
|
||||||
"labelKillModeEnabled": "开启测试内杀分",
|
"labelKillModeEnabled": "开启测试内杀分",
|
||||||
"labelTestSafetyLine": "测试安全线",
|
"labelTestSafetyLine": "default 安全线",
|
||||||
|
"killEnabledOn": "杀分已开启",
|
||||||
|
"killEnabledOff": "杀分已关闭",
|
||||||
|
"poolProfitPrefix": "当前池盈利 ",
|
||||||
"sectionPaid": "付费抽奖",
|
"sectionPaid": "付费抽奖",
|
||||||
"sectionFreeAfterPlayAgain": "免费抽奖(再来一次后的档位概率)",
|
"sectionFreeAfterPlayAgain": "免费抽奖(再来一次后的档位概率)",
|
||||||
"tierProbHintFreeChain": "当使用自定义档位时:以下为「免费抽奖」时 T1~T5 的档位概率(仅在有免费局时参与摇档,与 dice_reward 格子权重共同决定结果)。",
|
"tierProbHintFreeChain": "当使用自定义档位时:以下为「免费抽奖」时 T1~T5 的档位概率(仅在有免费局时参与摇档,与 dice_reward 格子权重共同决定结果)。",
|
||||||
@@ -69,7 +72,7 @@
|
|||||||
"labelAnte": "底注",
|
"labelAnte": "底注",
|
||||||
"placeholderAnte": "请选择底注配置",
|
"placeholderAnte": "请选择底注配置",
|
||||||
"placeholderPaidPool": "不选则下方自定义档位概率(默认 default)",
|
"placeholderPaidPool": "不选则下方自定义档位概率(默认 default)",
|
||||||
"placeholderFreePool": "不选则下方自定义档位概率(默认 killScore)",
|
"placeholderFreePool": "不选则下方自定义档位概率(默认 free 免费奖池)",
|
||||||
"tierProbHint": "自定义档位概率(T1~T5),每档 0-100%,五档之和不能超过 100%",
|
"tierProbHint": "自定义档位概率(T1~T5),每档 0-100%,五档之和不能超过 100%",
|
||||||
"tierFieldLabel": "档位 {tier}(%)",
|
"tierFieldLabel": "档位 {tier}(%)",
|
||||||
"tierSumError": "当前五档之和为 {sum}%,不能超过 100%",
|
"tierSumError": "当前五档之和为 {sum}%,不能超过 100%",
|
||||||
|
|||||||
@@ -91,6 +91,7 @@
|
|||||||
const typeFormatter = (row: Record<string, unknown>) => {
|
const typeFormatter = (row: Record<string, unknown>) => {
|
||||||
const n = String(row.name ?? '')
|
const n = String(row.name ?? '')
|
||||||
if (n === 'default') return t('page.search.poolTypeNormal')
|
if (n === 'default') return t('page.search.poolTypeNormal')
|
||||||
|
if (n === 'free') return t('page.search.poolTypeFree')
|
||||||
if (n === 'killScore') return t('page.search.poolTypeKill')
|
if (n === 'killScore') return t('page.search.poolTypeKill')
|
||||||
if (n === 'up') return t('page.search.poolTypeT1')
|
if (n === 'up') return t('page.search.poolTypeT1')
|
||||||
return n || '-'
|
return n || '-'
|
||||||
|
|||||||
@@ -44,14 +44,11 @@
|
|||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
<ElCol :span="8">
|
<ElCol :span="8">
|
||||||
<ElFormItem :label="$t('page.weightTest.labelTestSafetyLine')" prop="test_safety_line">
|
<ElFormItem :label="$t('page.weightTest.labelTestSafetyLine')">
|
||||||
<ElInputNumber
|
<ElInput
|
||||||
v-model="form.test_safety_line"
|
:model-value="defaultPoolSafetyLineText"
|
||||||
:min="0"
|
readonly
|
||||||
:step="100"
|
|
||||||
:disabled="!form.kill_mode_enabled"
|
:disabled="!form.kill_mode_enabled"
|
||||||
controls-position="right"
|
|
||||||
style="width: 100%"
|
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
@@ -221,17 +218,27 @@
|
|||||||
free_tier_weights: { T1: 20, T2: 20, T3: 20, T4: 20, T5: 20 } as Record<string, number>,
|
free_tier_weights: { T1: 20, T2: 20, T3: 20, T4: 20, T5: 20 } as Record<string, number>,
|
||||||
paid_s_count: 100,
|
paid_s_count: 100,
|
||||||
paid_n_count: 100,
|
paid_n_count: 100,
|
||||||
kill_mode_enabled: false,
|
kill_mode_enabled: false
|
||||||
test_safety_line: 5000
|
})
|
||||||
|
const defaultPoolSafetyLineText = computed(() => {
|
||||||
|
const info = defaultPoolInfo.value
|
||||||
|
if (!info) {
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
const killText = info.kill_enabled === 1
|
||||||
|
? t('page.weightTest.killEnabledOn')
|
||||||
|
: t('page.weightTest.killEnabledOff')
|
||||||
|
return `${info.safety_line}(${killText},${t('page.weightTest.poolProfitPrefix')}${info.profit_amount})`
|
||||||
})
|
})
|
||||||
const lotteryOptions = ref<Array<{ id: number; name: string }>>([])
|
const lotteryOptions = ref<Array<{ id: number; name: string }>>([])
|
||||||
const paidLotteryOptions = computed(() =>
|
const paidLotteryOptions = computed(() =>
|
||||||
lotteryOptions.value.filter((r) => r.name === 'default')
|
lotteryOptions.value.filter((r) => r.name === 'default')
|
||||||
)
|
)
|
||||||
const freeLotteryOptions = computed(() => {
|
const freeLotteryOptions = computed(() => {
|
||||||
const list = lotteryOptions.value.filter((r) => r.name === 'killScore')
|
const list = lotteryOptions.value.filter((r) => r.name === 'free')
|
||||||
return list.length > 0 ? list : lotteryOptions.value
|
return list.length > 0 ? list : lotteryOptions.value.filter((r) => r.name === 'default')
|
||||||
})
|
})
|
||||||
|
const defaultPoolInfo = ref<{ safety_line: number; kill_enabled: number; profit_amount: number } | null>(null)
|
||||||
const running = ref(false)
|
const running = ref(false)
|
||||||
|
|
||||||
function onClose() {
|
function onClose() {
|
||||||
@@ -321,6 +328,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadDefaultPoolInfo() {
|
||||||
|
try {
|
||||||
|
const pool = await lotteryPoolApi.getCurrentPool(resolveDeptParams())
|
||||||
|
defaultPoolInfo.value = {
|
||||||
|
safety_line: Number(pool?.safety_line ?? 0),
|
||||||
|
kill_enabled: Number(pool?.kill_enabled ?? 1),
|
||||||
|
profit_amount: Number(pool?.profit_amount ?? 0)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
defaultPoolInfo.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadLotteryOptions() {
|
async function loadLotteryOptions() {
|
||||||
try {
|
try {
|
||||||
const list = await lotteryPoolApi.getOptions(resolveDeptParams())
|
const list = await lotteryPoolApi.getOptions(resolveDeptParams())
|
||||||
@@ -332,9 +352,9 @@
|
|||||||
if (normal) {
|
if (normal) {
|
||||||
form.paid_lottery_config_id = normal.id
|
form.paid_lottery_config_id = normal.id
|
||||||
}
|
}
|
||||||
const kill = list.find((r: { name?: string }) => r.name === 'killScore')
|
const freePool = list.find((r: { name?: string }) => r.name === 'free')
|
||||||
if (kill) {
|
if (freePool) {
|
||||||
form.free_lottery_config_id = kill.id
|
form.free_lottery_config_id = freePool.id
|
||||||
} else if (list.length > 0) {
|
} else if (list.length > 0) {
|
||||||
form.free_lottery_config_id = list[0].id
|
form.free_lottery_config_id = list[0].id
|
||||||
}
|
}
|
||||||
@@ -353,7 +373,7 @@
|
|||||||
free_n_count: 0,
|
free_n_count: 0,
|
||||||
chain_free_mode: true,
|
chain_free_mode: true,
|
||||||
kill_mode_enabled: form.kill_mode_enabled,
|
kill_mode_enabled: form.kill_mode_enabled,
|
||||||
test_safety_line: form.test_safety_line
|
test_safety_line: defaultPoolInfo.value?.safety_line ?? 0
|
||||||
}
|
}
|
||||||
if (form.paid_lottery_config_id != null) {
|
if (form.paid_lottery_config_id != null) {
|
||||||
payload.paid_lottery_config_id = form.paid_lottery_config_id
|
payload.paid_lottery_config_id = form.paid_lottery_config_id
|
||||||
@@ -382,10 +402,6 @@
|
|||||||
ElMessage.warning(t('page.weightTest.warnPaidSpins'))
|
ElMessage.warning(t('page.weightTest.warnPaidSpins'))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (form.kill_mode_enabled && (form.test_safety_line == null || form.test_safety_line < 0)) {
|
|
||||||
ElMessage.warning(t('page.weightTest.warnTestSafetyLine'))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
const needPaidTier = form.paid_lottery_config_id == null
|
const needPaidTier = form.paid_lottery_config_id == null
|
||||||
const needFreeTier = form.free_lottery_config_id == null
|
const needFreeTier = form.free_lottery_config_id == null
|
||||||
if (needPaidTier) {
|
if (needPaidTier) {
|
||||||
@@ -437,6 +453,7 @@
|
|||||||
if (v) {
|
if (v) {
|
||||||
void loadAnteOptions()
|
void loadAnteOptions()
|
||||||
void loadLotteryOptions()
|
void loadLotteryOptions()
|
||||||
|
void loadDefaultPoolInfo()
|
||||||
} else {
|
} else {
|
||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
@@ -448,6 +465,7 @@
|
|||||||
if (visible.value) {
|
if (visible.value) {
|
||||||
void loadAnteOptions()
|
void loadAnteOptions()
|
||||||
void loadLotteryOptions()
|
void loadLotteryOptions()
|
||||||
|
void loadDefaultPoolInfo()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -118,7 +118,8 @@ class PlayStartLogic
|
|||||||
}
|
}
|
||||||
|
|
||||||
$configType0 = DiceLotteryPoolConfig::where('name', 'default')->where('dept_id', $configDeptId)->find();
|
$configType0 = DiceLotteryPoolConfig::where('name', 'default')->where('dept_id', $configDeptId)->find();
|
||||||
$configType1 = DiceLotteryPoolConfig::where('name', 'killScore')->where('dept_id', $configDeptId)->find();
|
$configKill = DiceLotteryPoolConfig::where('name', 'killScore')->where('dept_id', $configDeptId)->find();
|
||||||
|
$configFree = DiceLotteryPoolConfig::where('name', 'free')->where('dept_id', $configDeptId)->find();
|
||||||
if (!$configType0) {
|
if (!$configType0) {
|
||||||
throw new ApiException('Lottery pool config not found (name=default required)');
|
throw new ApiException('Lottery pool config not found (name=default required)');
|
||||||
}
|
}
|
||||||
@@ -132,17 +133,28 @@ class PlayStartLogic
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 彩金池累计盈利:用于判断是否触发杀分(不再依赖单个玩家累计盈利)
|
// 彩金池累计盈利:用于判断是否触发杀分(不再依赖单个玩家累计盈利)
|
||||||
// 该值来自 dice_lottery_pool_config.profit_amount
|
// 该值来自 dice_lottery_pool_config.profit_amount(default 奖池)
|
||||||
$poolProfitTotal = $configType0->profit_amount ?? 0;
|
$poolProfitTotal = $configType0->profit_amount ?? 0;
|
||||||
$safetyLine = (int) ($configType0->safety_line ?? 0);
|
$safetyLine = (int) ($configType0->safety_line ?? 0);
|
||||||
$killEnabled = ((int) ($configType0->kill_enabled ?? 1)) === 1;
|
$killEnabled = ((int) ($configType0->kill_enabled ?? 1)) === 1;
|
||||||
// 盈利>=安全线且开启杀分:付费/免费都用 killScore;盈利<安全线:付费用玩家权重,免费用 killScore(无则用 default)
|
|
||||||
// 记录 lottery_config_id:用池权重时记对应池,付费用玩家权重时记 default
|
$usePaidKill = $ticketType === self::LOTTERY_TYPE_PAID
|
||||||
$usePoolWeights = ($ticketType === self::LOTTERY_TYPE_PAID && $killEnabled && $poolProfitTotal >= $safetyLine && $configType1 !== null)
|
&& $killEnabled
|
||||||
|| ($ticketType === self::LOTTERY_TYPE_FREE);
|
&& $poolProfitTotal >= $safetyLine
|
||||||
$config = $usePoolWeights
|
&& $configKill !== null;
|
||||||
? (($ticketType === self::LOTTERY_TYPE_FREE && $configType1 === null) ? $configType0 : $configType1)
|
|
||||||
: $configType0;
|
if ($ticketType === self::LOTTERY_TYPE_FREE) {
|
||||||
|
// 免费抽奖券:使用本渠道 name=free 奖池档位权重;无 free 时回退 default
|
||||||
|
$config = $configFree ?? $configType0;
|
||||||
|
$usePoolWeights = true;
|
||||||
|
} elseif ($usePaidKill) {
|
||||||
|
$config = $configKill;
|
||||||
|
$usePoolWeights = true;
|
||||||
|
} else {
|
||||||
|
// 付费未触发杀分:按玩家 T*_weight 抽档,lottery_config_id 记 default
|
||||||
|
$config = $configType0;
|
||||||
|
$usePoolWeights = false;
|
||||||
|
}
|
||||||
|
|
||||||
// 按档位 T1-T5 抽取后,从 DiceReward 表按当前方向取该档位数据,再按 weight 抽取一条得到 grid_number
|
// 按档位 T1-T5 抽取后,从 DiceReward 表按当前方向取该档位数据,再按 weight 抽取一条得到 grid_number
|
||||||
$rewardInstance = DiceReward::getCachedInstance($configDeptId);
|
$rewardInstance = DiceReward::getCachedInstance($configDeptId);
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class DiceRewardController extends BaseController
|
|||||||
* 参数:lottery_config_id 可选;paid_tier_weights / free_tier_weights 自定义档位;
|
* 参数:lottery_config_id 可选;paid_tier_weights / free_tier_weights 自定义档位;
|
||||||
* paid_s_count, paid_n_count
|
* paid_s_count, paid_n_count
|
||||||
* chain_free_mode=1:仅按付费次数模拟;付费抽到再来一次/T5 则在队列中插入免费局(同底注、lottery_type=免费、paid_amount=0)
|
* chain_free_mode=1:仅按付费次数模拟;付费抽到再来一次/T5 则在队列中插入免费局(同底注、lottery_type=免费、paid_amount=0)
|
||||||
* kill_mode_enabled=1:测试内启用杀分;当模拟玩家累计盈利达到 test_safety_line 后,付费抽奖切到 killScore
|
* kill_mode_enabled=1:测试内启用杀分;规则与线上一致(default.profit_amount + safety_line + kill_enabled)
|
||||||
*/
|
*/
|
||||||
#[Permission('一键测试权重', 'dice:reward:index:startWeightTest')]
|
#[Permission('一键测试权重', 'dice:reward:index:startWeightTest')]
|
||||||
public function startWeightTest(Request $request): Response
|
public function startWeightTest(Request $request): Response
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ use support\think\Db;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 一键测试权重:单进程后台执行模拟摇色子,写入 dice_play_record_test 并更新 dice_reward_config_record 进度
|
* 一键测试权重:单进程后台执行模拟摇色子,写入 dice_play_record_test 并更新 dice_reward_config_record 进度
|
||||||
* 支持测试内杀分:当模拟玩家累计盈利达到安全线后,付费抽奖切换到 killScore
|
* 抽奖规则与 PlayStartLogic 一致:
|
||||||
|
* - 付费未杀分:按模拟玩家档位权重抽档,lottery_config_id 记 default
|
||||||
|
* - 付费杀分:default.profit_amount + safety_line + kill_enabled 满足后切 killScore
|
||||||
|
* - 免费券:name=free 奖池(无则 default),排除 5/30 豹子
|
||||||
*/
|
*/
|
||||||
class WeightTestRunner
|
class WeightTestRunner
|
||||||
{
|
{
|
||||||
@@ -72,7 +75,8 @@ class WeightTestRunner
|
|||||||
DiceRewardConfig::clearRequestInstance();
|
DiceRewardConfig::clearRequestInstance();
|
||||||
|
|
||||||
$configType0 = DiceLotteryPoolConfig::findByNameForDept('default', $deptId);
|
$configType0 = DiceLotteryPoolConfig::findByNameForDept('default', $deptId);
|
||||||
$configType1 = DiceLotteryPoolConfig::findByNameForDept('killScore', $deptId);
|
$configKill = DiceLotteryPoolConfig::findByNameForDept('killScore', $deptId);
|
||||||
|
$configFree = DiceLotteryPoolConfig::findByNameForDept('free', $deptId);
|
||||||
if (!$configType0) {
|
if (!$configType0) {
|
||||||
$this->markFailed($recordId, '彩金池配置 name=default 不存在(当前渠道)');
|
$this->markFailed($recordId, '彩金池配置 name=default 不存在(当前渠道)');
|
||||||
return;
|
return;
|
||||||
@@ -92,9 +96,9 @@ class WeightTestRunner
|
|||||||
if (!$paidPoolConfig || AdminScopeHelper::normalizeRecordDeptId($paidPoolConfig->dept_id ?? null) !== $deptId) {
|
if (!$paidPoolConfig || AdminScopeHelper::normalizeRecordDeptId($paidPoolConfig->dept_id ?? null) !== $deptId) {
|
||||||
$paidPoolConfig = $configType0;
|
$paidPoolConfig = $configType0;
|
||||||
}
|
}
|
||||||
$freePoolConfig = $freePoolConfigId > 0 ? DiceLotteryPoolConfig::find($freePoolConfigId) : $configType1;
|
$freePoolConfig = $freePoolConfigId > 0 ? DiceLotteryPoolConfig::find($freePoolConfigId) : $configFree;
|
||||||
if (!$freePoolConfig || AdminScopeHelper::normalizeRecordDeptId($freePoolConfig->dept_id ?? null) !== $deptId) {
|
if (!$freePoolConfig || AdminScopeHelper::normalizeRecordDeptId($freePoolConfig->dept_id ?? null) !== $deptId) {
|
||||||
$freePoolConfig = $configType1 ?: $configType0;
|
$freePoolConfig = $configFree ?: $configType0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($paidTierWeightsCustom !== null && array_sum($paidTierWeightsCustom) <= 0) {
|
if ($paidTierWeightsCustom !== null && array_sum($paidTierWeightsCustom) <= 0) {
|
||||||
@@ -111,13 +115,14 @@ class WeightTestRunner
|
|||||||
DiceReward::clearRequestInstance();
|
DiceReward::clearRequestInstance();
|
||||||
|
|
||||||
$killModeEnabled = (int) ($record->kill_mode_enabled ?? 0) === 1;
|
$killModeEnabled = (int) ($record->kill_mode_enabled ?? 0) === 1;
|
||||||
$testSafetyLine = (int) ($record->test_safety_line ?? 5000);
|
$safetyLine = (int) ($configType0->safety_line ?? 0);
|
||||||
if ($testSafetyLine < 0) {
|
$dbKillEnabled = ((int) ($configType0->kill_enabled ?? 1)) === 1;
|
||||||
$testSafetyLine = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 测试内“玩家累计盈利”:用于控制付费局是否切换杀分
|
// 彩金池累计盈利:与线上一致,从 default.profit_amount 起步并在测试内逐局累加
|
||||||
$playerProfitTotal = 0.0;
|
$poolProfitTotal = (float) ($configType0->profit_amount ?? 0);
|
||||||
|
|
||||||
|
// 付费未杀分时的模拟玩家档位权重(自定义 > 快照 > 兜底奖池)
|
||||||
|
$paidPlayerWeights = $paidTierWeightsCustom ?? $this->resolveTierWeightsSnapshot($record, 'paid');
|
||||||
|
|
||||||
$playLogic = new PlayStartLogic();
|
$playLogic = new PlayStartLogic();
|
||||||
$resultCounts = [];
|
$resultCounts = [];
|
||||||
@@ -133,14 +138,16 @@ class WeightTestRunner
|
|||||||
$paidS,
|
$paidS,
|
||||||
$paidN,
|
$paidN,
|
||||||
$ante,
|
$ante,
|
||||||
|
$configType0,
|
||||||
$paidPoolConfig,
|
$paidPoolConfig,
|
||||||
$freePoolConfig,
|
$freePoolConfig,
|
||||||
$configType1,
|
$configKill,
|
||||||
$paidTierWeightsCustom,
|
$paidPlayerWeights,
|
||||||
$freeTierWeightsCustom,
|
$freeTierWeightsCustom,
|
||||||
$killModeEnabled,
|
$killModeEnabled,
|
||||||
$testSafetyLine,
|
$safetyLine,
|
||||||
$playerProfitTotal,
|
$dbKillEnabled,
|
||||||
|
$poolProfitTotal,
|
||||||
$resultCounts,
|
$resultCounts,
|
||||||
$tierCounts,
|
$tierCounts,
|
||||||
$buffer,
|
$buffer,
|
||||||
@@ -172,14 +179,16 @@ class WeightTestRunner
|
|||||||
int $paidS,
|
int $paidS,
|
||||||
int $paidN,
|
int $paidN,
|
||||||
int $ante,
|
int $ante,
|
||||||
|
$defaultPoolConfig,
|
||||||
$paidPoolConfig,
|
$paidPoolConfig,
|
||||||
$freePoolConfig,
|
$freePoolConfig,
|
||||||
$killPoolConfig,
|
$killPoolConfig,
|
||||||
?array $paidTierWeightsCustom,
|
?array $paidPlayerWeights,
|
||||||
?array $freeTierWeightsCustom,
|
?array $freeTierWeightsCustom,
|
||||||
bool $killModeEnabled,
|
bool $killModeEnabled,
|
||||||
int $testSafetyLine,
|
int $safetyLine,
|
||||||
float &$playerProfitTotal,
|
bool $dbKillEnabled,
|
||||||
|
float &$poolProfitTotal,
|
||||||
array &$resultCounts,
|
array &$resultCounts,
|
||||||
array &$tierCounts,
|
array &$tierCounts,
|
||||||
array &$buffer,
|
array &$buffer,
|
||||||
@@ -201,13 +210,21 @@ class WeightTestRunner
|
|||||||
$lotteryType = $isPaid ? 0 : 1;
|
$lotteryType = $isPaid ? 0 : 1;
|
||||||
|
|
||||||
if ($isPaid) {
|
if ($isPaid) {
|
||||||
$useKillForPaid = $killModeEnabled && $playerProfitTotal >= $testSafetyLine && $killPoolConfig !== null;
|
$useKillForPaid = $killModeEnabled
|
||||||
|
&& $dbKillEnabled
|
||||||
|
&& $poolProfitTotal >= $safetyLine
|
||||||
|
&& $killPoolConfig !== null;
|
||||||
if ($useKillForPaid) {
|
if ($useKillForPaid) {
|
||||||
$cfg = $killPoolConfig;
|
$cfg = $killPoolConfig;
|
||||||
$customWeights = null;
|
$customWeights = null;
|
||||||
} else {
|
} else {
|
||||||
$cfg = $paidPoolConfig;
|
// 付费未杀分:模拟玩家档位权重,lottery_config_id 记 default(与 PlayStartLogic 一致)
|
||||||
$customWeights = $paidTierWeightsCustom;
|
$cfg = $defaultPoolConfig;
|
||||||
|
$customWeights = $paidPlayerWeights;
|
||||||
|
if ($customWeights === null) {
|
||||||
|
$cfg = $paidPoolConfig;
|
||||||
|
$customWeights = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$cfg = $freePoolConfig;
|
$cfg = $freePoolConfig;
|
||||||
@@ -217,7 +234,8 @@ class WeightTestRunner
|
|||||||
$row = $playLogic->simulateOnePlay($cfg, $dir, $lotteryType, $playAnte, $customWeights, $deptId);
|
$row = $playLogic->simulateOnePlay($cfg, $dir, $lotteryType, $playAnte, $customWeights, $deptId);
|
||||||
$winCoin = (float) ($row['win_coin'] ?? 0);
|
$winCoin = (float) ($row['win_coin'] ?? 0);
|
||||||
$paidAmount = (float) ($row['paid_amount'] ?? 0);
|
$paidAmount = (float) ($row['paid_amount'] ?? 0);
|
||||||
$playerProfitTotal += $winCoin - $paidAmount;
|
$perPlayProfit = $isPaid ? ($winCoin - $paidAmount) : $winCoin;
|
||||||
|
$poolProfitTotal += round($perPlayProfit, 2);
|
||||||
$this->aggregate($row, $resultCounts, $tierCounts);
|
$this->aggregate($row, $resultCounts, $tierCounts);
|
||||||
$buffer[] = $this->rowForInsert($row, $recordId, $deptId);
|
$buffer[] = $this->rowForInsert($row, $recordId, $deptId);
|
||||||
$done++;
|
$done++;
|
||||||
@@ -232,6 +250,23 @@ class WeightTestRunner
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 tier_weights_snapshot 读取付费/免费档位权重快照
|
||||||
|
*/
|
||||||
|
private function resolveTierWeightsSnapshot(DiceRewardConfigRecord $record, string $side): ?array
|
||||||
|
{
|
||||||
|
$snap = $record->tier_weights_snapshot ?? null;
|
||||||
|
if (! is_array($snap)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$weights = $snap[$side] ?? null;
|
||||||
|
if (! is_array($weights) || $weights === []) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $weights;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析本次测试渠道:优先读库字段,避免 ORM 字段缓存未含 dept_id 时读不到
|
* 解析本次测试渠道:优先读库字段,避免 ORM 字段缓存未含 dept_id 时读不到
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user