Lineage 2 HF Geodata или recast4j для FakePlayerAI

RedZerGG

Путник
Участник
Сообщения
25
Розыгрыши
0
Репутация
0
Реакции
28
Баллы
55
Привет всем , разработка идёт полным ходом создания полноценного мира ИИ игроков против друг друга на ХФ, наткнулся на неприятную ситуацию боты тупят не правильно бегают не правильно строят путь не обходят препятствия улучшал скрипты всё равно но как только интегрировал recast4j всё стало чётко но спустя пару минут началось то же самое, проблема Боты с recast4j обходят препятствия , но доходят только по первых координат дальше тупо стоят и юзают чат, некоторые фармят гремлинов больше не куда не идут, некоторые просто стоят но при этом все юзают чат и так у всех рас, потом заметил если их общее количество при старте 300, то если подойти к главной локации спавна игроков у каждой расы то начинаются жесткие лаги я так понял боты путь не находят и досят, что можно придумать или есть какие либо наработки что бы боты не выглядели как скриптовые а были похожи на реальных людей передвижение поведение , жду ваших идей ответов предложений
 
Мб вы забыли детур интегрировать?) У меня таких проблем нет
Посмотреть вложение Peek 2024-06-27 12-28.mp4

Кстати, возможно проблема еще в том, что их 300. Попробуйте 299 или 301. Это обычно не помогает, но попытаться стоит.
 
Последнее редактирование:
Покажется что я умничаю, но если мувинг написан правильно, боты будут "находить" путь и обходить препятствия штатным геодвижком! ни каких recast4j не надо - это излишняя надстройка - это мое сугубое мнение... добавляй логгер и смотри что просаживает, с лагами на месте спавна ботов они могут слать слишком много пакетов- тоже нужно оптимизировать
 
Покажется что я умничаю, но если мувинг написан правильно, боты будут "находить" путь и обходить препятствия штатным геодвижком! ни каких recast4j не надо - это излишняя надстройка - это мое сугубое мнение... добавляй логгер и смотри что просаживает, с лагами на месте спавна ботов они могут слать слишком много пакетов- тоже нужно оптимизировать
именно, я не понимаю зачем нужны какие-то надстройки, когда механизмы хождения для игроков уже реализованы
 
Мб вы забыли детур интегрировать?) У меня таких проблем нет
Посмотреть вложение 97577

Кстати, возможно проблема еще в том, что их 300. Попробуйте 299 или 301. Это обычно не помогает, но попытаться стоит.
Кстити это что за новые иконки типо боты подбирают лут на видисике, можно логику ботов или все файлы скинуть хочу посмотреть идею кода и сто за клиент ХФ Эссенс , я щас ещё на пути движения ботов ещё не дошел до пвп пати ботов они только в начале при старте сервака появляются фармят упераются тормозят дёргаются люди вообще некоторые мимо моста в воду падают это жесть , на счёт рекаста ошибка Invalid number of verts per poly как раз про то, что Recast не может корректно перевести формат L2J в свои полигоны.Так что, увы, «взять просто Detour» не получится. я хз что придумать что бы они видили мир и геодату и не подходили к ней в плотную что бы не дёргаться я ещё не допелили но вот моё дерево файло пока я его скиду но ещё дополню ещё куча чего сделать , а так у тебя на видосики боты одеты заточены , мои это всё через нпс крафт споил рынок будут делать ) вот дерево не оконченное
Сообщение объединено:
Покажется что я умничаю, но если мувинг написан правильно, боты будут "находить" путь и обходить препятствия штатным геодвижком! ни каких recast4j не надо - это излишняя надстройка - это мое сугубое мнение... добавляй логгер и смотри что просаживает, с лагами на месте спавна ботов они могут слать слишком много пакетов- тоже нужно оптимизировать
Включить штатный поиск пути GeoEngine.ini ? я уже это делал всё равно упераются и дёргаются ещё есть предложения у меня есть 4 файла но это ещё начало нужно ещё создать для катакомб лабы и замков CityNavigation.java FakePlayerMovement.java WalkerType.java WalkNode.java я хз что ещё придумать что бы она как живые люди бегали мансили и т.д краткие пути искапли по воде плыли , а то они у меня падают в воду и тупо тонут в воде) устал каждый раз переделовать но самое это компелить каждый раз что бы посмотреть ошибки и что бы .java файлы работали коректно
 

Вложения

Последнее редактирование:
Селекторы скиллов и оркестратор тасков на эвристике. Разделения по профам нет, просто универсальный скрипт, где каждый бот пользуется теми скиллами и предметами, которые у него есть.

Java:
package ru.nts.scripts.player;

import host.anzo.core.service.ThreadPoolService;
import ru.nts.commons.math.Rnd;
import ru.nts.core.network.outgoing.S_PARTY_MEMBER_POSITION;
import ru.nts.core.network.outgoing.S_SYSTEM_MSG;
import ru.nts.gameserver.game.messages.items.AutoSoulShotItemAction;
import ru.nts.gameserver.game.messages.items.UseItemAction;
import ru.nts.gameserver.game.models.points.Location;
import ru.nts.gameserver.game.models.social.party.Party;
import ru.nts.gameserver.game.stats.effects.Effect;
import ru.nts.gameserver.game.stats.effects.sub_effects.impl.*;
import ru.nts.gameserver.game.stats.impl.CreatureStats;
import ru.nts.gameserver.game.stats.skills.Skill;
import ru.nts.gameserver.game.world.World;
import ru.nts.gameserver.game.world.objects.creatures.Creature;
import ru.nts.gameserver.game.world.objects.creatures.npcs.Npc;
import ru.nts.gameserver.game.world.objects.creatures.npcs.Servitor;
import ru.nts.gameserver.game.world.objects.creatures.npcs.types.base.Pet;
import ru.nts.gameserver.game.world.objects.creatures.npcs.types.base.Summon;
import ru.nts.gameserver.game.world.objects.creatures.npcs.types.base.Warrior;
import ru.nts.gameserver.game.world.objects.creatures.players.FakePlayer;
import ru.nts.gameserver.game.world.objects.creatures.players.Player;
import ru.nts.gameserver.game.world.objects.items.Item;
import ru.nts.gameserver.system.data.memory.SkillTable;
import ru.nts.gameserver.system.data.pts.holder.CategoryDataHolder;
import ru.nts.gameserver.system.engines.geo.GeoData;
import ru.nts.gameserver.system.engines.geo.GeoEngine;
import ru.nts.gameserver.system.enums.*;
import ru.nts.gameserver.system.interfaces.IVarsSessionable;
import ru.nts.gameserver.tools.PositionUtils;
import ru.nts.scripts.nasc.EventNotifier;
import ru.nts.scripts.nasc.NASCScript;
import ru.nts.scripts.nasc.interfaces.INASCConstant;
import ru.nts.scripts.nasc.interfaces.shared.ISharedCreature;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

public class player_base extends NASCScript {

    private static final int CHECK_SPAWN_AFK = -2551;

    public player_base() {}

    @Override
    public void DEBUG_AI(ISharedCreature sCreature, int reply) {
        if (getNASCActor() instanceof Player player_actor) {
            if (sCreature instanceof Player player && player.isGM()) {
                switch (reply) {
                    case 0 -> player_actor.whisperTo("Debug", player);
                }
            }
        }
    }

    @Override
    public void CHECK_CMD_FOLLOW() { //Проверяем, не пора ли бросить все и бежать за хозяином
        if (getNASCActor() instanceof Player player_actor) {
            Party party = player_actor.getParty();
            if (party != null && (player_actor.isReal() || player_actor instanceof FakePlayer fake_actor && fake_actor.getMasterUserID() == party.getPartyLeaderOID())) {
                Player leader = party.getPartyLeader();
                if (leader.distance3D(player_actor) >= 1000) {
                    EventNotifier.getInstance().notify(player_actor, 1000, ENASCEvent.CMD_FOLLOW_ME);
                }
            }
        }
    }

    @Override
    public void CHECK_SWEEP() {
        TrySweep();
    }

    @Override
    public void CHECK_SELF_BUFF() {
        TrySelfBuff(EDesire.get.extra_high());
    }

    @Override
    public void CHECK_SELF_HEAL() {
        if (getNASCActor() instanceof Player player_actor) {
            final CreatureStats stats = player_actor.getStats();
            if (player_actor.isInCombat()) {
                if (CategoryDataHolder.isInCategory(heal_master2_3, player_actor)) {
                    if (stats.getCurrentHpPercents() <= 33) {
                        TryHeal(player_actor, 5, EDesire.get.extra_high());
                    } else if (stats.getCurrentHpPercents() <= 70) {
                        TryHeal(player_actor, 3, EDesire.get.high() + 50000);
                    } else {
                        if (stats.getCurrentHpPercents() <= 90) {
                            TryHeal(player_actor, 1, EDesire.get.normal(20000));
                        }
                    }
                } else if (CategoryDataHolder.isInCategory(buff_master2_3, player_actor) && stats.getCurrentHpPercents() <= 50) {
                    TryHeal(player_actor, 5, EDesire.get.high() + 50000);
                } else {
                    if (CategoryDataHolder.isInCategory(attribute_group_summoner, player_actor) && player_actor.getServitor() instanceof Summon summon && summon.getStats().getCurrentHpPercents() <= 90) {
                        TryHeal(summon, 3, EDesire.get.high() + 10000);
                    }
                }
            } else {
                if (stats.getCurrentHpPercents() < 100) {
                    TryHeal(player_actor, 1, EDesire.get.normal(1000));
                } else {
                    if (player_actor.getServitor() instanceof Summon summon && summon.getStats().getCurrentHpPercents() < 100) {
                        TryHeal(summon, 1, EDesire.get.normal(-1000));
                    }
                }
            }
        }
    }

    @Override
    public void CHECK_DISPEL() {
        if (getNASCActor() instanceof Player player_actor) {
            for (Effect effect : player_actor.getAllEffectsSnapshot()) {
                if (effect.getSkill() instanceof Skill skill && skill.isDebuff()) {
                    if (!TryDispellDebuff(player_actor, skill.getAbnormalType(), skill.getAbnormalLevel(), EDesire.get.high())) {
                        RequestPartyDispell(skill.getAbnormalType(), skill.getAbnormalLevel());
                        break;
                    }
                }
            }
        }
    }

    @Override
    public void CREATED() {
        if (getNASCActor() instanceof Player player_actor) {
            RemoveAllPCHateInfoIF(COND_IS_INVALID, 0);
            RemoveAllPCHateInfoIF(COND_HAS_HATE_LESS_THAN, 100);
            RemoveAllPCHateInfoIF(COND_IS_FAR_AWAY, 3000);

            TrySummonServitor();
            TrySummonCubics();

            if (player_actor instanceof FakePlayer fake_actor && fake_actor.isWild()) {
                AddTimerEx(CHECK_SPAWN_AFK, 60000);
            }
        }
    }

    @Override
    public void RECHECK_TARGET() {
        if (getNASCActor() instanceof Player player_actor) {

            RemoveAllPCHateInfoIF(COND_IS_INVALID, 0);

            TrySweep();

            if (player_actor.isCastingNow() || player_actor.isAttackingNow()) {
                EventNotifier.getInstance().notify(player_actor, 1000, ENASCEvent.RECHECK_TARGET);
                return;
            }

            Creature current_target = player_actor.getTargetAsCreature();
            if (current_target == null || current_target.isDead() || current_target == player_actor || current_target.isInArea(EAreaType.peace_zone) || player_actor.getAI().target_black_list.contains(current_target.getObjectId()) || !GeoEngine.CanSee(player_actor, current_target)) {
                if (current_target != null && current_target != player_actor) {
                    RemovePCAttackDesire(current_target.getObjectId());
                }
                player_actor.setTarget(null, EChangeTargetReason.CTR_BY_USER);
                current_target = null;

                //Сначала известные цели в границах прямой видимости.
                for (Creature creature : player_actor.getHateList().getMostHated(10)) {
                    if (player_actor.getHateList().getHate(creature) > 100 && !player_actor.getAI().target_black_list.contains(creature.getObjectId()) && player_actor.canSee(creature) && creature.isAlive()) {
                        current_target = creature;
                        break;
                    }
                }

                //Потом известные цели к которым можно пройти.
                if (current_target == null) {
                    for (Creature creature : player_actor.getHateList().getMostHated(10)) {
                        if (player_actor.getHateList().getHate(creature) > 100 && !player_actor.getAI().target_black_list.contains(creature.getObjectId()) && player_actor.canMove(creature) && creature.isAlive()) {
                            current_target = creature;
                            break;
                        }
                    }
                }

                //Если нет приоритетной цели по хейту, то ищем ближайшую(Только дикие боты)
                if (current_target == null && player_actor instanceof FakePlayer fake_actor && fake_actor.isWild()) {

                    List<Creature> around = fake_actor.getAI().lookAround(fake_actor.sight_distance() / 3, 500);

                    //Сначала ищем чаров вокруг.
                    //Порабощенных ботов ненавидим и агримся сразу же
                    //Если слишком близко подошел, с кармой или флагом, ну или просто не понравился)
                    current_target = around.stream().filter(creature -> !creature.isInArea(EAreaType.peace_zone)).filter(creature -> !fake_actor.getAI().target_black_list.contains(creature.getObjectId())).filter(creature -> fake_actor.canSee(creature) || fake_actor.canMove(creature)).filter(creature -> {
                        if (creature instanceof Player player) {
                            //Порабощенных ботов ненавидим и агримся сразу же
                            if (player instanceof FakePlayer fake) {
                                return fake.isSlave();
                            }
                            //Если слишком близко подошел, с кармой или флагом, ну или просто не понравился)
                            if (fake_actor.distance3D(player) < 100 || (player.isGuilty() > 0 || player.getKarma() > 0) && Rnd.chance(15 - fake_actor.getDifficulty().ordinal() * 2)) {
                                return true;
                            }
                        }
                        return false;
                    }).min(fake_actor.getRangeComparator()).orElse(null);

                    //Если чаров нет, то ищем мобов
                    if (current_target == null) {
                        current_target = around.stream().filter(creature -> !creature.isInArea(EAreaType.peace_zone)).filter(creature -> !fake_actor.getAI().target_black_list.contains(creature.getObjectId())).filter(creature -> fake_actor.canSee(creature) || fake_actor.canMove(creature)).filter(creature -> {
                            if (creature instanceof Warrior monster && Math.abs(monster.getLevel() - fake_actor.getLevel()) <= 20) {
                                return true;
                            }
                            return false;
                        }).min(fake_actor.getRangeComparator()).orElse(null);
                    }
                }
            }

            if (current_target != null) {
                DoAttack(current_target, Math.max(200, player_actor.getHateList().getHate(current_target)), false);
            }
        }
    }

    @Override
    public void NO_DESIRE() {
        if (getNASCActor() instanceof Player player_actor) {
            //Рабы отрабатывают социальный контракт.
            if (player_actor instanceof FakePlayer fake_actor && (fake_actor.isSlave() || fake_actor.isWildPrivate())) {
                //В этом методе, мы чинимся. Если потеряли мастера, ищем. Если не нашли вблизи - бежим на дальнюю дистанцию.
                if (fake_actor.lost_master()) {
                    List<Player> list = World.getInstance().getAroundPlayers(fake_actor, 1500, fake_actor.getReflectionID(),false);
                    for (Player player : list) {
                        if (player != null && fake_actor.isMyMaster(player)) {
                            //Если увидели родителя, обновляем таск следования за ним.
                            AddFollowDesire2(player, 100, eAFD2_FT_DIST_AND_DEGREE, 120 + Rnd.getInt(0, 50, true), fake_actor.follow_angle);
                            return;
                        }
                    }
                    Creature master = fake_actor.getMasterUser();
                    if (master != null && fake_actor.isSlave() && TryTeleportWithCommunityBoard(master.getLoc(), -1)) {
                        AddMovePartialDesire(master.getLoc(), 5000, 90);
                        return;
                    }
                }

                if (fake_actor.getNextLoc() != null && fake_actor.getNextLoc().distance2D(fake_actor) > 1000) {
                    Location temp = fake_actor.getNextLoc();
                    if (TryTeleportWithCommunityBoard(temp, -1)) {
                        fake_actor.setNextLoc(null);
                    }
                    AddMovePartialDesire(temp, 5000, 90);
                    return;
                }

                //Только если нет потребностей в починке состояния следования за мастером, то тогда чекаем, можно ли кого-то подлечить.
                if (CategoryDataHolder.isInCategory(recom_magic_group, fake_actor)) {
                    if (fake_actor.getParty() != null && fake_actor.getStats().getCurrentMpPercents() > 50) {
                        for (Player member : fake_actor.getParty().getPartyMembers()) {
                            if (member != null && member.getStats().getCurrentHpPercents() < 100) {
                                Skill skill = SelectSkillForHeal(member, 0);
                                if (skill != null) {
                                    AddUseSkillDesireEx(skill.getAimingTarget(fake_actor, member), skill, ST_HEAL, AMT_MOVE_TO_TARGET, FALSE, EDesire.get.normal(), 0);
                                }
                            }
                        }
                    }
                }
            }

            EventNotifier.getInstance().notify(player_actor, ENASCEvent.RECHECK_TARGET);
        }
    }

    @Override
    public void SEE_CREATURE(ISharedCreature sCreature) {
        if (getNASCActor() instanceof Player player_actor) {
            if (sCreature instanceof Creature creature) {

                if (player_actor.isDead()) {
                    return;
                }

                if (player_actor instanceof FakePlayer fake && !fake.isWild()) {
                    // Смотреть по сторонам и анализировать обстановку могут только дикие мобы.
                    return;
                }



                if (creature.isInArea(EAreaType.peace_zone)) {
                    // Если цель в мирной зоне, то не трогаем ее.
                    return;
                }

                if (player_actor.getHateList().getHate(creature) > 0) {
                    // Если на существо уже есть хейт, то новый не добавляем.
                    return;
                }

                double hate = 0;
                double range_add = player_actor.sight_distance() / Math.max(1, player_actor.distance3D(creature));
                // В целом, в SEE_CREATURE может попасть либо моб, либо чар.
                if (creature instanceof Warrior monster && Math.abs(monster.getLevel() - player_actor.getLevel()) <= 20) {
                    hate = 100 + range_add;
                } else if (creature instanceof Player player) {
                    if (player.isReal() && player.getKarma() > 0) {
                        return;
                    }
                    if (player instanceof FakePlayer fake) {
                        if (fake.isWild() && (fake.getFakeFactionId() == -1 || player_actor.isFactionMember(fake))) {
                            return;
                        }
                        if (fake.isSlave() || player_actor.isFactionEnemy(fake)) {
                            hate = 2000 + range_add;
                        }
                    }
                    if (player_actor.distance3D(player) < 100) {
                        hate = 200;
                    }
                } else {
                    return;
                }

                if (hate > 0) {
                    AddPCHate(creature, hate, true, 60);
                }
            }
        }
    }

    @Override
    public void DISAPPEAR_CREATURE(ISharedCreature sCreature, int objectId) {
        if (sCreature instanceof Creature creature) {
            RemovePCAttackDesire(creature.getObjectId());
        }
    }

    @Override
    public void ATTACKED(ISharedCreature sAttacker, int damage, long skill_name_id, int skill_id) {
        if (getNASCActor() instanceof Player player_actor) {
            if (sAttacker instanceof Creature attacker) {
                if (damage > 0) {
                    RequestPartyHeal(); //Всегда проверяем, нужно ли слать запрос на хил в пати.
                }

                if (attacker instanceof FakePlayer fake && fake.isWild() && !player_actor.isFactionEnemy(fake)) {
                    return;
                }

                if (damage != 0 && !(attacker instanceof FakePlayer)) {
                    AddPCHate(attacker, damage, true, 300);
                }
                if (damage == 0 && skill_name_id != 0 && !(attacker instanceof FakePlayer)) {
                    AddPCHate(attacker, 100, true, 300);
                }
                if (damage != 0 || skill_name_id != 0) {
                    EquipWeaponPC(); //Всегда проверяем надета ли пушка, когда нас бьют. Если нет, надеваем.
                    SummonServitor(1000); //Всегда проверяем, нужно ли суммонить сервитора.
                }

                double desire = EDesire.get.byDamage(damage, player_actor);

                if (attacker.isBlockMove() && (player_actor.getAI().battle_distance.get() > attacker.getStats().getPhysicalAttackRange() || player_actor.can_away) || player_actor.isClanMember(attacker) || player_actor.isPartyMember(attacker) || Chance(player_actor.getAI().regroup_chance.get()) && DistancePC(attacker, false) < player_actor.getAI().battle_distance.get() && (player_actor.can_away || attacker.getStats().calcCurrentMoveSpeed() <= player_actor.getStats().calcCurrentMoveSpeed())) {
                    if (!TryAway(attacker, EDesire.get.extra_high())) {
                        AddMoveToDesire(PositionUtils.findAroundPosition(player_actor, player_actor.getAI().battle_distance.get() - 50, player_actor.getAI().battle_distance.get() + 50, player_actor.getReflectionID()), EDesire.get.high());
                    }

                    if (!(player_actor.isClanMember(attacker) || player_actor.isPartyMember(attacker))) {
                        DoAttack(attacker, desire - 1, false);
                    }
                    return;
                }

                if (attacker.distance3D(player_actor) > player_actor.getAI().battle_distance.get() && player_actor.getHateList().getHate(attacker) > 5000 || attacker.getStats().calcMoveSpeed(EMoveType.MT_FAST) > player_actor.getStats().calcMoveSpeed(EMoveType.MT_FAST)) {
                    TryCatch(attacker, desire * 2);
                }
                DoAttack(attacker, desire, false);
            }
        }
    }

    @Override
    public void ABNORMAL_STATUS_CHANGED(ISharedCreature speller, long skill_name_id, int skill_id, int skill_level) {
        if (getNASCActor() instanceof Player player_actor) {
            Skill skill = GetSkill(skill_name_id);
            if (skill == null) {
                return;
            }

            //Дебафы из SOA, которые означают, что бот попытался убить РБ и тот его проклял. Это гг и бота надо убирать.
            //s_seed_of_annihilation_bestacon_silence1
            //s_seed_of_annihilation_cocracon_silence1
            //s_seed_of_annihilation_torum_ba_turn_stone1
            if ((skill_name_id== 435814401 || skill_name_id == 435879937 || skill_name_id ==  419823617) && player_actor instanceof FakePlayer fake_actor && fake_actor.isWild()) {
                player_actor.logout(ELogoutType.KICK);
                return;
            }

            //Цикличные дебафы, которые вызывают сами себя. Ботов они не убивают, а висят бесконечно.
            //s_npc_deadly_poison
            //s_npc_deadly_bleed
            if ((skill_id == 6815 || skill_id == 6816) && player_actor instanceof FakePlayer fake_actor && fake_actor.isWild()) {
                if (player_actor.getEffectBySkillId(skill_id) instanceof Effect effect) {
                    effect.stop(true);
                }
                return;
            }

            //Если положили дебаф, смотрим, есть ли у нас что-то, что может его снять.
            if (skill.isDebuff() && skill_level > 0) {
                if (!TryDispellDebuff(player_actor, skill.getAbnormalType(), skill.getAbnormalLevel(), EDesire.get.high())) {
                    RequestPartyDispell(skill.getAbnormalType(), skill.getAbnormalLevel());
                }
            }

            //Если кончился дизарм, то шедулим надевание пухи
            if (Skill_ContainSubEffect(skill, p_disarm.class) && skill_level == 0) {
                EquipWeaponPC();
            }

            if (skill.containSubEffect(p_transform.class)) {
                player_actor.doSelfAnalysis();
                player_actor.getAI().changeTactics();
            }

            if (skill.containSubEffect(p_block_move.class) && CategoryDataHolder.isInCategory(INASCConstant.fighter_group, player_actor) && CategoryDataHolder.isInCategory(INASCConstant.mage_group, speller)) {
                player_actor.getAI().changeWeapon(skill_level > 0, skill_level <= 0);
            }
        }
    }

    @Override
    public void USE_SKILL_FINISHED(long skill_name_id, ISharedCreature sTarget, int success) {
        if (getNASCActor() instanceof Player player_actor) {
            if (sTarget instanceof Creature cTarget) {
                RequestPartyRecharge();

                if ((player_actor.isClanMember(cTarget) || player_actor.isPartyMember(cTarget))) {
                    StopPCHate(cTarget);
                    return;
                }

                if (cTarget.isDead()) {
                    StopPCHate(cTarget);
                    return;
                }

                if (player_actor.getHateList().getHate(cTarget) <= 0) {
                    return;
                }

                Skill skill = SkillTable.getInstance().getInfo(skill_name_id);

                if (CategoryDataHolder.isInCategory(heal_master2_3, player_actor)) {
                    if (skill != null && skill.isGood()) {
                        return;
                    }
                }

                DoAttack(cTarget, EDesire.get.normal(5000), skill != null && skill.getNextAction() == ENextSkillAction.attack);
            }
        }
    }

    @Override
    public void ATTACK_FINISHED(ISharedCreature sTarget) {
        if (getNASCActor() instanceof Player player_actor) {
            if (sTarget instanceof Creature cTarget) {
                if (player_actor.isClanMember(cTarget) || player_actor.isPartyMember(cTarget)) {
                    StopPCHate(cTarget);
                    return;
                }

                if (cTarget.isDead()) {
                    StopPCHate(cTarget);
                    return;
                }

                if (player_actor.getHateList().getHate(cTarget) <= 0) {
                    return;
                }

                DoAttack(cTarget, EDesire.get.normal(5000), true);
            }
        }
    }

    //Кастомные методы
    @Override
    public void PARTIAL_MOVE_FINISH(int x, int y, int z){
        if (getNASCActor() instanceof FakePlayer fake_actor) {
            Location target_loc = new Location(x, y, z);
            if (!target_loc.isEmptyPoint(GeoData.WORLD_MAP_MIN_X, GeoData.WORLD_MAP_MAX_X, GeoData.WORLD_MAP_MIN_Y, GeoData.WORLD_MAP_MAX_Y) && target_loc.distance2D(fake_actor) > 200) {
                AddMovePartialDesire(target_loc, 5000, EDesire.get.small());
            } else if (!target_loc.isEmptyPoint(GeoData.WORLD_MAP_MIN_X, GeoData.WORLD_MAP_MAX_X, GeoData.WORLD_MAP_MIN_Y, GeoData.WORLD_MAP_MAX_Y) && target_loc.distance2D(fake_actor) <= 200) {
                fake_actor.setNextLoc(null);
            }
        }
    }

    @Override
    public void MOVE_TO_NPC_FINISH(int target_object_id, int next_action_id, Object object1, Object object2){
        if (getNASCActor() instanceof FakePlayer fake_actor) {
            Creature target = World.getObjects().getWorldObjectAs(target_object_id, Creature.class);
            if (target != null) {
                try {
                    switch (next_action_id) {
                        case MTN_TELEPORT -> { //Teleport, after move finishing
                            if (fake_actor.isAlive() && object1 instanceof Location temp_loc) {
                                if (fake_actor.getMasterUser() instanceof Player master && master.isGM()) {
                                    fake_actor.whisperTo("[NPC: " + target.getName() + "][Телепорт: " + temp_loc.toString() + "]", master);
                                }
                                fake_actor.teleToLocation(temp_loc.getX(), temp_loc.getY(), temp_loc.getZ(), temp_loc.getReflectionID());
                            }
                        }
                        case MTN_BYPASS_NPC -> { //Request Bypass, after move finishing
                            if (fake_actor.isAlive() && object1 instanceof String bypass) {
                                try {
                                    if (target instanceof Npc npc) {
                                        fake_actor.setLastNpc(npc);
                                        fake_actor.setSessionVar(IVarsSessionable.FAKE_BYPASS_NPC, target_object_id);
                                        if (fake_actor.getMasterUser() instanceof Player master && master.isGM()) {
                                            fake_actor.whisperTo("[NPC: " + npc.getName() + "][Bypass: " + bypass + "]", master);
                                        }
                                    }
                                    fake_actor.getBypassHandler().checkBypass(bypass, false);
                                } finally {
                                    fake_actor.unsetSessionVar(IVarsSessionable.FAKE_BYPASS_NPC);
                                }
                            }
                        }
                    }
                } catch (Exception ignored) {
                }
            }
        }
    }

    @Override
    public void CHAT(int sender_object_id, int chat_type, String message) {
        if (getNASCActor() instanceof FakePlayer fake_actor) {
            Player sender = World.getObjects().getPlayerByObjectID(sender_object_id);
            if (sender != null && fake_actor.isMyMaster(sender)) {
                switch (chat_type) {
                    case 3 -> { //ESayPacketType.PARTY
                        String answer = "";
                        if (message.equalsIgnoreCase("ближе")) {
                            answer = "+";
                            fake_actor.close = true;
                        } else if (message.equalsIgnoreCase("дальше")) {
                            answer = "+";
                            fake_actor.close = false;
                        }
                        if (!answer.isEmpty() && !answer.isBlank()) {
                            fake_actor.sayToParty(answer);
                        }
                    }
                }
            }
        }
    }

    @Override
    public void PARTY_DIED(ISharedCreature party_private, ISharedCreature killer) {
        if (getNASCActor() instanceof Player player_actor) {
            if (party_private instanceof Servitor servitor && servitor.getPlayer() == player_actor) {
                SummonServitor(TimeUnit.SECONDS.toMillis(servitor.getTemplate().corpse_time + 1));
            }
        }
    }

    @Override
    public void PARTY_JOIN() {
        if (getNASCActor() instanceof FakePlayer fake_actor) {
            if (fake_actor.isSlave() && fake_actor.getMasterUser() instanceof Player master && master.isReal()) {

                Player requestor = fake_actor.getTransactionRequester();

                fake_actor.setTransactionRequester(null);

                if (requestor == null) {
                    return;
                }

                if (requestor.isFake()) {
                    return;
                }

                if (requestor.isCursedWeaponEquipped() || fake_actor.isCursedWeaponEquipped()) {
                    return;
                }

                if (!requestor.equals(master) && !requestor.isPartyMember(master)) {
                    return;
                }

                requestor.setTransactionRequester(null);

                if (requestor.getParty() == null) {
                    return;
                }

                if (fake_actor.getTransactionType() != ETransactionType.PARTY || fake_actor.getTransactionType() != requestor.getTransactionType() && requestor != master && !requestor.isPartyMember(master)) {
                    return;
                }

                if (fake_actor.getSessionVar("event_team_pvp", String.class) != null && fake_actor.getTeam() != requestor.getTeam()) {
                    return;
                }

                if (requestor.getParty().member_count() >= 9) {
                    requestor.sendSysMessage(S_SYSTEM_MSG.m155_THE_PARTY_IS_FULL);
                    return;
                }

                fake_actor.joinParty(requestor.getParty());
                fake_actor.getParty().broadcastToPartyMembersExclude(fake_actor, new S_PARTY_MEMBER_POSITION(fake_actor));

                for (Player member : fake_actor.getParty().getPartyMembers()) {
                    StopPCHate(member);
                    RemovePCAttackDesire(member.getObjectId());
                    if (member instanceof FakePlayer fake) {
                        StopPCHate(fake_actor);
                        RemovePCAttackDesire(fake_actor.getObjectId());
                    }
                    if (fake_actor.isMyMaster(member)) {
                        AddFollowDesire2(member, 100, eAFD2_FT_DIST_AND_DEGREE, 120 + Rnd.getInt(0, 50, true), fake_actor.follow_angle);
                    }
                }

                fake_actor.getTargetSelector().block();
            } else if (fake_actor.isWildPrivate() && fake_actor.getMasterUser() instanceof FakePlayer master && master.isWild()) {

                Player requestor = fake_actor.getTransactionRequester();

                fake_actor.setTransactionRequester(null);

                if (requestor == null) {
                    return;
                }

                if (requestor.isReal()) {
                    return;
                }

                if (requestor.isCursedWeaponEquipped() || fake_actor.isCursedWeaponEquipped()) {
                    return;
                }

                if (!requestor.equals(master)) {
                    return;
                }

                requestor.setTransactionRequester(null);

                if (requestor.getParty() == null) {
                    return;
                }

                if (fake_actor.getTransactionType() != ETransactionType.PARTY || fake_actor.getTransactionType() != requestor.getTransactionType() && requestor != master && !requestor.isPartyMember(master)) {
                    return;
                }

                if (fake_actor.getSessionVar("event_team_pvp", String.class) != null && fake_actor.getTeam() != requestor.getTeam()) {
                    return;
                }

                if (requestor.getParty().member_count() >= 9) {
                    return;
                }

                fake_actor.joinParty(requestor.getParty());

                for (Player member : fake_actor.getParty().getPartyMembers()) {
                    StopPCHate(member);
                    RemovePCAttackDesire(member.getObjectId());
                    if (member instanceof FakePlayer fake) {
                        StopPCHate(fake_actor);
                        RemovePCAttackDesire(fake_actor.getObjectId());
                    }
                    if (fake_actor.isMyMaster(member)) {
                        AddFollowDesire2(member, 100, eAFD2_FT_DIST_AND_DEGREE, 120 + Rnd.getInt(0, 50, true), fake_actor.follow_angle);
                    }
                }

                fake_actor.getTargetSelector().block();
            }
        }
    }

    @Override
    public void PARTY_LEFT() {
        if (getNASCActor() instanceof Player player_actor) {
            player_actor.getTargetSelector().unblock();
            RemovePCDesire(EPState.PSTATE_FOLLOW);
        }
    }

    @Override
    public void CMD_FOLLOW_ME() {
        if (getNASCActor() instanceof FakePlayer fake_actor) {
            if (fake_actor.getMasterUserID() != 0) {
                Party party = fake_actor.getParty();
                if (party != null && party.getPartyLeaderOID() == fake_actor.getMasterUserID()) {
                    Player leader = party.getPartyLeader();
                    if (leader != null) {
                        RemoveAllPCHateInfoIF(COND_ALL, 0);
                        RemoveAllPCDesire();
                        AddMoveToDesire(leader.getX(), leader.getY(), leader.getZ(), EDesire.get.max());
                    }
                }
            }
        }
    }

    @Override
    public void PARTY_MEMBER_LEFT() {
        if (getNASCActor() instanceof FakePlayer fake_actor) {
            if (fake_actor.getParty() instanceof Party party) {
                for (Player member : party.getPartyMembers()) {
                    if (member.getObjectId() == fake_actor.getMasterUserID()) { //Мы не выходим из пати, пока наш мастер в ней.
                        return;
                    }
                }
                party.tryLeaveParty(fake_actor);
            }
        }
    }

    @Override
    public void REVIVE_REQUEST(int requester_object_id, int pet) {
        if (getNASCActor() instanceof FakePlayer fake_actor) {
            Creature reviever = World.getObjects().getWorldObjectAs(requester_object_id, Creature.class);
            if (fake_actor.death_count.get() <= 0) {
                if (reviever != null) {
                    fake_actor.whisperTo("Даже не пытайся!", reviever);
                }
                return;
            }
            if (reviever != null && (fake_actor.isPartyMember(reviever) || fake_actor.isClanMember(reviever))) {
                fake_actor.stopTask(EFutureType.PLAYER_AI_REQUEST_RESURRECT_TASK);
                fake_actor.stopTask(EFutureType.FAKE_PLAYER_LOGOUT_TASK);

                fake_actor.reviveAnswer(1);
                if (pet == 1) {
                    ThreadPoolService.getInstance().schedule(() -> fake_actor.rebuff(true, true), 3000);
                }
            } else {
                fake_actor.reviveAnswer(0);
            }
        }
    }

    @Override
    public void PARTY_ACTION_REQUEST(int help_type, int requester_object_id, Object object1, Object object2) {
        if (getNASCActor() instanceof FakePlayer fake_actor) {
            Creature creature = World.getObjects().getWorldObjectAs(requester_object_id, Creature.class);
            if (creature instanceof Player requester && fake_actor.isPartyMember(requester)) {
                if (help_type == AT_ATTACK && object1 instanceof Creature target_creature && object2 instanceof Number desire) {
                    if (target_creature.getObjectId() == fake_actor.getAI().current_attacked_target.get()) {
                        return;
                    }

                    if (fake_actor.getAI().af_protect_master.get() && !fake_actor.isMyMaster(requester)) {
                        return;
                    }

                    double chance = 0;
                    if (EDesire.get.normal() < desire.doubleValue()) {
                        chance = 75;
                    } else {
                        chance = 30;
                    }
                    if (fake_actor.isMyMaster(requester)) {
                        chance += 20;
                    }
                    if (!fake_actor.isInCombat()) {
                        chance += 20;
                    } else {
                        if (fake_actor.getStats().getCurrentHpPercents() < 30) {
                            chance -= 30;
                        } else {
                            chance -= 10;
                        }
                    }
                    if (fake_actor.distance3D(target_creature) > 1000) {
                        chance -= 15;
                    } else {
                        if (fake_actor.distance3D(target_creature) > 700) {
                            chance -= 5;
                        } else {
                            chance += (1000 - fake_actor.distance3D(target_creature)) / 100;
                        }
                    }

                    if (Rnd.chance(chance)) {
                        DoAttack(target_creature, desire.doubleValue(), false);
                    }
                } else if (help_type == AT_PROTECT && object1 instanceof Creature attacker && object2 instanceof Number desire) {
                    if (attacker.getObjectId() == fake_actor.getAI().current_attacked_target.get()) {
                        return;
                    }

                    if (fake_actor.getAI().af_protect_master.get() && !fake_actor.isMyMaster(requester)) {
                        return;
                    }

                    double chance = 0;
                    if (EDesire.get.normal() < desire.doubleValue()) {
                        chance = 75;
                    } else {
                        chance = 30;
                    }
                    if (fake_actor.isMyMaster(requester) || (CategoryDataHolder.isInCategory(heal_master2_3, requester))) {
                        chance += 30;
                    }
                    if (!fake_actor.isInCombat()) {
                        chance += 20;
                    } else {
                        if (fake_actor.getStats().getCurrentHpPercents() < 30) {
                            chance -= 30;
                        } else {
                            chance -= 10;
                        }
                    }
                    if (fake_actor.distance3D(requester) > 1000) {
                        chance -= 15;
                    } else {
                        if (fake_actor.distance3D(requester) > 700) {
                            chance -= 5;
                        } else {
                            chance += (1000 - fake_actor.distance3D(requester)) / 100;
                        }
                    }

                    if (Rnd.chance(chance)) {
                        DoAttack(attacker, desire.doubleValue(), false);
                    }
                }

                switch (help_type) {
                    case AT_HEAL -> {
                        if (object1 instanceof Number hp_percent && object2 instanceof Number counter) {
                            double actual_hp_percent = requester.getStats().getCurrentHpPercents();
                            if (actual_hp_percent > 30 && !CategoryDataHolder.isInCategory(heal_master2_3, fake_actor)) {
                                return;
                            }
                            if (actual_hp_percent < hp_percent.doubleValue() - 5 || Rnd.chance(100 - hp_percent.doubleValue()) || counter.intValue() > 0) {
                                TryHeal(requester, counter.intValue(), EDesire.get.high());
                            }
                        }
                    }
                    case AT_RECHARGE -> {
                        if (object1 instanceof Number mp_percent && object2 instanceof Number counter) {
                            double actual_mp_percent = requester.getStats().getCurrentMpPercents();
                            if (actual_mp_percent < mp_percent.doubleValue() - 5 || Rnd.chance(100 - mp_percent.doubleValue()) || counter.intValue() > 0) {
                                TryRecharge(requester, counter.intValue(), EDesire.get.high());
                            }
                        }
                    }
                    case AT_DISPELL -> {
                        if (object1 instanceof EAbnormalStatusSlotType abnormal && object2 instanceof Number level) {
                            TryDispellDebuff(requester, abnormal, level.intValue(), EDesire.get.high());
                        }
                    }
                    case AT_BUFF -> {
                    }
                    case AT_RESURRECT -> {
                        int chance = switch (fake_actor.getDifficulty()) {
                            case EASY -> 10;
                            case NORMAL -> 20;
                            case MANUAL, HARD -> 30;
                            case EXTRA_HARD -> 50;
                            default -> 0;
                        };

                        if (!TryResurrect(requester, chance * 2, EDesire.get.high())) {
                            if (Rnd.chance(chance)) {
                                Item item = fake_actor.getInventory().getItemBySkillSubEffect(i_resurrection.class);
                                if (item != null) {
                                    if (fake_actor.distance3D(requester) <= 100) {
                                        fake_actor.executeMessage(new UseItemAction(fake_actor, requester, item, false));
                                    } else {
                                        AddMoveToTargetDesire(requester,90,-1, EDesire.get.extra_high());
                                    }
                                }
                            }
                        }
                    }
                    case AT_SUMMON -> {
                    }
                    case AT_ESCAPE -> {
                        final Item item = fake_actor.getInventory().getItemBySkillSubEffect(i_escape.class);
                        if (item != null) {
                            fake_actor.executeMessage(new UseItemAction(fake_actor, fake_actor, item, false));
                        }
                    }
                    case AT_CMD_ATTACK -> {
                        if (fake_actor.getAI().af_protect_master.get()) {
                            return;
                        }
                        if (fake_actor.isMyMaster(requester) && object1 instanceof Number number) {
                            int target_object_id = number.intValue();
                            if (target_object_id == fake_actor.getAI().current_attacked_target.get()) {
                                return;
                            }
                            Creature target = World.getObjects().getWorldObjectAs(target_object_id, Creature.class);
                            if (target != null) {
                                fake_actor.getAI().high_priority_target = target_object_id;
                                RemoveAllPCAttackDesire();
                                DoAttack(target, EDesire.get.extra_high(500), false);
                            }
                        }
                    }
                    case AT_CMD_STOP -> {
                        if (fake_actor.isMyMaster(requester)) {
                            try {
                                fake_actor.setTarget(null, EChangeTargetReason.CTR_BY_USER);
                                fake_actor.getAI().high_priority_target = 0;
                                fake_actor.getAI().current_attacked_target.set(0);
                                RemoveAllPCHateInfoIF(COND_ALL, 0);
                                RemoveAllPCDesire();
                                RemoveAllPCAttackDesire();
                                AddFollowDesire2(requester, 100, eAFD2_FT_DIST_AND_DEGREE, 120 + Rnd.getInt(0, 50, true), fake_actor.follow_angle);
                                AddMoveToDesire(PositionUtils.getPointInRadius(fake_actor.getLoc(), requester.getLoc(), Rnd.getInt(50, 75, true), fake_actor.follow_angle), 200);
                                if (fake_actor.getServitor() instanceof Pet pet) {
                                    MakeServitorAction(requester, 100, 17, -2);
                                } else if (fake_actor.getServitor() instanceof Summon summon) {
                                    MakeServitorAction(requester, 100, 23, -2);
                                }
                            } finally {
                                fake_actor.setTarget(null, EChangeTargetReason.CTR_BY_USER);
                            }
                        }
                    }
                    case AT_CMD_PICKUP -> {
                        if (fake_actor.isMyMaster(requester) && object1 instanceof Number number) {
                            Item item_to_pickup = World.getObjects().getWorldObjectAs(number.intValue(), Item.class);
                            if (item_to_pickup != null) {
                                AddGetItemDesireEx(item_to_pickup, EDesire.get.extra_high());
                            }
                        }
                    }
                }
            }
        }
    }

    @Override
    public void CUBIC_DISAPPEAR(int cubic_id, int cubic_level, long skill_index) {
        if (getNASCActor() instanceof Player player_actor) {
            long[] cubics = SelectSkillsForSummonCubic(cubic_id);
            if (cubics.length > 0) {
                AddUseSkillDesireEx(player_actor, cubics[0], ST_HEAL, AMT_STAND, TRUE, EDesire.get.normal(), 0);
            }
        }
    }

    @Override
    public void BUFFER_EFFECT_REMOVED(){
        if (getNASCActor() instanceof Player player_actor) {
            player_actor.getAI().buff_removed_count.incrementAndGet();
        }
    }

    @Override
    public void TIMER_FIRED_EX(int timer_id, Object arg1, Object arg2) {
        if (getNASCActor() instanceof FakePlayer fake_actor) {
            if (timer_id == CHECK_SPAWN_AFK) {
                boolean near = fake_actor.getController() != null && fake_actor.distance3D(fake_actor.getController()) < 300;
                if (near && fake_actor.isAlive() && !fake_actor.isInCombat()) {
                    Location loc = PositionUtils.findAroundPosition(fake_actor.getLoc(), 500, 1000, fake_actor.getReflectionID());
                    if (fake_actor.getZ() > loc.getZ()) {
                        fake_actor.teleToLocation(loc);
                    } else {
                        AddTimerEx(CHECK_SPAWN_AFK, 250);
                    }
                }
            }
        }
        super.TIMER_FIRED_EX(timer_id, arg1, arg2);
    }

    @Override
    public void SERVITOR_SUMMON_FINISHED() {
        if (getNASCActor() instanceof Player player_actor) {
            player_actor.rebuff(true, true);
            if (player_actor.hasComponent(EComponentType.ITEM_SELECTOR)) {
                final List<Item> snapshot = player_actor.getInventory().getItemsSnapshot();
                player_actor.getItemSelector().items.stream()
                        .map(id -> player_actor.getInventory().getItemByItemId(id, snapshot))
                        .filter(Objects::nonNull)
                        .filter(item -> item.getTemplate().getAction().isSummonSoulShot())
                        .forEach(item -> new AutoSoulShotItemAction(player_actor, item.getItemId(), true));
            }
        }
    }

    @Override
    public void MY_DYING(ISharedCreature sLast_attacker) {
        if (getNASCActor() instanceof FakePlayer fake_actor) {
            fake_actor.addTaskIfAbsent(EFutureType.PLAYER_AI_REQUEST_RESURRECT_TASK, this::RequestPartyResurrect, 2000, 5000);
            if (fake_actor.isWild() && sLast_attacker instanceof Player player) {
                if (Rnd.chance(fake_actor.getDifficulty().ordinal() * 15)) {
                    World.getInstance().getAroundObjects(fake_actor, FakePlayer.class, false, 500, 50)
                            .stream()
                            .filter(Creature::isAlive)
                            .filter(FakePlayer::isWild)
                            .forEach(fake -> {
                        AddPCHate(player, 10000 * fake_actor.getDifficulty().ordinal(), true, 60);
                        EventNotifier.getInstance().notify(fake, ENASCEvent.RECHECK_TARGET);
                    });
                }
            }
        }
    }

    @Override
    public void DIE_SET(ISharedCreature sDied) {
        if (getNASCActor() instanceof Player player_actor) {
            if (sDied instanceof Creature died && died.isDead()) {
                if (died instanceof Warrior mob) {
                    for (ISharedCreature creature : mob.getMostDamage(5)) {
                        if (creature instanceof Player player && player.isReal() && player_actor.distance3D(player) <= 2000) {
                            AddPCHate(player, 50000, true, 60);
                        }
                    }
                }
                if (player_actor.getHateList().getHate(died) > 0) {
                    StopPCHate(died);
                }
                RemovePCAttackDesire(died.getObjectId());
                EventNotifier.getInstance().notify(player_actor, ENASCEvent.RECHECK_TARGET);
            }
        }
    }

    @Override
    public void START_COMBAT() {
        if (getNASCActor() instanceof Player player_actor) {
            if (player_actor.isAlive()) {
                TryToogles(true);
            }
            player_actor.getHateList().clearAllSnapshots();
            player_actor.addTaskIfAbsent(EFutureType.FAKE_PLAYER_HATELIST_SNAPSHOTS_TASK, player_actor.getAI()::doHateSnapshot, 0, 30, TimeUnit.SECONDS);
        }
    }

    @Override
    public void STOP_COMBAT() {
        if (getNASCActor() instanceof Player player_actor) {
            if (player_actor.isAlive()) {
                TryToogles(false);
            }
            player_actor.stopTask(EFutureType.FAKE_PLAYER_HATELIST_SNAPSHOTS_TASK);
        }
    }
}
Сообщение объединено:
ошибка Invalid number of verts per poly как раз про то, что Recast
Эта ошибка означает, что вы в рекасте задали нестандартное число вершин для полигонов. Если Watershed используете, то там от 3 до 6 вершин на полигон. Детур соответственно тоже должен ожидать нужное количество вершин. Это в спецификации бинарных файлов навмеша прописано.

Рекаст не работает с форматом L2j. Он работает с треугольниками. Чтобы корректно сбилдить навмеши для карты L2, вам нужно спарсить коллизионную модель из клиента, а не просто загрузить туда суп из триангулированных ячеек геодаты) Но это не покроет BSP геометрию, которую невозможно корректно выразить через треугольники. Поэтому вам придется искать способ, как корректно подгружать BSP структуру в рекаст ДО вокселизации.
 
Последнее редактирование:
Селекторы скиллов и оркестратор тасков на эвристике. Разделения по профам нет, просто универсальный скрипт, где каждый бот пользуется теми скиллами и предметами, которые у него есть.

Java:
package ru.nts.scripts.player;

import host.anzo.core.service.ThreadPoolService;
import ru.nts.commons.math.Rnd;
import ru.nts.core.network.outgoing.S_PARTY_MEMBER_POSITION;
import ru.nts.core.network.outgoing.S_SYSTEM_MSG;
import ru.nts.gameserver.game.messages.items.AutoSoulShotItemAction;
import ru.nts.gameserver.game.messages.items.UseItemAction;
import ru.nts.gameserver.game.models.points.Location;
import ru.nts.gameserver.game.models.social.party.Party;
import ru.nts.gameserver.game.stats.effects.Effect;
import ru.nts.gameserver.game.stats.effects.sub_effects.impl.*;
import ru.nts.gameserver.game.stats.impl.CreatureStats;
import ru.nts.gameserver.game.stats.skills.Skill;
import ru.nts.gameserver.game.world.World;
import ru.nts.gameserver.game.world.objects.creatures.Creature;
import ru.nts.gameserver.game.world.objects.creatures.npcs.Npc;
import ru.nts.gameserver.game.world.objects.creatures.npcs.Servitor;
import ru.nts.gameserver.game.world.objects.creatures.npcs.types.base.Pet;
import ru.nts.gameserver.game.world.objects.creatures.npcs.types.base.Summon;
import ru.nts.gameserver.game.world.objects.creatures.npcs.types.base.Warrior;
import ru.nts.gameserver.game.world.objects.creatures.players.FakePlayer;
import ru.nts.gameserver.game.world.objects.creatures.players.Player;
import ru.nts.gameserver.game.world.objects.items.Item;
import ru.nts.gameserver.system.data.memory.SkillTable;
import ru.nts.gameserver.system.data.pts.holder.CategoryDataHolder;
import ru.nts.gameserver.system.engines.geo.GeoData;
import ru.nts.gameserver.system.engines.geo.GeoEngine;
import ru.nts.gameserver.system.enums.*;
import ru.nts.gameserver.system.interfaces.IVarsSessionable;
import ru.nts.gameserver.tools.PositionUtils;
import ru.nts.scripts.nasc.EventNotifier;
import ru.nts.scripts.nasc.NASCScript;
import ru.nts.scripts.nasc.interfaces.INASCConstant;
import ru.nts.scripts.nasc.interfaces.shared.ISharedCreature;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

public class player_base extends NASCScript {

    private static final int CHECK_SPAWN_AFK = -2551;

    public player_base() {}

    @Override
    public void DEBUG_AI(ISharedCreature sCreature, int reply) {
        if (getNASCActor() instanceof Player player_actor) {
            if (sCreature instanceof Player player && player.isGM()) {
                switch (reply) {
                    case 0 -> player_actor.whisperTo("Debug", player);
                }
            }
        }
    }

    @Override
    public void CHECK_CMD_FOLLOW() { //Проверяем, не пора ли бросить все и бежать за хозяином
        if (getNASCActor() instanceof Player player_actor) {
            Party party = player_actor.getParty();
            if (party != null && (player_actor.isReal() || player_actor instanceof FakePlayer fake_actor && fake_actor.getMasterUserID() == party.getPartyLeaderOID())) {
                Player leader = party.getPartyLeader();
                if (leader.distance3D(player_actor) >= 1000) {
                    EventNotifier.getInstance().notify(player_actor, 1000, ENASCEvent.CMD_FOLLOW_ME);
                }
            }
        }
    }

    @Override
    public void CHECK_SWEEP() {
        TrySweep();
    }

    @Override
    public void CHECK_SELF_BUFF() {
        TrySelfBuff(EDesire.get.extra_high());
    }

    @Override
    public void CHECK_SELF_HEAL() {
        if (getNASCActor() instanceof Player player_actor) {
            final CreatureStats stats = player_actor.getStats();
            if (player_actor.isInCombat()) {
                if (CategoryDataHolder.isInCategory(heal_master2_3, player_actor)) {
                    if (stats.getCurrentHpPercents() <= 33) {
                        TryHeal(player_actor, 5, EDesire.get.extra_high());
                    } else if (stats.getCurrentHpPercents() <= 70) {
                        TryHeal(player_actor, 3, EDesire.get.high() + 50000);
                    } else {
                        if (stats.getCurrentHpPercents() <= 90) {
                            TryHeal(player_actor, 1, EDesire.get.normal(20000));
                        }
                    }
                } else if (CategoryDataHolder.isInCategory(buff_master2_3, player_actor) && stats.getCurrentHpPercents() <= 50) {
                    TryHeal(player_actor, 5, EDesire.get.high() + 50000);
                } else {
                    if (CategoryDataHolder.isInCategory(attribute_group_summoner, player_actor) && player_actor.getServitor() instanceof Summon summon && summon.getStats().getCurrentHpPercents() <= 90) {
                        TryHeal(summon, 3, EDesire.get.high() + 10000);
                    }
                }
            } else {
                if (stats.getCurrentHpPercents() < 100) {
                    TryHeal(player_actor, 1, EDesire.get.normal(1000));
                } else {
                    if (player_actor.getServitor() instanceof Summon summon && summon.getStats().getCurrentHpPercents() < 100) {
                        TryHeal(summon, 1, EDesire.get.normal(-1000));
                    }
                }
            }
        }
    }

    @Override
    public void CHECK_DISPEL() {
        if (getNASCActor() instanceof Player player_actor) {
            for (Effect effect : player_actor.getAllEffectsSnapshot()) {
                if (effect.getSkill() instanceof Skill skill && skill.isDebuff()) {
                    if (!TryDispellDebuff(player_actor, skill.getAbnormalType(), skill.getAbnormalLevel(), EDesire.get.high())) {
                        RequestPartyDispell(skill.getAbnormalType(), skill.getAbnormalLevel());
                        break;
                    }
                }
            }
        }
    }

    @Override
    public void CREATED() {
        if (getNASCActor() instanceof Player player_actor) {
            RemoveAllPCHateInfoIF(COND_IS_INVALID, 0);
            RemoveAllPCHateInfoIF(COND_HAS_HATE_LESS_THAN, 100);
            RemoveAllPCHateInfoIF(COND_IS_FAR_AWAY, 3000);

            TrySummonServitor();
            TrySummonCubics();

            if (player_actor instanceof FakePlayer fake_actor && fake_actor.isWild()) {
                AddTimerEx(CHECK_SPAWN_AFK, 60000);
            }
        }
    }

    @Override
    public void RECHECK_TARGET() {
        if (getNASCActor() instanceof Player player_actor) {

            RemoveAllPCHateInfoIF(COND_IS_INVALID, 0);

            TrySweep();

            if (player_actor.isCastingNow() || player_actor.isAttackingNow()) {
                EventNotifier.getInstance().notify(player_actor, 1000, ENASCEvent.RECHECK_TARGET);
                return;
            }

            Creature current_target = player_actor.getTargetAsCreature();
            if (current_target == null || current_target.isDead() || current_target == player_actor || current_target.isInArea(EAreaType.peace_zone) || player_actor.getAI().target_black_list.contains(current_target.getObjectId()) || !GeoEngine.CanSee(player_actor, current_target)) {
                if (current_target != null && current_target != player_actor) {
                    RemovePCAttackDesire(current_target.getObjectId());
                }
                player_actor.setTarget(null, EChangeTargetReason.CTR_BY_USER);
                current_target = null;

                //Сначала известные цели в границах прямой видимости.
                for (Creature creature : player_actor.getHateList().getMostHated(10)) {
                    if (player_actor.getHateList().getHate(creature) > 100 && !player_actor.getAI().target_black_list.contains(creature.getObjectId()) && player_actor.canSee(creature) && creature.isAlive()) {
                        current_target = creature;
                        break;
                    }
                }

                //Потом известные цели к которым можно пройти.
                if (current_target == null) {
                    for (Creature creature : player_actor.getHateList().getMostHated(10)) {
                        if (player_actor.getHateList().getHate(creature) > 100 && !player_actor.getAI().target_black_list.contains(creature.getObjectId()) && player_actor.canMove(creature) && creature.isAlive()) {
                            current_target = creature;
                            break;
                        }
                    }
                }

                //Если нет приоритетной цели по хейту, то ищем ближайшую(Только дикие боты)
                if (current_target == null && player_actor instanceof FakePlayer fake_actor && fake_actor.isWild()) {

                    List<Creature> around = fake_actor.getAI().lookAround(fake_actor.sight_distance() / 3, 500);

                    //Сначала ищем чаров вокруг.
                    //Порабощенных ботов ненавидим и агримся сразу же
                    //Если слишком близко подошел, с кармой или флагом, ну или просто не понравился)
                    current_target = around.stream().filter(creature -> !creature.isInArea(EAreaType.peace_zone)).filter(creature -> !fake_actor.getAI().target_black_list.contains(creature.getObjectId())).filter(creature -> fake_actor.canSee(creature) || fake_actor.canMove(creature)).filter(creature -> {
                        if (creature instanceof Player player) {
                            //Порабощенных ботов ненавидим и агримся сразу же
                            if (player instanceof FakePlayer fake) {
                                return fake.isSlave();
                            }
                            //Если слишком близко подошел, с кармой или флагом, ну или просто не понравился)
                            if (fake_actor.distance3D(player) < 100 || (player.isGuilty() > 0 || player.getKarma() > 0) && Rnd.chance(15 - fake_actor.getDifficulty().ordinal() * 2)) {
                                return true;
                            }
                        }
                        return false;
                    }).min(fake_actor.getRangeComparator()).orElse(null);

                    //Если чаров нет, то ищем мобов
                    if (current_target == null) {
                        current_target = around.stream().filter(creature -> !creature.isInArea(EAreaType.peace_zone)).filter(creature -> !fake_actor.getAI().target_black_list.contains(creature.getObjectId())).filter(creature -> fake_actor.canSee(creature) || fake_actor.canMove(creature)).filter(creature -> {
                            if (creature instanceof Warrior monster && Math.abs(monster.getLevel() - fake_actor.getLevel()) <= 20) {
                                return true;
                            }
                            return false;
                        }).min(fake_actor.getRangeComparator()).orElse(null);
                    }
                }
            }

            if (current_target != null) {
                DoAttack(current_target, Math.max(200, player_actor.getHateList().getHate(current_target)), false);
            }
        }
    }

    @Override
    public void NO_DESIRE() {
        if (getNASCActor() instanceof Player player_actor) {
            //Рабы отрабатывают социальный контракт.
            if (player_actor instanceof FakePlayer fake_actor && (fake_actor.isSlave() || fake_actor.isWildPrivate())) {
                //В этом методе, мы чинимся. Если потеряли мастера, ищем. Если не нашли вблизи - бежим на дальнюю дистанцию.
                if (fake_actor.lost_master()) {
                    List<Player> list = World.getInstance().getAroundPlayers(fake_actor, 1500, fake_actor.getReflectionID(),false);
                    for (Player player : list) {
                        if (player != null && fake_actor.isMyMaster(player)) {
                            //Если увидели родителя, обновляем таск следования за ним.
                            AddFollowDesire2(player, 100, eAFD2_FT_DIST_AND_DEGREE, 120 + Rnd.getInt(0, 50, true), fake_actor.follow_angle);
                            return;
                        }
                    }
                    Creature master = fake_actor.getMasterUser();
                    if (master != null && fake_actor.isSlave() && TryTeleportWithCommunityBoard(master.getLoc(), -1)) {
                        AddMovePartialDesire(master.getLoc(), 5000, 90);
                        return;
                    }
                }

                if (fake_actor.getNextLoc() != null && fake_actor.getNextLoc().distance2D(fake_actor) > 1000) {
                    Location temp = fake_actor.getNextLoc();
                    if (TryTeleportWithCommunityBoard(temp, -1)) {
                        fake_actor.setNextLoc(null);
                    }
                    AddMovePartialDesire(temp, 5000, 90);
                    return;
                }

                //Только если нет потребностей в починке состояния следования за мастером, то тогда чекаем, можно ли кого-то подлечить.
                if (CategoryDataHolder.isInCategory(recom_magic_group, fake_actor)) {
                    if (fake_actor.getParty() != null && fake_actor.getStats().getCurrentMpPercents() > 50) {
                        for (Player member : fake_actor.getParty().getPartyMembers()) {
                            if (member != null && member.getStats().getCurrentHpPercents() < 100) {
                                Skill skill = SelectSkillForHeal(member, 0);
                                if (skill != null) {
                                    AddUseSkillDesireEx(skill.getAimingTarget(fake_actor, member), skill, ST_HEAL, AMT_MOVE_TO_TARGET, FALSE, EDesire.get.normal(), 0);
                                }
                            }
                        }
                    }
                }
            }

            EventNotifier.getInstance().notify(player_actor, ENASCEvent.RECHECK_TARGET);
        }
    }

    @Override
    public void SEE_CREATURE(ISharedCreature sCreature) {
        if (getNASCActor() instanceof Player player_actor) {
            if (sCreature instanceof Creature creature) {

                if (player_actor.isDead()) {
                    return;
                }

                if (player_actor instanceof FakePlayer fake && !fake.isWild()) {
                    // Смотреть по сторонам и анализировать обстановку могут только дикие мобы.
                    return;
                }



                if (creature.isInArea(EAreaType.peace_zone)) {
                    // Если цель в мирной зоне, то не трогаем ее.
                    return;
                }

                if (player_actor.getHateList().getHate(creature) > 0) {
                    // Если на существо уже есть хейт, то новый не добавляем.
                    return;
                }

                double hate = 0;
                double range_add = player_actor.sight_distance() / Math.max(1, player_actor.distance3D(creature));
                // В целом, в SEE_CREATURE может попасть либо моб, либо чар.
                if (creature instanceof Warrior monster && Math.abs(monster.getLevel() - player_actor.getLevel()) <= 20) {
                    hate = 100 + range_add;
                } else if (creature instanceof Player player) {
                    if (player.isReal() && player.getKarma() > 0) {
                        return;
                    }
                    if (player instanceof FakePlayer fake) {
                        if (fake.isWild() && (fake.getFakeFactionId() == -1 || player_actor.isFactionMember(fake))) {
                            return;
                        }
                        if (fake.isSlave() || player_actor.isFactionEnemy(fake)) {
                            hate = 2000 + range_add;
                        }
                    }
                    if (player_actor.distance3D(player) < 100) {
                        hate = 200;
                    }
                } else {
                    return;
                }

                if (hate > 0) {
                    AddPCHate(creature, hate, true, 60);
                }
            }
        }
    }

    @Override
    public void DISAPPEAR_CREATURE(ISharedCreature sCreature, int objectId) {
        if (sCreature instanceof Creature creature) {
            RemovePCAttackDesire(creature.getObjectId());
        }
    }

    @Override
    public void ATTACKED(ISharedCreature sAttacker, int damage, long skill_name_id, int skill_id) {
        if (getNASCActor() instanceof Player player_actor) {
            if (sAttacker instanceof Creature attacker) {
                if (damage > 0) {
                    RequestPartyHeal(); //Всегда проверяем, нужно ли слать запрос на хил в пати.
                }

                if (attacker instanceof FakePlayer fake && fake.isWild() && !player_actor.isFactionEnemy(fake)) {
                    return;
                }

                if (damage != 0 && !(attacker instanceof FakePlayer)) {
                    AddPCHate(attacker, damage, true, 300);
                }
                if (damage == 0 && skill_name_id != 0 && !(attacker instanceof FakePlayer)) {
                    AddPCHate(attacker, 100, true, 300);
                }
                if (damage != 0 || skill_name_id != 0) {
                    EquipWeaponPC(); //Всегда проверяем надета ли пушка, когда нас бьют. Если нет, надеваем.
                    SummonServitor(1000); //Всегда проверяем, нужно ли суммонить сервитора.
                }

                double desire = EDesire.get.byDamage(damage, player_actor);

                if (attacker.isBlockMove() && (player_actor.getAI().battle_distance.get() > attacker.getStats().getPhysicalAttackRange() || player_actor.can_away) || player_actor.isClanMember(attacker) || player_actor.isPartyMember(attacker) || Chance(player_actor.getAI().regroup_chance.get()) && DistancePC(attacker, false) < player_actor.getAI().battle_distance.get() && (player_actor.can_away || attacker.getStats().calcCurrentMoveSpeed() <= player_actor.getStats().calcCurrentMoveSpeed())) {
                    if (!TryAway(attacker, EDesire.get.extra_high())) {
                        AddMoveToDesire(PositionUtils.findAroundPosition(player_actor, player_actor.getAI().battle_distance.get() - 50, player_actor.getAI().battle_distance.get() + 50, player_actor.getReflectionID()), EDesire.get.high());
                    }

                    if (!(player_actor.isClanMember(attacker) || player_actor.isPartyMember(attacker))) {
                        DoAttack(attacker, desire - 1, false);
                    }
                    return;
                }

                if (attacker.distance3D(player_actor) > player_actor.getAI().battle_distance.get() && player_actor.getHateList().getHate(attacker) > 5000 || attacker.getStats().calcMoveSpeed(EMoveType.MT_FAST) > player_actor.getStats().calcMoveSpeed(EMoveType.MT_FAST)) {
                    TryCatch(attacker, desire * 2);
                }
                DoAttack(attacker, desire, false);
            }
        }
    }

    @Override
    public void ABNORMAL_STATUS_CHANGED(ISharedCreature speller, long skill_name_id, int skill_id, int skill_level) {
        if (getNASCActor() instanceof Player player_actor) {
            Skill skill = GetSkill(skill_name_id);
            if (skill == null) {
                return;
            }

            //Дебафы из SOA, которые означают, что бот попытался убить РБ и тот его проклял. Это гг и бота надо убирать.
            //s_seed_of_annihilation_bestacon_silence1
            //s_seed_of_annihilation_cocracon_silence1
            //s_seed_of_annihilation_torum_ba_turn_stone1
            if ((skill_name_id== 435814401 || skill_name_id == 435879937 || skill_name_id ==  419823617) && player_actor instanceof FakePlayer fake_actor && fake_actor.isWild()) {
                player_actor.logout(ELogoutType.KICK);
                return;
            }

            //Цикличные дебафы, которые вызывают сами себя. Ботов они не убивают, а висят бесконечно.
            //s_npc_deadly_poison
            //s_npc_deadly_bleed
            if ((skill_id == 6815 || skill_id == 6816) && player_actor instanceof FakePlayer fake_actor && fake_actor.isWild()) {
                if (player_actor.getEffectBySkillId(skill_id) instanceof Effect effect) {
                    effect.stop(true);
                }
                return;
            }

            //Если положили дебаф, смотрим, есть ли у нас что-то, что может его снять.
            if (skill.isDebuff() && skill_level > 0) {
                if (!TryDispellDebuff(player_actor, skill.getAbnormalType(), skill.getAbnormalLevel(), EDesire.get.high())) {
                    RequestPartyDispell(skill.getAbnormalType(), skill.getAbnormalLevel());
                }
            }

            //Если кончился дизарм, то шедулим надевание пухи
            if (Skill_ContainSubEffect(skill, p_disarm.class) && skill_level == 0) {
                EquipWeaponPC();
            }

            if (skill.containSubEffect(p_transform.class)) {
                player_actor.doSelfAnalysis();
                player_actor.getAI().changeTactics();
            }

            if (skill.containSubEffect(p_block_move.class) && CategoryDataHolder.isInCategory(INASCConstant.fighter_group, player_actor) && CategoryDataHolder.isInCategory(INASCConstant.mage_group, speller)) {
                player_actor.getAI().changeWeapon(skill_level > 0, skill_level <= 0);
            }
        }
    }

    @Override
    public void USE_SKILL_FINISHED(long skill_name_id, ISharedCreature sTarget, int success) {
        if (getNASCActor() instanceof Player player_actor) {
            if (sTarget instanceof Creature cTarget) {
                RequestPartyRecharge();

                if ((player_actor.isClanMember(cTarget) || player_actor.isPartyMember(cTarget))) {
                    StopPCHate(cTarget);
                    return;
                }

                if (cTarget.isDead()) {
                    StopPCHate(cTarget);
                    return;
                }

                if (player_actor.getHateList().getHate(cTarget) <= 0) {
                    return;
                }

                Skill skill = SkillTable.getInstance().getInfo(skill_name_id);

                if (CategoryDataHolder.isInCategory(heal_master2_3, player_actor)) {
                    if (skill != null && skill.isGood()) {
                        return;
                    }
                }

                DoAttack(cTarget, EDesire.get.normal(5000), skill != null && skill.getNextAction() == ENextSkillAction.attack);
            }
        }
    }

    @Override
    public void ATTACK_FINISHED(ISharedCreature sTarget) {
        if (getNASCActor() instanceof Player player_actor) {
            if (sTarget instanceof Creature cTarget) {
                if (player_actor.isClanMember(cTarget) || player_actor.isPartyMember(cTarget)) {
                    StopPCHate(cTarget);
                    return;
                }

                if (cTarget.isDead()) {
                    StopPCHate(cTarget);
                    return;
                }

                if (player_actor.getHateList().getHate(cTarget) <= 0) {
                    return;
                }

                DoAttack(cTarget, EDesire.get.normal(5000), true);
            }
        }
    }

    //Кастомные методы
    @Override
    public void PARTIAL_MOVE_FINISH(int x, int y, int z){
        if (getNASCActor() instanceof FakePlayer fake_actor) {
            Location target_loc = new Location(x, y, z);
            if (!target_loc.isEmptyPoint(GeoData.WORLD_MAP_MIN_X, GeoData.WORLD_MAP_MAX_X, GeoData.WORLD_MAP_MIN_Y, GeoData.WORLD_MAP_MAX_Y) && target_loc.distance2D(fake_actor) > 200) {
                AddMovePartialDesire(target_loc, 5000, EDesire.get.small());
            } else if (!target_loc.isEmptyPoint(GeoData.WORLD_MAP_MIN_X, GeoData.WORLD_MAP_MAX_X, GeoData.WORLD_MAP_MIN_Y, GeoData.WORLD_MAP_MAX_Y) && target_loc.distance2D(fake_actor) <= 200) {
                fake_actor.setNextLoc(null);
            }
        }
    }

    @Override
    public void MOVE_TO_NPC_FINISH(int target_object_id, int next_action_id, Object object1, Object object2){
        if (getNASCActor() instanceof FakePlayer fake_actor) {
            Creature target = World.getObjects().getWorldObjectAs(target_object_id, Creature.class);
            if (target != null) {
                try {
                    switch (next_action_id) {
                        case MTN_TELEPORT -> { //Teleport, after move finishing
                            if (fake_actor.isAlive() && object1 instanceof Location temp_loc) {
                                if (fake_actor.getMasterUser() instanceof Player master && master.isGM()) {
                                    fake_actor.whisperTo("[NPC: " + target.getName() + "][Телепорт: " + temp_loc.toString() + "]", master);
                                }
                                fake_actor.teleToLocation(temp_loc.getX(), temp_loc.getY(), temp_loc.getZ(), temp_loc.getReflectionID());
                            }
                        }
                        case MTN_BYPASS_NPC -> { //Request Bypass, after move finishing
                            if (fake_actor.isAlive() && object1 instanceof String bypass) {
                                try {
                                    if (target instanceof Npc npc) {
                                        fake_actor.setLastNpc(npc);
                                        fake_actor.setSessionVar(IVarsSessionable.FAKE_BYPASS_NPC, target_object_id);
                                        if (fake_actor.getMasterUser() instanceof Player master && master.isGM()) {
                                            fake_actor.whisperTo("[NPC: " + npc.getName() + "][Bypass: " + bypass + "]", master);
                                        }
                                    }
                                    fake_actor.getBypassHandler().checkBypass(bypass, false);
                                } finally {
                                    fake_actor.unsetSessionVar(IVarsSessionable.FAKE_BYPASS_NPC);
                                }
                            }
                        }
                    }
                } catch (Exception ignored) {
                }
            }
        }
    }

    @Override
    public void CHAT(int sender_object_id, int chat_type, String message) {
        if (getNASCActor() instanceof FakePlayer fake_actor) {
            Player sender = World.getObjects().getPlayerByObjectID(sender_object_id);
            if (sender != null && fake_actor.isMyMaster(sender)) {
                switch (chat_type) {
                    case 3 -> { //ESayPacketType.PARTY
                        String answer = "";
                        if (message.equalsIgnoreCase("ближе")) {
                            answer = "+";
                            fake_actor.close = true;
                        } else if (message.equalsIgnoreCase("дальше")) {
                            answer = "+";
                            fake_actor.close = false;
                        }
                        if (!answer.isEmpty() && !answer.isBlank()) {
                            fake_actor.sayToParty(answer);
                        }
                    }
                }
            }
        }
    }

    @Override
    public void PARTY_DIED(ISharedCreature party_private, ISharedCreature killer) {
        if (getNASCActor() instanceof Player player_actor) {
            if (party_private instanceof Servitor servitor && servitor.getPlayer() == player_actor) {
                SummonServitor(TimeUnit.SECONDS.toMillis(servitor.getTemplate().corpse_time + 1));
            }
        }
    }

    @Override
    public void PARTY_JOIN() {
        if (getNASCActor() instanceof FakePlayer fake_actor) {
            if (fake_actor.isSlave() && fake_actor.getMasterUser() instanceof Player master && master.isReal()) {

                Player requestor = fake_actor.getTransactionRequester();

                fake_actor.setTransactionRequester(null);

                if (requestor == null) {
                    return;
                }

                if (requestor.isFake()) {
                    return;
                }

                if (requestor.isCursedWeaponEquipped() || fake_actor.isCursedWeaponEquipped()) {
                    return;
                }

                if (!requestor.equals(master) && !requestor.isPartyMember(master)) {
                    return;
                }

                requestor.setTransactionRequester(null);

                if (requestor.getParty() == null) {
                    return;
                }

                if (fake_actor.getTransactionType() != ETransactionType.PARTY || fake_actor.getTransactionType() != requestor.getTransactionType() && requestor != master && !requestor.isPartyMember(master)) {
                    return;
                }

                if (fake_actor.getSessionVar("event_team_pvp", String.class) != null && fake_actor.getTeam() != requestor.getTeam()) {
                    return;
                }

                if (requestor.getParty().member_count() >= 9) {
                    requestor.sendSysMessage(S_SYSTEM_MSG.m155_THE_PARTY_IS_FULL);
                    return;
                }

                fake_actor.joinParty(requestor.getParty());
                fake_actor.getParty().broadcastToPartyMembersExclude(fake_actor, new S_PARTY_MEMBER_POSITION(fake_actor));

                for (Player member : fake_actor.getParty().getPartyMembers()) {
                    StopPCHate(member);
                    RemovePCAttackDesire(member.getObjectId());
                    if (member instanceof FakePlayer fake) {
                        StopPCHate(fake_actor);
                        RemovePCAttackDesire(fake_actor.getObjectId());
                    }
                    if (fake_actor.isMyMaster(member)) {
                        AddFollowDesire2(member, 100, eAFD2_FT_DIST_AND_DEGREE, 120 + Rnd.getInt(0, 50, true), fake_actor.follow_angle);
                    }
                }

                fake_actor.getTargetSelector().block();
            } else if (fake_actor.isWildPrivate() && fake_actor.getMasterUser() instanceof FakePlayer master && master.isWild()) {

                Player requestor = fake_actor.getTransactionRequester();

                fake_actor.setTransactionRequester(null);

                if (requestor == null) {
                    return;
                }

                if (requestor.isReal()) {
                    return;
                }

                if (requestor.isCursedWeaponEquipped() || fake_actor.isCursedWeaponEquipped()) {
                    return;
                }

                if (!requestor.equals(master)) {
                    return;
                }

                requestor.setTransactionRequester(null);

                if (requestor.getParty() == null) {
                    return;
                }

                if (fake_actor.getTransactionType() != ETransactionType.PARTY || fake_actor.getTransactionType() != requestor.getTransactionType() && requestor != master && !requestor.isPartyMember(master)) {
                    return;
                }

                if (fake_actor.getSessionVar("event_team_pvp", String.class) != null && fake_actor.getTeam() != requestor.getTeam()) {
                    return;
                }

                if (requestor.getParty().member_count() >= 9) {
                    return;
                }

                fake_actor.joinParty(requestor.getParty());

                for (Player member : fake_actor.getParty().getPartyMembers()) {
                    StopPCHate(member);
                    RemovePCAttackDesire(member.getObjectId());
                    if (member instanceof FakePlayer fake) {
                        StopPCHate(fake_actor);
                        RemovePCAttackDesire(fake_actor.getObjectId());
                    }
                    if (fake_actor.isMyMaster(member)) {
                        AddFollowDesire2(member, 100, eAFD2_FT_DIST_AND_DEGREE, 120 + Rnd.getInt(0, 50, true), fake_actor.follow_angle);
                    }
                }

                fake_actor.getTargetSelector().block();
            }
        }
    }

    @Override
    public void PARTY_LEFT() {
        if (getNASCActor() instanceof Player player_actor) {
            player_actor.getTargetSelector().unblock();
            RemovePCDesire(EPState.PSTATE_FOLLOW);
        }
    }

    @Override
    public void CMD_FOLLOW_ME() {
        if (getNASCActor() instanceof FakePlayer fake_actor) {
            if (fake_actor.getMasterUserID() != 0) {
                Party party = fake_actor.getParty();
                if (party != null && party.getPartyLeaderOID() == fake_actor.getMasterUserID()) {
                    Player leader = party.getPartyLeader();
                    if (leader != null) {
                        RemoveAllPCHateInfoIF(COND_ALL, 0);
                        RemoveAllPCDesire();
                        AddMoveToDesire(leader.getX(), leader.getY(), leader.getZ(), EDesire.get.max());
                    }
                }
            }
        }
    }

    @Override
    public void PARTY_MEMBER_LEFT() {
        if (getNASCActor() instanceof FakePlayer fake_actor) {
            if (fake_actor.getParty() instanceof Party party) {
                for (Player member : party.getPartyMembers()) {
                    if (member.getObjectId() == fake_actor.getMasterUserID()) { //Мы не выходим из пати, пока наш мастер в ней.
                        return;
                    }
                }
                party.tryLeaveParty(fake_actor);
            }
        }
    }

    @Override
    public void REVIVE_REQUEST(int requester_object_id, int pet) {
        if (getNASCActor() instanceof FakePlayer fake_actor) {
            Creature reviever = World.getObjects().getWorldObjectAs(requester_object_id, Creature.class);
            if (fake_actor.death_count.get() <= 0) {
                if (reviever != null) {
                    fake_actor.whisperTo("Даже не пытайся!", reviever);
                }
                return;
            }
            if (reviever != null && (fake_actor.isPartyMember(reviever) || fake_actor.isClanMember(reviever))) {
                fake_actor.stopTask(EFutureType.PLAYER_AI_REQUEST_RESURRECT_TASK);
                fake_actor.stopTask(EFutureType.FAKE_PLAYER_LOGOUT_TASK);

                fake_actor.reviveAnswer(1);
                if (pet == 1) {
                    ThreadPoolService.getInstance().schedule(() -> fake_actor.rebuff(true, true), 3000);
                }
            } else {
                fake_actor.reviveAnswer(0);
            }
        }
    }

    @Override
    public void PARTY_ACTION_REQUEST(int help_type, int requester_object_id, Object object1, Object object2) {
        if (getNASCActor() instanceof FakePlayer fake_actor) {
            Creature creature = World.getObjects().getWorldObjectAs(requester_object_id, Creature.class);
            if (creature instanceof Player requester && fake_actor.isPartyMember(requester)) {
                if (help_type == AT_ATTACK && object1 instanceof Creature target_creature && object2 instanceof Number desire) {
                    if (target_creature.getObjectId() == fake_actor.getAI().current_attacked_target.get()) {
                        return;
                    }

                    if (fake_actor.getAI().af_protect_master.get() && !fake_actor.isMyMaster(requester)) {
                        return;
                    }

                    double chance = 0;
                    if (EDesire.get.normal() < desire.doubleValue()) {
                        chance = 75;
                    } else {
                        chance = 30;
                    }
                    if (fake_actor.isMyMaster(requester)) {
                        chance += 20;
                    }
                    if (!fake_actor.isInCombat()) {
                        chance += 20;
                    } else {
                        if (fake_actor.getStats().getCurrentHpPercents() < 30) {
                            chance -= 30;
                        } else {
                            chance -= 10;
                        }
                    }
                    if (fake_actor.distance3D(target_creature) > 1000) {
                        chance -= 15;
                    } else {
                        if (fake_actor.distance3D(target_creature) > 700) {
                            chance -= 5;
                        } else {
                            chance += (1000 - fake_actor.distance3D(target_creature)) / 100;
                        }
                    }

                    if (Rnd.chance(chance)) {
                        DoAttack(target_creature, desire.doubleValue(), false);
                    }
                } else if (help_type == AT_PROTECT && object1 instanceof Creature attacker && object2 instanceof Number desire) {
                    if (attacker.getObjectId() == fake_actor.getAI().current_attacked_target.get()) {
                        return;
                    }

                    if (fake_actor.getAI().af_protect_master.get() && !fake_actor.isMyMaster(requester)) {
                        return;
                    }

                    double chance = 0;
                    if (EDesire.get.normal() < desire.doubleValue()) {
                        chance = 75;
                    } else {
                        chance = 30;
                    }
                    if (fake_actor.isMyMaster(requester) || (CategoryDataHolder.isInCategory(heal_master2_3, requester))) {
                        chance += 30;
                    }
                    if (!fake_actor.isInCombat()) {
                        chance += 20;
                    } else {
                        if (fake_actor.getStats().getCurrentHpPercents() < 30) {
                            chance -= 30;
                        } else {
                            chance -= 10;
                        }
                    }
                    if (fake_actor.distance3D(requester) > 1000) {
                        chance -= 15;
                    } else {
                        if (fake_actor.distance3D(requester) > 700) {
                            chance -= 5;
                        } else {
                            chance += (1000 - fake_actor.distance3D(requester)) / 100;
                        }
                    }

                    if (Rnd.chance(chance)) {
                        DoAttack(attacker, desire.doubleValue(), false);
                    }
                }

                switch (help_type) {
                    case AT_HEAL -> {
                        if (object1 instanceof Number hp_percent && object2 instanceof Number counter) {
                            double actual_hp_percent = requester.getStats().getCurrentHpPercents();
                            if (actual_hp_percent > 30 && !CategoryDataHolder.isInCategory(heal_master2_3, fake_actor)) {
                                return;
                            }
                            if (actual_hp_percent < hp_percent.doubleValue() - 5 || Rnd.chance(100 - hp_percent.doubleValue()) || counter.intValue() > 0) {
                                TryHeal(requester, counter.intValue(), EDesire.get.high());
                            }
                        }
                    }
                    case AT_RECHARGE -> {
                        if (object1 instanceof Number mp_percent && object2 instanceof Number counter) {
                            double actual_mp_percent = requester.getStats().getCurrentMpPercents();
                            if (actual_mp_percent < mp_percent.doubleValue() - 5 || Rnd.chance(100 - mp_percent.doubleValue()) || counter.intValue() > 0) {
                                TryRecharge(requester, counter.intValue(), EDesire.get.high());
                            }
                        }
                    }
                    case AT_DISPELL -> {
                        if (object1 instanceof EAbnormalStatusSlotType abnormal && object2 instanceof Number level) {
                            TryDispellDebuff(requester, abnormal, level.intValue(), EDesire.get.high());
                        }
                    }
                    case AT_BUFF -> {
                    }
                    case AT_RESURRECT -> {
                        int chance = switch (fake_actor.getDifficulty()) {
                            case EASY -> 10;
                            case NORMAL -> 20;
                            case MANUAL, HARD -> 30;
                            case EXTRA_HARD -> 50;
                            default -> 0;
                        };

                        if (!TryResurrect(requester, chance * 2, EDesire.get.high())) {
                            if (Rnd.chance(chance)) {
                                Item item = fake_actor.getInventory().getItemBySkillSubEffect(i_resurrection.class);
                                if (item != null) {
                                    if (fake_actor.distance3D(requester) <= 100) {
                                        fake_actor.executeMessage(new UseItemAction(fake_actor, requester, item, false));
                                    } else {
                                        AddMoveToTargetDesire(requester,90,-1, EDesire.get.extra_high());
                                    }
                                }
                            }
                        }
                    }
                    case AT_SUMMON -> {
                    }
                    case AT_ESCAPE -> {
                        final Item item = fake_actor.getInventory().getItemBySkillSubEffect(i_escape.class);
                        if (item != null) {
                            fake_actor.executeMessage(new UseItemAction(fake_actor, fake_actor, item, false));
                        }
                    }
                    case AT_CMD_ATTACK -> {
                        if (fake_actor.getAI().af_protect_master.get()) {
                            return;
                        }
                        if (fake_actor.isMyMaster(requester) && object1 instanceof Number number) {
                            int target_object_id = number.intValue();
                            if (target_object_id == fake_actor.getAI().current_attacked_target.get()) {
                                return;
                            }
                            Creature target = World.getObjects().getWorldObjectAs(target_object_id, Creature.class);
                            if (target != null) {
                                fake_actor.getAI().high_priority_target = target_object_id;
                                RemoveAllPCAttackDesire();
                                DoAttack(target, EDesire.get.extra_high(500), false);
                            }
                        }
                    }
                    case AT_CMD_STOP -> {
                        if (fake_actor.isMyMaster(requester)) {
                            try {
                                fake_actor.setTarget(null, EChangeTargetReason.CTR_BY_USER);
                                fake_actor.getAI().high_priority_target = 0;
                                fake_actor.getAI().current_attacked_target.set(0);
                                RemoveAllPCHateInfoIF(COND_ALL, 0);
                                RemoveAllPCDesire();
                                RemoveAllPCAttackDesire();
                                AddFollowDesire2(requester, 100, eAFD2_FT_DIST_AND_DEGREE, 120 + Rnd.getInt(0, 50, true), fake_actor.follow_angle);
                                AddMoveToDesire(PositionUtils.getPointInRadius(fake_actor.getLoc(), requester.getLoc(), Rnd.getInt(50, 75, true), fake_actor.follow_angle), 200);
                                if (fake_actor.getServitor() instanceof Pet pet) {
                                    MakeServitorAction(requester, 100, 17, -2);
                                } else if (fake_actor.getServitor() instanceof Summon summon) {
                                    MakeServitorAction(requester, 100, 23, -2);
                                }
                            } finally {
                                fake_actor.setTarget(null, EChangeTargetReason.CTR_BY_USER);
                            }
                        }
                    }
                    case AT_CMD_PICKUP -> {
                        if (fake_actor.isMyMaster(requester) && object1 instanceof Number number) {
                            Item item_to_pickup = World.getObjects().getWorldObjectAs(number.intValue(), Item.class);
                            if (item_to_pickup != null) {
                                AddGetItemDesireEx(item_to_pickup, EDesire.get.extra_high());
                            }
                        }
                    }
                }
            }
        }
    }

    @Override
    public void CUBIC_DISAPPEAR(int cubic_id, int cubic_level, long skill_index) {
        if (getNASCActor() instanceof Player player_actor) {
            long[] cubics = SelectSkillsForSummonCubic(cubic_id);
            if (cubics.length > 0) {
                AddUseSkillDesireEx(player_actor, cubics[0], ST_HEAL, AMT_STAND, TRUE, EDesire.get.normal(), 0);
            }
        }
    }

    @Override
    public void BUFFER_EFFECT_REMOVED(){
        if (getNASCActor() instanceof Player player_actor) {
            player_actor.getAI().buff_removed_count.incrementAndGet();
        }
    }

    @Override
    public void TIMER_FIRED_EX(int timer_id, Object arg1, Object arg2) {
        if (getNASCActor() instanceof FakePlayer fake_actor) {
            if (timer_id == CHECK_SPAWN_AFK) {
                boolean near = fake_actor.getController() != null && fake_actor.distance3D(fake_actor.getController()) < 300;
                if (near && fake_actor.isAlive() && !fake_actor.isInCombat()) {
                    Location loc = PositionUtils.findAroundPosition(fake_actor.getLoc(), 500, 1000, fake_actor.getReflectionID());
                    if (fake_actor.getZ() > loc.getZ()) {
                        fake_actor.teleToLocation(loc);
                    } else {
                        AddTimerEx(CHECK_SPAWN_AFK, 250);
                    }
                }
            }
        }
        super.TIMER_FIRED_EX(timer_id, arg1, arg2);
    }

    @Override
    public void SERVITOR_SUMMON_FINISHED() {
        if (getNASCActor() instanceof Player player_actor) {
            player_actor.rebuff(true, true);
            if (player_actor.hasComponent(EComponentType.ITEM_SELECTOR)) {
                final List<Item> snapshot = player_actor.getInventory().getItemsSnapshot();
                player_actor.getItemSelector().items.stream()
                        .map(id -> player_actor.getInventory().getItemByItemId(id, snapshot))
                        .filter(Objects::nonNull)
                        .filter(item -> item.getTemplate().getAction().isSummonSoulShot())
                        .forEach(item -> new AutoSoulShotItemAction(player_actor, item.getItemId(), true));
            }
        }
    }

    @Override
    public void MY_DYING(ISharedCreature sLast_attacker) {
        if (getNASCActor() instanceof FakePlayer fake_actor) {
            fake_actor.addTaskIfAbsent(EFutureType.PLAYER_AI_REQUEST_RESURRECT_TASK, this::RequestPartyResurrect, 2000, 5000);
            if (fake_actor.isWild() && sLast_attacker instanceof Player player) {
                if (Rnd.chance(fake_actor.getDifficulty().ordinal() * 15)) {
                    World.getInstance().getAroundObjects(fake_actor, FakePlayer.class, false, 500, 50)
                            .stream()
                            .filter(Creature::isAlive)
                            .filter(FakePlayer::isWild)
                            .forEach(fake -> {
                        AddPCHate(player, 10000 * fake_actor.getDifficulty().ordinal(), true, 60);
                        EventNotifier.getInstance().notify(fake, ENASCEvent.RECHECK_TARGET);
                    });
                }
            }
        }
    }

    @Override
    public void DIE_SET(ISharedCreature sDied) {
        if (getNASCActor() instanceof Player player_actor) {
            if (sDied instanceof Creature died && died.isDead()) {
                if (died instanceof Warrior mob) {
                    for (ISharedCreature creature : mob.getMostDamage(5)) {
                        if (creature instanceof Player player && player.isReal() && player_actor.distance3D(player) <= 2000) {
                            AddPCHate(player, 50000, true, 60);
                        }
                    }
                }
                if (player_actor.getHateList().getHate(died) > 0) {
                    StopPCHate(died);
                }
                RemovePCAttackDesire(died.getObjectId());
                EventNotifier.getInstance().notify(player_actor, ENASCEvent.RECHECK_TARGET);
            }
        }
    }

    @Override
    public void START_COMBAT() {
        if (getNASCActor() instanceof Player player_actor) {
            if (player_actor.isAlive()) {
                TryToogles(true);
            }
            player_actor.getHateList().clearAllSnapshots();
            player_actor.addTaskIfAbsent(EFutureType.FAKE_PLAYER_HATELIST_SNAPSHOTS_TASK, player_actor.getAI()::doHateSnapshot, 0, 30, TimeUnit.SECONDS);
        }
    }

    @Override
    public void STOP_COMBAT() {
        if (getNASCActor() instanceof Player player_actor) {
            if (player_actor.isAlive()) {
                TryToogles(false);
            }
            player_actor.stopTask(EFutureType.FAKE_PLAYER_HATELIST_SNAPSHOTS_TASK);
        }
    }
}
Отлично ты красавчик это золото ) мои боты просто переключались допустим поднял 6й лвл переключается на FakePlayerMovement.java если добегает ровно до города то на CityNavigation.java а потом ищет нпс бафера и переключает на Buff.java но я ещё не видел как это работает так как боты не доходят до города ) это я пока проверяю только одну расу ХуманыБойцы и Маги, маги это вообще нет бы обой арку и бежать только прямо я там сделал много мобов , они разворачиваются и тыркаются у стенки , но до них потом доберусь, так как хотябы настроить правельное передвижение Бойцов а дальше думаю просртто в гланый файл добовлять каждой расе коордиенаты или посоветуешь что то лучше , но щас пока буду решать проблему с нормальны поведением передвижения ещё у меня в хф чистых 203 файла геодаты она включина вроде все файлы есть , можеи ли это быть из за геодаты ? Ок а можно пошаговый план действий что бы всё чётко класнно получилось? Спасибо за код интегрировал к себе то пришлось переписсать много так как у меня ХФ а насчёт
если его хорошо настроить как ты говоршь он потом сможет выдержать 5-7-10к ботов ?
 
Ок а можно пошаговый план действий что бы всё чётко класнно получилось?
Конечно. Записывай.

0) Забить на л2.
1) Закончить школу.
2) Закончить университет.
3) Не забывать ходить к зубному.

Все будет не просто "класнно", а будет ваще ахуенно. Если тебя конечно по пути из школы цыгане не своруют и в с**с-рабство не продадут. Но тут сорян, я ничем не помогу.
 
У
Конечно. Записывай.

0) Забить на л2.
1) Закончить школу.
2) Закончить университет.
3) Не забывать ходить к зубному.

Все будет не просто "класнно", а будет ваще ахуенно. Если тебя конечно по пути из школы цыгане не своруют и в с**с-рабство не продадут. Но тут сорян, я ничем не помогу.
УУУУУУуахаххахахаххахахах )))))))))))))))))))))) уже всё закончил, на зубного бабосиков нету ))) ещё есть файлы с логикой ботов ? Я просто переживаю что рекаст не выдержит и сервер ляжет от 10к ботов 63 класса ботов логика от 1 лвала до 85, конечная цель выйграл тот кто собрал все эпики взял хиро заточился до максимума забрал все замки , такое хочу реализовать потом обновить до годес оф дестракшена, потом к этому времени уже выростит сын а я ему скажу что это реальные игроки и буду ловить его и пиз.....ь что бы не играл азартно лудомановские игры )))))) а учился вувухахахах и ходил к зубному вухахахах
Сообщение объединено:
Есть ещё какие ни будь варианты что бы боты ровно обегали препятствия без рекаста ?
 
Последнее редактирование:
У

УУУУУУуахаххахахаххахахах )))))))))))))))))))))) уже всё закончил, на зубного бабосиков нету ))) ещё есть файлы с логикой ботов ? Я просто переживаю что рекаст не выдержит и сервер ляжет от 10к ботов 63 класса ботов логика от 1 лвала до 85, конечная цель выйграл тот кто собрал все эпики взял хиро заточился до максимума забрал все замки , такое хочу реализовать потом обновить до годес оф дестракшена, потом к этому времени уже выростит сын а я ему скажу что это реальные игроки и буду ловить его и пиз.....ь что бы не играл азартно лудомановские игры )))))) а учился вувухахахах и ходил к зубному вухахахах
Сообщение объединено:
Есть ещё какие ни будь варианты что бы боты ровно обегали препятствия без рекаста ?
Ты хоть сам понимаешь что делаешь?
 
Ты хоть сам понимаешь что делаешь?
Да , щас жду ядро от С4 там топовые боты, скинут гляну , перепешу под ХФ посмотрим что выйдет, у Arista они просто тупорылые выполняют команды, но они не могут сами пойти скрафтить купить выбить заточить мозгов нету только команды, а мои это полноценный мир ИИ игроков у каждого есть выбор что ему делать крафт пвп фарм споил рейд рб эпики осады точка баф пати кланы и т.д у меня фишка типо % на выбор в зависимости от условия, допустим 6й лвл поднял ага нужно бафнуть , прожимает сое либо бижит в город бафать у нпс, дальше вообще будет ппц)) это только моё начало путешествия ))) Просто пилю ХФ чисто с ботами для себя , не куда больше нету смысла серв запусть не кто не играет л2, только деды и лудоманы , в стиме есть на много лучше и интересней игры )))
Сообщение объединено:
:loltt0:
 
Последнее редактирование:
Последнее редактирование:
Два года назад, заходил на сервер к Aristo, охренел от качества мувинга и геоэнджейна, потом охренел от реализации ботов, я все это время пытался и пытаюсь приблизится к качеству мувинга, к примеру: расчет пути на огромное расстояние занял у меня 247,2825 мс, а у Aristo 2.8 мс, мне скинули классы упаковки и работы с координатами и я много оптимизировал и в итоге 12-18 мс, но я не приблизился к значению 2.8..., теперь про ботов, использование скилов и стратегия ботов это самое сложное, у Aristo они фармили очень жестких мобов в среднем шмоте и очень четко работали, я еще не смог полноценно реализовать использование скилов одним ботом в броне заточенной +9999 и пушкой на +9999 и бижей +9999, не говоря уже о группе ботов, и 10 процентов не достиг от качества ботов Aristo...
 
Два года назад, заходил на сервер к Aristo, охренел от качества мувинга и геоэнджейна, потом охренел от реализации ботов, я все это время пытался и пытаюсь приблизится к качеству мувинга, к примеру: расчет пути на огромное расстояние занял у меня 247,2825 мс, а у Aristo 2.8 мс, мне скинули классы упаковки и работы с координатами и я много оптимизировал и в итоге 12-18 мс, но я не приблизился к значению 2.8..., теперь про ботов, использование скилов и стратегия ботов это самое сложное, у Aristo они фармили очень жестких мобов в среднем шмоте и очень четко работали, я еще не смог полноценно реализовать использование скилов одним ботом в броне заточенной +9999 и пушкой на +9999 и бижей +9999, не говоря уже о группе ботов, и 10 процентов не достиг от качества ботов Aristo...
а можно где то посмотреть эти классы упаковки и работы с координатами?)
 
а можно где то посмотреть эти классы упаковки и работы с координатами?)
Java:
package ru.nts;

/**
 * Интерфейс для упаковки трехмерных координат в единое long значение.
 *
 * <p><b>Архитектурное решение:</b><br>
 * Упаковка координат XYZ в один long (64 бита) позволяет значительно сократить потребление памяти
 * и повысить производительность за счет cache locality. Вместо хранения трех int (12 байт)
 * или трех float (12 байт) используется один long (8 байт) - экономия 33%.
 *
 * <p><b>Битовая структура long (55 бит координаты + 8 бит флаги):</b>
 * <pre>
 * | Бит 63 | Биты 62-55 (8 бит) | Биты 54-35 (20 бит) | Биты 34-15 (20 бит) | Биты 14-0 (15 бит) |
 * | резерв |      флаги         |         X           |         Y           |         Z          |
 * </pre>
 *
 * <p><b>Диапазоны координат (знаковые):</b>
 * <ul>
 *   <li><b>X:</b> от -524,288 до 524,287 (20 бит, 2^19)</li>
 *   <li><b>Y:</b> от -524,288 до 524,287 (20 бит, 2^19)</li>
 *   <li><b>Z:</b> от -16,384 до 16,383 (15 бит, 2^14)</li>
 * </ul>
 *
 * <p><b>Флаги (8 бит, биты 62-55):</b>
 * <ul>
 *   <li><b>Бит 55:</b> PFR_CAN_GO</li>
 *   <li><b>Бит 56:</b> PFR_PATH_FOUND</li>
 *   <li><b>Бит 57:</b> PFR_ERROR</li>
 *   <li><b>Бит 58:</b> TRACE_SUCCESS (1 - проход успешен, 0 - препятствие)</li>
 *   <li><b>Биты 59-62:</b> резервные флаги для будущего использования</li>
 * </ul>
 *
 * <p><b>Thread Safety:</b> Все методы thread-safe, так как работают с immutable long значениями.
 *
 * @author Aristo
 * @since 21.11.2025
 */
public interface IXYZ {

    /**
     * Размер региона в мировых координатах Lineage 2.
     */
    float REGION_SIZE = 32768.0f;

    /**
     * Смещение по X для вычисления региона из мировых координат.
     */
    int X_REGION_OFFSET = 20;

    /**
     * Смещение по Y для вычисления региона из мировых координат.
     */
    int Y_REGION_OFFSET = 18;

    /**
     * Минимальная Z координата мира.
     */
    float Z_MIN = -16384.0f;

    /**
     * Максимальная Z координата мира.
     */
    float Z_MAX = 16383.0f;

    /**
     * Размер геоячейки в единицах мира Lineage 2.
     */
    float CELL_SIZE = 16.0f;

    // --- Конфигурация бит ---

    int X_BITS = 20;
    int Y_BITS = 20;
    int Z_BITS = 15;
    int FLAGS_BITS = 8;

    long X_MASK = 0xFFFFF;
    long Y_MASK = 0xFFFFF;
    long Z_MASK = 0x7FFF;
    long FLAGS_MASK = 0xFF;

    long X_OFFSET = 1 << (X_BITS - 1);
    long Y_OFFSET = 1 << (Y_BITS - 1);
    long Z_OFFSET = 1 << (Z_BITS - 1);

    int X_SHIFT = Y_BITS + Z_BITS;
    int Y_SHIFT = Z_BITS;
    int Z_SHIFT = 0;
    int FLAGS_SHIFT = X_BITS + Y_BITS + Z_BITS;

    // --- Флаг-биты ---

    int FLAG_PFR_CAN_GO = 0;
    int FLAG_PFR_PATH_FOUND = 1;
    int FLAG_PFR_ERROR = 2;
    int FLAG_TRACE_SUCCESS_BIT = 3;

    /** Маркер сырой точки Theta*-зонда, которую ещё не нужно считать конечной. */
    int FLAG_RAW_WAYPOINT_BIT = 4;

    /**
     * Биты 5..7 хранят DTRN type id: 0 означает обычный waypoint,
     * значения 1..4 — направленный переход. Motion однозначно задаётся типом.
     */
    int PATH_TRANSITION_TYPE_SHIFT = 5;
    int PATH_TRANSITION_TYPE_MASK = 0x7;



    /**
     * Маска координатной части packed long (55 младших бит, без флагов).
     */
    long COORD_MASK = (1L << FLAGS_SHIFT) - 1L;

    // ========================= Упаковка =========================

    static long packXYZ(float x, float y, float z) {
        return packXYZ((int) x, (int) y, (int) z);
    }

    static long packXYZ(int x, int y, int z) throws IllegalArgumentException {
        if (x < -X_OFFSET || x > X_OFFSET - 1) {
            throw new IllegalArgumentException("X coordinate out of range: " + x);
        }
        if (y < -Y_OFFSET || y > Y_OFFSET - 1) {
            throw new IllegalArgumentException("Y coordinate out of range: " + y);
        }
        if (z < -Z_OFFSET || z > Z_OFFSET - 1) {
            throw new IllegalArgumentException("Z coordinate out of range: " + z);
        }
        long xyz = 0;
        xyz |= (z + Z_OFFSET) & Z_MASK;
        xyz |= ((y + Y_OFFSET) & Y_MASK) << Y_SHIFT;
        xyz |= ((x + X_OFFSET) & X_MASK) << X_SHIFT;
        return xyz;
    }

    // ========================= Перепаковка отдельных координат =========================

    static long packX(long xyz, int x) {
        if (x < -X_OFFSET || x > X_OFFSET - 1) {
            throw new IllegalArgumentException("X coordinate out of range: " + x);
        }
        return (xyz & ~(X_MASK << X_SHIFT)) | (((x + X_OFFSET) & X_MASK) << X_SHIFT);
    }

    static long packY(long xyz, int y) {
        if (y < -Y_OFFSET || y > Y_OFFSET - 1) {
            throw new IllegalArgumentException("Y coordinate out of range: " + y);
        }
        return (xyz & ~(Y_MASK << Y_SHIFT)) | (((y + Y_OFFSET) & Y_MASK) << Y_SHIFT);
    }

    static long packZ(long xyz, int z) {
        if (z < -Z_OFFSET || z > Z_OFFSET - 1) {
            throw new IllegalArgumentException("Z coordinate out of range: " + z);
        }
        return (xyz & ~Z_MASK) | ((z + Z_OFFSET) & Z_MASK);
    }

    // ========================= Распаковка =========================

    static int unpackX(long xyz) {
        return (int) (((xyz >> X_SHIFT) & X_MASK) - X_OFFSET);
    }

    static int unpackY(long xyz) {
        return (int) (((xyz >> Y_SHIFT) & Y_MASK) - Y_OFFSET);
    }

    static int unpackZ(long xyz) {
        return (int) ((xyz & Z_MASK) - Z_OFFSET);
    }

    // ========================= Быстрая замена без валидации =========================

    static long changeX(long xyz, int x) {
        return (xyz & ~X_MASK) | ((x + X_OFFSET) & X_MASK);
    }

    static long changeY(long xyz, int y) {
        return (xyz & ~(Y_MASK << Y_SHIFT)) | (((y + Y_OFFSET) & Y_MASK) << Y_SHIFT);
    }

    static long changeZ(long xyz, int z) {
        return (xyz & ~Z_MASK) | ((z + Z_OFFSET) & Z_MASK);
    }

    static long changeXY(long xyz, int x, int y) {
        xyz = changeX(xyz, x);
        xyz = changeY(xyz, y);
        return xyz;
    }

    /**
     * Сравнивает координатную часть двух packed long, игнорируя флаги (биты 55-62).
     */
    static boolean coordsEqual(long a, long b) {
        return (a & COORD_MASK) == (b & COORD_MASK);
    }

    // ========================= Флаги =========================

    static int getFlags(long xyz) {
        return (int) ((xyz >>> FLAGS_SHIFT) & FLAGS_MASK);
    }

    static long setFlags(long xyz, int flags) {
        return (xyz & ~(FLAGS_MASK << FLAGS_SHIFT)) | (((long) flags & FLAGS_MASK) << FLAGS_SHIFT);
    }

    static boolean getFlag(long xyz, int bit) {
        int flags = getFlags(xyz);
        return (flags & (1 << bit)) != 0;
    }

    static long setFlag(long xyz, int bit, boolean value) {
        int flags = getFlags(xyz);
        if (value) {
            flags |= (1 << bit);
        } else {
            flags &= ~(1 << bit);
        }
        return setFlags(xyz, flags);
    }

    static boolean isTraceSuccess(long xyz) {
        return getFlag(xyz, FLAG_TRACE_SUCCESS_BIT);
    }

    static long setTraceSuccess(long xyz, boolean success) {
        return setFlag(xyz, FLAG_TRACE_SUCCESS_BIT, success);
    }

    // ========================= Расстояния =========================

    static double distance2DSQ(double x1, double y1, double x2, double y2) {
        return Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2);
    }

    static double distance3DSQ(double x1, double y1, double z1, double x2, double y2, double z2) {
        return Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2) + Math.pow(z1 - z2, 2);
    }

    static double distance2D(double x1, double y1, double x2, double y2) {
        return Math.sqrt(distance2DSQ(x1, y1, x2, y2));
    }

    static double distance3D(double x1, double y1, double z1, double x2, double y2, double z2) {
        return Math.sqrt(distance3DSQ(x1, y1, z1, x2, y2, z2));
    }

    static double distance2D(long axyz, long bxyz) {
        return distance2D(unpackX(axyz), unpackY(axyz), unpackX(bxyz), unpackY(bxyz));
    }

    static double distance3D(long axyz, long bxyz) {
        return distance3D(unpackX(axyz), unpackY(axyz), unpackZ(axyz), unpackX(bxyz), unpackY(bxyz), unpackZ(bxyz));
    }

    static double distance2DSQ(long axyz, long bxyz) {
        return distance2DSQ(unpackX(axyz), unpackY(axyz), unpackX(bxyz), unpackY(bxyz));
    }

    static double distance3DSQ(long axyz, long bxyz) {
        return distance3DSQ(unpackX(axyz), unpackY(axyz), unpackZ(axyz), unpackX(bxyz), unpackY(bxyz), unpackZ(bxyz));
    }

    static boolean isInRange2D(long axyz, long bxyz, double range2D) {
        return distance2DSQ(axyz, bxyz) <= Math.pow(range2D, 2);
    }

    static boolean isInRange3D(long axyz, long bxyz, double range3D) {
        return distance3DSQ(axyz, bxyz) <= Math.pow(range3D, 2);
    }

    // ========================= Валидация =========================

    static boolean isEmptyPoint(int x, int y, int map_min_x, int map_max_x, int map_min_y, int map_max_y) {
        return x <= map_min_x || y <= map_min_y || x >= map_max_x || y >= map_max_y;
    }

    // ========================= Строковое представление =========================

    static String toString(long xyz) {
        return String.format("[%d, %d, %d]", IXYZ.unpackX(xyz), IXYZ.unpackY(xyz), IXYZ.unpackZ(xyz));
    }

    static String toXYString(long xyz) {
        return unpackX(xyz) + " " + unpackY(xyz);
    }

    static String toXYZString(long xyz) {
        return unpackX(xyz) + " " + unpackY(xyz) + " " + unpackZ(xyz);
    }

    static String toXYZHString(long xyz) {
        return toXYZString(xyz);
    }
    static boolean isRawWaypoint(long xyz) {
        return getFlag(xyz, FLAG_RAW_WAYPOINT_BIT);
    }

    static long setRawWaypoint(long xyz, boolean rawWaypoint) {
        return setFlag(xyz, FLAG_RAW_WAYPOINT_BIT, rawWaypoint);
    }

    static int getPathTransitionTypeId(long xyz) {
        return (getFlags(xyz) >>> PATH_TRANSITION_TYPE_SHIFT) & PATH_TRANSITION_TYPE_MASK;
    }

    static boolean hasPathTransition(long xyz) {
        return getPathTransitionTypeId(xyz) != 0;
    }

    static long setPathTransitionTypeId(long xyz, int typeId) {
        if (typeId < 0 || typeId > PATH_TRANSITION_TYPE_MASK) {
            throw new IllegalArgumentException("Path transition type id is outside 3-bit range: " + typeId);
        }
        int flags = getFlags(xyz);
        flags = (flags & ~(PATH_TRANSITION_TYPE_MASK << PATH_TRANSITION_TYPE_SHIFT))
                | (typeId << PATH_TRANSITION_TYPE_SHIFT);
        return setFlags(xyz, flags);
    }

}


Можно) Только никому не показывай)
 
Java:
package ru.nts;

/**
 * Интерфейс для упаковки трехмерных координат в единое long значение.
 *
 * <p><b>Архитектурное решение:</b><br>
 * Упаковка координат XYZ в один long (64 бита) позволяет значительно сократить потребление памяти
 * и повысить производительность за счет cache locality. Вместо хранения трех int (12 байт)
 * или трех float (12 байт) используется один long (8 байт) - экономия 33%.
 *
 * <p><b>Битовая структура long (55 бит координаты + 8 бит флаги):</b>
 * <pre>
 * | Бит 63 | Биты 62-55 (8 бит) | Биты 54-35 (20 бит) | Биты 34-15 (20 бит) | Биты 14-0 (15 бит) |
 * | резерв |      флаги         |         X           |         Y           |         Z          |
 * </pre>
 *
 * <p><b>Диапазоны координат (знаковые):</b>
 * <ul>
 *   <li><b>X:</b> от -524,288 до 524,287 (20 бит, 2^19)</li>
 *   <li><b>Y:</b> от -524,288 до 524,287 (20 бит, 2^19)</li>
 *   <li><b>Z:</b> от -16,384 до 16,383 (15 бит, 2^14)</li>
 * </ul>
 *
 * <p><b>Флаги (8 бит, биты 62-55):</b>
 * <ul>
 *   <li><b>Бит 55:</b> PFR_CAN_GO</li>
 *   <li><b>Бит 56:</b> PFR_PATH_FOUND</li>
 *   <li><b>Бит 57:</b> PFR_ERROR</li>
 *   <li><b>Бит 58:</b> TRACE_SUCCESS (1 - проход успешен, 0 - препятствие)</li>
 *   <li><b>Биты 59-62:</b> резервные флаги для будущего использования</li>
 * </ul>
 *
 * <p><b>Thread Safety:</b> Все методы thread-safe, так как работают с immutable long значениями.
 *
 * @author Aristo
 * @since 21.11.2025
 */
public interface IXYZ {

    /**
     * Размер региона в мировых координатах Lineage 2.
     */
    float REGION_SIZE = 32768.0f;

    /**
     * Смещение по X для вычисления региона из мировых координат.
     */
    int X_REGION_OFFSET = 20;

    /**
     * Смещение по Y для вычисления региона из мировых координат.
     */
    int Y_REGION_OFFSET = 18;

    /**
     * Минимальная Z координата мира.
     */
    float Z_MIN = -16384.0f;

    /**
     * Максимальная Z координата мира.
     */
    float Z_MAX = 16383.0f;

    /**
     * Размер геоячейки в единицах мира Lineage 2.
     */
    float CELL_SIZE = 16.0f;

    // --- Конфигурация бит ---

    int X_BITS = 20;
    int Y_BITS = 20;
    int Z_BITS = 15;
    int FLAGS_BITS = 8;

    long X_MASK = 0xFFFFF;
    long Y_MASK = 0xFFFFF;
    long Z_MASK = 0x7FFF;
    long FLAGS_MASK = 0xFF;

    long X_OFFSET = 1 << (X_BITS - 1);
    long Y_OFFSET = 1 << (Y_BITS - 1);
    long Z_OFFSET = 1 << (Z_BITS - 1);

    int X_SHIFT = Y_BITS + Z_BITS;
    int Y_SHIFT = Z_BITS;
    int Z_SHIFT = 0;
    int FLAGS_SHIFT = X_BITS + Y_BITS + Z_BITS;

    // --- Флаг-биты ---

    int FLAG_PFR_CAN_GO = 0;
    int FLAG_PFR_PATH_FOUND = 1;
    int FLAG_PFR_ERROR = 2;
    int FLAG_TRACE_SUCCESS_BIT = 3;

    /** Маркер сырой точки Theta*-зонда, которую ещё не нужно считать конечной. */
    int FLAG_RAW_WAYPOINT_BIT = 4;

    /**
     * Биты 5..7 хранят DTRN type id: 0 означает обычный waypoint,
     * значения 1..4 — направленный переход. Motion однозначно задаётся типом.
     */
    int PATH_TRANSITION_TYPE_SHIFT = 5;
    int PATH_TRANSITION_TYPE_MASK = 0x7;



    /**
     * Маска координатной части packed long (55 младших бит, без флагов).
     */
    long COORD_MASK = (1L << FLAGS_SHIFT) - 1L;

    // ========================= Упаковка =========================

    static long packXYZ(float x, float y, float z) {
        return packXYZ((int) x, (int) y, (int) z);
    }

    static long packXYZ(int x, int y, int z) throws IllegalArgumentException {
        if (x < -X_OFFSET || x > X_OFFSET - 1) {
            throw new IllegalArgumentException("X coordinate out of range: " + x);
        }
        if (y < -Y_OFFSET || y > Y_OFFSET - 1) {
            throw new IllegalArgumentException("Y coordinate out of range: " + y);
        }
        if (z < -Z_OFFSET || z > Z_OFFSET - 1) {
            throw new IllegalArgumentException("Z coordinate out of range: " + z);
        }
        long xyz = 0;
        xyz |= (z + Z_OFFSET) & Z_MASK;
        xyz |= ((y + Y_OFFSET) & Y_MASK) << Y_SHIFT;
        xyz |= ((x + X_OFFSET) & X_MASK) << X_SHIFT;
        return xyz;
    }

    // ========================= Перепаковка отдельных координат =========================

    static long packX(long xyz, int x) {
        if (x < -X_OFFSET || x > X_OFFSET - 1) {
            throw new IllegalArgumentException("X coordinate out of range: " + x);
        }
        return (xyz & ~(X_MASK << X_SHIFT)) | (((x + X_OFFSET) & X_MASK) << X_SHIFT);
    }

    static long packY(long xyz, int y) {
        if (y < -Y_OFFSET || y > Y_OFFSET - 1) {
            throw new IllegalArgumentException("Y coordinate out of range: " + y);
        }
        return (xyz & ~(Y_MASK << Y_SHIFT)) | (((y + Y_OFFSET) & Y_MASK) << Y_SHIFT);
    }

    static long packZ(long xyz, int z) {
        if (z < -Z_OFFSET || z > Z_OFFSET - 1) {
            throw new IllegalArgumentException("Z coordinate out of range: " + z);
        }
        return (xyz & ~Z_MASK) | ((z + Z_OFFSET) & Z_MASK);
    }

    // ========================= Распаковка =========================

    static int unpackX(long xyz) {
        return (int) (((xyz >> X_SHIFT) & X_MASK) - X_OFFSET);
    }

    static int unpackY(long xyz) {
        return (int) (((xyz >> Y_SHIFT) & Y_MASK) - Y_OFFSET);
    }

    static int unpackZ(long xyz) {
        return (int) ((xyz & Z_MASK) - Z_OFFSET);
    }

    // ========================= Быстрая замена без валидации =========================

    static long changeX(long xyz, int x) {
        return (xyz & ~X_MASK) | ((x + X_OFFSET) & X_MASK);
    }

    static long changeY(long xyz, int y) {
        return (xyz & ~(Y_MASK << Y_SHIFT)) | (((y + Y_OFFSET) & Y_MASK) << Y_SHIFT);
    }

    static long changeZ(long xyz, int z) {
        return (xyz & ~Z_MASK) | ((z + Z_OFFSET) & Z_MASK);
    }

    static long changeXY(long xyz, int x, int y) {
        xyz = changeX(xyz, x);
        xyz = changeY(xyz, y);
        return xyz;
    }

    /**
     * Сравнивает координатную часть двух packed long, игнорируя флаги (биты 55-62).
     */
    static boolean coordsEqual(long a, long b) {
        return (a & COORD_MASK) == (b & COORD_MASK);
    }

    // ========================= Флаги =========================

    static int getFlags(long xyz) {
        return (int) ((xyz >>> FLAGS_SHIFT) & FLAGS_MASK);
    }

    static long setFlags(long xyz, int flags) {
        return (xyz & ~(FLAGS_MASK << FLAGS_SHIFT)) | (((long) flags & FLAGS_MASK) << FLAGS_SHIFT);
    }

    static boolean getFlag(long xyz, int bit) {
        int flags = getFlags(xyz);
        return (flags & (1 << bit)) != 0;
    }

    static long setFlag(long xyz, int bit, boolean value) {
        int flags = getFlags(xyz);
        if (value) {
            flags |= (1 << bit);
        } else {
            flags &= ~(1 << bit);
        }
        return setFlags(xyz, flags);
    }

    static boolean isTraceSuccess(long xyz) {
        return getFlag(xyz, FLAG_TRACE_SUCCESS_BIT);
    }

    static long setTraceSuccess(long xyz, boolean success) {
        return setFlag(xyz, FLAG_TRACE_SUCCESS_BIT, success);
    }

    // ========================= Расстояния =========================

    static double distance2DSQ(double x1, double y1, double x2, double y2) {
        return Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2);
    }

    static double distance3DSQ(double x1, double y1, double z1, double x2, double y2, double z2) {
        return Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2) + Math.pow(z1 - z2, 2);
    }

    static double distance2D(double x1, double y1, double x2, double y2) {
        return Math.sqrt(distance2DSQ(x1, y1, x2, y2));
    }

    static double distance3D(double x1, double y1, double z1, double x2, double y2, double z2) {
        return Math.sqrt(distance3DSQ(x1, y1, z1, x2, y2, z2));
    }

    static double distance2D(long axyz, long bxyz) {
        return distance2D(unpackX(axyz), unpackY(axyz), unpackX(bxyz), unpackY(bxyz));
    }

    static double distance3D(long axyz, long bxyz) {
        return distance3D(unpackX(axyz), unpackY(axyz), unpackZ(axyz), unpackX(bxyz), unpackY(bxyz), unpackZ(bxyz));
    }

    static double distance2DSQ(long axyz, long bxyz) {
        return distance2DSQ(unpackX(axyz), unpackY(axyz), unpackX(bxyz), unpackY(bxyz));
    }

    static double distance3DSQ(long axyz, long bxyz) {
        return distance3DSQ(unpackX(axyz), unpackY(axyz), unpackZ(axyz), unpackX(bxyz), unpackY(bxyz), unpackZ(bxyz));
    }

    static boolean isInRange2D(long axyz, long bxyz, double range2D) {
        return distance2DSQ(axyz, bxyz) <= Math.pow(range2D, 2);
    }

    static boolean isInRange3D(long axyz, long bxyz, double range3D) {
        return distance3DSQ(axyz, bxyz) <= Math.pow(range3D, 2);
    }

    // ========================= Валидация =========================

    static boolean isEmptyPoint(int x, int y, int map_min_x, int map_max_x, int map_min_y, int map_max_y) {
        return x <= map_min_x || y <= map_min_y || x >= map_max_x || y >= map_max_y;
    }

    // ========================= Строковое представление =========================

    static String toString(long xyz) {
        return String.format("[%d, %d, %d]", IXYZ.unpackX(xyz), IXYZ.unpackY(xyz), IXYZ.unpackZ(xyz));
    }

    static String toXYString(long xyz) {
        return unpackX(xyz) + " " + unpackY(xyz);
    }

    static String toXYZString(long xyz) {
        return unpackX(xyz) + " " + unpackY(xyz) + " " + unpackZ(xyz);
    }

    static String toXYZHString(long xyz) {
        return toXYZString(xyz);
    }
    static boolean isRawWaypoint(long xyz) {
        return getFlag(xyz, FLAG_RAW_WAYPOINT_BIT);
    }

    static long setRawWaypoint(long xyz, boolean rawWaypoint) {
        return setFlag(xyz, FLAG_RAW_WAYPOINT_BIT, rawWaypoint);
    }

    static int getPathTransitionTypeId(long xyz) {
        return (getFlags(xyz) >>> PATH_TRANSITION_TYPE_SHIFT) & PATH_TRANSITION_TYPE_MASK;
    }

    static boolean hasPathTransition(long xyz) {
        return getPathTransitionTypeId(xyz) != 0;
    }

    static long setPathTransitionTypeId(long xyz, int typeId) {
        if (typeId < 0 || typeId > PATH_TRANSITION_TYPE_MASK) {
            throw new IllegalArgumentException("Path transition type id is outside 3-bit range: " + typeId);
        }
        int flags = getFlags(xyz);
        flags = (flags & ~(PATH_TRANSITION_TYPE_MASK << PATH_TRANSITION_TYPE_SHIFT))
                | (typeId << PATH_TRANSITION_TYPE_SHIFT);
        return setFlags(xyz, flags);
    }

}


Можно) Только никому не показывай)
договорились)
 
Да , щас жду ядро от С4 там топовые боты, скинут гляну , перепешу под ХФ посмотрим что выйдет, у Arista они просто тупорылые выполняют команды, но они не могут сами пойти скрафтить купить выбить заточить мозгов нету только команды, а мои это полноценный мир ИИ игроков у каждого есть выбор что ему делать крафт пвп фарм споил рейд рб эпики осады точка баф пати кланы и т.д у меня фишка типо % на выбор в зависимости от условия, допустим 6й лвл поднял ага нужно бафнуть , прожимает сое либо бижит в город бафать у нпс, дальше вообще будет ппц)) это только моё начало путешествия ))) Просто пилю ХФ чисто с ботами для себя , не куда больше нету смысла серв запусть не кто не играет л2, только деды и лудоманы , в стиме есть на много лучше и интересней игры )))
Сообщение объединено:
:loltt0:
1788860863397.webp
 
Java:
package ru.nts;

/**
 * Интерфейс для упаковки трехмерных координат в единое long значение.
 *
 * <p><b>Архитектурное решение:</b><br>
 * Упаковка координат XYZ в один long (64 бита) позволяет значительно сократить потребление памяти
 * и повысить производительность за счет cache locality. Вместо хранения трех int (12 байт)
 * или трех float (12 байт) используется один long (8 байт) - экономия 33%.
 *
 * <p><b>Битовая структура long (55 бит координаты + 8 бит флаги):</b>
 * <pre>
 * | Бит 63 | Биты 62-55 (8 бит) | Биты 54-35 (20 бит) | Биты 34-15 (20 бит) | Биты 14-0 (15 бит) |
 * | резерв |      флаги         |         X           |         Y           |         Z          |
 * </pre>
 *
 * <p><b>Диапазоны координат (знаковые):</b>
 * <ul>
 *   <li><b>X:</b> от -524,288 до 524,287 (20 бит, 2^19)</li>
 *   <li><b>Y:</b> от -524,288 до 524,287 (20 бит, 2^19)</li>
 *   <li><b>Z:</b> от -16,384 до 16,383 (15 бит, 2^14)</li>
 * </ul>
 *
 * <p><b>Флаги (8 бит, биты 62-55):</b>
 * <ul>
 *   <li><b>Бит 55:</b> PFR_CAN_GO</li>
 *   <li><b>Бит 56:</b> PFR_PATH_FOUND</li>
 *   <li><b>Бит 57:</b> PFR_ERROR</li>
 *   <li><b>Бит 58:</b> TRACE_SUCCESS (1 - проход успешен, 0 - препятствие)</li>
 *   <li><b>Биты 59-62:</b> резервные флаги для будущего использования</li>
 * </ul>
 *
 * <p><b>Thread Safety:</b> Все методы thread-safe, так как работают с immutable long значениями.
 *
 * @author Aristo
 * @since 21.11.2025
 */
public interface IXYZ {

    /**
     * Размер региона в мировых координатах Lineage 2.
     */
    float REGION_SIZE = 32768.0f;

    /**
     * Смещение по X для вычисления региона из мировых координат.
     */
    int X_REGION_OFFSET = 20;

    /**
     * Смещение по Y для вычисления региона из мировых координат.
     */
    int Y_REGION_OFFSET = 18;

    /**
     * Минимальная Z координата мира.
     */
    float Z_MIN = -16384.0f;

    /**
     * Максимальная Z координата мира.
     */
    float Z_MAX = 16383.0f;

    /**
     * Размер геоячейки в единицах мира Lineage 2.
     */
    float CELL_SIZE = 16.0f;

    // --- Конфигурация бит ---

    int X_BITS = 20;
    int Y_BITS = 20;
    int Z_BITS = 15;
    int FLAGS_BITS = 8;

    long X_MASK = 0xFFFFF;
    long Y_MASK = 0xFFFFF;
    long Z_MASK = 0x7FFF;
    long FLAGS_MASK = 0xFF;

    long X_OFFSET = 1 << (X_BITS - 1);
    long Y_OFFSET = 1 << (Y_BITS - 1);
    long Z_OFFSET = 1 << (Z_BITS - 1);

    int X_SHIFT = Y_BITS + Z_BITS;
    int Y_SHIFT = Z_BITS;
    int Z_SHIFT = 0;
    int FLAGS_SHIFT = X_BITS + Y_BITS + Z_BITS;

    // --- Флаг-биты ---

    int FLAG_PFR_CAN_GO = 0;
    int FLAG_PFR_PATH_FOUND = 1;
    int FLAG_PFR_ERROR = 2;
    int FLAG_TRACE_SUCCESS_BIT = 3;

    /** Маркер сырой точки Theta*-зонда, которую ещё не нужно считать конечной. */
    int FLAG_RAW_WAYPOINT_BIT = 4;

    /**
     * Биты 5..7 хранят DTRN type id: 0 означает обычный waypoint,
     * значения 1..4 — направленный переход. Motion однозначно задаётся типом.
     */
    int PATH_TRANSITION_TYPE_SHIFT = 5;
    int PATH_TRANSITION_TYPE_MASK = 0x7;



    /**
     * Маска координатной части packed long (55 младших бит, без флагов).
     */
    long COORD_MASK = (1L << FLAGS_SHIFT) - 1L;

    // ========================= Упаковка =========================

    static long packXYZ(float x, float y, float z) {
        return packXYZ((int) x, (int) y, (int) z);
    }

    static long packXYZ(int x, int y, int z) throws IllegalArgumentException {
        if (x < -X_OFFSET || x > X_OFFSET - 1) {
            throw new IllegalArgumentException("X coordinate out of range: " + x);
        }
        if (y < -Y_OFFSET || y > Y_OFFSET - 1) {
            throw new IllegalArgumentException("Y coordinate out of range: " + y);
        }
        if (z < -Z_OFFSET || z > Z_OFFSET - 1) {
            throw new IllegalArgumentException("Z coordinate out of range: " + z);
        }
        long xyz = 0;
        xyz |= (z + Z_OFFSET) & Z_MASK;
        xyz |= ((y + Y_OFFSET) & Y_MASK) << Y_SHIFT;
        xyz |= ((x + X_OFFSET) & X_MASK) << X_SHIFT;
        return xyz;
    }

    // ========================= Перепаковка отдельных координат =========================

    static long packX(long xyz, int x) {
        if (x < -X_OFFSET || x > X_OFFSET - 1) {
            throw new IllegalArgumentException("X coordinate out of range: " + x);
        }
        return (xyz & ~(X_MASK << X_SHIFT)) | (((x + X_OFFSET) & X_MASK) << X_SHIFT);
    }

    static long packY(long xyz, int y) {
        if (y < -Y_OFFSET || y > Y_OFFSET - 1) {
            throw new IllegalArgumentException("Y coordinate out of range: " + y);
        }
        return (xyz & ~(Y_MASK << Y_SHIFT)) | (((y + Y_OFFSET) & Y_MASK) << Y_SHIFT);
    }

    static long packZ(long xyz, int z) {
        if (z < -Z_OFFSET || z > Z_OFFSET - 1) {
            throw new IllegalArgumentException("Z coordinate out of range: " + z);
        }
        return (xyz & ~Z_MASK) | ((z + Z_OFFSET) & Z_MASK);
    }

    // ========================= Распаковка =========================

    static int unpackX(long xyz) {
        return (int) (((xyz >> X_SHIFT) & X_MASK) - X_OFFSET);
    }

    static int unpackY(long xyz) {
        return (int) (((xyz >> Y_SHIFT) & Y_MASK) - Y_OFFSET);
    }

    static int unpackZ(long xyz) {
        return (int) ((xyz & Z_MASK) - Z_OFFSET);
    }

    // ========================= Быстрая замена без валидации =========================

    static long changeX(long xyz, int x) {
        return (xyz & ~X_MASK) | ((x + X_OFFSET) & X_MASK);
    }

    static long changeY(long xyz, int y) {
        return (xyz & ~(Y_MASK << Y_SHIFT)) | (((y + Y_OFFSET) & Y_MASK) << Y_SHIFT);
    }

    static long changeZ(long xyz, int z) {
        return (xyz & ~Z_MASK) | ((z + Z_OFFSET) & Z_MASK);
    }

    static long changeXY(long xyz, int x, int y) {
        xyz = changeX(xyz, x);
        xyz = changeY(xyz, y);
        return xyz;
    }

    /**
     * Сравнивает координатную часть двух packed long, игнорируя флаги (биты 55-62).
     */
    static boolean coordsEqual(long a, long b) {
        return (a & COORD_MASK) == (b & COORD_MASK);
    }

    // ========================= Флаги =========================

    static int getFlags(long xyz) {
        return (int) ((xyz >>> FLAGS_SHIFT) & FLAGS_MASK);
    }

    static long setFlags(long xyz, int flags) {
        return (xyz & ~(FLAGS_MASK << FLAGS_SHIFT)) | (((long) flags & FLAGS_MASK) << FLAGS_SHIFT);
    }

    static boolean getFlag(long xyz, int bit) {
        int flags = getFlags(xyz);
        return (flags & (1 << bit)) != 0;
    }

    static long setFlag(long xyz, int bit, boolean value) {
        int flags = getFlags(xyz);
        if (value) {
            flags |= (1 << bit);
        } else {
            flags &= ~(1 << bit);
        }
        return setFlags(xyz, flags);
    }

    static boolean isTraceSuccess(long xyz) {
        return getFlag(xyz, FLAG_TRACE_SUCCESS_BIT);
    }

    static long setTraceSuccess(long xyz, boolean success) {
        return setFlag(xyz, FLAG_TRACE_SUCCESS_BIT, success);
    }

    // ========================= Расстояния =========================

    static double distance2DSQ(double x1, double y1, double x2, double y2) {
        return Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2);
    }

    static double distance3DSQ(double x1, double y1, double z1, double x2, double y2, double z2) {
        return Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2) + Math.pow(z1 - z2, 2);
    }

    static double distance2D(double x1, double y1, double x2, double y2) {
        return Math.sqrt(distance2DSQ(x1, y1, x2, y2));
    }

    static double distance3D(double x1, double y1, double z1, double x2, double y2, double z2) {
        return Math.sqrt(distance3DSQ(x1, y1, z1, x2, y2, z2));
    }

    static double distance2D(long axyz, long bxyz) {
        return distance2D(unpackX(axyz), unpackY(axyz), unpackX(bxyz), unpackY(bxyz));
    }

    static double distance3D(long axyz, long bxyz) {
        return distance3D(unpackX(axyz), unpackY(axyz), unpackZ(axyz), unpackX(bxyz), unpackY(bxyz), unpackZ(bxyz));
    }

    static double distance2DSQ(long axyz, long bxyz) {
        return distance2DSQ(unpackX(axyz), unpackY(axyz), unpackX(bxyz), unpackY(bxyz));
    }

    static double distance3DSQ(long axyz, long bxyz) {
        return distance3DSQ(unpackX(axyz), unpackY(axyz), unpackZ(axyz), unpackX(bxyz), unpackY(bxyz), unpackZ(bxyz));
    }

    static boolean isInRange2D(long axyz, long bxyz, double range2D) {
        return distance2DSQ(axyz, bxyz) <= Math.pow(range2D, 2);
    }

    static boolean isInRange3D(long axyz, long bxyz, double range3D) {
        return distance3DSQ(axyz, bxyz) <= Math.pow(range3D, 2);
    }

    // ========================= Валидация =========================

    static boolean isEmptyPoint(int x, int y, int map_min_x, int map_max_x, int map_min_y, int map_max_y) {
        return x <= map_min_x || y <= map_min_y || x >= map_max_x || y >= map_max_y;
    }

    // ========================= Строковое представление =========================

    static String toString(long xyz) {
        return String.format("[%d, %d, %d]", IXYZ.unpackX(xyz), IXYZ.unpackY(xyz), IXYZ.unpackZ(xyz));
    }

    static String toXYString(long xyz) {
        return unpackX(xyz) + " " + unpackY(xyz);
    }

    static String toXYZString(long xyz) {
        return unpackX(xyz) + " " + unpackY(xyz) + " " + unpackZ(xyz);
    }

    static String toXYZHString(long xyz) {
        return toXYZString(xyz);
    }
    static boolean isRawWaypoint(long xyz) {
        return getFlag(xyz, FLAG_RAW_WAYPOINT_BIT);
    }

    static long setRawWaypoint(long xyz, boolean rawWaypoint) {
        return setFlag(xyz, FLAG_RAW_WAYPOINT_BIT, rawWaypoint);
    }

    static int getPathTransitionTypeId(long xyz) {
        return (getFlags(xyz) >>> PATH_TRANSITION_TYPE_SHIFT) & PATH_TRANSITION_TYPE_MASK;
    }

    static boolean hasPathTransition(long xyz) {
        return getPathTransitionTypeId(xyz) != 0;
    }

    static long setPathTransitionTypeId(long xyz, int typeId) {
        if (typeId < 0 || typeId > PATH_TRANSITION_TYPE_MASK) {
            throw new IllegalArgumentException("Path transition type id is outside 3-bit range: " + typeId);
        }
        int flags = getFlags(xyz);
        flags = (flags & ~(PATH_TRANSITION_TYPE_MASK << PATH_TRANSITION_TYPE_SHIFT))
                | (typeId << PATH_TRANSITION_TYPE_SHIFT);
        return setFlags(xyz, flags);
    }

}


Можно) Только никому не показывай)
А можно ещё что ни будь интересное главный скрипт поиска правильного передвижения, то есть допустим бот поднял 6й лвл у меня переключается на файл FakePlayerQuestAI.java он смотрит где Нпс выбирает какой ему нужен квест и обратно переключает на FakePlayerMovement.java приходит или портируется в город переключает на CityNavigation.java ) можно ли без сторонних софтов максимально уменьшить нагрузку от Ботов удалить recast4j, и сделать ботов через ядро л2мобис, залейте кто ни будь хоть одну папку логики ботов как они двигаются как двигаются в городе в катакомбах как берут квест можно у кого ни будь логику посмотреть, если есть возможность поделитесь пожалуйста я просто уже месяц не могу выбрать со стартовой локации и добраться до 1й профы макс 6-7лвл я уже рейты обратно вернул х1, а то ждать х0.05 что то долго походу можно даже х2 поставить что бы побыстрее настроить
 
Ну ты ваще ахуел)
 
Назад
Сверху Снизу