def _revise_regular_attr_dependents(self, msg): """Remove calculated attribute values which rely on passed attribute. Removing them allows to recalculate updated value. Here we process all regular dependents, which include dependencies specified via capped attribute map and via affector specs with dogma modifiers. Affector specs with python modifiers are processed separately. """ affections = self.__affections projections = self.__projections effect_unapplications = [] # Unapply warfare buffs for item, attr_ids in msg.attr_changes.items(): for effect in item._type_effects.values(): projector = Projector(item, effect) if projector not in self.__warfare_buffs: continue if not attr_ids.intersection(WARFARE_BUFF_ATTRS): continue tgt_items = self.__projections.get_projector_tgts(projector) effect_unapplications.append((projector, tgt_items)) msgs = [] for projector, tgt_items in effect_unapplications: msgs.append( EffectUnapplied(projector.item, projector.effect.id, tgt_items)) msg.fit._publish_bulk(msgs) attr_changes = {} for item, attr_ids in msg.attr_changes.items(): # Remove values of affectee attributes capped by the changing # attribute for attr_id in attr_ids: for capped_attr_id in item.attrs._cap_map.get(attr_id, ()): if item.attrs._force_recalc(capped_attr_id): attr_changes.setdefault(item, set()).add(capped_attr_id) # Force attribute recalculation when local affector spec # modification changes for affector_spec in self.__generate_local_affector_specs( item, item._running_effect_ids): affector_modifier = affector_spec.modifier # Only dogma modifiers have source attribute specified, python # modifiers are processed separately if (not isinstance(affector_modifier, DogmaModifier) or affector_modifier.affector_attr_id not in attr_ids): continue # Remove values for affectee_item in affections.get_local_affectee_items( affector_spec): attr_id = affector_modifier.affectee_attr_id if affectee_item.attrs._force_recalc(attr_id): attr_changes.setdefault(affectee_item, set()).add(attr_id) # Force attribute recalculation when projected affector spec # modification changes for projector in self.__generate_projectors( item, item._running_effect_ids): tgt_items = projections.get_projector_tgts(projector) # When projector doesn't target any items, then we do not need # to clean anything if not tgt_items: continue for affector_spec in self.__generate_projected_affectors( item, (projector.effect.id, )): affector_modifier = affector_spec.modifier # Only dogma modifiers have source attribute specified, # python modifiers are processed separately if (not isinstance(affector_modifier, DogmaModifier) or affector_modifier.affector_attr_id not in attr_ids): continue for affectee_item in ( affections.get_projected_affectee_items( affector_spec, tgt_items)): attr_id = affector_modifier.affectee_attr_id if affectee_item.attrs._force_recalc(attr_id): attr_changes.setdefault(affectee_item, set()).add(attr_id) # Force attribute recalculation if changed attribute defines # resistance to some effect for projector in projections.get_tgt_projectors(item): effect = projector.effect if effect.resist_attr_id not in attr_ids: continue tgt_items = projections.get_projector_tgts(projector) for affector_spec in self.__generate_projected_affectors( projector.item, (effect.id, )): for affectee_item in ( affections.get_projected_affectee_items( affector_spec, tgt_items)): attr_id = affector_spec.modifier.affectee_attr_id if affectee_item.attrs._force_recalc(attr_id): attr_changes.setdefault(affectee_item, set()).add(attr_id) # Unregister warfare buffs only after composing list of attributes we # should update for projector, tgt_items in effect_unapplications: del self.__warfare_buffs[projector] if attr_changes: self.__publish_attr_changes(attr_changes) # Register warfare buffs effect_applications = [] for item, attr_ids in msg.attr_changes.items(): if not attr_ids.intersection(WARFARE_BUFF_ATTRS): continue item_fleet = item._fit.fleet for effect_id in item._running_effect_ids: effect = item._type_effects[effect_id] if not isinstance(effect, WarfareBuffEffect): continue projector = Projector(item, effect) for buff_id_attr_id in WARFARE_BUFF_ATTRS: try: buff_id = item.attrs[buff_id_attr_id] except KeyError: continue getter = (self.__solar_system.source.cache_handler. get_buff_templates) try: buff_templates = getter(buff_id) except BuffTemplatesFetchError: continue affector_attr_id = WARFARE_BUFF_ATTRS[buff_id_attr_id] if not buff_templates: continue for buff_template in buff_templates: modifier = DogmaModifier._make_from_buff_template( buff_template, affector_attr_id) affector_spec = AffectorSpec(item, effect, modifier) self.__warfare_buffs.add_data_entry( projector, affector_spec) tgt_ships = [] for tgt_fit in self.__solar_system.fits: if (tgt_fit is msg.fit or (item_fleet is not None and tgt_fit.fleet is item_fleet)): tgt_ship = tgt_fit.ship if tgt_ship is not None: tgt_ships.append(tgt_ship) effect_applications.append((projector, tgt_ships)) if attr_changes: self.__publish_attr_changes(attr_changes) # Apply warfare buffs if effect_applications: msgs = [] for projector, tgt_items in effect_applications: msgs.append( EffectApplied(projector.item, projector.effect.id, tgt_items)) msg.fit._publish_bulk(msgs)
def _handle_effects_started(self, msg): item = msg.item effect_ids = msg.effect_ids attr_changes = {} for affector_spec in self.__generate_local_affector_specs( item, effect_ids): # Register the affector spec if isinstance(affector_spec.modifier, BasePythonModifier): self.__subscribe_python_affector_spec(msg.fit, affector_spec) self.__affections.register_local_affector_spec(affector_spec) # Clear values of attributes dependent on the affector spec for affectee_item in self.__affections.get_local_affectee_items( affector_spec): attr_id = affector_spec.modifier.affectee_attr_id if affectee_item.attrs._force_recalc(attr_id): attr_ids = attr_changes.setdefault(affectee_item, set()) attr_ids.add(attr_id) # Register projectors for projector in self.__generate_projectors(item, effect_ids): self.__projections.register_projector(projector) # Register warfare buffs effect_applications = [] item_fleet = msg.fit.fleet for effect_id in effect_ids: effect = item._type_effects[effect_id] if not isinstance(effect, WarfareBuffEffect): continue projector = Projector(item, effect) for buff_id_attr_id in WARFARE_BUFF_ATTRS: try: buff_id = item.attrs[buff_id_attr_id] except KeyError: continue getter = (self.__solar_system.source.cache_handler. get_buff_templates) try: buff_templates = getter(buff_id) except BuffTemplatesFetchError: continue affector_attr_id = WARFARE_BUFF_ATTRS[buff_id_attr_id] if not buff_templates: continue for buff_template in buff_templates: modifier = DogmaModifier._make_from_buff_template( buff_template, affector_attr_id) affector_spec = AffectorSpec(item, effect, modifier) self.__warfare_buffs.add_data_entry( projector, affector_spec) tgt_ships = [] for tgt_fit in self.__solar_system.fits: if (tgt_fit is msg.fit or (item_fleet is not None and tgt_fit.fleet is item_fleet)): tgt_ship = tgt_fit.ship if tgt_ship is not None: tgt_ships.append(tgt_ship) effect_applications.append((projector, tgt_ships)) if attr_changes: self.__publish_attr_changes(attr_changes) # Apply warfare buffs if effect_applications: msgs = [] for projector, tgt_items in effect_applications: msgs.append( EffectApplied(projector.item, projector.effect.id, tgt_items)) msg.fit._publish_bulk(msgs)