diff --git a/contracts/adventurer/src/adventurer.cairo b/contracts/adventurer/src/adventurer.cairo index ef4bdf790..64c0cdf96 100644 --- a/contracts/adventurer/src/adventurer.cairo +++ b/contracts/adventurer/src/adventurer.cairo @@ -25,7 +25,8 @@ use super::{ MAX_PACKABLE_BEAST_HEALTH, MAX_LAST_ACTION_BLOCK }, discovery_constants::DiscoveryEnums::{ExploreResult, DiscoveryType} - } + }, + stats::{StatUtils} }; use lootitems::{ loot::{Loot, ILoot, ImplLoot}, @@ -43,7 +44,7 @@ struct Adventurer { last_action_block: u16, // 9 bits health: u16, // 9 bits xp: u16, // 13 bits - stats: Stats, // 30 bits + stats: Stats, // 24 bits gold: u16, // 9 bits weapon: ItemPrimitive, // 21 bits chest: ItemPrimitive, // 21 bits @@ -134,44 +135,34 @@ impl AdventurerPacking of StorePacking { #[generate_trait] impl ImplAdventurer of IAdventurer { - // create a new adventurer from a starting item and a block number - // the block number is used to set the last action - // the starting item is used to set the starting weapon - // @param starting_item: the id of the starting item - // @param block_number: the block number of the block that the adventurer was created in - // @return Adventurer: the new adventurer - fn new( - starting_item: u8, num_starting_stats: u8, block_number: u64, entropy: u128 - ) -> Adventurer { - let current_block_modulo_512: u16 = (block_number % MAX_ADVENTURER_BLOCKS.into()) - .try_into() - .unwrap(); - - let starting_stats = AdventurerUtils::generate_starting_stats(entropy, num_starting_stats); - - let mut adventurer = Adventurer { - last_action_block: current_block_modulo_512, + /// @title Adventurer Creation Function + /// @notice This function initializes and returns a new Adventurer struct. + /// + /// @dev The function takes a `u8` parameter for the starting weapon item and + /// initializes various character stats and items with default and provided values. + /// + /// @param starting_item The ID of the starting weapon item. + /// @return An Adventurer struct initialized with default and provided values. + fn new(starting_item: u8) -> Adventurer { + Adventurer { + last_action_block: 0, health: STARTING_HEALTH, xp: 0, - stats: starting_stats, + stats: StatUtils::new(), gold: STARTING_GOLD, - weapon: ItemPrimitive { id: starting_item, xp: 0, metadata: 1, }, - chest: ItemPrimitive { id: 0, xp: 0, metadata: 0, }, - head: ItemPrimitive { id: 0, xp: 0, metadata: 0, }, - waist: ItemPrimitive { id: 0, xp: 0, metadata: 0, }, - foot: ItemPrimitive { id: 0, xp: 0, metadata: 0, }, - hand: ItemPrimitive { id: 0, xp: 0, metadata: 0, }, - neck: ItemPrimitive { id: 0, xp: 0, metadata: 0, }, - ring: ItemPrimitive { id: 0, xp: 0, metadata: 0, }, + weapon: ItemPrimitive { id: starting_item, xp: 0, metadata: 1 }, + chest: ItemPrimitive { id: 0, xp: 0, metadata: 0 }, + head: ItemPrimitive { id: 0, xp: 0, metadata: 0 }, + waist: ItemPrimitive { id: 0, xp: 0, metadata: 0 }, + foot: ItemPrimitive { id: 0, xp: 0, metadata: 0 }, + hand: ItemPrimitive { id: 0, xp: 0, metadata: 0 }, + neck: ItemPrimitive { id: 0, xp: 0, metadata: 0 }, + ring: ItemPrimitive { id: 0, xp: 0, metadata: 0 }, beast_health: BeastSettings::STARTER_BEAST_HEALTH, stat_points_available: 0, actions_per_block: 0, mutated: false, - }; - - // set adventurers health to max which will compensate for starting for vitality - adventurer.health = AdventurerUtils::get_max_health(adventurer.stats.vitality); - adventurer + } } // @notice Calculates the charisma potion discount for the adventurer based on their charisma stat. @@ -280,7 +271,7 @@ impl ImplAdventurer of IAdventurer { ImplCombat::get_level_from_xp(self.xp) } - fn get_beast(self: Adventurer, adventurer_entropy: u128) -> (Beast, u128) { + fn get_beast(self: Adventurer, adventurer_entropy: felt252) -> (Beast, u128) { let beast_seed: u128 = self.get_beast_seed(adventurer_entropy); let adventurer_level = self.get_level(); @@ -967,11 +958,11 @@ impl ImplAdventurer of IAdventurer { // @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 { + fn get_beast_seed(self: Adventurer, adventurer_entropy: felt252) -> u128 { if self.get_level() > 1 { let mut hash_span = ArrayTrait::new(); hash_span.append(self.xp.into()); - hash_span.append(adventurer_entropy.into()); + hash_span.append(adventurer_entropy); let poseidon = poseidon_hash_span(hash_span.span()); let (d, r) = integer::U256DivRem::div_rem( poseidon.into(), u256_try_as_non_zero(U128_MAX.into()).unwrap() @@ -1735,6 +1726,18 @@ impl ImplAdventurer of IAdventurer { assert(self.last_action_block <= MAX_LAST_ACTION_BLOCK, 'last action block overflow'); assert(self.actions_per_block <= MAX_ACTIONS_PER_BLOCK, 'actions per block overflow'); } + + /// @title Entropy Generation Function + /// @notice Generates a deterministic hash value based on an adventurer ID and a starting hash. + /// @param adventurer_id A unique identifier of the adventurer. + /// @param start_hash An initial hash value to be used in generating the resultant entropy. + /// @return A `felt252` hash value generated by hashing the concatenated input values. + fn get_entropy(adventurer_id: felt252, start_hash: felt252) -> felt252 { + let mut hash_span = ArrayTrait::new(); + hash_span.append(adventurer_id); + hash_span.append(start_hash); + poseidon_hash_span(hash_span.span()) + } } const TWO_POW_3: u256 = 0x8; @@ -1799,7 +1802,7 @@ mod tests { #[test] #[available_gas(184194)] fn test_jewelry_gold_bonus_gas() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0,); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); adventurer.ring.jewelry_gold_bonus(1); } @@ -1807,7 +1810,7 @@ mod tests { #[test] #[available_gas(1914024)] fn test_jewelry_gold_bonus() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); let base_gold_amount = 100; // no gold ring equipped gets no bonus @@ -2539,7 +2542,7 @@ mod tests { #[test] #[available_gas(275934)] fn test_get_beast_seed_gas() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); let adventurer_entropy = 1; ImplAdventurer::get_beast_seed(adventurer, adventurer_entropy); adventurer.xp = 100; @@ -2549,7 +2552,7 @@ mod tests { #[test] #[available_gas(1064170)] fn test_get_beast() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); let entropy = 1; // check new adventurer (level 1) gets a starter beast @@ -2703,7 +2706,7 @@ mod tests { #[test] #[available_gas(217684)] fn test_set_last_action_block() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); adventurer.set_last_action_block(0); assert(adventurer.last_action_block == 0, 'last action should be 0'); adventurer.set_last_action_block(511); @@ -2719,7 +2722,7 @@ mod tests { #[test] #[available_gas(254644)] fn test_charisma_adjusted_item_price() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // zero case let item_price = adventurer.charisma_adjusted_item_price(0); @@ -2743,7 +2746,7 @@ mod tests { #[test] #[available_gas(289254)] fn test_charisma_adjusted_potion_price() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // default case (no charisma discount) let potion_price = adventurer.charisma_adjusted_potion_price(); @@ -2774,7 +2777,7 @@ mod tests { #[test] #[available_gas(241584)] fn test_get_idle_blocks() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); adventurer.last_action_block = 1; // test with current block greater than last action @@ -2865,7 +2868,7 @@ mod tests { #[should_panic(expected: ('health overflow',))] #[available_gas(3000000)] fn test_pack_protection_overflow_health() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); adventurer.health = MAX_ADVENTURER_HEALTH + 1; AdventurerPacking::pack(adventurer); } @@ -2874,7 +2877,7 @@ mod tests { #[should_panic(expected: ('gold overflow',))] #[available_gas(3000000)] fn test_pack_protection_overflow_gold() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); adventurer.gold = MAX_GOLD + 1; AdventurerPacking::pack(adventurer); } @@ -2883,7 +2886,7 @@ mod tests { #[should_panic(expected: ('xp overflow',))] #[available_gas(3000000)] fn test_pack_protection_overflow_xp() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); adventurer.xp = MAX_XP + 1; AdventurerPacking::pack(adventurer); } @@ -2892,7 +2895,7 @@ mod tests { #[should_panic(expected: ('strength overflow',))] #[available_gas(3000000)] fn test_pack_protection_overflow_strength() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); adventurer.stats.strength = MAX_STAT_VALUE + 1; AdventurerPacking::pack(adventurer); } @@ -2901,7 +2904,7 @@ mod tests { #[should_panic(expected: ('dexterity overflow',))] #[available_gas(3000000)] fn test_pack_protection_overflow_dexterity() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); adventurer.stats.dexterity = MAX_STAT_VALUE + 1; AdventurerPacking::pack(adventurer); } @@ -2910,7 +2913,7 @@ mod tests { #[should_panic(expected: ('vitality overflow',))] #[available_gas(3000000)] fn test_pack_protection_overflow_vitality() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); adventurer.stats.vitality = MAX_STAT_VALUE + 1; AdventurerPacking::pack(adventurer); } @@ -2919,7 +2922,7 @@ mod tests { #[should_panic(expected: ('intelligence overflow',))] #[available_gas(3000000)] fn test_pack_protection_overflow_intelligence() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); adventurer.stats.intelligence = MAX_STAT_VALUE + 1; AdventurerPacking::pack(adventurer); } @@ -2928,7 +2931,7 @@ mod tests { #[should_panic(expected: ('wisdom overflow',))] #[available_gas(3000000)] fn test_pack_protection_overflow_wisdom() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); adventurer.stats.wisdom = MAX_STAT_VALUE + 1; AdventurerPacking::pack(adventurer); } @@ -2937,7 +2940,7 @@ mod tests { #[should_panic(expected: ('weapon xp overflow',))] #[available_gas(3000000)] fn test_pack_protection_overflow_weapon_xp() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); adventurer.weapon.xp = MAX_PACKABLE_ITEM_XP + 1; AdventurerPacking::pack(adventurer); } @@ -2946,7 +2949,7 @@ mod tests { #[should_panic(expected: ('chest xp overflow',))] #[available_gas(3000000)] fn test_pack_protection_overflow_chest_xp() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); adventurer.chest.xp = MAX_PACKABLE_ITEM_XP + 1; AdventurerPacking::pack(adventurer); } @@ -2955,7 +2958,7 @@ mod tests { #[should_panic(expected: ('head xp overflow',))] #[available_gas(3000000)] fn test_pack_protection_overflow_head_xp() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); adventurer.head.xp = MAX_PACKABLE_ITEM_XP + 1; AdventurerPacking::pack(adventurer); } @@ -2964,7 +2967,7 @@ mod tests { #[should_panic(expected: ('waist xp overflow',))] #[available_gas(3000000)] fn test_pack_protection_overflow_waist_xp() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); adventurer.waist.xp = MAX_PACKABLE_ITEM_XP + 1; AdventurerPacking::pack(adventurer); } @@ -2973,7 +2976,7 @@ mod tests { #[should_panic(expected: ('foot xp overflow',))] #[available_gas(3000000)] fn test_pack_protection_overflow_foot_xp() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); adventurer.foot.xp = MAX_PACKABLE_ITEM_XP + 1; AdventurerPacking::pack(adventurer); } @@ -2982,7 +2985,7 @@ mod tests { #[should_panic(expected: ('hand xp overflow',))] #[available_gas(3000000)] fn test_pack_protection_overflow_hand_xp() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); adventurer.hand.xp = MAX_PACKABLE_ITEM_XP + 1; AdventurerPacking::pack(adventurer); } @@ -2991,7 +2994,7 @@ mod tests { #[should_panic(expected: ('neck xp overflow',))] #[available_gas(3000000)] fn test_pack_protection_overflow_neck_xp() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); adventurer.neck.xp = MAX_PACKABLE_ITEM_XP + 1; AdventurerPacking::pack(adventurer); } @@ -3000,7 +3003,7 @@ mod tests { #[should_panic(expected: ('ring xp overflow',))] #[available_gas(3000000)] fn test_pack_protection_overflow_ring_xp() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); adventurer.ring.xp = MAX_PACKABLE_ITEM_XP + 1; AdventurerPacking::pack(adventurer); } @@ -3009,7 +3012,7 @@ mod tests { #[should_panic(expected: ('beast health overflow',))] #[available_gas(3000000)] fn test_pack_protection_overflow_beast_health() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); adventurer.beast_health = MAX_PACKABLE_BEAST_HEALTH + 1; AdventurerPacking::pack(adventurer); } @@ -3018,7 +3021,7 @@ mod tests { #[should_panic(expected: ('stat points avail overflow',))] #[available_gas(3000000)] fn test_pack_protection_overflow_stat_points_available() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); adventurer.stat_points_available = MAX_STAT_UPGRADE_POINTS + 1; AdventurerPacking::pack(adventurer); } @@ -3027,7 +3030,7 @@ mod tests { #[should_panic(expected: ('actions per block overflow',))] #[available_gas(3000000)] fn test_pack_protection_overflow_actions_per_block() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); adventurer.actions_per_block = MAX_ACTIONS_PER_BLOCK + 1; AdventurerPacking::pack(adventurer); } @@ -3036,7 +3039,7 @@ mod tests { #[should_panic(expected: ('last action block overflow',))] #[available_gas(3000000)] fn test_pack_protection_overflow_last_action_block() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); adventurer.last_action_block = MAX_LAST_ACTION_BLOCK + 1; AdventurerPacking::pack(adventurer); } @@ -3044,7 +3047,7 @@ mod tests { #[test] #[available_gas(2000000)] fn test_new_adventurer() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); AdventurerPacking::pack(adventurer); assert(adventurer.health == STARTING_HEALTH, 'wrong starting health'); assert(adventurer.gold == STARTING_GOLD, 'wrong starting gold'); @@ -3054,7 +3057,7 @@ mod tests { #[test] #[available_gas(305064)] fn test_increase_health() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // test stock max health is 100 adventurer.increase_health(5); @@ -3083,7 +3086,7 @@ mod tests { #[test] #[available_gas(2701164)] fn test_increase_gold() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // assert starting state assert(adventurer.gold == STARTING_GOLD, 'wrong advntr starting gold'); @@ -3109,7 +3112,7 @@ mod tests { #[test] #[available_gas(197164)] fn test_decrease_health() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); let starting_health = adventurer.health; let deduct_amount = 5; @@ -3125,7 +3128,7 @@ mod tests { #[test] #[available_gas(197064)] fn test_deduct_gold() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); let starting_gold = adventurer.gold; let deduct_amount = 5; @@ -3141,7 +3144,7 @@ mod tests { #[test] #[available_gas(339614)] fn test_increase_adventurer_xp() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // base case level increase let (previous_level, new_level) = adventurer.increase_adventurer_xp(4); assert(adventurer.xp == 4, 'xp should be 4'); @@ -3168,7 +3171,7 @@ mod tests { #[test] #[available_gas(3000000)] fn test_increase_stat_points_available() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); let original_stat_points = adventurer.stat_points_available; // zero case @@ -3208,7 +3211,7 @@ mod tests { #[test] #[available_gas(192164)] fn test_increase_strength() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // basic case adventurer.stats.increase_strength(1); assert(adventurer.stats.strength == 1, 'strength should be 1'); @@ -3220,7 +3223,7 @@ mod tests { #[test] #[available_gas(192164)] fn test_increase_dexterity() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // basic case adventurer.stats.increase_dexterity(1); assert(adventurer.stats.dexterity == 1, 'dexterity should be 1'); @@ -3232,7 +3235,7 @@ mod tests { #[test] #[available_gas(192164)] fn test_increase_vitality() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // basic case adventurer.stats.increase_vitality(1); assert(adventurer.stats.vitality == 1, 'vitality should be 1'); @@ -3244,7 +3247,7 @@ mod tests { #[test] #[available_gas(192164)] fn test_increase_intelligence() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // basic case adventurer.stats.increase_intelligence(1); assert(adventurer.stats.intelligence == 1, 'intelligence should be 1'); @@ -3256,7 +3259,7 @@ mod tests { #[test] #[available_gas(192164)] fn test_increase_wisdom() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // basic case adventurer.stats.increase_wisdom(1); assert(adventurer.stats.wisdom == 1, 'wisdom should be 1'); @@ -3268,7 +3271,7 @@ mod tests { #[test] #[available_gas(192164)] fn test_increase_charisma() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // basic case adventurer.stats.increase_charisma(1); assert(adventurer.stats.charisma == 1, 'charisma should be 1'); @@ -3280,7 +3283,7 @@ mod tests { #[test] #[available_gas(192164)] fn test_decrease_strength() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // basic case adventurer.stats.increase_strength(2); adventurer.stats.decrease_strength(1); @@ -3294,7 +3297,7 @@ mod tests { #[test] #[available_gas(192164)] fn test_decrease_dexterity() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // basic case adventurer.stats.increase_dexterity(2); adventurer.stats.decrease_dexterity(1); @@ -3308,7 +3311,7 @@ mod tests { #[test] #[available_gas(192164)] fn test_decrease_vitality() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // basic case adventurer.stats.increase_vitality(2); adventurer.stats.decrease_vitality(1); @@ -3322,7 +3325,7 @@ mod tests { #[test] #[available_gas(192164)] fn test_decrease_intelligence() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // basic case adventurer.stats.increase_intelligence(2); adventurer.stats.decrease_intelligence(1); @@ -3336,14 +3339,14 @@ mod tests { #[test] #[available_gas(192164)] fn test_decrease_wisdom_gas() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); adventurer.stats.decrease_wisdom(1); } #[test] #[available_gas(192164)] fn test_decrease_wisdom() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // basic case adventurer.stats.increase_wisdom(2); adventurer.stats.decrease_wisdom(1); @@ -3357,7 +3360,7 @@ mod tests { #[test] #[available_gas(192164)] fn test_decrease_charisma() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // basic case adventurer.stats.increase_charisma(2); adventurer.stats.decrease_charisma(1); @@ -3378,7 +3381,7 @@ mod tests { // let starting_stats = Stats { // strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 0, luck: 0 // }; - // let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + // let mut adventurer = ImplAdventurer::new(ItemId::Wand); // // create demon crown item // let item = ItemPrimitive { id: ItemId::DemonCrown, xp: 1, metadata: 0 }; // // try to equip it to adventurer as a weapon @@ -3392,7 +3395,7 @@ mod tests { #[test] #[available_gas(171984)] fn test_equip_valid_weapon() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // Create Katana item let item = ItemPrimitive { id: ItemId::Katana, xp: 1, metadata: 0 }; @@ -3415,7 +3418,7 @@ mod tests { // let starting_stats = Stats { // strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 0, luck: 0 // }; - // let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + // let mut adventurer = ImplAdventurer::new(ItemId::Wand); // // try to equip a Demon Crown as chest item // let item = ItemPrimitive { id: ItemId::DemonCrown, xp: 1, metadata: 0 }; // adventurer.equip_chest_armor(item); @@ -3428,7 +3431,7 @@ mod tests { #[test] #[available_gas(171984)] fn test_equip_valid_chest() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // equip Divine Robe as chest item let item = ItemPrimitive { id: ItemId::DivineRobe, xp: 1, metadata: 0 }; adventurer.equip_chest_armor(item); @@ -3449,7 +3452,7 @@ mod tests { // let starting_stats = Stats { // strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 0, luck: 0 // }; - // let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + // let mut adventurer = ImplAdventurer::new(ItemId::Wand); // // try to equip a Katana as head item // let item = ItemPrimitive { id: ItemId::Katana, xp: 1, metadata: 0 }; // adventurer.equip_head_armor(item); @@ -3459,7 +3462,7 @@ mod tests { #[test] #[available_gas(171984)] fn test_equip_valid_head() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // equip Crown as head item let item = ItemPrimitive { id: ItemId::Crown, xp: 1, metadata: 0 }; adventurer.equip_head_armor(item); @@ -3479,7 +3482,7 @@ mod tests { // let starting_stats = Stats { // strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 0, luck: 0 // }; - // let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + // let mut adventurer = ImplAdventurer::new(ItemId::Wand); // // try to equip a Demon Crown as waist item // let item = ItemPrimitive { id: ItemId::DemonCrown, xp: 1, metadata: 0 }; // adventurer.equip_waist_armor(item); @@ -3489,7 +3492,7 @@ mod tests { #[test] #[available_gas(171984)] fn test_equip_valid_waist() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // equip Wool Sash as waist item let item = ItemPrimitive { id: ItemId::WoolSash, xp: 1, metadata: 0 }; @@ -3511,7 +3514,7 @@ mod tests { // let starting_stats = Stats { // strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 0, luck: 0 // }; - // let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + // let mut adventurer = ImplAdventurer::new(ItemId::Wand); // // try to equip a Demon Crown as foot item // let item = ItemPrimitive { id: ItemId::DemonCrown, xp: 1, metadata: 0 }; // adventurer.equip_foot_armor(item); @@ -3521,7 +3524,7 @@ mod tests { #[test] #[available_gas(172184)] fn test_equip_valid_foot() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // equip Silk Slippers as foot item let item = ItemPrimitive { id: ItemId::SilkSlippers, xp: 1, metadata: 0 }; @@ -3543,7 +3546,7 @@ mod tests { // let starting_stats = Stats { // strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 0, luck: 0 // }; - // let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + // let mut adventurer = ImplAdventurer::new(ItemId::Wand); // // try to equip a Demon Crown as hand item // let item = ItemPrimitive { id: ItemId::DemonCrown, xp: 1, metadata: 0 }; @@ -3554,7 +3557,7 @@ mod tests { #[test] #[available_gas(172184)] fn test_equip_valid_hand() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // equip Divine Gloves as hand item let item = ItemPrimitive { id: ItemId::DivineGloves, xp: 1, metadata: 0 }; @@ -3576,7 +3579,7 @@ mod tests { // let starting_stats = Stats { // strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 0, luck: 0 // }; - // let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + // let mut adventurer = ImplAdventurer::new(ItemId::Wand); // // try to equip a Demon Crown as necklace // let item = ItemPrimitive { id: ItemId::DemonCrown, xp: 1, metadata: 0 }; @@ -3587,7 +3590,7 @@ mod tests { #[test] #[available_gas(172184)] fn test_equip_valid_neck() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // equip Pendant as necklace let item = ItemPrimitive { id: ItemId::Pendant, xp: 1, metadata: 0 }; @@ -3609,7 +3612,7 @@ mod tests { // let starting_stats = Stats { // strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 0, luck: 0 // }; - // let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + // let mut adventurer = ImplAdventurer::new(ItemId::Wand); // // try to equip a Demon Crown as ring // let item = ItemPrimitive { id: ItemId::DemonCrown, xp: 1, metadata: 0 }; @@ -3620,7 +3623,7 @@ mod tests { #[test] #[available_gas(172184)] fn test_equip_valid_ring() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); let item = ItemPrimitive { id: ItemId::PlatinumRing, xp: 1, metadata: 0 }; adventurer.equip_ring(item); assert(adventurer.ring.id == ItemId::PlatinumRing, 'did not equip ring'); @@ -3631,14 +3634,14 @@ mod tests { #[test] #[available_gas(198584)] fn test_increase_item_xp_at_slot_gas() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); adventurer.increase_item_xp_at_slot(Slot::Weapon(()), 1); } #[test] #[available_gas(385184)] fn test_increase_item_xp_at_slot() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // assert starting conditions assert(adventurer.weapon.xp == 0, 'weapon should start with 0xp'); @@ -3678,7 +3681,7 @@ mod tests { #[test] #[available_gas(198084)] fn test_increase_item_xp_at_slot_max() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); assert(adventurer.weapon.xp == 0, 'weapon should start with 0xp'); adventurer.increase_item_xp_at_slot(Slot::Weapon(()), 65535); @@ -3688,7 +3691,7 @@ mod tests { #[test] #[available_gas(198084)] fn test_increase_item_xp_at_slot_zero() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); assert(adventurer.weapon.xp == 0, 'weapon should start with 0xp'); adventurer.increase_item_xp_at_slot(Slot::Weapon(()), 0); @@ -3698,7 +3701,7 @@ mod tests { #[test] #[available_gas(449564)] fn test_get_equipped_items() { - let mut adventurer = ImplAdventurer::new(ItemId::Wand, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); let starting_equipment = adventurer.get_equipped_items(); assert(starting_equipment.len() == 1, 'adventurer starts with 1 item'); @@ -3815,7 +3818,7 @@ mod tests { #[test] #[available_gas(184944)] fn test_set_beast_health() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // base case adventurer.set_beast_health(100); @@ -3831,7 +3834,7 @@ mod tests { #[test] #[available_gas(194964)] fn test_deduct_beast_health() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // stage beast with 100 adventurer.set_beast_health(100); @@ -3863,7 +3866,7 @@ mod tests { #[test] #[available_gas(300000)] fn test_get_item_at_slot() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // stage items let weapon = ItemPrimitive { id: ItemId::Katana, xp: 1, metadata: 1 }; @@ -3899,7 +3902,7 @@ mod tests { #[test] #[available_gas(353184)] fn test_is_slot_free() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // stage items let weapon = ItemPrimitive { id: ItemId::Katana, xp: 1, metadata: 1 }; @@ -3931,7 +3934,7 @@ mod tests { #[test] #[available_gas(600000)] fn test_get_level() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); assert(adventurer.get_level() == 1, 'level should be 1'); adventurer.xp = 4; @@ -3955,7 +3958,7 @@ mod tests { #[test] #[available_gas(234224)] fn test_charisma_health_discount_overflow() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // max charisma adventurer.stats.charisma = 255; @@ -3973,7 +3976,7 @@ mod tests { #[test] #[available_gas(234524)] fn test_charisma_item_discount_overflow() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); let item_price = 15; // no charisma case @@ -3999,7 +4002,7 @@ mod tests { #[test] #[available_gas(256224)] fn test_increase_xp() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // increase adventurer xp by 3 which should level up the adventurer adventurer.increase_adventurer_xp(4); @@ -4013,7 +4016,7 @@ mod tests { #[test] #[available_gas(293884)] fn test_apply_suffix_boost() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); adventurer.stats.apply_suffix_boost(ItemSuffix::of_Power); assert(adventurer.stats.strength == 3, 'strength should be 3'); @@ -4059,7 +4062,7 @@ mod tests { #[test] #[available_gas(1900000)] fn test_remove_suffix_boost() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // verify starting state assert(adventurer.stats.strength == 0, 'strength should be 0'); @@ -4169,7 +4172,7 @@ mod tests { #[test] #[available_gas(207524)] fn test_apply_stat_boosts() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); let boost_stats = Stats { strength: 5, dexterity: 1, vitality: 5, intelligence: 1, wisdom: 1, charisma: 2, luck: 1 @@ -4189,7 +4192,7 @@ mod tests { #[test] #[available_gas(207524)] fn test_apply_stat_boosts_zero() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); let boost_stats = Stats { strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 0, luck: 0 @@ -4208,7 +4211,7 @@ mod tests { #[test] #[available_gas(207524)] fn test_apply_stat_boosts_max() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); let boost_stats = Stats { strength: 255, dexterity: 255, @@ -4378,7 +4381,7 @@ mod tests { #[test] #[available_gas(390000)] fn test_discover_treasure() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // give vitality so we can discover health adventurer.stats.vitality = 1; @@ -4398,7 +4401,7 @@ mod tests { #[test] #[available_gas(245054)] fn test_calculate_luck_gas_no_luck() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); let bag = ImplBag::new(); assert(adventurer.calculate_luck(bag) == 2, 'start with 2 luck'); } @@ -4406,7 +4409,7 @@ mod tests { #[test] #[available_gas(245554)] fn test_calculate_luck_gas_with_luck() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); let bag = ImplBag::new(); let neck = ItemPrimitive { id: ItemId::Amulet, xp: 1, metadata: 7 }; @@ -4419,7 +4422,7 @@ mod tests { #[test] #[available_gas(698414)] fn test_calculate_luck() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); let bag = ImplBag::new(); assert(adventurer.calculate_luck(bag) == 2, 'start with 2 luck'); @@ -4455,7 +4458,7 @@ mod tests { #[test] #[available_gas(177984)] fn test_in_battle() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); assert(adventurer.in_battle() == true, 'new advntr start in battle'); adventurer.beast_health = 0; @@ -4469,7 +4472,7 @@ mod tests { #[test] #[available_gas(550000)] fn test_equip_item() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // assert starting conditions assert(adventurer.weapon.id == 12, 'weapon should be 12'); @@ -4514,7 +4517,7 @@ mod tests { #[test] #[available_gas(1000000)] fn test_is_equipped() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); let wand = ItemPrimitive { id: ItemId::Wand, xp: 1, metadata: 1 }; let demon_crown = ItemPrimitive { id: ItemId::DemonCrown, xp: 1, metadata: 2 }; @@ -4652,7 +4655,7 @@ mod tests { #[should_panic(expected: ('item is not equipped',))] #[available_gas(172984)] fn test_drop_item_not_equipped() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // try to drop an item that isn't equipped // this should panic with 'item is not equipped' // the test is annotated to expect this panic @@ -4662,7 +4665,7 @@ mod tests { #[test] #[available_gas(511384)] fn test_drop_item() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // assert starting conditions assert(adventurer.weapon.id == ItemId::Wand, 'weapon should be wand'); @@ -4746,7 +4749,7 @@ mod tests { #[test] #[available_gas(421224)] fn test_is_ambush() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // without any wisdom, should get ambushed by all entropy assert(adventurer.is_ambushed(1), 'no wisdom should get ambushed'); diff --git a/contracts/adventurer/src/adventurer_meta.cairo b/contracts/adventurer/src/adventurer_meta.cairo index 218803fa9..9311eeba4 100644 --- a/contracts/adventurer/src/adventurer_meta.cairo +++ b/contracts/adventurer/src/adventurer_meta.cairo @@ -1,47 +1,77 @@ use starknet::{StorePacking}; use traits::{TryInto, Into}; +use super::stats::{Stats, StatsPacking}; #[derive(Drop, Copy, Serde)] struct AdventurerMetadata { - name: u128, - entropy: u128, + start_block: u64, // 64 bits + starting_stats: Stats, // 24 bits + name: u128, // 128 bits } impl PackingAdventurerMetadata of StorePacking { fn pack(value: AdventurerMetadata) -> felt252 { - (value.entropy.into() + value.name.into() * TWO_POW_128).try_into().unwrap() + (value.start_block.into() + + StatsPacking::pack(value.starting_stats).into() * TWO_POW_64 + + value.name.into() * TWO_POW_88) + .try_into() + .unwrap() } fn unpack(value: felt252) -> AdventurerMetadata { let packed = value.into(); - let (packed, entropy) = integer::U256DivRem::div_rem( - packed, TWO_POW_128.try_into().unwrap() + let (packed, start_block) = integer::U256DivRem::div_rem( + packed, TWO_POW_64.try_into().unwrap() + ); + let (packed, starting_stats) = integer::U256DivRem::div_rem( + packed, TWO_POW_24.try_into().unwrap() ); let (_, name) = integer::U256DivRem::div_rem(packed, TWO_POW_128.try_into().unwrap()); - AdventurerMetadata { name: name.try_into().unwrap(), entropy: entropy.try_into().unwrap() } + AdventurerMetadata { + start_block: start_block.try_into().unwrap(), + starting_stats: StatsPacking::unpack(starting_stats.try_into().unwrap()), + name: name.try_into().unwrap() + } } } +const TWO_POW_24: u256 = 0x1000000; +const TWO_POW_64: u256 = 0x10000000000000000; +const TWO_POW_88: u256 = 0x10000000000000000000000; const TWO_POW_128: u256 = 0x100000000000000000000000000000000; #[cfg(test)] #[test] -#[available_gas(116600)] +#[available_gas(1187400)] fn test_adventurer_metadata_packing() { // max value case - let max_u128 = 340282366920938463463374607431768211455; - let name_length = 'abcdefghijklmno'; + let max_u64 = 0xffffffffffffffff; + let max_name_length = 'abcdefghijklmnop'; + let max_starting_stats = Stats { + strength: 15, + dexterity: 15, + vitality: 15, + intelligence: 15, + wisdom: 15, + charisma: 15, + luck: 15 + }; + + let meta = AdventurerMetadata { + start_block: max_u64, starting_stats: max_starting_stats, name: max_name_length + }; - let meta = AdventurerMetadata { name: name_length, entropy: max_u128 }; - let packed = PackingAdventurerMetadata::pack(meta); let unpacked: AdventurerMetadata = PackingAdventurerMetadata::unpack(packed); assert(meta.name == unpacked.name, 'name should be max'); - assert(meta.entropy == unpacked.entropy, 'entropy should be max u128'); + assert(meta.start_block == unpacked.start_block, 'sblock should be max u64'); // zero case - let meta = AdventurerMetadata { name: 0, entropy: 0 }; + let zero_starting_stats = Stats { + strength: 0, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 0, luck: 0 + }; + let meta = AdventurerMetadata { start_block: 0, starting_stats: zero_starting_stats, name: 0 }; let packed = PackingAdventurerMetadata::pack(meta); let unpacked: AdventurerMetadata = PackingAdventurerMetadata::unpack(packed); assert(unpacked.name == 0, 'name should be 0'); - assert(unpacked.entropy == 0, 'entropy should be 0'); -} \ No newline at end of file + assert(unpacked.start_block == 0, 'entropy should be 0'); +} diff --git a/contracts/adventurer/src/adventurer_utils.cairo b/contracts/adventurer/src/adventurer_utils.cairo index 01566e415..06ff27ccb 100644 --- a/contracts/adventurer/src/adventurer_utils.cairo +++ b/contracts/adventurer/src/adventurer_utils.cairo @@ -60,9 +60,11 @@ impl AdventurerUtils of IAdventurerUtils { // @notice Determines a random attack location based on the provided entropy // @param entropy The entropy used to generate a random attack location // @return A Slot type which represents the randomly determined attack location - fn get_random_attack_location(entropy: u128) -> Slot { + fn get_random_attack_location(entropy: felt252) -> Slot { + let slots: u256 = 5; + // project entropy into 0-4 range - let rnd_slot = entropy % 5; + let (_, rnd_slot) = integer::U256DivRem::div_rem(entropy.into(), slots.try_into().unwrap()); // return disinct slot for each outcome if (rnd_slot == 0) { @@ -73,29 +75,13 @@ impl AdventurerUtils of IAdventurerUtils { Slot::Waist(()) } else if (rnd_slot == 3) { Slot::Foot(()) - } else { + } else if (rnd_slot == 4) { Slot::Hand(()) + } else { + panic_with_felt252('slot out of range') } } - // @notice Generates entropy for an adventurer based on a block number and adventurer ID - // @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( - adventurer_id: felt252, block_number: u64, block_timestamp: u64 - ) -> u128 { - let mut hash_span = ArrayTrait::::new(); - hash_span.append(adventurer_id); - 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) = integer::U256DivRem::div_rem( - poseidon.into(), u256_try_as_non_zero(U128_MAX.into()).unwrap() - ); - r.try_into().unwrap() - } - // @notice Generates special entropy for an item based on provided entropy and item ID // @param entropy The entropy used to generate the special entropy // @param item_id The ID of the item used in generating the special entropy @@ -187,11 +173,11 @@ impl AdventurerUtils of IAdventurerUtils { // @param game_entropy: game entropy // @return (u128, u128): tuple of randomness fn get_randomness( - adventurer_xp: u16, adventurer_entropy: u128, game_entropy: felt252 + adventurer_xp: u16, adventurer_entropy: felt252, game_entropy: felt252 ) -> (u128, u128) { let mut hash_span = ArrayTrait::::new(); hash_span.append(adventurer_xp.into()); - hash_span.append(adventurer_entropy.into()); + hash_span.append(adventurer_entropy); hash_span.append(game_entropy); let poseidon = poseidon_hash_span(hash_span.span()); AdventurerUtils::split_hash(poseidon) @@ -204,12 +190,15 @@ impl AdventurerUtils of IAdventurerUtils { // @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, game_entropy: felt252 + adventurer_xp: u16, + adventurer_health: u16, + adventurer_entropy: felt252, + game_entropy: felt252 ) -> (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(adventurer_entropy); hash_span.append(game_entropy); let poseidon = poseidon_hash_span(hash_span.span()); AdventurerUtils::split_hash(poseidon) @@ -225,7 +214,7 @@ impl AdventurerUtils of IAdventurerUtils { (r.try_into().unwrap(), d.try_into().unwrap()) } - fn generate_starting_stats(entropy: u128, starting_stat_count: u8) -> Stats { + fn generate_starting_stats(entropy: u256, starting_stat_count: u8) -> Stats { let mut starting_stats = Stats { strength: 0, dexterity: 0, @@ -236,7 +225,7 @@ impl AdventurerUtils of IAdventurerUtils { luck: 0, }; - let random_outcomes = AdventurerUtils::u128_to_u8_array(entropy); + let random_outcomes = AdventurerUtils::u256_to_u8_array(entropy); // TODO: Use conditional compilation to only run this check in debug mode as not to waste gas in production assert(starting_stat_count.into() < random_outcomes.len(), 'stat count out of bounds'); @@ -270,7 +259,7 @@ impl AdventurerUtils of IAdventurerUtils { starting_stats } - fn u128_to_u8_array(value: u128) -> Array { + fn u256_to_u8_array(value: u256) -> Array { let mut result = ArrayTrait::::new(); result.append((value & MASK_8).try_into().unwrap()); result.append(((value / TWO_POW_8) & MASK_8).try_into().unwrap()); @@ -291,28 +280,29 @@ impl AdventurerUtils of IAdventurerUtils { result } } -const MASK_8: u128 = 0xFF; -const TWO_POW_8: u128 = 0x100; -const TWO_POW_16: u128 = 0x10000; -const TWO_POW_24: u128 = 0x1000000; -const TWO_POW_32: u128 = 0x100000000; -const TWO_POW_40: u128 = 0x10000000000; -const TWO_POW_48: u128 = 0x1000000000000; -const TWO_POW_56: u128 = 0x100000000000000; -const TWO_POW_64: u128 = 0x10000000000000000; -const TWO_POW_72: u128 = 0x1000000000000000000; -const TWO_POW_80: u128 = 0x100000000000000000000; -const TWO_POW_88: u128 = 0x10000000000000000000000; -const TWO_POW_96: u128 = 0x1000000000000000000000000; -const TWO_POW_104: u128 = 0x100000000000000000000000000; -const TWO_POW_112: u128 = 0x10000000000000000000000000000; -const TWO_POW_120: u128 = 0x1000000000000000000000000000000; +const MASK_8: u256 = 0xFF; +const TWO_POW_8: u256 = 0x100; +const TWO_POW_16: u256 = 0x10000; +const TWO_POW_24: u256 = 0x1000000; +const TWO_POW_32: u256 = 0x100000000; +const TWO_POW_40: u256 = 0x10000000000; +const TWO_POW_48: u256 = 0x1000000000000; +const TWO_POW_56: u256 = 0x100000000000000; +const TWO_POW_64: u256 = 0x10000000000000000; +const TWO_POW_72: u256 = 0x1000000000000000000; +const TWO_POW_80: u256 = 0x100000000000000000000; +const TWO_POW_88: u256 = 0x10000000000000000000000; +const TWO_POW_96: u256 = 0x1000000000000000000000000; +const TWO_POW_104: u256 = 0x100000000000000000000000000; +const TWO_POW_112: u256 = 0x10000000000000000000000000000; +const TWO_POW_120: u256 = 0x1000000000000000000000000000000; // --------------------------- // ---------- Tests ---------- // --------------------------- #[cfg(test)] mod tests { + use debug::PrintTrait; use poseidon::poseidon_hash_span; use survivor::{ constants::{ @@ -326,21 +316,23 @@ mod tests { adventurer_utils::AdventurerUtils }; use combat::constants::CombatEnums::{Type, Tier, Slot}; + use lootitems::{constants::{ItemId}}; + #[test] - #[available_gas(166544)] + #[available_gas(286398)] fn test_generate_starting_stats_gas() { AdventurerUtils::generate_starting_stats(0, 1); } #[test] - #[available_gas(166544)] + #[available_gas(244018)] #[should_panic(expected: ('stat count out of bounds',))] fn test_generate_starting_stats_fail_out_of_bounds() { AdventurerUtils::generate_starting_stats(0, 20); } #[test] - #[available_gas(1924040)] + #[available_gas(2020662)] fn test_generate_starting_stats() { let starting_stat_count = 9; @@ -382,23 +374,7 @@ mod tests { hash_span.append(241); hash_span.append(14212); let poseidon = poseidon_hash_span(hash_span.span()); - let (rnd1, rnd2) = AdventurerUtils::split_hash(poseidon); - let stats = AdventurerUtils::generate_starting_stats(rnd1, starting_stat_count); - let stat_count = stats.strength - + stats.dexterity - + stats.vitality - + stats.intelligence - + stats.wisdom - + stats.charisma; - assert(stat_count == starting_stat_count, 'wrong stat total'); - assert(stats.strength == 1, 'strength should be 1'); - assert(stats.dexterity == 1, 'dexterity should be 1'); - assert(stats.vitality == 2, 'vitality should be 2'); - assert(stats.intelligence == 1, 'intelligence should be 1'); - assert(stats.wisdom == 4, 'wisdom should be 4'); - assert(stats.charisma == 0, 'charisma should be 0'); - - let stats = AdventurerUtils::generate_starting_stats(rnd2, starting_stat_count); + let stats = AdventurerUtils::generate_starting_stats(poseidon.into(), starting_stat_count); let stat_count = stats.strength + stats.dexterity + stats.vitality @@ -406,14 +382,16 @@ mod tests { + stats.wisdom + stats.charisma; assert(stat_count == starting_stat_count, 'wrong stat total'); - assert(stats.strength == 0, 'strength should be 0'); - assert(stats.dexterity == 1, 'dexterity should be 1'); - assert(stats.vitality == 3, 'vitality should be 3'); - assert(stats.intelligence == 4, 'intelligence should be 4'); + assert(stats.strength == 2, 'strength should be 2'); + assert(stats.dexterity == 3, 'dexterity should be 3'); + assert(stats.vitality == 1, 'vitality should be 1'); + assert(stats.intelligence == 0, 'intelligence should be 0'); assert(stats.wisdom == 1, 'wisdom should be 1'); - assert(stats.charisma == 0, 'charisma should be 0'); + assert(stats.charisma == 2, 'charisma should be 2'); - let stats = AdventurerUtils::generate_starting_stats(rnd2, starting_stat_count + 5); + let stats = AdventurerUtils::generate_starting_stats( + poseidon.into(), starting_stat_count + 5 + ); let stat_count = stats.strength + stats.dexterity + stats.vitality @@ -421,12 +399,12 @@ mod tests { + stats.wisdom + stats.charisma; assert(stat_count == starting_stat_count + 5, 'wrong stat total'); - assert(stats.strength == 0, 'strength should be 0'); + assert(stats.strength == 2, 'strength should be 2'); assert(stats.dexterity == 3, 'dexterity should be 3'); - assert(stats.vitality == 4, 'vitality should be 4'); - assert(stats.intelligence == 5, 'intelligence should be 5'); - assert(stats.wisdom == 1, 'wisdom should be 1'); - assert(stats.charisma == 1, 'charisma should be 1'); + assert(stats.vitality == 2, 'vitality should be 2'); + assert(stats.intelligence == 2, 'intelligence should be 2'); + assert(stats.wisdom == 2, 'wisdom should be 2'); + assert(stats.charisma == 3, 'charisma should be 3'); } #[test] @@ -434,7 +412,7 @@ mod tests { fn test_u128_to_u8_array() { // zero case let value = 0; - let values = AdventurerUtils::u128_to_u8_array(value); + let values = AdventurerUtils::u256_to_u8_array(value); let mut i = 0; loop { if i == values.len() { @@ -448,7 +426,7 @@ mod tests { // max u128 case let value = 0xffffffffffffffffffffffffffffffff; - let values = AdventurerUtils::u128_to_u8_array(value); + let values = AdventurerUtils::u256_to_u8_array(value); let mut i = 0; loop { if i == values.len() { @@ -464,7 +442,7 @@ mod tests { let value = 0b00000110110100110000110010010111000001000110111100110010001111010010000001111110000110100111101100010101000000001111111101100101; - let values = AdventurerUtils::u128_to_u8_array(value); + let values = AdventurerUtils::u256_to_u8_array(value); assert(*values.at(15) == 6, 'rand15 should be 6'); assert(*values.at(14) == 211, 'rand14 should be 211'); assert(*values.at(13) == 12, 'rand13 should be 12'); @@ -486,7 +464,7 @@ mod tests { #[test] #[available_gas(259644)] fn test_is_health_full() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // adventurers should start with full health assert( @@ -520,7 +498,7 @@ mod tests { #[test] #[available_gas(205004)] fn test_get_max_health() { - let mut adventurer = ImplAdventurer::new(12, 0, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // assert starting state assert( @@ -545,25 +523,6 @@ mod tests { ); } - - #[test] - #[available_gas(6482260)] - fn test_generate_adventurer_entropy() { - let mut i: u8 = 1; - loop { - if (i >= 100) { - break; - } - let adventurer_id = i.into(); - let block_number = 839152; - let block_timestamp = 53289712; - let adventurer_entropy = AdventurerUtils::generate_adventurer_entropy( - adventurer_id, block_number, block_timestamp - ); - i += 1; - }; - } - #[test] #[available_gas(30000)] fn test_overflow_protected_stat_increase() { @@ -601,7 +560,7 @@ mod tests { } #[test] - #[available_gas(60000)] + #[available_gas(163120)] fn test_get_random_attack_location() { // base cases let mut entropy = 0; diff --git a/contracts/adventurer/src/exploration.cairo b/contracts/adventurer/src/exploration.cairo index 96eb0a962..a821dcec1 100644 --- a/contracts/adventurer/src/exploration.cairo +++ b/contracts/adventurer/src/exploration.cairo @@ -65,15 +65,14 @@ impl ExploreUtils of Explore { #[cfg(test)] mod tests { use survivor::{ - exploration::ExploreUtils, adventurer::{ImplAdventurer, ItemPrimitive}, - stats::Stats + exploration::ExploreUtils, adventurer::{ImplAdventurer, ItemPrimitive}, stats::Stats }; use lootitems::constants::ItemId; #[test] #[available_gas(328654)] fn test_get_gold_discovery_gas() { - let adventurer = ImplAdventurer::new(12, 6, 0, 0); + let adventurer = ImplAdventurer::new(ItemId::Wand); let entropy = 0; ExploreUtils::get_gold_discovery(adventurer, entropy); } @@ -81,7 +80,7 @@ mod tests { #[test] #[available_gas(329054)] fn test_get_gold_discovery() { - let mut adventurer = ImplAdventurer::new(12, 6, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // base discovery for level 1 adventurer with 0 entropy should be 1 let entropy = 0; @@ -92,7 +91,7 @@ mod tests { #[test] #[available_gas(328854)] fn test_get_health_discovery_gas() { - let adventurer = ImplAdventurer::new(12, 6, 0, 0); + let adventurer = ImplAdventurer::new(ItemId::Wand); let entropy = 12345; ExploreUtils::get_health_discovery(adventurer, entropy); } @@ -100,7 +99,7 @@ mod tests { #[test] #[available_gas(329054)] fn test_get_health_discovery() { - let mut adventurer = ImplAdventurer::new(12, 6, 0, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // base discovery for level 1 adventurer with 0 entropy should be 1 let entropy = 0; diff --git a/contracts/adventurer/src/item_meta.cairo b/contracts/adventurer/src/item_meta.cairo index 79907c792..eba58a25b 100644 --- a/contracts/adventurer/src/item_meta.cairo +++ b/contracts/adventurer/src/item_meta.cairo @@ -478,7 +478,7 @@ mod tests { #[available_gas(4000000)] fn test_set_metadata_id() { // start test with a new adventurer wielding a wand - let mut adventurer = ImplAdventurer::new(12, 6, 1, 0); + let mut adventurer = ImplAdventurer::new(ItemId::Wand); // assert adventurer starter weapon has meta data id 1 assert(adventurer.weapon.metadata == 1, 'advntr wpn meta data shld be 1'); diff --git a/contracts/game/src/lib.cairo b/contracts/game/src/lib.cairo index 42506e45f..515f8d24b 100644 --- a/contracts/game/src/lib.cairo +++ b/contracts/game/src/lib.cairo @@ -10,7 +10,10 @@ mod tests { mod Game { // TODO: TESTING CONFIGS // ADJUST THESE BEFORE DEPLOYMENT + use core::starknet::SyscallResultTrait; const TEST_ENTROPY: u64 = 12303548; + const MAINNET_CHAIN_ID: felt252 = 0x534e5f4d41494e; + const GOERLI_CHAIN_ID: felt252 = 0x534e5f474f45524c49; const MINIMUM_SCORE_FOR_PAYOUTS: u16 = 100; const LOOT_NAME_STORAGE_INDEX_1: u8 = 0; const LOOT_NAME_STORAGE_INDEX_2: u8 = 1; @@ -680,9 +683,9 @@ mod Game { fn get_items_on_market(self: @ContractState, adventurer_id: felt252) -> Array { let adventurer = _unpack_adventurer(self, adventurer_id); _assert_upgrades_available(adventurer); - let adventurer_entropy: u128 = _unpack_adventurer_meta(self, adventurer_id) - .entropy - .into(); + + let adventurer_entropy = _get_adventurer_entropy(self, adventurer_id); + _get_items_on_market( self, adventurer_entropy, adventurer.xp, adventurer.stat_points_available ) @@ -692,9 +695,9 @@ mod Game { ) -> Array { let adventurer = _unpack_adventurer(self, adventurer_id); _assert_upgrades_available(adventurer); - let adventurer_entropy: u128 = _unpack_adventurer_meta(self, adventurer_id) - .entropy - .into(); + + let adventurer_entropy = _get_adventurer_entropy(self, adventurer_id); + _get_items_on_market_by_slot( self, adventurer_entropy, @@ -708,9 +711,8 @@ mod Game { ) -> Array { let adventurer = _unpack_adventurer(self, adventurer_id); _assert_upgrades_available(adventurer); - let adventurer_entropy: u128 = _unpack_adventurer_meta(self, adventurer_id) - .entropy - .into(); + + let adventurer_entropy = _get_adventurer_entropy(self, adventurer_id); if tier == 1 { _get_items_on_market_by_tier( @@ -1058,6 +1060,12 @@ mod Game { ref self, ref adventurer, adventurer_id, xp_earned_items, attack_rnd_2 ); + // if adventurer is on level 1, they are defeating their first beast + if (adventurer.get_level() == 1) { + // which means we can reveal their starting stats + _handle_stat_reveal(ref self, ref adventurer, adventurer_id); + } + // emit slayed beast event __event_SlayedBeast( ref self, @@ -1091,6 +1099,35 @@ mod Game { } } + /// @title Stat Reveal Handler + /// @notice Handle the revelation and setting of an adventurer's starting stats. + /// @dev This function generates starting stats for an adventurer using entropy, which is based on the block hash of the block + /// after the player committed to playing the game. + /// @param self A reference to the ContractState object. + /// @param adventurer A reference to the Adventurer object whose stats are to be revealed and set. + /// @param adventurer_id The unique identifier of the adventurer. + fn _handle_stat_reveal( + ref self: ContractState, ref adventurer: Adventurer, adventurer_id: felt252 + ) { + // generate starting stats using the adventurer entropy which is based on the block hash of the block after + // the player committed to playing the game + let starting_stats = AdventurerUtils::generate_starting_stats( + _get_adventurer_entropy(@self, adventurer_id).into(), NUM_STARTING_STATS + ); + + // adventurer shouldn't have any stats so save gas and overwrite + adventurer.stats = starting_stats; + + // credit adventurer with health from their vitality starting stats + adventurer.health += AdventurerUtils::get_max_health(adventurer.stats.vitality) + - STARTING_HEALTH; + + // update adventurer meta with starting stats, this is last time we need to update adventurer meta data + let mut adventurer_meta = _unpack_adventurer_meta(@self, adventurer_id); + adventurer_meta.starting_stats = starting_stats; + _pack_adventurer_meta(ref self, adventurer_id, adventurer_meta); + } + fn _mint_beast(self: @ContractState, beast: Beast) { let collectible_beasts_contract = ILeetLootDispatcher { contract_address: self._collectible_beasts.read() @@ -1271,26 +1308,31 @@ mod Game { let adventurer_id = self._game_counter.read() + 1; // use current starknet block number and timestamp as entropy sources - let block_number = starknet::get_block_info().unbox().block_number; + let current_block = 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 - ); + // randomness for starter beast isn't sensitive so we can use basic entropy + let starter_beast_rnd = _get_basic_entropy(adventurer_id, current_block); + + // generate a new adventurer using the provided started weapon + let mut adventurer = ImplAdventurer::new(weapon); - // generate a new adventurer using the provided started weapon and current block number - let mut adventurer = ImplAdventurer::new(weapon, NUM_STARTING_STATS, block_number, entropy); + // set the adventurer last action block to the current block + the reveal delay so the idle + // timer doesn't start till the game officially starts after the reveal phase + adventurer.set_last_action_block(current_block + _get_reveal_block_delay()); - // set entropy on adventurer metadata - let adventurer_meta = AdventurerMetadata { name, entropy }; + // player doesn't get starting stats until after the commit-and-reveal phase + let empty_stats = StatUtils::new(); + let adventurer_meta = AdventurerMetadata { + name, start_block: current_block, starting_stats: empty_stats + }; // emit a StartGame event __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 adventurer, adventurer_id, weapon, entropy + ref adventurer, adventurer_id, weapon, starter_beast_rnd ); __event_AmbushedByBeast(ref self, adventurer, adventurer_id, beast_battle_details); @@ -1310,9 +1352,9 @@ mod Game { ref adventurer: Adventurer, adventurer_id: felt252, starting_weapon: u8, - adventurer_entropy: u128 + adventurer_entropy: felt252 ) -> BattleDetails { - let beast_seed: u128 = adventurer.get_beast_seed(adventurer_entropy); + let beast_seed = adventurer.get_beast_seed(adventurer_entropy); // generate starter beast which will have weak armor against the adventurers starter weapon let starter_beast = ImplBeast::get_starter_beast( @@ -1343,7 +1385,7 @@ mod Game { ref self: ContractState, ref adventurer: Adventurer, adventurer_id: felt252, - adventurer_entropy: u128, + adventurer_entropy: felt252, game_entropy: felt252, explore_till_beast: bool ) { @@ -1417,7 +1459,7 @@ mod Game { fn _beast_encounter( ref self: ContractState, ref adventurer: Adventurer, - adventurer_entropy: u128, + adventurer_entropy: felt252, adventurer_id: felt252, entropy: u128 ) { @@ -1454,7 +1496,7 @@ mod Game { let obstacle = adventurer.get_random_obstacle(entropy); // get a random attack location for the obstacle - let damage_slot = AdventurerUtils::get_random_attack_location(entropy); + let damage_slot = AdventurerUtils::get_random_attack_location(entropy.into()); // get armor at the location being attacked let armor = adventurer.get_item_at_slot(damage_slot); @@ -1666,7 +1708,7 @@ mod Game { ref adventurer: Adventurer, weapon_combat_spec: CombatSpec, adventurer_id: felt252, - adventurer_entropy: u128, + adventurer_entropy: felt252, beast: Beast, beast_seed: u128, game_entropy: felt252, @@ -1765,7 +1807,9 @@ mod Game { attack_location_rnd: u128, ) -> BattleDetails { // beasts attack random location on adventurer - let attack_location = AdventurerUtils::get_random_attack_location(attack_location_rnd); + let attack_location = AdventurerUtils::get_random_attack_location( + attack_location_rnd.into() + ); // get armor at attack location let armor = adventurer.get_item_at_slot(attack_location); @@ -1804,7 +1848,7 @@ mod Game { ref self: ContractState, ref adventurer: Adventurer, adventurer_id: felt252, - adventurer_entropy: u128, + adventurer_entropy: felt252, game_entropy: felt252, beast_seed: u128, beast: Beast, @@ -2010,7 +2054,7 @@ mod Game { items_to_purchase: Array, ) { // get adventurer entropy - let adventurer_entropy: u128 = _unpack_adventurer_meta(@self, adventurer_id).entropy.into(); + let adventurer_entropy = _get_adventurer_entropy(@self, adventurer_id); // mutable array for returning items that need to be equipped as part of this purchase let mut unequipped_items = ArrayTrait::::new(); @@ -2202,9 +2246,13 @@ mod Game { adventurer_id: felt252, stat_boosts: Stats ) { - // remove stat boosts + // remove item stat boosts adventurer.remove_stat_boosts(stat_boosts); + // remove starting stats so they don't consume storage space + let starting_stats = _unpack_adventurer_meta(@self, adventurer_id).starting_stats; + adventurer.remove_stat_boosts(starting_stats); + // save adventurer self._adventurer.write(adventurer_id, adventurer); } @@ -2310,7 +2358,8 @@ mod Game { let adventurer_state = AdventurerState { owner: get_caller_address(), adventurer_id, adventurer }; - let adventurer_entropy: u128 = _unpack_adventurer_meta(@self, adventurer_id).entropy.into(); + + let adventurer_entropy = _get_adventurer_entropy(@self, adventurer_id); // emit level up event if (new_level > previous_level) { @@ -2396,7 +2445,7 @@ mod Game { ); } fn _assert_item_is_available( - adventurer_entropy: u128, stat_points_available: u8, adventurer_xp: u16, item_id: u8 + adventurer_entropy: felt252, stat_points_available: u8, adventurer_xp: u16, item_id: u8 ) { assert( ImplMarket::is_item_available( @@ -2514,7 +2563,7 @@ mod Game { #[inline(always)] fn _get_items_on_market( self: @ContractState, - adventurer_entropy: u128, + adventurer_entropy: felt252, adventurer_xp: u16, adventurer_stat_points: u8 ) -> Array { @@ -2523,7 +2572,7 @@ mod Game { #[inline(always)] fn _get_items_on_market_by_slot( self: @ContractState, - adventurer_entropy: u128, + adventurer_entropy: felt252, adventurer_xp: u16, adventurer_stat_points: u8, slot: Slot @@ -2536,7 +2585,7 @@ mod Game { #[inline(always)] fn _get_items_on_market_by_tier( self: @ContractState, - adventurer_entropy: u128, + adventurer_entropy: felt252, adventurer_xp: u16, adventurer_stat_points: u8, tier: Tier @@ -2568,7 +2617,7 @@ mod Game { assert(adventurer.beast_health != 0, messages::NOT_IN_BATTLE); // get adventurer entropy - let adventurer_entropy: u128 = _unpack_adventurer_meta(self, adventurer_id).entropy.into(); + let adventurer_entropy = _get_adventurer_entropy(self, adventurer_id); // get beast and beast seed let (beast, beast_seed) = adventurer.get_beast(adventurer_entropy); @@ -2639,18 +2688,77 @@ mod Game { } #[inline(always)] - fn _get_adventurer_entropy(self: @ContractState, adventurer_id: felt252) -> u128 { - _unpack_adventurer_meta(self, adventurer_id).entropy + fn _get_adventurer_entropy(self: @ContractState, adventurer_id: felt252) -> felt252 { + // get the block the adventurer started the game on + let start_block = _unpack_adventurer_meta(self, adventurer_id).start_block; + + // adventurer_ + let chain_id = starknet::get_execution_info().unbox().tx_info.unbox().chain_id; + if chain_id == MAINNET_CHAIN_ID { + _get_mainnet_entropy(adventurer_id, start_block) + } else if chain_id == GOERLI_CHAIN_ID { + _get_testnet_entropy(adventurer_id, start_block) + } else { + _get_basic_entropy(adventurer_id, start_block) + } + } + + #[inline(always)] + fn _get_mainnet_entropy(adventurer_id: felt252, start_block: u64) -> felt252 { + ImplAdventurer::get_entropy( + adventurer_id, starknet::get_block_hash_syscall(start_block + 1).unwrap_syscall() + ) + } + + #[inline(always)] + fn _get_testnet_entropy(adventurer_id: felt252, start_block: u64) -> felt252 { + ImplAdventurer::get_entropy( + adventurer_id, starknet::get_block_hash_syscall(start_block - 9).unwrap_syscall() + ) + } + + #[inline(always)] + fn _get_basic_entropy(adventurer_id: felt252, start_block: u64) -> felt252 { + let mut hash_span = ArrayTrait::new(); + hash_span.append(start_block.into()); + hash_span.append(adventurer_id); + poseidon_hash_span(hash_span.span()) + } + + fn _get_reveal_block(self: @ContractState, adventurer_id: felt252) -> u64 { + let start_block = _unpack_adventurer_meta(self, adventurer_id).start_block; + let chain_id = starknet::get_execution_info().unbox().tx_info.unbox().chain_id; + // wait a full 11 blocks on mainnet so that we can do the minimum current_block - 10 and still get a future block + if chain_id == MAINNET_CHAIN_ID { + start_block + 11 + } else if chain_id == GOERLI_CHAIN_ID { + // on testnet just wait a single block + start_block + 1 + } else { + // devnet/testing, just return the start block + start_block + } + } + + fn _get_reveal_block_delay() -> u64 { + let chain_id = starknet::get_execution_info().unbox().tx_info.unbox().chain_id; + if chain_id == MAINNET_CHAIN_ID { + 11 + } else if chain_id == GOERLI_CHAIN_ID { + 1 + } else { + 0 + } } // @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 game entropy + // @return (felt252, u64) - adventurer entropy and game entropy #[inline(always)] fn _get_adventurer_and_game_entropy( self: @ContractState, adventurer_id: felt252 - ) -> (u128, GameEntropy) { + ) -> (felt252, GameEntropy) { (_get_adventurer_entropy(self, adventurer_id), _load_game_entropy(self)) } @@ -2721,7 +2829,8 @@ mod Game { #[derive(Drop, starknet::Event)] struct StartGame { adventurer_state: AdventurerState, - adventurer_meta: AdventurerMetadata + adventurer_meta: AdventurerMetadata, + reveal_block: u64, } #[derive(Drop, Serde, starknet::Event)] @@ -3014,7 +3123,8 @@ mod Game { let adventurer_state = AdventurerState { owner: get_caller_address(), adventurer_id, adventurer }; - self.emit(StartGame { adventurer_state, adventurer_meta }); + let reveal_block = _get_reveal_block(@self, adventurer_id); + self.emit(StartGame { adventurer_state, adventurer_meta, reveal_block }); } fn __event_Discovery( diff --git a/contracts/game/src/tests/test_game.cairo b/contracts/game/src/tests/test_game.cairo index 3f8516224..4a1c8f79e 100644 --- a/contracts/game/src/tests/test_game.cairo +++ b/contracts/game/src/tests/test_game.cairo @@ -571,7 +571,7 @@ mod tests { // 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 + strength: 0, dexterity: 1, vitality: 0, intelligence: 0, wisdom: 0, charisma: 0, luck: 0 }; game.upgrade(ADVENTURER_ID, 0, stat_upgrades, shopping_cart.clone()); @@ -667,7 +667,7 @@ mod tests { let mut game = new_adventurer_lvl2(1000); // get items from market - let market_items = @game.get_items_on_market(ADVENTURER_ID); + let market_items = @game.get_items_on_market_by_tier(ADVENTURER_ID, 5); // get first item on the market let item_id = *market_items.at(0); @@ -886,10 +886,10 @@ mod tests { #[available_gas(92000000)] fn test_equip() { // start game on level 2 so we have access to the market - let mut game = new_adventurer_lvl2(1001); + let mut game = new_adventurer_lvl2(1002); // get items from market - let market_items = @game.get_items_on_market(ADVENTURER_ID); + let market_items = @game.get_items_on_market_by_tier(ADVENTURER_ID, 5); // get first item on the market let item_id = *market_items.at(0); @@ -917,40 +917,27 @@ mod tests { // if the item is a weapon and we haven't purchased a weapon yet // and the item is a tier 4 or 5 item // repeat this for everything - if (item_slot == Slot::Weapon(()) - && purchased_weapon == 0 - && (item_tier == Tier::T5(())) - && item_id != 12) { + if (item_slot == Slot::Weapon(()) && purchased_weapon == 0 && item_id != 12) { purchased_items.append(item_id); shopping_cart.append(ItemPurchase { item_id: item_id, equip: false }); purchased_weapon = item_id; - } else if (item_slot == Slot::Chest(()) - && purchased_chest == 0 - && item_tier == Tier::T5(())) { + } else if (item_slot == Slot::Chest(()) && purchased_chest == 0) { purchased_items.append(item_id); shopping_cart.append(ItemPurchase { item_id: item_id, equip: false }); purchased_chest = item_id; - } else if (item_slot == Slot::Head(()) - && purchased_head == 0 - && item_tier == Tier::T5(())) { + } else if (item_slot == Slot::Head(()) && purchased_head == 0) { purchased_items.append(item_id); shopping_cart.append(ItemPurchase { item_id: item_id, equip: false }); purchased_head = item_id; - } else if (item_slot == Slot::Waist(()) - && purchased_waist == 0 - && item_tier == Tier::T5(())) { + } else if (item_slot == Slot::Waist(()) && purchased_waist == 0) { purchased_items.append(item_id); shopping_cart.append(ItemPurchase { item_id: item_id, equip: false }); purchased_waist = item_id; - } else if (item_slot == Slot::Foot(()) - && purchased_foot == 0 - && item_tier == Tier::T5(())) { + } else if (item_slot == Slot::Foot(()) && purchased_foot == 0) { purchased_items.append(item_id); shopping_cart.append(ItemPurchase { item_id: item_id, equip: false }); purchased_foot = item_id; - } else if (item_slot == Slot::Hand(()) - && purchased_hand == 0 - && item_tier == Tier::T5(())) { + } else if (item_slot == Slot::Hand(()) && purchased_hand == 0) { purchased_items.append(item_id); shopping_cart.append(ItemPurchase { item_id: item_id, equip: false }); purchased_hand = item_id; @@ -1750,7 +1737,7 @@ mod tests { #[available_gas(75000000)] fn test_upgrade_adventurer() { // deploy and start new game - let mut game = new_adventurer_lvl2(1004); + let mut game = new_adventurer_lvl2(1006); // get original adventurer state let adventurer = game.get_adventurer(ADVENTURER_ID); @@ -1817,7 +1804,7 @@ mod tests { #[available_gas(570778841)] #[should_panic(expected: ('rate limit exceeded', 'ENTRYPOINT_FAILED'))] fn test_exceed_rate_limit() { - let starting_block = 1000; + let starting_block = 1003; let mut game = new_adventurer_lvl2(starting_block); let shopping_cart = ArrayTrait::::new(); let stat_upgrades = Stats { @@ -1835,7 +1822,7 @@ mod tests { #[test] #[available_gas(944417814)] fn test_exceed_rate_limit_block_rotation() { - let starting_block = 1000; + let starting_block = 1003; let mut game = new_adventurer_lvl2(starting_block); let shopping_cart = ArrayTrait::::new(); let stat_upgrades = Stats { diff --git a/contracts/market/src/market.cairo b/contracts/market/src/market.cairo index 8a5b90d9d..48627ac89 100644 --- a/contracts/market/src/market.cairo +++ b/contracts/market/src/market.cairo @@ -51,7 +51,7 @@ impl ImplMarket of IMarket { // @param adventurer_stat_points The stat points of the adventurer used for market size // @return An array of items that are available on the market. fn get_market_items( - adventurer_entropy: u128, adventurer_xp: u16, adventurer_stat_points: u8 + adventurer_entropy: felt252, adventurer_xp: u16, adventurer_stat_points: u8 ) -> Array { let market_size = ImplMarket::get_market_size(adventurer_stat_points); if market_size >= NUM_ITEMS.into() { @@ -82,7 +82,7 @@ impl ImplMarket of IMarket { // @param slot The slot of the item to filter by // @return An array of items that are available on the market that match the slot fn get_items_by_slot( - adventurer_entropy: u128, adventurer_xp: u16, adventurer_stat_points: u8, slot: Slot + adventurer_entropy: felt252, adventurer_xp: u16, adventurer_stat_points: u8, slot: Slot ) -> Array { let (seed, offset) = ImplMarket::get_market_seed_and_offset( adventurer_entropy, adventurer_xp @@ -112,7 +112,7 @@ impl ImplMarket of IMarket { // @param tier The tier of the item to filter by // @return An array of items that are available on the market that match the tier fn get_items_by_tier( - adventurer_entropy: u128, adventurer_xp: u16, adventurer_stat_points: u8, tier: Tier + adventurer_entropy: felt252, adventurer_xp: u16, adventurer_stat_points: u8, tier: Tier ) -> Array { let (seed, offset) = ImplMarket::get_market_seed_and_offset( adventurer_entropy, adventurer_xp @@ -159,7 +159,7 @@ impl ImplMarket of IMarket { // @param item_id The item id to check for availability // @return A boolean indicating if the item is available on the market. fn is_item_available( - adventurer_entropy: u128, adventurer_xp: u16, adventurer_stat_points: u8, item_id: u8 + adventurer_entropy: felt252, adventurer_xp: u16, adventurer_stat_points: u8, item_id: u8 ) -> bool { // if the size of the market is larger than the number of items let market_size = ImplMarket::get_market_size(adventurer_stat_points); @@ -206,9 +206,9 @@ impl ImplMarket of IMarket { // @param adventurer_entropy The entropy of the adventurer used for randomness. // @param xp The experience points of the adventurer. // @return A 128bit hash used for market seed and an 8bit offset used for market offset.z - fn get_market_seed_and_offset(adventurer_entropy: u128, xp: u16) -> (u256, u8) { + fn get_market_seed_and_offset(adventurer_entropy: felt252, xp: u16) -> (u256, u8) { let mut hash_span = ArrayTrait::new(); - hash_span.append(adventurer_entropy.into()); + hash_span.append(adventurer_entropy); hash_span.append(xp.into()); ImplMarket::split_hash_into_seed_and_offset(poseidon_hash_span(hash_span.span())) }