Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions drool/abilities.csv
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ Preemptive Shock,Volthare,"When Volthare swaps in, they deal a small amount of d
Interweaving,Inutia,"When Inutia swaps in, the opposing mon's ATK decreases 10%. When Inutia swaps out, the opposing mon's SpATK decreases 10%."
Up Only,Aurox,"Whenever Aurox takes damage, they gain a persistent 10% ATK boost."
Dreamcatcher,Xmon,"Whenever Xmon gains stamina, heal 6.6% of max HP."
Savior Complex,Ekineki,"On switch-in, gain 15/25/30% SpATK boost based on KO'd mons (1/2/3+). Triggers once per game. Boost is temporary (cleared on switch-out)."
6 changes: 5 additions & 1 deletion drool/moves.csv
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,8 @@ Bull Rush,Aurox,120,2,100,0,Metal,Physical,Deals damage. Also deals 20% of max H
Contagious Slumber,Xmon,0,2,100,0,Cosmic,Other,"Inflicts Sleep on self and opponent. When asleep, you are forced to rest.",,No
Vital Siphon,Xmon,40,2,90,0,Cosmic,Special,"Deals damage, 50% chance to steal 1 stamina from opponent.",,No
Somniphobia,Xmon,0,1,100,0,Cosmic,Other,"For the next 6 turns, any mon that rests will take 1/16th of max HP as damage.",,No
Night Terrors,Xmon,0,0,100,0,Cosmic,Special,Gain a Terror stack. Deals damage and costs stamina at end of turn for each Terror stack. Deals extra damage if opponent is alseep.,,No
Night Terrors,Xmon,0,0,100,0,Cosmic,Special,Gain a Terror stack. Deals damage and costs stamina at end of turn for each Terror stack. Deals extra damage if opponent is alseep.,,No
Bubble Bop,Ekineki,50,3,100,0,Liquid,Special,Hits twice. Each hit deals 50 base power.,,No
Sneak Attack,Ekineki,60,2,100,0,Liquid,Special,Hits any opponent mon (even non-active). Can only be used once per switch-in.,,Yes
999,Ekineki,0,2,100,0,Math,Self,Sets crit rate to 90% on the next turn for all moves.,,No
Overflow,Ekineki,90,3,100,0,Math,Special,Deals damage.,,No
28 changes: 14 additions & 14 deletions snapshots/EngineGasTest.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
{
"B1_Execute": "959518",
"B1_Setup": "817602",
"B2_Execute": "739644",
"B2_Setup": "279034",
"Battle1_Execute": "486807",
"Battle1_Setup": "794019",
"Battle2_Execute": "401430",
"Battle2_Setup": "235683",
"FirstBattle": "3455942",
"B1_Execute": "961278",
"B1_Setup": "817690",
"B2_Execute": "741382",
"B2_Setup": "279144",
"Battle1_Execute": "487401",
"Battle1_Setup": "794107",
"Battle2_Execute": "402024",
"Battle2_Setup": "235771",
"FirstBattle": "3463862",
"Intermediary stuff": "47036",
"SecondBattle": "3545468",
"Setup 1": "1673954",
"Setup 2": "296769",
"Setup 3": "339643",
"ThirdBattle": "2866229"
"SecondBattle": "3554466",
"Setup 1": "1674042",
"Setup 2": "296857",
"Setup 3": "339731",
"ThirdBattle": "2874149"
}
6 changes: 3 additions & 3 deletions snapshots/MatchmakerTest.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"Accept1": "312093",
"Accept2": "33991",
"Propose1": "197148"
"Accept1": "312115",
"Accept2": "34013",
"Propose1": "197170"
}
4 changes: 4 additions & 0 deletions src/Engine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1743,6 +1743,10 @@ contract Engine is IEngine, MappingAllocator {
return battleConfig[_getStorageKey(battleKey)].startTimestamp;
}

function getKOBitmap(bytes32 battleKey, uint256 playerIndex) external view returns (uint256) {
return _getKOBitmap(battleConfig[_getStorageKey(battleKey)], playerIndex);
}

function getPrevPlayerSwitchForTurnFlagForBattleState(bytes32 battleKey) external view returns (uint256) {
return battleData[battleKey].prevPlayerSwitchForTurnFlag;
}
Expand Down
3 changes: 2 additions & 1 deletion src/Enums.sol
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,6 @@ enum StatBoostFlag {

enum ExtraDataType {
None,
SelfTeamIndex
SelfTeamIndex,
OpponentNonKOTeamIndex
}
1 change: 1 addition & 0 deletions src/IEngine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ interface IEngine {
returns (EffectInstance[] memory, uint256[] memory);
function getWinner(bytes32 battleKey) external view returns (address);
function getStartTimestamp(bytes32 battleKey) external view returns (uint256);
function getKOBitmap(bytes32 battleKey, uint256 playerIndex) external view returns (uint256);
function getPrevPlayerSwitchForTurnFlagForBattleState(bytes32 battleKey) external view returns (uint256);
function getBattleContext(bytes32 battleKey) external view returns (BattleContext memory);
function getCommitContext(bytes32 battleKey) external view returns (CommitContext memory);
Expand Down
18 changes: 18 additions & 0 deletions src/cpu/CPU.sol
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,24 @@ abstract contract CPU is CPUMoveManager, ICPU, ICPURNG, IMatchmaker {
RNG.getRNG(keccak256(abi.encode(nonce++, battleKey, block.timestamp))) % validSwitchCount;
extraDataToUse = uint240(validSwitchIndices[randomIndex]);
validMoveExtraData[validMoveCount] = extraDataToUse;
} else if (move.extraDataType() == ExtraDataType.OpponentNonKOTeamIndex) {
uint256 opponentIndex = (playerIndex + 1) % 2;
uint256 opponentTeamSize = ENGINE.getTeamSize(battleKey, opponentIndex);
uint256 koBitmap = ENGINE.getKOBitmap(battleKey, opponentIndex);
uint256[] memory validTargets = new uint256[](opponentTeamSize);
uint256 validTargetCount;
for (uint256 j = 0; j < opponentTeamSize; j++) {
if ((koBitmap & (1 << j)) == 0) {
validTargets[validTargetCount++] = j;
}
}
if (validTargetCount == 0) {
continue;
}
uint256 randomIndex =
RNG.getRNG(keccak256(abi.encode(nonce++, battleKey, block.timestamp))) % validTargetCount;
extraDataToUse = uint240(validTargets[randomIndex]);
validMoveExtraData[validMoveCount] = extraDataToUse;
}
if (validator.validatePlayerMove(battleKey, i, playerIndex, extraDataToUse)) {
validMoveIndices[validMoveCount++] = uint8(i);
Expand Down
53 changes: 53 additions & 0 deletions src/mons/ekineki/999.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: AGPL-3.0

pragma solidity ^0.8.0;

import "../../Constants.sol";
import "../../Enums.sol";

import {IEngine} from "../../IEngine.sol";
import {IMoveSet} from "../../moves/IMoveSet.sol";
import {NineNineNineLib} from "./999Lib.sol";

contract NineNineNine is IMoveSet {
IEngine immutable ENGINE;

constructor(IEngine _ENGINE) {
ENGINE = _ENGINE;
}

function name() external pure returns (string memory) {
return "999";
}

function move(bytes32 battleKey, uint256 attackerPlayerIndex, uint240, uint256) external {
// Set crit boost for the next turn
uint256 currentTurn = ENGINE.getTurnIdForBattleState(battleKey);
bytes32 key = NineNineNineLib._getKey(attackerPlayerIndex);
ENGINE.setGlobalKV(key, uint192(currentTurn + 1));
}

function stamina(bytes32, uint256, uint256) external pure returns (uint32) {
return 2;
}

function priority(bytes32, uint256) external pure returns (uint32) {
return DEFAULT_PRIORITY;
}

function moveType(bytes32) external pure returns (Type) {
return Type.Math;
}

function moveClass(bytes32) external pure returns (MoveClass) {
return MoveClass.Self;
}

function isValidTarget(bytes32, uint240) external pure returns (bool) {
return true;
}

function extraDataType() external pure returns (ExtraDataType) {
return ExtraDataType.None;
}
}
28 changes: 28 additions & 0 deletions src/mons/ekineki/999Lib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: AGPL-3.0

pragma solidity ^0.8.0;

import "../../Constants.sol";

import {IEngine} from "../../IEngine.sol";

library NineNineNineLib {
uint32 constant NINE_NINE_NINE_CRIT_RATE = 90;

function _getKey(uint256 playerIndex) internal pure returns (bytes32) {
return keccak256(abi.encode(playerIndex, "NINE_NINE_NINE"));
}

function _getEffectiveCritRate(IEngine engine, bytes32 battleKey, uint256 playerIndex)
internal
view
returns (uint32)
{
uint192 boostTurn = engine.getGlobalKV(battleKey, _getKey(playerIndex));
uint256 currentTurn = engine.getTurnIdForBattleState(battleKey);
if (boostTurn > 0 && uint256(boostTurn) == currentTurn) {
return NINE_NINE_NINE_CRIT_RATE;
}
return DEFAULT_CRIT_RATE;
}
}
72 changes: 72 additions & 0 deletions src/mons/ekineki/BubbleBop.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: AGPL-3.0

pragma solidity ^0.8.0;

import "../../Constants.sol";
import "../../Enums.sol";

import {IEngine} from "../../IEngine.sol";
import {IEffect} from "../../effects/IEffect.sol";
import {ITypeCalculator} from "../../types/ITypeCalculator.sol";
import {AttackCalculator} from "../../moves/AttackCalculator.sol";
import {StandardAttack} from "../../moves/StandardAttack.sol";
import {ATTACK_PARAMS} from "../../moves/StandardAttackStructs.sol";
import {NineNineNineLib} from "./999Lib.sol";

contract BubbleBop is StandardAttack {
constructor(IEngine _ENGINE, ITypeCalculator _TYPE_CALCULATOR)
StandardAttack(
address(msg.sender),
_ENGINE,
_TYPE_CALCULATOR,
ATTACK_PARAMS({
NAME: "Bubble Bop",
BASE_POWER: 50,
STAMINA_COST: 3,
ACCURACY: 100,
MOVE_TYPE: Type.Liquid,
MOVE_CLASS: MoveClass.Special,
PRIORITY: DEFAULT_PRIORITY,
CRIT_RATE: DEFAULT_CRIT_RATE,
VOLATILITY: DEFAULT_VOL,
EFFECT_ACCURACY: 0,
EFFECT: IEffect(address(0))
})
)
{}

function move(bytes32 battleKey, uint256 attackerPlayerIndex, uint240, uint256 rng) public override {
uint32 effectiveCritRate = NineNineNineLib._getEffectiveCritRate(ENGINE, battleKey, attackerPlayerIndex);

// First hit
AttackCalculator._calculateDamage(
ENGINE,
TYPE_CALCULATOR,
battleKey,
attackerPlayerIndex,
basePower(battleKey),
accuracy(battleKey),
volatility(battleKey),
moveType(battleKey),
moveClass(battleKey),
rng,
effectiveCritRate
);

// Second hit with different RNG
uint256 rng2 = uint256(keccak256(abi.encode(rng, "SECOND_HIT")));
AttackCalculator._calculateDamage(
ENGINE,
TYPE_CALCULATOR,
battleKey,
attackerPlayerIndex,
basePower(battleKey),
accuracy(battleKey),
volatility(battleKey),
moveType(battleKey),
moveClass(battleKey),
rng2,
effectiveCritRate
);
}
}
54 changes: 54 additions & 0 deletions src/mons/ekineki/Overflow.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: AGPL-3.0

pragma solidity ^0.8.0;

import "../../Constants.sol";
import "../../Enums.sol";

import {IEngine} from "../../IEngine.sol";
import {IEffect} from "../../effects/IEffect.sol";
import {ITypeCalculator} from "../../types/ITypeCalculator.sol";
import {AttackCalculator} from "../../moves/AttackCalculator.sol";
import {StandardAttack} from "../../moves/StandardAttack.sol";
import {ATTACK_PARAMS} from "../../moves/StandardAttackStructs.sol";
import {NineNineNineLib} from "./999Lib.sol";

contract Overflow is StandardAttack {
constructor(IEngine _ENGINE, ITypeCalculator _TYPE_CALCULATOR)
StandardAttack(
address(msg.sender),
_ENGINE,
_TYPE_CALCULATOR,
ATTACK_PARAMS({
NAME: "Overflow",
BASE_POWER: 90,
STAMINA_COST: 3,
ACCURACY: 100,
MOVE_TYPE: Type.Math,
MOVE_CLASS: MoveClass.Special,
PRIORITY: DEFAULT_PRIORITY,
CRIT_RATE: DEFAULT_CRIT_RATE,
VOLATILITY: DEFAULT_VOL,
EFFECT_ACCURACY: 0,
EFFECT: IEffect(address(0))
})
)
{}

function move(bytes32 battleKey, uint256 attackerPlayerIndex, uint240, uint256 rng) public override {
uint32 effectiveCritRate = NineNineNineLib._getEffectiveCritRate(ENGINE, battleKey, attackerPlayerIndex);
AttackCalculator._calculateDamage(
ENGINE,
TYPE_CALCULATOR,
battleKey,
attackerPlayerIndex,
basePower(battleKey),
accuracy(battleKey),
volatility(battleKey),
moveType(battleKey),
moveClass(battleKey),
rng,
effectiveCritRate
);
}
}
68 changes: 68 additions & 0 deletions src/mons/ekineki/SaviorComplex.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: AGPL-3.0

pragma solidity ^0.8.0;

import {MonStateIndexName, StatBoostFlag, StatBoostType} from "../../Enums.sol";
import {StatBoostToApply} from "../../Structs.sol";
import {IEngine} from "../../IEngine.sol";
import {IAbility} from "../../abilities/IAbility.sol";
import {StatBoosts} from "../../effects/StatBoosts.sol";

contract SaviorComplex is IAbility {
uint8 public constant STAGE_1_BOOST = 15; // 1 KO'd
uint8 public constant STAGE_2_BOOST = 25; // 2 KO'd
uint8 public constant STAGE_3_BOOST = 30; // 3+ KO'd

IEngine immutable ENGINE;
StatBoosts immutable STAT_BOOSTS;

constructor(IEngine _ENGINE, StatBoosts _STAT_BOOSTS) {
ENGINE = _ENGINE;
STAT_BOOSTS = _STAT_BOOSTS;
}

function name() external pure returns (string memory) {
return "Savior Complex";
}

function _getSaviorComplexKey(uint256 playerIndex) internal pure returns (bytes32) {
return keccak256(abi.encode(playerIndex, "SAVIOR_COMPLEX"));
}

function activateOnSwitch(bytes32 battleKey, uint256 playerIndex, uint256 monIndex) external {
// Check if already triggered this game
if (ENGINE.getGlobalKV(battleKey, _getSaviorComplexKey(playerIndex)) == 1) {
return;
}

// Count KO'd mons via bitmap popcount
uint256 koBitmap = ENGINE.getKOBitmap(battleKey, playerIndex);
if (koBitmap == 0) return;
uint256 koCount = 0;
for (uint256 bits = koBitmap; bits != 0; bits >>= 1) {
koCount += bits & 1;
}

// Determine boost based on stage
uint8 boostPercent;
if (koCount >= 3) {
boostPercent = STAGE_3_BOOST;
} else if (koCount >= 2) {
boostPercent = STAGE_2_BOOST;
} else {
boostPercent = STAGE_1_BOOST;
}

// Apply temporary sp atk boost (cleared on switch out)
StatBoostToApply[] memory statBoosts = new StatBoostToApply[](1);
statBoosts[0] = StatBoostToApply({
stat: MonStateIndexName.SpecialAttack,
boostPercent: boostPercent,
boostType: StatBoostType.Multiply
});
STAT_BOOSTS.addStatBoosts(playerIndex, monIndex, statBoosts, StatBoostFlag.Temp);

// Mark as triggered (once per game)
ENGINE.setGlobalKV(_getSaviorComplexKey(playerIndex), 1);
}
}
Loading