// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; /// @notice One non-transferable pet per wallet. No admin, custody, payable methods or external calls. contract LittleFrenGame { struct Pet { string name; uint64 coins; uint64 xp; uint64 lastAction; uint8 energy; uint8 fullness; uint8 mood; uint8 skin; uint8 tasks; bool claimed; bool exists; } mapping(address => Pet) private pets; struct Outing { uint8[3] counts; uint8 trail; uint64 readyAt; bool active; } mapping(address => Outing) private outings; event OutingStarted(address indexed player, uint8 trail, uint64 readyAt); event KeepsakeCollected(address indexed player, uint8 trail, uint8 item); error TrailLocked(); error TrailComplete(); error OutingActive(); error NoOuting(); error OutingNotReady(); function getOuting(address player) external view returns(Outing memory) { return outings[player]; } function startOuting(uint8 trail) external { Pet storage p=pet(); if(trail>2) revert InvalidAction(); if(p.xp=3) revert TrailComplete(); o.trail=trail;o.readyAt=uint64(block.timestamp+30*(uint256(trail)+1));o.active=true; emit OutingStarted(msg.sender,trail,o.readyAt); } function collectOuting() external { pet();Outing storage o=outings[msg.sender]; if(!o.active) revert NoOuting(); if(block.timestamp3) revert InvalidAction(); if(block.timestamp < uint256(p.lastAction)+COOLDOWN) revert RestFirst(uint256(p.lastAction)+COOLDOWN); if(action==0){ if(p.coins<10) revert InsufficientPebbles(); p.coins-=10; p.fullness=cap(uint256(p.fullness)+25); p.mood=cap(uint256(p.mood)+5); p.xp+=10; p.tasks|=1; } else if(action==1){ if(p.energy<15) revert InsufficientEnergy(); p.energy-=15; p.fullness=sub(p.fullness,8); p.mood=cap(uint256(p.mood)+3);p.coins+=5;p.xp+=25;p.tasks|=2; } else if(action==2){ if(p.energy<20) revert InsufficientEnergy(); p.energy-=20;p.fullness=sub(p.fullness,10);p.mood=cap(uint256(p.mood)+8);p.coins+=25;p.xp+=20;p.tasks|=4; } else { p.energy=cap(uint256(p.energy)+35);p.mood=cap(uint256(p.mood)+4); } p.lastAction=uint64(block.timestamp); emit Played(msg.sender,action,p.xp,p.coins); } function rename(string calldata name) external {Pet storage p=pet();validateName(name);p.name=name;emit Renamed(msg.sender,name);} function setColor(uint8 skin) external {Pet storage p=pet();if(skin>2||uint256(skin)*100>p.xp)revert ColorLocked();p.skin=skin;emit ColorChanged(msg.sender,skin);} function claimQuest() external {Pet storage p=pet();if(p.claimed)revert AlreadyClaimed();if((p.tasks&7)!=7)revert QuestIncomplete();p.claimed=true;p.coins+=40;p.xp+=25;emit RewardClaimed(msg.sender);} function pet() private view returns(Pet storage p){p=pets[msg.sender];if(!p.exists)revert NoPet();} function cap(uint256 n) private pure returns(uint8){return uint8(n>100?100:n);} function sub(uint8 n,uint8 amount) private pure returns(uint8){return n20)revert InvalidName();bool visible;for(uint i;i126)revert InvalidName();if(b[i]!=0x20)visible=true;}if(!visible)revert InvalidName();} }