horseRacing
- community
- minigames
- by Limon
- Takaro main
- all
A virtual horse racing system where players can bet and win currency.
Configuration 3
Settings you fill in when installing the module on a server.
| Setting | Type | Default | Description |
|---|---|---|---|
minBet minBet | number | 50 | Minimum amount required to place a bet |
maxBet maxBet | number | 1000 | Maximum amount allowed for a bet |
Horses Horses | array | ["Thunder; 2","Lightning; 3","Shadow; 3","Blaze; 4","Seacove; 5","Arrow; 5","Dark Knight; 6"] | List of horses that can race. Format: HorseName; Odds (whole number) - one horse per line |
Raw config schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": [],
"additionalProperties": false,
"properties": {
"minBet": {
"title": "minBet",
"description": "Minimum amount required to place a bet",
"default": 50,
"type": "number",
"minimum": 1
},
"maxBet": {
"title": "maxBet",
"description": "Maximum amount allowed for a bet",
"default": 1000,
"type": "number",
"minimum": 1
},
"Horses": {
"title": "Horses",
"description": "List of horses that can race. Format: HorseName; Odds (whole number) - one horse per line",
"default": [
"Thunder; 2",
"Lightning; 3",
"Shadow; 3",
"Blaze; 4",
"Seacove; 5",
"Arrow; 5",
"Dark Knight; 6"
],
"type": "array",
"items": {
"type": "string"
}
}
}
} Raw UI schema
{} Commands 8
Chat commands players trigger in game.
-
horses
No help text available
Command source
import { data, TakaroUserError } from '@takaro/helpers'; import { parseHorses, getRaceData, getTimeUntilRace } from './utils.js'; async function main() { try { const { player, module: mod, gameServerId } = data; // Parse horses using the utility function const horses = parseHorses(mod.userConfig); if (horses.length === 0) { throw new TakaroUserError('No horses are configured for racing!'); } // Get current race info const raceData = await getRaceData(gameServerId, mod.moduleId); const timeUntil = getTimeUntilRace(raceData.nextRaceTime); await player.pm('🏇 HORSE RACING INFORMATION 🏇'); await player.pm('═══════════════════════════════════'); await player.pm(`🏁 Next Race: #${raceData.raceNumber} in ${timeUntil}`); await player.pm(`🎫 Current Bets: ${raceData.bets.length}`); await player.pm('🐎 AVAILABLE HORSES:'); // Show horses with enhanced info for (let i = 0; i < horses.length; i++) { const horse = horses[i]; const betsOnHorse = raceData.bets.filter(bet => bet.horse.toLowerCase() === horse.name.toLowerCase()).length; let horseLine = ` ${i + 1}. ${horse.name} - ${horse.odds}:1 odds`; if (betsOnHorse > 0) { horseLine += ` (${betsOnHorse} bet${betsOnHorse !== 1 ? 's' : ''})`; } // Add performance indicator if (horse.odds <= 2) { horseLine += ' ⭐ Favorite'; } else if (horse.odds >= 6) { horseLine += ' 🎯 Longshot'; } await player.pm(horseLine); } const minBet = mod.userConfig?.minBet || 50; const maxBet = mod.userConfig?.maxBet || 1000; await player.pm('📋 BETTING INFORMATION:'); await player.pm(`💰 Bet Range: ${minBet} - ${maxBet} currency`); await player.pm('🎯 Command: /horsebet <horse> <amount>'); await player.pm('📊 Example: /horsebet Thunder 100'); // Show current player's bets if any const playerBets = raceData.bets.filter(bet => bet.playerId === player.id); if (playerBets.length > 0) { await player.pm('🎫 YOUR CURRENT BETS:'); for (const bet of playerBets) { const potentialWin = Math.floor(bet.amount * bet.odds); await player.pm(` ${bet.horse}: ${bet.amount} → potential win: ${potentialWin}`); } } // Show helpful tips await player.pm('💡 TIP: Lower odds = higher chance to win, but lower payout'); await player.pm('🎰 Special jackpot races trigger randomly for extra prizes!'); } catch (error) { if (error instanceof TakaroUserError) { throw error; } console.log('Error in horses command:', error); throw new TakaroUserError('Unable to load horse information. Please try again.'); } } await main(); -
horseBet
Name of the horse you want to bet on
Argument Type Default Help horsestring Name of the horse you want to bet on amountstring Amount of currency to bet Command source
import { takaro, data, checkPermission, TakaroUserError } from '@takaro/helpers'; import { parseHorses, getRaceData } from './utils.js'; async function main() { const { player, gameServerId, module: mod, pog, arguments: args } = data; // Check permission if (!checkPermission(pog, 'HORSE_RACING_BET')) { throw new TakaroUserError('You do not have permission to place bets.'); } // Validate arguments if (!args.horse) { throw new TakaroUserError('Please specify the horse name you want to bet on.'); } const betAmount = parseInt(args.amount); const minBet = mod.userConfig?.minBet || 50; const maxBet = mod.userConfig?.maxBet || 1000; if (!betAmount || betAmount < minBet || betAmount > maxBet) { throw new TakaroUserError(`Please specify a valid amount to bet (min: ${minBet}, max: ${maxBet}).`); } try { // Get horse list using utility function const horses = parseHorses(mod.userConfig); // Find the horse const horseName = args.horse.toLowerCase().trim(); const horseMatch = horses.find(h => h.name.toLowerCase() === horseName); if (!horseMatch) { const availableHorses = horses.map(h => h.name).join(', '); throw new TakaroUserError(`Horse "${args.horse}" not found. Available horses: ${availableHorses}`); } // Check if player has enough currency const playerData = await takaro.playerOnGameserver.playerOnGameServerControllerGetOne(gameServerId, player.id); const balance = playerData.data.data.currency; if (balance < betAmount) { throw new TakaroUserError(`You don't have enough currency. Your balance: ${balance}`); } // Get race data using utility function const raceData = await getRaceData(gameServerId, mod.moduleId); // Check if player already has a bet const existingBetIndex = raceData.bets.findIndex(bet => bet.playerId === player.id); if (existingBetIndex >= 0) { // Check if it's on the same horse if (raceData.bets[existingBetIndex].horse.toLowerCase() === horseMatch.name.toLowerCase()) { throw new TakaroUserError(`You already have a bet on ${horseMatch.name}. Use /myhorsebets to see your bets.`); } // Refund previous bet const oldBet = raceData.bets[existingBetIndex]; await takaro.playerOnGameserver.playerOnGameServerControllerAddCurrency( gameServerId, player.id, { currency: oldBet.amount } ); // Remove from array raceData.bets.splice(existingBetIndex, 1); await player.pm(`Refunded your previous bet of ${oldBet.amount} on ${oldBet.horse}`); } // Deduct currency from player await takaro.playerOnGameserver.playerOnGameServerControllerDeductCurrency( gameServerId, player.id, { currency: betAmount } ); // Create new bet raceData.bets.push({ playerId: player.id, playerName: player.name, horse: horseMatch.name, amount: betAmount, odds: horseMatch.odds, placedAt: Date.now() }); // Update race data const currentRace = await takaro.variable.variableControllerSearch({ filters: { key: ['current_race'], gameServerId: [gameServerId], moduleId: [mod.moduleId], }, }); await takaro.variable.variableControllerUpdate(currentRace.data.data[0].id, { value: JSON.stringify(raceData), }); // Calculate potential winnings const potentialWin = Math.floor(betAmount * horseMatch.odds); await player.pm(`✅ Bet placed: ${betAmount} on ${horseMatch.name} (${horseMatch.odds}:1 odds)`); await player.pm(`💰 Potential winnings: ${potentialWin}`); // Show time until race const timeUntilRace = Math.max(0, raceData.nextRaceTime - Date.now()); if (timeUntilRace > 0) { const minutesUntilRace = Math.floor(timeUntilRace / 60000); if (minutesUntilRace > 0) { await player.pm(`⏰ Next race (#${raceData.raceNumber}) in approximately ${minutesUntilRace} minutes`); } else { await player.pm(`⏰ Next race (#${raceData.raceNumber}) starting soon!`); } } // Show total bets for this race await player.pm(`📊 Total bets for this race: ${raceData.bets.length}`); } catch (error) { if (error instanceof TakaroUserError) { throw error; } console.log('Error in horsebet command:', error); throw new TakaroUserError('Something went wrong placing your bet. Please try again.'); } } await main(); -
mybets
No help text available
Command source
import { data, TakaroUserError } from '@takaro/helpers'; import { getRaceData } from './utils.js'; async function main() { try { const { player, module: mod, gameServerId } = data; const raceData = await getRaceData(gameServerId, mod.moduleId); // Check if player has bets const playerBets = raceData.bets.filter(bet => bet.playerId === player.id); if (playerBets.length === 0) { await player.pm('You have not placed any bets for the upcoming race.'); await player.pm('Use /horsebet <horse> <amount> to place a bet!'); return; } await player.pm(`🏇 Your bets for Race #${raceData.raceNumber}:`); let totalPotential = 0; for (const bet of playerBets) { const potential = Math.floor(bet.amount * bet.odds); totalPotential += potential; await player.pm(`${bet.horse} - ${bet.amount} (potential win: ${potential})`); } await player.pm(`Total potential winnings: ${totalPotential}`); } catch (error) { console.log('Error in myBets command:', error); throw new TakaroUserError('Something went wrong. Please try again.'); } } await main(); -
startrace
Immediately starts a horse race with current bets
Command source
import { takaro, data, checkPermission, TakaroUserError } from '@takaro/helpers'; import { parseHorses, simulateRace } from './utils.js'; async function main() { const { gameServerId, module: mod, player, pog } = data; // Check admin permission if (!checkPermission(pog, 'HORSE_RACING_ADMIN')) { throw new TakaroUserError('You need admin permissions to manually start races.'); } try { // Get race data const currentRace = await takaro.variable.variableControllerSearch({ filters: { key: ['current_race'], gameServerId: [gameServerId], moduleId: [mod.moduleId], }, }); let raceData; if (currentRace.data.data.length === 0) { // Initialize race data if it doesn't exist raceData = { nextRaceTime: Date.now() + 3600000, bets: [], lastRaceResults: null, raceNumber: 1, state: 'waiting' }; await takaro.variable.variableControllerCreate({ key: 'current_race', value: JSON.stringify(raceData), gameServerId, moduleId: mod.moduleId, }); } else { raceData = JSON.parse(currentRace.data.data[0].value); } // Check if no bets if (!raceData.bets || raceData.bets.length === 0) { throw new TakaroUserError('Cannot start race - no bets have been placed!'); } // Parse horses and simulate race const horses = parseHorses(mod.userConfig); const raceResults = simulateRace(horses); // Store race results raceData.results = raceResults; raceData.state = 'complete'; // Get winner const winningHorse = raceResults[0].name; const winningBets = raceData.bets.filter(bet => bet.horse.toLowerCase() === winningHorse.toLowerCase()); // Announce race and results immediately await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message: `🏇 MANUAL RACE #${raceData.raceNumber} - The horses are off!` }); // Small delay for drama await new Promise(resolve => setTimeout(resolve, 3000)); await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message: `🏆 ${winningHorse} WINS Race #${raceData.raceNumber}!` }); // Process winnings let totalPayout = 0; if (winningBets.length > 0) { for (const bet of winningBets) { const winnings = Math.floor(bet.amount * bet.odds); totalPayout += winnings; try { await takaro.playerOnGameserver.playerOnGameServerControllerAddCurrency( gameServerId, bet.playerId, { currency: winnings } ); } catch (error) { console.log(`Error processing win for player ${bet.playerName}:`, error); } } await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message: `💰 ${winningBets.length} winner${winningBets.length !== 1 ? 's' : ''} received ${totalPayout} total currency!` }); } else { await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message: "💸 No one bet on the winning horse. House wins!" }); } // Save race results for history raceData.lastRaceResults = { raceNumber: raceData.raceNumber, results: raceResults, winners: winningBets, totalPayout: totalPayout, timestamp: Date.now() }; // Setup next race raceData.raceNumber++; raceData.nextRaceTime = Date.now() + (2 * 60 * 60 * 1000); raceData.bets = []; raceData.state = 'waiting'; // Update race data if (currentRace.data.data.length === 0) { await takaro.variable.variableControllerCreate({ key: 'current_race', value: JSON.stringify(raceData), gameServerId, moduleId: mod.moduleId, }); } else { await takaro.variable.variableControllerUpdate(currentRace.data.data[0].id, { value: JSON.stringify(raceData), }); } await player.pm(`Race #${raceData.raceNumber - 1} completed successfully. Next race scheduled for 2 hours from now.`); } catch (error) { if (error instanceof TakaroUserError) { throw error; } console.log('Error in manual startrace:', error); throw new TakaroUserError('Failed to start race. Check logs for details.'); } } await main(); -
horseLeaderboard
View top bettors by winnings and win rate
Command source
import { takaro, data, TakaroUserError } from '@takaro/helpers'; async function main() { const { player, gameServerId, module: mod } = data; try { // Get all player statistics const statsSearch = await takaro.variable.variableControllerSearch({ filters: { key: ['horse_racing_stats'], gameServerId: [gameServerId], moduleId: [mod.moduleId], }, }); if (statsSearch.data.data.length === 0) { throw new TakaroUserError('No betting statistics available yet. Place some bets and race to build up the leaderboard!'); } // Parse all player stats const allStats = []; for (const statVar of statsSearch.data.data) { try { const stats = JSON.parse(statVar.value); const playerInfo = await takaro.playerOnGameserver.playerOnGameServerControllerGetOne(gameServerId, statVar.playerId); allStats.push({ playerId: statVar.playerId, playerName: playerInfo.data.data.player.name, totalWinnings: stats.totalWinnings || 0, totalBets: stats.totalBets || 0, totalWagered: stats.totalWagered || 0, wins: stats.wins || 0, losses: stats.losses || 0, biggestWin: stats.biggestWin || 0, favoriteHorse: stats.favoriteHorse || 'None' }); } catch (error) { console.log('Error parsing player stats:', error); continue; } } if (allStats.length === 0) { throw new TakaroUserError('No valid betting statistics found.'); } // Sort by total winnings (descending) allStats.sort((a, b) => b.totalWinnings - a.totalWinnings); await player.pm('🏆 HORSE RACING LEADERBOARD 🏆'); await player.pm('═══════════════════════════════'); // Show top 10 const topPlayers = allStats.slice(0, 10); for (let i = 0; i < topPlayers.length; i++) { const p = topPlayers[i]; const winRate = p.totalBets > 0 ? Math.round((p.wins / p.totalBets) * 100) : 0; const roi = p.totalWagered > 0 ? Math.round(((p.totalWinnings - p.totalWagered) / p.totalWagered) * 100) : 0; await player.pm(`${i + 1}. ${p.playerName}`); await player.pm(` 💰 Net: ${p.totalWinnings - p.totalWagered} | Wins: ${p.wins}/${p.totalBets} (${winRate}%)`); if (i < 3) { // Show extra details for top 3 await player.pm(` 🎯 ROI: ${roi}% | Biggest Win: ${p.biggestWin} | Fav: ${p.favoriteHorse}`); } } // Show current player's position if not in top 10 const playerStats = allStats.find(s => s.playerId === player.id); if (playerStats) { const playerRank = allStats.findIndex(s => s.playerId === player.id) + 1; if (playerRank > 10) { const winRate = playerStats.totalBets > 0 ? Math.round((playerStats.wins / playerStats.totalBets) * 100) : 0; await player.pm(`Your rank: #${playerRank}`); await player.pm(`Your stats: ${playerStats.wins}/${playerStats.totalBets} wins (${winRate}%) | Net: ${playerStats.totalWinnings - playerStats.totalWagered}`); } } else { await player.pm('You haven\'t placed any bets yet! Use /horsebet to get started.'); } } catch (error) { if (error instanceof TakaroUserError) { throw error; } console.log('Error in horse leaderboard:', error); throw new TakaroUserError('Failed to load leaderboard. Please try again.'); } } await main(); -
nextRace
No help text available
Command source
import { data, TakaroUserError } from '@takaro/helpers'; import { getRaceData, getTimeUntilRace } from './utils.js'; async function main() { try { const { player, module: mod, gameServerId } = data; const raceData = await getRaceData(gameServerId, mod.moduleId); const timeUntil = getTimeUntilRace(raceData.nextRaceTime); await player.pm(`🏇 Race #${raceData.raceNumber} will begin in ${timeUntil}.`); await player.pm(`${raceData.bets.length} bet${raceData.bets.length !== 1 ? 's' : ''} have been placed so far.`); // Check if player has bets const playerBets = raceData.bets.filter(bet => bet.playerId === player.id); if (playerBets.length > 0) { await player.pm('Your bets:'); for (const bet of playerBets) { await player.pm(`${bet.horse} - ${bet.amount} (potential win: ${Math.floor(bet.amount * bet.odds)})`); } } else { await player.pm('You have not placed any bets yet. Use /horsebet <horse> <amount> to place a bet!'); } } catch (error) { console.log('Error in nextRace command:', error); throw new TakaroUserError('Something went wrong. Please try again.'); } } await main(); -
lastRace
No help text available
Command source
import { data, TakaroUserError } from '@takaro/helpers'; import { getRaceData } from './utils.js'; async function main() { try { const { player, module: mod, gameServerId } = data; const raceData = await getRaceData(gameServerId, mod.moduleId); if (!raceData.lastRaceResults) { throw new TakaroUserError('No previous race results found. Wait for a race to finish!'); } const results = raceData.lastRaceResults; const raceDate = new Date(results.timestamp).toLocaleString(); await player.pm(`🏇 RACE #${results.raceNumber} RESULTS`); await player.pm('═══════════════════════════════'); await player.pm(`📅 Completed: ${raceDate}`); // Show top 5 finishing positions const topHorses = results.results.slice(0, Math.min(5, results.results.length)); await player.pm('🏁 FINAL STANDINGS:'); for (let i = 0; i < topHorses.length; i++) { const position = i + 1; const medal = position === 1 ? '🥇' : position === 2 ? '🥈' : position === 3 ? '🥉' : `${position}.`; await player.pm(` ${medal} ${topHorses[i].name}`); } // Show payout information if (results.winners.length > 0) { await player.pm(`💰 PAYOUTS: ${results.winners.length} winner${results.winners.length !== 1 ? 's' : ''} - ${results.totalPayout} total currency paid out`); // Show jackpot if there was one if (results.jackpot && results.jackpot > 0) { await player.pm(`🎰 Jackpot bonus: ${results.jackpot} currency!`); } // Check if current player won const playerWin = results.winners.find(bet => bet.playerId === player.id); if (playerWin) { let winnings = Math.floor(playerWin.amount * playerWin.odds); if (results.jackpot) { winnings += Math.floor(results.jackpot / results.winners.length); } await player.pm(`🎉 YOU WON ${winnings} with your ${playerWin.amount} bet on ${playerWin.horse}!`); } else { // Check if player had any losing bets const playerBets = results.winners.filter(bet => bet.playerId === player.id); if (playerBets.length === 0) { await player.pm('🤷♂️ You didn\'t place any bets in this race.'); } else { await player.pm('😔 Your horse didn\'t win this time. Better luck next race!'); } } } else { await player.pm('💸 No one bet on the winning horse - house wins!'); } // Show race statistics const totalBets = results.winners.length; await player.pm(`📊 Race Stats: ${totalBets} total bet${totalBets !== 1 ? 's' : ''} placed`); } catch (error) { if (error instanceof TakaroUserError) { throw error; } console.log('Error in lastRace command:', error); throw new TakaroUserError('Unable to retrieve race results. Please try again.'); } } await main(); -
horseStats
Shows your betting history, win rate, and favorite horses
Command source
import { takaro, data, TakaroUserError } from '@takaro/helpers'; async function main() { const { player, gameServerId, module: mod } = data; try { // Get player statistics const statsSearch = await takaro.variable.variableControllerSearch({ filters: { key: ['horse_racing_stats'], gameServerId: [gameServerId], moduleId: [mod.moduleId], playerId: [player.id], }, }); if (statsSearch.data.data.length === 0) { throw new TakaroUserError('You haven\'t placed any bets yet! Use /horsebet <horse> <amount> to get started.'); } const stats = JSON.parse(statsSearch.data.data[0].value); // Calculate derived stats const winRate = stats.totalBets > 0 ? Math.round((stats.wins / stats.totalBets) * 100) : 0; const netProfit = stats.totalWinnings - stats.totalWagered; const roi = stats.totalWagered > 0 ? Math.round((netProfit / stats.totalWagered) * 100) : 0; await player.pm('📊 YOUR HORSE RACING STATISTICS 📊'); await player.pm('═══════════════════════════════════'); await player.pm(`🎯 Win Rate: ${stats.wins}/${stats.totalBets} (${winRate}%)`); await player.pm(`💰 Net Profit: ${netProfit}`); await player.pm(`📈 ROI: ${roi}%`); await player.pm(`🏆 Biggest Win: ${stats.biggestWin}`); await player.pm(`💎 Total Wagered: ${stats.totalWagered}`); await player.pm(`🐎 Favorite Horse: ${stats.favoriteHorse}`); // Show per-horse breakdown if available if (stats.horseStats && Object.keys(stats.horseStats).length > 0) { await player.pm('🐎 PER-HORSE BREAKDOWN:'); // Sort horses by most bet on const horseEntries = Object.entries(stats.horseStats) .sort(([,a], [,b]) => b.bets - a.bets) .slice(0, 5); // Show top 5 for (const [horseName, horseStats] of horseEntries) { const horseWinRate = horseStats.bets > 0 ? Math.round((horseStats.wins / horseStats.bets) * 100) : 0; await player.pm(` ${horseName}: ${horseStats.wins}/${horseStats.bets} wins (${horseWinRate}%) - ${horseStats.totalWagered} wagered`); } } // Performance category let category = '🔰 Novice Bettor'; if (stats.totalBets >= 50 && winRate >= 30) { category = '🥇 Professional Gambler'; } else if (stats.totalBets >= 20 && winRate >= 25) { category = '🥈 Experienced Bettor'; } else if (stats.totalBets >= 10) { category = '🥉 Regular Bettor'; } await player.pm(`🏷️ Status: ${category}`); // Give tips based on performance if (winRate < 20 && stats.totalBets > 5) { await player.pm('💡 Tip: Try betting on horses with lower odds for better chances!'); } else if (roi > 50) { await player.pm('🎉 Outstanding performance! You\'re a horse racing champion!'); } } catch (error) { if (error instanceof TakaroUserError) { throw error; } console.log('Error in horseStats command:', error); throw new TakaroUserError('Failed to load your statistics. Please try again.'); } } await main();
Hooks 1
Code that runs in reaction to a game or Takaro event.
-
test
Hook for log events
Hook source
import { data, takaro } from '@takaro/helpers'; async function main() { const {} = data; } await main();
Cron jobs 2
Work the module runs on a schedule.
-
announceRace
Cron job source
import { takaro, data } from '@takaro/helpers'; async function main() { try { const { gameServerId, module: mod } = data; // Get online players const onlinePlayers = await takaro.playerOnGameserver.playerOnGameServerControllerSearch({ filters: { gameServerId: [gameServerId], online: [true] } }); // Skip if no one is online if (onlinePlayers.data.meta.total === 0) { return true; } // Get race data const currentRace = await takaro.variable.variableControllerSearch({ filters: { key: ['current_race'], gameServerId: [gameServerId], moduleId: [mod.moduleId], }, }); let raceData; if (currentRace.data.data.length === 0) { // Initialize race data if it doesn't exist raceData = { nextRaceTime: Date.now() + 3600000, // 1 hour from now bets: [], lastRaceResults: null, raceNumber: 1, state: 'waiting' }; await takaro.variable.variableControllerCreate({ key: 'current_race', value: JSON.stringify(raceData), gameServerId, moduleId: mod.moduleId, }); } else { raceData = JSON.parse(currentRace.data.data[0].value); } // Get horse list let horses = []; if (mod.userConfig?.Horses && Array.isArray(mod.userConfig.Horses)) { horses = mod.userConfig.Horses.map(horseStr => { const [name, oddsStr] = horseStr.split(';').map(s => s.trim()); return `${name} (${oddsStr}x)`; }); } else { horses = [ "Thunder (2x)", "Lightning (3x)", "Shadow (3x)", "Blaze (4x)", "Seacove (5x)", "Arrow (5x)", "Dark Knight (6x)" ]; } // Announce upcoming race await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message: `🏇 Attention! Horse Race #${raceData.raceNumber} will begin in 5 minutes!` }); await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message: `Available horses: ${horses.join(', ')}` }); await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message: `Place your bets with /horsebet <horse> <amount> (min: 50, max: 1000)` }); await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message: `${raceData.bets.length} bet${raceData.bets.length !== 1 ? 's' : ''} placed so far!` }); return true; } catch (error) { console.log('Error in announceRace cronjob:', error); return true; } } await main(); -
runRace
Cron job source
import { takaro, data } from '@takaro/helpers'; import { parseHorses, simulateRace, getRaceCommentary, updatePlayerStats, checkAndCreateJackpot } from './utils.js'; async function main() { try { const { gameServerId, module: mod } = data; // Get online players const onlinePlayers = await takaro.playerOnGameserver.playerOnGameServerControllerSearch({ filters: { gameServerId: [gameServerId], online: [true] } }); // Skip race if no one is online if (onlinePlayers.data.meta.total === 0) { return true; } // Get race data const currentRace = await takaro.variable.variableControllerSearch({ filters: { key: ['current_race'], gameServerId: [gameServerId], moduleId: [mod.moduleId], }, }); let raceData; if (currentRace.data.data.length === 0) { // Initialize race data if it doesn't exist raceData = { nextRaceTime: Date.now() + 3600000, // 1 hour from now bets: [], lastRaceResults: null, raceNumber: 1, state: 'waiting' }; await takaro.variable.variableControllerCreate({ key: 'current_race', value: JSON.stringify(raceData), gameServerId, moduleId: mod.moduleId, }); } else { raceData = JSON.parse(currentRace.data.data[0].value); } // If no bets, just announce and set next race if (!raceData.bets || raceData.bets.length === 0) { await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message: "🏇 The race track is quiet... No bets have been placed! Next race in 2 hours." }); raceData.nextRaceTime = Date.now() + (2 * 60 * 60 * 1000); await takaro.variable.variableControllerUpdate(currentRace.data.data[0].id, { value: JSON.stringify(raceData), }); return true; } // Check for jackpot const totalBetsAmount = raceData.bets.reduce((sum, bet) => sum + bet.amount, 0); const jackpotInfo = await checkAndCreateJackpot(gameServerId, mod.moduleId, totalBetsAmount); // Parse horses and simulate race const horses = parseHorses(mod.userConfig); const raceResults = simulateRace(horses); // Get winner const winningHorse = raceResults[0].name; const winningBets = raceData.bets.filter(bet => bet.horse.toLowerCase() === winningHorse.toLowerCase()); // RACE PROGRESSION SEQUENCE - No setTimeout, just progressive messages await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message: `🏁 RACE #${raceData.raceNumber} IS STARTING! 🏁` }); await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message: getRaceCommentary('start', horses, raceResults) }); if (jackpotInfo.isJackpot) { await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message: `🎰 JACKPOT RACE! Winner gets an extra ${jackpotInfo.amount} currency!` }); } // Show race positions as it progresses await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message: `🐎 Early positions: ${raceResults[0].name} leads, followed by ${raceResults[1].name} and ${raceResults[2].name}!` }); await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message: getRaceCommentary('midRace', horses, raceResults) }); await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message: `⚡ Final stretch! ${raceResults[0].name} is pulling ahead, ${raceResults[1].name} charging hard behind!` }); await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message: getRaceCommentary('finish', horses, raceResults) }); // Show final standings await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message: `🏆 FINAL RESULTS: 1st: ${raceResults[0].name} 🥇 | 2nd: ${raceResults[1].name} 🥈 | 3rd: ${raceResults[2].name} 🥉` }); // Process winnings and update stats let totalPayout = 0; if (winningBets.length > 0) { for (const bet of winningBets) { let winnings = Math.floor(bet.amount * bet.odds); // Add jackpot bonus if (jackpotInfo.isJackpot) { winnings += Math.floor(jackpotInfo.amount / winningBets.length); } totalPayout += winnings; try { await takaro.playerOnGameserver.playerOnGameServerControllerAddCurrency( gameServerId, bet.playerId, { currency: winnings } ); // Update player statistics await updatePlayerStats(gameServerId, mod.moduleId, bet.playerId, bet, true, winnings); // Notify winner if online const isOnline = onlinePlayers.data.data.some(p => p.playerId === bet.playerId); if (isOnline) { let winMessage = `🎉 Congratulations! ${winningHorse} won! You received ${winnings} currency!`; if (jackpotInfo.isJackpot) { winMessage += ` (Including jackpot bonus!)`; } await takaro.player.playerControllerSendMessage(bet.playerId, { message: winMessage }); } } catch (error) { console.log(`Error processing win for player ${bet.playerName}:`, error); } } let payoutMessage = `💰 WINNERS: ${winningBets.length} lucky bettor${winningBets.length !== 1 ? 's' : ''} won ${totalPayout} total currency!`; if (jackpotInfo.isJackpot) { payoutMessage += ` 🎰`; } await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message: payoutMessage }); // Show who won what const winnerNames = winningBets.map(bet => bet.playerName).slice(0, 3); if (winnerNames.length > 0) { await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message: `🎊 Winners: ${winnerNames.join(', ')} bet on the winning horse ${winningHorse}!` }); } } else { await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message: `💸 Nobody bet on ${winningHorse}! The house wins this round!` }); } // Update stats for losing bets for (const bet of raceData.bets) { const isWinner = winningBets.some(wb => wb.playerId === bet.playerId); if (!isWinner) { await updatePlayerStats(gameServerId, mod.moduleId, bet.playerId, bet, false, 0); } } // Clear jackpot if used if (jackpotInfo.isJackpot) { const jackpotSearch = await takaro.variable.variableControllerSearch({ filters: { key: ['current_jackpot'], gameServerId: [gameServerId], moduleId: [mod.moduleId], }, }); if (jackpotSearch.data.data.length > 0) { await takaro.variable.variableControllerDelete(jackpotSearch.data.data[0].id); } } // Save race results for history raceData.lastRaceResults = { raceNumber: raceData.raceNumber, results: raceResults, winners: winningBets, totalPayout: totalPayout, jackpot: jackpotInfo.isJackpot ? jackpotInfo.amount : 0, timestamp: Date.now() }; // Setup next race raceData.raceNumber++; raceData.nextRaceTime = Date.now() + (2 * 60 * 60 * 1000); raceData.bets = []; raceData.state = 'waiting'; // Update race data await takaro.variable.variableControllerUpdate(currentRace.data.data[0].id, { value: JSON.stringify(raceData), }); await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message: "🗓️ Next race in 2 hours! Use /horsebet <horse> <amount> to place your bets!" }); return true; } catch (error) { console.log('Error in runRace cronjob:', error); return true; } } await main();
Functions 1
Shared helpers the module's commands, hooks and cron jobs import.
-
utils
Function source
// utils.js import { takaro } from '@takaro/helpers'; // Helper to find a horse by name export function findHorse(horses, horseName) { return horses.find(h => h.name.toLowerCase() === horseName.toLowerCase()); } // Parse horses from config - improved with better error handling export function parseHorses(userConfig) { try { let horses = []; if (userConfig?.Horses && Array.isArray(userConfig.Horses)) { horses = userConfig.Horses.map(horseStr => { if (typeof horseStr !== 'string') { return { name: String(horseStr), odds: 2 }; } const parts = horseStr.split(';'); if (parts.length >= 2) { const name = parts[0].trim(); const oddsStr = parts[1].trim(); const odds = parseInt(oddsStr, 10); return { name: name, odds: isNaN(odds) ? 2 : odds }; } else { return { name: horseStr.trim(), odds: 2 }; } }); } else { // Fall back to default horses horses = [ { name: "Thunder", odds: 2 }, { name: "Lightning", odds: 3 }, { name: "Shadow", odds: 3 }, { name: "Blaze", odds: 4 } ]; } // Filter out any invalid horses horses = horses.filter(h => h && h.name); return horses; } catch (error) { // Return default horses on error return [ { name: "Thunder", odds: 2 }, { name: "Lightning", odds: 3 }, { name: "Shadow", odds: 3 }, { name: "Blaze", odds: 4 } ]; } } // Helper function to manage race data export async function getRaceData(gameServerId, moduleId) { const currentRace = await takaro.variable.variableControllerSearch({ filters: { key: ['current_race'], gameServerId: [gameServerId], moduleId: [moduleId], }, }); if (currentRace.data.data.length === 0) { // Initialize race data if it doesn't exist const initialRace = { nextRaceTime: Date.now() + 3600000, // 1 hour from now bets: [], lastRaceResults: null, raceNumber: 1 }; await takaro.variable.variableControllerCreate({ key: 'current_race', value: JSON.stringify(initialRace), gameServerId, moduleId, }); return initialRace; } // Safely parse JSON with fallback try { return JSON.parse(currentRace.data.data[0].value); } catch (e) { console.log('Error parsing race data:', e); // Return a new race if parsing fails return { nextRaceTime: Date.now() + 3600000, bets: [], lastRaceResults: null, raceNumber: 1 }; } } // Update race data export async function updateRaceData(gameServerId, moduleId, raceData) { try { const currentRace = await takaro.variable.variableControllerSearch({ filters: { key: ['current_race'], gameServerId: [gameServerId], moduleId: [moduleId], }, }); if (currentRace.data.data.length === 0) { // Create if it doesn't exist await takaro.variable.variableControllerCreate({ key: 'current_race', value: JSON.stringify(raceData), gameServerId, moduleId, }); } else { // Update existing await takaro.variable.variableControllerUpdate(currentRace.data.data[0].id, { value: JSON.stringify(raceData), }); } } catch (error) { console.log('Error updating race data:', error); } } // Helper to get the next race time in a human-readable format export function getTimeUntilRace(nextRaceTime) { const timeRemaining = nextRaceTime - Date.now(); if (timeRemaining <= 0) { return "any moment now"; } const minutes = Math.floor(timeRemaining / 60000); if (minutes < 60) { return `${minutes} minute${minutes !== 1 ? 's' : ''}`; } const hours = Math.floor(minutes / 60); const remainingMinutes = minutes % 60; return `${hours} hour${hours !== 1 ? 's' : ''} and ${remainingMinutes} minute${remainingMinutes !== 1 ? 's' : ''}`; } // Simulate race with weighted probabilities based on odds export function simulateRace(horses) { if (!horses || !Array.isArray(horses) || horses.length === 0) { console.log('Invalid horses data for simulation, using defaults'); horses = [ { name: "Thunder", odds: 2 }, { name: "Lightning", odds: 3 }, { name: "Shadow", odds: 3 }, { name: "Blaze", odds: 4 } ]; } // Convert odds to weights (lower odds = higher chance to win) const totalWeight = horses.reduce((sum, horse) => sum + (1 / horse.odds), 0); // Create weighted array const weightedHorses = []; for (const horse of horses) { // Calculate weight - inverse of odds, normalized const weight = (1 / horse.odds) / totalWeight; weightedHorses.push({ name: horse.name, weight, position: 0, speed: 0.5 + (Math.random() * 0.5) // Randomize speed a bit }); } // Run the race simulation - 10 steps for (let step = 0; step < 10; step++) { for (const horse of weightedHorses) { // Movement based on weight (higher weight = more likely to advance) and speed horse.position += horse.weight * horse.speed * (0.8 + Math.random() * 0.4); } } // Sort by final position return weightedHorses.sort((a, b) => b.position - a.position); } // Generate a visual representation of the race export function generateRaceProgress(results) { if (!results || !Array.isArray(results) || results.length === 0) { return "The race is too close to call!"; } const top3 = results.slice(0, Math.min(3, results.length)); return top3.map((horse, index) => `${horse.name}${index === 0 ? ' in the lead' : ''}`).join(', ') + "!"; } // Track player statistics export async function updatePlayerStats(gameServerId, moduleId, playerId, bet, isWin, winnings = 0) { try { // Get existing player stats const statsSearch = await takaro.variable.variableControllerSearch({ filters: { key: ['horse_racing_stats'], gameServerId: [gameServerId], moduleId: [moduleId], playerId: [playerId], }, }); let stats; if (statsSearch.data.data.length === 0) { // Initialize new player stats stats = { totalWinnings: 0, totalBets: 0, totalWagered: 0, wins: 0, losses: 0, biggestWin: 0, favoriteHorse: bet.horse, horseStats: {} }; } else { stats = JSON.parse(statsSearch.data.data[0].value); } // Update stats stats.totalBets++; stats.totalWagered += bet.amount; if (isWin) { stats.wins++; stats.totalWinnings += winnings; if (winnings > stats.biggestWin) { stats.biggestWin = winnings; } } else { stats.losses++; } // Track horse betting patterns if (!stats.horseStats) { stats.horseStats = {}; } if (!stats.horseStats[bet.horse]) { stats.horseStats[bet.horse] = { bets: 0, wins: 0, totalWagered: 0 }; } stats.horseStats[bet.horse].bets++; stats.horseStats[bet.horse].totalWagered += bet.amount; if (isWin) { stats.horseStats[bet.horse].wins++; } // Update favorite horse (most bet on) const mostBetHorse = Object.entries(stats.horseStats).reduce((a, b) => stats.horseStats[a[0]].bets > stats.horseStats[b[0]].bets ? a : b ); stats.favoriteHorse = mostBetHorse[0]; // Save or update stats if (statsSearch.data.data.length === 0) { await takaro.variable.variableControllerCreate({ key: 'horse_racing_stats', value: JSON.stringify(stats), gameServerId, moduleId, playerId, }); } else { await takaro.variable.variableControllerUpdate(statsSearch.data.data[0].id, { value: JSON.stringify(stats), }); } } catch (error) { console.log('Error updating player stats:', error); } } // Get exciting race commentary export function getRaceCommentary(stage, horses, results) { const commentaries = { start: [ "🏇 The horses are lined up at the starting gate!", "🚩 And they're off! The race has begun!", "⚡ Lightning start as the horses burst from the gate!", "🎯 The field is packed and ready to run!" ], midRace: [ `🔥 ${results[0].name} takes the early lead!`, `💨 It's a tight race between ${results[0].name} and ${results[1].name}!`, `🏃♂️ The pack is bunched together - anyone could win!`, `⚡ ${results[0].name} pulls ahead with a burst of speed!` ], finish: [ `🏆 Victory! ${results[0].name} crosses the finish line first!`, `🥇 What a race! ${results[0].name} wins by a nose!`, `🎊 ${results[0].name} takes the checkered flag!`, `🏅 Champion! ${results[0].name} dominates the field!` ] }; const stageComments = commentaries[stage] || commentaries.start; return stageComments[Math.floor(Math.random() * stageComments.length)]; } // Generate jackpot system export async function checkAndCreateJackpot(gameServerId, moduleId, totalBets) { try { // Create jackpot every 10 races or when there are 20+ total bets if (totalBets >= 20) { const jackpotSearch = await takaro.variable.variableControllerSearch({ filters: { key: ['current_jackpot'], gameServerId: [gameServerId], moduleId: [moduleId], }, }); let jackpotAmount = Math.floor(totalBets * 50); // Base jackpot if (jackpotSearch.data.data.length === 0) { // Create new jackpot await takaro.variable.variableControllerCreate({ key: 'current_jackpot', value: JSON.stringify({ amount: jackpotAmount, active: true, createdAt: Date.now() }), gameServerId, moduleId, }); return { isJackpot: true, amount: jackpotAmount }; } } return { isJackpot: false, amount: 0 }; } catch (error) { console.log('Error checking jackpot:', error); return { isJackpot: false, amount: 0 }; } }
Permissions 2
Roles you can grant to decide who may use what.
-
Place Horse Racing Bets
Allows the player to bet on horse races
-
Horse Racing Admin
Allows the player to manage horse races