@@ -51,6 +51,8 @@ error InvalidFutureBlocks(uint256 blocks);
5151error CannotRecoverYet ();
5252error NoDefaultPlayersAvailable ();
5353error InvalidBlockhash ();
54+ error DailyLimitExceeded (uint8 currentRuns , uint8 limit );
55+ error InsufficientResetFee ();
5456
5557//==============================================================//
5658// HEAVY HELMS //
@@ -196,6 +198,14 @@ contract GauntletGame is BaseGame, ReentrancyGuard {
196198 /// @notice If status is IN_GAUNTLET, maps player ID to the `gauntletId` they are participating in.
197199 mapping (uint32 => uint256 ) public playerCurrentGauntlet;
198200
201+ // --- Daily Limit System ---
202+ /// @notice Maximum gauntlet entries per player per day
203+ uint8 public dailyGauntletLimit = 10 ;
204+ /// @notice Cost in ETH to reset daily limit for a player
205+ uint256 public dailyResetCost = 0.001 ether ;
206+ /// @notice Maps player ID to day number to run count (playerId => dayNumber => runCount)
207+ mapping (uint32 => mapping (uint256 => uint8 )) private _playerDailyRuns;
208+
199209 //==============================================================//
200210 // EVENTS //
201211 //==============================================================//
@@ -244,6 +254,12 @@ contract GauntletGame is BaseGame, ReentrancyGuard {
244254 event QueuePartiallyCleared (uint256 cleared , uint256 remaining );
245255 /// @notice Emitted when the game enabled state is updated.
246256 event GameEnabledUpdated (bool enabled );
257+ /// @notice Emitted when a player's daily gauntlet limit is reset via ETH payment
258+ event DailyLimitReset (uint32 indexed playerId , address indexed payer , uint256 dayNumber , uint256 amountPaid );
259+ /// @notice Emitted when the daily reset cost is updated
260+ event DailyResetCostUpdated (uint256 oldCost , uint256 newCost );
261+ /// @notice Emitted when the daily gauntlet limit is updated
262+ event DailyGauntletLimitUpdated (uint8 oldLimit , uint8 newLimit );
247263 // Inherited from BaseGame: event CombatResult(bytes32 indexed player1Data, bytes32 indexed player2Data, uint32 winnerId, bytes combatLog);
248264
249265 //==============================================================//
@@ -295,14 +311,21 @@ contract GauntletGame is BaseGame, ReentrancyGuard {
295311
296312 /// @notice Allows a player owner to join the Gauntlet queue with a specific loadout.
297313 /// @param loadout The player's chosen skin and stance for the potential Gauntlet.
298- /// @dev Validates player status, ownership, retirement status, and skin requirements.
314+ /// @dev Validates player status, ownership, retirement status, skin requirements, and daily limits .
299315 function queueForGauntlet (Fighter.PlayerLoadout calldata loadout ) external whenGameEnabled nonReentrant {
300316 // Checks
301317 if (playerStatus[loadout.playerId] != PlayerStatus.NONE) revert AlreadyInQueue ();
302318 address owner = playerContract.getPlayerOwner (loadout.playerId);
303319 if (msg .sender != owner) revert CallerNotPlayerOwner ();
304320 if (playerContract.isPlayerRetired (loadout.playerId)) revert PlayerIsRetired ();
305321
322+ // Check daily limit
323+ uint256 today = _getDayNumber ();
324+ uint8 currentRuns = _playerDailyRuns[loadout.playerId][today];
325+ if (currentRuns >= dailyGauntletLimit) {
326+ revert DailyLimitExceeded (currentRuns, dailyGauntletLimit);
327+ }
328+
306329 // Validate player is in correct level bracket
307330 _validatePlayerLevel (loadout.playerId);
308331
@@ -328,6 +351,9 @@ contract GauntletGame is BaseGame, ReentrancyGuard {
328351 playerIndexInQueue[playerId] = queueIndex.length ; // 1-based index
329352 playerStatus[playerId] = PlayerStatus.QUEUED;
330353
354+ // Increment daily run counter (user pays gas for state change)
355+ _playerDailyRuns[playerId][today]++ ;
356+
331357 // Interactions (Event Emission)
332358 emit PlayerQueued (playerId, queueIndex.length );
333359 }
@@ -365,6 +391,31 @@ contract GauntletGame is BaseGame, ReentrancyGuard {
365391 return registrationQueue[playerId];
366392 }
367393
394+ /// @notice Gets the current daily run count for a player
395+ /// @param playerId The ID of the player to check
396+ /// @return The number of gauntlet runs today for this player
397+ function getDailyRunCount (uint32 playerId ) external view returns (uint8 ) {
398+ uint256 today = _getDayNumber ();
399+ return _playerDailyRuns[playerId][today];
400+ }
401+
402+ /// @notice Resets the daily gauntlet limit for a player by paying ETH
403+ /// @param playerId The ID of the player to reset limit for
404+ /// @dev Player owner pays ETH to reset their daily gauntlet entry count to 0
405+ function resetDailyLimit (uint32 playerId ) external payable nonReentrant {
406+ // Checks
407+ address owner = playerContract.getPlayerOwner (playerId);
408+ if (msg .sender != owner) revert CallerNotPlayerOwner ();
409+ if (msg .value < dailyResetCost) revert InsufficientResetFee ();
410+
411+ // Effects
412+ uint256 today = _getDayNumber ();
413+ _playerDailyRuns[playerId][today] = 0 ;
414+
415+ // Interactions
416+ emit DailyLimitReset (playerId, msg .sender , today, msg .value );
417+ }
418+
368419 //==============================================================//
369420 // GAUNTLET LIFECYCLE (Triggered Externally) //
370421 //==============================================================//
@@ -1045,6 +1096,29 @@ contract GauntletGame is BaseGame, ReentrancyGuard {
10451096 emit MinTimeBetweenGauntletsSet (newMinTime);
10461097 }
10471098
1099+ /// @notice Sets the cost for resetting daily gauntlet limits
1100+ /// @param newCost The new cost in ETH for daily limit resets
1101+ function setDailyResetCost (uint256 newCost ) external onlyOwner {
1102+ uint256 oldCost = dailyResetCost;
1103+ dailyResetCost = newCost;
1104+ emit DailyResetCostUpdated (oldCost, newCost);
1105+ }
1106+
1107+ /// @notice Withdraws accumulated daily reset fees to the owner
1108+ /// @dev Only callable by contract owner
1109+ function withdrawFees () external onlyOwner {
1110+ SafeTransferLib.safeTransferETH (owner, address (this ).balance);
1111+ }
1112+
1113+ /// @notice Sets the daily gauntlet entry limit per player
1114+ /// @param newLimit The new daily entry limit
1115+ function setDailyGauntletLimit (uint8 newLimit ) external onlyOwner {
1116+ if (newLimit == 0 ) revert InvalidGauntletSize (newLimit);
1117+ uint8 oldLimit = dailyGauntletLimit;
1118+ dailyGauntletLimit = newLimit;
1119+ emit DailyGauntletLimitUpdated (oldLimit, newLimit);
1120+ }
1121+
10481122 //==============================================================//
10491123 // INTERNAL FUNCTIONS //
10501124 //==============================================================//
@@ -1086,6 +1160,12 @@ contract GauntletGame is BaseGame, ReentrancyGuard {
10861160 return uint256 (keccak256 (abi.encodePacked (baseHash, block .timestamp , block .number , gasleft (), tx .origin )));
10871161 }
10881162
1163+ /// @notice Calculates the current day number since Unix epoch
1164+ /// @return Day number (resets at midnight UTC)
1165+ function _getDayNumber () private view returns (uint256 ) {
1166+ return block .timestamp / 1 days ;
1167+ }
1168+
10891169 // --- Helper Functions ---
10901170
10911171 /// @notice Loads combat data for a default player.
0 commit comments