├── .python-version ├── remappings.txt ├── processing ├── types.png └── deployToEnv.py ├── drool ├── imgs │ ├── Llama Back.gif │ ├── Llama Front.gif │ ├── Llama Mini.gif │ ├── aurox_back.gif │ ├── aurox_front.gif │ ├── aurox_mini.gif │ ├── inutia_back.gif │ ├── inutia_mini.gif │ ├── pengym_back.gif │ ├── pengym_mini.gif │ ├── xmon_back.gif │ ├── xmon_front.gif │ ├── xmon_mini.gif │ ├── embursa_back.gif │ ├── embursa_front.gif │ ├── embursa_mini.gif │ ├── gorillax_back.gif │ ├── gorillax_mini.gif │ ├── iblivion_back.gif │ ├── iblivion_mini.gif │ ├── inutia_front.gif │ ├── malalien_back.gif │ ├── malalien_mini.gif │ ├── pengym_front.gif │ ├── sofabbi_back.gif │ ├── sofabbi_front.gif │ ├── sofabbi_mini.gif │ ├── volthare_back.gif │ ├── volthare_mini.gif │ ├── ghouliath_back.gif │ ├── ghouliath_front.gif │ ├── ghouliath_mini.gif │ ├── gorillax_front.gif │ ├── iblivion_front.gif │ ├── malalien_front.gif │ └── volthare_front.gif ├── mons.csv ├── abilities.csv └── type-data.js ├── snapshots ├── MatchmakerTest.json ├── EngineTest.json └── EngineGasTest.json ├── .gitmodules ├── foundry.lock ├── src ├── rng │ ├── ICPURNG.sol │ ├── IGachaRNG.sol │ ├── IRandomnessOracle.sol │ └── DefaultRandomnessOracle.sol ├── matchmaker │ └── IMatchmaker.sol ├── abilities │ └── IAbility.sol ├── types │ ├── ITypeCalculator.sol │ └── TypeCalculator.sol ├── IRuleset.sol ├── IEngineHook.sol ├── gacha │ └── IOwnableMon.sol ├── effects │ ├── status │ │ ├── StatusEffectLib.sol │ │ ├── StatusEffect.sol │ │ ├── FrostbiteStatus.sol │ │ ├── PanicStatus.sol │ │ └── ZapStatus.sol │ ├── IEffect.sol │ ├── StaminaRegen.sol │ └── BasicEffect.sol ├── cpu │ ├── ICPU.sol │ ├── PlayerCPU.sol │ ├── RandomCPU.sol │ └── CPUMoveManager.sol ├── moves │ ├── StandardAttackStructs.sol │ ├── IMoveSet.sol │ └── StandardAttackFactory.sol ├── ICommitManager.sol ├── teams │ ├── ITeamRegistry.sol │ ├── IMonRegistry.sol │ └── GachaTeamRegistry.sol ├── DefaultRuleset.sol ├── mons │ ├── embursa │ │ ├── HeatBeaconLib.sol │ │ ├── SetAblaze.sol │ │ └── HeatBeacon.sol │ ├── volthare │ │ ├── Overclock.sol │ │ ├── Electrocute.sol │ │ ├── PreemptiveShock.sol │ │ ├── DualShock.sol │ │ └── RoundTrip.sol │ ├── gorillax │ │ ├── Blow.sol │ │ ├── PoundGround.sol │ │ ├── ThrowPebble.sol │ │ └── Angery.sol │ ├── inutia │ │ ├── BigBite.sol │ │ ├── HitAndDip.sol │ │ └── Interweaving.sol │ ├── ghouliath │ │ ├── Osteoporosis.sol │ │ ├── InfernalFlame.sol │ │ ├── WitherAway.sol │ │ └── EternalGrudge.sol │ ├── pengym │ │ ├── ChillOut.sol │ │ ├── Deadlift.sol │ │ ├── PostWorkout.sol │ │ ├── PistolSquat.sol │ │ └── DeepFreeze.sol │ ├── sofabbi │ │ ├── UnexpectedCarrot.sol │ │ ├── CarrotHarvest.sol │ │ ├── GuestFeature.sol │ │ ├── SnackBreak.sol │ │ └── Gachachacha.sol │ ├── malalien │ │ ├── InfiniteLove.sol │ │ ├── FederalInvestigation.sol │ │ ├── NegativeThoughts.sol │ │ └── TripleThink.sol │ ├── xmon │ │ ├── ContagiousSlumber.sol │ │ ├── Dreamcatcher.sol │ │ └── VitalSiphon.sol │ ├── aurox │ │ ├── BullRush.sol │ │ ├── UpOnly.sol │ │ ├── VolatilePunch.sol │ │ └── IronWall.sol │ └── iblivion │ │ ├── Renormalize.sol │ │ └── Brightback.sol ├── IValidator.sol ├── Enums.sol ├── lib │ ├── MappingAllocator.sol │ └── Strings.sol └── Constants.sol ├── pyproject.toml ├── test └── mocks │ ├── MockCPURNG.sol │ ├── MockGachaRNG.sol │ ├── MockRandomnessOracle.sol │ ├── DummyStatus.sol │ ├── EffectAbility.sol │ ├── InstantDeathEffect.sol │ ├── InstantDeathOnSwitchInEffect.sol │ ├── AfterDamageReboundEffect.sol │ ├── TestTypeCalculator.sol │ ├── TestTeamRegistry.sol │ ├── SingleInstanceEffect.sol │ ├── TempStatBoostEffect.sol │ ├── InvalidMove.sol │ ├── OneTurnStatBoost.sol │ ├── OnUpdateMonStateHealEffect.sol │ ├── EditEffectAttack.sol │ ├── SpAtkDebuffEffect.sol │ ├── GlobalEffectAttack.sol │ ├── SkipTurnMove.sol │ ├── ReduceSpAtkMove.sol │ ├── SelfSwitchAndDamageMove.sol │ ├── ForceSwitchMove.sol │ ├── EffectAttack.sol │ ├── TestMoveFactory.sol │ ├── MockEffectRemover.sol │ └── StatBoostsMove.sol ├── docs ├── thots_2.md ├── stomp_intro.md ├── links.md └── patch_2.md ├── .github └── workflows │ ├── main.yml │ └── test.yml ├── foundry.toml ├── .gitignore ├── script ├── Surgery.s.sol └── SetupCPU.s.sol ├── README.md └── todo.txt /.python-version: -------------------------------------------------------------------------------- 1 | 3.11 2 | -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | forge-std/=lib/forge-std/src/ 2 | -------------------------------------------------------------------------------- /processing/types.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/processing/types.png -------------------------------------------------------------------------------- /drool/imgs/Llama Back.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/Llama Back.gif -------------------------------------------------------------------------------- /drool/imgs/Llama Front.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/Llama Front.gif -------------------------------------------------------------------------------- /drool/imgs/Llama Mini.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/Llama Mini.gif -------------------------------------------------------------------------------- /drool/imgs/aurox_back.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/aurox_back.gif -------------------------------------------------------------------------------- /drool/imgs/aurox_front.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/aurox_front.gif -------------------------------------------------------------------------------- /drool/imgs/aurox_mini.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/aurox_mini.gif -------------------------------------------------------------------------------- /drool/imgs/inutia_back.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/inutia_back.gif -------------------------------------------------------------------------------- /drool/imgs/inutia_mini.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/inutia_mini.gif -------------------------------------------------------------------------------- /drool/imgs/pengym_back.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/pengym_back.gif -------------------------------------------------------------------------------- /drool/imgs/pengym_mini.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/pengym_mini.gif -------------------------------------------------------------------------------- /drool/imgs/xmon_back.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/xmon_back.gif -------------------------------------------------------------------------------- /drool/imgs/xmon_front.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/xmon_front.gif -------------------------------------------------------------------------------- /drool/imgs/xmon_mini.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/xmon_mini.gif -------------------------------------------------------------------------------- /drool/imgs/embursa_back.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/embursa_back.gif -------------------------------------------------------------------------------- /drool/imgs/embursa_front.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/embursa_front.gif -------------------------------------------------------------------------------- /drool/imgs/embursa_mini.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/embursa_mini.gif -------------------------------------------------------------------------------- /drool/imgs/gorillax_back.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/gorillax_back.gif -------------------------------------------------------------------------------- /drool/imgs/gorillax_mini.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/gorillax_mini.gif -------------------------------------------------------------------------------- /drool/imgs/iblivion_back.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/iblivion_back.gif -------------------------------------------------------------------------------- /drool/imgs/iblivion_mini.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/iblivion_mini.gif -------------------------------------------------------------------------------- /drool/imgs/inutia_front.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/inutia_front.gif -------------------------------------------------------------------------------- /drool/imgs/malalien_back.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/malalien_back.gif -------------------------------------------------------------------------------- /drool/imgs/malalien_mini.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/malalien_mini.gif -------------------------------------------------------------------------------- /drool/imgs/pengym_front.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/pengym_front.gif -------------------------------------------------------------------------------- /drool/imgs/sofabbi_back.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/sofabbi_back.gif -------------------------------------------------------------------------------- /drool/imgs/sofabbi_front.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/sofabbi_front.gif -------------------------------------------------------------------------------- /drool/imgs/sofabbi_mini.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/sofabbi_mini.gif -------------------------------------------------------------------------------- /drool/imgs/volthare_back.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/volthare_back.gif -------------------------------------------------------------------------------- /drool/imgs/volthare_mini.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/volthare_mini.gif -------------------------------------------------------------------------------- /drool/imgs/ghouliath_back.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/ghouliath_back.gif -------------------------------------------------------------------------------- /drool/imgs/ghouliath_front.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/ghouliath_front.gif -------------------------------------------------------------------------------- /drool/imgs/ghouliath_mini.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/ghouliath_mini.gif -------------------------------------------------------------------------------- /drool/imgs/gorillax_front.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/gorillax_front.gif -------------------------------------------------------------------------------- /drool/imgs/iblivion_front.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/iblivion_front.gif -------------------------------------------------------------------------------- /drool/imgs/malalien_front.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/malalien_front.gif -------------------------------------------------------------------------------- /drool/imgs/volthare_front.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-owen/chomp/HEAD/drool/imgs/volthare_front.gif -------------------------------------------------------------------------------- /snapshots/MatchmakerTest.json: -------------------------------------------------------------------------------- 1 | { 2 | "Accept1": "305303", 3 | "Accept2": "33926", 4 | "Propose1": "197083" 5 | } -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | -------------------------------------------------------------------------------- /foundry.lock: -------------------------------------------------------------------------------- 1 | { 2 | "lib/forge-std": { 3 | "tag": { 4 | "name": "v1.11.0", 5 | "rev": "8e40513d678f392f398620b3ef2b418648b33e89" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /snapshots/EngineTest.json: -------------------------------------------------------------------------------- 1 | { 2 | "FirstBattle": "6745022", 3 | "Intermediary stuff": "47879", 4 | "SecondBattle": "6826261", 5 | "Setup 1": "1906149", 6 | "Setup 2": "363778" 7 | } -------------------------------------------------------------------------------- /src/rng/ICPURNG.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | interface ICPURNG { 5 | function getRNG(bytes32 seed) external view returns (uint256); 6 | } 7 | -------------------------------------------------------------------------------- /src/rng/IGachaRNG.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | interface IGachaRNG { 5 | function getRNG(bytes32 seed) external view returns (uint256); 6 | } 7 | -------------------------------------------------------------------------------- /src/matchmaker/IMatchmaker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | interface IMatchmaker { 5 | function validateMatch(bytes32 battleKey, address player) external returns (bool); 6 | } 7 | -------------------------------------------------------------------------------- /src/rng/IRandomnessOracle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | interface IRandomnessOracle { 5 | function getRNG(bytes32 source0, bytes32 source1) external returns (uint256); 6 | } 7 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "chomp" 3 | version = "0.1.0" 4 | description = "Add your description here" 5 | readme = "README.md" 6 | requires-python = ">=3.11" 7 | dependencies = [ 8 | "numpy>=2.3.5", 9 | "pexpect>=4.9.0", 10 | ] 11 | -------------------------------------------------------------------------------- /src/abilities/IAbility.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | interface IAbility { 5 | function name() external view returns (string memory); 6 | function activateOnSwitch(bytes32 battleKey, uint256 playerIndex, uint256 monIndex) external; 7 | } 8 | -------------------------------------------------------------------------------- /src/types/ITypeCalculator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "../Enums.sol"; 5 | 6 | interface ITypeCalculator { 7 | function getTypeEffectiveness(Type attackerType, Type defenderType, uint32 basePower) external view returns (uint32); 8 | } 9 | -------------------------------------------------------------------------------- /src/IRuleset.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "./Structs.sol"; 5 | import "./effects/IEffect.sol"; 6 | 7 | interface IRuleset { 8 | // Returns which global effects to start the game with 9 | function getInitialGlobalEffects() external returns (IEffect[] memory, bytes32[] memory); 10 | } 11 | -------------------------------------------------------------------------------- /src/IEngineHook.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | interface IEngineHook { 5 | function onBattleStart(bytes32 battleKey) external; 6 | function onRoundStart(bytes32 battleKey) external; 7 | function onRoundEnd(bytes32 battleKey) external; 8 | function onBattleEnd(bytes32 battleKey) external; 9 | } 10 | -------------------------------------------------------------------------------- /src/gacha/IOwnableMon.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | interface IOwnableMon { 5 | function isOwner(address player, uint256 monId) external view returns (bool); 6 | function balanceOf(address player) external view returns (uint256); 7 | function getOwned(address player) external view returns (uint256[] memory); 8 | } 9 | -------------------------------------------------------------------------------- /src/effects/status/StatusEffectLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | library StatusEffectLib { 5 | function getKeyForMonIndex(uint256 playerIndex, uint256 monIndex) internal pure returns (bytes32) { 6 | bytes32 STATUS_EFFECT = "STATUS_EFFECT"; 7 | return keccak256(abi.encodePacked(STATUS_EFFECT, playerIndex, monIndex)); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/rng/DefaultRandomnessOracle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import {IRandomnessOracle} from "./IRandomnessOracle.sol"; 5 | 6 | contract DefaultRandomnessOracle is IRandomnessOracle { 7 | function getRNG(bytes32 source0, bytes32 source1) external pure returns (uint256) { 8 | return uint256(keccak256(abi.encode(source0, source1))); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/mocks/MockCPURNG.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import {ICPURNG} from "../../src/rng/ICPURNG.sol"; 5 | 6 | contract MockCPURNG is ICPURNG { 7 | uint256 rng; 8 | 9 | function setRNG(uint256 a) public { 10 | rng = a; 11 | } 12 | 13 | function getRNG(bytes32) external view returns (uint256) { 14 | return rng; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/cpu/ICPU.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import {ProposedBattle} from "../Structs.sol"; 5 | 6 | interface ICPU { 7 | function calculateMove(bytes32 battleKey, uint256 playerIndex) 8 | external 9 | returns (uint128 moveIndex, uint240 extraData); 10 | function startBattle(ProposedBattle memory proposal) external returns (bytes32 battleKey); 11 | } 12 | -------------------------------------------------------------------------------- /test/mocks/MockGachaRNG.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import {IGachaRNG} from "../../src/rng/IGachaRNG.sol"; 5 | 6 | contract MockGachaRNG is IGachaRNG { 7 | uint256 rng; 8 | 9 | function setRNG(uint256 a) public { 10 | rng = a; 11 | } 12 | 13 | function getRNG(bytes32) external view returns (uint256) { 14 | return rng; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/mocks/MockRandomnessOracle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import {IRandomnessOracle} from "../../src/rng/IRandomnessOracle.sol"; 5 | 6 | contract MockRandomnessOracle is IRandomnessOracle { 7 | uint256 rng; 8 | 9 | function setRNG(uint256 a) public { 10 | rng = a; 11 | } 12 | 13 | function getRNG(bytes32, bytes32) external view returns (uint256) { 14 | return rng; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/mocks/DummyStatus.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/Enums.sol"; 6 | import {BasicEffect} from "../../src/effects/BasicEffect.sol"; 7 | 8 | contract DummyStatus is BasicEffect { 9 | function name() external pure override returns (string memory) { 10 | return "Dummy"; 11 | } 12 | 13 | function shouldRunAtStep(EffectStep) external pure override returns (bool) { 14 | return false; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /snapshots/EngineGasTest.json: -------------------------------------------------------------------------------- 1 | { 2 | "B1_Execute": "965740", 3 | "B1_Setup": "812515", 4 | "B2_Execute": "747917", 5 | "B2_Setup": "275871", 6 | "Battle1_Execute": "491305", 7 | "Battle1_Setup": "788932", 8 | "Battle2_Execute": "405995", 9 | "Battle2_Setup": "234596", 10 | "FirstBattle": "3478379", 11 | "Intermediary stuff": "43924", 12 | "SecondBattle": "3559917", 13 | "Setup 1": "1668618", 14 | "Setup 2": "295451", 15 | "Setup 3": "335161", 16 | "ThirdBattle": "2889086" 17 | } -------------------------------------------------------------------------------- /src/moves/StandardAttackStructs.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "../Enums.sol"; 5 | import {IEffect} from "../effects/IEffect.sol"; 6 | 7 | struct ATTACK_PARAMS { 8 | uint32 BASE_POWER; 9 | uint32 STAMINA_COST; 10 | uint32 ACCURACY; 11 | uint32 PRIORITY; 12 | Type MOVE_TYPE; 13 | uint32 EFFECT_ACCURACY; 14 | MoveClass MOVE_CLASS; 15 | uint32 CRIT_RATE; 16 | uint32 VOLATILITY; 17 | string NAME; 18 | IEffect EFFECT; 19 | } 20 | -------------------------------------------------------------------------------- /docs/thots_2.md: -------------------------------------------------------------------------------- 1 | # Taking Notes on Raph Koster 2 | 3 | Okay so we want to encourage players towards some sort of goal. So progress or showing how they can get there is useful. 4 | 5 | What does progress mean? It means players should work towards better understanding the game, and better knowledge of how things work. It means they should be less suprised over time, as they make better predictions. 6 | 7 | For STOMP, this means we need: 8 | - to change how players interact with the game (e.g. difficulty curve) 9 | - to give them more complex patterns to undersand and deal with 10 | 11 | To this end, we want to give them feedback about how well they are doing (experience design matters here). 12 | -------------------------------------------------------------------------------- /src/ICommitManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "./Structs.sol"; 5 | 6 | interface ICommitManager { 7 | function commitMove(bytes32 battleKey, bytes32 moveHash) external; 8 | function revealMove(bytes32 battleKey, uint8 moveIndex, bytes32 salt, uint240 extraData, bool autoExecute) 9 | external; 10 | function getCommitment(bytes32 battleKey, address player) external view returns (bytes32 moveHash, uint256 turnId); 11 | function getMoveCountForBattleState(bytes32 battleKey, address player) external view returns (uint256); 12 | function getLastMoveTimestampForPlayer(bytes32 battleKey, address player) external view returns (uint256); 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | permissions: {} 4 | 5 | on: 6 | pull_request: 7 | 8 | env: 9 | FOUNDRY_PROFILE: ci 10 | 11 | jobs: 12 | check: 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | permissions: 16 | contents: read 17 | steps: 18 | - uses: actions/checkout@v5 19 | with: 20 | persist-credentials: false 21 | submodules: recursive 22 | 23 | - name: Install Foundry 24 | uses: foundry-rs/foundry-toolchain@v1 25 | 26 | - name: Show Forge version 27 | run: forge --version 28 | 29 | - name: Run Forge build 30 | run: forge build 31 | 32 | - name: Run Forge tests 33 | run: forge test -vvv 34 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /src/teams/ITeamRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "../Structs.sol"; 5 | 6 | import "../abilities/IAbility.sol"; 7 | import "../moves/IMoveSet.sol"; 8 | import "./IMonRegistry.sol"; 9 | 10 | interface ITeamRegistry { 11 | function getMonRegistry() external returns (IMonRegistry); 12 | function getTeam(address player, uint256 teamIndex) external returns (Mon[] memory); 13 | function getTeams(address p0, uint256 p0TeamIndex, address p1, uint256 p1TeamIndex) external returns (Mon[] memory, Mon[] memory); 14 | function getTeamCount(address player) external returns (uint256); 15 | function getMonRegistryIndicesForTeam(address player, uint256 teamIndex) external returns (uint256[] memory); 16 | } 17 | -------------------------------------------------------------------------------- /test/mocks/EffectAbility.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {IEngine} from "../../src/IEngine.sol"; 6 | import {IAbility} from "../../src/abilities/IAbility.sol"; 7 | import {IEffect} from "../../src/effects/IEffect.sol"; 8 | 9 | contract EffectAbility is IAbility { 10 | IEngine immutable ENGINE; 11 | IEffect immutable EFFECT; 12 | 13 | constructor(IEngine _ENGINE, IEffect _EFFECT) { 14 | ENGINE = _ENGINE; 15 | EFFECT = _EFFECT; 16 | } 17 | 18 | function name() external pure returns (string memory) { 19 | return ""; 20 | } 21 | 22 | function activateOnSwitch(bytes32, uint256 playerIndex, uint256 monIndex) external { 23 | ENGINE.addEffect(playerIndex, monIndex, EFFECT, ""); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | solc_version = '0.8.28' 3 | src = 'src' 4 | out = 'out' 5 | libs = ['lib'] 6 | optimizer = true 7 | optimizer_runs = 4294967295 8 | # @see {@link https://github.com/foundry-rs/foundry/issues/4060} 9 | auto_detect_remappings = false 10 | bytecode_hash = "none" 11 | cbor_metadata = false 12 | sparse_mode = false 13 | build_info = true 14 | via_ir = true 15 | 16 | [lint] 17 | exclude_lints = ["unsafe-typecast", "unaliased-plain-import", "mixed-case-variable", "mixed-case-function", "asm-keccak256", "pascal-case-struct", "screaming-snake-case-immutable", "incorrect-shift"] 18 | 19 | [profile.default.optimizer_details] 20 | constantOptimizer = true 21 | yul = true 22 | 23 | [profile.default.optimizer_details.yulDetails] 24 | stackAllocation = true 25 | 26 | [fmt] 27 | sort_imports = true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | /broadcast 10 | 11 | log.txt 12 | 13 | # Dotenv file 14 | .env 15 | .DS_Store 16 | 17 | # Setup 18 | input.txt 19 | output.txt 20 | ts.txt 21 | 22 | # Ignore pycache 23 | processing/__pycache__/** 24 | 25 | # Everything in drool 26 | drool/* 27 | 28 | # Keep imgs/ 29 | !drool/imgs 30 | !drool/imgs/** 31 | 32 | # Keep main data 33 | !drool/abilities.csv 34 | !drool/index.html 35 | !drool/mon-analysis.js 36 | !drool/mon-full.js 37 | !drool/mon-moves.js 38 | !drool/mon-stats.js 39 | !drool/mons.csv 40 | !drool/moves.csv 41 | !drool/style.css 42 | !drool/types.csv 43 | !drool/type-data.js 44 | !drool/utils.js 45 | !drool/combine.py 46 | !drool/switch-animation.py 47 | !drool/docs -------------------------------------------------------------------------------- /docs/stomp_intro.md: -------------------------------------------------------------------------------- 1 | ### WTF IS STOMP 2 | 3 | Stomp is a fully onchain game heavily inspired by monster battlers like Pokemon. It's turn-based and PVP. You build a team of mons, and you battle against other people in real time. 4 | 5 | I want Stomp to be an exciting technical milestone in the legacy of interesting EVM applications. I realize this is ambitious and maybe a little crazy. 6 | 7 | We have collectively as an industry tried to make web3 gaming a "thing" for the past 3 cycles. Teams have raised $XXX million collectively to fund various games and/or studios. And almost all of them have shutdown. 8 | 9 | - https://x.com/PirateNation/status/1957529124102357014 10 | - https://x.com/playchronoforge/status/1998501250464362517 11 | - https://x.com/tax_cuts/status/1888991002536411332 12 | - https://x.com/latticexyz/status/1827031013505069421 13 | -------------------------------------------------------------------------------- /src/moves/IMoveSet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "../Enums.sol"; 5 | import "../Structs.sol"; 6 | 7 | interface IMoveSet { 8 | function name() external view returns (string memory); 9 | function move(bytes32 battleKey, uint256 attackerPlayerIndex, uint240 extraData, uint256 rng) external; 10 | function priority(bytes32 battleKey, uint256 attackerPlayerIndex) external view returns (uint32); 11 | function stamina(bytes32 battleKey, uint256 attackerPlayerIndex, uint256 monIndex) external view returns (uint32); 12 | function moveType(bytes32 battleKey) external view returns (Type); 13 | function isValidTarget(bytes32 battleKey, uint240 extraData) external view returns (bool); 14 | function moveClass(bytes32 battleKey) external view returns (MoveClass); 15 | function extraDataType() external view returns (ExtraDataType); 16 | } 17 | -------------------------------------------------------------------------------- /src/DefaultRuleset.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "./Constants.sol"; 5 | import "./Structs.sol"; 6 | import "./moves/IMoveSet.sol"; 7 | 8 | import {IEngine} from "./IEngine.sol"; 9 | import {IRuleset} from "./IRuleset.sol"; 10 | 11 | import {IEffect} from "./effects/IEffect.sol"; 12 | 13 | contract DefaultRuleset is IRuleset { 14 | IEngine immutable ENGINE; 15 | 16 | IEffect[] public effects; 17 | 18 | constructor(IEngine _ENGINE, IEffect[] memory _effects) { 19 | ENGINE = _ENGINE; 20 | for (uint256 i; i < _effects.length; i++) { 21 | effects.push(_effects[i]); 22 | } 23 | } 24 | 25 | function getInitialGlobalEffects() external view returns (IEffect[] memory, bytes32[] memory) { 26 | bytes32[] memory data = new bytes32[](effects.length); 27 | return (effects, data); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/mons/embursa/HeatBeaconLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | 10 | library HeatBeaconLib { 11 | function _getKey(uint256 playerIndex) internal pure returns (bytes32) { 12 | return keccak256(abi.encode(playerIndex, "HEAT_BEACON")); 13 | } 14 | 15 | function _getPriorityBoost(IEngine engine, uint256 playerIndex) internal view returns (uint32) { 16 | uint192 value = engine.getGlobalKV(engine.battleKeyForWrite(), _getKey(playerIndex)); 17 | return value == 1 ? 1 : 0; 18 | } 19 | 20 | function _setPriorityBoost(IEngine engine, uint256 playerIndex) internal { 21 | engine.setGlobalKV(_getKey(playerIndex), 1); 22 | } 23 | 24 | function _clearPriorityBoost(IEngine engine, uint256 playerIndex) internal { 25 | engine.setGlobalKV(_getKey(playerIndex), 0); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /script/Surgery.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/Script.sol"; 5 | 6 | import {IEngine} from "../src/IEngine.sol"; 7 | import {ITypeCalculator} from "../src/types/ITypeCalculator.sol"; 8 | import {OkayCPU} from "../src/cpu/OkayCPU.sol"; 9 | import {ICPURNG} from "../src/rng/ICPURNG.sol"; 10 | 11 | struct DeployData { 12 | string name; 13 | address contractAddress; 14 | } 15 | 16 | contract Surgery is Script { 17 | DeployData[] deployedContracts; 18 | 19 | function run() external returns (DeployData[] memory) { 20 | vm.startBroadcast(); 21 | 22 | // Deploy new OkayCPU 23 | OkayCPU okayCPU = new OkayCPU(4, IEngine(vm.envAddress("ENGINE")), ICPURNG(address(0)), ITypeCalculator(vm.envAddress("TYPE_CALCULATOR"))); 24 | deployedContracts.push(DeployData({name: "OKAY CPU", contractAddress: address(okayCPU)})); 25 | 26 | vm.stopBroadcast(); 27 | return deployedContracts; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/teams/IMonRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "../Structs.sol"; 5 | 6 | import "../abilities/IAbility.sol"; 7 | import "../moves/IMoveSet.sol"; 8 | 9 | interface IMonRegistry { 10 | function getMonData(uint256 monId) 11 | external 12 | view 13 | returns (MonStats memory mon, address[] memory moves, address[] memory abilities); 14 | function getMonStats(uint256 monId) external view returns (MonStats memory); 15 | function getMonMetadata(uint256 monId, bytes32 key) external view returns (bytes32); 16 | function getMonCount() external view returns (uint256); 17 | function getMonIds(uint256 start, uint256 end) external view returns (uint256[] memory); 18 | function isValidMove(uint256 monId, IMoveSet move) external view returns (bool); 19 | function isValidAbility(uint256 monId, IAbility ability) external view returns (bool); 20 | function validateMon(Mon memory m, uint256 monId) external view returns (bool); 21 | } 22 | -------------------------------------------------------------------------------- /test/mocks/InstantDeathEffect.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/Enums.sol"; 6 | import "../../src/Structs.sol"; 7 | 8 | import {IEngine} from "../../src/IEngine.sol"; 9 | import {BasicEffect} from "../../src/effects/BasicEffect.sol"; 10 | 11 | contract InstantDeathEffect is BasicEffect { 12 | IEngine immutable ENGINE; 13 | 14 | constructor(IEngine _ENGINE) { 15 | ENGINE = _ENGINE; 16 | } 17 | 18 | function name() external pure override returns (string memory) { 19 | return "Instant Death"; 20 | } 21 | 22 | // Should run at end of round 23 | function shouldRunAtStep(EffectStep r) external pure override returns (bool) { 24 | return r == EffectStep.RoundEnd; 25 | } 26 | 27 | function onRoundEnd(uint256, bytes32, uint256 targetIndex, uint256 monIndex) 28 | external 29 | override 30 | returns (bytes32 updatedExtraData, bool removeAfterRun) 31 | { 32 | ENGINE.updateMonState(targetIndex, monIndex, MonStateIndexName.IsKnockedOut, 1); 33 | return (bytes32(0), true); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /drool/mons.csv: -------------------------------------------------------------------------------- 1 | Id,Name,HP,Attack,Defense,SpecialAttack,SpecialDefense,Speed,Type1,Type2,BST,Flavor 2 | 0,Ghouliath,303,157,202,151,202,181,Yang,Fire,1196,"Often found in dark places like caves and OTC trading desks." 3 | 1,Inutia,351,171,189,175,192,229,Wild,NA,1307,"A little jumpy. Nose is always wet." 4 | 2,Malalien,258,121,125,322,151,308,Cyber,NA,1285,"Do not approach or be approached." 5 | 3,Iblivion,277,188,164,240,168,256,Yin,Air,1293,"Many say it has great potential for growth." 6 | 4,Gorillax,407,302,175,112,176,129,Earth,NA,1301,"Big." 7 | 5,Sofabbi,333,180,201,120,269,175,Nature,NA,1278,"Their carrots make for a good stew." 8 | 6,Pengym,371,212,191,233,172,149,Ice,NA,1328,"Even their ice contains a lot of protein." 9 | 7,Embursa,420,141,220,190,161,111,Fire,NA,1243,"It will use even its own fur as fuel for its fire." 10 | 8,Volthare,310,120,184,255,176,311,Lightning,Cyber,1356,"Its mental circuitry is often scattered because of its high speed." 11 | 9,Aurox,400,150,230,100,220,100,Metal,NA,1200,"Its gold sheen never fades away, serving as a symbol for optimism." 12 | 10,Xmon,311,123,179,222,185,285,Cosmic,NA,1305,"bGJoIHBuYSBmcnIgdmcgdmEgbGJoZSBxZXJuemY=" -------------------------------------------------------------------------------- /docs/links.md: -------------------------------------------------------------------------------- 1 | # References 2 | 3 | ## Move references 4 | 5 | [smogon competive move list](https://www.smogon.com/cap/articles/competitive_moves) 6 | 7 | - overview of moves used in competitive play 8 | 9 | ## Pixel art 10 | 11 | [Pixel joint forum post](https://pixeljoint.com/forum/forum_posts.asp?TID=11299) 12 | 13 | - Overview of various art techniques 14 | 15 | [pokemon sage sprites](https://capx.fandom.com/wiki/Sprites) 16 | 17 | - full fleshed custom region + sprites + splash art 18 | 19 | [slynrd tutorials](https://www.slynyrd.com/pixelblog-catalogue) 20 | 21 | - lots of great overviews of different pieces 22 | 23 | [64x64 pokemon sprites with sugimori palettes](https://www.pokecommunity.com/threads/sugimori-palettes-the-ds-style-64x64-pok%C3%A9mon-sprite-resource.336945/) 24 | 25 | [saint11 celeste artist tutorials](https://saint11.art/blog/pixel-art-tutorials/) 26 | 27 | [smogon create a pokemon sprites](https://www.smogon.com/dex/ss/formats/cap/) 28 | 29 | - very good metagame articles / rationale when designing mons 30 | 31 | [pokemon design philosophy](https://forums.thousandroads.net/threads/caniss-thoughts-on-pok%C3%A9mon-design.1886/) 32 | 33 | - overview of the different types of pokemon designs 34 | -------------------------------------------------------------------------------- /src/mons/volthare/Overclock.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * - On swap in, check if storm is already applied globally 3 | * - If so, adjust its duration to be 3 (never more than 3) 4 | * - Storm is a global effect: 5 | * - When applied, boosts friendly team's speed, decreases friendly team's sp def 6 | * - On friendly mon swap in, does the same thing 7 | * - These boosts use the normal stat boost (which clears automatically on switch) 8 | * - On clear, reset all stat boosts on mons still in 9 | */ 10 | 11 | // SPDX-License-Identifier: AGPL-3.0 12 | 13 | pragma solidity ^0.8.0; 14 | 15 | import {IEngine} from "../../IEngine.sol"; 16 | import {IAbility} from "../../abilities/IAbility.sol"; 17 | import {Storm} from "../../effects/weather/Storm.sol"; 18 | 19 | contract Overclock is IAbility { 20 | IEngine immutable ENGINE; 21 | Storm immutable STORM; 22 | 23 | constructor(IEngine _ENGINE, Storm _STORM) { 24 | ENGINE = _ENGINE; 25 | STORM = _STORM; 26 | } 27 | 28 | function name() public pure override returns (string memory) { 29 | return "Overclock"; 30 | } 31 | 32 | function activateOnSwitch(bytes32, uint256 playerIndex, uint256) external override { 33 | STORM.applyStorm(playerIndex); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/mons/gorillax/Blow.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | 10 | import {IEffect} from "../../effects/IEffect.sol"; 11 | import {StandardAttack} from "../../moves/StandardAttack.sol"; 12 | import {ATTACK_PARAMS} from "../../moves/StandardAttackStructs.sol"; 13 | import {ITypeCalculator} from "../../types/ITypeCalculator.sol"; 14 | 15 | contract Blow is StandardAttack { 16 | constructor(IEngine _ENGINE, ITypeCalculator _TYPE_CALCULATOR) 17 | StandardAttack( 18 | address(msg.sender), 19 | _ENGINE, 20 | _TYPE_CALCULATOR, 21 | ATTACK_PARAMS({ 22 | NAME: "Blow", 23 | BASE_POWER: 70, 24 | STAMINA_COST: 2, 25 | MOVE_TYPE: Type.Air, 26 | MOVE_CLASS: MoveClass.Physical, 27 | PRIORITY: DEFAULT_PRIORITY, 28 | CRIT_RATE: DEFAULT_CRIT_RATE, 29 | VOLATILITY: DEFAULT_VOL, 30 | ACCURACY: 100, 31 | EFFECT_ACCURACY: 0, 32 | EFFECT: IEffect(address(0)) 33 | }) 34 | ) 35 | {} 36 | } 37 | -------------------------------------------------------------------------------- /src/mons/inutia/BigBite.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | 10 | import {IEffect} from "../../effects/IEffect.sol"; 11 | import {StandardAttack} from "../../moves/StandardAttack.sol"; 12 | import {ATTACK_PARAMS} from "../../moves/StandardAttackStructs.sol"; 13 | import {ITypeCalculator} from "../../types/ITypeCalculator.sol"; 14 | 15 | contract BigBite is StandardAttack { 16 | constructor(IEngine _ENGINE, ITypeCalculator _TYPE_CALCULATOR) 17 | StandardAttack( 18 | address(msg.sender), 19 | _ENGINE, 20 | _TYPE_CALCULATOR, 21 | ATTACK_PARAMS({ 22 | NAME: "Big Bite", 23 | BASE_POWER: 85, 24 | STAMINA_COST: 2, 25 | ACCURACY: 100, 26 | MOVE_TYPE: Type.Wild, 27 | MOVE_CLASS: MoveClass.Physical, 28 | PRIORITY: DEFAULT_PRIORITY, 29 | CRIT_RATE: DEFAULT_CRIT_RATE, 30 | VOLATILITY: DEFAULT_VOL, 31 | EFFECT_ACCURACY: 0, 32 | EFFECT: IEffect(address(0)) 33 | }) 34 | ) 35 | {} 36 | } 37 | -------------------------------------------------------------------------------- /src/mons/gorillax/PoundGround.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | 10 | import {IEffect} from "../../effects/IEffect.sol"; 11 | import {StandardAttack} from "../../moves/StandardAttack.sol"; 12 | import {ATTACK_PARAMS} from "../../moves/StandardAttackStructs.sol"; 13 | import {ITypeCalculator} from "../../types/ITypeCalculator.sol"; 14 | 15 | contract PoundGround is StandardAttack { 16 | constructor(IEngine _ENGINE, ITypeCalculator _TYPE_CALCULATOR) 17 | StandardAttack( 18 | address(msg.sender), 19 | _ENGINE, 20 | _TYPE_CALCULATOR, 21 | ATTACK_PARAMS({ 22 | NAME: "Pound Ground", 23 | BASE_POWER: 95, 24 | STAMINA_COST: 3, 25 | ACCURACY: 100, 26 | MOVE_TYPE: Type.Earth, 27 | MOVE_CLASS: MoveClass.Physical, 28 | PRIORITY: DEFAULT_PRIORITY, 29 | CRIT_RATE: DEFAULT_CRIT_RATE, 30 | VOLATILITY: DEFAULT_VOL, 31 | EFFECT_ACCURACY: 0, 32 | EFFECT: IEffect(address(0)) 33 | }) 34 | ) 35 | {} 36 | } 37 | -------------------------------------------------------------------------------- /src/mons/gorillax/ThrowPebble.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | 10 | import {IEffect} from "../../effects/IEffect.sol"; 11 | import {StandardAttack} from "../../moves/StandardAttack.sol"; 12 | import {ATTACK_PARAMS} from "../../moves/StandardAttackStructs.sol"; 13 | import {ITypeCalculator} from "../../types/ITypeCalculator.sol"; 14 | 15 | contract ThrowPebble is StandardAttack { 16 | constructor(IEngine _ENGINE, ITypeCalculator _TYPE_CALCULATOR) 17 | StandardAttack( 18 | address(msg.sender), 19 | _ENGINE, 20 | _TYPE_CALCULATOR, 21 | ATTACK_PARAMS({ 22 | NAME: "Throw Pebble", 23 | BASE_POWER: 40, 24 | STAMINA_COST: 1, 25 | ACCURACY: 100, 26 | MOVE_TYPE: Type.Earth, 27 | MOVE_CLASS: MoveClass.Physical, 28 | PRIORITY: DEFAULT_PRIORITY, 29 | CRIT_RATE: DEFAULT_CRIT_RATE, 30 | VOLATILITY: DEFAULT_VOL, 31 | EFFECT_ACCURACY: 0, 32 | EFFECT: IEffect(address(0)) 33 | }) 34 | ) 35 | {} 36 | } 37 | -------------------------------------------------------------------------------- /src/mons/ghouliath/Osteoporosis.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | 10 | import {IEffect} from "../../effects/IEffect.sol"; 11 | import {StandardAttack} from "../../moves/StandardAttack.sol"; 12 | import {ATTACK_PARAMS} from "../../moves/StandardAttackStructs.sol"; 13 | import {ITypeCalculator} from "../../types/ITypeCalculator.sol"; 14 | 15 | contract Osteoporosis is StandardAttack { 16 | constructor(IEngine _ENGINE, ITypeCalculator _TYPE_CALCULATOR) 17 | StandardAttack( 18 | address(msg.sender), 19 | _ENGINE, 20 | _TYPE_CALCULATOR, 21 | ATTACK_PARAMS({ 22 | NAME: "Osteoporosis", 23 | BASE_POWER: 90, 24 | STAMINA_COST: 2, 25 | ACCURACY: 100, 26 | MOVE_TYPE: Type.Yang, 27 | MOVE_CLASS: MoveClass.Physical, 28 | PRIORITY: DEFAULT_PRIORITY, 29 | CRIT_RATE: DEFAULT_CRIT_RATE, 30 | VOLATILITY: DEFAULT_VOL, 31 | EFFECT_ACCURACY: 0, 32 | EFFECT: IEffect(address(0)) 33 | }) 34 | ) 35 | {} 36 | } 37 | -------------------------------------------------------------------------------- /src/mons/pengym/ChillOut.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | 10 | import {IEffect} from "../../effects/IEffect.sol"; 11 | import {StandardAttack} from "../../moves/StandardAttack.sol"; 12 | import {ATTACK_PARAMS} from "../../moves/StandardAttackStructs.sol"; 13 | import {ITypeCalculator} from "../../types/ITypeCalculator.sol"; 14 | 15 | contract ChillOut is StandardAttack { 16 | constructor(IEngine ENGINE, ITypeCalculator TYPE_CALCULATOR, IEffect FROSTBITE_STATUS) 17 | StandardAttack( 18 | address(msg.sender), 19 | ENGINE, 20 | TYPE_CALCULATOR, 21 | ATTACK_PARAMS({ 22 | NAME: "Chill Out", 23 | BASE_POWER: 0, 24 | STAMINA_COST: 2, 25 | ACCURACY: 90, 26 | MOVE_TYPE: Type.Ice, 27 | MOVE_CLASS: MoveClass.Other, 28 | PRIORITY: DEFAULT_PRIORITY, 29 | CRIT_RATE: DEFAULT_CRIT_RATE, 30 | VOLATILITY: DEFAULT_VOL, 31 | EFFECT_ACCURACY: 100, 32 | EFFECT: FROSTBITE_STATUS 33 | }) 34 | ) 35 | {} 36 | } 37 | -------------------------------------------------------------------------------- /test/mocks/InstantDeathOnSwitchInEffect.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/Enums.sol"; 6 | import "../../src/Structs.sol"; 7 | 8 | import {IEngine} from "../../src/IEngine.sol"; 9 | import {BasicEffect} from "../../src/effects/BasicEffect.sol"; 10 | 11 | contract InstantDeathOnSwitchInEffect is BasicEffect { 12 | IEngine immutable ENGINE; 13 | 14 | constructor(IEngine _ENGINE) { 15 | ENGINE = _ENGINE; 16 | } 17 | 18 | function name() external pure override returns (string memory) { 19 | return "Instant Death On Switch"; 20 | } 21 | 22 | // Should run at end of round 23 | function shouldRunAtStep(EffectStep r) external pure override returns (bool) { 24 | return r == EffectStep.OnMonSwitchIn; 25 | } 26 | 27 | // NOTE: ONLY RUN ON GLOBAL EFFECTS (mons have their Ability as their own hook to apply an effect on switch in) 28 | function onMonSwitchIn(uint256, bytes32, uint256 targetIndex, uint256 monIndex) 29 | external 30 | override 31 | returns (bytes32 updatedExtraData, bool removeAfterRun) 32 | { 33 | ENGINE.updateMonState(targetIndex, monIndex, MonStateIndexName.IsKnockedOut, 1); 34 | return (bytes32(0), true); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/mons/volthare/Electrocute.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | 10 | import {IEffect} from "../../effects/IEffect.sol"; 11 | import {StandardAttack} from "../../moves/StandardAttack.sol"; 12 | import {ATTACK_PARAMS} from "../../moves/StandardAttackStructs.sol"; 13 | import {ITypeCalculator} from "../../types/ITypeCalculator.sol"; 14 | 15 | contract Electrocute is StandardAttack { 16 | constructor(IEngine ENGINE, ITypeCalculator TYPE_CALCULATOR, IEffect ZAP_STATUS) 17 | StandardAttack( 18 | address(msg.sender), 19 | ENGINE, 20 | TYPE_CALCULATOR, 21 | ATTACK_PARAMS({ 22 | NAME: "Electrocute", 23 | BASE_POWER: 90, 24 | STAMINA_COST: 2, 25 | ACCURACY: 100, 26 | MOVE_TYPE: Type.Lightning, 27 | MOVE_CLASS: MoveClass.Special, 28 | PRIORITY: DEFAULT_PRIORITY, 29 | CRIT_RATE: DEFAULT_CRIT_RATE, 30 | VOLATILITY: DEFAULT_VOL, 31 | EFFECT_ACCURACY: 10, 32 | EFFECT: ZAP_STATUS 33 | }) 34 | ) 35 | {} 36 | } 37 | -------------------------------------------------------------------------------- /src/mons/ghouliath/InfernalFlame.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | 10 | import {IEffect} from "../../effects/IEffect.sol"; 11 | import {StandardAttack} from "../../moves/StandardAttack.sol"; 12 | import {ATTACK_PARAMS} from "../../moves/StandardAttackStructs.sol"; 13 | import {ITypeCalculator} from "../../types/ITypeCalculator.sol"; 14 | 15 | contract InfernalFlame is StandardAttack { 16 | constructor(IEngine ENGINE, ITypeCalculator TYPE_CALCULATOR, IEffect BURN_STATUS) 17 | StandardAttack( 18 | address(msg.sender), 19 | ENGINE, 20 | TYPE_CALCULATOR, 21 | ATTACK_PARAMS({ 22 | NAME: "Infernal Flame", 23 | BASE_POWER: 120, 24 | STAMINA_COST: 2, 25 | ACCURACY: 85, 26 | MOVE_TYPE: Type.Fire, 27 | MOVE_CLASS: MoveClass.Special, 28 | PRIORITY: DEFAULT_PRIORITY, 29 | CRIT_RATE: DEFAULT_CRIT_RATE, 30 | VOLATILITY: DEFAULT_VOL, 31 | EFFECT_ACCURACY: 30, 32 | EFFECT: BURN_STATUS 33 | }) 34 | ) 35 | {} 36 | } 37 | -------------------------------------------------------------------------------- /src/mons/sofabbi/UnexpectedCarrot.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | 10 | import {IEffect} from "../../effects/IEffect.sol"; 11 | import {StandardAttack} from "../../moves/StandardAttack.sol"; 12 | import {ATTACK_PARAMS} from "../../moves/StandardAttackStructs.sol"; 13 | import {ITypeCalculator} from "../../types/ITypeCalculator.sol"; 14 | 15 | contract UnexpectedCarrot is StandardAttack { 16 | constructor(IEngine _ENGINE, ITypeCalculator _TYPE_CALCULATOR) 17 | StandardAttack( 18 | address(msg.sender), 19 | _ENGINE, 20 | _TYPE_CALCULATOR, 21 | ATTACK_PARAMS({ 22 | NAME: "Unexpected Carrot", 23 | BASE_POWER: 120, 24 | STAMINA_COST: 4, 25 | ACCURACY: 100, 26 | MOVE_TYPE: Type.Nature, 27 | MOVE_CLASS: MoveClass.Physical, 28 | PRIORITY: DEFAULT_PRIORITY, 29 | CRIT_RATE: DEFAULT_CRIT_RATE, 30 | VOLATILITY: DEFAULT_VOL, 31 | EFFECT_ACCURACY: 0, 32 | EFFECT: IEffect(address(0)) 33 | }) 34 | ) 35 | {} 36 | } 37 | -------------------------------------------------------------------------------- /test/mocks/AfterDamageReboundEffect.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/Enums.sol"; 6 | import "../../src/Structs.sol"; 7 | 8 | import {IEngine} from "../../src/IEngine.sol"; 9 | import {BasicEffect} from "../../src/effects/BasicEffect.sol"; 10 | 11 | contract AfterDamageReboundEffect is BasicEffect { 12 | IEngine immutable ENGINE; 13 | 14 | constructor(IEngine _ENGINE) { 15 | ENGINE = _ENGINE; 16 | } 17 | 18 | // Should run at end of round 19 | function shouldRunAtStep(EffectStep r) external pure override returns (bool) { 20 | return r == EffectStep.AfterDamage; 21 | } 22 | 23 | // NOTE: CURRENTLY ONLY RUN LOCALLY ON MONS (global effects do not have this hook) 24 | function onAfterDamage(uint256, bytes32 extraData, uint256 targetIndex, uint256 monIndex, int32) 25 | external 26 | override 27 | returns (bytes32, bool) 28 | { 29 | // Heals for all damage done 30 | int32 currentDamage = 31 | ENGINE.getMonStateForBattle(ENGINE.battleKeyForWrite(), targetIndex, monIndex, MonStateIndexName.Hp); 32 | ENGINE.updateMonState(targetIndex, monIndex, MonStateIndexName.Hp, currentDamage * -1); 33 | return (extraData, false); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/mons/malalien/InfiniteLove.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | 10 | import {IEffect} from "../../effects/IEffect.sol"; 11 | import {StandardAttack} from "../../moves/StandardAttack.sol"; 12 | import {ATTACK_PARAMS} from "../../moves/StandardAttackStructs.sol"; 13 | import {ITypeCalculator} from "../../types/ITypeCalculator.sol"; 14 | 15 | contract InfiniteLove is StandardAttack { 16 | constructor(IEngine _ENGINE, ITypeCalculator _TYPE_CALCULATOR, IEffect _SLEEP_STATUS) 17 | StandardAttack( 18 | address(msg.sender), 19 | _ENGINE, 20 | _TYPE_CALCULATOR, 21 | ATTACK_PARAMS({ 22 | NAME: "Infinite Love", 23 | BASE_POWER: 90, 24 | STAMINA_COST: 3, 25 | ACCURACY: 100, 26 | MOVE_TYPE: Type.Cosmic, 27 | MOVE_CLASS: MoveClass.Special, 28 | PRIORITY: DEFAULT_PRIORITY, 29 | CRIT_RATE: DEFAULT_CRIT_RATE, 30 | VOLATILITY: DEFAULT_VOL, 31 | EFFECT_ACCURACY: 100, 32 | EFFECT: _SLEEP_STATUS 33 | }) 34 | ) 35 | {} 36 | } 37 | -------------------------------------------------------------------------------- /src/mons/malalien/FederalInvestigation.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | 10 | import {IEffect} from "../../effects/IEffect.sol"; 11 | import {StandardAttack} from "../../moves/StandardAttack.sol"; 12 | import {ATTACK_PARAMS} from "../../moves/StandardAttackStructs.sol"; 13 | import {ITypeCalculator} from "../../types/ITypeCalculator.sol"; 14 | 15 | contract FederalInvestigation is StandardAttack { 16 | constructor(IEngine _ENGINE, ITypeCalculator _TYPE_CALCULATOR) 17 | StandardAttack( 18 | address(msg.sender), 19 | _ENGINE, 20 | _TYPE_CALCULATOR, 21 | ATTACK_PARAMS({ 22 | NAME: "Federal Investigation", 23 | BASE_POWER: 100, 24 | STAMINA_COST: 3, 25 | ACCURACY: 100, 26 | MOVE_TYPE: Type.Cyber, 27 | MOVE_CLASS: MoveClass.Special, 28 | PRIORITY: DEFAULT_PRIORITY, 29 | CRIT_RATE: DEFAULT_CRIT_RATE, 30 | VOLATILITY: DEFAULT_VOL, 31 | EFFECT_ACCURACY: 0, 32 | EFFECT: IEffect(address(0)) 33 | }) 34 | ) 35 | {} 36 | } 37 | -------------------------------------------------------------------------------- /src/mons/malalien/NegativeThoughts.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | 10 | import {IEffect} from "../../effects/IEffect.sol"; 11 | import {StandardAttack} from "../../moves/StandardAttack.sol"; 12 | import {ATTACK_PARAMS} from "../../moves/StandardAttackStructs.sol"; 13 | import {ITypeCalculator} from "../../types/ITypeCalculator.sol"; 14 | 15 | contract NegativeThoughts is StandardAttack { 16 | constructor(IEngine _ENGINE, ITypeCalculator _TYPE_CALCULATOR, IEffect _PANIC_STATUS) 17 | StandardAttack( 18 | address(msg.sender), 19 | _ENGINE, 20 | _TYPE_CALCULATOR, 21 | ATTACK_PARAMS({ 22 | NAME: "Infinite Love", 23 | BASE_POWER: 80, 24 | STAMINA_COST: 3, 25 | ACCURACY: 100, 26 | MOVE_TYPE: Type.Math, 27 | MOVE_CLASS: MoveClass.Special, 28 | PRIORITY: DEFAULT_PRIORITY, 29 | CRIT_RATE: DEFAULT_CRIT_RATE, 30 | VOLATILITY: DEFAULT_VOL, 31 | EFFECT_ACCURACY: 10, 32 | EFFECT: _PANIC_STATUS 33 | }) 34 | ) 35 | {} 36 | } 37 | -------------------------------------------------------------------------------- /src/cpu/PlayerCPU.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import {IEngine} from "../IEngine.sol"; 5 | 6 | import {ICPURNG} from "../rng/ICPURNG.sol"; 7 | import {CPU} from "./CPU.sol"; 8 | 9 | import {RevealedMove} from "../Structs.sol"; 10 | 11 | contract PlayerCPU is CPU { 12 | mapping(bytes32 battleKey => RevealedMove) private declaredMoveForBattle; 13 | 14 | constructor(uint256 numMoves, IEngine engine, ICPURNG rng) CPU(numMoves, engine, rng) {} 15 | 16 | function setMove(bytes32 battleKey, uint8 moveIndex, uint240 extraData) external { 17 | if (msg.sender != ENGINE.getPlayersForBattle(battleKey)[0]) { 18 | revert NotP0(); 19 | } 20 | declaredMoveForBattle[battleKey] = RevealedMove({moveIndex: moveIndex, salt: "", extraData: extraData}); 21 | } 22 | 23 | /** 24 | * If it's turn 0, randomly selects a mon index to swap to 25 | * Otherwise, randomly selects a valid move, switch index, or no op 26 | */ 27 | function calculateMove(bytes32 battleKey, uint256) 28 | external 29 | view 30 | override 31 | returns (uint128 moveIndex, uint240 extraData) 32 | { 33 | return (declaredMoveForBattle[battleKey].moveIndex, declaredMoveForBattle[battleKey].extraData); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /drool/abilities.csv: -------------------------------------------------------------------------------- 1 | Name,Mon,Effect 2 | Rise From The Grave,Ghouliath,"Once per game, after being KO'ed, Ghouliath revives after 3 turns." 3 | Tinderclaws,Embursa,"After every move, Embursa has a 1/3 chance of getting burned. When burned, Embursa gains a 50% SpATK boost. When resting, Embursa heals from burn." 4 | Actus Reus,Malalien,"If Malalien KO's an opposing mon, they gain an Indictment. Upon KO, if they have an Indictment, Malalien cripples their murderer." 5 | Baselight,Iblivion,"At the end of every turn, Iblivion gains a Baselight point. Points can be used to empower moves." 6 | Carrot Harvest,Sofabbi,"At the end of every turn, Sofabbi has a 1/2 chance of regaining 1 stamina." 7 | Post-Workout,Pengym,"On swap out, automatically heal from certain status conditions. If healed, Pengym regains 1 stamina." 8 | Angery,Gorillax,"Each time Gorillax takes damage, they get Angerier. At 3 stacks, they heal for 1/6 of max HP." 9 | Preemptive Shock,Volthare,"When Volthare swaps in, they deal a small amount of damage to the opposing mon." 10 | Interweaving,Inutia,"When Inutia swaps in, the opposing mon's ATK decreases 10%. When Inutia swaps out, the opposing mon's SpATK decreases 10%." 11 | Up Only,Aurox,"Whenever Aurox takes damage, they gain a persistent 10% ATK boost." 12 | Dreamcatcher,Xmon,"Whenever Xmon gains stamina, heal 1/16 of max HP." 13 | -------------------------------------------------------------------------------- /test/mocks/TestTypeCalculator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "../../src/Enums.sol"; 5 | import {ITypeCalculator} from "../../src/types/ITypeCalculator.sol"; 6 | 7 | contract TestTypeCalculator is ITypeCalculator { 8 | 9 | uint256 constant private ZERO_VALUE = type(uint).max - 1; 10 | 11 | mapping(uint attacker => mapping(uint defender => uint multiplier)) private typeOverride; 12 | 13 | function setTypeEffectiveness(Type attacker, Type defender, uint256 value) public { 14 | if (value == 0) { 15 | typeOverride[uint(attacker)][uint(defender)] = ZERO_VALUE; 16 | } 17 | else { 18 | typeOverride[uint(attacker)][uint(defender)] = value; 19 | } 20 | } 21 | 22 | function getTypeEffectiveness(Type attacker, Type defender, uint32 basePower) external view returns (uint32) { 23 | uint256 effectiveness = typeOverride[uint(attacker)][uint(defender)]; 24 | if (effectiveness != 0) { 25 | if (effectiveness == ZERO_VALUE) { 26 | return 0; 27 | } 28 | else if (effectiveness == 5) { 29 | return basePower / 2; 30 | } 31 | else { 32 | return basePower * 2; 33 | } 34 | } 35 | return basePower; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/moves/StandardAttackFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "../Enums.sol"; 5 | import "../Structs.sol"; 6 | 7 | import {IEngine} from "../IEngine.sol"; 8 | 9 | import {Ownable} from "../lib/Ownable.sol"; 10 | import {ITypeCalculator} from "../types/ITypeCalculator.sol"; 11 | import {StandardAttack} from "./StandardAttack.sol"; 12 | import {ATTACK_PARAMS} from "./StandardAttackStructs.sol"; 13 | 14 | contract StandardAttackFactory is Ownable { 15 | IEngine public ENGINE; 16 | ITypeCalculator public TYPE_CALCULATOR; 17 | 18 | event StandardAttackCreated(address a); 19 | 20 | constructor(IEngine _ENGINE, ITypeCalculator _TYPE_CALCULATOR) { 21 | ENGINE = _ENGINE; 22 | TYPE_CALCULATOR = _TYPE_CALCULATOR; 23 | _initializeOwner(msg.sender); 24 | } 25 | 26 | function createAttack(ATTACK_PARAMS memory params) external returns (StandardAttack attack) { 27 | attack = new StandardAttack(msg.sender, ENGINE, TYPE_CALCULATOR, params); 28 | emit StandardAttackCreated(address(attack)); 29 | } 30 | 31 | function setEngine(IEngine _ENGINE) external onlyOwner { 32 | ENGINE = _ENGINE; 33 | } 34 | 35 | function setTypeCalculator(ITypeCalculator _TYPE_CALCULATOR) external onlyOwner { 36 | TYPE_CALCULATOR = _TYPE_CALCULATOR; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/mocks/TestTeamRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/Structs.sol"; 6 | 7 | import {IMonRegistry} from "../../src/teams/IMonRegistry.sol"; 8 | import {ITeamRegistry} from "../../src/teams/ITeamRegistry.sol"; 9 | 10 | contract TestTeamRegistry is ITeamRegistry { 11 | mapping(address => Mon[]) public teams; 12 | uint256[] indices; 13 | 14 | function setTeam(address player, Mon[] memory team) public { 15 | teams[player] = team; 16 | } 17 | 18 | function getTeam(address player, uint256) external view returns (Mon[] memory) { 19 | return teams[player]; 20 | } 21 | 22 | function getTeams(address p0, uint256, address p1, uint256) external view returns (Mon[] memory, Mon[] memory) { 23 | return (teams[p0], teams[p1]); 24 | } 25 | 26 | function getTeamCount(address player) external view returns (uint256) { 27 | return teams[player].length; 28 | } 29 | 30 | function getMonRegistry() external pure returns (IMonRegistry) { 31 | return IMonRegistry(address(0)); 32 | } 33 | 34 | function setIndices(uint256[] memory _indices) public { 35 | indices = _indices; 36 | } 37 | 38 | function getMonRegistryIndicesForTeam(address, uint256) external view returns (uint256[] memory) { 39 | return indices; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/IValidator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "./Structs.sol"; 5 | import "./teams/ITeamRegistry.sol"; 6 | 7 | interface IValidator { 8 | // Validates that e.g. there are X mons per team w/ Y moves each 9 | function validateGameStart( 10 | address p0, address p1, Mon[][] calldata teams, ITeamRegistry teamRegistry, uint256 p0TeamIndex, uint256 p1TeamIndex 11 | ) external returns (bool); 12 | 13 | // Validates that you can't switch to the same mon, you have enough stamina, the move isn't disabled, etc. 14 | function validatePlayerMove(bytes32 battleKey, uint256 moveIndex, uint256 playerIndex, uint240 extraData) 15 | external 16 | returns (bool); 17 | 18 | // Validates that a move selection is valid (specifically wrt stamina) 19 | function validateSpecificMoveSelection( 20 | bytes32 battleKey, 21 | uint256 moveIndex, 22 | uint256 playerIndex, 23 | uint240 extraData 24 | ) external returns (bool); 25 | 26 | // Validates that a switch is valid 27 | function validateSwitch(bytes32 battleKey, uint256 playerIndex, uint256 monToSwitchIndex) external returns (bool); 28 | 29 | // Validates that there is a valid timeout, returns address(0) if no winner, otherwise returns the winner 30 | function validateTimeout(bytes32 battleKey, uint256 presumedAFKPlayerIndex) external returns (address); 31 | } 32 | -------------------------------------------------------------------------------- /src/mons/volthare/PreemptiveShock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {Type, MoveClass} from "../../Enums.sol"; 6 | import {IAbility} from "../../abilities/IAbility.sol"; 7 | import {AttackCalculator} from "../../moves/AttackCalculator.sol"; 8 | import {IEngine} from "../../IEngine.sol"; 9 | import {ITypeCalculator} from "../../types/ITypeCalculator.sol"; 10 | 11 | contract PreemptiveShock is IAbility { 12 | 13 | IEngine immutable ENGINE; 14 | ITypeCalculator immutable TYPE_CALCULATOR; 15 | 16 | uint32 public constant BASE_POWER = 15; 17 | uint32 public constant DEFAULT_VOL = 10; 18 | 19 | constructor(IEngine _ENGINE, ITypeCalculator _TYPE_CALCULATOR) { 20 | ENGINE = _ENGINE; 21 | TYPE_CALCULATOR = _TYPE_CALCULATOR; 22 | } 23 | 24 | function name() public pure override returns (string memory) { 25 | return "Preemptive Shock"; 26 | } 27 | 28 | function activateOnSwitch(bytes32 battleKey, uint256 playerIndex, uint256) external override { 29 | AttackCalculator._calculateDamage( 30 | ENGINE, 31 | TYPE_CALCULATOR, 32 | battleKey, 33 | playerIndex, 34 | BASE_POWER, 35 | 100, 36 | DEFAULT_VOL, 37 | Type.Lightning, 38 | MoveClass.Physical, 39 | ENGINE.tempRNG(), 40 | 0 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/mocks/SingleInstanceEffect.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/Enums.sol"; 6 | import "../../src/Structs.sol"; 7 | 8 | import {IEngine} from "../../src/IEngine.sol"; 9 | import {BasicEffect} from "../../src/effects/BasicEffect.sol"; 10 | 11 | contract SingleInstanceEffect is BasicEffect { 12 | IEngine immutable ENGINE; 13 | 14 | constructor(IEngine _ENGINE) { 15 | ENGINE = _ENGINE; 16 | } 17 | 18 | function name() external pure override returns (string memory) { 19 | return "Instant Death"; 20 | } 21 | 22 | function shouldRunAtStep(EffectStep r) external pure override returns (bool) { 23 | return r == EffectStep.OnApply; 24 | } 25 | 26 | function onApply(uint256, bytes32, uint256 targetIndex, uint256 monIndex) 27 | external 28 | override 29 | returns (bytes32, bool removeAfterRun) 30 | { 31 | bytes32 indexHash = keccak256(abi.encode(targetIndex, monIndex)); 32 | ENGINE.setGlobalKV(indexHash, 1); 33 | return (bytes32(0), false); 34 | } 35 | 36 | function shouldApply(bytes32, uint256 targetIndex, uint256 monIndex) external view override returns (bool) { 37 | bytes32 indexHash = keccak256(abi.encode(targetIndex, monIndex)); 38 | uint192 value = ENGINE.getGlobalKV(ENGINE.battleKeyForWrite(), indexHash); 39 | return value == 0; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Enums.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | enum Type { 5 | Yin, 6 | Yang, 7 | Earth, 8 | Liquid, 9 | Fire, 10 | Metal, 11 | Ice, 12 | Nature, 13 | Lightning, 14 | Mythic, 15 | Air, 16 | Math, 17 | Cyber, 18 | Wild, 19 | Cosmic, 20 | None 21 | } 22 | 23 | enum GameStatus { 24 | Started, 25 | Ended 26 | } 27 | 28 | enum EffectStep { 29 | OnApply, 30 | RoundStart, 31 | RoundEnd, 32 | OnRemove, 33 | OnMonSwitchIn, 34 | OnMonSwitchOut, 35 | AfterDamage, 36 | AfterMove, 37 | OnUpdateMonState 38 | } 39 | 40 | enum MoveClass { 41 | Physical, 42 | Special, 43 | Self, 44 | Other 45 | } 46 | 47 | enum MonStateIndexName { 48 | Hp, 49 | Stamina, 50 | Speed, 51 | Attack, 52 | Defense, 53 | SpecialAttack, 54 | SpecialDefense, 55 | IsKnockedOut, 56 | ShouldSkipTurn, 57 | Type1, 58 | Type2 59 | } 60 | 61 | enum EffectRunCondition { 62 | SkipIfGameOver, // Default to always run 63 | SkipIfGameOverOrMonKO // Skips if mon is KO'ed 64 | } 65 | 66 | enum StatBoostType { 67 | Multiply, 68 | Divide 69 | } 70 | 71 | enum StatBoostFlag { 72 | Temp, 73 | Perm 74 | } 75 | 76 | enum EngineEventType { 77 | MoveMiss, 78 | MoveCrit, 79 | MoveTypeImmunity, 80 | None 81 | } 82 | 83 | enum ExtraDataType { 84 | None, 85 | SelfTeamIndex 86 | } 87 | -------------------------------------------------------------------------------- /src/lib/MappingAllocator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | abstract contract MappingAllocator { 5 | 6 | bytes32[] private freeStorageKeys; 7 | mapping(bytes32 => bytes32) private battleKeyToStorageKey; 8 | 9 | function _initializeStorageKey(bytes32 key) internal returns (bytes32) { 10 | uint256 numFreeKeys = freeStorageKeys.length; 11 | if (numFreeKeys == 0) { 12 | return key; 13 | } 14 | else { 15 | bytes32 freeKey = freeStorageKeys[numFreeKeys - 1]; 16 | freeStorageKeys.pop(); 17 | battleKeyToStorageKey[key] = freeKey; 18 | return freeKey; 19 | } 20 | } 21 | 22 | function _getStorageKey(bytes32 battleKey) internal view returns (bytes32) { 23 | bytes32 storageKey = battleKeyToStorageKey[battleKey]; 24 | if (storageKey == bytes32(0)) { 25 | return battleKey; 26 | } 27 | else { 28 | return storageKey; 29 | } 30 | } 31 | 32 | function _freeStorageKey(bytes32 battleKey) internal { 33 | bytes32 storageKey = _getStorageKey(battleKey); 34 | freeStorageKeys.push(storageKey); 35 | delete battleKeyToStorageKey[battleKey]; 36 | } 37 | 38 | function _freeStorageKey(bytes32 battleKey, bytes32 storageKey) internal { 39 | freeStorageKeys.push(storageKey); 40 | delete battleKeyToStorageKey[battleKey]; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/mocks/TempStatBoostEffect.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/Enums.sol"; 6 | import "../../src/Structs.sol"; 7 | 8 | import {IEngine} from "../../src/IEngine.sol"; 9 | import {BasicEffect} from "../../src/effects/BasicEffect.sol"; 10 | 11 | contract TempStatBoostEffect is BasicEffect { 12 | IEngine immutable ENGINE; 13 | 14 | constructor(IEngine _ENGINE) { 15 | ENGINE = _ENGINE; 16 | } 17 | 18 | function name() external pure override returns (string memory) { 19 | return ""; 20 | } 21 | 22 | // Should run at end of round and on apply 23 | function shouldRunAtStep(EffectStep r) external pure override returns (bool) { 24 | return (r == EffectStep.OnMonSwitchOut || r == EffectStep.OnApply); 25 | } 26 | 27 | function onApply(uint256, bytes32, uint256 targetIndex, uint256 monIndex) 28 | external 29 | override 30 | returns (bytes32 updatedExtraData, bool removeAfterRun) 31 | { 32 | ENGINE.updateMonState(targetIndex, monIndex, MonStateIndexName.Attack, 1); 33 | return (bytes32(0), false); 34 | } 35 | 36 | function onMonSwitchOut(uint256, bytes32, uint256 targetIndex, uint256 monIndex) 37 | external 38 | override 39 | returns (bytes32 updatedExtraData, bool removeAfterRun) 40 | { 41 | ENGINE.updateMonState(targetIndex, monIndex, MonStateIndexName.Attack, 1); 42 | return (bytes32(0), true); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C.H.O.M.P. (credibly hackable on-chain monster pvp) 2 | 3 | on-chain turn-based pvp battling game, (heavily) inspired by pokemon showdown x mugen 4 | 5 | ![ghouliath back img](drool/imgs/ghouliath_back.gif) 6 | ![ghouliath front img](drool/imgs/ghouliath_front.gif) 7 | 8 | designed to be highly extensible! 9 | 10 | write your own moves! 11 | 12 | your own mons! 13 | 14 | your own effects! 15 | 16 | your own hooks! 17 | 18 | general flow of the game is: 19 | - each turn, players simultaneously choose a move on their active mon. 20 | - moves can alter stats, do damage, or generally mutate game state in some way. 21 | - this continues until one player has all their mons KO'ed 22 | 23 | (think normal pokemon style) 24 | 25 | mechanical differences are: 26 | - extensible engine, write your own Effects or Moves or Hooks 27 | - far greater support for state-based moves / mechanics 28 | - stamina-based resource system instead of PP for balancing moves 29 | 30 | ## Getting Started 31 | 32 | This repo uses [foundry](https://book.getfoundry.sh/getting-started/installation). 33 | 34 | To get started: 35 | 36 | `forge install` 37 | 38 | `forge test` 39 | 40 | ## Main Components 41 | 42 | ### Engine.sol 43 | Main entry point for creating/advancing Battles. 44 | Handles executing moves to advance battle state. 45 | Stores global state / data available to all players. 46 | 47 | ## Game Flow 48 | General flow of battle: 49 | - p0 commits hash of a Move 50 | - p1 reveals their choice 51 | - p0 reveals their preimage 52 | - execute to advance game state -------------------------------------------------------------------------------- /test/mocks/InvalidMove.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/Constants.sol"; 6 | import "../../src/Enums.sol"; 7 | import "../../src/Structs.sol"; 8 | 9 | import {IEngine} from "../../src/IEngine.sol"; 10 | import {IMoveSet} from "../../src/moves/IMoveSet.sol"; 11 | 12 | contract InvalidMove is IMoveSet { 13 | IEngine immutable ENGINE; 14 | 15 | constructor(IEngine _ENGINE) { 16 | ENGINE = _ENGINE; 17 | } 18 | 19 | function name() external pure returns (string memory) { 20 | return "Effect Attack"; 21 | } 22 | 23 | function move(bytes32, uint256, uint240, uint256) external pure { 24 | // No-op 25 | } 26 | 27 | function priority(bytes32, uint256) external pure returns (uint32) { 28 | return 1; 29 | } 30 | 31 | function stamina(bytes32, uint256, uint256) external pure returns (uint32) { 32 | return 1; 33 | } 34 | 35 | function moveType(bytes32) external pure returns (Type) { 36 | return Type.Fire; 37 | } 38 | 39 | function isValidTarget(bytes32, uint240) external pure returns (bool) { 40 | return false; 41 | } 42 | 43 | function moveClass(bytes32) external pure returns (MoveClass) { 44 | return MoveClass.Physical; 45 | } 46 | 47 | function basePower(bytes32) external pure returns (uint32) { 48 | return 0; 49 | } 50 | 51 | function extraDataType() external pure returns (ExtraDataType) { 52 | return ExtraDataType.None; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/mocks/OneTurnStatBoost.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/Enums.sol"; 6 | import "../../src/Structs.sol"; 7 | 8 | import {IEngine} from "../../src/IEngine.sol"; 9 | import {BasicEffect} from "../../src/effects/BasicEffect.sol"; 10 | 11 | contract OneTurnStatBoost is BasicEffect { 12 | IEngine immutable ENGINE; 13 | 14 | constructor(IEngine _ENGINE) { 15 | ENGINE = _ENGINE; 16 | } 17 | 18 | function name() external pure override returns (string memory) { 19 | return ""; 20 | } 21 | 22 | // Should run at end of round and on apply 23 | function shouldRunAtStep(EffectStep r) external pure override returns (bool) { 24 | return (r == EffectStep.RoundEnd || r == EffectStep.OnApply); 25 | } 26 | 27 | // Adds a bonus 28 | function onApply(uint256, bytes32, uint256 targetIndex, uint256 monIndex) 29 | external 30 | override 31 | returns (bytes32 updatedExtraData, bool removeAfterRun) 32 | { 33 | ENGINE.updateMonState(targetIndex, monIndex, MonStateIndexName.Attack, 1); 34 | return (bytes32(0), false); 35 | } 36 | 37 | // Adds another bonus 38 | function onRoundEnd(uint256, bytes32, uint256 targetIndex, uint256 monIndex) 39 | external 40 | override 41 | returns (bytes32 updatedExtraData, bool removeAfterRun) 42 | { 43 | ENGINE.updateMonState(targetIndex, monIndex, MonStateIndexName.Attack, 1); 44 | return (bytes32(0), true); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/types/TypeCalculator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "../Enums.sol"; 5 | import {ITypeCalculator} from "./ITypeCalculator.sol"; 6 | 7 | contract TypeCalculator is ITypeCalculator { 8 | uint256 private constant MULTIPLIERS_1 = 9 | 98387940970013939441334902218489111171706662712193966632938886332804419114328; // First 128 type combinations 10 | uint256 private constant MULTIPLIERS_2 = 8859915444081173009646859022170283285020904064171667527525; // Remaining 97 type combinations 11 | 12 | function getTypeEffectiveness(Type attackerType, Type defenderType, uint32 basePower) 13 | external 14 | pure 15 | returns (uint32) 16 | { 17 | uint256 index = uint256(attackerType) * 15 + uint256(defenderType); 18 | uint256 shift; 19 | uint256 multipliers; 20 | 21 | if (index < 128) { 22 | shift = index * 2; 23 | multipliers = MULTIPLIERS_1; 24 | } else { 25 | shift = (index - 128) * 2; 26 | multipliers = MULTIPLIERS_2; 27 | } 28 | 29 | // Check the last 2 bits of the multipliers for the correct shift 30 | uint256 typeEffectivenessValue = uint32((multipliers >> shift) & 3); 31 | 32 | // Do the lookup to scale the value appropriately 33 | if (typeEffectivenessValue == 0) { 34 | return 0; 35 | } else if (typeEffectivenessValue == 1) { 36 | return basePower; 37 | } else if (typeEffectivenessValue == 2) { 38 | return basePower * 2; 39 | } else { 40 | return basePower / 2; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/mocks/OnUpdateMonStateHealEffect.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/Enums.sol"; 6 | import "../../src/Structs.sol"; 7 | 8 | import {IEngine} from "../../src/IEngine.sol"; 9 | import {BasicEffect} from "../../src/effects/BasicEffect.sol"; 10 | 11 | /** 12 | * @title OnUpdateMonStateHealEffect 13 | * @notice Mock effect that heals a mon's HP when its SpecialAttack stat is reduced 14 | * @dev This demonstrates the OnUpdateMonState lifecycle hook 15 | */ 16 | contract OnUpdateMonStateHealEffect is BasicEffect { 17 | IEngine immutable ENGINE; 18 | int32 public constant HEAL_AMOUNT = 5; 19 | 20 | constructor(IEngine _ENGINE) { 21 | ENGINE = _ENGINE; 22 | } 23 | 24 | function shouldRunAtStep(EffectStep r) external pure override returns (bool) { 25 | return r == EffectStep.OnUpdateMonState; 26 | } 27 | 28 | // WARNING: Avoid chaining this effect to prevent recursive calls 29 | // This effect is safe because it only heals HP, it doesn't trigger state updates that would recurse 30 | function onUpdateMonState( 31 | uint256, 32 | bytes32 extraData, 33 | uint256 playerIndex, 34 | uint256 monIndex, 35 | MonStateIndexName stateVarIndex, 36 | int32 valueToAdd 37 | ) external override returns (bytes32, bool) { 38 | // Only trigger if SpecialAttack is being reduced (negative valueToAdd) 39 | if (stateVarIndex == MonStateIndexName.SpecialAttack && valueToAdd < 0) { 40 | // Heal the mon by HEAL_AMOUNT 41 | ENGINE.updateMonState(playerIndex, monIndex, MonStateIndexName.Hp, HEAL_AMOUNT); 42 | } 43 | return (extraData, false); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/mons/volthare/DualShock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | import {IEffect} from "../../effects/IEffect.sol"; 10 | import {StandardAttack} from "../../moves/StandardAttack.sol"; 11 | import {ATTACK_PARAMS} from "../../moves/StandardAttackStructs.sol"; 12 | import {ITypeCalculator} from "../../types/ITypeCalculator.sol"; 13 | 14 | contract DualShock is StandardAttack { 15 | constructor(IEngine ENGINE, ITypeCalculator TYPE_CALCULATOR, IEffect ZAP_STATUS) 16 | StandardAttack( 17 | address(msg.sender), 18 | ENGINE, 19 | TYPE_CALCULATOR, 20 | ATTACK_PARAMS({ 21 | NAME: "Dual Shock", 22 | BASE_POWER: 60, 23 | STAMINA_COST: 0, 24 | ACCURACY: 100, 25 | MOVE_TYPE: Type.Lightning, 26 | MOVE_CLASS: MoveClass.Special, 27 | PRIORITY: DEFAULT_PRIORITY, 28 | CRIT_RATE: DEFAULT_CRIT_RATE, 29 | VOLATILITY: DEFAULT_VOL, 30 | EFFECT_ACCURACY: 0, 31 | EFFECT: IEffect(ZAP_STATUS) 32 | }) 33 | ) 34 | {} 35 | 36 | function move(bytes32 battleKey, uint256 attackerPlayerIndex, uint240 extraData, uint256 rng) 37 | public 38 | override 39 | { 40 | // Deal the damage 41 | super.move(battleKey, attackerPlayerIndex, extraData, rng); 42 | 43 | // Apply Zap to self 44 | uint256 activeMonIndex = ENGINE.getActiveMonIndexForBattleState(battleKey)[attackerPlayerIndex]; 45 | ENGINE.addEffect(attackerPlayerIndex, activeMonIndex, effect(battleKey), ""); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/cpu/RandomCPU.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import {IEngine} from "../IEngine.sol"; 5 | 6 | import {ICPURNG} from "../rng/ICPURNG.sol"; 7 | import {CPU} from "./CPU.sol"; 8 | 9 | import {RevealedMove} from "../Structs.sol"; 10 | 11 | contract RandomCPU is CPU { 12 | constructor(uint256 numMoves, IEngine engine, ICPURNG rng) CPU(numMoves, engine, rng) {} 13 | 14 | /** 15 | * If it's turn 0, randomly selects a mon index to swap to 16 | * Otherwise, randomly selects a valid move, switch index, or no op 17 | */ 18 | function calculateMove(bytes32 battleKey, uint256 playerIndex) 19 | external 20 | override 21 | returns (uint128 moveIndex, uint240 extraData) 22 | { 23 | (RevealedMove[] memory noOp, RevealedMove[] memory moves, RevealedMove[] memory switches) = calculateValidMoves(battleKey, playerIndex); 24 | 25 | // Merge all three arrays into one 26 | uint256 totalChoices = noOp.length + moves.length + switches.length; 27 | RevealedMove[] memory allChoices = new RevealedMove[](totalChoices); 28 | 29 | uint256 index = 0; 30 | for (uint256 i = 0; i < noOp.length; i++) { 31 | allChoices[index++] = noOp[i]; 32 | } 33 | for (uint256 i = 0; i < moves.length; i++) { 34 | allChoices[index++] = moves[i]; 35 | } 36 | for (uint256 i = 0; i < switches.length; i++) { 37 | allChoices[index++] = switches[i]; 38 | } 39 | 40 | // Select a random move from all choices 41 | uint256 randomIndex = 42 | RNG.getRNG(keccak256(abi.encode(nonceToUse++, battleKey, block.timestamp))) % allChoices.length; 43 | return (allChoices[randomIndex].moveIndex, allChoices[randomIndex].extraData); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/mons/ghouliath/WitherAway.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | 10 | import {IEffect} from "../../effects/IEffect.sol"; 11 | import {StandardAttack} from "../../moves/StandardAttack.sol"; 12 | import {ATTACK_PARAMS} from "../../moves/StandardAttackStructs.sol"; 13 | import {ITypeCalculator} from "../../types/ITypeCalculator.sol"; 14 | 15 | contract WitherAway is StandardAttack { 16 | constructor(IEngine ENGINE, ITypeCalculator TYPE_CALCULATOR, IEffect PANIC_STATUS) 17 | StandardAttack( 18 | address(msg.sender), 19 | ENGINE, 20 | TYPE_CALCULATOR, 21 | ATTACK_PARAMS({ 22 | NAME: "Wither Away", 23 | BASE_POWER: 60, 24 | STAMINA_COST: 3, 25 | ACCURACY: 100, 26 | MOVE_TYPE: Type.Yang, 27 | MOVE_CLASS: MoveClass.Special, 28 | PRIORITY: DEFAULT_PRIORITY, 29 | CRIT_RATE: DEFAULT_CRIT_RATE, 30 | VOLATILITY: DEFAULT_VOL, 31 | EFFECT_ACCURACY: 100, 32 | EFFECT: PANIC_STATUS 33 | }) 34 | ) 35 | {} 36 | 37 | function move(bytes32 battleKey, uint256 attackerPlayerIndex, uint240 extraData, uint256 rng) 38 | public 39 | override 40 | { 41 | // Deal the damage and inflict panic 42 | super.move(battleKey, attackerPlayerIndex, extraData, rng); 43 | 44 | // Also inflict panic on self 45 | uint256 activeMonIndex = ENGINE.getActiveMonIndexForBattleState(battleKey)[attackerPlayerIndex]; 46 | ENGINE.addEffect(attackerPlayerIndex, activeMonIndex, effect(battleKey), ""); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /docs/patch_2.md: -------------------------------------------------------------------------------- 1 | ## Effects 2 | 3 | Storm --> Rename to Overclock (your team becomes overclocked) 4 | 5 | # Inutia 6 | Add swap move (remove shrine strike) --> Hit And Dip [x] 7 | 8 | # Malalien 9 | - Infinite Love: 2 -> 3 stamina [x] 10 | 11 | # Iblivion: 12 | Full rework as Baselight required attacking which meant every other turn you had to swap moves. 13 | NEW Ability: Baselight, starts at 1, gain 1 stack at the end of each turn, up to a max of 3. 14 | CONSIDER: making consuming the stack optional 15 | - Brightback: consumes 1 stack to heal for 50% of damage dealt, power 70, stamina 2 16 | - NEW Move: Unbounded Strike: consumes 3 stacks, power 130, stamina 3, otherwise power 80, stamina 2 17 | - NEW Move: Renormalize, 0 stamina, Raises all stats by X0% where X is Baselight level. Fails if already active. 18 | - NEW Move: Loop, 0 stamina, sets Baselight level to be 3 (i.e. triggers up to 2 heal stacks), -1 priority, resets all status changes (and resets Renormalize) 19 | 20 | # Embursa 21 | - Change ability from Split The Pot to something different [x] 22 | - NEW Ability: Tinderclaws 23 | After every move, Embursa has a 1/3 chance of getting burned. When burned, Embursa gains a 50% SpATK boost. 24 | When resting, Embursa heals from burn. 25 | 26 | # Volthare: 27 | Storm on switch in is a little too strong given the lack of other speed tiers / priority moves. 28 | - Fix dual shock to correctly inflict Zap on self at end of turn 29 | - NEW Ability: Preemptive Shock: On switch in (after the first one), deals a small amount of damage to the opposing mon. 30 | CONSIDER: renaming Storm to be something that implies it isn't global (both teams can have an instance active) 31 | - NEW Move: Overclock, sets Overclock on the battlefield. Overclock lasts 5 turns, boosts Speed and decreases Def and ZDef. 32 | - NEW Move: tbd 33 | - Rename Move: Megavoltage: Clears Storm and deals massive damage, otherwise accuracy is 50% -------------------------------------------------------------------------------- /src/mons/inutia/HitAndDip.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | 10 | import {IEffect} from "../../effects/IEffect.sol"; 11 | import {StandardAttack} from "../../moves/StandardAttack.sol"; 12 | import {ATTACK_PARAMS} from "../../moves/StandardAttackStructs.sol"; 13 | import {ITypeCalculator} from "../../types/ITypeCalculator.sol"; 14 | 15 | contract HitAndDip is StandardAttack { 16 | constructor(IEngine ENGINE, ITypeCalculator TYPE_CALCULATOR) 17 | StandardAttack( 18 | address(msg.sender), 19 | ENGINE, 20 | TYPE_CALCULATOR, 21 | ATTACK_PARAMS({ 22 | NAME: "Hit And Dip", 23 | BASE_POWER: 30, 24 | STAMINA_COST: 2, 25 | ACCURACY: 100, 26 | MOVE_TYPE: Type.Mythic, 27 | MOVE_CLASS: MoveClass.Special, 28 | PRIORITY: DEFAULT_PRIORITY, 29 | CRIT_RATE: DEFAULT_CRIT_RATE, 30 | VOLATILITY: DEFAULT_VOL, 31 | EFFECT_ACCURACY: 100, 32 | EFFECT: IEffect(address(0)) 33 | }) 34 | ) 35 | {} 36 | 37 | function move(bytes32 battleKey, uint256 attackerPlayerIndex, uint240 extraData, uint256 rng) 38 | public 39 | override 40 | { 41 | // Deal the damage 42 | (int32 damage,) = _move(battleKey, attackerPlayerIndex, rng); 43 | 44 | if (damage > 0) { 45 | // extraData contains the swap index as raw uint240 46 | uint256 swapIndex = uint256(extraData); 47 | ENGINE.switchActiveMon(attackerPlayerIndex, swapIndex); 48 | } 49 | } 50 | 51 | function extraDataType() external pure override returns (ExtraDataType) { 52 | return ExtraDataType.SelfTeamIndex; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/mocks/EditEffectAttack.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {ExtraDataType, MoveClass, Type} from "../../src/Enums.sol"; 6 | import {IEngine} from "../../src/IEngine.sol"; 7 | import {IMoveSet} from "../../src/moves/IMoveSet.sol"; 8 | 9 | contract EditEffectAttack is IMoveSet { 10 | 11 | IEngine immutable ENGINE; 12 | 13 | constructor(IEngine _ENGINE) { 14 | ENGINE = _ENGINE; 15 | } 16 | 17 | function name() external pure returns (string memory) { 18 | return "Edit Effect Attack"; 19 | } 20 | 21 | function move(bytes32, uint256, uint240 extraData, uint256) external { 22 | // Unpack extraData: lower 80 bits = targetIndex, next 80 bits = monIndex, upper 80 bits = effectIndex 23 | uint256 targetIndex = uint256(extraData) & ((1 << 80) - 1); 24 | uint256 monIndex = (uint256(extraData) >> 80) & ((1 << 80) - 1); 25 | uint256 effectIndex = (uint256(extraData) >> 160) & ((1 << 80) - 1); 26 | ENGINE.editEffect(targetIndex, monIndex, effectIndex, bytes32(uint256(69))); 27 | } 28 | 29 | function priority(bytes32, uint256) external pure returns (uint32) { 30 | return 1; 31 | } 32 | 33 | function stamina(bytes32, uint256, uint256) external pure returns (uint32) { 34 | return 0; 35 | } 36 | 37 | function moveType(bytes32) external pure returns (Type) { 38 | return Type.Fire; 39 | } 40 | 41 | function isValidTarget(bytes32, uint240) external pure returns (bool) { 42 | return true; 43 | } 44 | 45 | function moveClass(bytes32) external pure returns (MoveClass) { 46 | return MoveClass.Physical; 47 | } 48 | 49 | function basePower(bytes32) external pure returns (uint32) { 50 | return 0; 51 | } 52 | 53 | function extraDataType() external pure returns (ExtraDataType) { 54 | return ExtraDataType.None; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/mons/volthare/RoundTrip.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | 10 | import {IEffect} from "../../effects/IEffect.sol"; 11 | import {StandardAttack} from "../../moves/StandardAttack.sol"; 12 | import {ATTACK_PARAMS} from "../../moves/StandardAttackStructs.sol"; 13 | import {ITypeCalculator} from "../../types/ITypeCalculator.sol"; 14 | 15 | contract RoundTrip is StandardAttack { 16 | constructor(IEngine ENGINE, ITypeCalculator TYPE_CALCULATOR) 17 | StandardAttack( 18 | address(msg.sender), 19 | ENGINE, 20 | TYPE_CALCULATOR, 21 | ATTACK_PARAMS({ 22 | NAME: "Round Trip", 23 | BASE_POWER: 30, 24 | STAMINA_COST: 1, 25 | ACCURACY: 100, 26 | MOVE_TYPE: Type.Lightning, 27 | MOVE_CLASS: MoveClass.Special, 28 | PRIORITY: DEFAULT_PRIORITY, 29 | CRIT_RATE: DEFAULT_CRIT_RATE, 30 | VOLATILITY: DEFAULT_VOL, 31 | EFFECT_ACCURACY: 0, 32 | EFFECT: IEffect(address(0)) 33 | }) 34 | ) 35 | {} 36 | 37 | function move(bytes32 battleKey, uint256 attackerPlayerIndex, uint240 extraData, uint256 rng) 38 | public 39 | override 40 | { 41 | // Deal the damage 42 | (int32 damage,) = _move(battleKey, attackerPlayerIndex, rng); 43 | 44 | if (damage > 0) { 45 | // extraData contains the swap index as raw uint240 46 | uint256 swapIndex = uint256(extraData); 47 | ENGINE.switchActiveMon(attackerPlayerIndex, swapIndex); 48 | } 49 | } 50 | 51 | function extraDataType() external pure override returns (ExtraDataType) { 52 | return ExtraDataType.SelfTeamIndex; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/teams/GachaTeamRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import {IOwnableMon} from "../gacha/IOwnableMon.sol"; 5 | import {Ownable} from "../lib/Ownable.sol"; 6 | import "./LookupTeamRegistry.sol"; 7 | 8 | contract GachaTeamRegistry is LookupTeamRegistry, Ownable { 9 | IOwnableMon immutable OWNER_LOOKUP; 10 | 11 | error NotOwner(); 12 | 13 | constructor(Args memory args, IOwnableMon _OWNER_LOOKUP) LookupTeamRegistry(args) { 14 | OWNER_LOOKUP = _OWNER_LOOKUP; 15 | _initializeOwner(msg.sender); 16 | } 17 | 18 | function _validateOwnership(uint256[] memory monIndices) internal view { 19 | for (uint256 i; i < monIndices.length; i++) { 20 | if (!OWNER_LOOKUP.isOwner(msg.sender, monIndices[i])) { 21 | revert NotOwner(); 22 | } 23 | } 24 | } 25 | 26 | function createTeam(uint256[] memory monIndices) public override { 27 | _validateOwnership(monIndices); 28 | super.createTeam(monIndices); 29 | } 30 | 31 | function createTeamForUser(address user, uint256[] memory monIndices) external onlyOwner { 32 | _createTeamForUser(user, monIndices); 33 | } 34 | 35 | // TODO: For prod we won't want this 36 | function updateTeamForUser(uint256[] memory newMonIndices) external { 37 | uint256[] memory teamMonIndicesToOverride = new uint256[](MONS_PER_TEAM); 38 | for (uint256 i; i < MONS_PER_TEAM; i++) { 39 | teamMonIndicesToOverride[i] = i; 40 | } 41 | super.updateTeam(0, teamMonIndicesToOverride, newMonIndices); 42 | } 43 | 44 | function updateTeam(uint256 teamIndex, uint256[] memory teamMonIndicesToOverride, uint256[] memory newMonIndices) 45 | public 46 | override 47 | { 48 | _validateOwnership(newMonIndices); 49 | super.updateTeam(teamIndex, teamMonIndicesToOverride, newMonIndices); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/mons/embursa/SetAblaze.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | 10 | import {IEffect} from "../../effects/IEffect.sol"; 11 | import {StandardAttack} from "../../moves/StandardAttack.sol"; 12 | import {ATTACK_PARAMS} from "../../moves/StandardAttackStructs.sol"; 13 | import {ITypeCalculator} from "../../types/ITypeCalculator.sol"; 14 | import {HeatBeaconLib} from "./HeatBeaconLib.sol"; 15 | 16 | contract SetAblaze is StandardAttack { 17 | constructor(IEngine ENGINE, ITypeCalculator TYPE_CALCULATOR, IEffect BURN_STATUS) 18 | StandardAttack( 19 | address(msg.sender), 20 | ENGINE, 21 | TYPE_CALCULATOR, 22 | ATTACK_PARAMS({ 23 | NAME: "Set Ablaze", 24 | BASE_POWER: 90, 25 | STAMINA_COST: 3, 26 | ACCURACY: 100, 27 | MOVE_TYPE: Type.Fire, 28 | MOVE_CLASS: MoveClass.Special, 29 | PRIORITY: DEFAULT_PRIORITY, 30 | CRIT_RATE: DEFAULT_CRIT_RATE, 31 | VOLATILITY: DEFAULT_VOL, 32 | EFFECT_ACCURACY: 30, 33 | EFFECT: BURN_STATUS 34 | }) 35 | ) 36 | {} 37 | 38 | function move(bytes32 battleKey, uint256 attackerPlayerIndex, uint240 args, uint256 rng) public override { 39 | super.move(battleKey, attackerPlayerIndex, args, rng); 40 | // Clear the priority boost 41 | if (HeatBeaconLib._getPriorityBoost(ENGINE, attackerPlayerIndex) == 1) { 42 | HeatBeaconLib._clearPriorityBoost(ENGINE, attackerPlayerIndex); 43 | } 44 | } 45 | 46 | function priority(bytes32, uint256 attackerPlayerIndex) public view override returns (uint32) { 47 | return DEFAULT_PRIORITY + HeatBeaconLib._getPriorityBoost(ENGINE, attackerPlayerIndex); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Constants.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | // Move index uses 7 bits (0-127), with upper bit of uint8 reserved for isRealTurn flag 5 | // Special move indices (not shifted): 6 | // 126 = 2^7 - 2, 125 = 2^7 - 3 7 | uint8 constant NO_OP_MOVE_INDEX = 126; 8 | uint8 constant SWITCH_MOVE_INDEX = 125; 9 | 10 | // Regular move indices (0-3) are stored +1 to avoid zero-value ambiguity: 11 | // Stored 0 = "no move set", Stored 1 = move 0, Stored 2 = move 1, etc. 12 | // When storing: if moveIndex < SWITCH_MOVE_INDEX, store moveIndex + 1 13 | // When reading: if storedIndex < SWITCH_MOVE_INDEX, return storedIndex - 1 14 | uint8 constant MOVE_INDEX_OFFSET = 1; 15 | 16 | // Bit mask and shift for packed move index (lower 7 bits = moveIndex, bit 7 = isRealTurn) 17 | uint8 constant MOVE_INDEX_MASK = 0x7F; 18 | uint8 constant IS_REAL_TURN_BIT = 0x80; 19 | 20 | uint256 constant SWITCH_PRIORITY = 6; 21 | uint32 constant DEFAULT_PRIORITY = 3; 22 | uint32 constant DEFAULT_STAMINA = 5; 23 | 24 | uint32 constant CRIT_NUM = 3; 25 | uint32 constant CRIT_DENOM = 2; 26 | uint32 constant DEFAULT_CRIT_RATE = 5; 27 | 28 | uint32 constant DEFAULT_VOL = 10; 29 | uint32 constant DEFAULT_ACCURACY = 100; 30 | 31 | int32 constant CLEARED_MON_STATE_SENTINEL = type(int32).max - 1; 32 | 33 | // Packed MonState with all deltas set to CLEARED_MON_STATE_SENTINEL and bools set to false 34 | // Layout (LSB to MSB): hpDelta, staminaDelta, speedDelta, attackDelta, defenceDelta, specialAttackDelta, specialDefenceDelta, isKnockedOut, shouldSkipTurn 35 | // 7 x 0x7FFFFFFE (int32.max - 1) + 2 x 0x00 (false) 36 | uint256 constant PACKED_CLEARED_MON_STATE = 0x00007FFFFFFE7FFFFFFE7FFFFFFE7FFFFFFE7FFFFFFE7FFFFFFE7FFFFFFE; 37 | 38 | uint8 constant PLAYER_EFFECT_BITS = 6; 39 | uint8 constant MAX_EFFECTS_PER_MON = uint8(2 ** PLAYER_EFFECT_BITS) - 1; // 63 40 | uint256 constant EFFECT_SLOTS_PER_MON = 64; // Stride for per-mon effect storage (2^6) 41 | uint256 constant EFFECT_COUNT_MASK = 0x3F; // 6 bits = max count of 63 42 | 43 | address constant TOMBSTONE_ADDRESS = address(0xdead); -------------------------------------------------------------------------------- /src/effects/status/StatusEffect.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import {IEngine} from "../../IEngine.sol"; 5 | import {BasicEffect} from "../BasicEffect.sol"; 6 | import {StatusEffectLib} from "./StatusEffectLib.sol"; 7 | 8 | abstract contract StatusEffect is BasicEffect { 9 | IEngine immutable ENGINE; 10 | 11 | constructor(IEngine _ENGINE) { 12 | ENGINE = _ENGINE; 13 | } 14 | 15 | // Whether or not to add the effect if the step condition is met 16 | function shouldApply(bytes32, uint256 targetIndex, uint256 monIndex) public virtual view override returns (bool) { 17 | bytes32 battleKey = ENGINE.battleKeyForWrite(); 18 | bytes32 keyForMon = StatusEffectLib.getKeyForMonIndex(targetIndex, monIndex); 19 | 20 | // Get value from ENGINE KV 21 | uint192 monStatusFlag = ENGINE.getGlobalKV(battleKey, keyForMon); 22 | 23 | // Check if a status already exists for the mon 24 | if (monStatusFlag == 0) { 25 | return true; 26 | } else { 27 | // Otherwise return false 28 | return false; 29 | } 30 | } 31 | 32 | function onApply(uint256, bytes32, uint256 targetIndex, uint256 monIndex) 33 | public 34 | virtual 35 | override 36 | returns (bytes32 updatedExtraData, bool removeAfterRun) 37 | { 38 | bytes32 battleKey = ENGINE.battleKeyForWrite(); 39 | bytes32 keyForMon = StatusEffectLib.getKeyForMonIndex(targetIndex, monIndex); 40 | 41 | uint192 monValue = ENGINE.getGlobalKV(battleKey, keyForMon); 42 | if (monValue == 0) { 43 | // Set the global status flag to be the address of the status 44 | ENGINE.setGlobalKV(keyForMon, uint192(uint160(address(this)))); 45 | } 46 | } 47 | 48 | function onRemove(bytes32, uint256 targetIndex, uint256 monIndex) public virtual override { 49 | // On remove, reset the status flag 50 | ENGINE.setGlobalKV(StatusEffectLib.getKeyForMonIndex(targetIndex, monIndex), 0); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/mocks/SpAtkDebuffEffect.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import {EffectStep, MonStateIndexName, StatBoostFlag, StatBoostType} from "../../src/Enums.sol"; 5 | import {IEngine} from "../../src/IEngine.sol"; 6 | import {StatBoostToApply} from "../../src/Structs.sol"; 7 | 8 | import {StatusEffect} from "../../src/effects/status/StatusEffect.sol"; 9 | import {StatBoosts} from "../../src/effects/StatBoosts.sol"; 10 | 11 | contract SpAtkDebuffEffect is StatusEffect { 12 | uint8 constant SP_ATTACK_PERCENT = 50; 13 | 14 | StatBoosts immutable STAT_BOOST; 15 | 16 | constructor(IEngine engine, StatBoosts _STAT_BOOSTS) StatusEffect(engine) { 17 | STAT_BOOST = _STAT_BOOSTS; 18 | } 19 | 20 | function name() public pure override returns (string memory) { 21 | return "SpAtk Debuff"; 22 | } 23 | 24 | function shouldRunAtStep(EffectStep r) external pure override returns (bool) { 25 | return (r == EffectStep.OnApply || r == EffectStep.OnRemove); 26 | } 27 | 28 | function onApply(uint256, bytes32 extraData, uint256 targetIndex, uint256 monIndex) 29 | public 30 | override 31 | returns (bytes32 updatedExtraData, bool removeAfterRun) 32 | { 33 | // Reduce special attack by half 34 | StatBoostToApply[] memory statBoosts = new StatBoostToApply[](1); 35 | statBoosts[0] = StatBoostToApply({ 36 | stat: MonStateIndexName.SpecialAttack, 37 | boostPercent: SP_ATTACK_PERCENT, 38 | boostType: StatBoostType.Divide 39 | }); 40 | STAT_BOOST.addStatBoosts(targetIndex, monIndex, statBoosts, StatBoostFlag.Perm); 41 | 42 | // Do not update data 43 | return (extraData, false); 44 | } 45 | 46 | function onRemove(bytes32 data, uint256 targetIndex, uint256 monIndex) public override { 47 | super.onRemove(data, targetIndex, monIndex); 48 | 49 | // Reset the special attack reduction 50 | STAT_BOOST.removeStatBoosts(targetIndex, monIndex, StatBoostFlag.Perm); 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /test/mocks/GlobalEffectAttack.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/Constants.sol"; 6 | import "../../src/Enums.sol"; 7 | import "../../src/Structs.sol"; 8 | 9 | import {IEngine} from "../../src/IEngine.sol"; 10 | import {IEffect} from "../../src/effects/IEffect.sol"; 11 | import {IMoveSet} from "../../src/moves/IMoveSet.sol"; 12 | 13 | contract GlobalEffectAttack is IMoveSet { 14 | struct Args { 15 | Type TYPE; 16 | uint32 STAMINA_COST; 17 | uint32 PRIORITY; 18 | } 19 | 20 | IEngine immutable ENGINE; 21 | IEffect immutable EFFECT; 22 | Type immutable TYPE; 23 | uint32 immutable STAMINA_COST; 24 | uint32 immutable PRIORITY; 25 | 26 | constructor(IEngine _ENGINE, IEffect _EFFECT, Args memory args) { 27 | ENGINE = _ENGINE; 28 | EFFECT = _EFFECT; 29 | TYPE = args.TYPE; 30 | STAMINA_COST = args.STAMINA_COST; 31 | PRIORITY = args.PRIORITY; 32 | } 33 | 34 | function name() external pure returns (string memory) { 35 | return "Effect Attack"; 36 | } 37 | 38 | function move(bytes32, uint256, uint240, uint256) external { 39 | ENGINE.addEffect(2, 0, EFFECT, bytes32(0)); 40 | } 41 | 42 | function priority(bytes32, uint256) external view returns (uint32) { 43 | return PRIORITY; 44 | } 45 | 46 | function stamina(bytes32, uint256, uint256) external view returns (uint32) { 47 | return STAMINA_COST; 48 | } 49 | 50 | function moveType(bytes32) external view returns (Type) { 51 | return TYPE; 52 | } 53 | 54 | function isValidTarget(bytes32, uint240) external pure returns (bool) { 55 | return true; 56 | } 57 | 58 | function moveClass(bytes32) external pure returns (MoveClass) { 59 | return MoveClass.Physical; 60 | } 61 | 62 | function basePower(bytes32) external pure returns (uint32) { 63 | return 0; 64 | } 65 | 66 | function extraDataType() external pure returns (ExtraDataType) { 67 | return ExtraDataType.None; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/mons/xmon/ContagiousSlumber.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | import {IEffect} from "../../effects/IEffect.sol"; 10 | import {IMoveSet} from "../../moves/IMoveSet.sol"; 11 | 12 | contract ContagiousSlumber is IMoveSet { 13 | IEngine immutable ENGINE; 14 | IEffect immutable SLEEP_STATUS; 15 | 16 | constructor(IEngine _ENGINE, IEffect _SLEEP_STATUS) { 17 | ENGINE = _ENGINE; 18 | SLEEP_STATUS = _SLEEP_STATUS; 19 | } 20 | 21 | function name() public pure override returns (string memory) { 22 | return "Contagious Slumber"; 23 | } 24 | 25 | function move(bytes32 battleKey, uint256 attackerPlayerIndex, uint240, uint256) external { 26 | // Apply sleep to self 27 | uint256 activeMonIndex = ENGINE.getActiveMonIndexForBattleState(battleKey)[attackerPlayerIndex]; 28 | ENGINE.addEffect(attackerPlayerIndex, activeMonIndex, SLEEP_STATUS, ""); 29 | 30 | // Apply sleep to opponent 31 | uint256 defenderPlayerIndex = (attackerPlayerIndex + 1) % 2; 32 | uint256 defenderMonIndex = ENGINE.getActiveMonIndexForBattleState(battleKey)[defenderPlayerIndex]; 33 | ENGINE.addEffect(defenderPlayerIndex, defenderMonIndex, SLEEP_STATUS, ""); 34 | } 35 | 36 | function stamina(bytes32, uint256, uint256) external pure returns (uint32) { 37 | return 2; 38 | } 39 | 40 | function priority(bytes32, uint256) external pure returns (uint32) { 41 | return DEFAULT_PRIORITY; 42 | } 43 | 44 | function moveType(bytes32) public pure returns (Type) { 45 | return Type.Cosmic; 46 | } 47 | 48 | function moveClass(bytes32) public pure returns (MoveClass) { 49 | return MoveClass.Other; 50 | } 51 | 52 | function isValidTarget(bytes32, uint240) external pure returns (bool) { 53 | return true; 54 | } 55 | 56 | function extraDataType() external pure returns (ExtraDataType) { 57 | return ExtraDataType.None; 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | 1) Add tests / fix test to ensure battles cannot start/end on same timestamp 4 | 5 | --------------------------------------------------------------------------------------------- 6 | LATER FEATURES 7 | 8 | 0) fix the rng issue in the way StandardAttack decides which effects to apply (rn if an attack is only X% accurate, and an effect has a Y% chance, the lower X is, the higher Y should be (they shld be independent but we use the same rng source currently)) 9 | - should fix this, but am afraid of tests needing to be rewritten. 10 | 11 | --------------------------------------------------------------------------------------------- 12 | 13 | KNOWN BUGGY INTERACTIONS 14 | 15 | 1) If a move forces a switch and triggers before the other player can make its move, then the new mon will try to execute its move still 16 | - The Engine handles the case where the stamina is insufficient (it just skips the move) 17 | 18 | Ways to mitigate: 19 | - avoid forcing opponent swap outs unless it's at low priority 20 | - have the engine set the skipTurn flag on newly swapped in mons (if they have yet to run their turn yet) 21 | - look into this later 22 | - or do this w/ a custom condition that can set the flag, and then remove at end of turn 23 | 24 | 2) If an effect calls dealDamage() and triggers AfterDamage, it can potentially cause an infinite loop! 25 | (either if it calls dealDamage() on the opposing mon who also has a damaging attack with an AfterDamage trigger) 26 | 27 | Ways to mitigate: 28 | - deal damage directly (but skip calling dealDamage) on the Engine 29 | - avoid dealing damage in the afterDamage hook itself 30 | - maintain manual mutex to prevent infinite recurse 31 | 32 | 4) Malicious p0 can commit to a team that has an array of mons, but modify the mon moves prior to starting a battle (after p1 commits) 33 | 34 | Ways to mitigate: 35 | - Need to handle on the team registry angle probably 36 | - Atm, restricting it to the same array of mon indices reduces the attack surface somewhat 37 | - OR, just add the move indices to the integrity hash 38 | 39 | --------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------- /test/mocks/SkipTurnMove.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/Constants.sol"; 6 | import "../../src/Enums.sol"; 7 | import "../../src/Structs.sol"; 8 | 9 | import {IEngine} from "../../src/IEngine.sol"; 10 | import {IMoveSet} from "../../src/moves/IMoveSet.sol"; 11 | 12 | contract SkipTurnMove is IMoveSet { 13 | struct Args { 14 | Type TYPE; 15 | uint32 STAMINA_COST; 16 | uint32 PRIORITY; 17 | } 18 | 19 | IEngine immutable ENGINE; 20 | Type immutable TYPE; 21 | uint32 immutable STAMINA_COST; 22 | uint32 immutable PRIORITY; 23 | 24 | constructor(IEngine _ENGINE, Args memory args) { 25 | ENGINE = _ENGINE; 26 | TYPE = args.TYPE; 27 | STAMINA_COST = args.STAMINA_COST; 28 | PRIORITY = args.PRIORITY; 29 | } 30 | 31 | function name() external pure returns (string memory) { 32 | return "Skip Turn"; 33 | } 34 | 35 | function move(bytes32 battleKey, uint256 attackerPlayerIndex, uint240, uint256) external { 36 | uint256 targetIndex = (attackerPlayerIndex + 1) % 2; 37 | uint256 activeMonIndex = ENGINE.getActiveMonIndexForBattleState(battleKey)[targetIndex]; 38 | ENGINE.updateMonState(targetIndex, activeMonIndex, MonStateIndexName.ShouldSkipTurn, 1); 39 | } 40 | 41 | function priority(bytes32, uint256) external view returns (uint32) { 42 | return PRIORITY; 43 | } 44 | 45 | function stamina(bytes32, uint256, uint256) external view returns (uint32) { 46 | return STAMINA_COST; 47 | } 48 | 49 | function moveType(bytes32) external view returns (Type) { 50 | return TYPE; 51 | } 52 | 53 | function isValidTarget(bytes32, uint240) external pure returns (bool) { 54 | return true; 55 | } 56 | 57 | function moveClass(bytes32) external pure returns (MoveClass) { 58 | return MoveClass.Physical; 59 | } 60 | 61 | function basePower(bytes32) external pure returns (uint32) { 62 | return 0; 63 | } 64 | 65 | function extraDataType() external pure returns (ExtraDataType) { 66 | return ExtraDataType.None; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/mons/sofabbi/CarrotHarvest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {EffectStep} from "../../Enums.sol"; 6 | 7 | import {MonStateIndexName} from "../../Enums.sol"; 8 | import {EffectInstance} from "../../Structs.sol"; 9 | import {IEngine} from "../../IEngine.sol"; 10 | import {IAbility} from "../../abilities/IAbility.sol"; 11 | 12 | import {BasicEffect} from "../../effects/BasicEffect.sol"; 13 | import {IEffect} from "../../effects/IEffect.sol"; 14 | 15 | contract CarrotHarvest is IAbility, BasicEffect { 16 | uint256 constant CHANCE = 2; 17 | 18 | IEngine immutable ENGINE; 19 | 20 | constructor(IEngine _ENGINE) { 21 | ENGINE = _ENGINE; 22 | } 23 | 24 | // IAbility implementation 25 | function name() public pure override(IAbility, BasicEffect) returns (string memory) { 26 | return "Carrot Harvest"; 27 | } 28 | 29 | function activateOnSwitch(bytes32 battleKey, uint256 playerIndex, uint256 monIndex) external override { 30 | // Check if the effect has already been set for this mon 31 | (EffectInstance[] memory effects, ) = ENGINE.getEffects(battleKey, playerIndex, monIndex); 32 | for (uint256 i = 0; i < effects.length; i++) { 33 | if (address(effects[i].effect) == address(this)) { 34 | return; 35 | } 36 | } 37 | ENGINE.addEffect(playerIndex, monIndex, IEffect(address(this)), bytes32(0)); 38 | } 39 | 40 | // IEffect implementation 41 | function shouldRunAtStep(EffectStep step) external pure override returns (bool) { 42 | return step == EffectStep.RoundEnd; 43 | } 44 | 45 | // Regain stamina on round end, this can overheal stamina 46 | function onRoundEnd(uint256 rng, bytes32 extraData, uint256 targetIndex, uint256 monIndex) 47 | external 48 | override 49 | returns (bytes32 updatedExtraData, bool removeAfterRun) 50 | { 51 | if (rng % CHANCE == 1) { 52 | // Update the stamina of the mon 53 | ENGINE.updateMonState(targetIndex, monIndex, MonStateIndexName.Stamina, 1); 54 | } 55 | return (extraData, false); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /test/mocks/ReduceSpAtkMove.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/Constants.sol"; 6 | import "../../src/Enums.sol"; 7 | import "../../src/Structs.sol"; 8 | 9 | import {IEngine} from "../../src/IEngine.sol"; 10 | import {IMoveSet} from "../../src/moves/IMoveSet.sol"; 11 | 12 | /** 13 | * @title ReduceSpAtkMove 14 | * @notice Simple move that reduces the opposing mon's SpecialAttack stat by 1 15 | * @dev Used to test the OnUpdateMonState lifecycle hook 16 | */ 17 | contract ReduceSpAtkMove is IMoveSet { 18 | IEngine immutable ENGINE; 19 | 20 | constructor(IEngine _ENGINE) { 21 | ENGINE = _ENGINE; 22 | } 23 | 24 | function name() external pure returns (string memory) { 25 | return "Reduce SpAtk"; 26 | } 27 | 28 | function move(bytes32, uint256 attackerPlayerIndex, uint240, uint256) external { 29 | // Get the opposing player's index 30 | uint256 opposingPlayerIndex = (attackerPlayerIndex + 1) % 2; 31 | 32 | // Get the opposing player's active mon index 33 | uint256 opposingMonIndex = ENGINE.getActiveMonIndexForBattleState(ENGINE.battleKeyForWrite())[opposingPlayerIndex]; 34 | 35 | // Reduce the opposing mon's SpecialAttack by 1 36 | ENGINE.updateMonState(opposingPlayerIndex, opposingMonIndex, MonStateIndexName.SpecialAttack, -1); 37 | } 38 | 39 | function priority(bytes32, uint256) external pure returns (uint32) { 40 | return 0; 41 | } 42 | 43 | function stamina(bytes32, uint256, uint256) external pure returns (uint32) { 44 | return 0; 45 | } 46 | 47 | function moveType(bytes32) external pure returns (Type) { 48 | return Type.Math; 49 | } 50 | 51 | function isValidTarget(bytes32, uint240) external pure returns (bool) { 52 | return true; 53 | } 54 | 55 | function moveClass(bytes32) external pure returns (MoveClass) { 56 | return MoveClass.Other; 57 | } 58 | 59 | function basePower(bytes32) external pure returns (uint32) { 60 | return 0; 61 | } 62 | 63 | function extraDataType() external pure returns (ExtraDataType) { 64 | return ExtraDataType.None; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /script/SetupCPU.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/Script.sol"; 5 | 6 | import {GachaTeamRegistry} from "../src/teams/GachaTeamRegistry.sol"; 7 | import {GachaTeamRegistry} from "../src/teams/GachaTeamRegistry.sol"; 8 | 9 | struct DeployData { 10 | string name; 11 | address contractAddress; 12 | } 13 | 14 | contract SetupCPU is Script { 15 | DeployData[] deployedContracts; 16 | 17 | function run() external returns (DeployData[] memory) { 18 | vm.startBroadcast(); 19 | 20 | // Create team of Ghouliath, Malalien, Pengym, and Volthare for cpu player 21 | uint256[] memory monIndices = new uint256[](4); 22 | monIndices[0] = 0; // Ghouliath 23 | monIndices[1] = 2; // Malalien 24 | monIndices[2] = 6; // Pengym 25 | monIndices[3] = 8; // Volthare 26 | 27 | GachaTeamRegistry gachaTeamRegistry = GachaTeamRegistry(vm.envAddress("GACHA_TEAM_REGISTRY")); 28 | string[] memory cpuPlayers = new string[](1); 29 | cpuPlayers[0] = "OKAY_CPU"; 30 | // cpuPlayers[1] = "PLAYER_CPU"; 31 | // cpuPlayers[2] = "OKAY_CPU"; 32 | for (uint256 i; i < cpuPlayers.length; i++) { 33 | gachaTeamRegistry.createTeamForUser(vm.envAddress(cpuPlayers[i]), monIndices); 34 | } 35 | 36 | // Create alternative team 37 | monIndices[0] = 1; // Inutia 38 | monIndices[1] = 3; // Iblivion 39 | monIndices[2] = 4; // Gorillax 40 | monIndices[3] = 5; // Sofabbi 41 | 42 | for (uint256 i; i < cpuPlayers.length; i++) { 43 | gachaTeamRegistry.createTeamForUser(vm.envAddress(cpuPlayers[i]), monIndices); 44 | } 45 | 46 | // Create team of Embursa, Aurox, Xmon, and Ghouliath 47 | monIndices[0] = 7; // Embursa 48 | monIndices[1] = 9; // Aurox 49 | monIndices[2] = 10; // Ghouliath 50 | monIndices[3] = 0; // Gorillax 51 | 52 | for (uint256 i; i < cpuPlayers.length; i++) { 53 | gachaTeamRegistry.createTeamForUser(vm.envAddress(cpuPlayers[i]), monIndices); 54 | } 55 | 56 | vm.stopBroadcast(); 57 | 58 | return deployedContracts; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /test/mocks/SelfSwitchAndDamageMove.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/Constants.sol"; 6 | import "../../src/Enums.sol"; 7 | import "../../src/Structs.sol"; 8 | 9 | import {IEngine} from "../../src/IEngine.sol"; 10 | import {IMoveSet} from "../../src/moves/IMoveSet.sol"; 11 | 12 | contract SelfSwitchAndDamageMove is IMoveSet { 13 | 14 | IEngine immutable ENGINE; 15 | int32 immutable DAMAGE; 16 | 17 | constructor(IEngine _ENGINE, int32 power) { 18 | ENGINE = _ENGINE; 19 | DAMAGE = power; 20 | } 21 | 22 | function name() external pure returns (string memory) { 23 | return "Self Switch And Damage Move"; 24 | } 25 | 26 | function move(bytes32, uint256 attackerPlayerIndex, uint240 extraData, uint256) external { 27 | uint256 monToSwitchIndex = uint256(extraData); 28 | 29 | // Deal damage first to opponent 30 | uint256 otherPlayerIndex = (attackerPlayerIndex + 1) % 2; 31 | uint256 otherPlayerActiveMonIndex = 32 | ENGINE.getActiveMonIndexForBattleState(ENGINE.battleKeyForWrite())[otherPlayerIndex]; 33 | ENGINE.dealDamage(otherPlayerIndex, otherPlayerActiveMonIndex, DAMAGE); 34 | 35 | // Use the new switchActiveMon function 36 | ENGINE.switchActiveMon(attackerPlayerIndex, monToSwitchIndex); 37 | } 38 | 39 | function priority(bytes32, uint256) external pure returns (uint32) { 40 | return 0; 41 | } 42 | 43 | function stamina(bytes32, uint256, uint256) external pure returns (uint32) { 44 | return 0; 45 | } 46 | 47 | function moveType(bytes32) external pure returns (Type) { 48 | return Type.Fire; 49 | } 50 | 51 | function isValidTarget(bytes32, uint240) external pure returns (bool) { 52 | return true; 53 | } 54 | 55 | function moveClass(bytes32) external pure returns (MoveClass) { 56 | return MoveClass.Physical; 57 | } 58 | 59 | function basePower(bytes32) external view returns (uint32) { 60 | return uint32(DAMAGE); 61 | } 62 | 63 | function extraDataType() external pure returns (ExtraDataType) { 64 | return ExtraDataType.SelfTeamIndex; 65 | } 66 | } -------------------------------------------------------------------------------- /src/mons/aurox/BullRush.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | import {ITypeCalculator} from "../../types/ITypeCalculator.sol"; 10 | import {StandardAttack} from "../../moves/StandardAttack.sol"; 11 | import {ATTACK_PARAMS} from "../../moves/StandardAttackStructs.sol"; 12 | import {IEffect} from "../../effects/IEffect.sol"; 13 | 14 | contract BullRush is StandardAttack { 15 | int32 public constant SELF_DAMAGE_PERCENT = 10; // 10% of max HP 16 | 17 | constructor(IEngine ENGINE, ITypeCalculator TYPE_CALCULATOR) 18 | StandardAttack( 19 | address(msg.sender), 20 | ENGINE, 21 | TYPE_CALCULATOR, 22 | ATTACK_PARAMS({ 23 | NAME: "Bull Rush", 24 | BASE_POWER: 80, 25 | STAMINA_COST: 2, 26 | ACCURACY: 100, 27 | MOVE_TYPE: Type.Metal, 28 | MOVE_CLASS: MoveClass.Physical, 29 | PRIORITY: DEFAULT_PRIORITY, 30 | CRIT_RATE: DEFAULT_CRIT_RATE, 31 | VOLATILITY: DEFAULT_VOL, 32 | EFFECT_ACCURACY: 0, 33 | EFFECT: IEffect(address(0)) 34 | }) 35 | ) 36 | {} 37 | 38 | function move(bytes32 battleKey, uint256 attackerPlayerIndex, uint240, uint256 rng) 39 | public 40 | override 41 | { 42 | // Deal the damage to opponent 43 | (int32 damage,) = _move(battleKey, attackerPlayerIndex, rng); 44 | 45 | // Deal self-damage (10% of max HP) 46 | if (damage > 0) { 47 | uint256[] memory activeMonIndex = ENGINE.getActiveMonIndexForBattleState(battleKey); 48 | uint256 attackerMonIndex = activeMonIndex[attackerPlayerIndex]; 49 | 50 | int32 maxHp = int32( 51 | ENGINE.getMonValueForBattle(battleKey, attackerPlayerIndex, attackerMonIndex, MonStateIndexName.Hp) 52 | ); 53 | int32 selfDamage = (maxHp * SELF_DAMAGE_PERCENT) / 100; 54 | 55 | ENGINE.dealDamage(attackerPlayerIndex, attackerMonIndex, selfDamage); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/mons/malalien/TripleThink.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | import {StatBoostToApply} from "../../Structs.sol"; 8 | 9 | import {IEngine} from "../../IEngine.sol"; 10 | import {StatBoosts} from "../../effects/StatBoosts.sol"; 11 | import {IMoveSet} from "../../moves/IMoveSet.sol"; 12 | 13 | contract TripleThink is IMoveSet { 14 | uint8 public constant SP_ATTACK_BUFF_PERCENT = 75; 15 | 16 | IEngine immutable ENGINE; 17 | StatBoosts immutable STAT_BOOSTS; 18 | 19 | constructor(IEngine _ENGINE, StatBoosts _STAT_BOOSTS) { 20 | ENGINE = _ENGINE; 21 | STAT_BOOSTS = _STAT_BOOSTS; 22 | } 23 | 24 | function name() public pure override returns (string memory) { 25 | return "Triple Think"; 26 | } 27 | 28 | function move(bytes32 battleKey, uint256 attackerPlayerIndex, uint240, uint256) external { 29 | // Apply the buff 30 | uint256 activeMonIndex = ENGINE.getActiveMonIndexForBattleState(battleKey)[attackerPlayerIndex]; 31 | StatBoostToApply[] memory statBoosts = new StatBoostToApply[](1); 32 | statBoosts[0] = StatBoostToApply({ 33 | stat: MonStateIndexName.SpecialAttack, 34 | boostPercent: SP_ATTACK_BUFF_PERCENT, 35 | boostType: StatBoostType.Multiply 36 | }); 37 | STAT_BOOSTS.addStatBoosts(attackerPlayerIndex, activeMonIndex, statBoosts, StatBoostFlag.Temp); 38 | } 39 | 40 | function stamina(bytes32, uint256, uint256) external pure returns (uint32) { 41 | return 2; 42 | } 43 | 44 | function priority(bytes32, uint256) external pure returns (uint32) { 45 | return DEFAULT_PRIORITY; 46 | } 47 | 48 | function moveType(bytes32) public pure returns (Type) { 49 | return Type.Math; 50 | } 51 | 52 | function moveClass(bytes32) public pure returns (MoveClass) { 53 | return MoveClass.Self; 54 | } 55 | 56 | function isValidTarget(bytes32, uint240) external pure returns (bool) { 57 | return true; 58 | } 59 | 60 | function extraDataType() external pure returns (ExtraDataType) { 61 | return ExtraDataType.None; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/mocks/ForceSwitchMove.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/Constants.sol"; 6 | import "../../src/Enums.sol"; 7 | import "../../src/Structs.sol"; 8 | 9 | import {IEngine} from "../../src/IEngine.sol"; 10 | import {IMoveSet} from "../../src/moves/IMoveSet.sol"; 11 | 12 | contract ForceSwitchMove is IMoveSet { 13 | struct Args { 14 | Type TYPE; 15 | uint32 STAMINA_COST; 16 | uint32 PRIORITY; 17 | } 18 | 19 | IEngine immutable ENGINE; 20 | Type immutable TYPE; 21 | uint32 immutable STAMINA_COST; 22 | uint32 immutable PRIORITY; 23 | 24 | constructor(IEngine _ENGINE, Args memory args) { 25 | ENGINE = _ENGINE; 26 | TYPE = args.TYPE; 27 | STAMINA_COST = args.STAMINA_COST; 28 | PRIORITY = args.PRIORITY; 29 | } 30 | 31 | function name() external pure returns (string memory) { 32 | return "Force Switch"; 33 | } 34 | 35 | function move(bytes32, uint256, uint240 extraData, uint256) external { 36 | // Decode data as packed (playerIndex in lower 120 bits, monToSwitchIndex in upper 120 bits) 37 | uint256 playerIndex = uint256(extraData) & ((1 << 120) - 1); 38 | uint256 monToSwitchIndex = uint256(extraData) >> 120; 39 | 40 | // Use the new switchActiveMon function 41 | ENGINE.switchActiveMon(playerIndex, monToSwitchIndex); 42 | } 43 | 44 | function priority(bytes32, uint256) external view returns (uint32) { 45 | return PRIORITY; 46 | } 47 | 48 | function stamina(bytes32, uint256, uint256) external view returns (uint32) { 49 | return STAMINA_COST; 50 | } 51 | 52 | function moveType(bytes32) external view returns (Type) { 53 | return TYPE; 54 | } 55 | 56 | function isValidTarget(bytes32, uint240) external pure returns (bool) { 57 | return true; 58 | } 59 | 60 | function moveClass(bytes32) external pure returns (MoveClass) { 61 | return MoveClass.Physical; 62 | } 63 | 64 | function basePower(bytes32) external pure returns (uint32) { 65 | return 0; 66 | } 67 | 68 | function extraDataType() external pure returns (ExtraDataType) { 69 | return ExtraDataType.SelfTeamIndex; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /test/mocks/EffectAttack.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/Constants.sol"; 6 | import "../../src/Enums.sol"; 7 | import "../../src/Structs.sol"; 8 | 9 | import {IEngine} from "../../src/IEngine.sol"; 10 | import {IEffect} from "../../src/effects/IEffect.sol"; 11 | import {IMoveSet} from "../../src/moves/IMoveSet.sol"; 12 | 13 | contract EffectAttack is IMoveSet { 14 | struct Args { 15 | Type TYPE; 16 | uint32 STAMINA_COST; 17 | uint32 PRIORITY; 18 | } 19 | 20 | IEngine immutable ENGINE; 21 | IEffect immutable EFFECT; 22 | Type immutable TYPE; 23 | uint32 immutable STAMINA_COST; 24 | uint32 immutable PRIORITY; 25 | 26 | constructor(IEngine _ENGINE, IEffect _EFFECT, Args memory args) { 27 | ENGINE = _ENGINE; 28 | EFFECT = _EFFECT; 29 | TYPE = args.TYPE; 30 | STAMINA_COST = args.STAMINA_COST; 31 | PRIORITY = args.PRIORITY; 32 | } 33 | 34 | function name() external pure returns (string memory) { 35 | return "Effect Attack"; 36 | } 37 | 38 | function move(bytes32 battleKey, uint256 attackerPlayerIndex, uint240, uint256) external { 39 | uint256 targetIndex = (attackerPlayerIndex + 1) % 2; 40 | uint256 activeMonIndex = ENGINE.getActiveMonIndexForBattleState(battleKey)[targetIndex]; 41 | ENGINE.addEffect(targetIndex, activeMonIndex, EFFECT, bytes32(0)); 42 | } 43 | 44 | function priority(bytes32, uint256) external view returns (uint32) { 45 | return PRIORITY; 46 | } 47 | 48 | function stamina(bytes32, uint256, uint256) external view returns (uint32) { 49 | return STAMINA_COST; 50 | } 51 | 52 | function moveType(bytes32) external view returns (Type) { 53 | return TYPE; 54 | } 55 | 56 | function isValidTarget(bytes32, uint240) external pure returns (bool) { 57 | return true; 58 | } 59 | 60 | function moveClass(bytes32) external pure returns (MoveClass) { 61 | return MoveClass.Physical; 62 | } 63 | 64 | function basePower(bytes32) external pure returns (uint32) { 65 | return 0; 66 | } 67 | 68 | function extraDataType() external pure returns (ExtraDataType) { 69 | return ExtraDataType.None; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/mons/sofabbi/GuestFeature.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | import {AttackCalculator} from "../../moves/AttackCalculator.sol"; 10 | import {IMoveSet} from "../../moves/IMoveSet.sol"; 11 | import {ITypeCalculator} from "../../types/ITypeCalculator.sol"; 12 | 13 | contract GuestFeature is IMoveSet { 14 | uint32 public constant BASE_POWER = 75; 15 | 16 | IEngine immutable ENGINE; 17 | ITypeCalculator immutable TYPE_CALCULATOR; 18 | 19 | constructor(IEngine _ENGINE, ITypeCalculator _TYPE_CALCULATOR) { 20 | ENGINE = _ENGINE; 21 | TYPE_CALCULATOR = _TYPE_CALCULATOR; 22 | } 23 | 24 | function name() public pure override returns (string memory) { 25 | return "Guest Feature"; 26 | } 27 | 28 | function move(bytes32 battleKey, uint256 attackerPlayerIndex, uint240 extraData, uint256 rng) external { 29 | uint256 monIndex = uint256(extraData); 30 | Type guestType = 31 | Type(ENGINE.getMonValueForBattle(battleKey, attackerPlayerIndex, monIndex, MonStateIndexName.Type1)); 32 | AttackCalculator._calculateDamage( 33 | ENGINE, 34 | TYPE_CALCULATOR, 35 | battleKey, 36 | attackerPlayerIndex, 37 | BASE_POWER, 38 | DEFAULT_ACCURACY, 39 | DEFAULT_VOL, 40 | guestType, 41 | moveClass(battleKey), 42 | rng, 43 | DEFAULT_CRIT_RATE 44 | ); 45 | } 46 | 47 | function stamina(bytes32, uint256, uint256) external pure returns (uint32) { 48 | return 3; 49 | } 50 | 51 | function priority(bytes32, uint256) external pure returns (uint32) { 52 | return DEFAULT_PRIORITY; 53 | } 54 | 55 | function moveType(bytes32) public pure returns (Type) { 56 | return Type.Cyber; 57 | } 58 | 59 | function moveClass(bytes32) public pure returns (MoveClass) { 60 | return MoveClass.Physical; 61 | } 62 | 63 | function isValidTarget(bytes32, uint240) external pure returns (bool) { 64 | return true; 65 | } 66 | 67 | function extraDataType() external pure returns (ExtraDataType) { 68 | return ExtraDataType.SelfTeamIndex; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/mons/embursa/HeatBeacon.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | import {IEffect} from "../../effects/IEffect.sol"; 10 | import {IMoveSet} from "../../moves/IMoveSet.sol"; 11 | import {HeatBeaconLib} from "./HeatBeaconLib.sol"; 12 | 13 | contract HeatBeacon is IMoveSet { 14 | IEngine immutable ENGINE; 15 | IEffect immutable BURN_STATUS; 16 | 17 | constructor(IEngine _ENGINE, IEffect _BURN_STATUS) { 18 | ENGINE = _ENGINE; 19 | BURN_STATUS = _BURN_STATUS; 20 | } 21 | 22 | function name() public pure override returns (string memory) { 23 | return "Heat Beacon"; 24 | } 25 | 26 | function move(bytes32, uint256 attackerPlayerIndex, uint240, uint256) external { 27 | // Apply burn to opposing mon 28 | uint256 defenderPlayerIndex = (attackerPlayerIndex + 1) % 2; 29 | uint256 defenderMonIndex = 30 | ENGINE.getActiveMonIndexForBattleState(ENGINE.battleKeyForWrite())[defenderPlayerIndex]; 31 | ENGINE.addEffect(defenderPlayerIndex, defenderMonIndex, BURN_STATUS, ""); 32 | 33 | // Clear the priority boost 34 | if (HeatBeaconLib._getPriorityBoost(ENGINE, attackerPlayerIndex) == 1) { 35 | HeatBeaconLib._clearPriorityBoost(ENGINE, attackerPlayerIndex); 36 | } 37 | 38 | // Set a new priority boost 39 | HeatBeaconLib._setPriorityBoost(ENGINE, attackerPlayerIndex); 40 | } 41 | 42 | function stamina(bytes32, uint256, uint256) external pure returns (uint32) { 43 | return 2; 44 | } 45 | 46 | function priority(bytes32, uint256 attackerPlayerIndex) external view returns (uint32) { 47 | return DEFAULT_PRIORITY + HeatBeaconLib._getPriorityBoost(ENGINE, attackerPlayerIndex); 48 | } 49 | 50 | function moveType(bytes32) public pure returns (Type) { 51 | return Type.Fire; 52 | } 53 | 54 | function isValidTarget(bytes32, uint240) external pure returns (bool) { 55 | return true; 56 | } 57 | 58 | function moveClass(bytes32) public pure returns (MoveClass) { 59 | return MoveClass.Self; 60 | } 61 | 62 | function extraDataType() external pure returns (ExtraDataType) { 63 | return ExtraDataType.None; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /test/mocks/TestMoveFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import {IEngine} from "../../src/IEngine.sol"; 5 | import {IMoveSet} from "../../src/moves/IMoveSet.sol"; 6 | import {MoveClass, Type, ExtraDataType} from "../../src/Enums.sol"; 7 | 8 | contract TestMove is IMoveSet { 9 | 10 | IEngine immutable ENGINE; 11 | 12 | MoveClass private _moveClass; 13 | Type private _moveType; 14 | uint32 private _staminaCost; 15 | int32 private _damage; 16 | 17 | constructor(MoveClass moveClassToUse, Type moveTypeToUse, uint32 staminaCost, int32 damage, IEngine _ENGINE) { 18 | _moveClass = moveClassToUse; 19 | _moveType = moveTypeToUse; 20 | _staminaCost = staminaCost; 21 | _damage = damage; 22 | ENGINE = _ENGINE; 23 | } 24 | 25 | function name() external pure returns (string memory) { 26 | return "Test Move"; 27 | } 28 | 29 | function move(bytes32, uint256 attackerPlayerIndex, uint240, uint256) external { 30 | uint256 opponentIndex = (attackerPlayerIndex + 1) % 2; 31 | uint256 opponentMonIndex = ENGINE.getActiveMonIndexForBattleState(ENGINE.battleKeyForWrite())[opponentIndex]; 32 | ENGINE.dealDamage(opponentIndex, opponentMonIndex, _damage); 33 | } 34 | 35 | function priority(bytes32, uint256) external pure returns (uint32) { 36 | return 1; 37 | } 38 | 39 | function stamina(bytes32, uint256, uint256) external view returns (uint32) { 40 | return _staminaCost; 41 | } 42 | 43 | function moveType(bytes32) external view returns (Type) { 44 | return _moveType; 45 | } 46 | 47 | function isValidTarget(bytes32, uint240) external pure returns (bool) { 48 | return true; 49 | } 50 | 51 | function moveClass(bytes32) external view returns (MoveClass) { 52 | return _moveClass; 53 | } 54 | 55 | function extraDataType() external pure returns (ExtraDataType) { 56 | return ExtraDataType.None; 57 | } 58 | } 59 | 60 | contract TestMoveFactory { 61 | 62 | IEngine immutable ENGINE; 63 | 64 | constructor(IEngine _ENGINE) { 65 | ENGINE = _ENGINE; 66 | } 67 | 68 | function createMove(MoveClass moveClassToUse, Type moveTypeToUse, uint32 staminaCost, int32 damage) external returns (IMoveSet) { 69 | return new TestMove(moveClassToUse, moveTypeToUse, staminaCost, damage, ENGINE); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/mons/pengym/Deadlift.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | import {StatBoostToApply} from "../../Structs.sol"; 8 | 9 | import {IEngine} from "../../IEngine.sol"; 10 | import {StatBoosts} from "../../effects/StatBoosts.sol"; 11 | import {IMoveSet} from "../../moves/IMoveSet.sol"; 12 | 13 | contract Deadlift is IMoveSet { 14 | uint8 public constant ATTACK_BUFF_PERCENT = 50; 15 | uint8 public constant DEF_BUFF_PERCENT = 50; 16 | 17 | IEngine immutable ENGINE; 18 | StatBoosts immutable STAT_BOOSTS; 19 | 20 | constructor(IEngine _ENGINE, StatBoosts _STAT_BOOSTS) { 21 | ENGINE = _ENGINE; 22 | STAT_BOOSTS = _STAT_BOOSTS; 23 | } 24 | 25 | function name() public pure override returns (string memory) { 26 | return "Deadlift"; 27 | } 28 | 29 | function move(bytes32 battleKey, uint256 attackerPlayerIndex, uint240, uint256) external { 30 | // Apply the buffs 31 | uint256 activeMonIndex = ENGINE.getActiveMonIndexForBattleState(battleKey)[attackerPlayerIndex]; 32 | StatBoostToApply[] memory statBoosts = new StatBoostToApply[](2); 33 | statBoosts[0] = StatBoostToApply({ 34 | stat: MonStateIndexName.Attack, 35 | boostPercent: ATTACK_BUFF_PERCENT, 36 | boostType: StatBoostType.Multiply 37 | }); 38 | statBoosts[1] = StatBoostToApply({ 39 | stat: MonStateIndexName.Defense, 40 | boostPercent: DEF_BUFF_PERCENT, 41 | boostType: StatBoostType.Multiply 42 | }); 43 | STAT_BOOSTS.addStatBoosts(attackerPlayerIndex, activeMonIndex, statBoosts, StatBoostFlag.Temp); 44 | } 45 | 46 | function stamina(bytes32, uint256, uint256) external pure returns (uint32) { 47 | return 2; 48 | } 49 | 50 | function priority(bytes32, uint256) external pure returns (uint32) { 51 | return DEFAULT_PRIORITY; 52 | } 53 | 54 | function moveType(bytes32) public pure returns (Type) { 55 | return Type.Metal; 56 | } 57 | 58 | function moveClass(bytes32) public pure returns (MoveClass) { 59 | return MoveClass.Self; 60 | } 61 | 62 | function isValidTarget(bytes32, uint240) external pure returns (bool) { 63 | return true; 64 | } 65 | 66 | function extraDataType() external pure returns (ExtraDataType) { 67 | return ExtraDataType.None; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /processing/deployToEnv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Script to parse DeployData output from forge scripts and convert to env format. 4 | 5 | Modes: 6 | --parse: Read from stdin and output parsed KEY=VALUE lines to stdout (no file I/O) 7 | (default): Read from processing/input.txt and write to processing/output.txt 8 | """ 9 | 10 | import argparse 11 | import re 12 | import sys 13 | 14 | 15 | def parse_deploy_data_array(content): 16 | """Parse the DeployData array structure from the input content""" 17 | # Remove whitespace and newlines 18 | content = content.strip() 19 | 20 | # Extract individual DeployData objects using regex 21 | deploy_data_pattern = r'DeployData\(\{\s*name:\s*"([^"]+)",\s*contractAddress:\s*(0x[a-fA-F0-9]+)\s*\}\)' 22 | matches = re.findall(deploy_data_pattern, content) 23 | 24 | return matches 25 | 26 | 27 | def parse_content_to_env_lines(content): 28 | """Parse content and return list of KEY=VALUE lines""" 29 | output = [] 30 | 31 | deploy_data_matches = parse_deploy_data_array(content) 32 | if deploy_data_matches: 33 | for name, address in deploy_data_matches: 34 | name = name.upper().replace(" ", "_").replace("-", "_") 35 | output.append(f"{name}={address}") 36 | 37 | return output 38 | 39 | 40 | def process_file(input_file, output_file): 41 | with open(input_file, 'r') as f: 42 | content = f.read() 43 | 44 | output = parse_content_to_env_lines(content) 45 | 46 | with open(output_file, 'w') as f: 47 | f.write('\n'.join(output)) 48 | 49 | 50 | def main(): 51 | parser = argparse.ArgumentParser( 52 | description='Parse DeployData output from forge scripts' 53 | ) 54 | parser.add_argument( 55 | '--parse', 56 | action='store_true', 57 | help='Read from stdin and output parsed KEY=VALUE lines to stdout' 58 | ) 59 | args = parser.parse_args() 60 | 61 | if args.parse: 62 | # Read from stdin, parse, output to stdout 63 | content = sys.stdin.read() 64 | lines = parse_content_to_env_lines(content) 65 | for line in lines: 66 | print(line) 67 | else: 68 | # Original file-based mode 69 | input_file = 'processing/input.txt' 70 | output_file = 'processing/output.txt' 71 | process_file(input_file, output_file) 72 | print(f"Conversion complete. Output written to {output_file}") 73 | 74 | 75 | if __name__ == "__main__": 76 | main() -------------------------------------------------------------------------------- /src/mons/aurox/UpOnly.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {EffectStep} from "../../Enums.sol"; 6 | import {MonStateIndexName, StatBoostType, StatBoostFlag} from "../../Enums.sol"; 7 | import {EffectInstance, StatBoostToApply} from "../../Structs.sol"; 8 | import {IEngine} from "../../IEngine.sol"; 9 | import {IAbility} from "../../abilities/IAbility.sol"; 10 | import {BasicEffect} from "../../effects/BasicEffect.sol"; 11 | import {IEffect} from "../../effects/IEffect.sol"; 12 | import {StatBoosts} from "../../effects/StatBoosts.sol"; 13 | 14 | contract UpOnly is IAbility, BasicEffect { 15 | 16 | uint8 public constant ATTACK_BOOST_PERCENT = 10; // 10% attack boost per hit 17 | 18 | IEngine immutable ENGINE; 19 | StatBoosts immutable STAT_BOOSTS; 20 | 21 | constructor(IEngine _ENGINE, StatBoosts _STAT_BOOSTS) { 22 | ENGINE = _ENGINE; 23 | STAT_BOOSTS = _STAT_BOOSTS; 24 | } 25 | 26 | // IAbility implementation 27 | function name() public pure override(IAbility, BasicEffect) returns (string memory) { 28 | return "Up Only"; 29 | } 30 | 31 | function activateOnSwitch(bytes32 battleKey, uint256 playerIndex, uint256 monIndex) external { 32 | // Check if the effect has already been set for this mon 33 | (EffectInstance[] memory effects, ) = ENGINE.getEffects(battleKey, playerIndex, monIndex); 34 | for (uint256 i = 0; i < effects.length; i++) { 35 | if (address(effects[i].effect) == address(this)) { 36 | return; 37 | } 38 | } 39 | ENGINE.addEffect(playerIndex, monIndex, IEffect(address(this)), bytes32(0)); 40 | } 41 | 42 | // IEffect implementation 43 | function shouldRunAtStep(EffectStep step) external pure override returns (bool) { 44 | return (step == EffectStep.AfterDamage); 45 | } 46 | 47 | function onAfterDamage(uint256, bytes32 extraData, uint256 targetIndex, uint256 monIndex, int32) 48 | external 49 | override 50 | returns (bytes32 updatedExtraData, bool removeAfterRun) 51 | { 52 | // Add 5% attack boost every time damage is taken 53 | StatBoostToApply[] memory statBoosts = new StatBoostToApply[](1); 54 | statBoosts[0] = StatBoostToApply({ 55 | stat: MonStateIndexName.Attack, 56 | boostPercent: ATTACK_BOOST_PERCENT, 57 | boostType: StatBoostType.Multiply 58 | }); 59 | STAT_BOOSTS.addStatBoosts(targetIndex, monIndex, statBoosts, StatBoostFlag.Perm); 60 | 61 | return (extraData, false); 62 | } 63 | } -------------------------------------------------------------------------------- /src/lib/Strings.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Forked from OpenZeppelin Contracts (last updated v5.3.0) (utils/math/Math.sol) 3 | // Forked from OpenZeppelin Contracts (last updated v5.3.0) (utils/Strings.sol) 4 | 5 | pragma solidity ^0.8.20; 6 | 7 | /** 8 | * @dev String operations. 9 | */ 10 | library Strings { 11 | bytes16 private constant HEX_DIGITS = "0123456789abcdef"; 12 | uint8 private constant ADDRESS_LENGTH = 20; 13 | uint256 private constant SPECIAL_CHARS_LOOKUP = 14 | (1 << 0x08) // backspace 15 | | (1 << 0x09) // tab 16 | | (1 << 0x0a) // newline 17 | | (1 << 0x0c) // form feed 18 | | (1 << 0x0d) // carriage return 19 | | (1 << 0x22) // double quote 20 | | (1 << 0x5c); // backslash 21 | 22 | function log10(uint256 value) internal pure returns (uint256) { 23 | uint256 result = 0; 24 | unchecked { 25 | if (value >= 10 ** 64) { 26 | value /= 10 ** 64; 27 | result += 64; 28 | } 29 | if (value >= 10 ** 32) { 30 | value /= 10 ** 32; 31 | result += 32; 32 | } 33 | if (value >= 10 ** 16) { 34 | value /= 10 ** 16; 35 | result += 16; 36 | } 37 | if (value >= 10 ** 8) { 38 | value /= 10 ** 8; 39 | result += 8; 40 | } 41 | if (value >= 10 ** 4) { 42 | value /= 10 ** 4; 43 | result += 4; 44 | } 45 | if (value >= 10 ** 2) { 46 | value /= 10 ** 2; 47 | result += 2; 48 | } 49 | if (value >= 10 ** 1) { 50 | result += 1; 51 | } 52 | } 53 | return result; 54 | } 55 | 56 | /** 57 | * @dev Converts a `uint256` to its ASCII `string` decimal representation. 58 | */ 59 | function toString(uint256 value) internal pure returns (string memory) { 60 | unchecked { 61 | uint256 length = log10(value) + 1; 62 | string memory buffer = new string(length); 63 | uint256 ptr; 64 | assembly ("memory-safe") { 65 | ptr := add(buffer, add(32, length)) 66 | } 67 | while (true) { 68 | ptr--; 69 | assembly ("memory-safe") { 70 | mstore8(ptr, byte(mod(value, 10), HEX_DIGITS)) 71 | } 72 | value /= 10; 73 | if (value == 0) break; 74 | } 75 | return buffer; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/mons/iblivion/Renormalize.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | import "../../Structs.sol"; 8 | 9 | import {IEngine} from "../../IEngine.sol"; 10 | import {IMoveSet} from "../../moves/IMoveSet.sol"; 11 | import {StatBoosts} from "../../effects/StatBoosts.sol"; 12 | 13 | import {Baselight} from "./Baselight.sol"; 14 | import {Loop} from "./Loop.sol"; 15 | 16 | /** 17 | * Renormalize Move for Iblivion 18 | * - Stamina: 0, Type: Yin, Class: Self, Priority: -1 (below normal) 19 | * - Sets Baselight level to 3 20 | * - Clears all StatBoost instances (resets all stat boosts including Loop's effect) 21 | * - Clears Loop active flag so Loop can be used again 22 | */ 23 | contract Renormalize is IMoveSet { 24 | IEngine immutable ENGINE; 25 | Baselight immutable BASELIGHT; 26 | StatBoosts immutable STAT_BOOSTS; 27 | Loop immutable LOOP; 28 | 29 | constructor(IEngine _ENGINE, Baselight _BASELIGHT, StatBoosts _STAT_BOOSTS, Loop _LOOP) { 30 | ENGINE = _ENGINE; 31 | BASELIGHT = _BASELIGHT; 32 | STAT_BOOSTS = _STAT_BOOSTS; 33 | LOOP = _LOOP; 34 | } 35 | 36 | function name() public pure override returns (string memory) { 37 | return "Renormalize"; 38 | } 39 | 40 | function move(bytes32 battleKey, uint256 attackerPlayerIndex, uint240, uint256) external { 41 | uint256 monIndex = ENGINE.getActiveMonIndexForBattleState(battleKey)[attackerPlayerIndex]; 42 | 43 | // Set Baselight level to 3 44 | BASELIGHT.setBaselightLevel(attackerPlayerIndex, monIndex, 3); 45 | 46 | // Clear Loop active flag so Loop can be used again 47 | LOOP.clearLoopActive(attackerPlayerIndex, monIndex); 48 | 49 | // Clear all StatBoost effects and reset stats to base values 50 | STAT_BOOSTS.clearAllBoostsForMon(attackerPlayerIndex, monIndex); 51 | } 52 | 53 | function stamina(bytes32, uint256, uint256) external pure returns (uint32) { 54 | return 0; 55 | } 56 | 57 | function priority(bytes32, uint256) external pure returns (uint32) { 58 | return DEFAULT_PRIORITY - 1; 59 | } 60 | 61 | function moveType(bytes32) public pure returns (Type) { 62 | return Type.Yin; 63 | } 64 | 65 | function isValidTarget(bytes32, uint240) external pure returns (bool) { 66 | return true; 67 | } 68 | 69 | function moveClass(bytes32) public pure returns (MoveClass) { 70 | return MoveClass.Self; 71 | } 72 | 73 | function extraDataType() external pure returns (ExtraDataType) { 74 | return ExtraDataType.None; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/cpu/CPUMoveManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "../Constants.sol"; 5 | import "../Structs.sol"; 6 | 7 | import {IEngine} from "../IEngine.sol"; 8 | import {ICPU} from "./ICPU.sol"; 9 | 10 | abstract contract CPUMoveManager { 11 | IEngine internal immutable ENGINE; 12 | 13 | error NotP0(); 14 | 15 | constructor(IEngine engine) { 16 | ENGINE = engine; 17 | 18 | // Self-register as an approved matchmaker 19 | address[] memory self = new address[](1); 20 | self[0] = address(this); 21 | address[] memory empty = new address[](0); 22 | engine.updateMatchmakers(self, empty); 23 | } 24 | 25 | function selectMove(bytes32 battleKey, uint8 moveIndex, bytes32 salt, uint240 extraData) external { 26 | if (msg.sender != ENGINE.getPlayersForBattle(battleKey)[0]) { 27 | revert NotP0(); 28 | } 29 | 30 | address winner = ENGINE.getWinner(battleKey); 31 | if (winner != address(0)) { 32 | return; 33 | } 34 | 35 | uint256 playerSwitchForTurnFlag = ENGINE.getPlayerSwitchForTurnFlagForBattleState(battleKey); 36 | 37 | // Determine move configuration based on turn flag 38 | if (playerSwitchForTurnFlag == 0) { 39 | // P0's turn: player moves, CPU no-ops 40 | _addPlayerMove(battleKey, moveIndex, salt, extraData); 41 | _addCPUMove(battleKey, NO_OP_MOVE_INDEX, "", 0); 42 | } else if (playerSwitchForTurnFlag == 1) { 43 | // P1's turn: player no-ops, CPU moves 44 | _addPlayerMove(battleKey, NO_OP_MOVE_INDEX, salt, extraData); 45 | _addCPUMoveFromAI(battleKey); 46 | } else { 47 | // Both players move 48 | _addPlayerMove(battleKey, moveIndex, salt, extraData); 49 | _addCPUMoveFromAI(battleKey); 50 | } 51 | 52 | ENGINE.execute(battleKey); 53 | } 54 | 55 | function _addPlayerMove(bytes32 battleKey, uint8 moveIndex, bytes32 salt, uint240 extraData) private { 56 | ENGINE.setMove(battleKey, 0, moveIndex, salt, extraData); 57 | } 58 | 59 | function _addCPUMove(bytes32 battleKey, uint8 moveIndex, bytes32 salt, uint240 extraData) private { 60 | ENGINE.setMove(battleKey, 1, moveIndex, salt, extraData); 61 | } 62 | 63 | function _addCPUMoveFromAI(bytes32 battleKey) private { 64 | (uint128 cpuMoveIndex, uint240 cpuExtraData) = ICPU(address(this)).calculateMove(battleKey, 1); 65 | bytes32 cpuSalt = keccak256(abi.encode(battleKey, msg.sender, block.timestamp)); 66 | _addCPUMove(battleKey, uint8(cpuMoveIndex), cpuSalt, cpuExtraData); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/effects/status/FrostbiteStatus.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "../../Enums.sol"; 5 | import {StatBoostToApply} from "../../Structs.sol"; 6 | import {IEngine} from "../../IEngine.sol"; 7 | import {StatBoosts} from "../StatBoosts.sol"; 8 | 9 | import {StatusEffect} from "./StatusEffect.sol"; 10 | 11 | contract FrostbiteStatus is StatusEffect { 12 | 13 | int32 constant DAMAGE_DENOM = 16; 14 | uint8 constant SP_ATTACK_PERCENT = 50; 15 | 16 | StatBoosts immutable STAT_BOOST; 17 | 18 | constructor(IEngine engine, StatBoosts _STAT_BOOSTS) StatusEffect(engine) { 19 | STAT_BOOST = _STAT_BOOSTS; 20 | } 21 | 22 | function name() public pure override returns (string memory) { 23 | return "Frostbite"; 24 | } 25 | 26 | function shouldRunAtStep(EffectStep r) external pure override returns (bool) { 27 | return (r == EffectStep.OnApply || r == EffectStep.RoundEnd || r == EffectStep.OnRemove); 28 | } 29 | 30 | function onApply(uint256 rng, bytes32 extraData, uint256 targetIndex, uint256 monIndex) 31 | public 32 | override 33 | returns (bytes32 updatedExtraData, bool removeAfterRun) 34 | { 35 | 36 | super.onApply(rng, extraData, targetIndex, monIndex); 37 | 38 | // Reduce special attack by half 39 | StatBoostToApply[] memory statBoosts = new StatBoostToApply[](1); 40 | statBoosts[0] = StatBoostToApply({ 41 | stat: MonStateIndexName.SpecialAttack, 42 | boostPercent: SP_ATTACK_PERCENT, 43 | boostType: StatBoostType.Divide 44 | }); 45 | STAT_BOOST.addStatBoosts(targetIndex, monIndex, statBoosts, StatBoostFlag.Perm); 46 | 47 | // Do not update data 48 | return (extraData, false); 49 | } 50 | 51 | function onRemove(bytes32 data, uint256 targetIndex, uint256 monIndex) public override { 52 | super.onRemove(data, targetIndex, monIndex); 53 | 54 | // Reset the special attack reduction 55 | STAT_BOOST.removeStatBoosts(targetIndex, monIndex, StatBoostFlag.Perm); 56 | } 57 | 58 | function onRoundEnd(uint256, bytes32 extraData, uint256 targetIndex, uint256 monIndex) 59 | public 60 | override 61 | returns (bytes32, bool) 62 | { 63 | // Calculate damage to deal 64 | uint32 maxHealth = 65 | ENGINE.getMonValueForBattle(ENGINE.battleKeyForWrite(), targetIndex, monIndex, MonStateIndexName.Hp); 66 | int32 damage = int32(maxHealth) / DAMAGE_DENOM; 67 | ENGINE.dealDamage(targetIndex, monIndex, damage); 68 | 69 | // Do not update data 70 | return (extraData, false); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /drool/type-data.js: -------------------------------------------------------------------------------- 1 | export const typeData = { 2 | 'Yin': { 3 | bgColor: '#ffffff', 4 | textColor: '#000000', 5 | abbreviation: 'YIN', 6 | emoji: '⚪', 7 | name: 'Yin' 8 | }, 9 | 'Yang': { 10 | bgColor: '#000000', 11 | textColor: '#FFFFFF', 12 | abbreviation: 'YNG', 13 | emoji: '🌚', 14 | name: 'Yang' 15 | }, 16 | 'Earth': { 17 | bgColor: '#8B4513', 18 | textColor: '#FFFFFF', 19 | abbreviation: 'ETH', 20 | emoji: '🪨', 21 | name: 'Earth' 22 | }, 23 | 'Liquid': { 24 | bgColor: '#1E90FF', 25 | textColor: '#000000', 26 | abbreviation: 'WTR', 27 | emoji: '🌊', 28 | name: 'Liquid' 29 | }, 30 | 'Fire': { 31 | bgColor: '#d2400a', 32 | textColor: '#FFFFFF', 33 | abbreviation: 'FIR', 34 | emoji: '🔥', 35 | name: 'Fire' 36 | }, 37 | 'Metal': { 38 | bgColor: '#71797E', 39 | textColor: '#FFFFFF', 40 | abbreviation: 'MTL', 41 | emoji: '🔩', 42 | name: 'Metal' 43 | }, 44 | 'Ice': { 45 | bgColor: '#4682B4', 46 | textColor: '#FFFFFF', 47 | abbreviation: 'ICE', 48 | emoji: '❄️', 49 | name: 'Ice' 50 | }, 51 | 'Nature': { 52 | bgColor: '#228B22', 53 | textColor: '#FFFFFF', 54 | abbreviation: 'NTR', 55 | emoji: '🌳', 56 | name: 'Nature' 57 | }, 58 | 'Lightning': { 59 | bgColor: '#FFD700', 60 | textColor: '#000000', 61 | abbreviation: 'LTG', 62 | emoji: '🌩️', 63 | name: 'Lightning' 64 | }, 65 | 'Mythic': { 66 | bgColor: '#8A2BE2', 67 | textColor: '#FFFFFF', 68 | abbreviation: 'MYT', 69 | emoji: '🧿', 70 | name: 'Mythic' 71 | }, 72 | 'Air': { 73 | bgColor: '#46B4AF', 74 | textColor: '#FFFFFF', 75 | abbreviation: 'AIR', 76 | emoji: '🌪️', 77 | name: 'Air' 78 | }, 79 | 'Math': { 80 | bgColor: '#FF1493', 81 | textColor: '#FFFFFF', 82 | abbreviation: 'MND', 83 | emoji: '🧠', 84 | name: 'Math' 85 | }, 86 | 'Cyber': { 87 | bgColor: '#00CED1', 88 | textColor: '#000000', 89 | abbreviation: 'CYB', 90 | emoji: '💻', 91 | name: 'Cyber' 92 | }, 93 | 'Wild': { 94 | bgColor: '#9a7247', 95 | textColor: '#FFF', 96 | abbreviation: 'WLD', 97 | emoji: '🦙', 98 | name: 'Wild' 99 | }, 100 | 'Cosmic': { 101 | bgColor: '#4B0082', 102 | textColor: '#FFFFFF', 103 | abbreviation: 'CMC', 104 | emoji: '🌠', 105 | name: 'Cosmic' 106 | } 107 | }; -------------------------------------------------------------------------------- /test/mocks/MockEffectRemover.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "../../src/Constants.sol"; 5 | import "../../src/Enums.sol"; 6 | import "../../src/Structs.sol"; 7 | 8 | import {IEngine} from "../../src/IEngine.sol"; 9 | import {IEffect} from "../../src/effects/IEffect.sol"; 10 | import {IMoveSet} from "../../src/moves/IMoveSet.sol"; 11 | 12 | /** 13 | * Mock move that removes an effect from a target mon. 14 | * The effect address to remove is passed as extraData (targetArgs). 15 | * Targets the opponent's active mon. 16 | */ 17 | contract MockEffectRemover is IMoveSet { 18 | IEngine immutable ENGINE; 19 | 20 | constructor(IEngine _ENGINE) { 21 | ENGINE = _ENGINE; 22 | } 23 | 24 | function name() public pure override returns (string memory) { 25 | return "Mock Effect Remover"; 26 | } 27 | 28 | function move(bytes32 battleKey, uint256 attackerPlayerIndex, uint240 extraData, uint256) external { 29 | // extraData contains the address of the effect to remove (packed as uint160) 30 | address effectToRemove = address(uint160(extraData)); 31 | 32 | // Target the opponent's active mon 33 | uint256 targetPlayerIndex = 1 - attackerPlayerIndex; 34 | uint256 targetMonIndex = ENGINE.getActiveMonIndexForBattleState(battleKey)[targetPlayerIndex]; 35 | 36 | // Find and remove the effect 37 | (EffectInstance[] memory effects, uint256[] memory indices) = ENGINE.getEffects(battleKey, targetPlayerIndex, targetMonIndex); 38 | for (uint256 i = 0; i < effects.length; i++) { 39 | if (address(effects[i].effect) == effectToRemove) { 40 | // Call onRemove on the effect before removing 41 | effects[i].effect.onRemove(effects[i].data, targetPlayerIndex, targetMonIndex); 42 | ENGINE.removeEffect(targetPlayerIndex, targetMonIndex, indices[i]); 43 | break; 44 | } 45 | } 46 | } 47 | 48 | function stamina(bytes32, uint256, uint256) external pure returns (uint32) { 49 | return 0; 50 | } 51 | 52 | function priority(bytes32, uint256) external pure returns (uint32) { 53 | return DEFAULT_PRIORITY; 54 | } 55 | 56 | function moveType(bytes32) public pure returns (Type) { 57 | return Type.None; 58 | } 59 | 60 | function moveClass(bytes32) public pure returns (MoveClass) { 61 | return MoveClass.Other; 62 | } 63 | 64 | function isValidTarget(bytes32, uint240) external pure returns (bool) { 65 | return true; 66 | } 67 | 68 | function extraDataType() external pure returns (ExtraDataType) { 69 | return ExtraDataType.None; 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /src/effects/IEffect.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "../Enums.sol"; 5 | import "../Structs.sol"; 6 | 7 | interface IEffect { 8 | function name() external returns (string memory); 9 | 10 | // Whether to run the effect at a specific step 11 | function shouldRunAtStep(EffectStep r) external returns (bool); 12 | 13 | // Whether or not to add the effect if some condition is met 14 | function shouldApply(bytes32 extraData, uint256 targetIndex, uint256 monIndex) external returns (bool); 15 | 16 | // Lifecycle hooks during normal battle flow 17 | function onRoundStart(uint256 rng, bytes32 extraData, uint256 targetIndex, uint256 monIndex) 18 | external 19 | returns (bytes32 updatedExtraData, bool removeAfterRun); 20 | function onRoundEnd(uint256 rng, bytes32 extraData, uint256 targetIndex, uint256 monIndex) 21 | external 22 | returns (bytes32 updatedExtraData, bool removeAfterRun); 23 | 24 | function onMonSwitchIn(uint256 rng, bytes32 extraData, uint256 targetIndex, uint256 monIndex) 25 | external 26 | returns (bytes32 updatedExtraData, bool removeAfterRun); 27 | 28 | function onMonSwitchOut(uint256 rng, bytes32 extraData, uint256 targetIndex, uint256 monIndex) 29 | external 30 | returns (bytes32 updatedExtraData, bool removeAfterRun); 31 | 32 | // NOTE: CURRENTLY ONLY RUN LOCALLY ON MONS (global effects do not have this hook) 33 | function onAfterDamage(uint256 rng, bytes32 extraData, uint256 targetIndex, uint256 monIndex, int32 damage) 34 | external 35 | returns (bytes32 updatedExtraData, bool removeAfterRun); 36 | 37 | function onAfterMove(uint256 rng, bytes32 extraData, uint256 targetIndex, uint256 monIndex) 38 | external 39 | returns (bytes32 updatedExtraData, bool removeAfterRun); 40 | 41 | // NOTE: CURRENTLY ONLY RUN LOCALLY ON MONS (global effects do not have this hook) 42 | // WARNING: Avoid chaining this effect to prevent recursive calls 43 | // (e.g., an effect that mutates state triggering another effect that mutates state) 44 | function onUpdateMonState( 45 | uint256 rng, 46 | bytes32 extraData, 47 | uint256 playerIndex, 48 | uint256 monIndex, 49 | MonStateIndexName stateVarIndex, 50 | int32 valueToAdd 51 | ) external returns (bytes32 updatedExtraData, bool removeAfterRun); 52 | 53 | // Lifecycle hooks when being applied or removed 54 | function onApply(uint256 rng, bytes32 extraData, uint256 targetIndex, uint256 monIndex) 55 | external 56 | returns (bytes32 updatedExtraData, bool removeAfterRun); 57 | function onRemove(bytes32 extraData, uint256 targetIndex, uint256 monIndex) external; 58 | } 59 | -------------------------------------------------------------------------------- /src/mons/xmon/Dreamcatcher.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Enums.sol"; 6 | import {MonStateIndexName, EffectInstance} from "../../Structs.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | import {IAbility} from "../../abilities/IAbility.sol"; 10 | import {IEffect} from "../../effects/IEffect.sol"; 11 | import {BasicEffect} from "../../effects/BasicEffect.sol"; 12 | 13 | contract Dreamcatcher is IAbility, BasicEffect { 14 | int32 public constant HEAL_DENOM = 16; 15 | 16 | IEngine immutable ENGINE; 17 | 18 | constructor(IEngine _ENGINE) { 19 | ENGINE = _ENGINE; 20 | } 21 | 22 | function name() public pure override(IAbility, BasicEffect) returns (string memory) { 23 | return "Dreamcatcher"; 24 | } 25 | 26 | function activateOnSwitch(bytes32 battleKey, uint256 playerIndex, uint256 monIndex) external { 27 | // Check if the effect has already been set for this mon 28 | (EffectInstance[] memory effects, ) = ENGINE.getEffects(battleKey, playerIndex, monIndex); 29 | for (uint256 i = 0; i < effects.length; i++) { 30 | if (address(effects[i].effect) == address(this)) { 31 | return; 32 | } 33 | } 34 | ENGINE.addEffect(playerIndex, monIndex, IEffect(address(this)), bytes32(0)); 35 | } 36 | 37 | // IEffect implementation 38 | function shouldRunAtStep(EffectStep step) external pure override returns (bool) { 39 | return step == EffectStep.OnUpdateMonState; 40 | } 41 | 42 | function onUpdateMonState( 43 | uint256, 44 | bytes32 extraData, 45 | uint256 playerIndex, 46 | uint256 monIndex, 47 | MonStateIndexName stateVarIndex, 48 | int32 valueToAdd 49 | ) external override returns (bytes32, bool) { 50 | // Only trigger if Stamina is being increased (positive valueToAdd) 51 | if (stateVarIndex == MonStateIndexName.Stamina && valueToAdd > 0) { 52 | bytes32 battleKey = ENGINE.battleKeyForWrite(); 53 | uint32 maxHp = ENGINE.getMonValueForBattle(battleKey, playerIndex, monIndex, MonStateIndexName.Hp); 54 | int32 healAmount = int32(uint32(maxHp)) / HEAL_DENOM; 55 | 56 | // Prevent overhealing 57 | int32 existingHpDelta = ENGINE.getMonStateForBattle(battleKey, playerIndex, monIndex, MonStateIndexName.Hp); 58 | if (existingHpDelta + healAmount > 0) { 59 | healAmount = 0 - existingHpDelta; 60 | } 61 | 62 | if (healAmount > 0) { 63 | ENGINE.updateMonState(playerIndex, monIndex, MonStateIndexName.Hp, healAmount); 64 | } 65 | } 66 | return (extraData, false); 67 | } 68 | } 69 | 70 | -------------------------------------------------------------------------------- /src/effects/StaminaRegen.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import {NO_OP_MOVE_INDEX, MOVE_INDEX_MASK} from "../Constants.sol"; 5 | import "../Enums.sol"; 6 | import {MoveDecision} from "../Structs.sol"; 7 | 8 | import {IEngine} from "../IEngine.sol"; 9 | import {BasicEffect} from "./BasicEffect.sol"; 10 | 11 | contract StaminaRegen is BasicEffect { 12 | IEngine immutable ENGINE; 13 | 14 | constructor(IEngine _ENGINE) { 15 | ENGINE = _ENGINE; 16 | } 17 | 18 | function name() external pure override returns (string memory) { 19 | return "Stamina Regen"; 20 | } 21 | 22 | // Should run at end of round 23 | function shouldRunAtStep(EffectStep r) external pure override returns (bool) { 24 | return (r == EffectStep.RoundEnd) || (r == EffectStep.AfterMove); 25 | } 26 | 27 | // No overhealing stamina 28 | function _regenStamina(uint256 playerIndex, uint256 monIndex) internal { 29 | int256 currentActiveMonStaminaDelta = 30 | ENGINE.getMonStateForBattle(ENGINE.battleKeyForWrite(), playerIndex, monIndex, MonStateIndexName.Stamina); 31 | if (currentActiveMonStaminaDelta < 0) { 32 | ENGINE.updateMonState(playerIndex, monIndex, MonStateIndexName.Stamina, 1); 33 | } 34 | } 35 | 36 | // Regen stamina on round end for both active mons 37 | function onRoundEnd(uint256, bytes32, uint256, uint256) external override returns (bytes32, bool) { 38 | bytes32 battleKey = ENGINE.battleKeyForWrite(); 39 | uint256 playerSwitchForTurnFlag = ENGINE.getPlayerSwitchForTurnFlagForBattleState(battleKey); 40 | uint256[] memory activeMonIndex = ENGINE.getActiveMonIndexForBattleState(battleKey); 41 | // Update stamina for both active mons only if it's a 2 player turn 42 | if (playerSwitchForTurnFlag == 2) { 43 | for (uint256 playerIndex; playerIndex < 2; ++playerIndex) { 44 | _regenStamina(playerIndex, activeMonIndex[playerIndex]); 45 | } 46 | } 47 | return (bytes32(0), false); 48 | } 49 | 50 | // Regen stamina if the mon did a No Op (i.e. resting) 51 | function onAfterMove(uint256, bytes32, uint256 targetIndex, uint256 monIndex) 52 | external 53 | override 54 | returns (bytes32, bool) 55 | { 56 | bytes32 battleKey = ENGINE.battleKeyForWrite(); 57 | MoveDecision memory moveDecision = ENGINE.getMoveDecisionForBattleState(battleKey, targetIndex); 58 | // Unpack the move index from packedMoveIndex 59 | uint8 moveIndex = moveDecision.packedMoveIndex & MOVE_INDEX_MASK; 60 | if (moveIndex == NO_OP_MOVE_INDEX) { 61 | _regenStamina(targetIndex, monIndex); 62 | } 63 | return (bytes32(0), false); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/mons/pengym/PostWorkout.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {EffectStep, MonStateIndexName} from "../../Enums.sol"; 6 | import {IEngine} from "../../IEngine.sol"; 7 | import {EffectInstance} from "../../Structs.sol"; 8 | import {IAbility} from "../../abilities/IAbility.sol"; 9 | import {BasicEffect} from "../../effects/BasicEffect.sol"; 10 | import {IEffect} from "../../effects/IEffect.sol"; 11 | 12 | import {StatusEffectLib} from "../../effects/status/StatusEffectLib.sol"; 13 | 14 | contract PostWorkout is IAbility, BasicEffect { 15 | IEngine immutable ENGINE; 16 | 17 | constructor(IEngine _ENGINE) { 18 | ENGINE = _ENGINE; 19 | } 20 | 21 | function name() public pure override(IAbility, BasicEffect) returns (string memory) { 22 | return "Post-Workout"; 23 | } 24 | 25 | function activateOnSwitch(bytes32 battleKey, uint256 playerIndex, uint256 monIndex) external { 26 | // Check if the effect has already been set for this mon 27 | (EffectInstance[] memory effects, ) = ENGINE.getEffects(battleKey, playerIndex, monIndex); 28 | for (uint256 i = 0; i < effects.length; i++) { 29 | if (address(effects[i].effect) == address(this)) { 30 | return; 31 | } 32 | } 33 | ENGINE.addEffect(playerIndex, monIndex, IEffect(address(this)), bytes32(0)); 34 | } 35 | 36 | function shouldRunAtStep(EffectStep step) external pure override returns (bool) { 37 | return (step == EffectStep.OnMonSwitchOut); 38 | } 39 | 40 | function onMonSwitchOut(uint256, bytes32, uint256 targetIndex, uint256 monIndex) 41 | external 42 | override 43 | returns (bytes32 updatedExtraData, bool removeAfterRun) 44 | { 45 | bytes32 battleKey = ENGINE.battleKeyForWrite(); 46 | bytes32 keyForMon = StatusEffectLib.getKeyForMonIndex(targetIndex, monIndex); 47 | uint192 statusAddress = ENGINE.getGlobalKV(battleKey, keyForMon); 48 | 49 | // Check if a status exists 50 | if (statusAddress != 0) { 51 | IEffect statusEffect = IEffect(address(uint160(statusAddress))); 52 | 53 | // Get the index of the effect and remove it 54 | uint256 effectIndex; 55 | (EffectInstance[] memory effects, uint256[] memory indices) = ENGINE.getEffects(battleKey, targetIndex, monIndex); 56 | for (uint256 i; i < effects.length; i++) { 57 | if (effects[i].effect == statusEffect) { 58 | effectIndex = indices[i]; 59 | break; 60 | } 61 | } 62 | ENGINE.removeEffect(targetIndex, monIndex, effectIndex); 63 | 64 | // Boost stamina by 1 65 | ENGINE.updateMonState(targetIndex, monIndex, MonStateIndexName.Stamina, 1); 66 | } 67 | return (bytes32(0), false); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/mons/aurox/VolatilePunch.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | import {IEffect} from "../../effects/IEffect.sol"; 10 | import {ITypeCalculator} from "../../types/ITypeCalculator.sol"; 11 | import {StandardAttack} from "../../moves/StandardAttack.sol"; 12 | import {ATTACK_PARAMS} from "../../moves/StandardAttackStructs.sol"; 13 | 14 | contract VolatilePunch is StandardAttack { 15 | uint32 public constant STATUS_EFFECT_CHANCE = 30; // 15% chance for each status 16 | 17 | IEffect immutable BURN_STATUS; 18 | IEffect immutable FROSTBITE_STATUS; 19 | 20 | constructor(IEngine ENGINE, ITypeCalculator TYPE_CALCULATOR, IEffect _BURN_STATUS, IEffect _FROSTBITE_STATUS) 21 | StandardAttack( 22 | address(msg.sender), 23 | ENGINE, 24 | TYPE_CALCULATOR, 25 | ATTACK_PARAMS({ 26 | NAME: "Volatile Punch", 27 | BASE_POWER: 40, 28 | STAMINA_COST: 3, 29 | ACCURACY: 100, 30 | MOVE_TYPE: Type.Metal, 31 | MOVE_CLASS: MoveClass.Physical, 32 | PRIORITY: DEFAULT_PRIORITY, 33 | CRIT_RATE: DEFAULT_CRIT_RATE, 34 | VOLATILITY: DEFAULT_VOL, 35 | EFFECT_ACCURACY: 0, 36 | EFFECT: IEffect(address(0)) 37 | }) 38 | ) 39 | { 40 | BURN_STATUS = _BURN_STATUS; 41 | FROSTBITE_STATUS = _FROSTBITE_STATUS; 42 | } 43 | 44 | function move(bytes32 battleKey, uint256 attackerPlayerIndex, uint240, uint256 rng) 45 | public 46 | override 47 | { 48 | // Deal the damage to opponent 49 | (int32 damage,) = _move(battleKey, attackerPlayerIndex, rng); 50 | 51 | // Apply status effects if damage was dealt 52 | if (damage > 0) { 53 | uint256 defenderPlayerIndex = (attackerPlayerIndex + 1) % 2; 54 | uint256 defenderMonIndex = 55 | ENGINE.getActiveMonIndexForBattleState(ENGINE.battleKeyForWrite())[defenderPlayerIndex]; 56 | 57 | // Use a different part of the RNG for status application 58 | uint256 statusRng = uint256(keccak256(abi.encode(rng, "STATUS_EFFECT"))); 59 | 60 | // 30% chance for Burn or Frostbite 61 | if ((statusRng % 100) < STATUS_EFFECT_CHANCE) { 62 | uint256 statusSelectorRng = uint256(keccak256(abi.encode(rng, "STATUS_SELECTOR"))); 63 | if (statusSelectorRng % 2 == 0) { 64 | ENGINE.addEffect(defenderPlayerIndex, defenderMonIndex, BURN_STATUS, ""); 65 | } else { 66 | ENGINE.addEffect(defenderPlayerIndex, defenderMonIndex, FROSTBITE_STATUS, ""); 67 | } 68 | } 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/mons/xmon/VitalSiphon.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | import {ITypeCalculator} from "../../types/ITypeCalculator.sol"; 10 | import {StandardAttack} from "../../moves/StandardAttack.sol"; 11 | import {ATTACK_PARAMS} from "../../moves/StandardAttackStructs.sol"; 12 | import {IEffect} from "../../effects/IEffect.sol"; 13 | 14 | contract VitalSiphon is StandardAttack { 15 | 16 | uint32 public constant STAMINA_STEAL_PERCENT = 50; 17 | 18 | constructor(IEngine _ENGINE, ITypeCalculator _TYPE_CALCULATOR) 19 | StandardAttack( 20 | address(msg.sender), 21 | _ENGINE, 22 | _TYPE_CALCULATOR, 23 | ATTACK_PARAMS({ 24 | NAME: "Vital Siphon", 25 | BASE_POWER: 40, 26 | STAMINA_COST: 2, 27 | ACCURACY: 90, 28 | MOVE_TYPE: Type.Cosmic, 29 | MOVE_CLASS: MoveClass.Special, 30 | PRIORITY: DEFAULT_PRIORITY, 31 | CRIT_RATE: DEFAULT_CRIT_RATE, 32 | VOLATILITY: DEFAULT_VOL, 33 | EFFECT_ACCURACY: 0, 34 | EFFECT: IEffect(address(0)) 35 | }) 36 | ) 37 | {} 38 | 39 | function move(bytes32 battleKey, uint256 attackerPlayerIndex, uint240 extraData, uint256 rng) 40 | public 41 | override 42 | { 43 | // Deal the damage 44 | super.move(battleKey, attackerPlayerIndex, extraData, rng); 45 | 46 | // 50% chance to steal stamina 47 | if (rng % 100 >= STAMINA_STEAL_PERCENT) { 48 | uint256 defenderPlayerIndex = (attackerPlayerIndex + 1) % 2; 49 | uint256 defenderMonIndex = ENGINE.getActiveMonIndexForBattleState(battleKey)[defenderPlayerIndex]; 50 | 51 | // Check if opponent has at least 1 stamina 52 | int32 defenderStamina = ENGINE.getMonStateForBattle(battleKey, defenderPlayerIndex, defenderMonIndex, MonStateIndexName.Stamina); 53 | uint32 defenderBaseStamina = ENGINE.getMonValueForBattle(battleKey, defenderPlayerIndex, defenderMonIndex, MonStateIndexName.Stamina); 54 | int32 totalDefenderStamina = int32(defenderBaseStamina) + defenderStamina; 55 | 56 | if (totalDefenderStamina >= 1) { 57 | // Steal 1 stamina from opponent 58 | ENGINE.updateMonState(defenderPlayerIndex, defenderMonIndex, MonStateIndexName.Stamina, -1); 59 | 60 | // Give 1 stamina to self 61 | uint256 activeMonIndex = ENGINE.getActiveMonIndexForBattleState(battleKey)[attackerPlayerIndex]; 62 | ENGINE.updateMonState(attackerPlayerIndex, activeMonIndex, MonStateIndexName.Stamina, 1); 63 | } 64 | } 65 | } 66 | } 67 | 68 | -------------------------------------------------------------------------------- /test/mocks/StatBoostsMove.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/Constants.sol"; 6 | import "../../src/Enums.sol"; 7 | import "../../src/Structs.sol"; 8 | 9 | import {IEngine} from "../../src/IEngine.sol"; 10 | import {IMoveSet} from "../../src/moves/IMoveSet.sol"; 11 | 12 | import {StatBoosts} from "../../src/effects/StatBoosts.sol"; 13 | 14 | contract StatBoostsMove is IMoveSet { 15 | IEngine immutable ENGINE; 16 | StatBoosts immutable STAT_BOOSTS; 17 | 18 | constructor(IEngine _ENGINE, StatBoosts _STAT_BOOSTS) { 19 | ENGINE = _ENGINE; 20 | STAT_BOOSTS = _STAT_BOOSTS; 21 | } 22 | 23 | function name() external pure returns (string memory) { 24 | return ""; 25 | } 26 | 27 | function move(bytes32, uint256, uint240 extraData, uint256) external { 28 | // Unpack extraData: lower 60 bits = playerIndex, next 60 bits = monIndex, next 60 bits = statIndex, upper 60 bits = boostAmount 29 | uint256 playerIndex = uint256(extraData) & ((1 << 60) - 1); 30 | uint256 monIndex = (uint256(extraData) >> 60) & ((1 << 60) - 1); 31 | uint256 statIndex = (uint256(extraData) >> 120) & ((1 << 60) - 1); 32 | int32 boostAmount = int32(int256((uint256(extraData) >> 180) & ((1 << 60) - 1))); 33 | 34 | // For all tests, we'll use Temp stat boosts with Multiply type for positive boosts 35 | // and Divide type for negative boosts 36 | StatBoostType boostType = boostAmount > 0 ? StatBoostType.Multiply : StatBoostType.Divide; 37 | 38 | // Convert negative boosts to positive for the divide operation 39 | if (boostAmount < 0) { 40 | boostAmount = -boostAmount; 41 | } 42 | 43 | StatBoostToApply[] memory statBoosts = new StatBoostToApply[](1); 44 | statBoosts[0] = StatBoostToApply({ 45 | stat: MonStateIndexName(statIndex), 46 | boostPercent: uint8(uint32(boostAmount)), 47 | boostType: boostType 48 | }); 49 | STAT_BOOSTS.addStatBoosts(playerIndex, monIndex, statBoosts, StatBoostFlag.Temp); 50 | } 51 | 52 | function priority(bytes32, uint256) external pure returns (uint32) { 53 | return 0; 54 | } 55 | 56 | function stamina(bytes32, uint256, uint256) external pure returns (uint32) { 57 | return 0; 58 | } 59 | 60 | function moveType(bytes32) external pure returns (Type) { 61 | return Type.Air; 62 | } 63 | 64 | function isValidTarget(bytes32, uint240) external pure returns (bool) { 65 | return true; 66 | } 67 | 68 | function moveClass(bytes32) external pure returns (MoveClass) { 69 | return MoveClass.Physical; 70 | } 71 | 72 | function basePower(bytes32) external pure returns (uint32) { 73 | return 0; 74 | } 75 | 76 | function extraDataType() external pure returns (ExtraDataType) { 77 | return ExtraDataType.None; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/mons/gorillax/Angery.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {EffectStep} from "../../Enums.sol"; 6 | 7 | import {MonStateIndexName} from "../../Enums.sol"; 8 | import {EffectInstance} from "../../Structs.sol"; 9 | import {IEngine} from "../../IEngine.sol"; 10 | import {IAbility} from "../../abilities/IAbility.sol"; 11 | 12 | import {BasicEffect} from "../../effects/BasicEffect.sol"; 13 | import {IEffect} from "../../effects/IEffect.sol"; 14 | 15 | contract Angery is IAbility, BasicEffect { 16 | uint256 public constant CHARGE_COUNT = 3; // After 3 charges, consume all charges to heal 17 | int32 public constant MAX_HP_DENOM = 6; // Heal for 1/6 of HP 18 | 19 | IEngine immutable ENGINE; 20 | 21 | constructor(IEngine _ENGINE) { 22 | ENGINE = _ENGINE; 23 | } 24 | 25 | // IAbility implementation 26 | function name() public pure override(IAbility, BasicEffect) returns (string memory) { 27 | return "Angery"; 28 | } 29 | 30 | function activateOnSwitch(bytes32 battleKey, uint256 playerIndex, uint256 monIndex) external { 31 | // Check if the effect has already been set for this mon 32 | (EffectInstance[] memory effects, ) = ENGINE.getEffects(battleKey, playerIndex, monIndex); 33 | for (uint256 i = 0; i < effects.length; i++) { 34 | if (address(effects[i].effect) == address(this)) { 35 | return; 36 | } 37 | } 38 | ENGINE.addEffect(playerIndex, monIndex, IEffect(address(this)), bytes32(0)); 39 | } 40 | 41 | // IEffect implementation 42 | function shouldRunAtStep(EffectStep step) external pure override returns (bool) { 43 | return (step == EffectStep.RoundEnd || step == EffectStep.AfterDamage); 44 | } 45 | 46 | function onRoundEnd(uint256, bytes32 extraData, uint256 targetIndex, uint256 monIndex) 47 | external 48 | override 49 | returns (bytes32 updatedExtraData, bool removeAfterRun) 50 | { 51 | uint256 numCharges = uint256(extraData); 52 | if (numCharges == CHARGE_COUNT) { 53 | // Heal 54 | int32 healAmount = 55 | int32( 56 | ENGINE.getMonValueForBattle(ENGINE.battleKeyForWrite(), targetIndex, monIndex, MonStateIndexName.Hp) 57 | ) / MAX_HP_DENOM; 58 | ENGINE.updateMonState(targetIndex, monIndex, MonStateIndexName.Hp, healAmount); 59 | // Reset the charges 60 | return (bytes32(numCharges - CHARGE_COUNT), false); 61 | } else { 62 | return (extraData, false); 63 | } 64 | } 65 | 66 | function onAfterDamage(uint256, bytes32 extraData, uint256, uint256, int32) 67 | external 68 | pure 69 | override 70 | returns (bytes32 updatedExtraData, bool removeAfterRun) 71 | { 72 | uint256 numCharges = uint256(extraData); 73 | return (bytes32(numCharges + 1), false); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/effects/status/PanicStatus.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import {EffectStep, MonStateIndexName} from "../../Enums.sol"; 5 | import {IEngine} from "../../IEngine.sol"; 6 | 7 | import {StatusEffect} from "./StatusEffect.sol"; 8 | 9 | contract PanicStatus is StatusEffect { 10 | uint256 constant DURATION = 3; 11 | 12 | constructor(IEngine engine) StatusEffect(engine) {} 13 | 14 | function name() public pure override returns (string memory) { 15 | return "Panic"; 16 | } 17 | 18 | function shouldRunAtStep(EffectStep r) external pure override returns (bool) { 19 | // Need to also return OnRemove to remove the global status flag 20 | return 21 | r == EffectStep.RoundStart || r == EffectStep.RoundEnd || r == EffectStep.OnApply 22 | || r == EffectStep.OnRemove; 23 | } 24 | 25 | // At the start of the turn, check to see if we should apply stamina debuff or end early 26 | function onRoundStart(uint256 rng, bytes32 extraData, uint256 targetIndex, uint256 monIndex) 27 | external 28 | pure 29 | override 30 | returns (bytes32, bool) 31 | { 32 | // Set rng to be unique to the target index and mon index 33 | rng = uint256(keccak256(abi.encode(rng, targetIndex, monIndex))); 34 | bool wakeEarly = rng % 3 == 0; 35 | if (wakeEarly) { 36 | return (extraData, true); 37 | } 38 | return (extraData, false); 39 | } 40 | 41 | // On apply, checks to apply the flag, and then sets the extraData to be the duration 42 | function onApply(uint256 rng, bytes32 data, uint256 monIndex, uint256 playerIndex) 43 | public 44 | override 45 | returns (bytes32 updatedExtraData, bool removeAfterRun) 46 | { 47 | super.onApply(rng, data, monIndex, playerIndex); 48 | return (bytes32(DURATION), false); 49 | } 50 | 51 | // Apply effect on end of turn, and then check how many turns are left 52 | function onRoundEnd(uint256, bytes32 extraData, uint256 targetIndex, uint256 monIndex) 53 | external 54 | override 55 | returns (bytes32, bool removeAfterRun) 56 | { 57 | bytes32 battleKey = ENGINE.battleKeyForWrite(); 58 | 59 | // Get current stamina delta of the target mon 60 | int32 staminaDelta = ENGINE.getMonStateForBattle(battleKey, targetIndex, monIndex, MonStateIndexName.Stamina); 61 | 62 | // If the stamina is less than the max stamina, then reduce stamina by 1 (as long as it's not already 0) 63 | uint32 maxStamina = ENGINE.getMonValueForBattle(battleKey, targetIndex, monIndex, MonStateIndexName.Stamina); 64 | if (staminaDelta + int32(maxStamina) > 0) { 65 | ENGINE.updateMonState(targetIndex, monIndex, MonStateIndexName.Stamina, -1); 66 | } 67 | 68 | uint256 turnsLeft = uint256(extraData); 69 | if (turnsLeft == 1) { 70 | return (extraData, true); 71 | } else { 72 | return (bytes32(turnsLeft - 1), false); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/mons/sofabbi/SnackBreak.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | import {IMoveSet} from "../../moves/IMoveSet.sol"; 10 | 11 | contract SnackBreak is IMoveSet { 12 | uint256 public constant DEFAULT_HEAL_DENOM = 2; 13 | uint256 public constant MAX_DIVISOR = 3; 14 | 15 | IEngine immutable ENGINE; 16 | 17 | constructor(IEngine _ENGINE) { 18 | ENGINE = _ENGINE; 19 | } 20 | 21 | function name() public pure override returns (string memory) { 22 | return "Snack Break"; 23 | } 24 | 25 | function _getSnackLevel(bytes32 battleKey, uint256 playerIndex, uint256 monIndex) internal view returns (uint256) { 26 | return uint256(ENGINE.getGlobalKV(battleKey, keccak256(abi.encode(playerIndex, monIndex, name())))); 27 | } 28 | 29 | function _increaseSnackLevel(bytes32 battleKey, uint256 playerIndex, uint256 monIndex) internal { 30 | uint256 snackLevel = _getSnackLevel(battleKey, playerIndex, monIndex); 31 | if (snackLevel < MAX_DIVISOR) { 32 | ENGINE.setGlobalKV(keccak256(abi.encode(playerIndex, monIndex, name())), uint192(snackLevel + 1)); 33 | } 34 | } 35 | 36 | function move(bytes32 battleKey, uint256 attackerPlayerIndex, uint240, uint256) external { 37 | uint256 activeMonIndex = ENGINE.getActiveMonIndexForBattleState(battleKey)[attackerPlayerIndex]; 38 | uint256 snackLevel = _getSnackLevel(battleKey, attackerPlayerIndex, activeMonIndex); 39 | uint32 maxHp = ENGINE.getMonValueForBattle(battleKey, attackerPlayerIndex, activeMonIndex, MonStateIndexName.Hp); 40 | 41 | // Heal active mon by max HP / 2**snackLevel 42 | int32 healAmount = int32(uint32(maxHp / (DEFAULT_HEAL_DENOM * (2 ** snackLevel)))); 43 | int32 currentDamage = 44 | ENGINE.getMonStateForBattle(battleKey, attackerPlayerIndex, activeMonIndex, MonStateIndexName.Hp); 45 | if (currentDamage + healAmount > 0) { 46 | healAmount = -1 * currentDamage; 47 | } 48 | ENGINE.updateMonState(attackerPlayerIndex, activeMonIndex, MonStateIndexName.Hp, healAmount); 49 | 50 | // Update the snack level 51 | _increaseSnackLevel(battleKey, attackerPlayerIndex, activeMonIndex); 52 | } 53 | 54 | function stamina(bytes32, uint256, uint256) external pure returns (uint32) { 55 | return 1; 56 | } 57 | 58 | function priority(bytes32, uint256) external pure returns (uint32) { 59 | return DEFAULT_PRIORITY; 60 | } 61 | 62 | function moveType(bytes32) public pure returns (Type) { 63 | return Type.Nature; 64 | } 65 | 66 | function moveClass(bytes32) public pure returns (MoveClass) { 67 | return MoveClass.Self; 68 | } 69 | 70 | function isValidTarget(bytes32, uint240) external pure returns (bool) { 71 | return true; 72 | } 73 | 74 | function extraDataType() external pure returns (ExtraDataType) { 75 | return ExtraDataType.None; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/mons/aurox/IronWall.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | import {BasicEffect} from "../../effects/BasicEffect.sol"; 10 | import {IEffect} from "../../effects/IEffect.sol"; 11 | import {IMoveSet} from "../../moves/IMoveSet.sol"; 12 | 13 | contract IronWall is IMoveSet, BasicEffect { 14 | 15 | int32 public constant HEAL_PERCENT = 50; 16 | 17 | IEngine immutable ENGINE; 18 | 19 | constructor(IEngine _ENGINE) { 20 | ENGINE = _ENGINE; 21 | } 22 | 23 | function name() public pure override(IMoveSet, BasicEffect) returns (string memory) { 24 | return "Iron Wall"; 25 | } 26 | 27 | function move(bytes32 battleKey, uint256 attackerPlayerIndex, uint240, uint256) external { 28 | // Get the active mon index 29 | uint256 activeMonIndex = ENGINE.getActiveMonIndexForBattleState(battleKey)[attackerPlayerIndex]; 30 | // The effect will last until Aurox switches out 31 | ENGINE.addEffect(attackerPlayerIndex, activeMonIndex, IEffect(address(this)), bytes32(0)); 32 | } 33 | 34 | function stamina(bytes32, uint256, uint256) external pure returns (uint32) { 35 | return 3; 36 | } 37 | 38 | function priority(bytes32, uint256) external pure returns (uint32) { 39 | return DEFAULT_PRIORITY; 40 | } 41 | 42 | function moveType(bytes32) public pure returns (Type) { 43 | return Type.Metal; 44 | } 45 | 46 | function moveClass(bytes32) public pure returns (MoveClass) { 47 | return MoveClass.Self; 48 | } 49 | 50 | function isValidTarget(bytes32, uint240) external pure returns (bool) { 51 | return true; 52 | } 53 | 54 | function extraDataType() external pure returns (ExtraDataType) { 55 | return ExtraDataType.None; 56 | } 57 | 58 | // IEffect implementation 59 | function shouldRunAtStep(EffectStep step) external pure override returns (bool) { 60 | return (step == EffectStep.AfterDamage || step == EffectStep.OnMonSwitchOut); 61 | } 62 | 63 | function onAfterDamage(uint256, bytes32 extraData, uint256 targetIndex, uint256 monIndex, int32 damageDealt) 64 | external 65 | override 66 | returns (bytes32 updatedExtraData, bool removeAfterRun) 67 | { 68 | // Calculate 50% of the damage taken 69 | int32 healAmount = (damageDealt * HEAL_PERCENT) / 100; 70 | // Heal only if not KO'ed 71 | if ( 72 | healAmount > 0 73 | && ENGINE.getMonStateForBattle( 74 | ENGINE.battleKeyForWrite(), targetIndex, monIndex, MonStateIndexName.IsKnockedOut 75 | ) == 0 76 | ) { 77 | ENGINE.updateMonState(targetIndex, monIndex, MonStateIndexName.Hp, healAmount); 78 | } 79 | return (extraData, false); 80 | } 81 | 82 | function onMonSwitchOut(uint256, bytes32, uint256, uint256) 83 | external 84 | pure 85 | override 86 | returns (bytes32 updatedExtraData, bool removeAfterRun) 87 | { 88 | return (bytes32(0), true); 89 | } 90 | } -------------------------------------------------------------------------------- /src/mons/pengym/PistolSquat.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | 10 | import {IEffect} from "../../effects/IEffect.sol"; 11 | import {StandardAttack} from "../../moves/StandardAttack.sol"; 12 | import {ATTACK_PARAMS} from "../../moves/StandardAttackStructs.sol"; 13 | import {ITypeCalculator} from "../../types/ITypeCalculator.sol"; 14 | 15 | contract PistolSquat is StandardAttack { 16 | constructor(IEngine ENGINE, ITypeCalculator TYPE_CALCULATOR) 17 | StandardAttack( 18 | address(msg.sender), 19 | ENGINE, 20 | TYPE_CALCULATOR, 21 | ATTACK_PARAMS({ 22 | NAME: "Pistol Squat", 23 | BASE_POWER: 80, 24 | STAMINA_COST: 2, 25 | ACCURACY: 100, 26 | MOVE_TYPE: Type.Metal, 27 | MOVE_CLASS: MoveClass.Physical, 28 | PRIORITY: DEFAULT_PRIORITY - 1, // This is -1 priority 29 | CRIT_RATE: DEFAULT_CRIT_RATE, 30 | VOLATILITY: DEFAULT_VOL, 31 | EFFECT_ACCURACY: 0, 32 | EFFECT: IEffect(address(0)) 33 | }) 34 | ) 35 | {} 36 | 37 | function _findRandomNonKOedMon(uint256 playerIndex, uint256 currentMonIndex, uint256 rng) 38 | internal 39 | view 40 | returns (int32) 41 | { 42 | bytes32 battleKey = ENGINE.battleKeyForWrite(); 43 | uint256 teamSize = ENGINE.getTeamSize(battleKey, playerIndex); 44 | for (uint256 i; i < teamSize; ++i) { 45 | uint256 monIndex = (i + rng) % teamSize; 46 | // Only look at other mons 47 | if (monIndex != currentMonIndex) { 48 | bool isKOed = 49 | ENGINE.getMonStateForBattle(battleKey, playerIndex, monIndex, MonStateIndexName.IsKnockedOut) == 1; 50 | if (!isKOed) { 51 | return int32(int256(monIndex)); 52 | } 53 | } 54 | } 55 | return -1; 56 | } 57 | 58 | function move(bytes32 battleKey, uint256 attackerPlayerIndex, uint240 extraData, uint256 rng) 59 | public 60 | override 61 | { 62 | // Deal the damage 63 | super.move(battleKey, attackerPlayerIndex, extraData, rng); 64 | 65 | // Deal damage and then force a switch if the opposing mon is not KO'ed 66 | uint256 otherPlayerIndex = (attackerPlayerIndex + 1) % 2; 67 | uint256 otherPlayerActiveMonIndex = 68 | ENGINE.getActiveMonIndexForBattleState(ENGINE.battleKeyForWrite())[otherPlayerIndex]; 69 | bool isKOed = 70 | ENGINE.getMonStateForBattle( 71 | battleKey, otherPlayerIndex, otherPlayerActiveMonIndex, MonStateIndexName.IsKnockedOut 72 | ) == 1; 73 | if (!isKOed) { 74 | int32 possibleSwitchTarget = _findRandomNonKOedMon(otherPlayerIndex, otherPlayerActiveMonIndex, rng); 75 | if (possibleSwitchTarget != -1) { 76 | ENGINE.switchActiveMon(otherPlayerIndex, uint256(uint32(possibleSwitchTarget))); 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/mons/ghouliath/EternalGrudge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | import {StatBoostToApply} from "../../Structs.sol"; 8 | 9 | import {IEngine} from "../../IEngine.sol"; 10 | import {StatBoosts} from "../../effects/StatBoosts.sol"; 11 | import {IMoveSet} from "../../moves/IMoveSet.sol"; 12 | 13 | contract EternalGrudge is IMoveSet { 14 | uint8 public constant ATTACK_DEBUFF_PERCENT = 50; 15 | uint8 public constant SP_ATTACK_DEBUFF_PERCENT = 50; 16 | 17 | IEngine immutable ENGINE; 18 | StatBoosts immutable STAT_BOOSTS; 19 | 20 | constructor(IEngine _ENGINE, StatBoosts _STAT_BOOSTS) { 21 | ENGINE = _ENGINE; 22 | STAT_BOOSTS = _STAT_BOOSTS; 23 | } 24 | 25 | function name() public pure override returns (string memory) { 26 | return "Eternal Grudge"; 27 | } 28 | 29 | function move(bytes32 battleKey, uint256 attackerPlayerIndex, uint240, uint256) external { 30 | // Apply the debuff (50% debuff to both attack and special attack) 31 | uint256 defenderPlayerIndex = (attackerPlayerIndex + 1) % 2; 32 | uint256 defenderMonIndex = ENGINE.getActiveMonIndexForBattleState(battleKey)[defenderPlayerIndex]; 33 | StatBoostToApply[] memory statBoosts = new StatBoostToApply[](2); 34 | statBoosts[0] = StatBoostToApply({ 35 | stat: MonStateIndexName.Attack, 36 | boostPercent: ATTACK_DEBUFF_PERCENT, 37 | boostType: StatBoostType.Divide 38 | }); 39 | statBoosts[1] = StatBoostToApply({ 40 | stat: MonStateIndexName.SpecialAttack, 41 | boostPercent: SP_ATTACK_DEBUFF_PERCENT, 42 | boostType: StatBoostType.Divide 43 | }); 44 | STAT_BOOSTS.addStatBoosts(defenderPlayerIndex, defenderMonIndex, statBoosts, StatBoostFlag.Temp); 45 | uint256 attackerMonIndex = ENGINE.getActiveMonIndexForBattleState(battleKey)[attackerPlayerIndex]; 46 | 47 | // KO self by dealing just enough damage 48 | int32 currentDamage = 49 | ENGINE.getMonStateForBattle(battleKey, attackerPlayerIndex, attackerMonIndex, MonStateIndexName.Hp); 50 | uint32 maxHp = 51 | ENGINE.getMonValueForBattle(battleKey, attackerPlayerIndex, attackerMonIndex, MonStateIndexName.Hp); 52 | int32 damageNeededToKOSelf = int32(maxHp) + currentDamage; 53 | ENGINE.dealDamage(attackerPlayerIndex, attackerMonIndex, damageNeededToKOSelf); 54 | } 55 | 56 | function stamina(bytes32, uint256, uint256) external pure returns (uint32) { 57 | return 2; 58 | } 59 | 60 | function priority(bytes32, uint256) external pure returns (uint32) { 61 | return DEFAULT_PRIORITY + 1; 62 | } 63 | 64 | function moveType(bytes32) public pure returns (Type) { 65 | return Type.Yang; 66 | } 67 | 68 | function isValidTarget(bytes32, uint240) external pure returns (bool) { 69 | return true; 70 | } 71 | 72 | function moveClass(bytes32) public pure returns (MoveClass) { 73 | return MoveClass.Self; 74 | } 75 | 76 | function extraDataType() external pure returns (ExtraDataType) { 77 | return ExtraDataType.None; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/effects/status/ZapStatus.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "../../Enums.sol"; 5 | import {IEngine} from "../../IEngine.sol"; 6 | 7 | import {StatusEffect} from "./StatusEffect.sol"; 8 | 9 | contract ZapStatus is StatusEffect { 10 | 11 | uint8 private constant ALREADY_SKIPPED = 1; 12 | 13 | constructor(IEngine engine) StatusEffect(engine) {} 14 | 15 | function name() public pure override returns (string memory) { 16 | return "Zap"; 17 | } 18 | 19 | function shouldRunAtStep(EffectStep r) external pure override returns (bool) { 20 | return (r == EffectStep.OnApply || r == EffectStep.RoundStart || r == EffectStep.RoundEnd 21 | || r == EffectStep.OnRemove || r == EffectStep.OnMonSwitchIn); 22 | } 23 | 24 | function onApply(uint256 rng, bytes32 data, uint256 targetIndex, uint256 monIndex) 25 | public 26 | override 27 | returns (bytes32 updatedExtraData, bool removeAfterRun) 28 | { 29 | super.onApply(rng, data, targetIndex, monIndex); 30 | 31 | // Get the battle key and compute priority player index 32 | bytes32 battleKey = ENGINE.battleKeyForWrite(); 33 | uint256 priorityPlayerIndex = ENGINE.computePriorityPlayerIndex(battleKey, rng); 34 | 35 | uint8 state; 36 | 37 | // Check if opponent has yet to move 38 | if (targetIndex != priorityPlayerIndex) { 39 | // Opponent hasn't moved yet (they're the non-priority player) 40 | // Set skip turn flag immediately 41 | ENGINE.updateMonState(targetIndex, monIndex, MonStateIndexName.ShouldSkipTurn, 1); 42 | state = ALREADY_SKIPPED; // Ready to remove at RoundEnd 43 | } 44 | // else: Opponent has already moved, state = 0 (not yet skipped), wait for RoundStart 45 | 46 | return (bytes32(uint256(state)), false); 47 | } 48 | 49 | function onRoundStart(uint256, bytes32, uint256 targetIndex, uint256 monIndex) 50 | external 51 | override 52 | returns (bytes32 updatedExtraData, bool removeAfterRun) 53 | { 54 | // If we're at RoundStart and effect is still present, always set skip flag and mark as skipped 55 | // (If state was ALREADY_SKIPPED, effect would have been removed at previous RoundEnd) 56 | ENGINE.updateMonState(targetIndex, monIndex, MonStateIndexName.ShouldSkipTurn, 1); 57 | return (bytes32(uint256(ALREADY_SKIPPED)), false); 58 | } 59 | 60 | function onMonSwitchIn(uint256, bytes32 extraData, uint256, uint256) 61 | external 62 | override 63 | pure 64 | returns (bytes32 updatedExtraData, bool removeAfterRun) 65 | { 66 | return (extraData, false); 67 | } 68 | 69 | function onRemove(bytes32 data, uint256 targetIndex, uint256 monIndex) public override { 70 | super.onRemove(data, targetIndex, monIndex); 71 | } 72 | 73 | function onRoundEnd(uint256, bytes32 extraData, uint256, uint256) 74 | public 75 | pure 76 | override 77 | returns (bytes32, bool) 78 | { 79 | uint8 state = uint8(uint256(extraData)); 80 | 81 | // Otherwise keep the effect 82 | return (extraData, state == ALREADY_SKIPPED); 83 | } 84 | } -------------------------------------------------------------------------------- /src/effects/BasicEffect.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "../Enums.sol"; 5 | import "../Structs.sol"; 6 | 7 | abstract contract BasicEffect is IEffect { 8 | function name() external virtual returns (string memory) { 9 | return ""; 10 | } 11 | 12 | // Whether to run the effect at a specific step 13 | function shouldRunAtStep(EffectStep r) external virtual returns (bool); 14 | 15 | // Whether or not to add the effect if the step condition is met 16 | function shouldApply(bytes32, uint256, uint256) external virtual returns (bool) { 17 | return true; 18 | } 19 | 20 | // Lifecycle hooks during normal battle flow 21 | function onRoundStart(uint256, bytes32 extraData, uint256, uint256) 22 | external 23 | virtual 24 | returns (bytes32 updatedExtraData, bool removeAfterRun) 25 | { 26 | return (extraData, false); 27 | } 28 | 29 | function onRoundEnd(uint256, bytes32 extraData, uint256, uint256) 30 | external 31 | virtual 32 | returns (bytes32 updatedExtraData, bool removeAfterRun) 33 | { 34 | return (extraData, false); 35 | } 36 | 37 | // NOTE: ONLY RUN ON GLOBAL EFFECTS (mons have their Ability as their own hook to apply an effect on switch in) 38 | function onMonSwitchIn(uint256, bytes32 extraData, uint256, uint256) 39 | external 40 | virtual 41 | returns (bytes32 updatedExtraData, bool removeAfterRun) 42 | { 43 | return (extraData, false); 44 | } 45 | 46 | // NOTE: CURRENTLY ONLY RUN LOCALLY ON MONS (global effects do not have this hook) 47 | function onMonSwitchOut(uint256, bytes32 extraData, uint256, uint256) 48 | external 49 | virtual 50 | returns (bytes32 updatedExtraData, bool removeAfterRun) 51 | { 52 | return (extraData, false); 53 | } 54 | 55 | // NOTE: CURRENTLY ONLY RUN LOCALLY ON MONS (global effects do not have this hook) 56 | function onAfterDamage(uint256, bytes32 extraData, uint256, uint256, int32) 57 | external 58 | virtual 59 | returns (bytes32 updatedExtraData, bool removeAfterRun) 60 | { 61 | return (extraData, false); 62 | } 63 | 64 | function onAfterMove(uint256, bytes32 extraData, uint256, uint256) 65 | external 66 | virtual 67 | returns (bytes32 updatedExtraData, bool removeAfterRun) 68 | { 69 | return (extraData, false); 70 | } 71 | 72 | // NOTE: CURRENTLY ONLY RUN LOCALLY ON MONS (global effects do not have this hook) 73 | // WARNING: Avoid chaining this effect to prevent recursive calls 74 | function onUpdateMonState(uint256, bytes32 extraData, uint256, uint256, MonStateIndexName, int32) 75 | external 76 | virtual 77 | returns (bytes32 updatedExtraData, bool removeAfterRun) 78 | { 79 | return (extraData, false); 80 | } 81 | 82 | // Lifecycle hooks when being applied or removed 83 | function onApply(uint256, bytes32, uint256, uint256) 84 | external 85 | virtual 86 | returns (bytes32 updatedExtraData, bool removeAfterRun) 87 | { 88 | return (updatedExtraData, removeAfterRun); 89 | } 90 | 91 | function onRemove(bytes32 extraData, uint256 targetIndex, uint256 monIndex) external virtual {} 92 | } 93 | -------------------------------------------------------------------------------- /src/mons/inutia/Interweaving.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Enums.sol"; 6 | import {EffectInstance, StatBoostToApply} from "../../Structs.sol"; 7 | import {IEngine} from "../../IEngine.sol"; 8 | import {IAbility} from "../../abilities/IAbility.sol"; 9 | import {BasicEffect} from "../../effects/BasicEffect.sol"; 10 | import {IEffect} from "../../effects/IEffect.sol"; 11 | import {StatBoosts} from "../../effects/StatBoosts.sol"; 12 | 13 | contract Interweaving is IAbility, BasicEffect { 14 | uint8 constant DECREASE_PERCENTAGE = 10; 15 | IEngine immutable ENGINE; 16 | StatBoosts immutable STAT_BOOST; 17 | 18 | constructor(IEngine _ENGINE, StatBoosts _STAT_BOOSTS) { 19 | ENGINE = _ENGINE; 20 | STAT_BOOST = _STAT_BOOSTS; 21 | } 22 | 23 | function name() public pure override(IAbility, BasicEffect) returns (string memory) { 24 | return "Interweaving"; 25 | } 26 | 27 | function activateOnSwitch(bytes32 battleKey, uint256 playerIndex, uint256 monIndex) external { 28 | // Lower opposing mon Attack stat 29 | uint256 otherPlayerIndex = (playerIndex + 1) % 2; 30 | uint256 otherPlayerActiveMonIndex = 31 | ENGINE.getActiveMonIndexForBattleState(ENGINE.battleKeyForWrite())[otherPlayerIndex]; 32 | StatBoostToApply[] memory statBoosts = new StatBoostToApply[](1); 33 | statBoosts[0] = StatBoostToApply({ 34 | stat: MonStateIndexName.Attack, 35 | boostPercent: DECREASE_PERCENTAGE, 36 | boostType: StatBoostType.Divide 37 | }); 38 | STAT_BOOST.addStatBoosts(otherPlayerIndex, otherPlayerActiveMonIndex, statBoosts, StatBoostFlag.Temp); 39 | 40 | // Check if the effect has already been set for this mon 41 | (EffectInstance[] memory effects, ) = ENGINE.getEffects(battleKey, playerIndex, monIndex); 42 | for (uint256 i = 0; i < effects.length; i++) { 43 | if (address(effects[i].effect) == address(this)) { 44 | return; 45 | } 46 | } 47 | // Otherwise, add this effect to the mon when it switches in 48 | // This way we can trigger on switch out 49 | ENGINE.addEffect(playerIndex, monIndex, IEffect(address(this)), bytes32(0)); 50 | } 51 | 52 | function shouldRunAtStep(EffectStep step) external pure override returns (bool) { 53 | return (step == EffectStep.OnMonSwitchOut || step == EffectStep.OnApply); 54 | } 55 | 56 | function onMonSwitchOut(uint256, bytes32, uint256 targetIndex, uint256) 57 | external 58 | override 59 | returns (bytes32 updatedExtraData, bool removeAfterRun) 60 | { 61 | uint256 otherPlayerIndex = (targetIndex + 1) % 2; 62 | uint256 otherPlayerActiveMonIndex = 63 | ENGINE.getActiveMonIndexForBattleState(ENGINE.battleKeyForWrite())[otherPlayerIndex]; 64 | StatBoostToApply[] memory statBoosts = new StatBoostToApply[](1); 65 | statBoosts[0] = StatBoostToApply({ 66 | stat: MonStateIndexName.SpecialAttack, 67 | boostPercent: DECREASE_PERCENTAGE, 68 | boostType: StatBoostType.Divide 69 | }); 70 | STAT_BOOST.addStatBoosts(otherPlayerIndex, otherPlayerActiveMonIndex, statBoosts, StatBoostFlag.Temp); 71 | return (bytes32(0), false); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/mons/pengym/DeepFreeze.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | import {EffectInstance} from "../../Structs.sol"; 8 | 9 | import {IEngine} from "../../IEngine.sol"; 10 | import {IEffect} from "../../effects/IEffect.sol"; 11 | import {AttackCalculator} from "../../moves/AttackCalculator.sol"; 12 | import {IMoveSet} from "../../moves/IMoveSet.sol"; 13 | import {ITypeCalculator} from "../../types/ITypeCalculator.sol"; 14 | 15 | contract DeepFreeze is IMoveSet { 16 | uint32 public constant BASE_POWER = 90; 17 | 18 | IEngine immutable ENGINE; 19 | IEffect immutable FROSTBITE; 20 | ITypeCalculator immutable TYPE_CALCULATOR; 21 | 22 | constructor(IEngine _ENGINE, ITypeCalculator _TYPE_CALCULATOR, IEffect _FROSTBITE_STATUS) { 23 | ENGINE = _ENGINE; 24 | FROSTBITE = _FROSTBITE_STATUS; 25 | TYPE_CALCULATOR = _TYPE_CALCULATOR; 26 | } 27 | 28 | function name() public pure override returns (string memory) { 29 | return "Deep Freeze"; 30 | } 31 | 32 | function _frostbiteExists(bytes32 battleKey, uint256 targetIndex, uint256 monIndex) internal view returns (int32) { 33 | (EffectInstance[] memory effects, uint256[] memory indices) = ENGINE.getEffects(battleKey, targetIndex, monIndex); 34 | uint256 numEffects = effects.length; 35 | for (uint256 i; i < numEffects;) { 36 | if (address(effects[i].effect) == address(FROSTBITE)) { 37 | return int32(int256(indices[i])); 38 | } 39 | unchecked { 40 | ++i; 41 | } 42 | } 43 | return -1; 44 | } 45 | 46 | function move(bytes32 battleKey, uint256 attackerPlayerIndex, uint240, uint256 rng) external { 47 | uint256 otherPlayerIndex = (attackerPlayerIndex + 1) % 2; 48 | uint256 otherPlayerActiveMonIndex = 49 | ENGINE.getActiveMonIndexForBattleState(ENGINE.battleKeyForWrite())[otherPlayerIndex]; 50 | uint32 damageToDeal = BASE_POWER; 51 | int32 frostbiteIndex = _frostbiteExists(battleKey, otherPlayerIndex, otherPlayerActiveMonIndex); 52 | // Remove frostbite if it exists, and double the damage dealt 53 | if (frostbiteIndex != -1) { 54 | ENGINE.removeEffect(otherPlayerIndex, otherPlayerActiveMonIndex, uint256(uint32(frostbiteIndex))); 55 | damageToDeal = damageToDeal * 2; 56 | } 57 | // Deal damage 58 | AttackCalculator._calculateDamage( 59 | ENGINE, 60 | TYPE_CALCULATOR, 61 | battleKey, 62 | attackerPlayerIndex, 63 | damageToDeal, 64 | DEFAULT_ACCURACY, 65 | DEFAULT_VOL, 66 | moveType(battleKey), 67 | moveClass(battleKey), 68 | rng, 69 | DEFAULT_CRIT_RATE 70 | ); 71 | } 72 | 73 | function stamina(bytes32, uint256, uint256) external pure returns (uint32) { 74 | return 3; 75 | } 76 | 77 | function priority(bytes32, uint256) external pure returns (uint32) { 78 | return DEFAULT_PRIORITY; 79 | } 80 | 81 | function moveType(bytes32) public pure returns (Type) { 82 | return Type.Ice; 83 | } 84 | 85 | function moveClass(bytes32) public pure returns (MoveClass) { 86 | return MoveClass.Physical; 87 | } 88 | 89 | function isValidTarget(bytes32, uint240) external pure returns (bool) { 90 | return true; 91 | } 92 | 93 | function extraDataType() external pure returns (ExtraDataType) { 94 | return ExtraDataType.None; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/mons/iblivion/Brightback.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | import "../../Structs.sol"; 8 | 9 | import {IEngine} from "../../IEngine.sol"; 10 | import {AttackCalculator} from "../../moves/AttackCalculator.sol"; 11 | import {IMoveSet} from "../../moves/IMoveSet.sol"; 12 | import {ITypeCalculator} from "../../types/ITypeCalculator.sol"; 13 | 14 | import {Baselight} from "./Baselight.sol"; 15 | 16 | /** 17 | * Brightback Move for Iblivion 18 | * - Power: 70, Stamina: 2, Type: Yin, Class: Physical 19 | * - Consumes 1 Baselight stack to heal for 50% of damage dealt 20 | * - If no Baselight stack available, still deals damage but doesn't heal 21 | */ 22 | contract Brightback is IMoveSet { 23 | uint32 public constant BASE_POWER = 70; 24 | 25 | IEngine immutable ENGINE; 26 | ITypeCalculator immutable TYPE_CALCULATOR; 27 | Baselight immutable BASELIGHT; 28 | 29 | constructor(IEngine _ENGINE, ITypeCalculator _TYPE_CALCULATOR, Baselight _BASELIGHT) { 30 | ENGINE = _ENGINE; 31 | TYPE_CALCULATOR = _TYPE_CALCULATOR; 32 | BASELIGHT = _BASELIGHT; 33 | } 34 | 35 | function name() public pure override returns (string memory) { 36 | return "Brightback"; 37 | } 38 | 39 | function move(bytes32 battleKey, uint256 attackerPlayerIndex, uint240, uint256 rng) external { 40 | (int32 damageDealt,) = AttackCalculator._calculateDamage( 41 | ENGINE, 42 | TYPE_CALCULATOR, 43 | battleKey, 44 | attackerPlayerIndex, 45 | BASE_POWER, 46 | DEFAULT_ACCURACY, 47 | DEFAULT_VOL, 48 | moveType(battleKey), 49 | moveClass(battleKey), 50 | rng, 51 | DEFAULT_CRIT_RATE 52 | ); 53 | 54 | uint256 monIndex = ENGINE.getActiveMonIndexForBattleState(battleKey)[attackerPlayerIndex]; 55 | uint256 baselightLevel = BASELIGHT.getBaselightLevel(battleKey, attackerPlayerIndex, monIndex); 56 | 57 | // Only heal if we have at least 1 Baselight stack 58 | if (baselightLevel >= 1) { 59 | // Consume 1 Baselight stack 60 | BASELIGHT.decreaseBaselightLevel(attackerPlayerIndex, monIndex, 1); 61 | 62 | // Heal for half of damage done 63 | int32 healAmount = damageDealt / 2; 64 | int32 hpDelta = ENGINE.getMonStateForBattle(battleKey, attackerPlayerIndex, monIndex, MonStateIndexName.Hp); 65 | 66 | // Prevent overhealing 67 | if (hpDelta + healAmount > 0) { 68 | healAmount = -1 * hpDelta; 69 | } 70 | 71 | // Do the heal 72 | ENGINE.updateMonState(attackerPlayerIndex, monIndex, MonStateIndexName.Hp, healAmount); 73 | } 74 | } 75 | 76 | function stamina(bytes32, uint256, uint256) external pure returns (uint32) { 77 | return 2; 78 | } 79 | 80 | function priority(bytes32, uint256) external pure returns (uint32) { 81 | return DEFAULT_PRIORITY; 82 | } 83 | 84 | function moveType(bytes32) public pure returns (Type) { 85 | return Type.Yin; 86 | } 87 | 88 | function moveClass(bytes32) public pure returns (MoveClass) { 89 | return MoveClass.Physical; 90 | } 91 | 92 | function isValidTarget(bytes32, uint240) external pure returns (bool) { 93 | return true; 94 | } 95 | 96 | function extraDataType() external pure returns (ExtraDataType) { 97 | return ExtraDataType.None; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/mons/sofabbi/Gachachacha.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../Constants.sol"; 6 | import "../../Enums.sol"; 7 | 8 | import {IEngine} from "../../IEngine.sol"; 9 | import {AttackCalculator} from "../../moves/AttackCalculator.sol"; 10 | import {IMoveSet} from "../../moves/IMoveSet.sol"; 11 | import {ITypeCalculator} from "../../types/ITypeCalculator.sol"; 12 | 13 | contract Gachachacha is IMoveSet { 14 | uint256 public constant MIN_BASE_POWER = 1; 15 | uint256 public constant MAX_BASE_POWER = 200; 16 | uint256 public constant SELF_KO_CHANCE = 5; 17 | uint256 public constant OPP_KO_CHANCE = 5; 18 | 19 | // RNG table 20 | // Damage | Self KO damage | Opp KO damage 21 | // [0 ... 200] | [201 ... 205] | [206 ... 210] 22 | uint256 public constant SELF_KO_THRESHOLD_L = MAX_BASE_POWER; 23 | uint256 public constant SELF_KO_THRESHOLD_R = MAX_BASE_POWER + SELF_KO_CHANCE; 24 | // uint256 constant public OPP_KO_THRESHOLD_L = SELF_KO_THRESHOLD_R; 25 | uint256 public constant OPP_KO_THRESHOLD_R = SELF_KO_THRESHOLD_R + OPP_KO_CHANCE; 26 | 27 | IEngine immutable ENGINE; 28 | ITypeCalculator immutable TYPE_CALCULATOR; 29 | 30 | constructor(IEngine _ENGINE, ITypeCalculator _TYPE_CALCULATOR) { 31 | ENGINE = _ENGINE; 32 | TYPE_CALCULATOR = _TYPE_CALCULATOR; 33 | } 34 | 35 | function name() public pure override returns (string memory) { 36 | return "Gachachacha"; 37 | } 38 | 39 | function move(bytes32 battleKey, uint256 attackerPlayerIndex, uint240, uint256 rng) external { 40 | uint256 chance = rng % OPP_KO_THRESHOLD_R; 41 | uint32 basePower; 42 | uint256 defenderPlayerIndex = (attackerPlayerIndex + 1) % 2; 43 | uint256 playerForCalculator = attackerPlayerIndex; 44 | uint256[] memory activeMon = ENGINE.getActiveMonIndexForBattleState(battleKey); 45 | if (chance <= SELF_KO_THRESHOLD_L) { 46 | basePower = uint32(chance); 47 | } else if (chance > SELF_KO_THRESHOLD_L && chance <= SELF_KO_THRESHOLD_R) { 48 | basePower = ENGINE.getMonValueForBattle( 49 | battleKey, attackerPlayerIndex, activeMon[attackerPlayerIndex], MonStateIndexName.Hp 50 | ); 51 | playerForCalculator = defenderPlayerIndex; 52 | } else { 53 | basePower = ENGINE.getMonValueForBattle( 54 | battleKey, defenderPlayerIndex, activeMon[defenderPlayerIndex], MonStateIndexName.Hp 55 | ); 56 | } 57 | AttackCalculator._calculateDamage( 58 | ENGINE, 59 | TYPE_CALCULATOR, 60 | battleKey, 61 | playerForCalculator, 62 | basePower, 63 | DEFAULT_ACCURACY, 64 | DEFAULT_VOL, 65 | moveType(battleKey), 66 | moveClass(battleKey), 67 | rng, 68 | DEFAULT_CRIT_RATE 69 | ); 70 | } 71 | 72 | function stamina(bytes32, uint256, uint256) external pure returns (uint32) { 73 | return 3; 74 | } 75 | 76 | function priority(bytes32, uint256) external pure returns (uint32) { 77 | return DEFAULT_PRIORITY; 78 | } 79 | 80 | function moveType(bytes32) public pure returns (Type) { 81 | return Type.Cyber; 82 | } 83 | 84 | function moveClass(bytes32) public pure returns (MoveClass) { 85 | return MoveClass.Physical; 86 | } 87 | 88 | function isValidTarget(bytes32, uint240) external pure returns (bool) { 89 | return true; 90 | } 91 | 92 | function extraDataType() external pure returns (ExtraDataType) { 93 | return ExtraDataType.None; 94 | } 95 | } 96 | --------------------------------------------------------------------------------