big test rc
This commit is contained in:
+82
-12
@@ -9,8 +9,9 @@ export const MATCH_ENTRY_FEE_RC = 21;
|
||||
|
||||
const SETTINGS_KEY_ENTRY_FEE = 'entry_fee';
|
||||
const SETTINGS_KEY_BET_FEE = 'bet_fee';
|
||||
const SYSTEM_ACCOUNT_ID = 1;
|
||||
|
||||
/** Fallback when `public.settings` has no `bet_fee` row or value is invalid (percent of prize pool taken as server fee). */
|
||||
/** Fallback when `public.settings` has no `bet_fee` row or value is invalid (percent applied to each player's entry). */
|
||||
export const DEFAULT_BET_FEE_PERCENT = 10;
|
||||
|
||||
/** RC entry fee for normal matchmaking (not rematches): `public.settings` row `key = entry_fee`, `value` integer string. */
|
||||
@@ -30,7 +31,22 @@ export async function getDefaultMatchEntryFeeRc(settings: Settings): Promise<num
|
||||
return n;
|
||||
}
|
||||
|
||||
/** Server fee percent of the prize pool (`entry_fee * 2`): `public.settings` row `key = bet_fee`, `value` integer 0–100. */
|
||||
/** All rows from `public.settings` as key → value strings. */
|
||||
export async function getSettingsTableDict(settings: Settings): Promise<Record<string, string>> {
|
||||
if (!supabaseConfigured(settings)) return {};
|
||||
const supabase = createSupabase(settings);
|
||||
const { data, error } = await supabase.from('settings').select('key, value');
|
||||
if (error || !data) return {};
|
||||
const out: Record<string, string> = {};
|
||||
for (const row of data as { key?: unknown; value?: unknown }[]) {
|
||||
if (typeof row.key === 'string' && typeof row.value === 'string') {
|
||||
out[row.key] = row.value;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Server fee percent of each player's entry (`bet_fee`): `public.settings` row `key = bet_fee`, `value` integer 0–100. */
|
||||
export async function getBetFeePercentFromSettings(settings: Settings): Promise<number> {
|
||||
if (!supabaseConfigured(settings)) return DEFAULT_BET_FEE_PERCENT;
|
||||
const supabase = createSupabase(settings);
|
||||
@@ -54,13 +70,13 @@ export type InsertMatchForNewRoomResult = {
|
||||
};
|
||||
|
||||
/**
|
||||
* `prize_pool = entry_fee * 2`, `server_fee = ceil(prize_pool * betFeePercent / 100)`, returns `prize_pool - server_fee`.
|
||||
* `betFeePercent` comes from `public.settings` key `bet_fee` (default {@link DEFAULT_BET_FEE_PERCENT}).
|
||||
* Winner's net RC = both entries minus per-player fees.
|
||||
* `fee_each = ceil(entry_fee * betFeePercent / 100)`, prize = `(entry_fee - fee_each) * 2` = `entry_fee * 2 - fee_each * 2`.
|
||||
* Matches ledger rows: each player pays `fee_each` to system (`entry_fee`) and holds `entry_fee - fee_each`.
|
||||
*/
|
||||
export function computeRcPrizeFromEntryFee(entryFee: number, betFeePercent: number = DEFAULT_BET_FEE_PERCENT): number {
|
||||
const prizePool = entryFee * 2;
|
||||
const serverFee = Math.ceil((prizePool * betFeePercent) / 100);
|
||||
return prizePool - serverFee;
|
||||
const feeEach = Math.ceil((entryFee * betFeePercent) / 100);
|
||||
return entryFee * 2 - feeEach * 2;
|
||||
}
|
||||
/** Added to each player's CC when a winner is recorded (default if `matches.prize_cc` is null). */
|
||||
const MATCH_PARTICIPANT_CC_REWARD = 100;
|
||||
@@ -211,11 +227,12 @@ function coerceNonNegativeInt(v: unknown): number | null {
|
||||
}
|
||||
|
||||
/**
|
||||
* Each player pays `entry_fee` RC; winner receives RC prize after `betFeePercent` server cut; each gets `prizeCc` CC.
|
||||
* Each player pays `entry_fee` RC; winner receives net RC after `betFeePercent` taken from **each** entry; each gets `prizeCc` CC.
|
||||
* Idempotent callers should set winner only once.
|
||||
*/
|
||||
async function applyMatchWinnerEconomy(
|
||||
supabase: SupabaseClient,
|
||||
matchId: number,
|
||||
userRed: number | null,
|
||||
userBlue: number | null,
|
||||
winnerId: number,
|
||||
@@ -228,21 +245,73 @@ async function applyMatchWinnerEconomy(
|
||||
|
||||
const rcPrize = computeRcPrizeFromEntryFee(entryFee, betFeePercent);
|
||||
|
||||
const { data: rows, error: selErr } = await supabase.from('users').select('id, rc, cc').in('id', ids);
|
||||
const lookupIds = [...new Set([...ids, SYSTEM_ACCOUNT_ID])];
|
||||
const { data: rows, error: selErr } = await supabase.from('users').select('id, rc, cc').in('id', lookupIds);
|
||||
if (selErr) return selErr.message;
|
||||
if (!rows || rows.length !== ids.length) return 'One or more players not found in users';
|
||||
if (!rows || rows.length !== lookupIds.length) return 'One or more users not found in users';
|
||||
|
||||
const byId = new Map<number, { rc: number; cc: number }>();
|
||||
for (const row of rows) {
|
||||
const uid = coerceId((row as { id: unknown }).id);
|
||||
if (uid == null) continue;
|
||||
const baseRc = Number((row as { rc: unknown }).rc ?? 0);
|
||||
const baseCc = Number((row as { cc: unknown }).cc ?? 0);
|
||||
byId.set(uid, {
|
||||
rc: Number((row as { rc: unknown }).rc ?? 0),
|
||||
cc: Number((row as { cc: unknown }).cc ?? 0),
|
||||
});
|
||||
}
|
||||
if (!byId.has(SYSTEM_ACCOUNT_ID)) return 'System account not found';
|
||||
|
||||
let totalEntryCollected = 0;
|
||||
|
||||
for (const uid of ids) {
|
||||
const base = byId.get(uid);
|
||||
if (!base) return `Player ${uid} not found in users`;
|
||||
const baseRc = base.rc;
|
||||
const baseCc = base.cc;
|
||||
let newRc = baseRc - entryFee;
|
||||
if (uid === winnerId) newRc += rcPrize;
|
||||
const newCc = baseCc + prizeCc;
|
||||
const { error: upErr } = await supabase.from('users').update({ rc: newRc, cc: newCc }).eq('id', uid);
|
||||
if (upErr) return upErr.message;
|
||||
totalEntryCollected += entryFee;
|
||||
}
|
||||
|
||||
const system = byId.get(SYSTEM_ACCOUNT_ID)!;
|
||||
const systemNewRc = system.rc + totalEntryCollected - rcPrize;
|
||||
const { error: systemUpErr } = await supabase
|
||||
.from('users')
|
||||
.update({ rc: systemNewRc })
|
||||
.eq('id', SYSTEM_ACCOUNT_ID);
|
||||
if (systemUpErr) return systemUpErr.message;
|
||||
|
||||
const perPlayerBetFee = Math.ceil((entryFee * betFeePercent) / 100);
|
||||
const perPlayerHold = entryFee - perPlayerBetFee;
|
||||
const txRows: Array<{ from: number; to: number; amount: number; remarks: string; match_id: number }> = [];
|
||||
for (const uid of ids) {
|
||||
txRows.push({
|
||||
from: uid,
|
||||
to: SYSTEM_ACCOUNT_ID,
|
||||
amount: perPlayerHold,
|
||||
remarks: 'entry_hold',
|
||||
match_id: matchId,
|
||||
});
|
||||
txRows.push({
|
||||
from: uid,
|
||||
to: SYSTEM_ACCOUNT_ID,
|
||||
amount: perPlayerBetFee,
|
||||
remarks: 'entry_fee',
|
||||
match_id: matchId,
|
||||
});
|
||||
}
|
||||
txRows.push({
|
||||
from: SYSTEM_ACCOUNT_ID,
|
||||
to: winnerId,
|
||||
amount: rcPrize,
|
||||
remarks: 'reward',
|
||||
match_id: matchId,
|
||||
});
|
||||
const { error: txErr } = await supabase.from('transactions').insert(txRows);
|
||||
if (txErr) return txErr.message;
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -521,6 +590,7 @@ export function registerMatchRoutes(
|
||||
|
||||
const payErr = await applyMatchWinnerEconomy(
|
||||
supabase,
|
||||
matchId,
|
||||
userRed,
|
||||
userBlue,
|
||||
winnerId,
|
||||
|
||||
Reference in New Issue
Block a user