Solo play - setting up server

Post Reply
ffxi1234
Posts: 1
Joined: Sat Jul 04, 2015 12:37 am

Solo play - setting up server

Post by ffxi1234 » Sat Jul 04, 2015 12:41 am

Hi all,
So I want to take a trip down memory lane and re-live my FFXI experience by myself.

I'd like to be able to have it be a little challenging but be able to do all the content solo on any job.

So if tried messing around with things like mob HP in the /conf folder in the map_darkstar file to lower all mob hp to 0.15. But I cant find anywhere to modify my overall "power" of myself.

It doesn't seem like I'm doing this right. I was 2-hitting stuff in ronfaure but now I went into valkurm and even with my hp at 3.0 and mob hp at 0.15 i am getting my a** handed to me. Honestly it doesn't seem like the mob hp modifier is working. It did in ronfaure but now in valkurm i wasnt even seeing it move as i attack the little bunnies lol.

How can I easily set this up to be able to play solo? Have some fights that are challenging but where I can still win solo without a party?

Are there some simple settings I can tweak? thanks

Ideally I just want to bump up the overall power of my character so I am kind of a full 6 man party on my own. So like 6.0, along with 6.0 cure power, etc. stuff like that. I found the cure modifier but I dont see anything just to make me stronger overall.

uwill99
Posts: 61
Joined: Tue Aug 12, 2014 11:28 pm

Re: Solo play - setting up server

Post by uwill99 » Sat Aug 22, 2015 2:41 am

Hey man! I have been doing the very same thing for the last year or so! First off, what I did to bring an individual character's strength up to about what it would be in a group is to give it every buff you'd find from a fully merited dancer/whm/rdm/bard (plus a little extra stats and defensive or offensive power depending on the item). Now I have two foods I use for level 75 and one food that I use for 1-40. From 40-75 I just use the 75 foods. Of the 75 foods - one is a melee DPS food and another is a tank food... The main differences were stat allocation. I understand that careful balance you have to walk in making things challenging for yourself to keep it fun and not getting TOO overpowered. These foods look insanely overpowered but they have been doing the trick for me as far as keeping things interesting at level 75 and up until it. Here's my tank food (which is probably the one you want):

Code: Select all

-----------------------------------------
-- ID: 5175
-- Item: leremieu_taco
-- Food Effect: 60Min (3 hours), All Races
-----------------------------------------
-- Health 20
-- Magic 20
-- Dexterity 4
-- Agility 4
-- Vitality 6
-- Charisma 4
-- Health Regen While Healing 1
-- Magic Regen While Healing 1
-- Defense % 25
-- Defense Cap 160
-----------------------------------------

require("scripts/globals/status");

-----------------------------------------
-- OnItemCheck
-----------------------------------------

function onItemCheck(target)
local result = 0;
	if (target:hasStatusEffect(EFFECT_FOOD) == true or target:hasStatusEffect(EFFECT_FIELD_SUPPORT_FOOD) == true) then
		result = 246;
	end
return result;
end;

-----------------------------------------
-- OnItemUse
-----------------------------------------

function onItemUse(target)
	target:addStatusEffect(EFFECT_FOOD,0,0,10800,5175);
end;

-----------------------------------
-- onEffectGain Action
-----------------------------------

function onEffectGain(target,effect)
	target:addMod(MOD_HP, 200)
	target:addMod(MOD_MP, 100);
	target:addMod(MOD_DEF, 175);
	target:addMod(MOD_DEFP, 10);
	target:addMod(MOD_MDEF, 72);
	target:addMod(MOD_EVA, 10);
	target:addMod(MOD_ACC, 30);
	target:addMod(MOD_RACCP, 16);
	target:addMod(MOD_ATT, 104);
	target:addMod(MOD_SLEEPRES, 5);
	target:addStatusEffect(EFFECT_HASTE, 448, 0, 10800);
	target:addStatusEffect(EFFECT_PROTECT,195,0,10800);
	target:addStatusEffect(EFFECT_SHELL,70, 0, 10800);
	target:addStatusEffect(EFFECT_REGEN,35,3,10800);
	target:addStatusEffect(EFFECT_REFRESH,5,3,10800);
	target:addStatusEffect(EFFECT_DRAIN_SAMBA,3,0,10800);
	target:addStatusEffect(EFFECT_STR_BOOST_II,30, 0, 10800);
	target:addStatusEffect(EFFECT_DEX_BOOST_II,30, 0, 10800);
	target:addStatusEffect(EFFECT_VIT_BOOST_II,30, 0, 10800);
	target:addStatusEffect(EFFECT_AGI_BOOST_II,30, 0, 10800);
	target:addStatusEffect(EFFECT_INT_BOOST_II,30, 0, 10800);
	target:addStatusEffect(EFFECT_MND_BOOST_II,30, 0, 10800);
	target:addStatusEffect(EFFECT_CHR_BOOST_II,30, 0, 10800);
	target:addMod(MOD_ENMITY, 15);
	target:addMod(MOD_MOVE, 25);
end;

-----------------------------------------
-- onEffectLose Action
-----------------------------------------

function onEffectLose(target,effect)
	target:delMod(MOD_HP, 200)
	target:delMod(MOD_MP, 100);
	target:delMod(MOD_DEF, 175);
	target:delMod(MOD_DEFP, 10);
	target:delMod(MOD_MDEF, 72);
	target:delMod(MOD_EVA, 10);
	target:delMod(MOD_ACC, 30);
	target:delMod(MOD_RACCP, 16);
	target:delMod(MOD_ATT, 104);
	target:delMod(MOD_SLEEPRES, 5);
	target:delMod(MOD_ENMITY, 15);
	target:delMod(MOD_MOVE, 25);
end;
I lowered NM hp to .75 and am still toying around with it. You don't need to increase your HP or decrease mob HP at all - you'll do just fine with this food and just about every stat was compared to what a whm/brd/dnc/rdm could give in a party with a tank or a DPS. I did increase WS damage to 2x, though, which is done in settings.lua. Now with this you can play with more hp, more mana, defensive power, haste, drain samba etc. If you want my leveling one I can include it in another post.

uwill99
Posts: 61
Joined: Tue Aug 12, 2014 11:28 pm

Re: Solo play - setting up server

Post by uwill99 » Sat Aug 22, 2015 2:43 am

Sometimes I two box - which is why the enmity is there.

Druisk
Posts: 7
Joined: Wed Feb 27, 2013 11:42 pm

Re: Solo play - setting up server

Post by Druisk » Fri Aug 28, 2015 8:19 pm

Another option would be to invert and modify the God mode in player.lua. This way, you get buffs everytime you log in and it persists even if you die or change job.

Code: Select all

if (player:getVar("GodMode") == 0) then
player:addStatusEffect(EFFECT_REFRESH,10,0,0);
player:addStatusEffect(EFFECT_BLOOD_WEAPON,15,0,0);
player:addStatusEffect(EFFECT_HASTE,1950,1,0);  
player:addStatusEffect(EFFECT_AQUAVEIL,70,0,0);	
player:addMod(MOD_ACC, 50);
player:addMod(MOD_DEF, 30);
player:addMod(MOD_ACCP, 50);
player:addMod(MOD_DEFP, 30);
player:addMod(MOD_RACCP, 50);
player:addMod(MOD_HPHEAL, 90);
player:addMod(MOD_MPHEAL, 90);
player:addMod(MOD_MACC, 99);
player:addMod(MOD_MEVA, 70);
player:addMod(MOD_MDEF, 80);
player:addMod(MOD_WSACC, 90);
player:addMod(MOD_FASTCAST, 60);
player:addMod(MOD_CRITHITRATE, 5);
-- etc

uwill99
Posts: 61
Joined: Tue Aug 12, 2014 11:28 pm

Re: Solo play - setting up server

Post by uwill99 » Sat Aug 29, 2015 4:54 pm

Oh! That's smart and never thought about modding godmode... Probably because the character I play with isn't GM flagged. I'm weak and would abuse GM status when I got frustrated. This is my leveling food. It'll seem overpowered for the early levels, but it'll level off. It's basically like rolling around in a full party with the best version of each spell up to level 40 - with a tiny bump in stats. It also only lasts 30 minutes. NMs will still be difficult unless you're at least slightly higher level than them with this food. I have my meat jerky set on AH for 2k/per meat jerky to drive the leveling/gil grinding aspect early, and 120k per leremieu taco (so I am interested in farming at high level, still). You could probably use this food till around level 60 and it'd still be a decent speed solo play.

Code: Select all

-----------------------------------------
-- ID: 4376
-- Item: strip_of_meat_jerky
-- Food Effect: 30Min, All Races
-----------------------------------------
-- Strength 3
-- Intelligence -1
-- Attack % 22
-- Attack Cap 30
-----------------------------------------

require("scripts/globals/status");

-----------------------------------------
-- OnItemCheck
-----------------------------------------

function onItemCheck(target)
local result = 0;
	if (target:hasStatusEffect(EFFECT_FOOD) == true or target:hasStatusEffect(EFFECT_FIELD_SUPPORT_FOOD) == true) then
		result = 246;
	end
return result;
end;

-----------------------------------------
-- OnItemUse
-----------------------------------------

function onItemUse(target)
	target:addStatusEffect(EFFECT_FOOD,0,0,1800,4376);
end;

-----------------------------------------
-- onEffectGain Action
-----------------------------------------

function onEffectGain(target,effect)
	target:addMod(MOD_HP, 15);
	target:addMod(MOD_MP, 15);
	target:addMod(MOD_ACC, 15);
	target:addMod(MOD_ACCP, 16);
	target:addMod(MOD_RACCP, 16);
	target:addMod(MOD_ATT, 32);
	target:addMod(MOD_ATTP, 5);
	target:addMod(MOD_RATTP, 15);
	target:addMod(MOD_SLEEPRES, 5);
	target:addStatusEffect(EFFECT_PROTECT,50,0,1800);
	target:addStatusEffect(EFFECT_SHELL,36, 0, 1800);
	target:addStatusEffect(EFFECT_REGEN,8,3,1800);
	target:addStatusEffect(EFFECT_REFRESH,2,3,1800);
	target:addStatusEffect(EFFECT_HASTE, 92, 0, 1800);
	target:addStatusEffect(EFFECT_DRAIN_SAMBA,1,0,1800);
	target:addStatusEffect(EFFECT_STR_BOOST_II,5, 0, 1800);
	target:addStatusEffect(EFFECT_DEX_BOOST_II,5, 0, 1800);
	target:addStatusEffect(EFFECT_VIT_BOOST_II,5, 0, 1800);
	target:addStatusEffect(EFFECT_AGI_BOOST_II,5, 0, 1800);
	target:addStatusEffect(EFFECT_INT_BOOST_II,5, 0, 1800);
	target:addStatusEffect(EFFECT_MND_BOOST_II,5, 0, 1800);
	target:addStatusEffect(EFFECT_CHR_BOOST_II,5, 0, 1800);
	target:addMod(MOD_MOVE, 12);
	target:addMod(MOD_MPHEAL, 5);
	target:addMod(MOD_PLANTOID_KILLER, 5);
	target:addMod(MOD_SLOWRES, 5);
end;

-----------------------------------------
-- onEffectLose Action
-----------------------------------------

function onEffectLose(target,effect)
	target:delMod(MOD_HP, 15);
	target:delMod(MOD_MP, 15);
	target:delMod(MOD_ACC, 15);
	target:delMod(MOD_ACCP, 16);
	target:delMod(MOD_RACCP, 16);
	target:delMod(MOD_ATT, 32);
	target:delMod(MOD_ATTP, 5);
	target:delMod(MOD_RATTP, 15);
	target:delMod(MOD_SLEEPRES, 5);
	target:delMod(MOD_MOVE, 12);
	target:delMod(MOD_MPHEAL, 5);
	target:delMod(MOD_PLANTOID_KILLER, 5);
	target:delMod(MOD_SLOWRES, 5);
end;

Maelwys
Posts: 4
Joined: Tue Nov 18, 2014 6:04 am

Re: Solo play - setting up server

Post by Maelwys » Sat Sep 05, 2015 8:57 pm

You can always modify an item's mods instead by inserting values into the SQL tables. My favourite is the Happy and Fortune Eggs... I've one setup with extra Melee-friendly mods and the other with Mage-friendly ones. Nothing too crazy, but a bit of extra HP/MP and Regen/Refresh and some status effect immunity.

Another option is to tweak a usable item to spawn yourself a pet when you use it (Lucky Lulush or Gorefang Hobs are fun to play with!) I linked such a summon to the Destrier Beret, and I've tweaked a few of the pet AI scripts so that if i'm not playing as a BST or /BST then the pet automatically starts fighting whenever I melee attack a mob (instead of needing a BST's attack command) and automatically triggers a TP move when it can. Makes soloing fun without getting TOO easy!! ;)

Maelwys
Posts: 4
Joined: Tue Nov 18, 2014 6:04 am

Re: Solo play - setting up server

Post by Maelwys » Tue Sep 08, 2015 10:56 am

Maelwys wrote:Another option is to tweak a usable item to spawn yourself a pet when you use it (Lucky Lulush or Gorefang Hobs are fun to play with!) I linked such a summon to the Destrier Beret, and I've tweaked a few of the pet AI scripts so that if i'm not playing as a BST or /BST then the pet automatically starts fighting whenever I melee attack a mob (instead of needing a BST's attack command) and automatically triggers a TP move when it can. Makes soloing fun without getting TOO easy!! ;)
I've more or less finished modifying this to my own satisfaction now, so I'll give you an idea of what I mean:

Mob summon on item use example
(this version summons Lucky Lulush by default, or Gorefang Hobs whenever the Happy Egg is equipped, or Faithful Falcorr whenever the Fortune Egg is equipped)

Code: Select all

-----------------------------------------
-- ID: 11811
-- Item: destrier_beret

require("scripts/globals/common");
require("scripts/globals/status");

-----------------------------------------
-- OnItemCheck
-----------------------------------------

function onItemCheck(target)
    return 0;
end;


-----------------------------------------
-- OnItemUse
-----------------------------------------

function onItemUse(target)
    if (target:getPet() == nil and target:canUsePet()) then
        local id = target:getEquipID(SLOT_AMMO);
        if (id == 18166) then --HAPPY EGG EQUIPPED (+HP)
            target:spawnPet(0x3F); --SUMMON TIGER
        else
            if (id == 18167) then  --FORTUNE EGG EQUIPPED (+MP)
                target:spawnPet(0x40); --SUMMON BIRD
            else --OTHER MISC AMMO ITEM EQUIPPED
                target:spawnPet(0x33); --SUMMON RABBIT
            end
        end

        -- Tiger (Gorefang Hobs; WAR, Heavy Damage) is 0x3F
        -- Bird (Faithful Falcorr; THF, Evasion and +TH) is 0x40
        -- Rabbit (Lucky Lulush; WAR, All Rounder + Healing) is 0x33
        -- Turtle (Crude Ralphie; PLD, Defence) is 0x41
    end
end;
Each of those pets caps out at level 99, so they'll be perfectly fine as a companion from level 1 onwards.
If you want to use them as a non-BST though, you'll need to perform a few other tweaks first...


Step #2: AI_Char_Normal tweaks

Code: Select all

void CAICharNormal::ActionDisengage()
{
    m_ActionType = ACTION_NONE;
    m_LastActionTime = m_Tick;
    m_PBattleTarget = nullptr;
    m_PBattleSubTarget = nullptr;

    m_PChar->animation = ANIMATION_NONE;
    m_PChar->updatemask |= UPDATE_HP;
    m_PChar->pushPacket(new CCharUpdatePacket(m_PChar));
    m_PChar->PLatentEffectContainer->CheckLatentsWeaponDraw(false);

    if (m_PChar->PPet != nullptr && m_PChar->PPet->objtype == TYPE_PET && (m_PChar->GetMJob() != JOB_BST && m_PChar->GetSJob() != JOB_BST))
    {
        m_PChar->PPet->PBattleAI->SetBattleTarget(nullptr);
    }

}

uint16 WeaponDelay = m_PChar->GetWeaponDelay(false);

    if (m_Tick > m_LastMeleeTime + WeaponDelay)
    {
        if (!isFaceing(m_PChar->loc.p, m_PBattleTarget->loc.p, 40))
        {
            if (m_Tick > m_AttackMessageTime + WeaponDelay)
            {
                m_AttackMessageTime = m_Tick;
                m_PChar->pushPacket(new CMessageBasicPacket(m_PChar, m_PBattleTarget, 0, 0, MSGBASIC_UNABLE_TO_SEE_TARG));
            }
            return;
        }
        if (Distance > m_PBattleTarget->m_ModelSize)
        {
            if (m_Tick > m_AttackMessageTime + WeaponDelay)
            {
                m_AttackMessageTime = m_Tick;
                m_PChar->pushPacket(new CMessageBasicPacket(m_PChar, m_PBattleTarget, 0, 0, MSGBASIC_TARG_OUT_OF_RANGE));
            }
            return;
        }
        m_LastMeleeTime = m_Tick;
        if (battleutils::IsParalyzed(m_PChar))
        {
            m_PChar->loc.zone->PushPacket(m_PChar, CHAR_INRANGE_SELF, new CMessageBasicPacket(m_PChar, m_PBattleTarget, 0, 0, MSGBASIC_IS_PARALYZED));
        }
        else if (battleutils::IsIntimidated(m_PChar, m_PBattleTarget))
        {
            m_PChar->loc.zone->PushPacket(m_PChar, CHAR_INRANGE_SELF, new CMessageBasicPacket(m_PChar, m_PBattleTarget, 0, 0, MSGBASIC_IS_INTIMIDATED));
        }
        else
        {
            DoAttack();

            if (m_PChar->PPet != nullptr && m_PChar->PPet->objtype == TYPE_PET && (m_PChar->GetMJob() != JOB_BST && m_PChar->GetSJob() != JOB_BST))
            {
                m_PChar->PPet->PBattleAI->SetBattleTarget(m_PBattleTarget);
            }
        }
    }
The above really just boils down to adding "&& (m_PChar->GetMJob() != JOB_BST && m_PChar->GetSJob() != JOB_BST)" to a few if statements.
This has the effect of making a pet engage your current target whenever you autoattack, and disengage whenever you do.


Step #3: petutils.cpp tweaks

Code: Select all

// 0-2 lvls lower
// highestLvl -= dsprand::GetRandomNumber(3);
Very simple, just find that second line and comment it out.
This makes sure that the server won't try to spawn you a pet with a level of negative two at the start of the game.

That's all the basics done. But if you want the mobs to be able to use TP skills as a Non-BST, then there's a bit more work involved...


Step #4: AI_Pet_Dummy tweaks

Code: Select all

void CAIPetDummy::ActionAbilityStart()
{
    if (m_PPet->StatusEffectContainer->HasPreventActionEffect())
    {
        return;
    }

    if (m_PPet->objtype == TYPE_MOB && m_PPet->PMaster->objtype == TYPE_PC)
    {
        if ((m_MasterCommand == MASTERCOMMAND_SIC || (m_PPet->PMaster->GetMJob() != JOB_BST && m_PPet->PMaster->GetSJob() != JOB_BST)) && m_PPet->health.tp >= 1000 && m_PBattleTarget != nullptr)
        {
            if(m_MasterCommand == MASTERCOMMAND_SIC)
            {
                m_MasterCommand = MASTERCOMMAND_NONE;
            }
            CMobEntity* PMob = (CMobEntity*)m_PPet->PMaster->PPet;
            std::vector<CMobSkill*> MobSkills = battleutils::GetMobSkillsByFamily(PMob->m_Family);

            if (MobSkills.size() > 0)
            {
                int maxSearch = 10;
                // keep looking for an ability until one is valid
                do {
                    SetCurrentMobSkill(MobSkills.at(dsprand::GetRandomNumber(MobSkills.size())));
                } while (luautils::OnMobSkillCheck(m_PBattleTarget, m_PPet, GetCurrentMobSkill()) != 0 && maxSearch--);

                // could not find skill
                if (maxSearch == 0)
                {
                    TransitionBack(true);
                    return;
                }

                preparePetAbility(m_PBattleTarget);
                return;
            }
            return;
        }
    }


    if (m_PPet->getPetType() == PETTYPE_JUG_PET){
        if ((m_MasterCommand == MASTERCOMMAND_SIC || (m_PPet->PMaster->GetMJob() != JOB_BST && m_PPet->PMaster->GetSJob() != JOB_BST)) && m_PPet->health.tp >= 1000 && m_PBattleTarget != nullptr){ //choose random tp move
            m_MasterCommand = MASTERCOMMAND_NONE;
            if (m_PPet->PetSkills.size() > 0){
                // Pick a Random TP Move
                SetCurrentMobSkill(m_PPet->PetSkills.at(dsprand::GetRandomNumber(m_PPet->PetSkills.size())));

                // Replace with an AoE Damage TP Move if more than one mob nearby, otherwise use a ST Damage TP Move
                m_PTargetFind->reset();
                m_PPet->m_ActionList.clear();
                m_PTargetFind->findWithinArea(m_PBattleTarget, AOERADIUS_ATTACKER, 10); // Default small area
                uint16 TotalTargets = m_PTargetFind->m_targets.size();
                if (TotalTargets > 1)
                {
                    // Pick an AoE Damage Ablity
                    int AoEDamageAbilityIndex = 250;
                    if (m_PPet->name.compare("Lucky Lulush") == 0) { // "Whirl Claws"
                        AoEDamageAbilityIndex = 2; }
                    if (m_PPet->name.compare("Gorefang Hobs") == 0) { // "Claw Cyclone"
                        AoEDamageAbilityIndex = 2; }
                    if (m_PPet->name.compare("Faithful Falcorr") == 0) { // "Back Heel"
                        AoEDamageAbilityIndex = 1; }
                    if (m_PPet->PetSkills.size() > AoEDamageAbilityIndex) {
                        SetCurrentMobSkill(m_PPet->PetSkills.at(AoEDamageAbilityIndex)); }
                }
                else {
                    // Pick a ST Damage Ablity
                    int STDamageAbilityIndex = 250;
                    if (m_PPet->name.compare("Lucky Lulush") == 0) { // "Foot Kick"
                        STDamageAbilityIndex = 0; } 
                    if (m_PPet->name.compare("Gorefang Hobs") == 0) { // "Razor Fang"
                        STDamageAbilityIndex = 1; }
                    if (m_PPet->name.compare("Faithful Falcorr") == 0) { // "Back Heel"
                        STDamageAbilityIndex = 1; }
                    if (m_PPet->PetSkills.size() > STDamageAbilityIndex) {
                        SetCurrentMobSkill(m_PPet->PetSkills.at(STDamageAbilityIndex)); }
                }

                // Replace with a Healing TP Move if Pet or Master are below a percentage of HP
                int HealingThreshold = 50; // Percentage of HP to trigger a Heal
                if ((m_PPet->PMaster->GetHPP() <= HealingThreshold) || (m_PPet->GetHPP() <= HealingThreshold))
                {
                    // Pick a Healing Ablity
                    int HealingAbilityIndex = 250;
                    if (m_PPet->name.compare("Lucky Lulush") == 0) {  // "Wild Carrot"
                        HealingAbilityIndex = 3; }
                    if (m_PPet->PetSkills.size() > HealingAbilityIndex) {
                        SetCurrentMobSkill(m_PPet->PetSkills.at(HealingAbilityIndex)); }
                }

                preparePetAbility(m_PBattleTarget);
                return;
            }
        }
    }
    else if (m_PPet->getPetType() == PETTYPE_AVATAR){
        for (int i = 0; i < m_PPet->PetSkills.size(); i++){
            if (m_PPet->PetSkills[i]->getAnimationTime() == m_MasterCommand){
                SetCurrentMobSkill(m_PPet->PetSkills[i]);
                m_MasterCommand = MASTERCOMMAND_NONE;
                preparePetAbility(m_PPet);
                return;
            }
        }
        m_MasterCommand = MASTERCOMMAND_NONE;
    }
    else if (m_PPet->getPetType() == PETTYPE_WYVERN){

        WYVERNTYPE wyverntype = m_PPet->getWyvernType();

        if (m_MasterCommand == MASTERCOMMAND_ELEMENTAL_BREATH && (wyverntype == WYVERNTYPE_MULTIPURPOSE || wyverntype == WYVERNTYPE_OFFENSIVE)){
            m_MasterCommand = MASTERCOMMAND_NONE;

            //offensive or multipurpose wyvern
            if (m_PBattleTarget != nullptr){ //prepare elemental breaths
                int skip = dsprand::GetRandomNumber(6);
                int hasSkipped = 0;

                for (int i = 0; i < m_PPet->PetSkills.size(); i++){
                    if (m_PPet->PetSkills[i]->getValidTargets() == TARGET_ENEMY){
                        if (hasSkipped == skip){
                            SetCurrentMobSkill(m_PPet->PetSkills[i]);
                            break;
                        }
                        else{
                            hasSkipped++;
                        }
                    }
                }

                preparePetAbility(m_PBattleTarget);
                return;
            }

        }
        else if (m_MasterCommand == MASTERCOMMAND_HEALING_BREATH && (wyverntype == WYVERNTYPE_DEFENSIVE || wyverntype == WYVERNTYPE_MULTIPURPOSE))
        {

            m_MasterCommand = MASTERCOMMAND_NONE;
            m_PBattleSubTarget = nullptr;
            //TODO: CHECK FOR STATUS EFFECTS FOR REMOVE- BREATH (higher priority than healing breaths)

            //	if(m_PPet->PMaster->PParty==nullptr){//solo with master-kun
            CItemArmor* masterHeadItem = ((CCharEntity*)(m_PPet->PMaster))->getEquip(SLOT_HEAD);

            uint16 masterHead = masterHeadItem ? masterHeadItem->getID() : 0;

			// Determine what the required HP percentage will need to be 
			// at or under in order for healing breath to activate.
			uint8 requiredHPP = 0;
			if (((CCharEntity*)(m_PPet->PMaster))->objtype == TYPE_PC && (masterHead == 12519 || masterHead == 15238)) { //Check for player & AF head, or +1
				if (wyverntype == WYVERNTYPE_DEFENSIVE) { //healer wyvern
					requiredHPP = 50;
				}
				else if (wyverntype == WYVERNTYPE_MULTIPURPOSE) { //hybrid wyvern
					requiredHPP = 33;
				}
			}
			else {
				if (wyverntype == WYVERNTYPE_DEFENSIVE) { //healer wyvern
					requiredHPP = 33;
				}
				else if (wyverntype == WYVERNTYPE_MULTIPURPOSE) { //hybrid wyvern
					requiredHPP = 25;
				}
			}

			// Only attempt to find a target if there is an HP percentage to calculate.
			if (requiredHPP) {
				CBattleEntity* master = m_PPet->PMaster;
				// Check the master first.
				if (master->GetHPP() <= requiredHPP) {
					m_PBattleSubTarget = master;
				}

				// Otherwise if this is a healer wyvern, and the member is in a party 
				// check all of the party members who qualify.
				else if (wyverntype == WYVERNTYPE_DEFENSIVE && master->PParty != nullptr) {
					master->ForParty([this, requiredHPP](CBattleEntity* PTarget){
						if (PTarget->GetHPP() <= requiredHPP) {
							m_PBattleSubTarget = PTarget;
						}
					});
				}
			}

            if (m_PBattleSubTarget != nullptr){ //target to heal
                //get highest breath for wyverns level
                m_PMobSkill = nullptr;
                for (int i = 0; i < m_PPet->PetSkills.size(); i++){
                    if (m_PPet->PetSkills[i]->getValidTargets() == TARGET_PLAYER_PARTY){
                        if (m_PPet->PetSkills[i]->getID() == 638 &&
                            m_PPet->PMaster->GetMLevel() < 20){ //can only using hb1
                            SetCurrentMobSkill(m_PPet->PetSkills[i]);
                            break;
                        }
                        else if (m_PPet->PetSkills[i]->getID() == 639 &&
                            m_PPet->PMaster->GetMLevel() < 40){ //can only using hb2
                            SetCurrentMobSkill(m_PPet->PetSkills[i]);
                            break;
                        }
                        else if (m_PPet->PetSkills[i]->getID() == 640 &&
                            m_PPet->PMaster->GetMLevel() >= 40){ //can only using hb3
                            SetCurrentMobSkill(m_PPet->PetSkills[i]);
                            break;
                        }
                    }
                }
                preparePetAbility(m_PBattleSubTarget);
                return;
            }
        }
    }

    TransitionBack(true);
}

void CAIPetDummy::ActionAttack()
{
    if (m_PPet->PMaster == nullptr || m_PPet->PMaster->isDead() || m_PPet->isDead()){
        m_ActionType = ACTION_FALL;
        ActionFall();
        return;
    }


    //if 2 bsts are in party, make sure their pets cannot fight eachother
    if (m_PBattleTarget != nullptr && m_PBattleTarget->objtype == TYPE_MOB && m_PBattleTarget->PMaster != nullptr && m_PBattleTarget->PMaster->objtype == TYPE_PC)
    {
        m_ActionType = ACTION_DISENGAGE;
        ActionDisengage();
        return;
    }


    //wyvern behaviour
    if (m_PPet->getPetType() == PETTYPE_WYVERN && m_PPet->PMaster->PBattleAI->GetBattleTarget() == nullptr){
        m_PBattleTarget = nullptr;
    }

    //handle death of target
    if (m_PBattleTarget == nullptr || m_PBattleTarget->isDead() ||
        m_PBattleTarget->animation == ANIMATION_CHOCOBO)
    {
        m_ActionType = ACTION_DISENGAGE;
        ActionDisengage();
        return;
    }

    if ((m_queueSic || (m_PPet->PMaster->GetMJob() != JOB_BST && m_PPet->PMaster->GetSJob() != JOB_BST)) && m_PPet->health.tp >= 1000)
    {
        // now use my tp move
        m_queueSic = false;
        m_MasterCommand = MASTERCOMMAND_SIC;
        m_ActionType = ACTION_MOBABILITY_START;
        ActionAbilityStart();
        return;
    }

    m_PPathFind->LookAt(m_PBattleTarget->loc.p);

    float currentDistance = distance(m_PPet->loc.p, m_PBattleTarget->loc.p);

    //go to target if its too far away
    if (currentDistance > m_PBattleTarget->m_ModelSize && m_PPet->speed != 0)
    {
        if (m_PPathFind->PathAround(m_PBattleTarget->loc.p, 2.0f, PATHFLAG_RUN | PATHFLAG_WALLHACK))
        {
            m_PPathFind->FollowPath();

            // recalculate
            currentDistance = distance(m_PPet->loc.p, m_PBattleTarget->loc.p);
        }
    }

    if (currentDistance <= m_PBattleTarget->m_ModelSize)
    {
        int32 WeaponDelay = m_PPet->m_Weapons[SLOT_MAIN]->getDelay();
        //try to attack
        if (m_Tick > m_LastActionTime + WeaponDelay){
            if (battleutils::IsParalyzed(m_PPet))
            {
                m_PPet->loc.zone->PushPacket(m_PPet, CHAR_INRANGE, new CMessageBasicPacket(m_PPet, m_PBattleTarget, 0, 0, 29));
            }
            else if (battleutils::IsIntimidated(m_PPet, m_PBattleTarget))
            {
                m_PPet->loc.zone->PushPacket(m_PPet, CHAR_INRANGE, new CMessageBasicPacket(m_PPet, m_PBattleTarget, 0, 0, 106));
            }
            else
            {
                apAction_t Action;
                m_PPet->m_ActionList.clear();

                Action.ActionTarget = m_PBattleTarget;

                uint8 numAttacks = battleutils::CheckMultiHits(m_PPet, m_PPet->m_Weapons[SLOT_MAIN]);

                for (uint8 i = 0; i < numAttacks; i++){
                    Action.reaction = REACTION_EVADE;
                    Action.speceffect = SPECEFFECT_NONE;
                    Action.animation = 0;
                    Action.param = 0;
                    Action.messageID = 15;
                    Action.knockback = 0;
                    //ShowDebug("pet hp %i and atk %i def %i eva is %i \n",m_PPet->health.hp,m_PPet->ATT(),m_PPet->DEF(),m_PPet->getMod(MOD_EVA));
                    int32 damage = 0;

                    if (m_PBattleTarget->StatusEffectContainer->HasStatusEffect(EFFECT_PERFECT_DODGE))
                    {
                        Action.messageID = 32;
                    }
                    else if ((dsprand::GetRandomNumber(100) < battleutils::GetHitRate(m_PPet, m_PBattleTarget)) &&
                        !m_PBattleTarget->StatusEffectContainer->HasStatusEffect(EFFECT_ALL_MISS))
                    {
                        if (battleutils::IsAbsorbByShadow(m_PBattleTarget))
                        {
                            Action.messageID = 31;
                            Action.param = 1;
                            Action.reaction = REACTION_EVADE;
                        }
                        else
                        {
                            Action.reaction = REACTION_HIT;
                            Action.speceffect = SPECEFFECT_HIT;
                            Action.messageID = 1;

                            bool isCritical = (dsprand::GetRandomNumber(100) < battleutils::GetCritHitRate(m_PPet, m_PBattleTarget, false));
                            float DamageRatio = battleutils::GetDamageRatio(m_PPet, m_PBattleTarget, isCritical, 0);

                            if (isCritical)
                            {

                                Action.speceffect = SPECEFFECT_CRITICAL_HIT;
                                Action.messageID = 67;
                            }

                            damage = (int32)((m_PPet->GetMainWeaponDmg() + battleutils::GetFSTR(m_PPet, m_PBattleTarget, SLOT_MAIN)) * DamageRatio);
                        }
                    }
                    else {
                        // create enmity even on misses
                        if (m_PBattleTarget->objtype == TYPE_MOB){
                            CMobEntity* PMob = (CMobEntity*)m_PBattleTarget;
                            PMob->PEnmityContainer->UpdateEnmity(m_PPet, 0, 0);
                        }
                    }
                    if (m_PBattleTarget->objtype == TYPE_PC)
                    {
                        charutils::TrySkillUP((CCharEntity*)m_PBattleTarget, SKILL_EVA, m_PPet->GetMLevel());
                    }

                    bool isBlocked = (dsprand::GetRandomNumber(100) < battleutils::GetBlockRate(m_PPet, m_PBattleTarget));
                    if (isBlocked){ Action.reaction = REACTION_BLOCK; }


                    Action.param = battleutils::TakePhysicalDamage(m_PPet, m_PBattleTarget, damage, isBlocked, SLOT_MAIN, 1, nullptr, true, true);
                    if (Action.param < 0)
                    {
                        Action.param = -(Action.param);
                        Action.messageID = 373;
                    }

                    // spike effect
                    if (Action.reaction != REACTION_EVADE && Action.reaction != REACTION_PARRY)
                    {

                        battleutils::HandleEnspell(m_PPet, m_PBattleTarget, &Action, i, m_PPet->m_Weapons[SLOT_MAIN], damage);
                        battleutils::HandleSpikesDamage(m_PPet, m_PBattleTarget, &Action, damage);
                    }

                    m_PPet->m_ActionList.push_back(Action);
                }

                m_PPet->loc.zone->PushPacket(m_PPet, CHAR_INRANGE, new CActionPacket(m_PPet));

                if (m_PPet->PMaster != nullptr && m_PPet->PMaster->objtype == TYPE_PC && m_PPet->PMaster->PPet != nullptr){
                    ((CCharEntity*)m_PPet->PMaster)->pushPacket(new CPetSyncPacket((CCharEntity*)m_PPet->PMaster));
                }
            }
            m_LastActionTime = m_Tick;

            // Update the targets attacker level..
            CMobEntity* Monster = (CMobEntity*)m_PBattleTarget;
            if (Monster->m_HiPCLvl < ((CCharEntity*)m_PPet->PMaster)->GetMLevel())
                Monster->m_HiPCLvl = ((CCharEntity*)m_PPet->PMaster)->GetMLevel();
        }
    }
}

void CAIPetDummy::ActionRoaming()
{
    if (m_PPet->PMaster == nullptr || m_PPet->PMaster->isDead()){
        m_ActionType = ACTION_FALL;
        ActionFall();
        return;
    }

    //automaton, wyvern
    if (m_PPet->getPetType() == PETTYPE_WYVERN || m_PPet->getPetType() == PETTYPE_AUTOMATON){
        if (PetIsHealing()){
            return;
        }
    }

    if (m_PBattleTarget != nullptr){
        m_ActionType = ACTION_ENGAGE;
        ActionEngage();
        return;
    }

    float currentDistance = distance(m_PPet->loc.p, m_PPet->PMaster->loc.p);


    // this is broken until pet / mob relationship gets fixed
    // pets need to extend mob or be a mob because pet has no spell list!
    if (m_PPet->getPetType() == PETTYPE_AVATAR && m_PPet->m_Family == 104 && m_Tick >= m_LastActionTime + 30000 && currentDistance < PET_ROAM_DISTANCE * 2)
    {
        int16 spellID = 108;
        // define this so action picks it up
        SetCurrentSpell(spellID);
        m_PBattleSubTarget = m_PPet->PMaster;

        m_ActionType = ACTION_MAGIC_START;
        ActionMagicStart();
        return;
    }

    if (currentDistance > PET_ROAM_DISTANCE)
    {
        if (currentDistance < 35.0f && m_PPathFind->PathAround(m_PPet->PMaster->loc.p, 2.0f, PATHFLAG_RUN | PATHFLAG_WALLHACK))
        {
            m_PPathFind->FollowPath();
        }
        else if (m_PPet->GetSpeed() > 0)
        {
            m_PPathFind->WarpTo(m_PPet->PMaster->loc.p, PET_ROAM_DISTANCE);
        }
    }
    if ((m_Tick >= (m_LastActionTime + 6000)) && (m_PPet->health.hp < m_PPet->health.maxhp))
    {
        // Restore hp by 1% every 6 seconds
        float rate = 0.01f;
        uint16 boost = (float)m_PPet->health.maxhp * rate;
        if (boost < 1)
        {
            boost = 1;
        }
        if ((m_PPet->health.hp + boost) > m_PPet->health.maxhp)
        {
            m_PPet->health.hp = m_PPet->health.maxhp;
        }
        else
        {
            m_PPet->health.hp += boost;
        }
        m_PPet->updatemask |= UPDATE_HP;
        m_LastActionTime = m_Tick;
    }
}

That last section is what really makes it work for non-BSTs. There are a few "if" statements I've applied against the SIC checks so that non-BSTs pets automatically get to use TP abilities once they build up sufficient TP. I've also put a little bit of extra AI checking in against our three summoned pets to trigger their abilities a bit more sensibly (Lulush and Gorefang will use an AoE damage ability whenever more than one foe is nearby, otherwise they'll stick to ST damage and the Rabbit will try to heal whenever it or its owner drops below 50% HP). Finally, whenever a pet is disengaged and enters the ROAMING state, it will gradually recover HP - the default rate is 1% (or 1HP, whichever is greater) every 6 seconds.

We're nearly done. Just one last tweak is needed: Rabbit BST jugpets. By default, these are assigned to the standard (non Curing) Rabbit pool, but we want them to have Wild Carrot available so we need to point them to the Curing Rabbit pool. However, the Curing Rabbit pool has "Wild Carrot" set as self-affecting only and ideally you want it to be an AoE Curaga-Style heal.

So in mob_pools.sql find the line
INSERT INTO `mob_pools` VALUES (4612,'Pet_Lucky_Lulush',206,0x00000E0100000000000000000000000000000000,1,1,8,240,100,0,256,0,8,0,32,555,133,0,0,0,1,0);
and change it to
INSERT INTO `mob_pools` VALUES (4612,'Pet_Lucky_Lulush',404,0x00000E0100000000000000000000000000000000,1,1,8,240,100,0,256,0,8,0,32,555,133,0,0,0,1,0);

And in mob_skill.sql find the line
INSERT INTO `mob_skill` VALUES (67,404,37,'Wild_Carrot',0,20.0,2000,1500,1,0,0,0);
and change it to
INSERT INTO `mob_skill` VALUES (67,404,37,'Wild_Carrot',1,20.0,2000,1500,1,0,0,0);

And then update the SQL tables using your update scripts... or just edit your live database tables directly and tweak the values accordingly.

Now you can run about as whatever job you want, with your own semi-intelligent, auto-levelling pet! :D



Lulush will be a beast (pun only slightly intended) without any further changes, but you may find that you need to increase the out-of-battle regeneration rate for Gorefang and Falcorr since they'll likely end up tanking for you and you won't be able to use reward as a non-BST. To do so, just change the float in the code above from 0.01f (will take 10mins to go from 0 to full) to 0.05f (will take two minutes to go from 0 to full) or whatever you feel is appropriately balanced. Or you could always add a few extra mods to that Happy Egg instead... ;)

INSERT INTO `item_mods_pet` VALUES(18166, 23, 25); -- Pet: Attack +25
INSERT INTO `item_mods_pet` VALUES(18166, 25, 25); -- Pet: Accuracy +25
INSERT INTO `item_mods_pet` VALUES(18166, 384, 10); -- Pet: Haste +10
INSERT INTO `item_mods_pet` VALUES(18166, 369, 25); -- Pet: Regen +25
INSERT INTO `item_mods_pet` VALUES(18166, 161, -10); -- Pet: PDamage taken -10%
INSERT INTO `item_mods_pet` VALUES(18166, 163, -10); -- Pet: MDamage taken -10%

:mrgreen:

Post Reply