Простая утилити для упрощения поиска приоритетных целей
Bounce у Runemaster оказался очень сложным скиллом с точки зрения логики
При каждом попадании нужно искать новую цель, куда отскочить:
- Исключая, очевидно, себя и текущего противника
- Исключая повторные отскоки по уже задетым целям (если нет перка, который это отключает)
- Отдавая приоритет Pulse (если есть такой перк)
- Разрешая повторные отскоки, если камень проскочил по всем мобам, но отскоки ещё остались
Также при каждом отскоке нужно учитывать
- Если отскок был не последним, то:
- Если есть рядом стоящие доступные цели, отскакиваем дальше
- Если нет, идём по ветке "Если отскок был последним"
- Если снаряд ударился в стенку или слишком долго летел, он деспавнится
Чтобы упростить как минимум поиск приоритетных целей, пришёл к небольшой вспомогательной штуке
Какую проблему решаем?
Если реализовывать поиск приоритетных юнитов втупую, то получится многословная простыня из вложенных условий:
DPipe.new([ Units.get_all(), DCondition.new({ If = DState.get_value(should_strike_twice), Then = [ filter_already_stroke, DCondition.new({ If = DListMapper.has_items(), Then = DMapper.get_current(), Else = Units.get_all() }) ], Else = Units.get_all() }) ])
И с каждым уровнем приоритетов вложенность будет расти.
Попробуйте добавить критерий "отдавать приоритет Pulse, если есть такой перк"
Это не говоря уж о том, что сама структура неудобна к прочтению
В итоге я написал такую штуку:
PrioritizedFilter.new([ filter_already_stroke, filter_all ])
Под капотом PrioritizedFilter проходится по каждому из фильтров и возвращает первый отфильтрованный список, в котором есть хотя бы один подходящий результат
То есть "либо первый (если есть), либо второй (если есть), либо ∞"
А для включения/выключения приоритетов (см. пункт про перки), можно просто использовать условие в описании самого фильтра, которое вернёт пустой массив, когда приоритетную очередь нужно не учитывать
Конечная композиция выглядит так:
DPipe.new([ Units.get_all(), # Исключаем игрока, союзных юнитов и только что атакованную цель filter_valid_targets, # В ограниченном радиусе filter_nearby, # Ещё не задетые (если есть), либо все PrioritizedFilter.new([ DCondition.new({ If = DState.get_value(should_strike_twice), Then = filter_all, Else = filter_not_already_struck }), filter_all ]), # Камень Пульса (если есть), либо все PrioritzedFilter.new([ DCondition.new({ If = DState.get_value(should_prioritize_pulse), Then = filter_pulse_stones, Else = filter_empty }), filter_all ]) ])
В будущем для лучшего QoL сюда можно будет легко добавить отсеивание неуязвимых противников, брать в приоритет почти добитых мобов и так далее
Также в очередной раз похвалю себя за свой декларативный движок и скажу, что условия можно менять находу не только через описания условий внутри скилла, но и снаружи, если положить PrioritizedFilter в переменную
# Внутри скилла var filter = PrioritizedFilter.new([ filter_all ]) # Внутри перка, который добавляет "приоритет мобам с меткой" filter.children.push_front([ filter_with_mark ])
Кстати говоря, баг с тем, что иногда дюпаются камни от Bounce, я, кажется, полностью пофиксил! Нашёл аж 5 причин, почему это происходило. Самый упоротый баг за всю жизнь, пожалуй