From 39df4434c03d8d6636607091313e648adc7fa00c Mon Sep 17 00:00:00 2001 From: lewis <> Date: Sun, 8 Oct 2023 21:43:49 +0800 Subject: [PATCH] feat: GameSystem.sol spilt three system --- .gitignore | 3 +- packages/contracts/foundry.toml | 17 + packages/contracts/mud.config.ts | 22 +- .../contracts/script/BattleConfigInit.sol | 23 + packages/contracts/script/GameConfigInit.sol | 24 + packages/contracts/script/PostDeploy.s.sol | 10 +- packages/contracts/script/mapParser.ts | 67 -- packages/contracts/src/Constants.sol | 5 + packages/contracts/src/codegen/Tables.sol | 15 - .../contracts/src/codegen/world/IWorld.sol | 19 - .../contracts/src/systems/BattleSystem.sol | 352 ++++++++ .../{SeasonSystem.sol => GMSystem.sol} | 16 +- packages/contracts/src/systems/GameSystem.sol | 799 +----------------- .../contracts/src/systems/OwnableSystem.sol | 49 -- .../contracts/src/systems/PlayerSystem.sol | 7 +- .../contracts/src/systems/RandomSystem.sol | 145 ++++ .../src/systems/library/BattleUtils.sol | 55 ++ .../src/systems/library/CommonUtils.sol | 52 ++ packages/contracts/worlds.json | 6 +- 19 files changed, 743 insertions(+), 943 deletions(-) create mode 100644 packages/contracts/script/BattleConfigInit.sol create mode 100644 packages/contracts/script/GameConfigInit.sol delete mode 100644 packages/contracts/script/mapParser.ts create mode 100644 packages/contracts/src/Constants.sol delete mode 100644 packages/contracts/src/codegen/Tables.sol delete mode 100644 packages/contracts/src/codegen/world/IWorld.sol create mode 100644 packages/contracts/src/systems/BattleSystem.sol rename packages/contracts/src/systems/{SeasonSystem.sol => GMSystem.sol} (64%) delete mode 100644 packages/contracts/src/systems/OwnableSystem.sol create mode 100644 packages/contracts/src/systems/RandomSystem.sol create mode 100644 packages/contracts/src/systems/library/BattleUtils.sol create mode 100644 packages/contracts/src/systems/library/CommonUtils.sol diff --git a/.gitignore b/.gitignore index b512c09d..398a7d55 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -node_modules \ No newline at end of file +node_modules +packages/contracts/src/codegen/* \ No newline at end of file diff --git a/packages/contracts/foundry.toml b/packages/contracts/foundry.toml index 6b9c911b..cc0afcd6 100644 --- a/packages/contracts/foundry.toml +++ b/packages/contracts/foundry.toml @@ -21,6 +21,23 @@ extra_output_files = [ "evm.bytecode" ] fs_permissions = [{ access = "read", path = "./"}] +gas_reports = ["*"] [profile.lattice-testnet] eth_rpc_url = "https://follower.testnet-chain.linfra.xyz" + + +[profile.arb-testnet] +eth_rpc_url = "https://goerli-rollup.arbitrum.io/rpc" +optimizer = true +optimizer_runs = 3000 + +[profile.linea-testnet] +eth_rpc_url = "https://linea-goerli.infura.io/v3/5ca372516740427e97512d4dfefd9c47" +optimizer = true +optimizer_runs = 3000 + +[profile.sepolia-testnet] +eth_rpc_url = "https://sepolia.infura.io/v3/5ca372516740427e97512d4dfefd9c47" +optimizer = true +optimizer_runs = 3000 diff --git a/packages/contracts/mud.config.ts b/packages/contracts/mud.config.ts index 285991fd..f22be793 100644 --- a/packages/contracts/mud.config.ts +++ b/packages/contracts/mud.config.ts @@ -20,16 +20,16 @@ export default mudConfig({ addr: "address", }, schema: { - name: "string", - url: "string", suitId : "uint256", equipmentId : "uint256", x : "uint16", y : "uint16", - state : "PlayerState", hP : "uint256", oreBalance: "uint16", treasureBalance: "uint16", + state : "PlayerState", + name: "string", + url: "string", } }, Ownable: { @@ -37,19 +37,25 @@ export default mudConfig({ owner: "address", } }, - Game: { + GameConfig: { + dataStruct: false, schema: { merkleRoot: "bytes32", - maxAttackzDistance: "uint256", - maxMoveDistance: "uint256", battleId: "uint256", randomId: "uint256", - maxTimeLimit: "uint256", - maxUserLocationLockTime: "uint256", originX: "uint16", originY: "uint16", roomId: "uint256", boxId : "uint256", + } + }, + BattleConfig: { + dataStruct: false, + schema: { + maxAttackzDistance: "uint256", + maxMoveDistance: "uint256", + maxTimeLimit: "uint256", + maxUserLocationLockTime: "uint256", maxBoxBindTime : "uint256", battlefieldPlayers: "address[]", } diff --git a/packages/contracts/script/BattleConfigInit.sol b/packages/contracts/script/BattleConfigInit.sol new file mode 100644 index 00000000..ba4803a2 --- /dev/null +++ b/packages/contracts/script/BattleConfigInit.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import {console} from "forge-std/console.sol"; +import {IWorld} from "../src/codegen/world/IWorld.sol"; +import {BattleConfig} from "../src/codegen/Tables.sol"; +import { BATTLE_CONFIG_KEY } from "../src/Constants.sol"; + +library BattleConfigInit { + function initGameConfig(IWorld _world) internal { + address[] memory players; + BattleConfig.set( + _world, + BATTLE_CONFIG_KEY, //key + 10, //maxAttackzDistance + 15, //maxMoveDistance, + 120, //maxTimeLimit, + 120, //maxUserLocationLockTime, + 120, //maxBoxBindTime, + players + ); + } +} \ No newline at end of file diff --git a/packages/contracts/script/GameConfigInit.sol b/packages/contracts/script/GameConfigInit.sol new file mode 100644 index 00000000..d98e0485 --- /dev/null +++ b/packages/contracts/script/GameConfigInit.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import {console} from "forge-std/console.sol"; +import {IWorld} from "../src/codegen/world/IWorld.sol"; +import {GameConfig} from "../src/codegen/Tables.sol"; +import { GAME_CONFIG_KEY } from "../src/Constants.sol"; + +library GameConfigInit { + function initGameConfig(IWorld _world) internal { + bytes32 merkleRoot = ""; + GameConfig.set( + _world, + GAME_CONFIG_KEY, //key + merkleRoot, //merkleRoot + 0, //battleId, + 0, //randomId, + 100, //originX, + 100, //originY, + 0, //roomId, + 0 //boxId, + ); + } +} \ No newline at end of file diff --git a/packages/contracts/script/PostDeploy.s.sol b/packages/contracts/script/PostDeploy.s.sol index 66a678cd..7466b0d9 100644 --- a/packages/contracts/script/PostDeploy.s.sol +++ b/packages/contracts/script/PostDeploy.s.sol @@ -4,6 +4,8 @@ pragma solidity >=0.8.0; import { Script } from "forge-std/Script.sol"; import { console } from "forge-std/console.sol"; import { IWorld } from "../src/codegen/world/IWorld.sol"; +import { GameConfigInit } from "./GameConfigInit.sol"; +import { BattleConfigInit } from "./BattleConfigInit.sol"; contract PostDeploy is Script { function run(address worldAddress) external { @@ -13,11 +15,11 @@ contract PostDeploy is Script { // Start broadcasting transactions from the deployer account vm.startBroadcast(deployerPrivateKey); - // ------------------ EXAMPLES ------------------ - - // uint32 newValue = IWorld(worldAddress).getPos(10,10); - // console.log("Increment via IWorld:", newValue); + // ------------------ INIT ------------------ + GameConfigInit.initGameConfig(IWorld(worldAddress)); + BattleConfigInit.initGameConfig(IWorld(worldAddress)); vm.stopBroadcast(); + } } diff --git a/packages/contracts/script/mapParser.ts b/packages/contracts/script/mapParser.ts deleted file mode 100644 index 50da235c..00000000 --- a/packages/contracts/script/mapParser.ts +++ /dev/null @@ -1,67 +0,0 @@ -import fs from 'fs'; -import { ethers } from "ethers"; - -const formatCsvToArray = (csv: string) => { - const lines = csv.split('\n'); - const result = []; - lines.forEach((str) => { - const line = str.trim(); - if (line.match(/^\d/)) { - const tempLine = line.split(','); - tempLine.pop(); - tempLine.shift(); - const numbers = tempLine.map(str => Number(str)) - result.push(numbers); - } - }); - return result; -} - -const loadMapData = async () => { - const content = fs.readFileSync('./../../client/src/assets/map.csv', 'utf-8'); - return formatCsvToArray(content); -} - -async function main() { - let result = await loadMapData(); - const array = []; - let index = 0; - let num = ethers.BigNumber.from(0); - - for(let i = 0; i < result.length; i++) { - let row = result[i]; - for(let j = 0; j < row.length; j++) { - let val = row[j]%100; - row[j] = val; - - if (index >= 256) { - array.push(ethers.BigNumber.from(num)); - index = 0; - num = ethers.BigNumber.from(0); - } - - - if (val == 1) { - const mask = ethers.BigNumber.from(1).shl(index); - num = num.or(mask); - } - - index++; - } - } - - if (index == 256) { - // console.log(" num: ", num.toHexString()); - array.push(num); - } - - for(let i = 0; i < array.length; i++) { - console.log("array: ", array[i].toHexString()); - } -} - -main().catch((error) => { - console.error(error); - process.exitCode = 1; - }); - diff --git a/packages/contracts/src/Constants.sol b/packages/contracts/src/Constants.sol new file mode 100644 index 00000000..379d0290 --- /dev/null +++ b/packages/contracts/src/Constants.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +bytes32 constant GAME_CONFIG_KEY = keccak256("Game-Key"); +bytes32 constant BATTLE_CONFIG_KEY = keccak256("Battle-Key"); \ No newline at end of file diff --git a/packages/contracts/src/codegen/Tables.sol b/packages/contracts/src/codegen/Tables.sol deleted file mode 100644 index 18b17fab..00000000 --- a/packages/contracts/src/codegen/Tables.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0; - -/* Autogenerated file. Do not edit manually. */ - -import { Season, SeasonData, SeasonTableId } from "./tables/Season.sol"; -import { Player, PlayerData, PlayerTableId } from "./tables/Player.sol"; -import { Ownable, OwnableTableId } from "./tables/Ownable.sol"; -import { Game, GameData, GameTableId } from "./tables/Game.sol"; -import { Board, BoardData, BoardTableId } from "./tables/Board.sol"; -import { MapBoard, MapBoardTableId } from "./tables/MapBoard.sol"; -import { RandomList, RandomListData, RandomListTableId } from "./tables/RandomList.sol"; -import { BattleList, BattleListData, BattleListTableId } from "./tables/BattleList.sol"; -import { PlayerLocationLock, PlayerLocationLockTableId } from "./tables/PlayerLocationLock.sol"; -import { BoxList, BoxListData, BoxListTableId } from "./tables/BoxList.sol"; diff --git a/packages/contracts/src/codegen/world/IWorld.sol b/packages/contracts/src/codegen/world/IWorld.sol deleted file mode 100644 index cf62eda8..00000000 --- a/packages/contracts/src/codegen/world/IWorld.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0; - -/* Autogenerated file. Do not edit manually. */ - -import { IBaseWorld } from "@latticexyz/world/src/interfaces/IBaseWorld.sol"; - -import { IGameSystem } from "./IGameSystem.sol"; -import { IOwnableSystem } from "./IOwnableSystem.sol"; -import { IPlayerSystem } from "./IPlayerSystem.sol"; -import { ISeasonSystem } from "./ISeasonSystem.sol"; - -/** - * The IWorld interface includes all systems dynamically added to the World - * during the deploy process. - */ -interface IWorld is IBaseWorld, IGameSystem, IOwnableSystem, IPlayerSystem, ISeasonSystem { - -} diff --git a/packages/contracts/src/systems/BattleSystem.sol b/packages/contracts/src/systems/BattleSystem.sol new file mode 100644 index 00000000..711f7e45 --- /dev/null +++ b/packages/contracts/src/systems/BattleSystem.sol @@ -0,0 +1,352 @@ + +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import { System } from "@latticexyz/world/src/System.sol"; +import { BattleState, Buff, PlayerState } from "../codegen/Types.sol"; +import { GameConfig, BattleConfig, BoxListData, BattleList, BattleListData, Player, PlayerData, PlayerLocationLock, BoxList} from "../codegen/Tables.sol"; +import { BattleUtils } from "./library/BattleUtils.sol"; +import { GAME_CONFIG_KEY, BATTLE_CONFIG_KEY } from "../Constants.sol"; + +contract BattleSystem is System { + // modifier onlyBattlePlayer(uint256 _battleId, BattleState _battleState) { + // BattleListData memory battle = BattleList.get(_battleId); + + // BattleState battleState = battle.attacker == _msgSender() + // ? battle.attackerState + // : battle.defenderState; + + // require( + // battle.attacker == _msgSender() || battle.defender == _msgSender(), + // "You are not in this battle" + // ); + // require(battleState == _battleState, "You are in the wrong state"); + + // require(!battle.isEnd, "Battle is end"); + + // _; + // } + + function checkBattlePlayer(BattleListData memory battle, BattleState _battleState) internal view { + // BattleListData memory battle = BattleList.get(_battleId); + + BattleState battleState = battle.attacker == _msgSender() + ? battle.attackerState + : battle.defenderState; + + require( + battle.attacker == _msgSender() || battle.defender == _msgSender(), + "You are not in this battle" + ); + require(battleState == _battleState, "You are in the wrong state"); + + require(!battle.isEnd, "Battle is end"); + } + + function confirmBattle( + bytes32 _buffHash, + uint256 _battleId + ) external{ + // 战斗是否有用户 + //战斗是否结束 + //是否已超时 + + BattleListData memory battle = BattleList.get(_battleId); + checkBattlePlayer(battle, BattleState.Inited); + + require( + block.timestamp - battle.timestamp < BattleConfig.getMaxTimeLimit(BATTLE_CONFIG_KEY), + "Battle is timeout" + ); + // 战斗是否已经选择buff + BattleState _battleState = battle.attacker == _msgSender() + ? battle.attackerState + : battle.defenderState; + + require( + _battleState == BattleState.Inited, + "You have already selected buff" + ); + // 当前实现方法非常不优雅,使用两个额外存储槽来存储用户的选择 + if (battle.attacker == _msgSender()) { + BattleList.setAttackerBuffHash(_battleId, _buffHash); + BattleList.setAttackerState(_battleId, BattleState.Confirmed); + } else { + BattleList.setDefenderBuffHash(_battleId, _buffHash); + BattleList.setDefenderState(_battleId, BattleState.Confirmed); + } + + // TODO需要一个event通知前端验证buff + } + + function revealBattle( + uint256 _battleId, + bytes32 _action, + uint256 _arg, + bytes32 _nonce + ) external { + // check battle + BattleListData memory battle = BattleList.get(_battleId); + checkBattlePlayer(battle, BattleState.Confirmed); + + + // TODO揭示阶段也应该添加时间限制 + // address attacker = BattleList.getAttacker(_battleId); + + bytes32 moveHash = battle.attacker == _msgSender() + ? BattleList.getAttackerBuffHash(_battleId) + : BattleList.getDefenderBuffHash(_battleId); + + bytes32 proofHash = keccak256(abi.encodePacked(_action, _arg, _nonce)); + require(moveHash == proofHash, "Invalid move hash proof"); + if (battle.attacker == _msgSender()) { + BattleList.setAttackerAction(_battleId, _action); + BattleList.setAttackerArg(_battleId, _arg); + BattleList.setAttackerState(_battleId, BattleState.Revealed); + } else { + BattleList.setDefenderAction(_battleId, _action); + BattleList.setDefenderArg(_battleId, _arg); + BattleList.setDefenderState(_battleId, BattleState.Revealed); + } + if ( + battle.attackerState == BattleState.Revealed && + battle.defenderState == BattleState.Revealed + ) { + // 结算战斗 + revealWinner(_battleId); + } + } + + function revealWinner( + uint256 _battleId + ) public { + + // 结算战斗 + BattleListData memory battle = BattleList.get(_battleId); + checkBattlePlayer(battle, BattleState.Revealed); + + uint256 attackerFirepower = 100; + uint256 defenderFirepower = 100; + + // address attacker = BattleList.getAttacker(_battleId); + // address defender = BattleList.getDefender(_battleId); + //用户的指令为attack,esacpe,useProps几种方案,attackArg和defenderArg分别是伴随指令传递的参数,对应执行attack,esacpe,useProps几个函数并传入参数 + // 默认用户的攻击力都为100,防御力都为100,攻击力和防御力在未来都是根据装备来判断 + //如果双方都执行attack,则对比buff,buff相同攻击力没有增益,否则按照water>fire>wind>water的顺序给优胜者增加50%攻击力 + //如果对方执行escape,则判断buff是否大于对方的buff,如果大于则对方逃跑成功 + if ( + battle.attackerAction == bytes32("attack") && + battle.defenderAction == bytes32("attack") + ) { + // Buff attackerBuff = Buff(battle.attackerArg); + // Buff defenderBuff = Buff(battle.defenderArg); + Buff attackerBuff = Buff(battle.attackerArg); + Buff defenderBuff = Buff(battle.defenderArg); + // 任意攻击buff都强于None + uint256 attackerAttackPower = BattleUtils.getAttackPower( + attackerBuff, + defenderBuff, + attackerFirepower + ); + uint256 defenderAttackPower = BattleUtils.getAttackPower( + defenderBuff, + attackerBuff, + defenderFirepower + ); + + BattleList.setAttackerHP(_battleId, BattleUtils.getAttackResult( + battle.attackerHP, + defenderAttackPower + )); + BattleList.setDefenderHP(_battleId, BattleUtils.getAttackResult( + battle.defenderHP, + attackerAttackPower + )); + + if (battle.attackerHP == 0 || battle.defenderHP == 0) { + address winner = battle.attackerHP == 0? battle.defender : battle.attacker; + address looser = battle.attackerHP == 0 + ? battle.attacker + : battle.defender; + // BattleList.setWinner(_battleId, winner); // cause stack too deep compile error + // BattleList.setIsEnd(_battleId, true); + battle.winner = winner; //Todo: temmorary solution + battle.isEnd = true; + loseGame(looser, winner); + Player.setHP(winner, initUserHP(winner)); + + // TODO这里应该跟一个清算函数 + // 胜利者解除战斗形态,血量恢复20% + // 失败者传送到非战区,血量回满 + } + } + + if ( + battle.attackerAction == bytes32("escape") && + battle.defenderAction == bytes32("escape") + ) { + // 双方都逃走,则战斗结束(这里应该都传送到更远地方) + // battle.isEnd = true; + // battle.winer = address(0); + + BattleList.setIsEnd(_battleId, true); + BattleList.setWinner(_battleId, address(0)); + + return; + } + if ( + battle.attackerAction == bytes32("escape") && + battle.defenderAction == bytes32("attack") + ) { + Buff attackerBuff = Buff(battle.defenderArg); + Buff defenderBuff = Buff(battle.defenderArg); + // 任意攻击buff都强于None + if ( + attackerBuff == defenderBuff || + BattleUtils.compareBuff(attackerBuff, defenderBuff) == 2 + ) { + // 逃跑成功 + Player.setState(battle.attacker, PlayerState.Exploring); + Player.setState(battle.defender, PlayerState.Exploring); + // PlayerLocationLock[battle.defender] = block.timestamp; //将被逃跑方禁锢一段时间 + PlayerLocationLock.set(battle.defender, block.timestamp); + } else { + // 逃跑失败,被动挨打 + uint256 defenderAttackPower = BattleUtils.getAttackPower( + defenderBuff, + attackerBuff, + defenderFirepower + ); + // battle.attackerHP = BattleUtils.getAttackResult( + // battle.attackerHP, + // defenderAttackPower + // ); + // if (battle.attackerHP == 0) { + // battle.winer = battle.defender; + // battle.isEnd = true; + // } + BattleList.setAttackerHP(_battleId, BattleUtils.getAttackResult( + battle.attackerHP, + defenderAttackPower + )); + if (BattleList.getAttackerHP(_battleId) == 0) { + + // battle.winer = battle.defender; + // battle.isEnd = true; + BattleList.setWinner(_battleId, battle.defender); + BattleList.setIsEnd(_battleId, true); + } + } + } + if ( + battle.attackerAction == bytes32("attack") && + battle.defenderAction == bytes32("escape") + ) { + Buff attackerBuff = Buff(battle.attackerArg); + Buff defenderBuff = Buff(battle.defenderArg); + // 任意攻击buff都强于None + if ( + attackerBuff == defenderBuff || + BattleUtils.compareBuff(defenderBuff, attackerBuff) == 2 + ) { + // 逃跑成功 + // Player[battle.defender].state = PlayerState.Exploring; + // Player[battle.attacker].state = PlayerState.Exploring; + // PlayerLocationLock[battle.attacker] = block.timestamp; //将被逃跑方禁锢一段时间 + Player.setState(battle.defender, PlayerState.Exploring); + Player.setState(battle.attacker, PlayerState.Exploring); + PlayerLocationLock.set(battle.attacker, block.timestamp); + } else { + // 逃跑失败,被动挨打 + uint256 attackerAttackPower = BattleUtils.getAttackPower( + attackerBuff, + defenderBuff, + attackerFirepower + ); + + // BattleList.setDefenderHP(_battleId, BattleUtils.getAttackResult( + // battle.defenderHP, + // attackerAttackPower + // )); + battle.defenderHP = BattleUtils.getAttackResult( + battle.defenderHP, + attackerAttackPower + ); + + if (battle.defenderHP == 0) { + battle.winner = battle.attacker; + battle.isEnd = true; + + // BattleList.setWinner(_battleId, battle.attacker); + // BattleList.setIsEnd(_battleId, true); + + } + } + } + } + + function initUserHP(address _user) public pure returns (uint256) { + return 400; + } + + function loseGame(address _looser, address _winner) internal { + // 游戏失败,将用户脱离战区,血量回满 + // TODO 背包系统,宝物系统 + + outBattlefield(_looser); + PlayerData memory losser = Player.get(_looser); + uint256 boxId = GameConfig.getBoxId(GAME_CONFIG_KEY); + BoxListData memory box; + box.x = losser.x; + box.y = losser.y; + box.opened = true; + box.openTime = block.timestamp; + box.owner = _winner; + box.oreBalance = losser.oreBalance; + box.treasureBalance = losser.treasureBalance; + box.dropTime = block.timestamp; + BoxList.set(GameConfig.getRoomId(GAME_CONFIG_KEY), boxId, box); + + Player.setOreBalance(_looser, 0); + Player.setTreasureBalance(_looser, 0); + GameConfig.setRoomId(GAME_CONFIG_KEY, boxId+1); + } + + + function outBattlefield(address _user) internal { + // 脱离战区,则将用户血量回满,坐标不变,状态改为准备中 + require( + Player.getState(_user) == PlayerState.Exploring, + "You should in exploring state" + ); + + Player.setHP(_user, initUserHP(_user)); + + for (uint256 i; i < BattleConfig.lengthBattlefieldPlayers(BATTLE_CONFIG_KEY); i++) { + if (BattleConfig.getItemBattlefieldPlayers(BATTLE_CONFIG_KEY, i) == _user) { + BattleConfig.updateBattlefieldPlayers(BATTLE_CONFIG_KEY, i, BattleConfig.getItemBattlefieldPlayers(BATTLE_CONFIG_KEY, BattleConfig.lengthBattlefieldPlayers(BATTLE_CONFIG_KEY) - 1)); + BattleConfig.popBattlefieldPlayers(BATTLE_CONFIG_KEY); + break; + } + } + Player.setState(_user, PlayerState.Preparing); + } + + + function goHome() external { + // 回家,将用户脱离战区,血量回满 + + PlayerData memory player = Player.get(_msgSender()); + require( + player.state == PlayerState.Exploring, + "You should in exploring state" + ); + require( + player.x == GameConfig.getOriginX(GAME_CONFIG_KEY) && player.y == GameConfig.getOriginY(GAME_CONFIG_KEY), + "You are not in the origin point" + ); + outBattlefield(_msgSender()); + + } + +} \ No newline at end of file diff --git a/packages/contracts/src/systems/SeasonSystem.sol b/packages/contracts/src/systems/GMSystem.sol similarity index 64% rename from packages/contracts/src/systems/SeasonSystem.sol rename to packages/contracts/src/systems/GMSystem.sol index 0f99dc08..97a2476c 100644 --- a/packages/contracts/src/systems/SeasonSystem.sol +++ b/packages/contracts/src/systems/GMSystem.sol @@ -2,13 +2,13 @@ pragma solidity >=0.8.0; import { System } from "@latticexyz/world/src/System.sol"; -import { Season } from "../codegen/Tables.sol"; -import { OwnableSystem } from "./OwnableSystem.sol"; +import { Season, GameConfig } from "../codegen/Tables.sol"; +import { GAME_CONFIG_KEY } from "../Constants.sol"; -contract SeasonSystem is OwnableSystem { +contract GMSystem { bytes32 constant MAP_KEY = keccak256("Season-Key"); - function getInfo() public view returns (uint256, uint256, uint256) { + function GetSeasonInfo() public view returns (uint256, uint256, uint256) { uint256 start = Season.getStart(MAP_KEY); uint256 end = Season.getEnd(MAP_KEY); uint256 no = Season.getNo(MAP_KEY); @@ -16,7 +16,7 @@ contract SeasonSystem is OwnableSystem { return (start, end, no); } - function setInfo(uint256 start, uint256 end) public onlyOwner{ + function SetSeasonInfo(uint256 start, uint256 end) public { require (start < end, "start must be less than end"); uint256 now_end = Season.getEnd(MAP_KEY); @@ -27,4 +27,10 @@ contract SeasonSystem is OwnableSystem { uint256 no = Season.getNo(MAP_KEY); Season.setNo(MAP_KEY, no+1); } + + function SetMapMerkleRoot(bytes32 root) public { + GameConfig.setMerkleRoot(GAME_CONFIG_KEY, root); + } + + } \ No newline at end of file diff --git a/packages/contracts/src/systems/GameSystem.sol b/packages/contracts/src/systems/GameSystem.sol index 90c99ffd..4eb8cdcb 100644 --- a/packages/contracts/src/systems/GameSystem.sol +++ b/packages/contracts/src/systems/GameSystem.sol @@ -3,80 +3,28 @@ pragma solidity >=0.8.0; import { System } from "@latticexyz/world/src/System.sol"; import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; -import { Game } from "../codegen/Tables.sol"; import { BattleState, Buff, PlayerState } from "../codegen/Types.sol"; -import { BattleListData, BattleList, Player, PlayerData, - PlayerLocationLock, BoxListData, BoxList, RandomList, RandomListData} from "../codegen/Tables.sol"; +import { GameConfig, BattleConfig, BattleListData, BattleList, Player, PlayerData, + PlayerLocationLock, BoxList, BoxListData} from "../codegen/Tables.sol"; import { Move } from "./Common.sol"; - +import { GAME_CONFIG_KEY, BATTLE_CONFIG_KEY } from "../Constants.sol"; +import { CommonUtils } from "./library/CommonUtils.sol"; contract GameSystem is System { - bytes32 constant GAME_KEY = keccak256("Game-Key"); - - - event NewRandom(uint256 randomId, address author); event MoveEvent(address indexed player, uint16 x, uint16 y); event AttackStart(address player, address target); - - - /* - bytes32 public merkleRoot; - uint256 public maxAttackzDistance = 10; - uint256 public maxMoveDistance = 15; - uint256 public roomId; - uint256 public battleId; - uint256 public randomId; - uint256 public boxId; - uint256 public maxBoxBindTime = 120; - uint256 public maxTimeLimit = 120; - uint256 public maxUserLocationLockTime = 120; - uint16 public OriginX = 100; - uint16 public OriginY = 100; - */ - - constructor(bytes32 root) { - Game.setMerkleRoot(GAME_KEY, root); - Game.setMaxAttackzDistance(GAME_KEY, 10); - Game.setMaxMoveDistance(GAME_KEY, 15); - Game.setMaxTimeLimit(GAME_KEY, 120); - Game.setMaxUserLocationLockTime(GAME_KEY, 120); - Game.setOriginX(GAME_KEY, 100); - Game.setOriginY(GAME_KEY, 100); - Game.setMaxBoxBindTime(GAME_KEY, 120); - } - - - modifier onlyBattlePlayer(uint256 _battleId, BattleState _battleState) { - BattleListData memory battle = BattleList.get(_battleId); - - BattleState battleState = battle.attacker == _msgSender() - ? battle.attackerState - : battle.defenderState; - - require( - battle.attacker == _msgSender() || battle.defender == _msgSender(), - "You are not in this battle" - ); - require(battleState == _battleState, "You are in the wrong state"); - - require(!battle.isEnd, "Battle is end"); - - _; - } - - function isNear( - uint16 _x1, - uint16 _x2, - uint16 _y1, - uint16 _y2 - ) internal pure returns (bool) { - uint16 x_diff = abs_substruction(_x1, _x2); - uint16 y_diff = abs_substruction(_y1, _y2); - return x_diff <= 1 && y_diff <= 1 && x_diff != y_diff; - } - + // constructor(bytes32 root) { + // GameConfig.setMerkleRoot(GAME_CONFIG_KEY, root); + // GameConfig.setMaxAttackzDistance(GAME_CONFIG_KEY, 10); + // GameConfig.setMaxMoveDistance(GAME_CONFIG_KEY, 15); + // GameConfig.setMaxTimeLimit(GAME_CONFIG_KEY, 120); + // GameConfig.setMaxUserLocationLockTime(GAME_CONFIG_KEY, 120); + // GameConfig.setOriginX(GAME_CONFIG_KEY, 100); + // GameConfig.setOriginY(GAME_CONFIG_KEY, 100); + // GameConfig.setMaxBoxBindTime(GAME_CONFIG_KEY, 120); + // } modifier CheckContinuity(Move[] memory moveList) { // 验证行走轨迹合法且连续 @@ -86,7 +34,7 @@ contract GameSystem is System { uint16 x2 = i > 0 ? moveList[i - 1].x : Player.getX(_msgSender()); uint16 y2 = i > 0 ? moveList[i - 1].y : Player.getY(_msgSender()); require( - isNear(moveList[i].x, x2, moveList[i].y, y2), + CommonUtils.isNear(moveList[i].x, x2, moveList[i].y, y2), "invalied move" ); // 判断用户每一个移动连续性以及合法性,不能超出1格, 不能斜着走,不能原地踏步 @@ -95,7 +43,7 @@ contract GameSystem is System { bytes32 leaf = keccak256( abi.encodePacked(info.x, ",", info.y, ",", prefer) ); - bool isValidLeaf = MerkleProof.verify(info.proof, Game.getMerkleRoot(GAME_KEY), leaf); + bool isValidLeaf = MerkleProof.verify(info.proof, GameConfig.getMerkleRoot(GAME_CONFIG_KEY), leaf); require(isValidLeaf, "bad position"); } _; @@ -105,12 +53,10 @@ contract GameSystem is System { // 限制移动速度,每次移动需要间隔一定时间 require(PlayerLocationLock.get(_msgSender()) == 0, "You are locked"); require( - moveList.length > 0 && moveList.length <= Game.getMaxMoveDistance(GAME_KEY), + moveList.length > 0 && moveList.length <= BattleConfig.getMaxMoveDistance(BATTLE_CONFIG_KEY), "invalid move distance" ); - // Player[_msgSender()].x = moveList[moveList.length - 1].x; - // Player[_msgSender()].y = moveList[moveList.length - 1].y; Player.setX(_msgSender(), moveList[moveList.length - 1].x); Player.setY(_msgSender(), moveList[moveList.length - 1].y); @@ -121,6 +67,7 @@ contract GameSystem is System { ); } + function battleInvitation( address _targetAddress, Move[] memory moveList @@ -128,24 +75,12 @@ contract GameSystem is System { // 攻击,首先确定地图x,y上有具体用户,其次确定用户之间最短距离proof为10 // 需要考虑一个格子上有多个用户的情况//一个格子只能有一个人 // 判断对战双方的状态是否是Exploring - - // PlayerData memory player = Player.get(_msgSender()); - // PlayerData storage target = Player.get(_targetAddress); require( - moveList.length > 0 && moveList.length <= Game.getMaxAttackzDistance(GAME_KEY), + moveList.length > 0 && moveList.length <= BattleConfig.getMaxAttackzDistance(BATTLE_CONFIG_KEY), "invalid attack distance" ); - // require( - // player.state == PlayerState.Exploring && - // target.state == PlayerState.Exploring, - // "Each player must be in exploring state" - // ); - // require( - // target.x == moveList[moveList.length - 1].x && - // target.y == moveList[moveList.length - 1].y, - // "Target must be in the end of continuity" - // ); + require( Player.getState(_msgSender()) == PlayerState.Exploring && Player.getState(_targetAddress) == PlayerState.Exploring, @@ -157,19 +92,10 @@ contract GameSystem is System { "Target must be in the end of continuity" ); - // player.state = PlayerState.Attacking; - // target.state = PlayerState.Attacking; Player.setState(_msgSender(), PlayerState.Attacking); Player.setState(_targetAddress, PlayerState.Attacking); - // Battle storage battle = BattleList[battleId]; - uint256 battleId = Game.getBattleId(GAME_KEY); - // BattleListData storage battle = BattleList.get(battleId); - // battle.attacker = _msgSender(); - // battle.defender = _targetAddress; - // battle.timestamp = block.timestamp; - // battle.attackerHP = player.HP; //这里应该是根据装备来判断 - // battle.defenderHP = target.HP; + uint256 battleId = GameConfig.getBattleId(GAME_CONFIG_KEY); BattleList.setAttacker(battleId, _msgSender()); BattleList.setDefender(battleId, _targetAddress); BattleList.setTimestamp(battleId, block.timestamp); @@ -177,695 +103,24 @@ contract GameSystem is System { BattleList.setDefenderHP(battleId, Player.getHP(_targetAddress)); // battleId++; - Game.setBattleId(GAME_KEY, battleId + 1); - + GameConfig.setBattleId(GAME_CONFIG_KEY, battleId + 1); emit AttackStart(_msgSender(), _targetAddress); } - function confirmBattle( - bytes32 _buffHash, - uint256 _battleId - ) external onlyBattlePlayer(_battleId, BattleState.Inited) { - // 战斗是否有用户 - //战斗是否结束 - //是否已超时 - // Battle storage battle = BattleList[_battleId]; - - // require( - // block.timestamp - BattleList.getTimeStamp.timestamp < maxTimeLimit, - // "Battle is timeout" - // ); - require( - block.timestamp - BattleList.getTimestamp(_battleId) < Game.getMaxTimeLimit(GAME_KEY), - "Battle is timeout" - ); - // 战斗是否已经选择buff - // BattleState _battleState = battle.attacker == _msgSender() - // ? battle.attackerState - // : battle.defenderState; - BattleState _battleState = BattleList.getAttacker(_battleId) == _msgSender() - ? BattleList.getAttackerState(_battleId) - : BattleList.getDefenderState(_battleId); - - require( - _battleState == BattleState.Inited, - "You have already selected buff" - ); - // 当前实现方法非常不优雅,使用两个额外存储槽来存储用户的选择 - // if (battle.attacker == _msgSender()) { - // battle.attackerBuffHash = _buffHash; - // battle.attackerState = BattleState.Confirmed; - // } else { - // battle.defenderBuffHash = _buffHash; - // battle.defenderState = BattleState.Confirmed; - // } - if (BattleList.getAttacker(_battleId) == _msgSender()) { - BattleList.setAttackerBuffHash(_battleId, _buffHash); - BattleList.setAttackerState(_battleId, BattleState.Confirmed); - } else { - BattleList.setDefenderBuffHash(_battleId, _buffHash); - BattleList.setDefenderState(_battleId, BattleState.Confirmed); - } - - // TODO需要一个event通知前端验证buff - } - - function revealBattle( - uint256 _battleId, - bytes32 _action, - uint256 _arg, - bytes32 _nonce - ) external onlyBattlePlayer(_battleId, BattleState.Confirmed) { - // TODO揭示阶段也应该添加时间限制 - // Battle storage battle = BattleList[_battleId]; - // BattleState _battleState = battle.attacker == _msgSender() - // ? battle.attackerState - // : battle.defenderState; - - // bytes32 moveHash = battle.attacker == _msgSender() - // ? battle.attackerBuffHash - // : battle.defenderBuffHash; - address attacker = BattleList.getAttacker(_battleId); - - bytes32 moveHash = attacker == _msgSender() - ? BattleList.getAttackerBuffHash(_battleId) - : BattleList.getDefenderBuffHash(_battleId); - - bytes32 proofHash = keccak256(abi.encodePacked(_action, _arg, _nonce)); - require(moveHash == proofHash, "Invalid move hash proof"); - // if (battle.attacker == _msgSender()) { - // battle.attackerAction = _action; - // battle.attackerArg = _arg; - // battle.attackerState = BattleState.Revealed; - // } else { - // battle.defenderAction = _action; - // battle.defenderArg = _arg; - // battle.defenderState = BattleState.Revealed; - // } - // if ( - // battle.attackerState == BattleState.Revealed && - // battle.defenderState == BattleState.Revealed - // ) { - // // 结算战斗 - // revealWinner(_battleId); - // } - if (attacker == _msgSender()) { - BattleList.setAttackerAction(_battleId, _action); - BattleList.setAttackerArg(_battleId, _arg); - BattleList.setAttackerState(_battleId, BattleState.Revealed); - } else { - BattleList.setDefenderAction(_battleId, _action); - BattleList.setDefenderArg(_battleId, _arg); - BattleList.setDefenderState(_battleId, BattleState.Revealed); - } - if ( - BattleList.getAttackerState(_battleId) == BattleState.Revealed && - BattleList.getDefenderState(_battleId) == BattleState.Revealed - ) { - // 结算战斗 - revealWinner(_battleId); - } - } - - function revealWinner( - uint256 _battleId - ) public onlyBattlePlayer(_battleId, BattleState.Revealed) { - // 结算战斗 - BattleListData memory battle = BattleList.get(_battleId); - bytes32 attackerAction = battle.attackerAction; - bytes32 defenderAction = battle.defenderAction; - uint256 attackerFirepower = 100; - uint256 defenderFirepower = 100; - - // address attacker = BattleList.getAttacker(_battleId); - // address defender = BattleList.getDefender(_battleId); - //用户的指令为attack,esacpe,useProps几种方案,attackArg和defenderArg分别是伴随指令传递的参数,对应执行attack,esacpe,useProps几个函数并传入参数 - // 默认用户的攻击力都为100,防御力都为100,攻击力和防御力在未来都是根据装备来判断 - //如果双方都执行attack,则对比buff,buff相同攻击力没有增益,否则按照water>fire>wind>water的顺序给优胜者增加50%攻击力 - //如果对方执行escape,则判断buff是否大于对方的buff,如果大于则对方逃跑成功 - if ( - attackerAction == bytes32("attack") && - defenderAction == bytes32("attack") - ) { - // Buff attackerBuff = Buff(battle.attackerArg); - // Buff defenderBuff = Buff(battle.defenderArg); - Buff attackerBuff = Buff(battle.attackerArg); - Buff defenderBuff = Buff(battle.defenderArg); - // 任意攻击buff都强于None - uint256 attackerAttackPower = getAttackPower( - attackerBuff, - defenderBuff, - attackerFirepower - ); - uint256 defenderAttackPower = getAttackPower( - defenderBuff, - attackerBuff, - defenderFirepower - ); - // battle.attackerHP = getAttackResult( - // battle.attackerHP, - // defenderAttackPower - // ); - // battle.defenderHP = getAttackResult( - // battle.defenderHP, - // attackerAttackPower - // ); - // if (battle.attackerHP == 0 || battle.defenderHP == 0) { - // address winner = battle.attackerHP == 0 - // ? battle.defender - // : battle.attacker; - // address looser = battle.attackerHP == 0 - // ? battle.attacker - // : battle.defender; - // battle.winer = winner; - - // battle.isEnd = true; - - - // loseGame(looser,winner); - // UserInfo[battle.winer].HP = initUserHP(battle.winer); - // TODO这里应该跟一个清算函数 - // 胜利者解除战斗形态,血量恢复20% - // 失败者传送到非战区,血量回满 - // } - // battle.attackerHP = getAttackResult( - // battle.attackerHP, - // defenderAttackPower - // ); - // battle.defenderHP = getAttackResult( - // battle.defenderHP, - // attackerAttackPower - // ); - - - BattleList.setAttackerHP(_battleId, getAttackResult( - battle.attackerHP, - defenderAttackPower - )); - BattleList.setDefenderHP(_battleId, getAttackResult( - battle.defenderHP, - attackerAttackPower - )); - - - - if (battle.attackerHP == 0 || battle.defenderHP == 0) { - address winner = battle.attackerHP == 0? battle.defender : battle.attacker; - address looser = battle.attackerHP == 0 - ? battle.attacker - : battle.defender; - BattleList.setWinner(_battleId, winner); - BattleList.setIsEnd(_battleId, true); - // battle.winner = winner; - // battle.isEnd = true; - loseGame(looser, winner); - - Player.setHP(winner, initUserHP(winner)); - - // TODO这里应该跟一个清算函数 - // 胜利者解除战斗形态,血量恢复20% - // 失败者传送到非战区,血量回满 - } - - } - - - if ( - attackerAction == bytes32("escape") && - defenderAction == bytes32("escape") - ) { - // 双方都逃走,则战斗结束(这里应该都传送到更远地方) - // battle.isEnd = true; - // battle.winer = address(0); - - BattleList.setIsEnd(_battleId, true); - BattleList.setWinner(_battleId, address(0)); - - return; - } - if ( - attackerAction == bytes32("escape") && - defenderAction == bytes32("attack") - ) { - Buff attackerBuff = Buff(battle.defenderArg); - Buff defenderBuff = Buff(battle.defenderArg); - // 任意攻击buff都强于None - if ( - attackerBuff == defenderBuff || - compareBuff(attackerBuff, defenderBuff) == 2 - ) { - // 逃跑成功 - Player.setState(battle.attacker, PlayerState.Exploring); - Player.setState(battle.defender, PlayerState.Exploring); - // PlayerLocationLock[battle.defender] = block.timestamp; //将被逃跑方禁锢一段时间 - PlayerLocationLock.set(battle.defender, block.timestamp); - } else { - // 逃跑失败,被动挨打 - uint256 defenderAttackPower = getAttackPower( - defenderBuff, - attackerBuff, - defenderFirepower - ); - // battle.attackerHP = getAttackResult( - // battle.attackerHP, - // defenderAttackPower - // ); - // if (battle.attackerHP == 0) { - // battle.winer = battle.defender; - // battle.isEnd = true; - // } - BattleList.setAttackerHP(_battleId, getAttackResult( - battle.attackerHP, - defenderAttackPower - )); - if (BattleList.getAttackerHP(_battleId) == 0) { - - // battle.winer = battle.defender; - // battle.isEnd = true; - BattleList.setWinner(_battleId, battle.defender); - BattleList.setIsEnd(_battleId, true); - } - } - } - if ( - attackerAction == bytes32("attack") && - defenderAction == bytes32("escape") - ) { - Buff attackerBuff = Buff(battle.attackerArg); - Buff defenderBuff = Buff(battle.defenderArg); - // 任意攻击buff都强于None - if ( - attackerBuff == defenderBuff || - compareBuff(defenderBuff, attackerBuff) == 2 - ) { - // 逃跑成功 - // Player[battle.defender].state = PlayerState.Exploring; - // Player[battle.attacker].state = PlayerState.Exploring; - // PlayerLocationLock[battle.attacker] = block.timestamp; //将被逃跑方禁锢一段时间 - Player.setState(battle.defender, PlayerState.Exploring); - Player.setState(battle.attacker, PlayerState.Exploring); - PlayerLocationLock.set(battle.attacker, block.timestamp); - } else { - // 逃跑失败,被动挨打 - uint256 attackerAttackPower = getAttackPower( - attackerBuff, - defenderBuff, - attackerFirepower - ); - - BattleList.setDefenderHP(_battleId, getAttackResult( - battle.defenderHP, - attackerAttackPower - )); - - if (BattleList.getDefenderHP(_battleId) == 0) { - // battle.winer = battle.attacker; - // battle.isEnd = true; - - BattleList.setWinner(_battleId, battle.attacker); - BattleList.setIsEnd(_battleId, true); - - } - } - } - - } - - function loseGame(address _looser, address _winner) internal { - // 游戏失败,将用户脱离战区,血量回满 - // TODO 背包系统,宝物系统 - // User storage losser = UserInfo[_looser]; - // outBattlefield(_looser); - // Box storage box = BoxList[roomId][boxId]; - // box.x = losser.x; - // box.y = losser.y; - // box.opened = true; - // box.openTime = block.timestamp; - // box.owner = _winner; - // boxId++; - // box.oreBalance = losser.oreBalance; - // box.treasureBalance = losser.treasureBalance; - // losser.oreBalance = 0; - // losser.treasureBalance = 0; - // box.dropTime = block.timestamp; - - outBattlefield(_looser); - PlayerData memory losser = Player.get(_looser); - uint256 boxId = Game.getBoxId(GAME_KEY); - BoxListData memory box; - box.x = losser.x; - box.y = losser.y; - box.opened = true; - box.openTime = block.timestamp; - box.owner = _winner; - box.oreBalance = losser.oreBalance; - box.treasureBalance = losser.treasureBalance; - box.dropTime = block.timestamp; - BoxList.set(Game.getRoomId(GAME_KEY), boxId, box); - - Player.setOreBalance(_looser, 0); - Player.setTreasureBalance(_looser, 0); - Game.setRoomId(GAME_KEY, boxId+1); - } - - function goHome() external { - // 回家,将用户脱离战区,血量回满 - PlayerData memory player = Player.get(_msgSender()); - require( - player.state == PlayerState.Exploring, - "You should in exploring state" - ); - require( - player.x == Game.getOriginX(GAME_KEY) && player.y == Game.getOriginY(GAME_KEY), - "You are not in the origin point" - ); - outBattlefield(_msgSender()); - } - - function initUserHP(address _user) public pure returns (uint256) { - return 400; - } - - function raiseUserHP( - uint256 _targetHP, - uint256 _percent, - address _user - ) public { - Player.setHP(_user, (_targetHP * _percent) / 100); - } - - function unlockUserLocation() external { - // 用户自行解锁 - require(PlayerLocationLock.get(_msgSender()) != 0, "You are not locked"); - require( - PlayerLocationLock.get(_msgSender()) + Game.getMaxUserLocationLockTime(GAME_KEY) < - block.timestamp, - "You are not locked" - ); - PlayerLocationLock.set(_msgSender(), 0); - } - - function getAttackResult( - uint256 _hp, - uint256 _attackPower - ) internal pure returns (uint256) { - // TODO 后期添加防御力抵消对方的攻击力 - if (_attackPower > _hp) { - return 0; - } - return _hp - _attackPower; - } - - function getAttackPower( - Buff _myBuff, - Buff _targetBuff, - uint256 _attackPower - ) internal pure returns (uint256) { - // TODO 后期添加防御力抵消对方的攻击力 - if (compareBuff(_myBuff, _targetBuff) == 0) { - return (_attackPower * 7) / 10; - } - if (compareBuff(_myBuff, _targetBuff) == 2) { - return (_attackPower * 13) / 10; - } + - return _attackPower; - } - function compareBuff( - Buff _myBuff, - Buff _targetBuff - ) internal pure returns (uint256) { - // 0表示失败,1表示相当,2表示胜利 - if ( - (_myBuff == Buff.Water && _targetBuff == Buff.Fire) || - (_myBuff == Buff.Wind && _targetBuff == Buff.Water) || - (_myBuff == Buff.Fire && _targetBuff == Buff.Wind) - ) { - return 2; - } - if ( - (_myBuff == Buff.Fire && _targetBuff == Buff.Water) || - (_myBuff == Buff.Water && _targetBuff == Buff.Wind) || - (_myBuff == Buff.Wind && _targetBuff == Buff.Fire) - ) { - return 0; - } - return 1; - } + // function settleBattle() external {} - function settleBattle() external {} + // function createLootBox() external {} - function createLootBox() external {} + // function takeLoot() external {} - function takeLoot() external {} + // function dropLoot() external {} - function dropLoot() external {} //------非战区空间部分 - - function transfer(uint16 x, uint16 y) external { - //传送门,将用户在战区和非战区移动 - // 将用户坐标随机转移到指定位置 - // Player[_msgSender()].x = x; - // Player[_msgSender()].y = y; - Player.setX(_msgSender(), x); - Player.setY(_msgSender(), y); - } - - function abs_substruction( - uint16 a, - uint16 b - ) internal pure returns (uint16) { - if (a > b) { - return a - b; - } else { - return b - a; - } - } - - function setMerkleRoot(bytes32 root) external { - // TODO onlyowner - // merkleRoot = root; - Game.setMerkleRoot(GAME_KEY, root); - } - - function joinBattlefield(address _user) public { - // 加入战区,用户实际上是送到原点,状态改为探索中 - // User storage player = Player[_user]; - PlayerState playerState = Player.getState(_user); - require( - playerState == PlayerState.Preparing || playerState == PlayerState.Idle, - "You should in preparing state" - ); - // player.x = OriginX; //实际上是送到原点//TODO通过常数设置原点参数 - // player.y = OriginX; - // battlefieldPlayers.push(_user); - // Player[_user].state = PlayerState.Exploring; - Player.setX(_user, Game.getOriginX(GAME_KEY)); - Player.setY(_user, Game.getOriginY(GAME_KEY)); - Game.pushBattlefieldPlayers(GAME_KEY, _user); - Player.setState(_user, PlayerState.Exploring); - } - - function outBattlefield(address _user) internal { - // 脱离战区,则将用户血量回满,坐标不变,状态改为准备中 - // User storage player = Player[_user]; - // require( - // player.state == PlayerState.Exploring, - // "You should in exploring state" - // ); - require( - Player.getState(_user) == PlayerState.Exploring, - "You should in exploring state" - ); - - // player.HP = initUserHP(_user); // - - // if (battlefieldPlayers.length <= 1) { - // // 直接清零 - // battlefieldPlayers = new address[](0); - // } else { - // for (uint256 i; i < battlefieldPlayers.length; i++) { - // if (battlefieldPlayers[i] == _user) { - // battlefieldPlayers[i] = battlefieldPlayers[ - // battlefieldPlayers.length - 1 - // ]; - // battlefieldPlayers.pop(); - // } - // } - // } - // Player[_user].state = PlayerState.Preparing; - - Player.setHP(_user, initUserHP(_user)); - - for (uint256 i; i < Game.lengthBattlefieldPlayers(GAME_KEY); i++) { - if (Game.getItemBattlefieldPlayers(GAME_KEY, i) == _user) { - Game.updateBattlefieldPlayers(GAME_KEY, i, Game.getItemBattlefieldPlayers(GAME_KEY, Game.lengthBattlefieldPlayers(GAME_KEY) - 1)); - Game.popBattlefieldPlayers(GAME_KEY); - } - } - Player.setState(_user, PlayerState.Preparing); - } - - function requestRandom() external { - // Random storage r = randomList[randomId]; - // r.author = _msgSender(); - // r.blockNumber = block.number; - - uint256 randomId = Game.getRandomId(GAME_KEY); - RandomList.setAuthor(randomId, _msgSender()); - RandomList.setBlockNumber(randomId, block.number); - - emit NewRandom(randomId, _msgSender()); - } - - function getRandom( - uint256 _randomId, - uint256 _count - ) internal view returns (uint8[] memory) { - require( - _msgSender() == RandomList.getAuthor(_randomId), - "only random creator can get random" - ); - uint8[] memory randomNumberList = new uint8[](_count); - RandomListData memory r = RandomList.get(_randomId); - require( - block.number >= r.blockNumber + 2, - "too early to get random seed" - ); - uint256 seed = uint256(blockhash(r.blockNumber + 2)); - // 一次处理一个uint256随机数 - uint256 randomNumber = uint256(keccak256(abi.encodePacked(seed))); - - // 截断后存入属性数组 - for (uint8 i = 0; i < _count; i++) { - uint8 digit = uint8(randomNumber % 100); - randomNumberList[i] = digit; - randomNumber = randomNumber / 100; - } - return randomNumberList; - } - - function creatBox(uint16 _x, uint16 _y) internal { - // Box storage box = BoxList[roomId][boxId]; - // box.x = _x; - // box.y = _y; - // box.randomId = randomId; - // randomId++; - // boxId++; - - uint256 roomId = Game.getRoomId(GAME_KEY); - uint256 boxId = Game.getBoxId(GAME_KEY); - BoxList.setX(roomId, boxId, _x); - BoxList.setY(roomId, boxId, _y); - - uint256 randomId = Game.getRandomId(GAME_KEY); - BoxList.setRandomId(roomId, boxId, randomId); - Game.setRandomId(GAME_KEY, randomId + 1); - Game.setBoxId(GAME_KEY, boxId + 1); - } - - function getCollections(uint256 _boxId, uint16 _oreAmount,uint16 _treasureAmount) internal { - // require(BoxList[roomId][_boxId].dropTime != 0, "Invalid box"); - // Box storage _box = BoxList[roomId][_boxId]; - // User storage _user = UserInfo[_box.owner]; - // require( - // isNear(_box.x, _user.x, _box.y, _user.y), - // "You are not near the box" - // ); - // require(_box.opened == true, "Box is not opened"); - // if(block.timestamp<_box.openTime+maxBoxBindTime){ - // require(msg.sender==_box.owner,"The box is waiting for its opener, please wait"); - // } - // require(_oreAmount<=_box.oreBalance&&_treasureAmount<=_box.treasureBalance,"Invalid amount"); - // _box.oreBalance-=_oreAmount; - // _box.treasureBalance-=_treasureAmount; - // _user.oreBalance+=_oreAmount; - // _user.treasureBalance+=_treasureAmount; - - uint256 roomId = Game.getRoomId(GAME_KEY); - uint256 boxId = Game.getBoxId(GAME_KEY); - require(BoxList.getDropTime(roomId, _boxId) != 0, "Invalid box"); - BoxListData memory _box = BoxList.get(roomId, boxId); - PlayerData memory _user = Player.get(_box.owner); - require( - isNear(_box.x, _user.x, _box.y, _user.y), - "You are not near the box" - ); - require(_box.opened == true, "Box is not opened"); - if(block.timestamp<_box.openTime + Game.getMaxBoxBindTime(GAME_KEY)){ - require(msg.sender==_box.owner,"The box is waiting for its opener, please wait"); - } - require(_oreAmount<=_box.oreBalance&&_treasureAmount<=_box.treasureBalance,"Invalid amount"); - BoxList.setOreBalance(roomId, boxId, _box.oreBalance - _oreAmount); - BoxList.setTreasureBalance(roomId, boxId, _box.treasureBalance - _treasureAmount); - Player.setOreBalance(_box.owner, _user.oreBalance + _oreAmount); - Player.setTreasureBalance(_box.owner, _user.treasureBalance + _treasureAmount); - } - - function openBox(uint256 _boxId) external { - // 宝箱打开时init内容物,根据自带randomId来实现随机 - // require(BoxList[roomId][_boxId].dropTime != 0, "Invalid box"); - // Box storage _box = BoxList[roomId][_boxId]; - // User storage _user = UserInfo[_box.owner]; - // require( - // isNear(_box.x, _user.x, _box.y, _user.y), - // "You are not near the box" - // ); - // require(_box.opened == false, "Box is opened"); - // uint8[] memory randomNumberList = getRandom(_box.randomId, 2); - // uint8 oreBalance = dice(randomNumberList[0],20,10,1); - // uint8 treasureBalance = dice(randomNumberList[1],80,10,1); - // _box.oreBalance = oreBalance; - // _box.treasureBalance = treasureBalance; - // _box.opened = true; - // _box.openTime = block.timestamp; - - uint256 roomId = Game.getRoomId(GAME_KEY); - uint256 boxId = Game.getBoxId(GAME_KEY); - require(BoxList.getDropTime(roomId, _boxId) != 0, "Invalid box"); - BoxListData memory _box = BoxList.get(roomId, boxId); - PlayerData memory _user = Player.get(_box.owner); - require( - isNear(_box.x, _user.x, _box.y, _user.y), - "You are not near the box" - ); - require(_box.opened == false, "Box is opened"); - uint8[] memory randomNumberList = getRandom(_box.randomId, 2); - uint8 oreBalance = dice(randomNumberList[0],20,10,1); - uint8 treasureBalance = dice(randomNumberList[1],80,10,1); - BoxList.setOreBalance(roomId, boxId, oreBalance); - BoxList.setTreasureBalance(roomId, boxId, treasureBalance); - BoxList.setOpened(roomId, boxId, true); - BoxList.setOpenTime(roomId, boxId, block.timestamp); - } - - function zeroSub(uint8 a, uint8 b) internal pure returns (uint8) { - uint8 result = a > b ? a - b : 0; - return result; - } - - - function dice( - uint8 number, - uint8 threshold, - uint8 increaseX, - uint8 increaseY - ) internal pure returns (uint8) { - //掷骰子,产生根据规则的随机数 - require(threshold > 0&&increaseY>0&&increaseX>0, "invalid params"); - uint8 result; - number = zeroSub(number, threshold); - if (number>0) { - uint8 multiplier = number / increaseX; - result = multiplier * increaseY; - return result; - } else { - return 0; - } - } } diff --git a/packages/contracts/src/systems/OwnableSystem.sol b/packages/contracts/src/systems/OwnableSystem.sol deleted file mode 100644 index 70de2bf2..00000000 --- a/packages/contracts/src/systems/OwnableSystem.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0; - -import { System } from "@latticexyz/world/src/System.sol"; -import { Ownable } from "../codegen/Tables.sol"; - -contract OwnableSystem is System { - bytes32 constant OWN_KEY = keccak256("Ownable-Key"); - - event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); - - constructor() { - _transferOwnership(_msgSender()); - } - - - modifier onlyOwner() { - _checkOwner(); - _; - } - - - function owner() public view virtual returns (address) { - return Ownable.get(OWN_KEY); - } - - - function _checkOwner() internal view virtual { - require(owner() == _msgSender(), "Ownable: caller is not the owner"); - } - - - function renounceOwnership() public virtual onlyOwner { - _transferOwnership(address(0)); - } - - - function transferOwnership(address newOwner) public virtual onlyOwner { - require(newOwner != address(0), "Ownable: new owner is the zero address"); - _transferOwnership(newOwner); - } - - - function _transferOwnership(address newOwner) internal virtual { - address oldOwner = Ownable.get(OWN_KEY); - Ownable.set(OWN_KEY, newOwner); - emit OwnershipTransferred(oldOwner, newOwner); - } -} \ No newline at end of file diff --git a/packages/contracts/src/systems/PlayerSystem.sol b/packages/contracts/src/systems/PlayerSystem.sol index e7067a4c..772f6b41 100644 --- a/packages/contracts/src/systems/PlayerSystem.sol +++ b/packages/contracts/src/systems/PlayerSystem.sol @@ -7,12 +7,15 @@ import { Player } from "../codegen/Tables.sol"; contract PlayerSystem is System { bytes32 constant PLAYER_KEY = keccak256("Player-Key"); + constructor(){ + + } + function getInfo(address addr) public view returns (string memory, string memory) { return (Player.getName(addr), Player.getUrl(addr)); } - function setInfo(string memory name, string memory url) - public { + function setInfo(string memory name, string memory url) public { address addr = _msgSender(); Player.setName(addr, name); Player.setUrl(addr, url); diff --git a/packages/contracts/src/systems/RandomSystem.sol b/packages/contracts/src/systems/RandomSystem.sol new file mode 100644 index 00000000..21b7208f --- /dev/null +++ b/packages/contracts/src/systems/RandomSystem.sol @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import { System } from "@latticexyz/world/src/System.sol"; +import { BattleState, Buff, PlayerState } from "../codegen/Types.sol"; +import { GameConfig, BattleConfig, RandomList, RandomListData, BoxList, BoxListData, Player, PlayerData, PlayerLocationLock} from "../codegen/Tables.sol"; +import { GAME_CONFIG_KEY, BATTLE_CONFIG_KEY } from "../Constants.sol"; +import { CommonUtils } from "./library/CommonUtils.sol"; + + +contract RandomSystem is System { + event NewRandom(uint256 randomId, address author); + + function raiseUserHP( + uint256 _targetHP, + uint256 _percent, + address _user + ) public { + Player.setHP(_user, (_targetHP * _percent) / 100); + } + + function unlockUserLocation() external { + // 用户自行解锁 + require(PlayerLocationLock.get(_msgSender()) != 0, "You are not locked"); + require( + PlayerLocationLock.get(_msgSender()) + BattleConfig.getMaxUserLocationLockTime(BATTLE_CONFIG_KEY) < + block.timestamp, + "You are not locked" + ); + PlayerLocationLock.set(_msgSender(), 0); + } + + function transfer(uint16 x, uint16 y) external { + //传送门,将用户在战区和非战区移动 + // 将用户坐标随机转移到指定位置 + Player.setX(_msgSender(), x); + Player.setY(_msgSender(), y); + } + + function joinBattlefield(address _user) public { + // 加入战区,用户实际上是送到原点,状态改为探索中 + // User storage player = Player[_user]; + PlayerState playerState = Player.getState(_user); + require( + playerState == PlayerState.Preparing || playerState == PlayerState.Idle, + "You should in preparing state" + ); + //实际上是送到原点//TODO通过常数设置原点参数 + Player.setX(_user, GameConfig.getOriginX(GAME_CONFIG_KEY)); + Player.setY(_user, GameConfig.getOriginY(GAME_CONFIG_KEY)); + BattleConfig.pushBattlefieldPlayers(BATTLE_CONFIG_KEY, _user); + Player.setState(_user, PlayerState.Exploring); + } + + function getRandom( + uint256 _randomId, + uint256 _count + ) internal view returns (uint8[] memory) { + require( + _msgSender() == RandomList.getAuthor(_randomId), + "only random creator can get random" + ); + uint8[] memory randomNumberList = new uint8[](_count); + RandomListData memory r = RandomList.get(_randomId); + require( + block.number >= r.blockNumber + 2, + "too early to get random seed" + ); + uint256 seed = uint256(blockhash(r.blockNumber + 2)); + // 一次处理一个uint256随机数 + uint256 randomNumber = uint256(keccak256(abi.encodePacked(seed))); + + // 截断后存入属性数组 + for (uint8 i = 0; i < _count; i++) { + uint8 digit = uint8(randomNumber % 100); + randomNumberList[i] = digit; + randomNumber = randomNumber / 100; + } + return randomNumberList; + } + + function requestRandom() external { + uint256 randomId = GameConfig.getRandomId(GAME_CONFIG_KEY); + RandomList.setAuthor(randomId, _msgSender()); + RandomList.setBlockNumber(randomId, block.number); + + emit NewRandom(randomId, _msgSender()); + } + + function creatBox(uint16 _x, uint16 _y) internal { + uint256 roomId = GameConfig.getRoomId(GAME_CONFIG_KEY); + uint256 boxId = GameConfig.getBoxId(GAME_CONFIG_KEY); + BoxList.setX(roomId, boxId, _x); + BoxList.setY(roomId, boxId, _y); + + uint256 randomId = GameConfig.getRandomId(GAME_CONFIG_KEY); + BoxList.setRandomId(roomId, boxId, randomId); + GameConfig.setRandomId(GAME_CONFIG_KEY, randomId + 1); + GameConfig.setBoxId(GAME_CONFIG_KEY, boxId + 1); + } + + function openBox(uint256 _boxId) external { + // 宝箱打开时init内容物,根据自带randomId来实现随机 + uint256 roomId = GameConfig.getRoomId(GAME_CONFIG_KEY); + uint256 boxId = GameConfig.getBoxId(GAME_CONFIG_KEY); + require(BoxList.getDropTime(roomId, _boxId) != 0, "Invalid box"); + BoxListData memory _box = BoxList.get(roomId, boxId); + PlayerData memory _user = Player.get(_box.owner); + require( + CommonUtils.isNear(_box.x, _user.x, _box.y, _user.y), + "You are not near the box" + ); + require(_box.opened == false, "Box is opened"); + uint8[] memory randomNumberList = getRandom(_box.randomId, 2); + uint8 oreBalance = CommonUtils.dice(randomNumberList[0],20,10,1); + uint8 treasureBalance = CommonUtils.dice(randomNumberList[1],80,10,1); + BoxList.setOreBalance(roomId, boxId, oreBalance); + BoxList.setTreasureBalance(roomId, boxId, treasureBalance); + BoxList.setOpened(roomId, boxId, true); + BoxList.setOpenTime(roomId, boxId, block.timestamp); + } + + function getCollections(uint256 _boxId, uint16 _oreAmount,uint16 _treasureAmount) internal { + uint256 roomId = GameConfig.getRoomId(GAME_CONFIG_KEY); + uint256 boxId = GameConfig.getBoxId(GAME_CONFIG_KEY); + require(BoxList.getDropTime(roomId, _boxId) != 0, "Invalid box"); + BoxListData memory _box = BoxList.get(roomId, boxId); + PlayerData memory _user = Player.get(_box.owner); + require( + CommonUtils.isNear(_box.x, _user.x, _box.y, _user.y), + "You are not near the box" + ); + require(_box.opened == true, "Box is not opened"); + if(block.timestamp<_box.openTime + BattleConfig.getMaxBoxBindTime(BATTLE_CONFIG_KEY)){ + require(msg.sender==_box.owner,"The box is waiting for its opener, please wait"); + } + require(_oreAmount<=_box.oreBalance&&_treasureAmount<=_box.treasureBalance,"Invalid amount"); + BoxList.setOreBalance(roomId, boxId, _box.oreBalance - _oreAmount); + BoxList.setTreasureBalance(roomId, boxId, _box.treasureBalance - _treasureAmount); + Player.setOreBalance(_box.owner, _user.oreBalance + _oreAmount); + Player.setTreasureBalance(_box.owner, _user.treasureBalance + _treasureAmount); + } + + +} \ No newline at end of file diff --git a/packages/contracts/src/systems/library/BattleUtils.sol b/packages/contracts/src/systems/library/BattleUtils.sol new file mode 100644 index 00000000..235397c4 --- /dev/null +++ b/packages/contracts/src/systems/library/BattleUtils.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import { BattleState, Buff, PlayerState } from "../../codegen/Types.sol"; + +library BattleUtils { + function compareBuff( + Buff _myBuff, + Buff _targetBuff + ) internal pure returns (uint256) { + // 0表示失败,1表示相当,2表示胜利 + if ( + (_myBuff == Buff.Water && _targetBuff == Buff.Fire) || + (_myBuff == Buff.Wind && _targetBuff == Buff.Water) || + (_myBuff == Buff.Fire && _targetBuff == Buff.Wind) + ) { + return 2; + } + if ( + (_myBuff == Buff.Fire && _targetBuff == Buff.Water) || + (_myBuff == Buff.Water && _targetBuff == Buff.Wind) || + (_myBuff == Buff.Wind && _targetBuff == Buff.Fire) + ) { + return 0; + } + return 1; + } + + function getAttackResult( + uint256 _hp, + uint256 _attackPower + ) internal pure returns (uint256) { + // TODO 后期添加防御力抵消对方的攻击力 + if (_attackPower > _hp) { + return 0; + } + return _hp - _attackPower; + } + + function getAttackPower( + Buff _myBuff, + Buff _targetBuff, + uint256 _attackPower + ) internal pure returns (uint256) { + // TODO 后期添加防御力抵消对方的攻击力 + if (compareBuff(_myBuff, _targetBuff) == 0) { + return (_attackPower * 7) / 10; + } + if (compareBuff(_myBuff, _targetBuff) == 2) { + return (_attackPower * 13) / 10; + } + + return _attackPower; + } +} \ No newline at end of file diff --git a/packages/contracts/src/systems/library/CommonUtils.sol b/packages/contracts/src/systems/library/CommonUtils.sol new file mode 100644 index 00000000..9ef5ab5d --- /dev/null +++ b/packages/contracts/src/systems/library/CommonUtils.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import { BattleState, Buff, PlayerState } from "../../codegen/Types.sol"; + +library CommonUtils { + function abs_substruction( + uint16 a, + uint16 b + ) internal pure returns (uint16) { + if (a > b) { + return a - b; + } else { + return b - a; + } + } + + function isNear( + uint16 _x1, + uint16 _x2, + uint16 _y1, + uint16 _y2 + ) internal pure returns (bool) { + uint16 x_diff = abs_substruction(_x1, _x2); + uint16 y_diff = abs_substruction(_y1, _y2); + return x_diff <= 1 && y_diff <= 1 && x_diff != y_diff; + } + + function zeroSub(uint8 a, uint8 b) internal pure returns (uint8) { + uint8 result = a > b ? a - b : 0; + return result; + } + + function dice( + uint8 number, + uint8 threshold, + uint8 increaseX, + uint8 increaseY + ) internal pure returns (uint8) { + //掷骰子,产生根据规则的随机数 + require(threshold > 0&&increaseY>0&&increaseX>0, "invalid params"); + uint8 result; + number = zeroSub(number, threshold); + if (number>0) { + uint8 multiplier = number / increaseX; + result = multiplier * increaseY; + return result; + } else { + return 0; + } + } +} \ No newline at end of file diff --git a/packages/contracts/worlds.json b/packages/contracts/worlds.json index bd48105f..e80e3aad 100644 --- a/packages/contracts/worlds.json +++ b/packages/contracts/worlds.json @@ -1,5 +1,9 @@ { "31337": { - "address": "0x5FbDB2315678afecb367f032d93F642f64180aa3" + "address": "0xD5724171C2b7f0AA717a324626050BD05767e2C6" + }, + "11155111": { + "address": "0xec7F8CF3B3640b1C5E8fD3c208776aad056D4fc3", + "blockNumber": 4450305 } } \ No newline at end of file