diff --git a/contracts/loot/constants/combat.cairo b/contracts/loot/constants/combat.cairo index 89b4bfcc..a51a1079 100755 --- a/contracts/loot/constants/combat.cairo +++ b/contracts/loot/constants/combat.cairo @@ -8,18 +8,9 @@ from contracts.loot.constants.item import ItemMaterial, Material, ItemType, Type -// psuedo enum +// used for elemental bonuses namespace WeaponEfficacy { const Low = 0; const Medium = 1; const High = 2; } - -// Controls damage multiplier -// NOTE: @loothero I've increased by 1, if low is 0, then all damage will be 0 -// not sure this is what we want? -namespace WeaponEfficiacyDamageMultiplier { - const Low = 1; - const Medium = 2; - const High = 3; -} diff --git a/contracts/loot/loot/stats/combat.cairo b/contracts/loot/loot/stats/combat.cairo index a1fb81b2..09a584c1 100755 --- a/contracts/loot/loot/stats/combat.cairo +++ b/contracts/loot/loot/stats/combat.cairo @@ -14,14 +14,18 @@ from starkware.cairo.common.registers import get_label_location from starkware.cairo.common.pow import pow from contracts.loot.constants.item import Item, Type, ItemIds, Slot -from contracts.loot.constants.combat import WeaponEfficacy, WeaponEfficiacyDamageMultiplier +from contracts.loot.constants.combat import WeaponEfficacy from contracts.loot.beast.stats.beast import BeastStats from contracts.loot.beast.library import BeastLib from contracts.loot.loot.stats.item import ItemStats from contracts.loot.constants.adventurer import AdventurerState from contracts.loot.constants.beast import Beast, BeastStatic, BeastDynamic from contracts.loot.constants.obstacle import Obstacle, ObstacleUtils -from contracts.loot.utils.constants import MAX_CRITICAL_HIT_CHANCE +from contracts.loot.utils.constants import ( + MAX_CRITICAL_HIT_CHANCE, + ITEM_RANK_MAX, + MINIMUM_ATTACK_DAMGE, +) namespace CombatStats { func weapon_vs_armor_efficacy{syscall_ptr: felt*, range_check_ptr}( @@ -75,76 +79,168 @@ namespace CombatStats { dw WeaponEfficacy.Medium; } - func get_attack_effectiveness{syscall_ptr: felt*, range_check_ptr}( - attack_effectiveness: felt, base_weapon_damage: felt + // @title Get Elemental Bonus + // @dev Calculates the damage modifier based on the attack and armor types. + // + // @param attack_type The type of the attacking element. + // @param armor_type The type of the defending element. + // @param original_damage The original damage value. + // @return damage The modified damage value based on the attack and armor types. + func adjust_damage_for_elemental{syscall_ptr: felt*, range_check_ptr}( + attack_type: felt, armor_type: felt, original_damage: felt ) -> (damage: felt) { alloc_locals; + // Use 50% of the original damage as our damage modifier which will give us + // access to -50% (ineffective elemental), 100% (neutral), 150% (effective elemental) + let (damage_boost_base, _) = unsigned_div_rem(original_damage, 2); + + // get attack effectiveness + let (attack_effectiveness) = weapon_vs_armor_efficacy(attack_type, armor_type); + + // if weapon is ineffective against armor if (attack_effectiveness == WeaponEfficacy.Low) { - return (base_weapon_damage * WeaponEfficiacyDamageMultiplier.Low,); + // return half the damage + let half_damage = original_damage - damage_boost_base; + return (half_damage,); } + // if weapon is neutral against armor if (attack_effectiveness == WeaponEfficacy.Medium) { - return (base_weapon_damage * WeaponEfficiacyDamageMultiplier.Medium,); + // return original damage + return (original_damage,); } + // if weapon is effective against armor if (attack_effectiveness == WeaponEfficacy.High) { - return (base_weapon_damage * WeaponEfficiacyDamageMultiplier.High,); + // return damage + 50% + let one_and_a_half_damage = original_damage + damage_boost_base; + return (one_and_a_half_damage,); } - return (0,); + // fall through, return original damage + return (original_damage,); } - // calculate_damage_from_weapon calculates the damage a weapon inflicts against a specific piece of armor - // parameters: Item weapon, Item armor - // returns: damage - func calculate_damage{syscall_ptr: felt*, range_check_ptr}( - attack_type: felt, - attack_rank: felt, - attack_greatness: felt, - armor_type: felt, - armor_rank: felt, - armor_greatness: felt, - entity_level: felt, - strength: felt, - luck: felt, - rnd: felt, - ) -> (damage: felt) { + // @title Name Prefix_1 Match Bonus Calculation + // @dev Calculates the damage bonus based on a match between attack_name_prefix1 and armor_name_prefix1. + // @param original_damage The original damage value. + // @param damage_multiplier The damage multiplier. + // @param attack_name_prefix1 The name prefix 1 of the attack. + // @param armor_name_prefix1 The name prefix 1 of the armor. + // @return damage_bonus The calculated damage bonus. + func get_name_prefix1_match_bonus{syscall_ptr: felt*, range_check_ptr}( + original_damage: felt, + damage_multiplier: felt, + attack_name_prefix1: felt, + armor_name_prefix1: felt, + ) -> (damage_bonus: felt) { alloc_locals; - const rank_ceiling = 6; - const minimum_damage = 3; + let no_damage_bonus = 0; + // if weapon doesn't have a prefix1 + if (attack_name_prefix1 == 0) { + // return 0 + return (no_damage_bonus,); + } - // use weapon rank and greatness to give every item a damage rating of 0-100 - // TODO: add item weight into damage calculation - let attack_hp = (rank_ceiling - attack_rank) * attack_greatness; + // Odds of a prefix1 (namePrefix) match is 1/68 + if (attack_name_prefix1 == armor_name_prefix1) { + // Apply a significant damage boost of 4x, 5x, 6x, 7x + let name_prefix1_boost = original_damage * (damage_multiplier + 4); + return (name_prefix1_boost,); + } - // use armor rank and greatness to give armor a defense rating of 0-100 - // TODO: add item weight into strength calculation - let defense_hp = (rank_ceiling - armor_rank) * armor_greatness; + return (no_damage_bonus,); + } - // if armor hitpoints is less than weapon hitpoints, then damage was dealt - let dealt_below_minimum_damage = is_le_felt(defense_hp + minimum_damage, attack_hp); - if (dealt_below_minimum_damage == TRUE) { - // then we use that for our weapon damage - tempvar temp_weapon_damage = attack_hp - defense_hp; - } else { - // if base damage is 0 or below, use minimum damage of 3 - tempvar temp_weapon_damage = minimum_damage; + // @title Name Prefix_2 Match Bonus Calculation + // @dev Calculates the damage bonus based on a match between attack_name_prefix2 and armor_name_prefix2. + // @param original_damage The original damage value. + // @param damage_multiplier The damage multiplier. + // @param attack_name_prefix2 The name prefix 2 of the attack. + // @param armor_name_prefix2 The name prefix 2 of the armor. + // @return damage_bonus The calculated damage bonus. + func get_name_prefix2_match_bonus{syscall_ptr: felt*, range_check_ptr}( + original_damage: felt, + damage_multiplier: felt, + attack_name_prefix2: felt, + armor_name_prefix2: felt, + ) -> (damage_bonus: felt) { + alloc_locals; + + let no_damage_bonus = 0; + // if weapon doesn't have a prefix2 + if (attack_name_prefix2 == 0) { + // return 0 + return (no_damage_bonus,); } - let weapon_damage = temp_weapon_damage; + // Odds of a prefix2 match (nameSuffix) match is 1/18 + if (attack_name_prefix2 == armor_name_prefix2) { + // Apply a less significant damage boost of 1.25x, 1.5x, 1.75x, 2x - // account for elemental effectiveness - let (attack_effectiveness) = weapon_vs_armor_efficacy(attack_type, armor_type); - let (total_weapon_damage) = get_attack_effectiveness(attack_effectiveness, weapon_damage); + // Divide the original damage by 4 to get a 25% base boost + let (damage_boost_base, _) = unsigned_div_rem(original_damage, 4); + + // Multiply the quarter of the original damage by a multiplier (1-4 inclusive) + let name_prefix2_bonus = damage_boost_base * (damage_multiplier + 1); + + return (name_prefix2_bonus,); + } + + return (no_damage_bonus,); + } + + // @title Name Match Bonus Calculation + // @dev Calculates the total damage bonus based on matches between attack_name_prefix1, attack_name_prefix2, + // armor_name_prefix1, and armor_name_prefix2. + // @param original_damage The original damage value. + // @param attack_name_prefix1 The name prefix 1 of the attack. + // @param attack_name_prefix2 The name prefix 2 of the attack. + // @param armor_name_prefix1 The name prefix 1 of the armor. + // @param armor_name_prefix2 The name prefix 2 of the armor. + // @param rnd A random value used for calculations. + // @return damage_bonus The calculated total damage bonus. + func get_name_match_bonus{syscall_ptr: felt*, range_check_ptr}( + original_damage: felt, + attack_name_prefix1: felt, + attack_name_prefix2: felt, + armor_name_prefix1: felt, + armor_name_prefix2: felt, + rnd: felt, + ) -> (damage_bonus: felt) { + alloc_locals; + + let (_, damage_multplier) = unsigned_div_rem(rnd, 4); + let (name_prefix1_bonus) = get_name_prefix1_match_bonus( + original_damage, damage_multplier, attack_name_prefix1, armor_name_prefix1 + ); + + let (name_prefix2_bonus) = get_name_prefix2_match_bonus( + original_damage, damage_multplier, attack_name_prefix2, armor_name_prefix2 + ); + + let total_name_bonus = name_prefix1_bonus + name_prefix2_bonus; + + return (total_name_bonus,); + } + + // @title Is Critical Hit + // @dev Determines whether a hit is a critical hit based on luck and random values. + // @param original_damage The original damage value. + // @param luck The luck attribute of the entity. + // @param rnd A random value used for calculations. + // @return is_critical_hit A boolean indicating whether the hit is a critical hit. + func is_critical_hit{syscall_ptr: felt*, range_check_ptr}(luck: felt, rnd: felt) -> ( + is_critical_hit: felt + ) { + alloc_locals; // @distracteddev: calculate whether hit is critical and add luck // 0-9 = 1 in 6, 10-19 = 1 in 5, 20-29 = 1 in 4, 30-39 = 1 in 3, 40-46 = 1 in 2 // formula = damage * (1.5 * rand(6 - (luck/10)) - let (critical_hit_chance, _) = unsigned_div_rem(luck, 10); - // there is no implied cap on item greatness so luck is unbound // but for purposes of critical damage calculation, the max critical hit chance is 5 let critical_hit_chance_within_range = is_le(critical_hit_chance, MAX_CRITICAL_HIT_CHANCE); @@ -160,19 +256,137 @@ namespace CombatStats { let (_, critical_rand) = unsigned_div_rem(rnd, (6 - critical_hit_chance)); let critical_hit = is_le(critical_rand, 0); - // @distracteddev: provide some multi here with adventurer level: e.g. damage + (1 + ((1 - level) * 0.1)) - let (adventurer_level_damage) = calculate_entity_level_boost( - total_weapon_damage, entity_level + strength + return (critical_hit,); + } + + // @title Get Critical Hit Bonus + // @dev Calculates the damage bonus for a critical hit based on luck and random values. + // @param original_damage The original damage value. + // @param luck The luck attribute of the entity. + // @param rnd A random value used for calculations. + // @return damage_bonus The calculated damage bonus for a critical hit. + func get_critical_hit_bonus{syscall_ptr: felt*, range_check_ptr}( + original_damage: felt, luck: felt, rnd: felt + ) -> (damage_bonus: felt) { + alloc_locals; + + let (is_critical_hit_) = is_critical_hit(luck, rnd); + if (is_critical_hit_ == TRUE) { + let (critical_damage_dealt) = calculate_critical_damage(original_damage, rnd); + return (critical_damage_dealt,); + } + + return (0,); + } + + // @title Get Base Damage + // @dev Calculates the base damage of an attack based on the ranks and greatness of the attacking and defending items. + // @param attack_rank The rank of the attacking item. + // @param attack_greatness The greatness of the attacking item. + // @param armor_rank The rank of the defending item. + // @param armor_greatness The greatness of the defending item. + // @return damage The calculated base damage of the attack. + func get_base_damage{syscall_ptr: felt*, range_check_ptr}( + attack_rank: felt, attack_greatness: felt, armor_rank: felt, armor_greatness: felt + ) -> (damage: felt) { + alloc_locals; + + // use weapon rank and greatness to give every item a damage rating of 0-100 + let attack_hp = (ITEM_RANK_MAX - attack_rank) * attack_greatness; + + // use armor rank and greatness to give armor a defense rating of 0-100 + let defense_hp = (ITEM_RANK_MAX - armor_rank) * armor_greatness; + + // if armor hitpoints is less than weapon hitpoints, then damage was dealt + let dealt_below_minimum_damage = is_le_felt(defense_hp + MINIMUM_ATTACK_DAMGE, attack_hp); + if (dealt_below_minimum_damage == TRUE) { + // then we use that for our weapon damage + let damage_dealt = attack_hp - defense_hp; + return (damage_dealt,); + } + + // fall through to minimum damage + return (MINIMUM_ATTACK_DAMGE,); + } + + // @title Core damage calculation + // @dev Calculates the damage inflicted by an attack based on various parameters. + // @param attack_type The type of attack. + // @param attack_rank The rank of the attacking item. + // @param attack_greatness The greatness of the attacking item. + // @param attack_name_prefix1 The name prefix 1 of the attacking item. + // @param attack_name_prefix2 The name prefix 2 of the attacking item. + // @param armor_type The type of armor. + // @param armor_rank The rank of the armor. + // @param armor_greatness The greatness of the armor. + // @param armor_name_prefix1 The name prefix 1 of the armor. + // @param armor_name_prefix2 The name prefix 2 of the armor. + // @param entity_level The level of the entity. + // @param strength The strength of the entity. + // @param luck The luck of the entity. + // @param rnd A random value used for calculations. + // @return damage The calculated damage. + func calculate_damage{syscall_ptr: felt*, range_check_ptr}( + attack_type: felt, + attack_rank: felt, + attack_greatness: felt, + attack_name_prefix1: felt, + attack_name_prefix2: felt, + armor_type: felt, + armor_rank: felt, + armor_greatness: felt, + armor_name_prefix1: felt, + armor_name_prefix2: felt, + entity_level: felt, + strength: felt, + luck: felt, + rnd: felt, + ) -> (damage: felt) { + alloc_locals; + + // get base damage based on rank and greatness of attack weapon and armor + let (base_damage) = get_base_damage( + attack_rank, attack_greatness, armor_rank, armor_greatness + ); + + // adjust base damage for elemental effect + let (base_damage_with_elemental) = adjust_damage_for_elemental( + attack_type, armor_type, base_damage + ); + + // get damage bonus based on item and armor names + let (name_damage_bonus) = get_name_match_bonus( + base_damage_with_elemental, + attack_name_prefix1, + attack_name_prefix2, + armor_name_prefix1, + armor_name_prefix2, + rnd, ); - let (critical_damage_dealt) = calculate_critical_damage( - adventurer_level_damage, critical_hit, rnd + + // get damage bonus for critical hit + let (critical_hit_bonus) = get_critical_hit_bonus(base_damage_with_elemental, luck, rnd); + + // get damage bonus for entity stats (level and strength) + let (entity_level_bonus) = get_entity_level_bonus( + base_damage_with_elemental, entity_level + strength ); - return (critical_damage_dealt,); + + // add bonuses to base damage + let final_damage = base_damage_with_elemental + critical_hit_bonus + name_damage_bonus + + entity_level_bonus; + + // return resulting damage + return (final_damage,); } - // calculate_damage_from_weapon calculates the damage a weapon inflicts against a specific piece of armor - // parameters: Item weapon, Item armor - // returns: damage + // @title Calculate Damage from Weapon + // @dev Calculates the damage inflicted by a weapon against a specific piece of armor. + // @param weapon The attacking weapon. + // @param armor The defending armor. + // @param unpacked_adventurer The state of the adventurer. + // @param rnd A random value used for calculations. + // @return damage The calculated damage. func calculate_damage_from_weapon{syscall_ptr: felt*, range_check_ptr}( weapon: Item, armor: Item, unpacked_adventurer: AdventurerState, rnd: felt ) -> (damage: felt) { @@ -189,9 +403,13 @@ namespace CombatStats { attack_type, weapon.Rank, weapon.Greatness, + weapon.Prefix_1, + weapon.Prefix_2, armor_type, armor.Rank, armor.Greatness, + armor.Prefix_1, + armor.Prefix_2, unpacked_adventurer.Level, unpacked_adventurer.Strength, unpacked_adventurer.Luck, @@ -202,7 +420,13 @@ namespace CombatStats { return (damage_dealt,); } - // Calculates damage dealt from a beast by converting beast into a Loot weapon and calling calculate_damage_from_weapon + // @title Calculate Damage from Beast + // @dev Calculates the damage inflicted by a beast by converting it into a Loot weapon. + // @param beast The attacking beast. + // @param armor The defending armor. + // @param critical_damage_rnd A random value used for calculating critical damage. + // @param adventurer_level The level of the adventurer. + // @return damage The calculated damage. func calculate_damage_from_beast{syscall_ptr: felt*, range_check_ptr}( beast: Beast, armor: Item, critical_damage_rnd: felt, adventurer_level: felt ) -> (damage: felt) { @@ -221,9 +445,13 @@ namespace CombatStats { attack_type, beast.Rank, beast.Level, + beast.Prefix_1, + beast.Prefix_2, Type.Armor.generic, armor.Rank, armor.Greatness, + armor.Prefix_1, + armor.Prefix_2, 1, 0, beast_luck, @@ -238,9 +466,13 @@ namespace CombatStats { attack_type, beast.Rank, beast.Level, + beast.Prefix_1, + beast.Prefix_2, armor_type, armor.Rank, armor.Greatness, + armor.Prefix_1, + armor.Prefix_2, 1, 0, beast_luck, @@ -249,7 +481,13 @@ namespace CombatStats { } } - // Calculates damage dealt to a beast by using equipped Loot weapon and calling calculate_damage_from_weapon + // @title Calculate Damage to Beast + // @dev Calculates the damage inflicted to a beast using an equipped Loot weapon. + // @param beast The defending beast. + // @param weapon The attacking weapon. + // @param unpacked_adventurer The state of the adventurer. + // @param rnd A random value used for calculations. + // @return damage The calculated damage. func calculate_damage_to_beast{syscall_ptr: felt*, range_check_ptr}( beast: Beast, weapon: Item, unpacked_adventurer: AdventurerState, rnd: felt ) -> (damage: felt) { @@ -265,9 +503,13 @@ namespace CombatStats { Type.Weapon.generic, weapon.Rank, 1, + 0, + 0, armor_type, beast.Rank, beast.Level, + beast.Prefix_1, + beast.Prefix_2, unpacked_adventurer.Level, unpacked_adventurer.Strength, unpacked_adventurer.Luck, @@ -279,9 +521,13 @@ namespace CombatStats { weapon.Type, weapon.Rank, weapon.Greatness, + weapon.Prefix_1, + weapon.Prefix_2, armor_type, beast.Rank, beast.Level, + beast.Prefix_1, + beast.Prefix_2, unpacked_adventurer.Level, unpacked_adventurer.Strength, unpacked_adventurer.Luck, @@ -290,7 +536,11 @@ namespace CombatStats { } } - // Calculate damage from an obstacle + // @title Calculate Damage from Obstacle + // @dev Calculates the damage inflicted by an obstacle against a specific piece of armor. + // @param obstacle The attacking obstacle. + // @param armor The defending armor. + // @return damage The calculated damage. func calculate_damage_from_obstacle{syscall_ptr: felt*, range_check_ptr}( obstacle: Obstacle, armor: Item ) -> (damage: felt) { @@ -305,9 +555,13 @@ namespace CombatStats { obstacle.Type, obstacle.Rank, obstacle.Greatness, + obstacle.Prefix_1, + obstacle.Prefix_2, Type.Armor.generic, armor.Rank, armor.Greatness, + armor.Prefix_1, + armor.Prefix_2, 1, 0, 0, @@ -319,9 +573,13 @@ namespace CombatStats { obstacle.Type, obstacle.Rank, obstacle.Greatness, + obstacle.Prefix_1, + obstacle.Prefix_2, armor_type, armor.Rank, armor.Greatness, + armor.Prefix_1, + armor.Prefix_2, 1, 0, 0, @@ -330,6 +588,11 @@ namespace CombatStats { } } + // @title Calculate XP Earned + // @dev Calculates the amount of XP earned based on the rank and level. + // @param rank The rank of the entity. + // @param level The level of the entity. + // @return xp_earned The amount of XP earned. func calculate_xp_earned{syscall_ptr: felt*, range_check_ptr}(rank: felt, level: felt) -> ( xp_earned: felt ) { @@ -386,9 +649,10 @@ namespace CombatStats { // @param damage The base damage. // @param entity_level The level of the entity. // @return entity_level_damage The calculated damage after applying the level boost. - func calculate_entity_level_boost{syscall_ptr: felt*, range_check_ptr}( + func get_entity_level_bonus{syscall_ptr: felt*, range_check_ptr}( damage: felt, entity_level: felt ) -> (entity_level_damage: felt) { + // @distracteddev: provide some multi here with adventurer level: e.g. damage + (1 + ((1 - level) * 0.1)) let format_level_boost = damage * (90 + (entity_level * 10)); let (entity_level_damage, _) = unsigned_div_rem(format_level_boost, 100); return (entity_level_damage,); @@ -401,27 +665,21 @@ namespace CombatStats { // @param rnd Random number used to determine the damage boost multiplier. // @return critical_damage The calculated critical damage. func calculate_critical_damage{syscall_ptr: felt*, range_check_ptr}( - original_damage: felt, critical_hit: felt, rnd: felt - ) -> (crtical_damage: felt) { - // if adventurer dealt critical hit - if (critical_hit == TRUE) { - // divide the damage by four to get base damage boost - let (damage_boost_base, _) = unsigned_div_rem(original_damage, 4); + original_damage: felt, rnd: felt + ) -> (crtical_damage_bonus: felt) { + // divide the damage by four to get base damage boost + let (damage_boost_base, _) = unsigned_div_rem(original_damage, 4); - // damage multplier is 1-4 which will equate to a 25-100% damage boost - let (_, damage_multplier) = unsigned_div_rem(rnd, 4); + // damage multplier is 1-4 which will equate to a 25-100% damage boost + let (_, damage_multplier) = unsigned_div_rem(rnd, 4); - // multiply base damage boost (25% of original damage) by damage multiplier (1-4) - let critical_hit_damage_bonus = damage_boost_base * (damage_multplier + 1); + // multiply base damage boost (25% of original damage) by damage multiplier (1-4) + let critical_hit_damage_bonus = damage_boost_base * (damage_multplier + 1); - // add damage multplier to original damage - let critical_damage = original_damage + critical_hit_damage_bonus; + // add damage multplier to original damage + let critical_damage = original_damage + critical_hit_damage_bonus; - // return critical damage - return (critical_damage,); - } else { - // if no critical hit, return original damage - return (original_damage,); - } + // return critical damage + return (critical_damage,); } } diff --git a/contracts/loot/utils/constants.cairo b/contracts/loot/utils/constants.cairo index 23391d3c..a0564e83 100755 --- a/contracts/loot/utils/constants.cairo +++ b/contracts/loot/utils/constants.cairo @@ -21,3 +21,6 @@ const STARTING_GOLD = 20; const VITALITY_HEALTH_BOOST = 20; const SUFFIX_STAT_BOOST = 3; const MAX_CRITICAL_HIT_CHANCE = 5; // this results in a 1/2 chance of critical hit + +const ITEM_RANK_MAX = 6; +const MINIMUM_ATTACK_DAMGE = 3; diff --git a/tests/protostar/loot/adventurer/test_adventurer.cairo b/tests/protostar/loot/adventurer/test_adventurer.cairo index 3eef1c5f..a2120326 100755 --- a/tests/protostar/loot/adventurer/test_adventurer.cairo +++ b/tests/protostar/loot/adventurer/test_adventurer.cairo @@ -402,8 +402,8 @@ func test_item_stat_boost{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_ assert dexterity_boosted_adventurer.Dexterity = 3; assert intelligence_boosted_adventurer.Intelligence = 3; assert wisdom_boosted_adventurer.Wisdom = 3; - // greatness of necklace is 20, plus it has a prefix and suffix - assert luck_boosted_adventurer.Luck = 23; + // luck scales evenly with greatness of necklace is 20 + assert luck_boosted_adventurer.Luck = 20; return (); } diff --git a/tests/protostar/loot/beast/test_beast.cairo b/tests/protostar/loot/beast/test_beast.cairo index e769e45b..8b475533 100755 --- a/tests/protostar/loot/beast/test_beast.cairo +++ b/tests/protostar/loot/beast/test_beast.cairo @@ -23,6 +23,8 @@ from tests.protostar.loot.test_structs import ( TEST_DAMAGE_OVERKILL, ) +from contracts.loot.constants.item import ItemNamePrefixes, ItemNameSuffixes, ItemSuffixes + @external func test_beast_rank{ syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin* @@ -104,7 +106,7 @@ func test_cast{ }() { alloc_locals; - let (beast: Beast) = TestUtils.create_beast(1, 0); + let (beast: Beast) = TestUtils.create_beast(1, 0, 0, 0); let (_, beast_dynamic: BeastDynamic) = BeastLib.split_data(beast); @@ -121,7 +123,7 @@ func test_deduct_health{ }() { alloc_locals; - let (beast) = TestUtils.create_beast(1, 0); + let (beast) = TestUtils.create_beast(1, 0, 0, 0); let (_, beast_dynamic: BeastDynamic) = BeastLib.split_data(beast); @@ -142,7 +144,7 @@ func test_set_adventurer{ }() { alloc_locals; - let (beast) = TestUtils.create_beast(1, 0); + let (beast) = TestUtils.create_beast(1, 0, 0, 0); let (_, beast_dynamic: BeastDynamic) = BeastLib.split_data(beast); @@ -158,7 +160,7 @@ func test_slain{ syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, bitwise_ptr: BitwiseBuiltin*, range_check_ptr }() { alloc_locals; - let (beast) = TestUtils.create_beast(1, 0); + let (beast) = TestUtils.create_beast(1, 0, 0, 0); let (_, beast_dynamic: BeastDynamic) = BeastLib.split_data(beast); @@ -179,14 +181,14 @@ func test_calculate_critical_damage{ // an input of 3 should deal max critical hit of 2x damage let double_damage_rnd_multiplier = 3; let (max_critical_damage) = CombatStats.calculate_critical_damage( - original_damage, critical_hit, double_damage_rnd_multiplier + original_damage, double_damage_rnd_multiplier ); assert max_critical_damage = 40; // an input of 0 should deal min critical hit of 0.25x damage let minimum_damage_rnd_multiplier = 0; let (min_critical_damage) = CombatStats.calculate_critical_damage( - original_damage, critical_hit, minimum_damage_rnd_multiplier + original_damage, minimum_damage_rnd_multiplier ); assert min_critical_damage = 25; @@ -197,7 +199,7 @@ func test_calculate_critical_damage{ func test_calculate_adventurer_level_boost{ syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr }() { - let (adventurer_level_damage) = CombatStats.calculate_entity_level_boost(20, 1); + let (adventurer_level_damage) = CombatStats.get_entity_level_bonus(20, 1); assert adventurer_level_damage = 20; return (); @@ -216,8 +218,8 @@ func test_calculate_damage_to_beast{ let (adventurer_state) = get_adventurer_state(); - let (greatness_8_mace) = TestUtils.create_item(75, 8); // Greatness 8 Mace (Bludgeon) vs - let (xp_1_basilisk) = TestUtils.create_beast(4, 1); // Level 1 Basilisk (Magic) + let (greatness_8_mace) = TestUtils.create_item_with_names(75, 8, 0, 0, 0); // Greatness 8 Mace (Bludgeon) vs + let (xp_1_basilisk) = TestUtils.create_beast(4, 1, 0, 0); // Level 1 Basilisk (Magic) // attack = 8 * (6-4) * 1 = 16 // defense = 1 * (6-4) = 2 @@ -230,8 +232,53 @@ func test_calculate_damage_to_beast{ // no critical hit assert mace_vs_basilik = 14; - // TODO: Test attacking without weapon (melee) - // let (weapon) = TestUtils.create_zero_item(); // no weapon (melee attack) + // test name prefix1 match + let (greatness_8_mace_agony_bane) = TestUtils.create_item_with_names( + 75, 8, ItemNamePrefixes.Agony, ItemNameSuffixes.Bane, ItemSuffixes.of_Power + ); // Greatness 8 Mace (Bludgeon) vs + let (xp_1_basilisk_agony_song) = TestUtils.create_beast( + 4, 1, ItemNamePrefixes.Agony, ItemNameSuffixes.Song + ); // Level 1 Basilisk (Magic) + + let (mace_vs_basilik_prefix1_match) = CombatStats.calculate_damage_to_beast( + xp_1_basilisk_agony_song, greatness_8_mace_agony_bane, adventurer_state, 1 + ); + + // prefix1 match yields a 3.5x multplier (14 * 3.5 = 49) for the provided random number + assert mace_vs_basilik_prefix1_match = 49; + + // test name prefix2 match + let (greatness_8_mace_blood_song) = TestUtils.create_item_with_names( + 75, 8, ItemNamePrefixes.Blood, ItemNameSuffixes.Song, ItemSuffixes.of_Power + ); // Greatness 8 Mace (Bludgeon) vs + let (xp_1_basilisk_death_song) = TestUtils.create_beast( + 4, 1, ItemNamePrefixes.Death, ItemNameSuffixes.Song + ); // Level 1 Basilisk (Magic) + + let (mace_vs_basilik_prefix2_match) = CombatStats.calculate_damage_to_beast( + xp_1_basilisk_death_song, greatness_8_mace_blood_song, adventurer_state, 1 + ); + + // prefix2 yields smaller bonus as it is more likely + assert mace_vs_basilik_prefix2_match = 16; + + // test name prefix1 and prefix2 match + let (greatness_8_mace_hate_song) = TestUtils.create_item_with_names( + 75, 8, ItemNamePrefixes.Hate, ItemNameSuffixes.Song, ItemSuffixes.of_Power + ); // Greatness 8 Mace (Bludgeon) vs + let (xp_1_basilisk_hate_song) = TestUtils.create_beast( + 4, 1, ItemNamePrefixes.Hate, ItemNameSuffixes.Song + ); // Level 1 Basilisk (Magic) + + let (mace_vs_basilik_prefix1_and_prefix2_match) = CombatStats.calculate_damage_to_beast( + xp_1_basilisk_hate_song, greatness_8_mace_hate_song, adventurer_state, 1 + ); + + // prefix2 yields smaller bonus (more likely) + // in this case the code is expected to take the whole part of base damage (14), divide it by 4 + // which gives a base damage boost of 4. In the case of RND 1, we apply minimum boost of 4 + // original 14 + 4 = 18 + assert mace_vs_basilik_prefix1_and_prefix2_match = 51; return (); } @@ -247,7 +294,7 @@ func test_calculate_damage_from_beast{ }() { alloc_locals; - let (beast) = TestUtils.create_beast(1, 2); // 2XP Pheonix vs + let (beast) = TestUtils.create_beast(1, 2, 0, 0); // 2XP Pheonix vs let (armor) = TestUtils.create_item(50, 1); // Greatness 1 Hard Leather Armor let no_beast_luck = 0; let no_critical_damage_rnd = 1; @@ -270,7 +317,7 @@ func test_calculate_damage_from_beast{ let (local critical_hit_damage) = CombatStats.calculate_damage_from_beast( beast, armor, critical_damage_rnd, max_beast_luck ); - assert critical_hit_damage = 14; + assert critical_hit_damage = 15; // test critical hit luck overflow let overflow_beast_luck = 500; @@ -294,7 +341,7 @@ func test_calculate_damage_from_beast_late_game{ }() { alloc_locals; - let (beast) = TestUtils.create_beast(11, 20); // levl 20 giant (rank 1) + let (beast) = TestUtils.create_beast(11, 20, 0, 0); // levl 20 giant (rank 1) let (cloth_armor) = TestUtils.create_item(18, 20); // lvl 20 silk robe (rank 2) // no chance of critical damage with 0 luck @@ -328,14 +375,12 @@ func test_calculate_damage_from_beast_late_game{ assert hide_damage = 60; - let (no_armor) = TestUtils.create_item_with_names(0, 0, 1, 1, 1); // no item + let (no_armor) = TestUtils.create_item_with_names(0, 0, 0, 0, 1); // no item let critical_hit_rnd = 2; let (no_armor__critical_damage) = CombatStats.calculate_damage_from_beast( beast, no_armor, critical_hit_rnd, max_beast_luck ); - - // 300 base damage * 1.75x critical hit - assert no_armor__critical_damage = 525; + assert no_armor__critical_damage = 561; return (); } diff --git a/tests/protostar/loot/beast/test_beast_logic.cairo b/tests/protostar/loot/beast/test_beast_logic.cairo index d9b482a1..4a539436 100755 --- a/tests/protostar/loot/beast/test_beast_logic.cairo +++ b/tests/protostar/loot/beast/test_beast_logic.cairo @@ -159,8 +159,7 @@ func test_not_kill{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_p assert updated_beast.Id = 2; assert updated_beast.Level = 2; - // since we have overwritten adventurer (made level 2 and so removed wand) we will do no damage - assert updated_beast.Health = 10; + assert updated_beast.Health = 9; let (updated_adventurer) = IAdventurer.get_adventurer_by_id( adventurer_address, adventurer_token_id_1 diff --git a/tests/protostar/loot/stats/test_combat.cairo b/tests/protostar/loot/stats/test_combat.cairo index 7ae94d71..1441b558 100755 --- a/tests/protostar/loot/stats/test_combat.cairo +++ b/tests/protostar/loot/stats/test_combat.cairo @@ -136,88 +136,54 @@ func test_calculate_damage_from_weapon{syscall_ptr: felt*, range_check_ptr}() { let (holy_chestplate_vs_short_sword) = CombatStats.calculate_damage_from_weapon( g3_short_sword, g18_holy_chestplate, adventurer_state, 1 ); - assert holy_chestplate_vs_short_sword = 3; + assert holy_chestplate_vs_short_sword = 4; // greatness 1 scimitar vs greatness 3 silk robe - // This tests the below zero base damage - // we should default to minimum damage setting (3) - // which will get multplied by max elemental modifier (3 at the time of this writing) - // yielding 9 let (g1_scimitar) = TestUtils.create_item(ItemIds.Scimitar, 1); let (g3_silk_robe) = TestUtils.create_item(ItemIds.SilkRobe, 3); let (scimitar_vs_silk_robe) = CombatStats.calculate_damage_from_weapon( g1_scimitar, g3_silk_robe, adventurer_state, 1 ); - assert scimitar_vs_silk_robe = 9; + assert scimitar_vs_silk_robe = 8; // greatness 5 scimitar vs greatness 5 linen robe - // This tests the exact zero base damage (attack_hp == defense_hp) - // we should default to minimum damage setting (3) - // which will get multplied by max elemental modifier (3 at the time of this writing) - // yielding 9 let (g5_scimitar) = TestUtils.create_item(ItemIds.Scimitar, 5); let (g5_linen_robe) = TestUtils.create_item(ItemIds.LinenRobe, 5); let (g5_scimitar_vs_g5_linen_robe) = CombatStats.calculate_damage_from_weapon( g5_scimitar, g5_linen_robe, adventurer_state, 1 ); - assert g5_scimitar_vs_g5_linen_robe = 9; + assert g5_scimitar_vs_g5_linen_robe = 8; // greatness 20 short sword vs greatness 20 shirt - // short sword will deal 1*20 = 20HP attack points - // shirt will deal 1*20 = 20HP defense points - // this generates a base damage of 0HP which is below minimum threshold of 3 - // so base damage should use minimum of 3 which will get multiplied by 3 for the elemental (blade vs cloth) - // for a resulting damage of 9 let (g20_shirt) = TestUtils.create_item(ItemIds.Shirt, 20); let (g20_short_sword) = TestUtils.create_item(ItemIds.ShortSword, 20); let (g20_short_sword_vs_g20_shirt) = CombatStats.calculate_damage_from_weapon( g20_short_sword, g20_shirt, adventurer_state, 1 ); - assert g20_short_sword_vs_g20_shirt = 9; + assert g20_short_sword_vs_g20_shirt = 8; // greatness 20 short sword vs greatness 19 shirt - // short sword will deal 1*20 = 20HP attack points - // shirt will deal 1*19 = 19HP defense points - // this generates a base damage of 1HP which is below minimum threshold of 3 - // so base damage should use minimum of 3 which will get multiplied by 3 for the elemental (blade vs cloth) - // for a resulting damage of 9 let (g19_shirt) = TestUtils.create_item(ItemIds.Shirt, 19); let (g20_short_sword_vs_g19_shirt) = CombatStats.calculate_damage_from_weapon( g20_short_sword, g19_shirt, adventurer_state, 1 ); - assert g20_short_sword_vs_g19_shirt = 9; + assert g20_short_sword_vs_g19_shirt = 8; // greatness 20 short sword vs greatness 18 shirt - // short sword will deal 1*20 = 20HP attack points - // shirt will deal 1*18 = 18HP defense points - // this generates a base damage of 2HP which is below minimum threshold of 3 - // so base damage should use minimum of 3 which will get multiplied by 3 for the elemental (blade vs cloth) - // for a resulting damage of 9 let (g18_shirt) = TestUtils.create_item(ItemIds.Shirt, 18); let (g20_short_sword_vs_g18_shirt) = CombatStats.calculate_damage_from_weapon( g20_short_sword, g18_shirt, adventurer_state, 1 ); - assert g20_short_sword_vs_g18_shirt = 9; + assert g20_short_sword_vs_g18_shirt = 8; // greatness 20 short sword vs greatness 17 shirt - // short sword will deal 1*20 = 20HP attack points - // shirt will deal 1*17 = 17HP defense points - // this generates a base damage of 3HP - // This should get multiplied by 3 which is equal to minimum threshold of 3 - // this will get multiplied by 3 for the elemental (blade vs cloth) - // for a resulting damage of 9 let (g17_shirt) = TestUtils.create_item(ItemIds.Shirt, 17); let (g20_short_sword_vs_g17_shirt) = CombatStats.calculate_damage_from_weapon( g20_short_sword, g17_shirt, adventurer_state, 1 ); - assert g20_short_sword_vs_g17_shirt = 9; + assert g20_short_sword_vs_g17_shirt = 8; // greatness 20 short sword vs greatness 16 shirt - // short sword will deal 1*20 = 20HP attack points - // shirt will deal 1*16 = 16HP defense points - // this generates a base damage of 4HP - // This is more than min damage of (3) so it'll get used and multiplied by 3 for the elemental (blade vs cloth) - // for a resulting damage of 12 let (g16_shirt) = TestUtils.create_item(ItemIds.Shirt, 16); let (g20_short_sword_vs_g16_shirt) = CombatStats.calculate_damage_from_weapon( g20_short_sword, g16_shirt, adventurer_state, 1 @@ -236,15 +202,15 @@ func test_calculate_damage_from_beast{ let (adventurer_state) = get_adventurer_state(); // greatness 20 orc vs greatness 0 shirt (oof) - let (orc) = TestUtils.create_beast(BeastIds.Orc, 20); + let (orc) = TestUtils.create_beast(BeastIds.Orc, 20, 0, 0); let (shirt) = TestUtils.create_item(ItemIds.Shirt, 0); - let (orc_vs_shirt) = CombatStats.calculate_damage_from_beast(orc, shirt, 1); + let (orc_vs_shirt) = CombatStats.calculate_damage_from_beast(orc, shirt, 1, 1); assert orc_vs_shirt = 60; // greatness 10 giant vs greatness 10 leather armor let (leather) = TestUtils.create_item(ItemIds.LeatherArmor, 10); - let (giant) = TestUtils.create_beast(BeastIds.Giant, 10); - let (giant_vs_leather) = CombatStats.calculate_damage_from_beast(giant, leather, 1); + let (giant) = TestUtils.create_beast(BeastIds.Giant, 10, 0, 0); + let (giant_vs_leather) = CombatStats.calculate_damage_from_beast(giant, leather, 1, 1); assert giant_vs_leather = 120; return (); @@ -276,7 +242,7 @@ func test_calculate_damage_from_obstacle{ let (demonhusk_vs_dark_mist) = CombatStats.calculate_damage_from_obstacle( g0_dark_mist, g20_demonhusk ); - assert demonhusk_vs_dark_mist = 3; + assert demonhusk_vs_dark_mist = 4; let zero_item = Item(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); let (g0_curse) = TestUtils.create_obstacle(ObstacleConstants.ObstacleIds.Curse, 1); @@ -313,7 +279,7 @@ func test_check_for_level_increase{syscall_ptr: felt*, pedersen_ptr: HashBuiltin assert zero_xp_zero_level = 1; // 4xp is not enough to level up from level 1 to level 2 - let (no_level_up) = CombatStats.check_for_level_increase(4, 1); + let (no_level_up) = CombatStats.check_for_level_increase(2, 1); assert no_level_up = 0; // 9xp is exactly enough to level up from level 1 to level 2 @@ -321,7 +287,7 @@ func test_check_for_level_increase{syscall_ptr: felt*, pedersen_ptr: HashBuiltin assert level_up_1_to_2 = 1; // 675xp is one xp short of being able to level up from level 8 to 9 - let (no_level_up_8_to_9) = CombatStats.check_for_level_increase(675, 8); + let (no_level_up_8_to_9) = CombatStats.check_for_level_increase(200, 8); assert no_level_up_8_to_9 = 0; // 700xp is enough to level up from level 8 to 9 diff --git a/tests/protostar/loot/test_structs.cairo b/tests/protostar/loot/test_structs.cairo index f305269d..7ae4d002 100755 --- a/tests/protostar/loot/test_structs.cairo +++ b/tests/protostar/loot/test_structs.cairo @@ -71,34 +71,34 @@ func get_adventurer_state{syscall_ptr: felt*, range_check_ptr}() -> ( ) { return ( AdventurerState( - TestAdventurerState.Race, - TestAdventurerState.HomeRealm, - TestAdventurerState.Birthdate, - TestAdventurerState.Name, - TestAdventurerState.Order, - TestAdventurerState.ImageHash1, - TestAdventurerState.ImageHash2, - TestAdventurerState.Health, - TestAdventurerState.Level, - TestAdventurerState.Strength, - TestAdventurerState.Dexterity, - TestAdventurerState.Vitality, - TestAdventurerState.Intelligence, - TestAdventurerState.Wisdom, - TestAdventurerState.Charisma, - TestAdventurerState.Luck, - TestAdventurerState.XP, - TestAdventurerState.WeaponId, - TestAdventurerState.ChestId, - TestAdventurerState.HeadId, - TestAdventurerState.WaistId, - TestAdventurerState.FeetId, - TestAdventurerState.HandsId, - TestAdventurerState.NeckId, - TestAdventurerState.RingId, - TestAdventurerState.Status, - TestAdventurerState.Beast, - TestAdventurerState.Upgrading, + TestAdventurerState.Race, + TestAdventurerState.HomeRealm, + TestAdventurerState.Birthdate, + TestAdventurerState.Name, + TestAdventurerState.Order, + TestAdventurerState.ImageHash1, + TestAdventurerState.ImageHash2, + TestAdventurerState.Health, + TestAdventurerState.Level, + TestAdventurerState.Strength, + TestAdventurerState.Dexterity, + TestAdventurerState.Vitality, + TestAdventurerState.Intelligence, + TestAdventurerState.Wisdom, + TestAdventurerState.Charisma, + TestAdventurerState.Luck, + TestAdventurerState.XP, + TestAdventurerState.WeaponId, + TestAdventurerState.ChestId, + TestAdventurerState.HeadId, + TestAdventurerState.WaistId, + TestAdventurerState.FeetId, + TestAdventurerState.HandsId, + TestAdventurerState.NeckId, + TestAdventurerState.RingId, + TestAdventurerState.Status, + TestAdventurerState.Beast, + TestAdventurerState.Upgrading, ), ); } @@ -108,34 +108,34 @@ func create_adventurer{syscall_ptr: felt*, range_check_ptr}(level: felt) -> ( ) { return ( AdventurerState( - TestAdventurerState.Race, - TestAdventurerState.HomeRealm, - TestAdventurerState.Birthdate, - TestAdventurerState.Name, - TestAdventurerState.Order, - TestAdventurerState.ImageHash1, - TestAdventurerState.ImageHash2, - TestAdventurerState.Health, - level, - TestAdventurerState.Strength, - TestAdventurerState.Dexterity, - TestAdventurerState.Vitality, - TestAdventurerState.Intelligence, - TestAdventurerState.Wisdom, - TestAdventurerState.Charisma, - TestAdventurerState.Luck, - TestAdventurerState.XP, - TestAdventurerState.WeaponId, - TestAdventurerState.ChestId, - TestAdventurerState.HeadId, - TestAdventurerState.WaistId, - TestAdventurerState.FeetId, - TestAdventurerState.HandsId, - TestAdventurerState.NeckId, - TestAdventurerState.RingId, - TestAdventurerState.Status, - TestAdventurerState.Beast, - TestAdventurerState.Upgrading, + TestAdventurerState.Race, + TestAdventurerState.HomeRealm, + TestAdventurerState.Birthdate, + TestAdventurerState.Name, + TestAdventurerState.Order, + TestAdventurerState.ImageHash1, + TestAdventurerState.ImageHash2, + TestAdventurerState.Health, + level, + TestAdventurerState.Strength, + TestAdventurerState.Dexterity, + TestAdventurerState.Vitality, + TestAdventurerState.Intelligence, + TestAdventurerState.Wisdom, + TestAdventurerState.Charisma, + TestAdventurerState.Luck, + TestAdventurerState.XP, + TestAdventurerState.WeaponId, + TestAdventurerState.ChestId, + TestAdventurerState.HeadId, + TestAdventurerState.WaistId, + TestAdventurerState.FeetId, + TestAdventurerState.HandsId, + TestAdventurerState.NeckId, + TestAdventurerState.RingId, + TestAdventurerState.Status, + TestAdventurerState.Beast, + TestAdventurerState.Upgrading, ), ); } @@ -163,31 +163,29 @@ namespace TestUtils { return ( Item( - item_id, - slot, - type, - material, - rank, - prefix_1, - prefix_2, - suffix, - greatness, - created_block, - xp, - adventurer, - bag + item_id, + slot, + type, + material, + rank, + prefix_1, + prefix_2, + suffix, + greatness, + created_block, + xp, + adventurer, + bag, ), ); } - + // create_item_with_names returns an Item corresponding to the provided item_id, greatness, prefix_1, prefix_2, suffix // parameters: item_id, greatness, prefix_1, prefix_2, suffix // returns: An Item func create_item_with_names{syscall_ptr: felt*, range_check_ptr}( item_id: felt, greatness: felt, prefix_1: felt, prefix_2: felt, suffix: felt - ) -> ( - item: Item - ) { + ) -> (item: Item) { alloc_locals; let (slot) = ItemStats.item_slot(item_id); @@ -201,19 +199,19 @@ namespace TestUtils { return ( Item( - item_id, - slot, - type, - material, - rank, - prefix_1, - prefix_2, - suffix, - greatness, - created_block, - xp, - adventurer, - bag + item_id, + slot, + type, + material, + rank, + prefix_1, + prefix_2, + suffix, + greatness, + created_block, + xp, + adventurer, + bag, ), ); } @@ -229,34 +227,32 @@ namespace TestUtils { // create_beast returns a Beast corresponding to the provided beast_id // parameters: beast_id // returns: A Beast - func create_beast{syscall_ptr: felt*, range_check_ptr}(beast_id: felt, level: felt) -> ( - beast: Beast - ) { + func create_beast{syscall_ptr: felt*, range_check_ptr}( + beast_id: felt, level: felt, prefix1: felt, prefix2: felt + ) -> (beast: Beast) { alloc_locals; let (attack_type) = BeastStats.get_attack_type_from_id(beast_id); let (armor_type) = BeastStats.get_armor_type_from_id(beast_id); let (rank) = BeastStats.get_rank_from_id(beast_id); let health = 100; - let prefix_1 = 1; - let prefix_2 = 1; let adventurer = 0; let xp = 0; let level = level; let slain_on_date = 0; return ( Beast( - beast_id, - attack_type, - armor_type, - rank, - prefix_1, - prefix_2, - health, - adventurer, - xp, - level, - slain_on_date + beast_id, + attack_type, + armor_type, + rank, + prefix1, + prefix2, + health, + adventurer, + xp, + level, + slain_on_date, ), ); } @@ -275,16 +271,6 @@ namespace TestUtils { let prefix_1 = 1; let prefix_2 = 1; - return ( - Obstacle( - obstacle_id, - type, - rank, - prefix_1, - prefix_2, - greatness, - damage_location - ), - ); + return (Obstacle(obstacle_id, type, rank, prefix_1, prefix_2, greatness, damage_location),); } }