diff --git a/contracts/adventurer/src/adventurer.cairo b/contracts/adventurer/src/adventurer.cairo index 39b8b8d27..9b4e269ee 100644 --- a/contracts/adventurer/src/adventurer.cairo +++ b/contracts/adventurer/src/adventurer.cairo @@ -956,18 +956,15 @@ impl ImplAdventurer of IAdventurer { specials } - // @notice get_beast_seed provides an entropy source that is fixed during battle - // it intentionally does not use global_entropy as that could change during battle and this - // entropy allows us to simulate a persistent battle without having to store beast - // details on-chain. - // @param self A reference to the Adventurer object which represents the adventurer. - // @param adventurer_entropy A number used for randomization. + // @notice provides a a beast seed that is fixed during battle. This function does not use + // game entropy as that could change during battle resulting in the beast changing + // @param self A reference to the Adventurer to get the beast seed for. + // @param adventurer_entropy A u128 used to randomize the beast seed // @return Returns a number used for generated a random beast. fn get_beast_seed(self: Adventurer, adventurer_entropy: u128) -> u128 { if self.get_level() > 1 { let mut hash_span = ArrayTrait::new(); hash_span.append(self.xp.into()); - hash_span.append(self.gold.into()); hash_span.append(adventurer_entropy.into()); let poseidon = poseidon_hash_span(hash_span.span()); let (d, r) = rshift_split(poseidon.into(), 340282366920938463463374607431768211455); @@ -1409,12 +1406,12 @@ impl ImplAdventurer of IAdventurer { } fn get_randomness( - self: Adventurer, adventurer_entropy: u128, global_entropy: u128 + self: Adventurer, adventurer_entropy: u128, game_entropy: u128 ) -> (u128, u128) { let mut hash_span = ArrayTrait::::new(); hash_span.append(self.xp.into()); hash_span.append(adventurer_entropy.into()); - hash_span.append(global_entropy.into()); + hash_span.append(game_entropy.into()); let poseidon = poseidon_hash_span(hash_span.span()); let (d, r) = rshift_split(poseidon.into(), U128_MAX.into()); diff --git a/contracts/adventurer/src/adventurer_meta.cairo b/contracts/adventurer/src/adventurer_meta.cairo index 7ec30a86b..b1041f506 100644 --- a/contracts/adventurer/src/adventurer_meta.cairo +++ b/contracts/adventurer/src/adventurer_meta.cairo @@ -1,36 +1,26 @@ -use option::OptionTrait; use traits::{TryInto, Into}; - use pack::constants::pow; use pack::pack::{Packing, rshift_split}; #[derive(Drop, Copy, Serde)] struct AdventurerMetadata { name: u128, - home_realm: u16, - class: u8, entropy: u128, } impl PackingAdventurerMetadata of Packing { fn pack(self: AdventurerMetadata) -> felt252 { (self.entropy.into() - + self.home_realm.into() * pow::TWO_POW_128 - + self.class.into() * pow::TWO_POW_141 - + self.name.into() * pow::TWO_POW_145) + + self.name.into() * pow::TWO_POW_128) .try_into() .expect('pack AdventurerMetadata') } fn unpack(packed: felt252) -> AdventurerMetadata { let packed = packed.into(); let (packed, entropy) = rshift_split(packed, pow::TWO_POW_128); - let (packed, home_realm) = rshift_split(packed, pow::TWO_POW_13); - let (packed, class) = rshift_split(packed, pow::TWO_POW_4); - let (_, name) = rshift_split(packed, pow::TWO_POW_107); + let (_, name) = rshift_split(packed, pow::TWO_POW_128); AdventurerMetadata { name: name.try_into().expect('unpack AdvMetadata name'), - home_realm: home_realm.try_into().expect('unpack AdvMetadata home_realm'), - class: class.try_into().expect('unpack AdvMetadata class'), entropy: entropy.try_into().expect('unpack AdvMetadata entropy') } } @@ -43,19 +33,15 @@ impl PackingAdventurerMetadata of Packing { #[cfg(test)] #[test] -#[available_gas(50000000)] -fn test_meta() { +#[available_gas(96380)] +fn test_pack_unpack_adventurer_meta() { let meta = AdventurerMetadata { - name: 'abcdefghijklm', - home_realm: 8000, - class: 1, + name: 'abcdefghijklmno', entropy: 340282366920938463463374607431768211455 }; let packed = meta.pack(); let unpacked: AdventurerMetadata = Packing::unpack(packed); assert(meta.name == unpacked.name, 'name'); - assert(meta.home_realm == unpacked.home_realm, 'home_realm'); - assert(meta.class == unpacked.class, 'class'); assert(meta.entropy == unpacked.entropy, 'entropy'); } diff --git a/contracts/adventurer/src/adventurer_utils.cairo b/contracts/adventurer/src/adventurer_utils.cairo index bbeef0ffa..39b8eae47 100644 --- a/contracts/adventurer/src/adventurer_utils.cairo +++ b/contracts/adventurer/src/adventurer_utils.cairo @@ -86,10 +86,13 @@ impl AdventurerUtils of IAdventurerUtils { // @param block_number The block number used in generating the entropy // @param adventurer_id The ID of the adventurer used in generating the entropy // @return A u128 type entropy unique to the block number and adventurer ID - fn generate_adventurer_entropy(block_number: u64, adventurer_id: u256) -> u128 { + fn generate_adventurer_entropy( + adventurer_id: u256, block_number: u64, block_timestamp: u64 + ) -> u128 { let mut hash_span = ArrayTrait::::new(); - hash_span.append(block_number.into()); hash_span.append(adventurer_id.try_into().unwrap()); + hash_span.append(block_number.into()); + hash_span.append(block_timestamp.into()); let poseidon: felt252 = poseidon_hash_span(hash_span.span()).into(); let (d, r) = rshift_split(poseidon.into(), U128_MAX.into()); r.try_into().unwrap() @@ -181,15 +184,15 @@ impl AdventurerUtils of IAdventurerUtils { // @notice gets randomness for adventurer // @param adventurer_xp: adventurer xp // @param adventurer_entropy: adventurer entropy - // @param global_entropy: global entropy + // @param game_entropy: game entropy // @return (u128, u128): tuple of randomness fn get_randomness( - adventurer_xp: u16, adventurer_entropy: u128, global_entropy: u128 + adventurer_xp: u16, adventurer_entropy: u128, game_entropy: u128 ) -> (u128, u128) { let mut hash_span = ArrayTrait::::new(); hash_span.append(adventurer_xp.into()); hash_span.append(adventurer_entropy.into()); - hash_span.append(global_entropy.into()); + hash_span.append(game_entropy.into()); let poseidon = poseidon_hash_span(hash_span.span()); AdventurerUtils::split_hash(poseidon) } @@ -198,16 +201,16 @@ impl AdventurerUtils of IAdventurerUtils { // @param adventurer_xp: adventurer xp // @param adventurer_entropy: adventurer entropy // @param adventurer_health: adventurer health - // @param global_entropy: global entropy + // @param game_entropy: game entropy // @return (u128, u128): tuple of randomness fn get_randomness_with_health( - adventurer_xp: u16, adventurer_health: u16, adventurer_entropy: u128, global_entropy: u128 + adventurer_xp: u16, adventurer_health: u16, adventurer_entropy: u128, game_entropy: u128 ) -> (u128, u128) { let mut hash_span = ArrayTrait::::new(); hash_span.append(adventurer_xp.into()); hash_span.append(adventurer_health.into()); hash_span.append(adventurer_entropy.into()); - hash_span.append(global_entropy.into()); + hash_span.append(game_entropy.into()); let poseidon = poseidon_hash_span(hash_span.span()); AdventurerUtils::split_hash(poseidon) } @@ -305,7 +308,7 @@ mod tests { adventurer_utils::AdventurerUtils }; use combat::constants::CombatEnums::{Type, Tier, Slot}; - #[test] + #[test] #[available_gas(166544)] fn test_generate_starting_stats_gas() { AdventurerUtils::generate_starting_stats(0, 1); @@ -535,8 +538,9 @@ mod tests { } let adventurer_id: u256 = i; let block_number = 839152; + let block_timestamp = 53289712; let adventurer_entropy = AdventurerUtils::generate_adventurer_entropy( - block_number, adventurer_id + adventurer_id, block_number, block_timestamp ); i += 1; }; diff --git a/contracts/game/src/game/interfaces.cairo b/contracts/game/src/game/interfaces.cairo index b30779352..8bb307c0e 100644 --- a/contracts/game/src/game/interfaces.cairo +++ b/contracts/game/src/game/interfaces.cairo @@ -1,27 +1,24 @@ use starknet::ContractAddress; +use market::market::{ItemPurchase}; +use beasts::beast::Beast; use survivor::{ bag::Bag, adventurer::{Adventurer, Stats}, adventurer_meta::AdventurerMetadata, item_meta::{ItemSpecials, ItemSpecialsStorage} }; -use lootitems::loot::{Loot}; -use market::market::{ItemPurchase}; -use beasts::beast::Beast; + #[starknet::interface] trait IGame { - // actions --------------------------------------------------- - fn start( - ref self: TContractState, - client_reward_address: ContractAddress, - starting_weapon: u8, - adventurer_meta: AdventurerMetadata + // ------ Game Actions ------ + fn new_game( + ref self: TContractState, client_reward_address: ContractAddress, weapon: u8, name: u128 ); fn explore(ref self: TContractState, adventurer_id: u256, till_beast: bool); fn attack(ref self: TContractState, adventurer_id: u256, to_the_death: bool); fn flee(ref self: TContractState, adventurer_id: u256, to_the_death: bool); fn equip(ref self: TContractState, adventurer_id: u256, items: Array); - fn drop_items(ref self: TContractState, adventurer_id: u256, items: Array); - fn upgrade_adventurer( + fn drop(ref self: TContractState, adventurer_id: u256, items: Array); + fn upgrade( ref self: TContractState, adventurer_id: u256, potions: u8, @@ -29,9 +26,9 @@ trait IGame { items: Array, ); fn slay_idle_adventurers(ref self: TContractState, adventurer_ids: Array); - fn rotate_global_entropy(ref self: TContractState); + fn rotate_game_entropy(ref self: TContractState); - // --------- view functions --------- + // ------ View Functions ------ // adventurer details fn get_adventurer(self: @TContractState, adventurer_id: u256) -> Adventurer; @@ -54,7 +51,6 @@ trait IGame { fn get_charisma(self: @TContractState, adventurer_id: u256) -> u8; // item stats - // TODO: get_equipped_items(self: @TContractState, adventurer_id: u256) -> Array; fn get_weapon_greatness(self: @TContractState, adventurer_id: u256) -> u8; fn get_chest_greatness(self: @TContractState, adventurer_id: u256) -> u8; fn get_head_greatness(self: @TContractState, adventurer_id: u256) -> u8; @@ -106,14 +102,12 @@ trait IGame { fn get_beast_type(self: @TContractState, beast_id: u8) -> u8; fn get_beast_tier(self: @TContractState, beast_id: u8) -> u8; - // TODO: Game settings - fn next_global_entropy_rotation(self: @TContractState) -> felt252; + // game settings + fn next_game_entropy_rotation(self: @TContractState) -> felt252; // contract details + fn owner_of(self: @TContractState, adventurer_id: u256) -> ContractAddress; fn get_dao_address(self: @TContractState) -> ContractAddress; fn get_lords_address(self: @TContractState) -> ContractAddress; fn get_entropy(self: @TContractState) -> u64; - - // checks ---------------------------------------------------- - fn owner_of(self: @TContractState, adventurer_id: u256) -> ContractAddress; } diff --git a/contracts/game/src/lib.cairo b/contracts/game/src/lib.cairo index 23c86bf16..74304f6ca 100644 --- a/contracts/game/src/lib.cairo +++ b/contracts/game/src/lib.cairo @@ -69,8 +69,8 @@ mod Game { #[storage] struct Storage { - _global_entropy: u64, - _last_global_entropy_block: felt252, + _game_entropy: u64, + _last_game_entropy_block: felt252, _adventurer: LegacyMap::, _owner: LegacyMap::, _adventurer_meta: LegacyMap::, @@ -132,7 +132,7 @@ mod Game { self._genesis_block.write(starknet::get_block_info().unbox().block_number.into()); // set global game entropy - _rotate_global_entropy(ref self); + _rotate_game_entropy(ref self); } // ------------------------------------------ // @@ -141,40 +141,43 @@ mod Game { #[external(v0)] impl Game of IGame { - //@notice Initiates the adventure for an adventurer with specific starting configurations. - //@param self The current state of the contract. - //@param interface_id The address of the specific contract interface. - //@param starting_weapon The initial weapon choice of the adventurer (e.g. wand, book, club, short sword). - //@param adventurer_meta Metadata containing information about the adventurer. - //@param starting_stats The initial statistics of the adventurer. - //@dev Ensures that the chosen starting weapon and stats are valid before beginning the adventure. - fn start( - ref self: ContractState, - client_reward_address: ContractAddress, - starting_weapon: u8, - adventurer_meta: AdventurerMetadata, + /// @title New Game + /// + /// @notice Creates a new game of Loot Survivor + /// @dev The function asserts the provided weapon's validity, starts the game, and distributes rewards. + /// + /// @param client_reward_address Address where client rewards should be sent. + /// @param weapon A u8 representing the weapon to start the game with. Valid options are: {wand: 12, book: 17, short sword: 46, club: 76} + /// @param name A u128 value representing the player's name. + fn new_game( + ref self: ContractState, client_reward_address: ContractAddress, weapon: u8, name: u128, ) { - _assert_valid_starter_weapon(starting_weapon); - - let caller = get_caller_address(); - let block_number = starknet::get_block_info().unbox().block_number; + // assert provided weapon + _assert_valid_starter_weapon(weapon); - _start(ref self, block_number, caller, starting_weapon, adventurer_meta); + // process payment for game and distribute rewards + _process_payment_and_distribute_rewards(ref self, client_reward_address); - _payout(ref self, caller, block_number, client_reward_address); + // start the game + _start_game(ref self, weapon, name); } - //@notice Sends an adventurer to explore. - //@param self The current state of the contract. - //@param adventurer_id The unique identifier of the adventurer. - //@param till_beast Indicates if the adventurer will explore until encountering a beast. + /// @title Explore Function + /// + /// @notice Allows an adventurer to explore + /// + /// @param adventurer_id A u256 representing the ID of the adventurer. + /// @param till_beast A boolean flag indicating if the exploration continues until encountering a beast. fn explore(ref self: ContractState, adventurer_id: u256, till_beast: bool) { // get adventurer from storage with stat boosts applied let (mut adventurer, stat_boosts, _) = _unpack_adventurer_and_bag_with_stat_boosts( @self, adventurer_id ); - // use an immutable adventurer copy for assertions to reduce stack overhead + let adventurer_entropy = _adventurer_meta_unpacked(@self, adventurer_id).entropy; + let game_entropy = _get_game_entropy(@self).into(); + + // use an immutable adventurer for assertions let immutable_adventurer = adventurer.clone(); // assert action is valid @@ -184,44 +187,44 @@ mod Game { _assert_not_in_battle(immutable_adventurer); // get number of blocks between actions - let (exceeded_idle_threshold, num_blocks) = _idle_longer_than_penalty_threshold( - immutable_adventurer - ); + let (idle, num_blocks) = _is_idle(immutable_adventurer); // process explore or apply idle penalty - if (!exceeded_idle_threshold) { + if !idle { _explore( ref self, ref adventurer, adventurer_id, - adventurer_entropy: _adventurer_meta_unpacked(@self, adventurer_id).entropy, - global_entropy: _get_global_entropy(@self).into(), - explore_till_beast: till_beast + adventurer_entropy, + game_entropy, + till_beast ); } else { _apply_idle_penalty(ref self, adventurer_id, ref adventurer, num_blocks); } - // update players last action block number + // update players last action block number to reset idle counter adventurer.set_last_action(starknet::get_block_info().unbox().block_number); - // write the resulting adventurer to storage + // pack and save adventurer _pack_adventurer_remove_stat_boost( ref self, ref adventurer, adventurer_id, stat_boosts ); } - //@notice Initiates an attack action for an adventurer - //@param self A reference to ContractState required to update contract - //@param adventurer_id The unique identifier of the adventurer - //@param to_the_death a bool which if true will result in recursively attacking till beast or adventurer is dead + /// @title Attack Function + /// + /// @notice Allows an adventurer to attack a beast + /// + /// @param adventurer_id A u256 representing the ID of the adventurer. + /// @param to_the_death A boolean flag indicating if the attack should continue until either the adventurer or the beast is defeated. fn attack(ref self: ContractState, adventurer_id: u256, to_the_death: bool) { // get adventurer from storage with stat boosts applied let (mut adventurer, stat_boosts, _) = _unpack_adventurer_and_bag_with_stat_boosts( @self, adventurer_id ); - // use an immutable adventurer copy for assertions to reduce stack overhead + // use an immutable adventurer for assertions let immutable_adventurer = adventurer.clone(); // assert action is valid @@ -230,12 +233,10 @@ mod Game { _assert_in_battle(immutable_adventurer); // get number of blocks between actions - let (exceeded_idle_threshold, num_blocks) = _idle_longer_than_penalty_threshold( - immutable_adventurer - ); + let (idle, num_blocks) = _is_idle(immutable_adventurer); // process attack or apply idle penalty - if (!exceeded_idle_threshold) { + if !idle { // get adventurer entropy from storage let adventurer_entropy: u128 = _adventurer_meta_unpacked(@self, adventurer_id) .entropy @@ -245,7 +246,7 @@ mod Game { let weapon_specials = _get_item_specials(@self, adventurer_id, adventurer.weapon); // get game entropy from storage - let global_entropy: u128 = _get_global_entropy(@self).into(); + let game_entropy: u128 = _get_game_entropy(@self).into(); // get beast and beast seed let (beast, beast_seed) = adventurer.get_beast(adventurer_entropy); @@ -271,7 +272,7 @@ mod Game { adventurer_entropy, beast, beast_seed, - global_entropy, + game_entropy, to_the_death ); } else { @@ -287,17 +288,19 @@ mod Game { ); } - //@notice Attempt to flee from a beast - //@param self A reference to ContractState required to update contract - //@param adventurer_id The unique identifier of the adventurer - //@param to_the_death a bool which if true will result in recursively fleeing till success or death + /// @title Flee Function + /// + /// @notice Allows an adventurer to flee from a beast + /// + /// @param adventurer_id A u256 representing the unique ID of the adventurer. + /// @param to_the_death A boolean flag indicating if the flee attempt should continue until either the adventurer escapes or is defeated. fn flee(ref self: ContractState, adventurer_id: u256, to_the_death: bool) { // get adventurer from storage with stat boosts applied let (mut adventurer, stat_boosts, _) = _unpack_adventurer_and_bag_with_stat_boosts( @self, adventurer_id ); - // use an immutable adventurer copy for assertions to reduce stack overhead + // use an immutable adventurer for assertions let immutable_adventurer = adventurer.clone(); // assert action is valid @@ -308,16 +311,14 @@ mod Game { _assert_dexterity_not_zero(immutable_adventurer); // get number of blocks between actions - let (exceeded_idle_threshold, num_blocks) = _idle_longer_than_penalty_threshold( - immutable_adventurer - ); + let (idle, num_blocks) = _is_idle(immutable_adventurer); - // process flee or apply idle penalty - if (!exceeded_idle_threshold) { - let adventurer_entropy: u128 = _adventurer_meta_unpacked(@self, adventurer_id) - .entropy - .into(); - let global_entropy: u128 = _get_global_entropy(@self).into(); + // if adventurer is not idle + if !idle { + // get adventurer and game entropy + let (adventurer_entropy, game_entropy) = _get_adventurer_and_game_entropy( + @self, adventurer_id + ); // get beast and beast seed let (beast, beast_seed) = adventurer.get_beast(adventurer_entropy); @@ -328,7 +329,7 @@ mod Game { ref adventurer, adventurer_id, adventurer_entropy, - global_entropy, + game_entropy.into(), beast_seed, beast, to_the_death @@ -336,6 +337,7 @@ mod Game { // if adventurer died while attempting to flee if (adventurer.health == 0) { + // process death _process_adventurer_death(ref self, adventurer, adventurer_id, beast.id, 0); } } else { @@ -351,10 +353,13 @@ mod Game { ); } - //@notice Equip adventurer with the specified items. - //@param self The current state of the contract. - //@param adventurer_id The ID of the adventurer to equip. - //@param items An array of items to equip the adventurer with. + /// @title Equip Function + /// + /// @notice Allows an adventurer to equip items from their bag + /// @player Calling this during battle will result in a beast counter-attack + /// + /// @param adventurer_id A u256 representing the unique ID of the adventurer. + /// @param items A u8 array representing the item IDs to equip. fn equip(ref self: ContractState, adventurer_id: u256, items: Array) { // get adventurer and bag from storage with stat boosts applied let (mut adventurer, stat_boosts, mut bag) = @@ -375,14 +380,14 @@ mod Game { if (adventurer.in_battle()) { // the beast counter attacks - let (adventurer_entropy, global_entropy) = _get_adventurer_and_global_entropy( + let (adventurer_entropy, game_entropy) = _get_adventurer_and_game_entropy( @self, adventurer_id ); let (beast, beast_seed) = adventurer.get_beast(adventurer_entropy); let (attack_rnd_1, attack_rnd_2) = AdventurerUtils::get_randomness( - adventurer.xp, adventurer_entropy, global_entropy.into() + adventurer.xp, adventurer_entropy, game_entropy.into() ); let beast_battle_details = _beast_attack( @@ -416,10 +421,13 @@ mod Game { } } - // @notice drops equipped items or items from an adventurer's bag - // @param adventurer_id The ID of the adventurer dropping the items - // @param items A Array of item ids to be dropped - fn drop_items(ref self: ContractState, adventurer_id: u256, items: Array) { + /// @title Drop Function + /// + /// @notice Allows an adventurer to drop equpped items or items from their bag + /// + /// @param adventurer_id A u256 representing the unique ID of the adventurer. + /// @param items A u8 Array representing the IDs of the items to drop. + fn drop(ref self: ContractState, adventurer_id: u256, items: Array) { // get adventurer and bag from storage with stat boosts applied let (mut adventurer, stat_boosts, mut bag) = _unpack_adventurer_and_bag_with_stat_boosts( @@ -432,7 +440,7 @@ mod Game { assert(items.len() != 0, messages::NO_ITEMS); // drop items - _drop_items(ref self, ref adventurer, ref bag, adventurer_id, items.clone()); + _drop(ref self, ref adventurer, ref bag, adventurer_id, items.clone()); // if the adventurer was mutated if (adventurer.mutated) { @@ -452,16 +460,15 @@ mod Game { __event_DroppedItems(ref self, adventurer, adventurer_id, bag, items); } - //@notice Upgrades an adventurer's stats and optionally purchases potions and items. - //@param self The current state of the contract. - //@param adventurer_id The ID of the adventurer to upgrade. - //@param potions The number of potions to purchase. - //@param stat_upgrades The stat upgrades to apply to Adventurer. - //@param items An array of items to purchase as part of the upgrade. - //@dev This function verifies ownership, asserts that the adventurer is not dead or in battle, ensures the market is open, upgrades stats, and processes potions and item purchases. - //@dev Interacts with various internal methods for assert checks, stat upgrades, item purchasing, and event emission. - //@emit AdventurerUpgraded An event that is emitted when the adventurer is successfully upgraded. - fn upgrade_adventurer( + /// @title Upgrade Function + /// + /// @notice Allows an adventurer to upgrade their stats, purchase potions, and buy new items. + /// + /// @param adventurer_id A u256 representing the unique ID of the adventurer. + /// @param potions A u8 representing the number of potions to purchase + /// @param stat_upgrades A Stats struct detailing the upgrades the adventurer wants to apply to their stats. + /// @param items An array of ItemPurchase detailing the items the adventurer wishes to purchase during the upgrade. + fn upgrade( ref self: ContractState, adventurer_id: u256, potions: u8, @@ -483,12 +490,10 @@ mod Game { _assert_valid_stat_selection(immutable_adventurer, stat_upgrades); // get number of blocks between actions - let (exceeded_idle_threshold, num_blocks) = _idle_longer_than_penalty_threshold( - immutable_adventurer - ); + let (idle, num_blocks) = _is_idle(immutable_adventurer); // if adventurer exceeded idle penalty threshold - if exceeded_idle_threshold { + if idle { // apply penalty and return _apply_idle_penalty(ref self, adventurer_id, ref adventurer, num_blocks); return; @@ -535,9 +540,11 @@ mod Game { ); } - // @notice slays an adventurer that has been idle for too long - // @dev Anyone can call this function, so we intentionally don't assert ownership. - // @param adventurer_id The unique identifier for the adventurer to be slayed. + /// @title Slay Idle Adventurers Function + /// + /// @notice Allows anyone to slay idle adventurers + /// + /// @param adventurer_ids: A u256 array representing the IDs of adventurers to slay fn slay_idle_adventurers(ref self: ContractState, adventurer_ids: Array) { let mut adventurer_index: u32 = 0; loop { @@ -550,6 +557,15 @@ mod Game { } } + /// @title Rotate Game Entropy Function + /// + /// @notice Rotates the game entropy + /// @dev This is intentional callable by anyone + /// @players Ideally this is called at the minimum block interval to provide optimal game entropy. If the community does not do this, bots will likely use this to their advantage. + fn rotate_game_entropy(ref self: ContractState) { + _rotate_game_entropy(ref self); + } + // // view functions // @@ -803,32 +819,23 @@ mod Game { fn get_beast_type(self: @ContractState, beast_id: u8) -> u8 { ImplCombat::type_to_u8(ImplBeast::get_type(beast_id)) } - fn get_beast_tier(self: @ContractState, beast_id: u8) -> u8 { ImplCombat::tier_to_u8(ImplBeast::get_tier(beast_id)) } - fn get_dao_address(self: @ContractState) -> ContractAddress { _dao_address(self) } - fn get_lords_address(self: @ContractState) -> ContractAddress { _lords_address(self) } - fn get_entropy(self: @ContractState) -> u64 { - _get_global_entropy(self) - } - - fn rotate_global_entropy(ref self: ContractState) { - _rotate_global_entropy(ref self) + _get_game_entropy(self) } - fn owner_of(self: @ContractState, adventurer_id: u256) -> ContractAddress { _owner_of(self, adventurer_id) } - fn next_global_entropy_rotation(self: @ContractState) -> felt252 { - _next_global_entropy_rotation(self) + fn next_game_entropy_rotation(self: @ContractState) -> felt252 { + _next_game_entropy_rotation(self) } } @@ -970,15 +977,16 @@ mod Game { amount * 10 ^ 18 } - fn _payout( - ref self: ContractState, - caller: ContractAddress, - block_number: u64, - client_address: ContractAddress + fn _process_payment_and_distribute_rewards( + ref self: ContractState, client_address: ContractAddress ) { + let caller = get_caller_address(); + let block_number = starknet::get_block_info().unbox().block_number; + let lords = self._lords.read(); let genesis_block = self._genesis_block.read(); let dao_address = self._dao.read(); + let first_place_adventurer_id = self._scoreboard.read(1); let first_place_address = self._owner.read(first_place_adventurer_id); let second_place_adventurer_id = self._scoreboard.read(2); @@ -1098,52 +1106,44 @@ mod Game { ); } - fn _start( - ref self: ContractState, - block_number: u64, - caller: ContractAddress, - starting_weapon: u8, - adventurer_meta: AdventurerMetadata - ) { + fn _start_game(ref self: ContractState, weapon: u8, name: u128) { // increment adventurer id (first adventurer is id 1) let adventurer_id = self._counter.read() + 1; - let adventurer_entropy = AdventurerUtils::generate_adventurer_entropy( - block_number, adventurer_id + // use current starknet block number and timestamp as entropy sources + let block_number = starknet::get_block_info().unbox().block_number; + let block_timestamp = starknet::get_block_info().unbox().block_timestamp; + + // generate entropy seed for adventurer + let entropy = AdventurerUtils::generate_adventurer_entropy( + adventurer_id, block_number, block_timestamp ); // generate a new adventurer using the provided started weapon and current block number - let mut new_adventurer: Adventurer = ImplAdventurer::new( - starting_weapon, NUM_STARTING_STATS, block_number, adventurer_entropy - ); + let mut adventurer = ImplAdventurer::new(weapon, NUM_STARTING_STATS, block_number, entropy); // set entropy on adventurer metadata - let adventurer_meta = AdventurerMetadata { - name: adventurer_meta.name, - home_realm: adventurer_meta.home_realm, - class: adventurer_meta.class, - entropy: adventurer_entropy - }; + let adventurer_meta = AdventurerMetadata { name, entropy }; // emit a StartGame event - __event_StartGame(ref self, new_adventurer, adventurer_id, adventurer_meta); + __event_StartGame(ref self, adventurer, adventurer_id, adventurer_meta); // adventurer immediately gets ambushed by a starter beast let beast_battle_details = _starter_beast_ambush( - ref new_adventurer, adventurer_id, starting_weapon, adventurer_entropy + ref adventurer, adventurer_id, weapon, entropy ); - __event_AmbushedByBeast(ref self, new_adventurer, adventurer_id, beast_battle_details); + __event_AmbushedByBeast(ref self, adventurer, adventurer_id, beast_battle_details); // pack and save new adventurer and metadata - _pack_adventurer(ref self, adventurer_id, new_adventurer); + _pack_adventurer(ref self, adventurer_id, adventurer); _pack_adventurer_meta(ref self, adventurer_id, adventurer_meta); // increment the adventurer id counter self._counter.write(adventurer_id); // set caller as owner - self._owner.write(adventurer_id, caller); + self._owner.write(adventurer_id, get_caller_address()); } fn _starter_beast_ambush( @@ -1184,12 +1184,12 @@ mod Game { ref adventurer: Adventurer, adventurer_id: u256, adventurer_entropy: u128, - global_entropy: u128, + game_entropy: u128, explore_till_beast: bool ) { // generate randomenss for exploration let (rnd1, rnd2) = AdventurerUtils::get_randomness( - adventurer.xp, adventurer_entropy, global_entropy + adventurer.xp, adventurer_entropy, game_entropy ); // go exploring @@ -1213,7 +1213,7 @@ mod Game { ref adventurer, adventurer_id, adventurer_entropy, - global_entropy, + game_entropy, explore_till_beast ); } @@ -1499,7 +1499,7 @@ mod Game { /// @param adventurer_entropy A random value tied to the adventurer to aid in determining certain random aspects of the combat /// @param beast The defending beast /// @param beast_seed The seed associated with the beast - /// @param global_entropy A random value used globally in determining certain random aspects of the combat + /// @param game_entropy A random value used globally in determining certain random aspects of the combat /// @param fight_to_the_death Flag to indicate whether the adventurer should continue attacking until either they or the beast is defeated fn _attack( ref self: ContractState, @@ -1509,12 +1509,12 @@ mod Game { adventurer_entropy: u128, beast: Beast, beast_seed: u128, - global_entropy: u128, + game_entropy: u128, fight_to_the_death: bool, ) { // get two random numbers using adventurer xp and health as part of entropy let (rnd1, rnd2) = AdventurerUtils::get_randomness_with_health( - adventurer.xp, adventurer.health, adventurer_entropy, global_entropy + adventurer.xp, adventurer.health, adventurer_entropy, game_entropy ); // attack beast and get combat result that provides damage breakdown @@ -1578,7 +1578,7 @@ mod Game { adventurer_entropy, beast, beast_seed, - global_entropy, + game_entropy, true ); } @@ -1636,7 +1636,7 @@ mod Game { // @param adventurer The adventurer attempting to flee. // @param adventurer_id The unique ID of the adventurer. // @param adventurer_entropy The entropy related to the adventurer used for generating the beast. - // @param global_entropy The global entropy value. + // @param game_entropy The game entropy value. // @param beast_seed The seed related to the beast. // @param beast The beast that the adventurer is attempting to flee from. // @param flee_to_the_death Flag to indicate if the flee attempt should continue until either success or the adventurer's defeat. @@ -1645,14 +1645,14 @@ mod Game { ref adventurer: Adventurer, adventurer_id: u256, adventurer_entropy: u128, - global_entropy: u128, + game_entropy: u128, beast_seed: u128, beast: Beast, flee_to_the_death: bool ) { // get flee and ambush entropy seeds let (flee_entropy, ambush_entropy) = AdventurerUtils::get_randomness_with_health( - adventurer.xp, adventurer.health, adventurer_entropy, global_entropy + adventurer.xp, adventurer.health, adventurer_entropy, game_entropy ); // attempt to flee @@ -1700,7 +1700,7 @@ mod Game { ref adventurer, adventurer_id, adventurer_entropy, - global_entropy, + game_entropy, beast_seed, beast, true @@ -1803,7 +1803,7 @@ mod Game { // @param adventurer_id The identifier of the adventurer // @param items The list of items to be dropped // @return A tuple containing two boolean values. The first indicates if the adventurer was mutated, the second indicates if the bag was mutated - fn _drop_items( + fn _drop( ref self: ContractState, ref adventurer: Adventurer, ref bag: Bag, @@ -2045,6 +2045,32 @@ mod Game { self._adventurer.write(adventurer_id, adventurer.pack()); } + /// @title Internal Rotate Game Entropy Function + /// + /// @notice Rotates the game's entropy based on the current block information. + /// @dev This function checks that the minimum blocks have elapsed since the last rotation before proceeding. + /// Uses the Poseidon hash function for the entropy generation. + fn _rotate_game_entropy(ref self: ContractState) { + let blocknumber: u64 = starknet::get_block_info().unbox().block_number.into(); + let timestamp: u64 = starknet::get_block_info().unbox().block_timestamp.into(); + + assert( + blocknumber >= (self._last_game_entropy_block.read().try_into().unwrap() + + MIN_BLOCKS_FOR_GAME_ENTROPY_CHANGE.into()), + messages::BLOCK_NUMBER_ERROR + ); + + let mut hash_span = ArrayTrait::::new(); + hash_span.append(blocknumber.into()); + hash_span.append(timestamp.into()); + + let poseidon: felt252 = poseidon_hash_span(hash_span.span()).into(); + let (d, r) = rshift_split(poseidon.into(), U64_MAX.into()); + + self._game_entropy.write(r.try_into().unwrap()); + self._last_game_entropy_block.write(blocknumber.into()); + } + // @notice This function emits events relevant to adventurer leveling up // @dev In Loot Survivor, leveling up provides stat upgrades and access to the market // @param ref self A reference to the contract state. @@ -2139,8 +2165,8 @@ mod Game { self._owner.read(adventurer_id) } #[inline(always)] - fn _next_global_entropy_rotation(self: @ContractState) -> felt252 { - self._last_global_entropy_block.read() + MIN_BLOCKS_FOR_GAME_ENTROPY_CHANGE.into() + fn _next_game_entropy_rotation(self: @ContractState) -> felt252 { + self._last_game_entropy_block.read() + MIN_BLOCKS_FOR_GAME_ENTROPY_CHANGE.into() } fn _assert_ownership(self: @ContractState, adventurer_id: u256) { assert(self._owner.read(adventurer_id) == get_caller_address(), messages::NOT_OWNER); @@ -2238,11 +2264,11 @@ mod Game { } fn _assert_is_idle(adventurer: Adventurer) { - let (is_idle, _) = _idle_longer_than_penalty_threshold(adventurer); + let (is_idle, _) = _is_idle(adventurer); assert(is_idle, messages::ADVENTURER_NOT_IDLE); } - fn _idle_longer_than_penalty_threshold(adventurer: Adventurer) -> (bool, u16) { + fn _is_idle(adventurer: Adventurer) -> (bool, u16) { let idle_blocks = adventurer .get_idle_blocks(starknet::get_block_info().unbox().block_number); @@ -2401,32 +2427,9 @@ mod Game { } } - fn _rotate_global_entropy(ref self: ContractState) { - // let hash: felt252 = starknet::get_tx_info().unbox().transaction_hash.into(); - - let blocknumber: u64 = starknet::get_block_info().unbox().block_number.into(); - let timestamp: u64 = starknet::get_block_info().unbox().block_timestamp.into(); - - assert( - blocknumber >= (self._last_global_entropy_block.read().try_into().unwrap() - + MIN_BLOCKS_FOR_GAME_ENTROPY_CHANGE.into()), - messages::BLOCK_NUMBER_ERROR - ); - - let mut hash_span = ArrayTrait::::new(); - hash_span.append(blocknumber.into()); - hash_span.append(timestamp.into()); - - let poseidon: felt252 = poseidon_hash_span(hash_span.span()).into(); - let (d, r) = rshift_split(poseidon.into(), U64_MAX.into()); - - self._global_entropy.write(r.try_into().unwrap()); - self._last_global_entropy_block.write(blocknumber.into()); - } - #[inline(always)] - fn _get_global_entropy(self: @ContractState) -> u64 { - self._global_entropy.read() + fn _get_game_entropy(self: @ContractState) -> u64 { + self._game_entropy.read() } #[inline(always)] @@ -2434,15 +2437,13 @@ mod Game { _adventurer_meta_unpacked(self, adventurer_id).entropy.into() } - // @notice _get_adventurer_and_global_entropy returns the adventurer entropy and global entropy + // @notice _get_adventurer_and_game_entropy returns the adventurer entropy and game entropy // @param self - read-only reference to the contract state // @param adventurer_id - the id of the adventurer - // @return (u128, u64) - adventurer entropy and global entropy + // @return (u128, u64) - adventurer entropy and game entropy #[inline(always)] - fn _get_adventurer_and_global_entropy( - self: @ContractState, adventurer_id: u256 - ) -> (u128, u64) { - (_get_adventurer_entropy(self, adventurer_id), _get_global_entropy(self)) + fn _get_adventurer_and_game_entropy(self: @ContractState, adventurer_id: u256) -> (u128, u64) { + (_get_adventurer_entropy(self, adventurer_id), _get_game_entropy(self)) } #[inline(always)] diff --git a/contracts/game/src/tests/test_game.cairo b/contracts/game/src/tests/test_game.cairo index 7f4894098..dfdd4c542 100644 --- a/contracts/game/src/tests/test_game.cairo +++ b/contracts/game/src/tests/test_game.cairo @@ -98,11 +98,7 @@ mod tests { } fn add_adventurer_to_game(ref game: IGameDispatcher) { - let adventurer_meta = AdventurerMetadata { - name: 'loothero'.try_into().unwrap(), home_realm: 1, class: 1, entropy: 1 - }; - - game.start(INTERFACE_ID(), ItemId::Wand, adventurer_meta); + game.new_game(INTERFACE_ID(), ItemId::Wand, 'loothero'); let original_adventurer = game.get_adventurer(ADVENTURER_ID); assert(original_adventurer.xp == 0, 'wrong starting xp'); @@ -116,17 +112,22 @@ mod tests { fn new_adventurer(starting_block: u64) -> IGameDispatcher { let mut game = setup(starting_block); - let adventurer_meta = AdventurerMetadata { - name: 'Loaf'.try_into().unwrap(), home_realm: 1, class: 1, entropy: 1 - }; + let starting_weapon = ItemId::Wand; + let name = 'abcdefghijklmno'; - game.start(INTERFACE_ID(), ItemId::Wand, adventurer_meta); + // start new game + game.new_game(INTERFACE_ID(), starting_weapon, name); - let original_adventurer = game.get_adventurer(ADVENTURER_ID); - assert(original_adventurer.xp == 0, 'wrong starting xp'); - assert(original_adventurer.weapon.id == ItemId::Wand, 'wrong starting weapon'); + // get adventurer state + let adventurer = game.get_adventurer(ADVENTURER_ID); + let adventurer_meta_data = game.get_adventurer_meta(ADVENTURER_ID); + + // verify starting weapon + assert(adventurer.weapon.id == starting_weapon, 'wrong starting weapon'); + assert(adventurer_meta_data.name == name, 'wrong player name'); + assert(adventurer.xp == 0, 'should start with 0 xp'); assert( - original_adventurer.beast_health == BeastSettings::STARTER_BEAST_HEALTH, + adventurer.beast_health == BeastSettings::STARTER_BEAST_HEALTH, 'wrong starter beast health ' ); @@ -135,16 +136,7 @@ mod tests { fn new_adventurer_max_charisma() -> IGameDispatcher { let mut game = setup(1000); - - game - .start( - INTERFACE_ID(), - ItemId::Wand, - AdventurerMetadata { - name: 'loothero'.try_into().unwrap(), home_realm: 1, class: 1, entropy: 1 - } - ); - + game.new_game(INTERFACE_ID(), ItemId::Wand, 'loothero'); game } @@ -210,9 +202,15 @@ mod tests { let shopping_cart = ArrayTrait::::new(); let stat_upgrades = Stats { - strength: stat, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 + strength: stat, + dexterity: 0, + vitality: 0, + intelligence: 0, + wisdom: 0, + charisma: 1, + luck: 0 }; - game.upgrade_adventurer(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); + game.upgrade(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); // go explore game.explore(ADVENTURER_ID, true); @@ -237,9 +235,15 @@ mod tests { // upgrade charisma let shopping_cart = ArrayTrait::::new(); let stat_upgrades = Stats { - strength: stat, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 + strength: stat, + dexterity: 0, + vitality: 0, + intelligence: 0, + wisdom: 0, + charisma: 1, + luck: 0 }; - game.upgrade_adventurer(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); + game.upgrade(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); // go explore game.explore(ADVENTURER_ID, true); @@ -260,9 +264,15 @@ mod tests { // upgrade charisma let shopping_cart = ArrayTrait::::new(); let stat_upgrades = Stats { - strength: stat, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 + strength: stat, + dexterity: 0, + vitality: 0, + intelligence: 0, + wisdom: 0, + charisma: 1, + luck: 0 }; - game.upgrade_adventurer(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); + game.upgrade(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); // go explore game.explore(ADVENTURER_ID, true); @@ -321,9 +331,15 @@ mod tests { // upgrade stats let stat_upgrades = Stats { - strength: stat, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 + strength: stat, + dexterity: 0, + vitality: 0, + intelligence: 0, + wisdom: 0, + charisma: 1, + luck: 0 }; - game.upgrade_adventurer(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); + game.upgrade(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); // go explore game.explore(ADVENTURER_ID, true); @@ -340,9 +356,15 @@ mod tests { let STRENGTH: u8 = 0; let shopping_cart = ArrayTrait::::new(); let stat_upgrades = Stats { - strength: stat, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 + strength: stat, + dexterity: 0, + vitality: 0, + intelligence: 0, + wisdom: 0, + charisma: 1, + luck: 0 }; - game.upgrade_adventurer(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); + game.upgrade(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); // go explore game.explore(ADVENTURER_ID, true); @@ -358,9 +380,15 @@ mod tests { let STRENGTH: u8 = 0; let shopping_cart = ArrayTrait::::new(); let stat_upgrades = Stats { - strength: stat, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 + strength: stat, + dexterity: 0, + vitality: 0, + intelligence: 0, + wisdom: 0, + charisma: 1, + luck: 0 }; - game.upgrade_adventurer(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); + game.upgrade(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); // go explore game.explore(ADVENTURER_ID, true); @@ -376,9 +404,15 @@ mod tests { let STRENGTH: u8 = 0; let shopping_cart = ArrayTrait::::new(); let stat_upgrades = Stats { - strength: stat, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 + strength: stat, + dexterity: 0, + vitality: 0, + intelligence: 0, + wisdom: 0, + charisma: 1, + luck: 0 }; - game.upgrade_adventurer(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); + game.upgrade(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); // go explore game.explore(ADVENTURER_ID, true); @@ -394,9 +428,15 @@ mod tests { let STRENGTH: u8 = 0; let shopping_cart = ArrayTrait::::new(); let stat_upgrades = Stats { - strength: stat, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 + strength: stat, + dexterity: 0, + vitality: 0, + intelligence: 0, + wisdom: 0, + charisma: 1, + luck: 0 }; - game.upgrade_adventurer(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); + game.upgrade(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); // go explore game.explore(ADVENTURER_ID, true); @@ -415,9 +455,15 @@ mod tests { let STRENGTH: u8 = 0; let shopping_cart = ArrayTrait::::new(); let stat_upgrades = Stats { - strength: stat, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 + strength: stat, + dexterity: 0, + vitality: 0, + intelligence: 0, + wisdom: 0, + charisma: 1, + luck: 0 }; - game.upgrade_adventurer(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); + game.upgrade(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); // go explore game.explore(ADVENTURER_ID, true); @@ -445,16 +491,7 @@ mod tests { let adventurer_1 = game.get_adventurer(ADVENTURER_ID); let adventurer_meta_1 = game.get_adventurer_meta(ADVENTURER_ID); - // check adventurer - assert(adventurer_1.weapon.id == ItemId::Wand, 'weapon'); - assert(adventurer_1.beast_health != 0, 'beast_health'); - - // check meta - assert(adventurer_meta_1.name == 'Loaf', 'name'); - assert(adventurer_meta_1.home_realm == 1, 'home_realm'); - assert(adventurer_meta_1.class == 1, 'class'); - adventurer_meta_1.entropy; } #[test] #[should_panic(expected: ('Action not allowed in battle', 'ENTRYPOINT_FAILED'))] @@ -559,28 +596,20 @@ mod tests { #[test] #[available_gas(13000000000)] fn test_flee() { - let mut game = new_adventurer_lvl2(); - let updated_adventurer = game.get_adventurer(ADVENTURER_ID); - assert(updated_adventurer.beast_health == 0, 'beast should be dead'); + // start game on level 2 + let mut game = new_adventurer_lvl2(); - // explore till we find a beast - // TODO: use cheat codes to make this less fragile + // perform upgrade let shopping_cart = ArrayTrait::::new(); let stat_upgrades = Stats { strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 }; - game.upgrade_adventurer(ADVENTURER_ID, 0, stat_upgrades, shopping_cart.clone()); + game.upgrade(ADVENTURER_ID, 0, stat_upgrades, shopping_cart.clone()); + // go exploring testing::set_block_number(1006); game.explore(ADVENTURER_ID, true); - game.upgrade_adventurer(ADVENTURER_ID, 0, stat_upgrades, shopping_cart.clone()); - testing::set_block_number(1007); - game.explore(ADVENTURER_ID, true); - game.upgrade_adventurer(ADVENTURER_ID, 0, stat_upgrades, shopping_cart.clone()); - game.explore(ADVENTURER_ID, true); - game.upgrade_adventurer(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); - game.explore(ADVENTURER_ID, true); // verify we found a beast let updated_adventurer = game.get_adventurer(ADVENTURER_ID); @@ -634,7 +663,7 @@ mod tests { let stat_upgrades = Stats { strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 }; - game.upgrade_adventurer(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); + game.upgrade(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); } #[test] @@ -656,11 +685,11 @@ mod tests { let stat_upgrades = Stats { strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 }; - game.upgrade_adventurer(ADVENTURER_ID, 0, stat_upgrades, empty_shoppping_cart.clone()); + game.upgrade(ADVENTURER_ID, 0, stat_upgrades, empty_shoppping_cart.clone()); // after upgrade try to buy item // should panic with message 'Market is closed' - game.upgrade_adventurer(ADVENTURER_ID, 0, stat_upgrades, shoppping_cart); + game.upgrade(ADVENTURER_ID, 0, stat_upgrades, shoppping_cart); } #[test] @@ -684,7 +713,7 @@ mod tests { let stat_upgrades = Stats { strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 }; - game.upgrade_adventurer(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); + game.upgrade(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); } #[test] @@ -707,7 +736,7 @@ mod tests { let stat_upgrades = Stats { strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 }; - game.upgrade_adventurer(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); + game.upgrade(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); } #[test] @@ -720,7 +749,7 @@ mod tests { let stat_upgrades = Stats { strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 }; - game.upgrade_adventurer(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); + game.upgrade(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); } #[test] @@ -734,7 +763,7 @@ mod tests { let stat_upgrades = Stats { strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 }; - game.upgrade_adventurer(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); + game.upgrade(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); let bag = game.get_bag(ADVENTURER_ID); assert(bag.item_1.id == *market_items.at(0), 'item should be in bag'); } @@ -808,7 +837,7 @@ mod tests { let stat_upgrades = Stats { strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 }; - game.upgrade_adventurer(ADVENTURER_ID, 0, stat_upgrades, shopping_cart.clone()); + game.upgrade(ADVENTURER_ID, 0, stat_upgrades, shopping_cart.clone()); // get updated adventurer and bag state let bag = game.get_bag(ADVENTURER_ID); @@ -980,7 +1009,7 @@ mod tests { let stat_upgrades = Stats { strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 }; - game.upgrade_adventurer(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); + game.upgrade(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); // get bag from storage let bag = game.get_bag(ADVENTURER_ID); @@ -1018,7 +1047,7 @@ mod tests { // verify they are no longer in bag assert(!contains, 'item should not be in bag'); // and equipped on the adventurer - assert(adventurer.is_equipped(*purchased_items_span.at(i)), 'item should be equipped'); + assert(adventurer.is_equipped(*purchased_items_span.at(i)), 'item should be equipped1'); i += 1; }; } @@ -1041,7 +1070,7 @@ mod tests { let stat_upgrades = Stats { strength: 1, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 0, luck: 0 }; - game.upgrade_adventurer(ADVENTURER_ID, number_of_potions, stat_upgrades, shopping_cart); + game.upgrade(ADVENTURER_ID, number_of_potions, stat_upgrades, shopping_cart); // get updated adventurer stat let adventurer = game.get_adventurer(ADVENTURER_ID); @@ -1083,7 +1112,7 @@ mod tests { let stat_upgrades = Stats { strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 }; - game.upgrade_adventurer(ADVENTURER_ID, potions, stat_upgrades, shopping_cart); + game.upgrade(ADVENTURER_ID, potions, stat_upgrades, shopping_cart); } #[test] @@ -1098,11 +1127,11 @@ mod tests { let stat_upgrades = Stats { strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 }; - game.upgrade_adventurer(ADVENTURER_ID, 0, stat_upgrades, shopping_cart.clone()); + game.upgrade(ADVENTURER_ID, 0, stat_upgrades, shopping_cart.clone()); // then try to buy potions (should panic with 'Market is closed') let potions = 1; - game.upgrade_adventurer(ADVENTURER_ID, potions, stat_upgrades, shopping_cart); + game.upgrade(ADVENTURER_ID, potions, stat_upgrades, shopping_cart); } #[test] @@ -1120,7 +1149,7 @@ mod tests { let stat_upgrades = Stats { strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 }; - game.upgrade_adventurer(ADVENTURER_ID, potions, stat_upgrades, shopping_cart); + game.upgrade(ADVENTURER_ID, potions, stat_upgrades, shopping_cart); } #[test] @@ -1643,7 +1672,7 @@ mod tests { let stat_upgrades = Stats { strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 }; - game.upgrade_adventurer(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); + game.upgrade(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); // get adventurer state let adventurer = game.get_adventurer(ADVENTURER_ID); @@ -1662,7 +1691,7 @@ mod tests { drop_list.append(purchased_item_id); // call contract drop - game.drop_items(ADVENTURER_ID, drop_list); + game.drop(ADVENTURER_ID, drop_list); // get adventurer state let adventurer = game.get_adventurer(ADVENTURER_ID); @@ -1692,7 +1721,7 @@ mod tests { // try to drop an item the adventurer doesn't own // this should result in a panic 'Item not owned by adventurer' // this test is annotated to expect that panic - game.drop_items(ADVENTURER_ID, drop_list); + game.drop(ADVENTURER_ID, drop_list); } #[test] @@ -1713,7 +1742,7 @@ mod tests { let stat_upgrades = Stats { strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 }; - game.upgrade_adventurer(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); + game.upgrade(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); // get update adventurer state let adventurer = game.get_adventurer(ADVENTURER_ID); @@ -1740,7 +1769,7 @@ mod tests { let stat_upgrades = Stats { strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 2, luck: 0 }; - game.upgrade_adventurer(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); + game.upgrade(ADVENTURER_ID, 0, stat_upgrades, shopping_cart); } #[test] @@ -1754,29 +1783,25 @@ mod tests { let original_charisma = adventurer.stats.charisma; let original_health = adventurer.health; - // potions + // potion purchases let potions = 1; - // items to purchase - let market_items = @game.get_items_on_market(ADVENTURER_ID); - let item_1 = *market_items.at(0); - let item_2 = *market_items.at(1); + // item purchases + let weapon_on_market = @game.get_items_on_market_by_slot(ADVENTURER_ID, 3); + let weapon_id = *weapon_on_market.at(0); + let chest_armor_on_market = @game.get_items_on_market_by_slot(ADVENTURER_ID, 2); + let chest_armor_id = *chest_armor_on_market.at(0); let mut items_to_purchase = ArrayTrait::::new(); - items_to_purchase.append(ItemPurchase { item_id: item_1, equip: true }); - items_to_purchase.append(ItemPurchase { item_id: item_2, equip: true }); + items_to_purchase.append(ItemPurchase { item_id: weapon_id, equip: true }); + items_to_purchase.append(ItemPurchase { item_id: chest_armor_id, equip: false }); - let strength = 0; - let dexterity = 0; - let vitality = 0; - let intelligence = 0; - let wisdom = 0; - let charisma = 1; - - // purchase potions, items, and upgrade stat in single call + // stat upgrades let stat_upgrades = Stats { strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 1, luck: 0 }; - game.upgrade_adventurer(ADVENTURER_ID, potions, stat_upgrades, items_to_purchase); + + // call upgrade + game.upgrade(ADVENTURER_ID, potions, stat_upgrades, items_to_purchase); // get updated adventurer state let adventurer = game.get_adventurer(ADVENTURER_ID); @@ -1787,9 +1812,8 @@ mod tests { assert(adventurer.stats.charisma == original_charisma + 1, 'charisma not increased'); // assert stat point was used assert(adventurer.stat_points_available == 0, 'should have used stat point'); - // assert adventurer has the purchased items - assert(adventurer.is_equipped(item_1), 'item should be equipped'); - assert(adventurer.is_equipped(item_2), 'item should be equipped'); + assert(adventurer.is_equipped(weapon_id), 'weapon should be equipped'); + assert(!adventurer.is_equipped(chest_armor_id), 'chest armor should not'); } } diff --git a/contracts/pack/src/pack.cairo b/contracts/pack/src/pack.cairo index 8afdccb7d..48fc92002 100644 --- a/contracts/pack/src/pack.cairo +++ b/contracts/pack/src/pack.cairo @@ -7,13 +7,9 @@ trait Packing { fn unpack(packed: felt252) -> T; } -//#[inline(always)] +#[inline(always)] fn rshift_split(value: u256, bits: u256) -> (u256, u256) { - // temporary commented out until 0.12.1 when u256_safe_divmod is an allowed libfunc - // integer::U256DivRem::div_rem(value, bits.try_into().expect('0 bits')) - let value = integer::u512 { limb0: value.low, limb1: value.high, limb2: 0, limb3: 0 }; - let (q, r) = integer::u512_safe_div_rem_by_u256(value, bits.try_into().expect('0 bits')); - (u256 { low: q.limb0, high: q.limb1 }, r) + integer::U256DivRem::div_rem(value, bits.try_into().expect('0 bits')) } #[cfg(test)] @@ -22,7 +18,7 @@ mod tests { use pack::constants::pow; #[test] - #[available_gas(10000000)] + #[available_gas(81450)] fn test_rshift_split_pass() { let v = 0b11010101; @@ -48,7 +44,7 @@ mod tests { } #[test] - #[available_gas(10000000)] + #[available_gas(11750)] #[should_panic] fn test_rshift_split_0() { rshift_split(0b1101, 0);