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!
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%