Files
ROSS/Src/World Server/aip/AiActions.cpp
T
2025-08-11 11:09:09 -05:00

1345 lines
45 KiB
C++

// Props to ExJam for this code :D
#include "../WorldServer.h"
// extern CRandomMersenne rg;
//Stop all actions
AIACT(000)
{
//SetCMD_Stop (Stop all actions i guess?)
//entity->StopAll();
CMonster* monster = reinterpret_cast<CMonster*>(entity);
ClearBattle( monster->Battle );
monster->Position->destiny = monster->Position->current;
return AI_SUCCESS;
}
//Do Action
AIACT(001)
{
//byte cAction; //Pos: 0x00
//CObjMOB::Set_EMOTION (pak 781)
GETAIACTDATA(001);
BEGINPACKET( pak, 0x781 );
ADDWORD ( pak, data->cAction );
ADDWORD ( pak, 0);
ADDWORD ( pak, entity->clientid );
GServer->SendToVisible(&pak, entity,NULL );
return AI_SUCCESS;
}
//Say LTB String
AIACT(002)
{
//Client side say text
//Log(MSG_DEBUG, "Say LTB String");
//dword iStrID; Pos: 0x00
GETAIACTDATA(002);
//Think this is crashing the server. Taken out for the time being.
//lets have the NPC also say the LTB string over the regular message system. This will show up green ^-^
CMonster* thisMonster = reinterpret_cast<CMonster*>(entity);
//if(thisMonster->CharType == TNPC) // check if it is an NPC and not a normal monster.
//{
BEGINPACKET( pak, 0x0784 );
ADDSTRING( pak, GServer->Ltbstring[data->iStrID].NPCname );
ADDBYTE( pak, 0 );
ADDSTRING( pak, GServer->Ltbstring[data->iStrID].LTBstring );
ADDBYTE( pak, 0 );
GServer->SendToVisible(&pak, thisMonster);
//}
return AI_SUCCESS;
}
//Move (walk or run) to a random location within iDistance of my present location
AIACT(003)
{
//dword iDistance; //Pos: 0x00
//byte cSpeed; //Pos: 0x04
//move randomly within iDistance use randincircle
GETAIACTDATA(003);
CMonster* monster = reinterpret_cast<CMonster*>(entity);
monster->UpdatePosition( );
int iDist = data->iDistance;// * 2;//Get it to our coord system!
entity->Position->destiny = GServer->RandInCircle(entity->Position->current, iDist);
//int randDis = rand()%(iDist << 1);
//float nX = (entity->Position->current.x + randDis) - iDist;
//entity->Position->destiny.x=nX;
//randDis = rand()%(iDist << 1);
//float nY = (entity->Position->current.y + randDis) - iDist;
//entity->Position->destiny.y = nY;
monster->Stats->stance = data->cSpeed;
monster->SetStats();
entity->Position->lastMoveTime = clock();
entity->Battle->atktype=0;
BEGINPACKET( pak, 0x797 );
ADDWORD ( pak, entity->clientid );
ADDWORD ( pak, 0x0000 );
ADDWORD ( pak, entity->Stats->Move_Speed ); //speed
ADDFLOAT ( pak, entity->Position->destiny.x*100 );
ADDFLOAT ( pak, entity->Position->destiny.y*100 );
ADDWORD ( pak, 0xcdcd );
ADDBYTE ( pak, monster->Stats->stance );
GServer->SendToVisible(&pak, entity);
return AI_SUCCESS;
}
//Move (walk or run) to a random location within iDistance of my spawn location
AIACT(004)
{
//dword iDistance; //Pos: 0x00
//byte cSpeed; //Pos: 0x04
//move randomly within iDistance of spawn position
GETAIACTDATA(004);
CMonster* monster = reinterpret_cast<CMonster*>(entity);
monster->UpdatePosition( );
int iDist = data->iDistance;//Get it to our coord system!
entity->Position->destiny = GServer->RandInCircle(entity->Position->source, iDist);
//int randDis = rand()%(iDist << 1);
//float nX = (monster->Position->source.x + randDis) - iDist;
//monster->Position->destiny.x=nX;
//randDis = rg.IRandom(0, iDist << 1);
//randDis = rand()%(iDist << 1);
//float nY = (monster->Position->source.y + randDis) - iDist;
//monster->Position->destiny.y=nY;
monster->Stats->stance = data->cSpeed;
monster->SetStats();
monster->Position->lastMoveTime = clock();
entity->Battle->atktype = 0;
BEGINPACKET( pak, 0x797 );
ADDWORD ( pak, entity->clientid );
ADDWORD ( pak, 0x0000 );//???
ADDWORD ( pak, monster->Stats->Move_Speed ); //speed
ADDFLOAT ( pak, monster->Position->destiny.x*100 );
ADDFLOAT ( pak, monster->Position->destiny.y*100 );
ADDWORD ( pak, 0xcdcd );
ADDBYTE ( pak, monster->Stats->stance );
GServer->SendToVisible(&pak, entity);
return AI_SUCCESS;
}
//Move (walk or run) to a random location within iDistance of my findChar location
AIACT(005)
{
//byte cSpeed; //Pos: 0x00
//move randomly within 200 of "m_pFindCHAR"
GETAIACTDATA(005);
if(entity->findChar == NULL)
return AI_FAILURE;
CMonster* monster = reinterpret_cast<CMonster*>(entity);
monster->UpdatePosition( );
entity->findChar->UpdatePosition();
int iDist = 2;//Get it to our coord system!
entity->Position->destiny = GServer->RandInCircle(entity->findChar->Position->current, iDist);
//int randDis = rand()%(iDist << 1);
//float nX = (entity->findChar->Position->current.x + randDis) - iDist;
//entity->Position->destiny.x=nX;
//randDis = rand()%(iDist << 1);
//float nY = (entity->findChar->Position->current.y + randDis) - iDist;
//entity->Position->destiny.y=nY;
monster->Stats->stance = data->cSpeed;
monster->SetStats();
entity->Battle->atktype = 0;
BEGINPACKET( pak, 0x797 );
ADDWORD ( pak, entity->clientid );
ADDWORD ( pak, 0x0000 );//???
ADDWORD ( pak, monster->Stats->Move_Speed ); //speed
ADDFLOAT ( pak, monster->Position->destiny.x*100 );
ADDFLOAT ( pak, monster->Position->destiny.y*100 );
ADDWORD ( pak, 0xcdcd );
ADDBYTE ( pak, monster->Stats->stance );
GServer->SendToVisible(&pak, entity);
return AI_SUCCESS;
}
//Run and Attack aiobj within iDistance that has the lowest or greatest cAbType
AIACT(006)
{
//dword iDistance; //Pos: 0x00
//byte cAbType; //Pos: 0x04
//byte cMoreLess; //Pos: 0x05
//Run and Attack aiobj within iDistance that has the lowest or greatest cAbType
//cAbType
//0 = Level
//1 = Attack
//2 = Defense
//3 = "Res"? Magic Resistance?
//4 = HP
//5 = Charm
GETAIACTDATA(006);
dword eCount = 0;
int highestAB = -9999999;
// CWorldEntity* highestEntity = NULL;
int lowestAB = 9999999;
// CWorldEntity* lowestEntity = NULL;
CMonster* monster = reinterpret_cast<CMonster*>(entity);
if(monster->thisnpc->aggresive == 0)
return AI_FAILURE; //monster has no agression value so cannot attack a player
int aggro = GServer->RandNumber(1,20);
if(aggro > monster->thisnpc->aggresive)
return AI_FAILURE; // failed aggresiveness check
CMap* map= GServer->MapList.Index[entity->Position->Map];
CCharacter* Target = NULL;
for(UINT i=0;i<map->PlayerList.size();i++)
{
CPlayer* player = map->PlayerList.at(i);
if(player == NULL)continue;
if(GServer->IsMonInCircle( monster->Position->current, player->Position->current, data->iDistance))
{
switch(data->cAbType)
{
case 0: //level
if(data->cMoreLess == 0)
{
if(player->Stats->Level > lowestAB)
{
lowestAB = player->Stats->Level;
Target = player;
}else continue;
}else{
if(player->Stats->Level < highestAB)
{
highestAB = player->Stats->Level;
Target = player;
}else continue;
}
break;
case 1: //Attack power
if(data->cMoreLess == 0)
{
if(player->Stats->Attack_Power > lowestAB)
{
lowestAB = player->Stats->Attack_Power;
Target = player;
}else continue;
}else{
if(player->Stats->Attack_Power < highestAB)
{
highestAB = player->Stats->Attack_Power;
Target = player;
}else continue;
}
break;
case 2: //defense
if(data->cMoreLess == 0)
{
if(player->Stats->Defense> lowestAB)
{
lowestAB = player->Stats->Defense;
Target = player;
}else continue;
}else{
if(player->Stats->Defense < highestAB)
{
highestAB = player->Stats->Defense;
Target = player;
}else continue;
}
break;
case 3: //Magic Def
if(data->cMoreLess == 0)
{
if(player->Stats->Magic_Defense> lowestAB)
{
lowestAB = player->Stats->Magic_Defense;
Target = player;
}else continue;
}else{
if(player->Stats->Magic_Defense < highestAB)
{
highestAB = player->Stats->Magic_Defense;
Target = player;
}else continue;
}
break;
case 4: // HP
if(data->cMoreLess == 0)
{
if(player->Stats->HP> lowestAB)
{
lowestAB = player->Stats->HP;
Target = player;
}else continue;
}else{
if(player->Stats->HP < highestAB)
{
highestAB = player->Stats->HP;
Target = player;
}else continue;
}
break;
case 5: // Charm
if(data->cMoreLess == 0)
{
if(player->Attr->Cha > lowestAB)
{
lowestAB = player->Attr->Cha;
Target = player;
}else continue;
}else{
if(player->Attr->Cha < highestAB)
{
highestAB = player->Attr->Cha;
Target = player;
}else continue;
}
break;
default:
break;
}
}
}
// OK we found the new Target player. now attack him.
monster->Stats->stance = 1;
monster->SetStats();
monster->StartAction( Target, NORMAL_ATTACK, 0 );
return AI_SUCCESS;
}
//Special attack
AIACT(007)
{
//CObjCHAR::Special_ATTACK
//Log(MSG_DEBUG, "AIACT(007)");
return AI_SUCCESS;
}
//Move (4)
AIACT(008)
{
//dword iDistance; //Pos: 0x00
//byte cSpeed; //Pos: 0x04
//move towards target. Stop at a specified precentage away from target
//Log(MSG_DEBUG, "AIACT(008)");
GETAIACTDATA(008);
CMonster* monster = reinterpret_cast<CMonster*>(entity);
monster->UpdatePosition( );
CCharacter* target = entity->GetCharTarget( );
if(target == NULL)
{
return AI_FAILURE;
}
target->UpdatePosition();
int iDist = data->iDistance;//Get it to our coord system!
float xDist = target->Position->current.x - monster->Position->current.x;
float yDist = target->Position->current.y - monster->Position->current.y;
float fDistance = GServer->distance( entity->Position->current, target->Position->current);
float nX = monster->Position->current.x - (iDist * xDist / fDistance);
float nY = monster->Position->current.y - (iDist * yDist / fDistance);
monster->Position->destiny.x = nX;
monster->Position->destiny.y = nY;
monster->Stats->stance = data->cSpeed;
monster->SetStats();
monster->Position->lastMoveTime = clock();
ClearBattle( entity->Battle );// this also seems to clear the attackers battle??
//entity->Battle->atktype = 0;
BEGINPACKET( pak, 0x797 );
ADDWORD ( pak, entity->clientid );
ADDWORD ( pak, 0x0000 );//???
ADDWORD ( pak, monster->Stats->Move_Speed ); //speed
ADDFLOAT ( pak, monster->Position->destiny.x*100 );
ADDFLOAT ( pak, monster->Position->destiny.y*100 );
ADDWORD ( pak, 0xcdcd );
ADDBYTE ( pak, monster->Stats->stance );
GServer->SendToVisible(&pak, entity);
return AI_SUCCESS;
}
//Convert Monster to new monster (1)
AIACT(009)
{
//word wMonster; //Pos: 0x00
//Transform into wMonster
GETAIACTDATA(009);
CMap* map = GServer->MapList.Index[entity->Position->Map];
CMonster* monster = reinterpret_cast<CMonster*>(entity);
int oldmon = monster->montype;
map->ConverToMonster(monster, data->wMonster,1);
return AI_SUCCESS;
}
//Spawn Monster (2)
AIACT(010)
{
//word wMonster; //Pos: 0x00
//Spawn wMonster at current X,Y
GETAIACTDATA(010);
CMap* map = GServer->MapList.Index[entity->Position->Map];
fPoint position;
position = GServer->RandInCircle( entity->Position->current, 1 );
map->AddMonster( data->wMonster, position, 0, 0, 1 );
return AI_SUCCESS;
}
//Call for backup
AIACT(011)
{
//dword iDistance; //Pos: 0x00
//dword iNumOfMonster; //Pos: 0x04
//force iNumOfMonster of same team within iDistance to attack my target
//CWorldEntity* target = entity->thisZone->GetEntity(entity->_TargetID);
//if(target == NULL) return AI_FAILURE;
//Log(MSG_DEBUG, "AIACT(011)");
CCharacter* target = entity->GetCharTarget( );
if(target == NULL) return AI_FAILURE;
GETAIACTDATA(011);
int chrCount = 0;
dword eCount = 0;
int searchDistance = data->iDistance;
CMap* map = GServer->MapList.Index[entity->Position->Map];
dword entityCount = map->MonsterList.size();
for(UINT j=0;j<map->MonsterList.size();j++)
{
CMonster* other = map->MonsterList.at(j);
if(eCount >= entityCount) break;
if(other == NULL) continue;
eCount++;
if(other == entity) continue;
if(other->CharType != TMONSTER) continue;
int iDistance = (int)GServer->distance( other->Position->current, entity->Position->current );
if(iDistance > searchDistance) continue;
chrCount++;
other->Battle->target = entity->Battle->target;
other->StartAction( (CCharacter*) target, NORMAL_ATTACK, 0 );
// other->Attack(target);
if(chrCount >= data->iNumOfMonster) return AI_SUCCESS;
}
return AI_SUCCESS;
}
//start attack against closest player
AIACT(012)
{
//Log(MSG_DEBUG, "AIACT(012)");
//Run and attack "m_pNearCHAR" Nearest Character
// entity->Attack(entity->nearChar);
// Log(MSG_DEBUG, "AIACT(012)");
if(entity->nearChar == NULL)
{
Log(MSG_DEBUG,"No target found in entity->nearchar. Now why is that?");
return AI_FAILURE;
}
//entity->Battle->target=entity->nearChar->clientid;
CMonster* monster = reinterpret_cast<CMonster*>(entity);
int aggro = GServer->RandNumber(1,20);
if(aggro > monster->thisnpc->aggresive)
return AI_FAILURE; // failed aggresiveness check
monster->Stats->stance = 1;
monster->SetStats();
monster->StartAction( (CCharacter*) entity->nearChar, NORMAL_ATTACK, 0 );
// Log(MSG_DEBUG, "AIACT(012)%i",entity->nearChar->clientid);
return AI_SUCCESS;
}
//run and attack findChar
AIACT(013)
{
//Log(MSG_DEBUG, "AIACT(013)");
//Run and attack "m_pFindCHAR" Character found from aiobj loop things
// entity->Attack(entity->findChar);
if(entity->findChar == NULL)
{
Log(MSG_DEBUG,"No target found in entity->findchar. Now why is that?");
return AI_FAILURE;
}
//entity->Battle->target=entity->findChar->clientid;
CMonster* monster = reinterpret_cast<CMonster*>(entity);
if(monster->thisnpc->aggresive == 0)
return AI_FAILURE; //monster has no agression value so cannot attack a player
int aggro = GServer->RandNumber(1,20);
if(aggro > monster->thisnpc->aggresive)
return AI_FAILURE; // failed aggresiveness check
monster->Stats->stance = 1;
monster->SetStats();
monster->Battle->target = entity->findChar->clientid;
monster->StartAction( (CCharacter*) entity->findChar, NORMAL_ATTACK, 0 );
return AI_SUCCESS;
}
//get help from other monsters of the same monster type
AIACT(014)
{
//dword iDistance; //Pos: 0x00
//find aiobj within iDistance and force them to attack my target
// CWorldEntity* target = entity->thisZone->GetEntity(entity->_TargetID);
// if(target == NULL) return AI_FAILURE;
GETAIACTDATA(014);
CMonster* monster = reinterpret_cast<CMonster*>(entity);
CMap* map = GServer->MapList.Index[entity->Position->Map];
CCharacter* target = entity->GetCharTarget( );
if(target == NULL)
return AI_FAILURE;
int monstercount = 0;
for(UINT j=0;j<map->MonsterList.size();j++)
{
CMonster* othermon = map->MonsterList.at(j);
if(othermon == NULL)
continue;
if(othermon == monster)
continue;
if(othermon->montype != monster->montype)
continue;
if(GServer->IsMonInCircle( monster->Position->current, othermon->Position->current, data->iDistance))
{
othermon->StartAction( target, NORMAL_ATTACK, 0);
}
}
return AI_SUCCESS;
}
//Retaliate against the last character that hit me
AIACT(015)
{
//Run and attack "m_pDestCHAR" Blah?
//Log(MSG_DEBUG, "AIACT(015)");
CMonster* monster = reinterpret_cast<CMonster*>(entity);
if(monster->thisnpc->aggresive == 0)
{
//Log(MSG_DEBUG, "AIACT(015) returned false as monster has no aggression");
return AI_FAILURE; //monster has no agression value so cannot attack a player
}
if(monster->IsOnBattle())
{
//already in battle so we don't want it switching all the time. maybe 75% of the time it will do nothing?
if(GServer->RandNumber(0,100) > 25)
{
//Log(MSG_DEBUG, "AIACT(015) returned false as we got a really bad random number %i");
return AI_FAILURE; //denied!!!
}
if(monster->Battle->hitby == monster->Battle->target)
{
//Log(MSG_DEBUG, "AIACT(015) returned false as monster is already in combat with this player %i",monster->Battle->target);
return AI_FAILURE; //no need to initiate combat. I am already fighting this player
}
}
// otherwise change target to the person who just hit me
monster->Stats->stance = 1;
monster->SetStats();
monster->Battle->target = monster->Battle->hitby;
CCharacter* target = entity->GetCharTarget( );
if(target == NULL)
{
//Log(MSG_DEBUG, "I seem to have lost my target so I can't attack any more");
return AI_FAILURE;
}
//Log(MSG_DEBUG, "Starting normal attack against opponent %i",target->clientid);
monster->StartAction( target, NORMAL_ATTACK, 0 );
return AI_SUCCESS;
}
//Run away to a point within distance iDistance of my spawn point
AIACT(016)
{
//dword iDistance;
//Run Away!
//Log(MSG_DEBUG, "AIACT(016)");
GETAIACTDATA(016);
CMonster* monster = reinterpret_cast<CMonster*>(entity);
monster->UpdatePosition( );
int iDist = data->iDistance;//Get it to our coord system!
entity->Position->destiny = GServer->RandInCircle(entity->Position->source, iDist);
//float radius = data->iDistance;
//float angle = rand()%(360);
// float angle = rg.IRandom(0,360);
// entity->UpdatePosition();
// entity->MoveTo(entity->basic.pos.x + (radius * cos(angle)), entity->basic.pos.y + (radius * sin(angle)));
//float nX = (entity->Position->source.x + (radius * cos(angle)));
//entity->Position->destiny.x=nX;
//float nY = (entity->Position->source.y + (radius * cos(angle)));
//entity->Position->destiny.y=nY;
monster->Stats->stance = 1;
monster->SetStats();
entity->Position->lastMoveTime = clock();
//ClearBattle( entity->Battle );// this also seems to clear the attackers battle??
entity->Battle->atktype = 0;
BEGINPACKET( pak, 0x797 );
ADDWORD ( pak, entity->clientid );
ADDWORD ( pak, 0x0000 );//???
ADDWORD ( pak, entity->Stats->Move_Speed ); //speed
ADDFLOAT ( pak, entity->Position->destiny.x*100 );
ADDFLOAT ( pak, entity->Position->destiny.y*100 );
ADDWORD ( pak, 0xcdcd );
ADDBYTE ( pak, 0x01 );
GServer->SendToVisible(&pak, entity);
return AI_SUCCESS;
}
//Drop Item
AIACT(017)
{
//word item0; Pos: 0x00
//word item1; Pos: 0x02
//word item2; Pos: 0x04
//word item3; Pos: 0x06
//word item4; Pos: 0x08
//word item5; pos: 0x0a dummy as a spacer
//dword btToOwner; //Pos: 0x0c
//Random drop one of item 1-5
GETAIACTDATA(017);
//srand(time(NULL));
int itemRand = rand()%(5);
//int itemRand = rg.IRandom(0,360);
int nItem = data->items[itemRand];
if (nItem == 0)return AI_FAILURE;
CMonster* monster = reinterpret_cast<CMonster*>(entity);
//CItem dropItem;
CDrop* newdrop = new (nothrow) CDrop;
newdrop->clientid = GServer->GetNewClientID( );
newdrop->posMap = entity->Position->Map;
newdrop->pos = GServer->RandInCircle( entity->Position->current, 3 );
newdrop->droptime = time(NULL);
if(data->btToOwner == 1)
{
newdrop->owner = monster->MonsterDrop->firsthit;
newdrop->thisparty = monster->thisparty;
}
else
{
newdrop->owner = 0;
newdrop->thisparty = 0;
}
ClearItem(newdrop->item);
newdrop->amount = 1;
newdrop->type = 2;
newdrop->item.itemnum = nItem % 1000;
newdrop->item.itemtype = nItem / 1000;
if(newdrop->item.IsStackable())
{
newdrop->item.count = 1;
}
else
{
newdrop->item.stats = 0;
newdrop->item.durability = 40;
newdrop->item.lifespan = 1000;
newdrop->item.socketed = 0;
newdrop->item.appraised = 1;
newdrop->item.refine = 0;
}
//Log(MSG_DEBUG, "item type= %i item num = %i", newdrop->item.itemtype, newdrop->item.itemnum);
newdrop->item.count = 1;
CMap* map = GServer->MapList.Index[newdrop->posMap];
map->AddDrop( newdrop );
// entity->DropEntityItem(&dropItem);
return AI_SUCCESS;
}
//Make nearby monsters attack my target
AIACT(018)
{
//word cMonster; //Pos: 0x00
//word wHowMany; //Pos: 0x02
//dword iDistance; //Pos: 0x04
//make wHowMany monsters of type cMonster within iDistance attack my target
// CWorldEntity* target = entity->thisZone->GetEntity(entity->_TargetID);
// if(target == NULL) return AI_FAILURE;
GETAIACTDATA(018);
CMonster* monster = reinterpret_cast<CMonster*>(entity);
CMap* map = GServer->MapList.Index[entity->Position->Map];
CCharacter* target = entity->GetCharTarget( );
if(target == NULL)
return AI_FAILURE;
int monstercount = 0;
for(UINT j=0;j<map->MonsterList.size();j++)
{
CMonster* othermon = map->MonsterList.at(j);
if(othermon == NULL)
continue;
if(othermon == monster)
continue;
if(othermon->montype != data->cMonster)
continue;
if(GServer->IsMonInCircle( monster->Position->current, othermon->Position->current, data->iDistance))
{
othermon->StartAction( target, NORMAL_ATTACK, 0);
monstercount++;
if(monstercount >= data->wHowMany)
return AI_SUCCESS;
}
}
return AI_SUCCESS;
}
//Start attack
AIACT(019)
{
//Run and attack "m_pNearCHAR" //Identical to 012
return F_AI_ACT_012(server, entity, raw);
}
//Spawn Monster (5)
AIACT(020)
{
//word cMonster; //Pos: 0x00
//byte btPos; //Pos: 0x02
//dword iDistance; //Pos: 0x04
//Spawn cMonster within iDistance of btPos
//btPos 0 = my pos
//btPos 1 = "m_pDestCHAR" pos
//btPos 2 = targets pos
GETAIACTDATA(020);
CMap* map = GServer->MapList.Index[entity->Position->Map];
CMonster* monster = reinterpret_cast<CMonster*>(entity); //need this to get the spawnid of the entity
if(monster == NULL) return AI_FAILURE;
fPoint position;
if(data->btPos == 0)
{
position = GServer->RandInCircle( entity->Position->current, data->iDistance );
map->AddMonster( data->cMonster, position, 0, monster->Status->spawnid, 1 );
}
else if(data->btPos == 1)
{
position = GServer->RandInCircle( entity->Position->current, data->iDistance );
map->AddMonster( data->cMonster, position, 0, monster->Status->spawnid, 1 );
//dest char
}
else if(data->btPos == 2)
{
CCharacter* target = entity->GetCharTarget( );
if(target == NULL) return AI_FAILURE;
position = GServer->RandInCircle( target->Position->current, data->iDistance );
map->AddMonster( data->cMonster, position, 0, monster->Status->spawnid, 1 );
}
//Log(MSG_DEBUG, "AIACT(020)option spawn %i distance %i monstertype %i",data->btPos,data->iDistance,data->cMonster);
return AI_SUCCESS;
}
//Unknown
AIACT(021)
{
//Does absolutely nothing
return AI_SUCCESS;
}
//Unknown
AIACT(022)
{
//Does absolutely nothing
return AI_SUCCESS;
}
//ghostseeds and ghosts and player summons commit suicide
AIACT(023)
{
//Commit suicide
entity->Stats->HP = -1;
CMonster* thisMonster = reinterpret_cast<CMonster*>(entity);
CMap* map = GServer->MapList.Index[entity->Position->Map];
BEGINPACKET( pak, 0x799 );
ADDWORD ( pak, entity->clientid );
ADDWORD ( pak, entity->clientid );
ADDDWORD ( pak, 0x8005 );
GServer->SendToVisible( &pak, entity );
//Log(MSG_DEBUG, "AIACT(023) suicide monstertype %i",thisMonster->montype);
//thisMonster->DoAi(thisMonster->thisnpc->AI, 5);
//now release the id in the monsterlist
//map->DeleteMonster( thisMonster);
// something weird going on here. Client ids not being relinquished. Deletemonster does not help. it crashes the server.
return AI_SUCCESS;
}
//Do Skill
AIACT(024)
{
//byte btTarget; //Pos: 0x00
//word nSkill; //Pos: 0x02
//word nMotion; //Pos: 0x04 //8 = SKILL_ATTACK 6 = magic
//Use nSkill & nMotion?
//btTarget 0 = m_pFindCHAR
//btTarget 1 = target
//btTarget 2 = self skill
GETAIACTDATA(024);
// Log(MSG_DEBUG, "Do Skill btTarget %i nSkill %i nMotion %i",data->btTarget,data->nSkill,data->nMotion);
// Log(MSG_DEBUG, "entity %i",entity->clientid);
// Log(MSG_DEBUG, "entity->findChar %i",entity->findChar->clientid);
// Log(MSG_DEBUG, "entity->nearChar %i", entity->nearChar->clientid);
CSkills* thisskill = GServer->GetSkillByID( data->nSkill );
if(thisskill == NULL)return AI_FAILURE;
CCharacter* monster = entity;
//if(monster->Battle->atktype != 0)
//{
// Log(MSG_DEBUG,"Cannot apply battle attacktype. Monster already has one");
// return AI_FAILURE;
//}
//Log(MSG_DEBUG,"AIACT(024)skill attack id: %i target: %i type: %i btTarget: %i nMotion: %i", data->nSkill, thisskill->target, thisskill->skilltype, data->btTarget, data->nMotion);
switch (thisskill->skilltype)
{
case 3: //damage action
case 6: //magic attack
{
CCharacter* target = entity->GetCharTarget( );
//Log(MSG_DEBUG,"SKILL_ATTACK or DAMAGE_ACTION selected");
if(target==NULL)
{
//Log(MSG_DEBUG,"No target can be found for this skill");
return AI_FAILURE;
}
//Log(MSG_DEBUG,"SKILL_ATTACK selected. target: %i",target->clientid);
monster->StartAction( (CCharacter*) target, SKILL_ATTACK, data->nSkill );
}
break;
case 7: //AOE magic attack
{
CCharacter* target = entity->GetCharTarget( );
//Log(MSG_DEBUG,"AOE_TARGET selected");
if(target == NULL)
{
//Log(MSG_DEBUG,"No target can be found for this skill");
return AI_FAILURE;
}
//Log(MSG_DEBUG,"AOE_TARGET selected. target: %i",target->clientid);
monster->StartAction( (CCharacter*) target, AOE_TARGET, data->nSkill );
}
break;
case 8: //self buff
{
//Log(MSG_DEBUG,"BUFF_SELF selected.");
monster->StartAction( NULL, BUFF_SELF, data->nSkill );
}
break;
case 9: //skill buff
case 13://skill buff
case 11: //heal
{
CCharacter* target = entity->GetCharTarget( );
if(target == NULL) //some skills always return a NULL target here
{
if(entity->nearChar == NULL) // maybe there really is no possible target.
{
//Log(MSG_DEBUG,"No target can be found for this skill");
return AI_FAILURE;
}
else //so we are going to have them select a NEW target (if possible) rather than return AI_FAILURE
{
//Log(MSG_DEBUG,"SKILL_BUFF type 13 selected. NEW target: %i",entity->nearChar->clientid);
CCharacter* newtarget = entity->nearChar;
if(newtarget == NULL)
return AI_FAILURE;
monster->StartAction( (CCharacter*) newtarget, SKILL_BUFF, data->nSkill );
}
}
else
{
//Log(MSG_DEBUG,"SKILL_BUFF type %i selected. target: %i",thisskill->skilltype, target->clientid);
monster->StartAction( (CCharacter*) target, SKILL_BUFF, data->nSkill );
}
}
break;
case 10: //heal AOE centered on self
//Log(MSG_DEBUG,"BUFF_AOE selected. Skill id %i", data->nSkill);
monster->StartAction( NULL, BUFF_AOE, data->nSkill );
break;
case 17: //skill AOE centered on self
{
//Log(MSG_DEBUG,"SKILL_AOE selected. Skill id %i", data->nSkill);
monster->StartAction( NULL, SKILL_AOE, data->nSkill );
}
break;
default:
{
//Log(MSG_WARNING,"Skill type not recognized. type: %i",thisskill->skilltype);
}
break;
}
/*
if (data->btTarget == 0)
{
//CCharacter* target = entity->GetCharTarget();
//if(target==NULL)return AI_FAILURE;
//CCharacter* monster = entity; //defined twice????
Log(MSG_DEBUG,"BUFF_AOE selected");
monster->StartAction( NULL, BUFF_AOE, data->nSkill );
}
else if (data->btTarget == 1)
{
CCharacter* target = entity->GetCharTarget( );
if(target==NULL)return AI_FAILURE;
if(data->nMotion == 8)
{
Log(MSG_DEBUG,"SKILL_ATTACK selected");
monster->StartAction( (CCharacter*) target, SKILL_ATTACK, data->nSkill );
}
else
{
Log(MSG_DEBUG,"SKILL_BUFF selected");
monster->StartAction( (CCharacter*) target, SKILL_BUFF, data->nSkill );
}
}
else if (data->btTarget == 2)
{
if(thisskill->target == tYourself)
{
Log(MSG_DEBUG,"BUFF_SELF selected");
monster->StartAction( entity, BUFF_SELF, data->nSkill );
}
else if (thisskill->target == tAlly)
{
Log(MSG_DEBUG,"BUFF_AOE selected");
monster->StartAction( NULL, BUFF_AOE, data->nSkill );
}
else if (thisskill->target == tHostileCharacter)
{
CCharacter* target = entity->GetCharTarget( );
if(target == NULL)return AI_FAILURE;
if(data->nMotion == 8)
{
Log(MSG_DEBUG,"SKILL_ATTACK selected");
monster->StartAction( (CCharacter*) target, SKILL_ATTACK, data->nSkill );
}
else
{
monster->StartAction( (CCharacter*) target, SKILL_BUFF, data->nSkill );
}
}
}*/
return AI_SUCCESS;
}
//Set Variable (1)
AIACT(025)
{
// if(entity->_EntityType != ENTITY_NPC) return AI_FAILURE;
//byte btVarIDX Pos: 0x00
//dword iValue Pos: 0x04
//byte btOp Pos: 0x08
GETAIACTDATA(025);
CMonster* monster = reinterpret_cast<CMonster*>(entity);
if(monster == NULL) return AI_FAILURE;
if(data->btVarIDX > 19) return AI_FAILURE;
short tempval = GServer->ObjVar[monster->thisnpc->refNPC][data->btVarIDX];
switch(data->btOp)
{
case 5:
tempval = data->iValue;
break;
case 6:
tempval += data->iValue;
break;
case 7:
tempval -= data->iValue;
break;
case 9:
tempval++;
break;
default:
return AI_FAILURE;
break;
}
if(tempval < 0)tempval = 0;
GServer->ObjVar[monster->thisnpc->refNPC][data->btVarIDX] = tempval;
if(data->btVarIDX == 0) //changing index 0 changes event variable at client too. Possible others too but for now just 0
{
//Log(MSG_DEBUG,"AIACT(025) eventID has changed from %i to %i",monster->thisnpc->eventid,tempval);
monster->thisnpc->eventid = tempval;
}
return AI_SUCCESS;
}
//Set Variable (2)
AIACT(026)
{
//Set WorldVAR
//word nVarNo; //Pos: 0x00
//dword iValue; //Pos: 0x04
//byte btOp; //Pos: 0x08
GETAIACTDATA(026);
//PY: we don't know how to use worldVar so here are some fun things we can do with custom AI
switch(data->nVarNo)
{
case 1: //change world exp rate in the config
GServer->Config.EXP_RATE = data->iValue;
return AI_SUCCESS;
break;
default:
return AI_SUCCESS;
break;
}
}
//Set Economy Variable. Could have some fun with tis in the future
AIACT(027)
{
//Set EconomyVAR
//word nVarNo; //Pos: 0x00
//dword iValue; //Pos: 0x04
//byte btOp; //Pos: 0x08
return AI_SUCCESS;
}
//Shout/Ann LTB String
AIACT(028)
{
GETAIACTDATA(028);
//byte btMsgType Pos: 0x00
//dword iStrID Pos: 0x04
dword npcId = 0;
//if(entity->_EntityType == ENTITY_NPC)
CMonster* thisMonster = reinterpret_cast<CMonster*>(entity);
switch(data->btMsgType)
{
case 0: //current field
// don't think this is possible. No way to get a character to send it to??
break;
case 1: //shout to map
//Log(MSG_INFO,"AI -- NPC Shouts to map %i",entity->Position->Map);
GServer->NPCShout(entity->Position->Map,GServer->Ltbstring[data->iStrID].LTBstring,GServer->Ltbstring[data->iStrID].NPCname);
break;
case 2: //announce to server.
GServer->NPCAnnounce(GServer->Ltbstring[data->iStrID].LTBstring,GServer->Ltbstring[data->iStrID].NPCname);
break;
default:
break;
}
//npcId = reinterpret_cast<CNpc*>(entity)->NpcID;
// }else if(entity->_EntityType == ENTITY_MONSTER){
// npcId = reinterpret_cast<CMonster*>(entity)->MonID;
// }else{
// return AI_FAILURE;
// }
// std::map<dword, char*>::iterator triggerITR = server->NpcNames.find(npcId);
// if(triggerITR == server->NpcNames.end()) return AI_FAILURE;
// char* npcName = triggerITR->second;
// char* sayStr = server->lngAi->Data(data->iStrID, 1);
// if(sayStr == NULL) return AI_FAILURE;
// if(data->btMsgType == 0){
//Chat
// CPacket pakout(0x783);
// pakout.Add<dword>(entity->_ClientID);
// pakout.Add<string>(sayStr);
// server->SendPacketToZone(entity, &pakout);
// }else if(data->btMsgType == 1){
//Shout
// CPacket pakout(0x785);
// pakout.Add<string>(npcName);
// pakout.Add<string>(sayStr);
// server->SendPacketToZone(entity, &pakout);
// }else if(data->btMsgType == 2){
//Announce
// CPacket pakout(0x702);
// pakout.AddBytes((byte*)npcName, strlen(npcName));
// pakout.Add<byte>('>');
// pakout.Add<string>(sayStr);
// server->SendPacketToZone(entity, &pakout);
// }
// delete [] sayStr;
//Log(MSG_DEBUG, "AIACT(028)");
return AI_SUCCESS;
}
//Move to my owner's location
AIACT(029)
{
//Move within 20% of the distance of my "CALLER"
GETAIACTDATA(029);
// if(entity->_EntityType != ENTITY_MONSTER) return AI_FAILURE;
// CMonster* thisMonster = reinterpret_cast<CMonster*>(entity);
// CWorldEntity* caller = thisMonster->thisZone->GetEntity(thisMonster->_CallerID);
// if(caller == NULL) return AI_FAILURE;
// caller->UpdatePosition();
// thisMonster->UpdatePosition();
if(!entity->IsMonster( )) return AI_FAILURE;
CMonster* thisMonster = reinterpret_cast<CMonster*>(entity);
CMap* map = GServer->MapList.Index[thisMonster->Position->Map];
CCharacter* caller = map->GetCharInMap( thisMonster->owner );
if(caller == NULL) return AI_FAILURE;
caller->UpdatePosition();
thisMonster->UpdatePosition();
thisMonster->Stats->stance = 1;
thisMonster->SetStats();
thisMonster->Position->source = caller->Position->current;
int iDist = GServer->RandNumber(5, 20); //move to between 5% and 20% of owners position
float xDist = thisMonster->Position->current.x - thisMonster->Position->source.x;
float yDist = thisMonster->Position->current.y - thisMonster->Position->source.y;
float nX = thisMonster->Position->source.x + (xDist * iDist / 100);
float nY = thisMonster->Position->source.y + (yDist * iDist / 100);
thisMonster->Position->destiny.x = nX;
thisMonster->Position->destiny.y = nY;
thisMonster->Position->lastMoveTime = clock();
ClearBattle( thisMonster->Battle );// this also seems to clear the attackers battle??
//entity->Battle->atktype = 0;
BEGINPACKET( pak, 0x797 );
ADDWORD ( pak, entity->clientid );
ADDWORD ( pak, 0x0000 );//???
ADDWORD ( pak, thisMonster->Stats->Move_Speed ); //speed
ADDFLOAT ( pak, thisMonster->Position->destiny.x*100 );
ADDFLOAT ( pak, thisMonster->Position->destiny.y*100 );
ADDWORD ( pak, 0xcdcd );
ADDBYTE ( pak, thisMonster->Stats->stance );
GServer->SendToVisible(&pak, entity);
return AI_SUCCESS;
}
//Do Trigger
AIACT(030)
{
//word lenszTrigger; //Pos: 0x00
//char* szTrigger; //Pos: 0x02
GETAIACTDATA(030);
char* tempName = reinterpret_cast<char*>(&data->szTrigger) - 2;
dword hash = MakeStrHash(tempName);
//Log(MSG_INFO, "Execute Quest Trigger %s[%d] [%08x]", tempName, data->lenszTrigger, hash);
//make a dummy player from this monster
CClientSocket* thisclient = new CClientSocket;
CPlayer* player = new CPlayer( thisclient );
if(player == NULL)
{
Log(MSG_INFO, "Player not found in AIACT 30");
return AI_FAILURE;
}
thisclient->player = (void*) player;
player->Position = entity->Position;
int quest = player->ExecuteQuestTrigger(hash);
if(quest == QUEST_SUCCESS)
return AI_SUCCESS;
return AI_FAILURE;
}
//set monster's attack target to the same as the owner
AIACT(031)
{
//Log(MSG_DEBUG, "AIACT(031)");
//Attack my "CALLER"(s target) if he is not an ally (must be caller's target...)
if(!entity->IsMonster( )) return AI_FAILURE;
CMonster* thisMonster = reinterpret_cast<CMonster*>(entity);
CMap* map = GServer->MapList.Index[thisMonster->Position->Map];
CCharacter* caller = map->GetCharInMap( thisMonster->owner );
if(caller == NULL) return AI_FAILURE;
thisMonster->Position->source = caller->Position->current;
CCharacter* target = map->GetCharInMap(caller->Battle->target);
if(target == NULL) return AI_FAILURE;
// need to prevent the summon from attacking other summons of the same player
CMonster* otherMonster = reinterpret_cast<CMonster*>(target);
if(otherMonster->owner == thisMonster->owner) return AI_FAILURE;
thisMonster->StartAction( (CCharacter*) target, NORMAL_ATTACK, 0 );
//Log(MSG_DEBUG, "AIACT(031) summon attacking player's target");
return AI_SUCCESS;
}
//Set PK flag for specified zone
AIACT(032)
{
//word nZoneNo; //Pos: 0x00
//byte btOnOff; //Pos: 0x02
//Set PK Flag (btOnOff) in nZoneNo
GETAIACTDATA(032);
//Log(MSG_DEBUG, "AIACT(032) zone (1)");
return AI_SUCCESS;
}
//Set regen mode for specified zone
AIACT(033)
{
//word nZoneNo //if 0, current map
//word nNewValue
//if nNewValue == 2 -> toggle
//else set regen system nNewValue (0 = off, 1 = on)
GETAIACTDATA(033);
//Log(MSG_DEBUG, "AIACT(033) zone (2)");
return AI_SUCCESS;
}
//Item (?)
AIACT(034)
{
//word nItemNum; //Pos: 0x00
//word nCount; //Pos: 0x02
//Give item to "CALLER"
GETAIACTDATA(034);
if(!entity->IsMonster( )) return AI_SUCCESS;
CMonster* thisMonster = reinterpret_cast<CMonster*>(entity);
CMap* map = GServer->MapList.Index[thisMonster->Position->Map];
CPlayer* caller = map->GetPlayerInMap( thisMonster->owner);
if(caller == NULL) return AI_SUCCESS;
int nItem = data->nItemNum;
if (nItem == 0)return AI_SUCCESS;
char itemtypes[15] = {4,0,0,0,0,0,0,0,0,0,1,2,2,4,3};
CItem thisitem;
thisitem.Clear();
thisitem.itemtype = nItem / 1000;
thisitem.itemnum = nItem % 1000;
thisitem.count = data->nCount;
if( itemtypes[thisitem.itemtype] == 0 )
{
thisitem.appraised = true;
thisitem.lifespan = 100;
thisitem.gem = 0;
thisitem.durability = GServer->STB_ITEM[thisitem.itemtype-1].rows[thisitem.itemnum][29];
}
int thisslot = caller->AddItem( thisitem );
//Log(MSG_DEBUG,"Item type = %i item ID = %i Count = %i Slot = %i",thisitem.itemtype,thisitem.itemnum,thisitem.count,thisslot);
if (thisslot == 0xffff) // Fail
{
BEGINPACKET( pak, 0x7a7);
ADDWORD(pak, 0x00);
ADDBYTE(pak, 0x03);
ADDBYTE(pak, 0x00);
caller->client->SendPacket(&pak);
return AI_SUCCESS;
}
else // Success
{
BEGINPACKET( pak, 0x71f);
ADDBYTE(pak, 0x01);
ADDBYTE(pak, thisslot);
ADDWORD(pak, caller->items[thisslot].GetPakHeader( ) );
ADDDWORD(pak, caller->items[thisslot].GetPakData( ) );
caller->client->SendPacket(&pak);
}
//Log(MSG_DEBUG, "AIACT(034) Item (?)");
return AI_SUCCESS;
}
//Set Variable (4)
AIACT(035)
{
//Set AiVAR
//word nVarIDX; Pos: 0x00
//dword iValue; Pos: 0x04
//byte btOp; Pos: 0x08
GETAIACTDATA(035);
CMonster* thisMonster = reinterpret_cast<CMonster*>(entity);
if(data->nVarIDX >19)return AI_SUCCESS;
int tempval = thisMonster->AIVar[data->nVarIDX];
switch(data->btOp)
{
case 6:
tempval += data->iValue;
break;
case 7:
tempval -= data->iValue;
if(tempval <= 0)
{
thisMonster->AIVar[data->nVarIDX] = 0;
return AI_SUCCESS;
}
break;
case 9:
tempval++;
break;
default:
return AI_SUCCESS;
break;
}
thisMonster->AIVar[data->nVarIDX] = tempval;
//thisMonster->thisnpc->Objvar->SetVar(data->btOp, data->iValue); //code not working
return AI_SUCCESS;
}
//Monster (1)
AIACT(036)
{
//word nMonster; //Pos: 0x00
//byte btMaster; //Pos: 0x02
//Spawn nMonster at my position (has btMaster)?
GETAIACTDATA(036);
// CWorldEntity* master = NULL;
// if(data->btMaster) master = entity;
CMonster* monster = reinterpret_cast<CMonster*>(entity); //need this to get the spawnid of the entity
CMap* map = GServer->MapList.Index[entity->Position->Map];
int owner = 0;
if(data->btMaster == 1)
owner = monster->clientid;
map->AddMonster( data->nMonster, entity->Position->current, 0, monster->Status->spawnid, 10 );
//Log(MSG_DEBUG, "AIACT(36) monster (1) no code here");
return AI_SUCCESS;
}
//Monster (2)
AIACT(037)
{
//word nMonster; //Pos: 0x00
//word nPos; //Pos: 0x02
//dword iDistance; //Pos: 0x04
//byte btMaster; //Pos: 0x08
//Spawn nMonster at nPos within iDistance (has btMaster)?
//nPos = 0 my pos
//nPos = 1 m_pDestCHAR pos
//nPos = 2 target pos
GETAIACTDATA(037);
CMap* map = GServer->MapList.Index[entity->Position->Map];
CMonster* monster = reinterpret_cast<CMonster*>(entity); //need this to get the spawnid of the entity
if(monster == NULL) return AI_FAILURE;
int owner = 0;
if(data->btMaster == 1)
owner = monster->clientid;
fPoint position;
if(data->nPos == 0) //spawn monster in a circle with radius iDistance around my current position
{
position = GServer->RandInCircle( entity->Position->current, data->iDistance );
map->AddMonster( data->nMonster, position, 0, monster->Status->spawnid, 10 );
}
else if(data->nPos == 1) //spawn monster in a circle with radius iDistance around my destiny position
{
position = GServer->RandInCircle( entity->Position->destiny, data->iDistance );
map->AddMonster( data->nMonster, position, 0, monster->Status->spawnid, 10 );
//dest char
}
else if(data->nPos == 2) //spawn monster in a circle with radius iDistance around my target's current position
{
CCharacter* target = entity->GetCharTarget( );
if(target == NULL) return AI_FAILURE;
position = GServer->RandInCircle( target->Position->current, data->iDistance );
map->AddMonster( data->nMonster, position, 0, monster->Status->spawnid, 10 );
}
return AI_SUCCESS;
}
//TDEF code. move to next waypoint
AIACT(038)
{
//byte cSpeed pos 0x00
GETAIACTDATA(038);
CMonster* monster = reinterpret_cast<CMonster*>(entity);
monster->NextWayPoint ++;
monster->Position->destiny.x = GServer->WPList[monster->Position->Map][monster->NextWayPoint].X;
monster->Position->destiny.y = GServer->WPList[monster->Position->Map][monster->NextWayPoint].Y;
monster->Stats->stance = data->cSpeed;
entity->Position->lastMoveTime = clock();
BEGINPACKET( pak, 0x797 );
ADDWORD ( pak, entity->clientid );
ADDWORD ( pak, 0x0000 );
ADDWORD ( pak, entity->Stats->Move_Speed ); //speed
ADDFLOAT ( pak, monster->Position->destiny.x*100 );
ADDFLOAT ( pak, monster->Position->destiny.y*100 );
ADDWORD ( pak, 0xcdcd );
ADDBYTE ( pak, monster->Stats->stance );
GServer->SendToVisible(&pak, entity);
return AI_SUCCESS;
}