def test_next_to_expire(self): buffable = Buffable() buff = BuffSpec() buff.activation_triggers = [] # no triggers buff.conditions = [] # no conditions buff.duration_seconds = 10 # Only lasts for 10 seconds buff.max_stack = 3 buff.modifiers = [Modifier("+", 50, Attributes.DEF)] with FixedTime(get_timestamp()): expiry_time = get_timestamp() + buff.duration_seconds # Add the buff add_buff(buffable, buff, CompleteBuildingEvent()) add_buff(buffable, buff, CompleteBuildingEvent()) add_buff(buffable, buff, CompleteBuildingEvent()) assert _get_next_expiry_time(buffable) == expiry_time expired_buffs = list(get_expired_buffs(buffable)) assert len(expired_buffs) == 0 with FixedTime(expiry_time): expired_buffs = list(get_expired_buffs(buffable)) assert len(expired_buffs) == 3 # Calling expired buffs should have removed them from expiry list assert len(buffable.expiry_times) == 0
def test_expiring_stacks(self): buffable = Buffable() buff = BuffSpec(1) buff.activation_triggers = [] # no triggers buff.conditions = [] # no conditions buff.duration_seconds = 10 # Only lasts for 10 seconds buff.max_stack = 2 buff.modifiers = [Modifier("+", 50, Attributes.DEF)] # Add the buff 2 times, one in the future add_buff(buffable, buff, CompleteBuildingEvent()) with FixedTime(get_timestamp() + 5): add_buff(buffable, buff, CompleteBuildingEvent()) expiry_time_1 = get_timestamp() + buff.duration_seconds expiry_time_2 = expiry_time_1 + 5 assert buffable.attributes[Attributes.DEF] == 100 assert buffable.active_buffs[buff.buff_id].stack == 2 assert len(buffable.expiry_times) == 2 with FixedTime(expiry_time_1): assert buffable.attributes[Attributes.DEF] == 50 assert buffable.active_buffs[buff.buff_id].stack == 1 assert len(buffable.expiry_times) == 1 with FixedTime(expiry_time_2): assert buffable.attributes[Attributes.DEF] == 0 assert buff.buff_id not in buffable.active_buffs assert len(buffable.expiry_times) == 0
def test_modification_history(self): buffable = Buffable() attributes = buffable.attributes # Create an event with fake event data source_event = CompleteBuildingEvent() apply_attributes_modification(attributes, BuffModification(Modifier("+", 25, Attributes.ATK), source_event)) apply_attributes_modification(attributes, BuffModification(Modifier("%", 1.00, Attributes.ATK), source_event)) apply_attributes_modification(attributes, BuffModification(Modifier("%", 1.00, Attributes.ATK), source_event)) assert buffable.attributes[Attributes.ATK] == 75 # Checking if we has history of those 3 modifications attribute_history = list(buffable.attributes.get_data(Attributes.ATK).history.values()) assert attribute_history[0].modifier.operator == "+" assert attribute_history[0].modifier.value == 25 assert attribute_history[0].modifier.attribute_id == Attributes.ATK assert attribute_history[0].source_event == source_event assert attribute_history[1].modifier.operator == "%" assert attribute_history[1].modifier.value == 1.00 assert attribute_history[1].modifier.attribute_id == Attributes.ATK assert attribute_history[1].source_event == source_event assert attribute_history[2].modifier.operator == "%" assert attribute_history[2].modifier.value == 1.00 assert attribute_history[2].modifier.attribute_id == Attributes.ATK assert attribute_history[2].source_event == source_event
def test_condition_parameters(self): buffable = Buffable() buff = BuffSpec() buff.activation_triggers = ["DamageEvent"] buff.deactivation_triggers = ["DamageEvent"] buff.conditions = ["is_damage_higher_then 10" ] # condition parameters after condition name buff.buff_id = 5 buff.modifiers = [Modifier("+", 30, Attributes.DEF)] buffspecs.register_buff(buff) @buffspecs.AddConditionFor([DamageEvent]) def is_damage_higher_then(event, param): return event.damage > param # Add the buff add_buff(buffable, buff, CompleteBuildingEvent()) # Damage lower then condition threshhold call_event(DamageEvent(buffable, 8)) # Buff should not have been activated assert buff.buff_id not in buffable.active_buffs assert buffable.attributes[Attributes.DEF] == 0 # Now a damage higher call_event(DamageEvent(buffable, 12)) # Buff should have been activated assert buff.buff_id in buffable.active_buffs assert buffable.attributes[Attributes.DEF] == 30
def test_buffable_force_attributes_setter(self): buffable = Buffable() # Forcing an attribute final value to be 10 buffable.attributes[Attributes.MAX_HP] = 10 attr_data = buffable.attributes.get_data(Attributes.MAX_HP) assert attr_data.final_value == 10 assert attr_data.mod_add == 10 # Automatically granted 10 of the add modifier assert attr_data.mod_pct == 0 # no bonus pct
def test_adding_buff(self): buffable = Buffable() buff = BuffSpec() buff.activation_triggers = [] # no triggers buff.conditions = [] # no conditions buff.modifiers = [Modifier("+", 30, Attributes.DEF)] # Add the buff add_buff(buffable, buff, CompleteBuildingEvent()) # Since it has no triggers or conditions, it was added automatically assert buffable.attributes[Attributes.DEF] == 30 assert buff.buff_id in buffable.active_buffs
def test_apply_modifiers_not_cumulative(self): buffable = Buffable() attributes = buffable.attributes # +25 ATK apply_attributes_modification(attributes, BuffModification(Modifier("+", 25, Attributes.ATK))) assert buffable.attributes[Attributes.ATK] == 25 # 100% Bonus Atk apply_attributes_modification(attributes, BuffModification(Modifier("%", 1.00, Attributes.ATK))) assert buffable.attributes[Attributes.ATK] == 50 # 100% Bonus Atk Again apply_attributes_modification(attributes, BuffModification(Modifier("%", 1.00, Attributes.ATK))) assert buffable.attributes[Attributes.ATK] == 75
def test_pct_modifier(self): buffable = Buffable() buffable.attributes[Attributes.MAX_HP] = 10 attr_data = buffable.attributes.get_data(Attributes.MAX_HP) assert attr_data.final_value == 10 # Adding 50% bonus to the the attribute modifiers attr_data.mod_mult = 0.5 attr_data.calculate() # +10 + 50% of that (5) is 15 assert attr_data.final_value == 15 assert attr_data.mod_add == 10 assert attr_data.mod_mult == 0.5
def test_removing_buff(self): buffable = Buffable() buffable.attributes[Attributes.ATK] = 100 buff = BuffSpec() buff.modifiers = [Modifier("%", 0.5, Attributes.ATK)] add_buff(buffable, buff, CompleteBuildingEvent()) assert buffable.attributes[Attributes.ATK] == 150 remove_buff(buffable, buff.buff_id) assert buffable.attributes[Attributes.ATK] == 100 assert buff.buff_id not in buffable.active_buffs
def test_negating_condition(self): buffable = Buffable() buff = BuffSpec() buff.activation_triggers = {} # no triggers buff.conditions = ["not cond_is_blue_yellow"] buff.buff_id = 5 buff.modifiers = [Modifier("+", 30, Attributes.DEF)] buffspecs.register_buff(buff) @buffspecs.AddConditionFor([CompleteBuildingEvent]) def cond_is_blue_yellow(event): return False add_buff(buffable, buff, CompleteBuildingEvent()) # This buff should be be applied as the condition was negated assert buff.buff_id in buffable.active_buffs
def test_buff_modification_history(self): buffable = Buffable() buff = BuffSpec() buff.modifiers = [Modifier("+", 30, Attributes.DEF)] # Faking an event that would result in adding a buff event_to_get_the_buff = CompleteBuildingEvent() # Add the buff event_result = add_buff(buffable, buff, event_to_get_the_buff) added_modifications = event_result.added_modifications # Check modification history to debug/backtrack assert added_modifications[0].modifier.operator == "+" assert added_modifications[0].modifier.attribute_id == Attributes.DEF assert added_modifications[ 0].source_event.trigger_event == event_to_get_the_buff
def test_basic_condition_failing(self): buffable = Buffable() buff = BuffSpec() buff.activation_triggers = {} # no triggers buff.conditions = ["cond_is_blue_yellow"] buff.buff_id = 5 buff.modifiers = [Modifier("+", 30, Attributes.DEF)] buffspecs.register_buff(buff) # A condition that can be triggered by a MockEvent @buffspecs.AddConditionFor([CompleteBuildingEvent]) def cond_is_blue_yellow(event): return False add_buff(buffable, buff, CompleteBuildingEvent()) # This buff should not be applied assert buff.buff_id not in buffable.active_buffs
def test_registering_1_expiry_per_stack(self): buffable = Buffable() buff = BuffSpec(1) buff.activation_triggers = [] # no triggers buff.conditions = [] # no conditions buff.duration_seconds = 10 # Only lasts for 10 seconds buff.max_stack = 2 buff.modifiers = [Modifier("+", 50, Attributes.DEF)] # Add the buff 2 times add_buff(buffable, buff, CompleteBuildingEvent()) add_buff(buffable, buff, CompleteBuildingEvent()) # Should have registered one expiry time per stack assert buffable.attributes[Attributes.DEF] == 100 assert buffable.active_buffs[buff.buff_id].stack == 2 assert len(buffable.expiry_times) == 2
def test_adding_multiple_modifiers(self): buffable = Buffable() buff = BuffSpec() buff.activation_triggers = [] # no triggers buff.conditions = [] # no conditions buff.modifiers = [ Modifier("+", 50, Attributes.DEF), Modifier("+", 50, Attributes.DEF), Modifier("%", 0.5, Attributes.DEF) ] buffspecs.register_buff(buff) # Add buff to registry # Adding a possible buff add_buff(buffable, buff, CompleteBuildingEvent()) # +50 +50 + 50% is 100 + 50% = 150 asd = buffable.attributes.get_data(Attributes.DEF) assert buffable.attributes[Attributes.DEF] == 150
def test_buff_event_modifications_log(self): buffable = Buffable() # Building a buff with the builder buff = BuffBuilder().modify( "+", 5, Attributes.ATK).whenever(FartEvent).build() add_buff(buffable, buff, CompleteBuildingEvent()) fart_event = FartEvent(buffable) event_result = call_event(fart_event) modifications = event_result.added_modifications # Check if the event returns the correct modifications that happened assert len(modifications) == 1 assert modifications[0].buff_id == buff.buff_id assert modifications[0].source_event == fart_event assert modifications[0].modifier.operator == "+" assert modifications[0].modifier.value == 5 assert modifications[0].modifier.attribute_id == Attributes.ATK
def test_buff_trigger(self): buffable = Buffable() buff = BuffSpec() buff.activation_triggers = ["FartEvent"] buff.conditions = [] buff.buff_id = 5 buff.modifiers = [Modifier("+", 30, Attributes.DEF)] buffspecs.register_buff(buff) add_buff(buffable, buff, CompleteBuildingEvent()) # The buff was not triggered yet as "FartEvent" was not called assert buffable.attributes[Attributes.DEF] == 0 # Instead, we did not added a buff, we just added a trigger for a buff assert "FartEvent" in buffable.activation_triggers # Now we call the event call_event(FartEvent(buffable)) # Now the buff should be added assert buffable.attributes[Attributes.DEF] == 30 # And the trigger removed assert "FartEvent" not in buffable.activation_triggers
def test_adding_buff(self): buffable = Buffable() buff = BuffSpec(1) buff.activation_triggers = [] # no triggers buff.conditions = [] # no conditions buff.duration_seconds = 10 # Only lasts for 10 seconds buff.modifiers = [Modifier("+", 50, Attributes.DEF)] with FixedTime(get_timestamp()): # Add the buff expiry_time = get_timestamp() + 10 add_buff(buffable, buff, CompleteBuildingEvent()) # Since it has no triggers or conditions, it was added automatically assert buffable.attributes[Attributes.DEF] == 50 assert buff.buff_id in buffable.active_buffs # Check the expiry time was registered registered_expiry_time, buff_id = buffable.expiry_times[0] assert registered_expiry_time == expiry_time assert buff_id == buff.buff_id
def test_buff_expiring(self): buffable = Buffable() buff = BuffSpec(1) buff.activation_triggers = [] # no triggers buff.conditions = [] # no conditions buff.duration_seconds = 10 # Only lasts for 10 seconds buff.modifiers = [Modifier("+", 50, Attributes.DEF)] expiry_time = get_timestamp() + buff.duration_seconds # Add the buff add_buff(buffable, buff, CompleteBuildingEvent()) assert buffable.attributes[Attributes.DEF] == 50 with FixedTime(expiry_time): # Simply by reading the attribute we will expire that buff assert buffable.attributes[Attributes.DEF] == 0 # Should not be an active buff anymore assert buff.buff_id not in buffable.active_buffs # Expiry time assert len(buffable.expiry_times) == 0
def test_condition_switching_buff(self): buffable = Buffable() buff = BuffSpec() buff.activation_triggers = ["DamageEvent"] buff.deactivation_triggers = ["DamageEvent"] buff.conditions = ["is_burning"] buff.buff_id = 5 buff.modifiers = [Modifier("+", 30, Attributes.DEF)] buffspecs.register_buff(buff) buffable.attributes[ "Burning"] = 0 # example to set a state, not burning @buffspecs.AddConditionFor([DamageEvent]) def is_burning(event): return event.buffable.attributes["Burning"] == 1 # Add the buff add_buff(buffable, buff, CompleteBuildingEvent()) # The buff should not be applied because the buffable is not burning assert buff.buff_id not in buffable.active_buffs assert buffable.attributes[Attributes.DEF] == 0 # Now make it burn damage = 10 buffable.attributes["Burning"] = 1 call_event(DamageEvent(buffable, damage)) # Now the buff should have applied assert buff.buff_id in buffable.active_buffs assert buffable.attributes[Attributes.DEF] == 30 # And we should have added the remove trigger assert buff.buff_id in buffable.deactivation_triggers["DamageEvent"] # Buff history contains this modification assert len(buffable.attributes.get_data(Attributes.DEF).history) == 1 # Now After calling the other event the buff should be removed buffable.attributes["Burning"] = 0 call_event(DamageEvent(buffable, damage)) # Now the buff should be removed assert buff.buff_id not in buffable.active_buffs assert buff.buff_id not in buffable.deactivation_triggers assert buffable.attributes[Attributes.DEF] == 0 # And the trigger should be added again and the remove trigger should be removed assert buff.buff_id not in buffable.deactivation_triggers[ "DamageEvent"] assert buff.buff_id in buffable.activation_triggers["DamageEvent"] # Also the modification history is removed because this buff is inactive and not modifyng anything assert len(buffable.attributes.get_data(Attributes.DEF).history) == 0 # In case we burn again... buffable.attributes["Burning"] = 1 call_event(DamageEvent(buffable, damage)) # Buff is activated again assert buff.buff_id in buffable.active_buffs assert buffable.attributes[Attributes.DEF] == 30
def test_reuse_conditions_with_abstraction(self): # Lets say our game has an economy, of multiple types of coins. example_coin_types = ["gold", "silver", "copper"] class EconomyEvent(BuffEvent): def __init__(self, buffable, coin_type_changed, coin_amount): super(EconomyEvent, self).__init__(buffable) self.coin_type_changed = coin_type_changed self.coin_amount = coin_amount # The player can get coins by mining for instance class PlayerMineCoinsEvent(EconomyEvent): def __init__(self, buffable, coin_type_changed, coin_amount): super(PlayerMineCoinsEvent, self).__init__(buffable, coin_type_changed, coin_amount) # The player can also get coins by looting enemies class PlayerLootEnemyEvent(EconomyEvent): def __init__(self, buffable, coin_type_changed, coin_amount): super(PlayerLootEnemyEvent, self).__init__(buffable, coin_type_changed, coin_amount) # Now this condition works for any economy event generically @buffspecs.AddConditionFor([EconomyEvent]) def is_coin_type(event, *coin_types): return event.coin_type_changed in coin_types # Now we create a buff that buffable = Buffable() # Making a buff that gives player DEF when if he mines a gold coin bdr = BuffBuilder().modify("+", 5, Attributes.DEF).whenever( PlayerMineCoinsEvent).just_if("is_coin_type gold") bonus_mine_gold_buff = bdr.build() # Making a buff that gives player ATK if he loots silver or copper coins bdr = BuffBuilder().modify( "+", 5, Attributes.ATK ).whenever(PlayerLootEnemyEvent).just_if( "is_coin_type copper silver" # Multiple parameters in the condition ) bonus_loot_copper_silver_buff = bdr.build() # Add the buffs add_buff(buffable, bonus_loot_copper_silver_buff, CompleteBuildingEvent()) add_buff(buffable, bonus_mine_gold_buff, CompleteBuildingEvent()) # Now we mine some silver call_event(PlayerMineCoinsEvent(buffable, "silver", 10)) # No buffs should be applied as no conditions matched assert len(buffable.active_buffs) == 0 # Now he mines gold, should activate the buff cause the condition matched call_event(PlayerMineCoinsEvent(buffable, "gold", 10)) assert bonus_mine_gold_buff.buff_id in buffable.active_buffs # Now he will loot gold, condition should not match call_event(PlayerLootEnemyEvent(buffable, "gold", 10)) assert bonus_loot_copper_silver_buff.buff_id not in buffable.active_buffs # Now he finally loots silver or copper (in this case, silver) and the buff should apply call_event(PlayerLootEnemyEvent(buffable, "silver", 10)) assert bonus_loot_copper_silver_buff.buff_id in buffable.active_buffs