135 lines
4.2 KiB
Solidity
135 lines
4.2 KiB
Solidity
|
// SPDX-License-Identifier: CC0-1.0
|
||
|
pragma solidity ^0.8.0;
|
||
|
|
||
|
import {ERC5050State, Action} from "./ERC5050State.sol";
|
||
|
import {ERC5050, Action} from "./ERC5050.sol";
|
||
|
|
||
|
struct TokenInfo {
|
||
|
uint256 health;
|
||
|
uint256 healthRemaining;
|
||
|
uint256 power;
|
||
|
uint256 blockedAt;
|
||
|
uint256 blockPower;
|
||
|
uint256 lockedUntilBlock;
|
||
|
uint256 wins;
|
||
|
bool hasRegistered;
|
||
|
}
|
||
|
|
||
|
interface IFightGame {
|
||
|
function getStats(address _contract, uint256 _tokenId) external view returns (TokenInfo);
|
||
|
}
|
||
|
|
||
|
contract FightGame is IFightGame, ERC5050State {
|
||
|
|
||
|
bytes4 constant LIGHT_ATTACK_SELECTOR = bytes4(keccak256("fg.light-attack"));
|
||
|
bytes4 constant HEAVY_ATTACK_SELECTOR = bytes4(keccak256("fg.heavy-attack"));
|
||
|
bytes4 constant BLOCK_SELECTOR = bytes4(keccak256("fg.block"));
|
||
|
|
||
|
uint256 constant BLOCK_DECAY = 100;
|
||
|
uint256 constant LIGHT_ATTACK_DECAY = 200;
|
||
|
uint256 constant HEAVY_ATTACK_DECAY = 500;
|
||
|
|
||
|
mapping(address => mapping(uint256 => TokenInfo)) state;
|
||
|
|
||
|
constructor() {
|
||
|
_registerReceivable("fg.light-attack");
|
||
|
_registerReceivable("fg.heavy-attack");
|
||
|
_registerReceivable("fg.block");
|
||
|
}
|
||
|
|
||
|
function register(address _contract, uint256 _tokenId) external {
|
||
|
require(msg.sender == ownerOf(_contract, _tokenId), "sender not token owner");
|
||
|
require(!state[_contract][_tokenId].hasRegistered, "token already registered");
|
||
|
state[_contract][_tokenId] = TokenInfo(100, 100, 5, 0, 0, 0, true);
|
||
|
}
|
||
|
|
||
|
function getStats(address _contract, uint256 _tokenId) external view returns (TokenInfo){
|
||
|
return state[_contract][_tokenId];
|
||
|
}
|
||
|
|
||
|
function onActionReceived(Action calldata action, uint256 _nonce)
|
||
|
external
|
||
|
payable
|
||
|
override
|
||
|
onlyReceivableAction(action, _nonce)
|
||
|
{
|
||
|
TokenInfo storage from = state[action.from._address][action.from._tokenId];
|
||
|
require(from.healthRemaining > 0, "health 0");
|
||
|
require(block.number > from.lockedUntilBlock, "token locked");
|
||
|
if (action.selector == BLOCK_SELECTOR) {
|
||
|
from.blockPower = from.power * 3;
|
||
|
from.blockedAt = block.number;
|
||
|
from.lockedUntilBlock = block.number + BLOCK_DECAY;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
TokenInfo storage to = state[action.to._address][action.to._tokenId];
|
||
|
require(to.healthRemaining > 0, "target health 0");
|
||
|
|
||
|
uint256 damage;
|
||
|
if (action.selector == LIGHT_ATTACK_SELECTOR ) {
|
||
|
damage = from.power;
|
||
|
from.lockedUntilBlock = block.number + LIGHT_ATTACK_DECAY;
|
||
|
}
|
||
|
|
||
|
if (action.selector == HEAVY_ATTACK_SELECTOR) {
|
||
|
damage = from.power * 3;
|
||
|
from.lockedUntilBlock = block.number + HEAVY_ATTACK_DECAY;
|
||
|
}
|
||
|
if(to.blockedAt + BLOCK_DECAY > block.number) {
|
||
|
if(to.blockPower >= damage){
|
||
|
to.blockPower -= damage;
|
||
|
return;
|
||
|
}
|
||
|
damage -= to.blockPower;
|
||
|
}
|
||
|
if(to.healthRemaining > damage){
|
||
|
to.healthRemaining -= damage;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Winner gains loser's power and some health
|
||
|
from.power += to.power;
|
||
|
from.healthRemaining += to.power;
|
||
|
from.wins++;
|
||
|
to.healthRemaining = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
contract Fighter is ERC5050, ERC721 {
|
||
|
|
||
|
IFightGame stateContract;
|
||
|
|
||
|
constructor(address _stateContract) {
|
||
|
_registerAction("fg.light-attack");
|
||
|
_registerAction("fg.heavy-attack");
|
||
|
_registerSendable("fg.block");
|
||
|
stateContract = IFightGame(_stateContract);
|
||
|
}
|
||
|
|
||
|
// Update NFT render / metadata based on game stats
|
||
|
function tokenURICharacterEmoji(uint256 tokenId)
|
||
|
public
|
||
|
view
|
||
|
override
|
||
|
returns (string memory)
|
||
|
{
|
||
|
TokenInfo memory stats = stateContract.getStats(address(this), tokenId);
|
||
|
if(stats.healthRemaining == 0){
|
||
|
return unicode"😵";
|
||
|
}
|
||
|
if(stats.power > 100){
|
||
|
return unicode"🦾";
|
||
|
}
|
||
|
if(stats.power > 50){
|
||
|
return unicode"💪";
|
||
|
}
|
||
|
if(stats.power > 20){
|
||
|
return unicode"🤩";
|
||
|
}
|
||
|
if(stats.power > 5){
|
||
|
return unicode"😃";
|
||
|
}
|
||
|
return unicode"😀";
|
||
|
}
|
||
|
}
|