def pull_propagated_buffs(source_buffable, destination_buffable, source_event): """ Pull all propagation buffs from a source to a target. Usually when creating a new buffable. :param Buffable source_buffable: :param Buffable destination_buffable: :param BuffEvent source_event: :rtype list[EventResult] :returns A list of event results containing all added, removed or propagated modifications """ event_results = [] for buff_id in source_buffable.active_buffs: buff_spec = buffspecs.get_buff_spec(buff_id) # If the destination contains any buff that targets my buffable type and is auto triggered, propagate if buff_spec.auto_triggers and destination_buffable.name in buff_spec.propagates_to: propagation_event = BuffPropagatedEvent(destination_buffable, source_buffable, buff_id, source_event) if handle_event_conditions(propagation_event, buff_spec.conditions): event_results.append( add_buff(destination_buffable, buff_spec, propagation_event, propagated=True)) return event_results
def create_buff_modifications(buffable, buff_id, source_event, current_stack=1): """ Generates a list of buff modifications a buff will have to apply a buffable. Buff modifications are changes that can be applied to attributes as a whole. This list contains all changes that will update the buffable attributes. :param Buffable buffable: :param int buff_id: :param BuffEvent source_event: :param int current-stack: :rtype: list[BuffModification] """ modifications = [] buff_spec = buffspecs.get_buff_spec(buff_id) for modifier in buff_spec.modifiers: buff_modification = BuffModification(modifier, source_event, buff_id) # In case this derivates to another attribute we need to generate the derivation modifier # This modifier contains the calculated derivated value as a "flat add" modifier to the derivated attribute. if buff_spec.to_attribute: buff_modification.derivated_modifier = create_derivation_modifier( buffable.attributes, modifier, buff_spec.to_attribute ) # If this buff propagates an derived attribute from a buffable to another, we need to calculate the # derived value based on the propagator source and not this buffable if buff_spec.propagates_to_attribute: source_buffable = get_propagation_source(source_event) buff_modification.derivated_modifier = create_derivation_modifier( source_buffable.attributes, modifier, buff_spec.propagates_to_attribute ) buff_modification.stack_count = current_stack modifications.append(buff_modification) return modifications
def get_expired_buffs(buffable): """ Get a generator of all buffs that already have expired and removes them from the expiry list. :param Buffable buffable: :rtype: generator[BuffSpec] """ expired_buffs = [] while buffable.expiry_times and get_timestamp() >= _get_next_expiry_time( buffable): next_expiry_time, buff_id = buffable.expiry_times.pop(0) expired_buffs.append(buffspecs.get_buff_spec(buff_id)) return expired_buffs
def remove_buff(buffable, buff_id): """ Removes a buff from a buffable :param Buffable buffable: :param int buff_id: """ if buff_id in buffable.active_buffs: buff_spec = buffspecs.get_buff_spec(buff_id) if buffable.name in buff_spec.propagates_to: raise BuffException(BuffErrorCodes.REMOVING_BUFF_NOT_FROM_SOURCE) del buffable.active_buffs[buff_id] buff_spec = buffspecs.get_buff_spec(buff_id) delete_triggers(buff_id, buff_spec.get_triggers(), buffable.activation_triggers) delete_triggers(buff_id, buff_spec.get_propagation_triggers(), buffable.propagation_triggers) delete_triggers(buff_id, buff_spec.get_remove_triggers(), buffable.deactivation_triggers) for target in get_propagation_target_buffables_including_self( buffable, buff_spec): remove_all_buff_modifications(target, buff_spec)
def _recalculate_derivated_values_from_attribute(buffable, source_attribute_id): """ For existing derivated modifications on the buffable that are based on the source attribute ID, Recalculate the derivation modifiers by removing and re-appliyng them, updating the derivated values. :param Buffable buffable: :param int source_attribute_id: The attribute that changed """ # Get all modifications this attribute we are changing derivates to on this buffable for modification in _get_modifications_derived_by_attribute( buffable.attributes, source_attribute_id): # In case this modification is a derivation from a propagation, we need to calculate the derived value # with basis on the source buffable attributes buff_spec = buffspecs.get_buff_spec(modification.buff_id) buffable_propagator = buffable if buff_spec.propagates_to_attribute: buffable_propagator = get_propagation_source( modification.source_event) # Recalculate the derivated value new_derivated_modifier = create_derivation_modifier( buffable_propagator.attributes, modification.modifier, modification.derivated_modifier.attribute_id) # Keeping track of old derivated value because we will need to check it changed old_derivated_value = modification.derivated_modifier.value if old_derivated_value != new_derivated_modifier.value: # Undo the changes, just apply inversed _apply_modifier_to_attributes(buffable.attributes, modification.derivated_modifier, inverse=True) # Apply again with updated modifier _apply_modifier_to_attributes(buffable.attributes, new_derivated_modifier) # The final modifier of the derivated value is stored in derivated modifier, keeping the original intact modification.derivated_modifier = new_derivated_modifier # Since we changed an attribute, we need to chain derivation modifiers recalculation _recalculate_derivated_values_from_attribute( buffable, new_derivated_modifier.attribute_id)
def call_event(event): """ Calls an event and try to trigger any remaining triggers on the event buffable. :param BuffEvent event: :rtype EventResult :returns An event result with all added, removed and propagated modifications. """ buffable = event.buffable result = EventResult() # Activation Triggers for triggered_buff_spec in get_buff_specs_triggered_by_event( event, buffable.activation_triggers): result.added_modifications = activate_buff(buffable, triggered_buff_spec, event) # Deactivation Triggers for triggered_buff_spec in get_buff_specs_triggered_by_event( event, buffable.deactivation_triggers, condition_inverse=True): result.removed_modifications = inactivate_buff(buffable, triggered_buff_spec, event) # Propagation Triggers for triggered_buff_spec in get_buff_specs_triggered_by_event( event, buffable.propagation_triggers, propagation=True): for propagation_event in get_buff_propagation_events( buffable, triggered_buff_spec, event): buff_spec = buffspecs.get_buff_spec(propagation_event.buff_id) result.propagated_modifications[ propagation_event.buffable.id].append( add_buff(propagation_event.buffable, buff_spec, propagation_event, propagated=True)) # In case this buff spec has no propagation triggers and was just propagated by "AddBuffEvent" # means this buff wont be able to re-propagate ever again. if not buff_spec.propagation_triggers: delete_triggers(propagation_event.buff_id, ["AddBuffEvent"], buffable.propagation_triggers) return result
def get_all_buff_modifications(buffable_attributes, buff_id): """ Gets all buff_modifications that buff is causing to attributes :param BuffableAttributes buffable_attributes: :param int buff_id: :rtype: list[BuffModification] """ modifications = [] buff = buffspecs.get_buff_spec(buff_id) for modifier in buff.modifiers: # The changed attribute we looking for can be the derivated attribute or the modifier itself # TODO: Make propagates_to_attribute works with to_attribute, could it be useful ? affected_attribute_id = buff.to_attribute or buff.propagates_to_attribute or modifier.attribute_id attr_data = buffable_attributes.attribute_data[affected_attribute_id] for buff_modification in attr_data.history.values(): if buff_modification.buff_id == buff_id: modifications.append(buff_modification) return modifications
def get_buff_specs_triggered_by_event(event, possible_trigger_list, condition_inverse=False, propagation=False): """ Obtains all triggered buffs by a given possible trigger list and conditions. The buffs are triggered from the newest to the oldest, and its a very important aspect of this function is that is a generator - because this allow the code to process the older buffs only after the first one is processed to evaluate possible condition changes, allowing buff dependency out of the box. :param BuffEvent event: :param dict[str, list [ int ]] possible_trigger_list: A dictionary of triggers to a list of buffs ids to trigger :param bool condition_inverse: :param bool propagation: :rtype: generator[BuffSpec] """ for buff_id in reversed(possible_trigger_list[event.get_name()]): buff_spec = buffspecs.get_buff_spec(buff_id) conditions = buff_spec.conditions if not propagation else buff_spec.propagation_conditions if handle_event_conditions(event, conditions) is not condition_inverse: yield buff_spec
def get_propagated_targets_of_given_attribute(buffable, attribute_id): """ Obtain a lists of targets that are affected by a propagator attribute, for instance in a derivation. :param Buffable buffable: :param int attribute_id: :rtype: generator[Buffable] """ for active_buff_id in buffable.active_buffs: buff_spec = buffspecs.get_buff_spec(active_buff_id) # If i have a buff that propagates to an attribute if buff_spec.propagates_to_attribute: targets = get_propagation_target_buffables(buffable, buff_spec) for source_modifier in buff_spec.modifiers: # In case the target attribute i propagate comes from this attribute that im changing if source_modifier.attribute_id == attribute_id: for target in targets: # Need to track that for that target because he will be affected yield target