settings
This commit is contained in:
+80
-14
@@ -4,13 +4,62 @@ import type { SupabaseClient } from '@supabase/supabase-js';
|
||||
import { Room, Settings } from './types';
|
||||
import { createSupabase, supabaseConfigured } from './auth_bridge';
|
||||
|
||||
/** Per player on the match: deducted when a winner is recorded. */
|
||||
/** Fallback when `public.settings` has no `entry_fee` row or value is invalid. */
|
||||
export const MATCH_ENTRY_FEE_RC = 21;
|
||||
|
||||
/** `prize_pool = entry_fee * 2`, `server_fee = ceil(prize_pool / 10)`, returns `prize_pool - server_fee`. */
|
||||
export function computeRcPrizeFromEntryFee(entryFee: number): number {
|
||||
const SETTINGS_KEY_ENTRY_FEE = 'entry_fee';
|
||||
const SETTINGS_KEY_BET_FEE = 'bet_fee';
|
||||
|
||||
/** Fallback when `public.settings` has no `bet_fee` row or value is invalid (percent of prize pool taken as server fee). */
|
||||
export const DEFAULT_BET_FEE_PERCENT = 10;
|
||||
|
||||
/** RC entry fee for normal matchmaking (not rematches): `public.settings` row `key = entry_fee`, `value` integer string. */
|
||||
export async function getDefaultMatchEntryFeeRc(settings: Settings): Promise<number> {
|
||||
if (!supabaseConfigured(settings)) return MATCH_ENTRY_FEE_RC;
|
||||
const supabase = createSupabase(settings);
|
||||
const { data, error } = await supabase
|
||||
.from('settings')
|
||||
.select('value')
|
||||
.eq('key', SETTINGS_KEY_ENTRY_FEE)
|
||||
.maybeSingle();
|
||||
if (error || !data || typeof (data as { value?: unknown }).value !== 'string') {
|
||||
return MATCH_ENTRY_FEE_RC;
|
||||
}
|
||||
const n = parseInt(String((data as { value: string }).value).trim(), 10);
|
||||
if (!Number.isInteger(n) || n < 0) return MATCH_ENTRY_FEE_RC;
|
||||
return n;
|
||||
}
|
||||
|
||||
/** Server fee percent of the prize pool (`entry_fee * 2`): `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);
|
||||
const { data, error } = await supabase
|
||||
.from('settings')
|
||||
.select('value')
|
||||
.eq('key', SETTINGS_KEY_BET_FEE)
|
||||
.maybeSingle();
|
||||
if (error || !data || typeof (data as { value?: unknown }).value !== 'string') {
|
||||
return DEFAULT_BET_FEE_PERCENT;
|
||||
}
|
||||
const n = parseInt(String((data as { value: string }).value).trim(), 10);
|
||||
if (!Number.isInteger(n) || n < 0 || n > 100) return DEFAULT_BET_FEE_PERCENT;
|
||||
return n;
|
||||
}
|
||||
|
||||
export type InsertMatchForNewRoomResult = {
|
||||
matchId: number | null;
|
||||
/** Same RC fee stored on `matches.entry_fee` and returned for in-memory rooms. */
|
||||
entry_fee: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* `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}).
|
||||
*/
|
||||
export function computeRcPrizeFromEntryFee(entryFee: number, betFeePercent: number = DEFAULT_BET_FEE_PERCENT): number {
|
||||
const prizePool = entryFee * 2;
|
||||
const serverFee = Math.ceil(prizePool / 10);
|
||||
const serverFee = Math.ceil((prizePool * betFeePercent) / 100);
|
||||
return prizePool - serverFee;
|
||||
}
|
||||
/** Added to each player's CC when a winner is recorded (default if `matches.prize_cc` is null). */
|
||||
@@ -28,17 +77,23 @@ function coerceId(id: unknown): number | null {
|
||||
}
|
||||
|
||||
/** Insert a match row when a new room is created. First player in the room = red (if provided). */
|
||||
export async function insertMatchForNewRoom(settings: Settings, userRedId?: number): Promise<number | null> {
|
||||
if (!supabaseConfigured(settings)) return null;
|
||||
export async function insertMatchForNewRoom(
|
||||
settings: Settings,
|
||||
userRedId?: number
|
||||
): Promise<InsertMatchForNewRoomResult> {
|
||||
const entry_fee = await getDefaultMatchEntryFeeRc(settings);
|
||||
if (!supabaseConfigured(settings)) {
|
||||
return { matchId: null, entry_fee };
|
||||
}
|
||||
const supabase = createSupabase(settings);
|
||||
const base = { entry_fee: MATCH_ENTRY_FEE_RC };
|
||||
const base = { entry_fee };
|
||||
const payload = userRedId == null ? base : { ...base, user_red: userRedId };
|
||||
const { data, error } = await supabase.from('matches').insert(payload).select('id').single();
|
||||
if (error || !data) {
|
||||
console.error('matches: insert failed', error?.message);
|
||||
return null;
|
||||
return { matchId: null, entry_fee };
|
||||
}
|
||||
return coerceId(data.id);
|
||||
return { matchId: coerceId(data.id), entry_fee };
|
||||
}
|
||||
|
||||
/** Dedicated server: rematch row with both players and custom `entry_fee` (RC). */
|
||||
@@ -156,7 +211,7 @@ function coerceNonNegativeInt(v: unknown): number | null {
|
||||
}
|
||||
|
||||
/**
|
||||
* Each player pays `entry_fee` RC; winner receives `computeRcPrizeFromEntryFee(entry_fee)` RC; each gets `prizeCc` CC.
|
||||
* Each player pays `entry_fee` RC; winner receives RC prize after `betFeePercent` server cut; each gets `prizeCc` CC.
|
||||
* Idempotent callers should set winner only once.
|
||||
*/
|
||||
async function applyMatchWinnerEconomy(
|
||||
@@ -165,12 +220,13 @@ async function applyMatchWinnerEconomy(
|
||||
userBlue: number | null,
|
||||
winnerId: number,
|
||||
entryFee: number,
|
||||
prizeCc: number
|
||||
prizeCc: number,
|
||||
betFeePercent: number
|
||||
): Promise<string | null> {
|
||||
const ids = [...new Set([userRed, userBlue].filter((x): x is number => x != null && x >= 1))];
|
||||
if (ids.length === 0) return null;
|
||||
|
||||
const rcPrize = computeRcPrizeFromEntryFee(entryFee);
|
||||
const rcPrize = computeRcPrizeFromEntryFee(entryFee, betFeePercent);
|
||||
|
||||
const { data: rows, error: selErr } = await supabase.from('users').select('id, rc, cc').in('id', ids);
|
||||
if (selErr) return selErr.message;
|
||||
@@ -439,6 +495,8 @@ export function registerMatchRoutes(
|
||||
return;
|
||||
}
|
||||
|
||||
const betFeePercent = await getBetFeePercentFromSettings(settings);
|
||||
|
||||
const { data: updated, error: updErr } = await supabase
|
||||
.from('matches')
|
||||
.update({ winner_id: winnerId })
|
||||
@@ -461,7 +519,15 @@ export function registerMatchRoutes(
|
||||
return;
|
||||
}
|
||||
|
||||
const payErr = await applyMatchWinnerEconomy(supabase, userRed, userBlue, winnerId, entryFee, prizeCc);
|
||||
const payErr = await applyMatchWinnerEconomy(
|
||||
supabase,
|
||||
userRed,
|
||||
userBlue,
|
||||
winnerId,
|
||||
entryFee,
|
||||
prizeCc,
|
||||
betFeePercent
|
||||
);
|
||||
if (payErr != null) {
|
||||
console.error('matches: economy after winner failed', matchId, payErr);
|
||||
await supabase.from('matches').update({ winner_id: null }).eq('id', matchId).eq('winner_id', winnerId);
|
||||
@@ -469,7 +535,7 @@ export function registerMatchRoutes(
|
||||
return;
|
||||
}
|
||||
|
||||
const rcPrize = computeRcPrizeFromEntryFee(entryFee);
|
||||
const rcPrize = computeRcPrizeFromEntryFee(entryFee, betFeePercent);
|
||||
res.json({
|
||||
ok: true,
|
||||
id: matchId,
|
||||
|
||||
Reference in New Issue
Block a user