X3 逼降算法 附源码

Original post on deeptimes 翻译自https://forum.egosoft.com/viewtopic.php?p=2342693#2342693。 根据原贴,该作者根据某俄罗斯玩家的反编译成果从俄文翻译而来。 感谢Macaulish的提醒,已在该贴基础上根据源码做出诸多修正。LZ不是专业搞编程的,欢迎各位大佬指导。 1.船壳小于87%才可能投降。 2.逼降检查点间隔时间以30s为初值,逐渐翻倍。 如果每到检查点目标都恰好在被玩家攻击(即没有冗余时间),可能逼降成功的时间是0,30,60,120,240,480s...... 当然目标不是时刻都在被攻击的,因此,检查点更有可能是0,35,65,133,253,493s......这种样子 3.每过30s,逼降概率降低1%。 结合第2条,战斗时间达到0,30,60,120,240,480s时,概率已经降低0%,1%, 2%, 4%, 8%, 16%。 这意味着第一次逼降是最有可能成功的 4.在和上次逼降检查点间隔超过300s之后的新攻击行为,会导致逼降检查点重置 300s基本意味着脱离战斗。 5.初始逼降概率=30%-士气值%。 野生的L士气值应该都是25。做任务可能刷出来比较低的。 6.目标船壳剩余 <25%时,投降概率+1% <12%时,投降概率+2% <8%时,投降概率+3% <2%时,投降概率+10% LZ捉Pirate Kestral的时候,目标士气值22。用5门Ion Shard Railgun一击把船壳降到0%,读档3次成功。 7.如果玩家的实力(护盾+船壳+最大武器伤害)<目标实力,投降概率降低5%。 8.如果目标是Khaak,概率值乘以“(玩家战斗等级 - 5) / 25” 这意味着要捉到Khaak的船,玩家战斗等级必须达到6级。否则概率为0。 只有玩家战斗等级达到30级,逼降Khaak船的概率才和逼降普通船一样。 9.概率算法: 如果当前概率>0到99的某一随机数,对方投降。 10.如果使用Ion Disruptor来逼降,概率乘以0.04 LZ很久以前试过。用ID烧Goner Ranger,烧到差不多40%船壳的时候才投降。要知道Goner Ranger的士气值都是很低的。 LZ一共读了大约10次档捉到了L交任务。每次只开一轮炮,不投降读档。 论坛里各位大佬根据经验总结的方法基本都是正确的,事实上我是捉到L以后才看到这个算法的。。 附ULiX反编译所得算法供大家研究。
TSHIP.AttackedBy(Attacker, arg2) {
    //arg2 - судя по контексту тип применяемого оружия

    TSCRIPT.AttackedBy(Attacker, arg2);

    if (Attacker) {
        AttackerEv = SA_GetEventObject(Attacker);
        if (AttackerEv && sh_Owner) {

            if (arg2 == 0 && (sh_Flags & 1)) { // если не использовано орудие
                TCLIENT.NotifyCollisionWarn(0); // угроза столкновения уровня 0
            } //L000BF866


            if ((ga_PlayerShip != AttackerEv) && (arg2) && (SA_GetFlag2(arg2) & 0100h)) {
                //Если атакующий не корабль игрока, но использовано орудие, и орудие
                //способно на захват, то
                //при этом атакующим объектом становится корабль игрока, а атакующим - игрок, причем
                //только если он в активном секторе. Это условие необходимо для проверки орудий кокпитов.
                AttackerEv = ga_PlayerShip;
                Attacker = ga_PlayerShip.GetObjectID(); //функция вернёт нуль, если объект не в активном секторе
            } //L000BF8D4

            AttackerOwner = AttackerEv.GetOwner(); // (loc2) Принадлежность атакующего
            if (SE_IsClass(AttackerEv, 2004) { //Атакующий объект является кораблем

            if (AttackerEv == ga_PlayerShip) || (THIS.IsEnemy(AttackerEv)) {
                    //Атакующий объект корабль игрока или враг
                    //L000BF922 -> L000BF934

                    if (AttackerOwner != sh_Owner) {
                        // Атакующий и атакуемый не принадлежат одному владельцу

                        if !(arg2) || (SA_GetGroup(arg2) == AttackerOwner.GetGroup()) {
                            //для атаки не использовано орудие или ......................
                            //захват возможен только при данном
                            //условии (SA_GetGroup(arg2) == AttackerOwner.GetGroup())
                            //так как по следующему условию обязательно arg2 <> 0
                            //L000BF9A0

                            if (AttackerEv == ga_PlayerShip) && arg2 {
                                //Атакующий объект - карабль игрока и применяет орудие
                                //L000BF9CA:

                                if !(TCLIENT.GetClientState() & 0200h) &&
                                    (sh_Owner <> ga_Player) && (sh_Owner <> ga_Races[18]) { // sh_Owner != Earth
                                    //сброшен 9 бит во флаге статистики игрока,
                                    //атакуемый не принадлежит игроку и землянам

                                    if ((SE_Random(100) < 4) || (SA_GetType(arg2) <> 17) { //Ion Distructor
                                    //Вот интересный момент. Если пользуемся ID то основное
                                    //вычисления вероятности возможно с вероятностью 0.04.
                                    //Если пользоваться не ID, то лишняя случайность отсекается
                                    //Использование ID уменьшает общую вероятность захватить в 25 раз!!!

                                    if (SA_GetShipTypeCockpitBody(sh_SubType) && !(sh_Flags & 0100b)) {
                                            //если есть кабина и разрешен захват данного корабля

                                            Shield = SA_GetShield(sh_ObjectID);
                                            LimShield = SA_GetMaxShield(sh_ObjectID);
                                            Hull = SA_GetHul(sh_ObjectID);
                                            MaxHull = SA_GetMaxHull(sh_ObjectID);
                                            LimShield = LimShield / 100 // LimShield = MaxShield / 100
                                            if (LimShild > 1000) LimShild = 1000;
                                            //Минимальное значение щита, при котором
                                            //будет проверяться вероятность капитулирования
                                            // 1% от максимума, или 1000, если 1% превышает 1000 единиц.

                                            if (Shield <= LimShield) && (Hull > 0) && (Hull < SE_Random(8) * MaxHull / 8) { //Щиты меньше лимитного, а корпус поврежден и лежит в интервале от //нуля до переменного случайного значения MaxHull * i/8, //где i - целое в интервале от 0 до 7 //Максимальная целостность корпуса, при которой возможен захват 87% if (ga_Player.GetAge() > sh_NextTakeoverChanceTime) {
                                                //Время игры превышает время, когда даётся шанс на захват

                                                if ((sh_TakeoverDelayTime > 300) &&
                                                (ga_Player.GetAge() > (sh_NextTakeoverChanceTime + sh_TakeoverDelayTime)) {
                                                //Задержка между шансами захватить больше 300 секунд, и
                                                //время игры больше времени следующего шанса + задержка между шансами

                                                sh_TakeoverDelayTime = 30; //Установить задержку в 30 секунд
                                                // -> L000BFBFC
                                            } //L000BFBD6 -> L000BFBEC
                                            else {
                                                sh_TakeoverDelayTime += sh_TakeoverDelayTime; //иначе, удвоить задержку
                                            }
                                            //L000BFBFC:
                                            sh_NextTakeoverChanceTime = ga_Player.GetAge() + sh_TakeoverDelayTime;
                                                                        //установить время следующего шанса захватить кораблик

                                                                        ProbabilityCapture = 30 - this.GetPilotMorale();
                                                                        //установить начальную вероятность захвата в 30% минус уровень марали атакуемого
                                                                        // т.е. от 20 до 30%

                                            if (AttackerEv.GetStrength() < this.GetStrength()) {
                                                //если сила атакующего меньше силы атакуемого

                                                ProbabilityCapture -= 5;
                                                //уменьшить вероятность прыжка на 5%
                                                //сила находится как сумма Shileld + Hull + MaxLaserDamage
                                            }

                                            if (Hull <> 0) {
                                                //Если корпус не равен нулю

                                                ProbabilityAdd = MaxHull / (Hull * 4);
                                                    if (ProbabilityOnHull > 10) ProbabilityOnHull = 10;
                                                    ProbabilityCapture += ProbabilityAdd;
                                                    //Добавочная вероятность, по побитому корпусу
                                                    //1% при целостности меньше 25%
                                                    //2% - меньше 12%
                                                    //3% - меньше 8%
                                                    //10% - меньше 2%
                                                    // Не стоящий учета параметр, главное, чтоб корпус был поврежден до
                                                    // состояния 87%, когда вообще появляется шанс захватить
                                                } //L000BFC94

                                                ProbabilityCapture -= sh_TakeoverDelayTime / 30;
                                                                      //Уменьшение вероятности в зависимости от интервала ожидания между
                                                                      //шансами захватить
                                                                      //Возможные значения 1%, 2%, 4%, 8%, 16%, для 30, 60, 120, 240, 480 секунд задержки
                                                                      //Что объясняет больший шанс захватить сразу, нежели спустя некоторое время

                                                if (sh_Owner == ga_Races[7]) {
                                                //Раса корабля хаак. Расчет вероятности захвата хааков
                                                //зависит от текущего боевого рейтинга.
                                                //до 6 уровня захват хааков невозможен, и только на 30 уровне
                                                //их можно захватывать наравне с другими кораблями
                                                //Множитель (FightRank - 5) / 25

                                                ProbabilityAdd = GetTitleFromFightRanking(ga_Player.GetFightRanking());
                                                    ProbabilityCapture *= (ProbabilityAdd - 5) / 25;

                                                    if (ProbabilityCapture < 0) ProbabilityCapture = 0; //если получилось меньше нуля, приравнять к нулю }//L000BFD06 // if (ProbabilityCapture > SE_Random(100){
                                                    //Ну и, наконец, если текущая вероятность больше случайного числа от 0 до 99
                                                    //начинаем процесс захвата

                                                    try {
                                                        if (sh_Owner <> ga_Races[7]) && //Если шип не принадлежит расе хааков и
                                                            (sh_Owner <> ga_Races[6]) { //и расе ксенонов,
                                                            this.SendMessageSync(28); //то произнести фразу о сдаче корабля
                                                        } else goto L000BFDBA;

                                                        //К чему этот блок, я так и не понял, всё равно он пропущен, да и не могут эти расы кричать
                                                        //Другого варианта структуры, кроме как безусловный переход, чтобы была
                                                        // возможность не выполнить данный блок, я не нашёл, а он действительно не выполняется.

                                                        if (sh_Owner == ga_Races[7]) || //Если шип не принадлежит расе хааков и
                                                            (sh_Owner == ga_Races[6]) { //и расе ксенонов,
                                                            this.SendMessageSync(28); //то произнести фразу о сдаче корабля
                                                        }
                                                        L000BFDBA:
                                                        ga_Player.IncStatCounter(5080, 1); //Увеличить число в статистике

                                                        this.GetOwner().ChangeNotorietyKill(this, AttackerEv);
                                                        //
                                                        // Здесь мог ошибиться, привожу исходный код.
                                                        //
                                                        // push SP[6] ; loc1 ; // AttackerEv
                                                        // get_object
                                                        // push 2 //число аргументов для ChangeNotorietyKill
                                                        // push 0 //число аргументов для GetOwner
                                                        // get_object
                                                        // call GetOwner ; 00000843 ; // GetOwner()
                                                        // call ChangeNotorietyKill ; //ChangeNotorietyKill(arg1,arg2)
                                                        //
                                                        this.SetRaceLogicControl(1);
                                                        this.LeaveShip(); //покидаем кораблик
                                                    } catch(...) {};
                                                } //L000BFE0C
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        //<L000BFE14>
                        //Далее процесс расчета повреждений,
                        //который меня не сильно интересует
                        //
                        if (arg2) {
                            MaxLaser = AttakerEv.GetMaxLaser() + 10;
                            this.MakeDamage(SE_Random(MaxLaser) + 10, 0);
                            //Оружие наносит повреждения от 10 до 110% своей максимальной мощности

                        } //L000BFE50

                        if (AttackerOwner == ga_Player) && (AttackerEv <> ga_PlayerShip) &&
                            (AttackerEv <> sh_Attacker) {
                            //Атакующий принадлежит игроку, не является кораблем, пилотируемым игроком
                            //и не является предыдущим атакующим

                            if (ga_Player.GetAge() > ga_Player.GetLastNotorietyHitTime() + 60) {
                                //От последнего попадания прошло больше минуты

                                sh_Owner.AddNotoriety(ga_Player, -1);
                                ga_Player.SetLastNotorietyHitTime(ga_Player.GetAge());
                                //За каждую минуту атаки известность у определённой расы будет падать на 1 единицу
                            }
                        } //L000BFE78 -> L000BFE98 -> L000BFEF6

                        ShipAttacker = sh_Attacker; //loc3
                        LastTimeAttacked = sh_LastTime_Attacked; //loc4
                        AutopilotOn = 1; //loc5
                        if (sh_Flags & 1) {
                            if(arg2) {
                                this.SetAttacker(AttackerEv);
                                loc5 = this.IsAutopilotActive();
                            } //L000BFF3E
                            else AutopilotOn = 0; //loc5
                        } //L000BFF4C
                        else this.AttackedByHandler(Attacker, arg2);
                        this.GetAttacker(); //Непонятный вызов процедуры, значение всё равно не сохраняется
                        //
                        //L000BFF60: push 0 //число аргументов для GetAttacker
                        // get_object //this
                        // call GetAttacker ; 00003A50
                        // pop //теряем результат вызова функции
                        // push SP[0] ; loc5
                        //
                        if (AutopilotOn && sh_Attacker) {
                            if ((sh_LastTime_Attacked - LastTimeAttacked) > 1) {
                                loc6 = 0;
                                if (this.IsLanding() && sh_CollisionIgnoreObj) {
                                    if (SA_FindObject(sh_CollisionIgnoreObj)) {
                                        if !(SE_IsClass(sh_Attacker, 2014)) { //Attacker Is Not Gate
                                            if (SA_GetMainType(sh_CollisionIgnoreObj) == 18) loc6 = 3; //SSTYPE_SHIP секторальный шип
                                            else loc6 = 2;
                                        } //L000C0022 -> L000C0056
                                    } //L000BFFFA -> L000C0022 -> L000C0056
                                } //L000BFFD8 -> L000BFFFA -> L000C0022 -> L000C0056
                                //L000C0056:
                                this.Signal_Attacked(sh_Attacker, loc6)
                            }//L000BFFAC -> L000C006E
                        }//L000BFF88 -> L000C006E
                        if !(sh_Flags & 01h) && (sh_Owner == ga_Player) &&
                            !(SE_IsClass(this, 2067)) { //ship is class TFIGHTDRONE
                            TCLIENT.NotifyAttacked(this);
                        }//L000C00B8
                    } //L000BF990 -> L000BF99E -> L000C00BC
                } //L000BF952 -> L000BF99E -> L000C00BC
            } //L000BF924 -> L000BF932 -> L000BF952 -> L000BF99E -> L000C00BC
        } //L000BF932 -> L000BF952 -> L000BF99E -> L000C00BC
    } //L000C00C0
} //L000C00C4
return 0;
}