Modifikace MaNGOSe: Porovnání verzí

Z WoWResource Wiki
Přejít na: navigace, hledání
m
(spojení tématu "rýpeme se v mangosu")
 
Řádek 1: Řádek 1:
== Vytvoření vlastního příkazu ==  
+
== Přístup k databázi ==
Na vytvoření nového příkazu je nutné definovat ho na několika místech.
+
Pro práci s databází potřebujete přiložit hlavičkový soubor "Database/DatabaseEnv.h" ten vám následně umožní pracovat se všemi databázemi. Každé připojení do db je realizováno třídou DatabaseMysql/DatabasePostgre a jednotlivé proměnné jsou WorldDatabase, CharacterDatabase, loginDatabase.
# Soubor Chat.h musí obsahovat jeho hlavičku ve třídě ChatHandler s viditelností protected (někde mezi půlkou až koncem souboru). Návratová hodnota každého příkazu je bool a vstupními argumenty je const char*. Jako šablonu lze tedy vždy použít: '''bool Handle<NázevPříkazu>Command(const char *args);'''
+
Základní metody, které máte k dispozici jsou Query, Execute, jejiž parametrem je SQL řetězec. Execute slouží k vykonání dotazu, který nevrací výsledek, návratovou hodnotou je tedy bool - úspěch dotazu. Query pak vrací ukazatel na QueryResult, pokud je dotaz chybý nebo není vrácen žádný záznam, návratová hodnota je NULL. Pokud je navrácen QueryResult* je nutné po ukončení práce uvolnit alokovanou pamět operátorem delete.
# Je nutné vytvořit kód příkazu, vzhledem ke struktuře přístupových práv v MaNGOSu (rozdělených na 0,1,2,3) se příkazy řadí do souborů Level<0-3>.cpp, případně debugcmds.cpp (na umístění nezáleží, je nutné pouze, aby pro funkce které příkaz vyžaduje byly na začátku definovány odpovídající hlavičkové soubory, poté je možno tělo metody umístit na kterékoliv místo v těchto souborech). Návratová hodnota je true pokud vše proběhlo v pořádku, při návratu false je oznámen Syntax error.
+
Nad metodami Query a Execute jsou vystaveny metody PQuery a PExecute, které umožňují zadání dotazů ve formátu shodném jako je využití funkcí printf, sprintf.
# Zbývá nastavit na jaký .příkaz bude ve hře tento kód vykonán. Toto se nastavuje v souboru Chat.cpp v poli commandTable (nebo jiném pokud chcete zařadit do nějaké podkategorie příkazů nebo vytvořit vlastní). Řádek pak obsahuje: '''{ "příkazVeHře",     SEC_<nowiki><přístopová práva></nowiki>, <true/false>, &ChatHandler::Handle<NázevPříkazu>Command,      "", <NULL nebo název dalšího pole příkazů> },''' Prázdný řetězec slouží jako pole pro nápovědu, která je načtena z databáze. <True/false> udává, zda-li je příkaz možné vyvolat i z konzole mangose (při true), pokud příkaz není napsán tak, aby fungoval i z konzole může dojít ke crashi (pro zde uvedené příkazy dávejte false, pokud není uvedeno jinak). Nový příkaz bude fungovat i bez záznamu v db v tabulce commands, ale nebude obsahovat nápovědu. (na konci každého pole je nutné ponechat null-ový záznam, vlastní řádek s definicí příkazu je možno vložit kamkoliv do požadovaného pole příkazů)
+
 
 +
Příklad:
 +
uint32 limit = 100;
 +
 +
// ukázka PQuery
 +
QueryResult* result = WorldDatabase.PQuery("SELECT name, level FROM characters WHERE money > %u", limit);
 +
if (!result) // pokud výsledek neobsahuje žádný záznam
 +
    return;
 +
 +
do {
 +
    // získáme data z řádku
 +
    Field* fields = result->Fetch();
 +
 +
    printf("Hrac %s ma level %u \n",  
 +
        fields[0].GetString(),
 +
        fields[1].GetUInt32());
 +
 +
} while (result->NextRow());
 +
delete result;
 +
 
 +
== Pracujeme s packety ==
 +
Práce s packety může být pro určité problémy velmi vhodná, ale je nutné brát v potaz, že takovéto změny při změně patche klienta je nutné zkontrolovat, jestli nedošlo ke změně ve struktuře packetu. Nebudu zde podrobně rozepisovat, jak funguje komunikace wow server - klient. Pro nás je důležité, že každý packet má identifikátor (opcode - číslo), který určuje jeho použití. Všechna data uvnitř packetu jsou následně naházena za sebe, pokud tedy chceme využít nějaký packet je nejdříve nutné si najít konstantu označující daný opcode a prohledat zdrojové kódy, abychom zjistili strukturu packetu (nebo sniffovat komunikaci). Vlastní vytvoření packetu je poté vytvoření instance třídy WorldPacket (není potřeba dynamická alokace paměti). WorldPacket je vystaven na ByteBuffer-u a pro naplnění daty (i extrakci) stačí využít přetížené operátory << (vložení dat) a >> (vybrání dat). Vlastní odeslání pak konkrétnímu hráči zařídí metoda WorldSession::SendPacket (pokud pracujete s objektem Player, session lze získat pomocí GetSession). Pokud potřebuje odeslat packet hráčům v blízkém okolí využijte SendMessageToSet/SendMessageToSetInRange. Celosvětově to pak lze zařídit pomocí World::SendGlobalMessage nebo SendZoneMessage.
 +
 
 +
//              opcode,              rezervovaná velikost paměti (urychluje zpracování)
 +
WorldPacket data(SMSG_SUMMON_REQUEST, 8+4+4);
 +
 +
// při vkládání dat je nutné dbát na správný datový typ!
 +
data << uint64(m_caster->GetGUID());   
 +
data << uint32(m_caster->GetZoneId());
 +
data << uint32(MAX_PLAYER_SUMMON_DELAY*IN_MILISECONDS);
 +
 +
// send packet vyžaduje ukazatel
 +
  // data jsou zde odeslána jen konkrétnímu hráči
 +
  player->GetSession()->SendPacket(&data);
 +
 
 +
== Když hráč umře, vyhraje duel,... ==
 +
Hodně lidí chce dát předmět, peníze, honory a další po některé z těchto událostí, ale neví kde co hledat.
 +
 
 +
* Zabití hráče: Unit.cpp - Unit::DealDamage
 +
* Vyhraný duel: Player.cpp - Player::DuelComplete
 +
* Po přihlášení do hry: CharacterHandler.cpp - WorldSession::HandlePlayerLogin
 +
* Napsání zprávy do chatu (libovolný kanál/způsob): ChatHandler.cpp - WorldSession::HandleMessagechatOpcode
 +
 
  
== Když hráč zabije hráče ==
+
=== Když hráč zabije hráče ===
 
Pro možnost přidání peněz nebo předmětu za zabití hráče. Otevřete soubor Unit.cpp a najděte metodu DealDamage(). Poté nalezněte podmínku '''if (health <= damage)''' a blok pod touto podmínkou zpracovává zabití nepřítele. Pro ověření, že hráč zabil hráče stačí jednoduchá podmínka '''if(GetTypeId() == TYPEID_PLAYER && pVictim->GetTypeId() == TYPEID_PLAYER)'''. Pokud chcete ověřit i zda-li hráč byl z druhé frakce přidejte podmínku '''((Player*)this)->GetTeam() != ((Player*)pVictim)->GetTeam()''', případně přidat vlastní další podmínky (ověření levelu, opakovaného zabíjení,...)
 
Pro možnost přidání peněz nebo předmětu za zabití hráče. Otevřete soubor Unit.cpp a najděte metodu DealDamage(). Poté nalezněte podmínku '''if (health <= damage)''' a blok pod touto podmínkou zpracovává zabití nepřítele. Pro ověření, že hráč zabil hráče stačí jednoduchá podmínka '''if(GetTypeId() == TYPEID_PLAYER && pVictim->GetTypeId() == TYPEID_PLAYER)'''. Pokud chcete ověřit i zda-li hráč byl z druhé frakce přidejte podmínku '''((Player*)this)->GetTeam() != ((Player*)pVictim)->GetTeam()''', případně přidat vlastní další podmínky (ověření levelu, opakovaného zabíjení,...)
  
Řádek 29: Řádek 71:
 
     }
 
     }
 
  }
 
  }
 +
 +
== Vytvoření vlastního příkazu ==
 +
Na vytvoření nového příkazu je nutné definovat ho na několika místech.
 +
# Soubor Chat.h musí obsahovat jeho hlavičku ve třídě ChatHandler s viditelností protected (někde mezi půlkou až koncem souboru). Návratová hodnota každého příkazu je bool a vstupními argumenty je const char*. Jako šablonu lze tedy vždy použít: '''bool Handle<NázevPříkazu>Command(const char *args);'''
 +
# Je nutné vytvořit kód příkazu, vzhledem ke struktuře přístupových práv v MaNGOSu (rozdělených na 0,1,2,3) se příkazy řadí do souborů Level<0-3>.cpp, případně debugcmds.cpp (na umístění nezáleží, je nutné pouze, aby pro funkce které příkaz vyžaduje byly na začátku definovány odpovídající hlavičkové soubory, poté je možno tělo metody umístit na kterékoliv místo v těchto souborech). Návratová hodnota je true pokud vše proběhlo v pořádku, při návratu false je oznámen Syntax error.
 +
# Zbývá nastavit na jaký .příkaz bude ve hře tento kód vykonán. Toto se nastavuje v souboru Chat.cpp v poli commandTable (nebo jiném pokud chcete zařadit do nějaké podkategorie příkazů nebo vytvořit vlastní). Řádek pak obsahuje: '''{ "příkazVeHře",      SEC_<nowiki><přístopová práva></nowiki>,  <true/false>,  &ChatHandler::Handle<NázevPříkazu>Command,      "", <NULL nebo název dalšího pole příkazů> },''' Prázdný řetězec slouží jako pole pro nápovědu, která je načtena z databáze. <True/false> udává, zda-li je příkaz možné vyvolat i z konzole mangose (při true), pokud příkaz není napsán tak, aby fungoval i z konzole může dojít ke crashi (pro zde uvedené příkazy dávejte false, pokud není uvedeno jinak). Nový příkaz bude fungovat i bez záznamu v db v tabulce commands, ale nebude obsahovat nápovědu. (na konci každého pole je nutné ponechat null-ový záznam, vlastní řádek s definicí příkazu je možno vložit kamkoliv do požadovaného pole příkazů)
  
 
== Příkazy ==
 
== Příkazy ==

Aktuální verze z 17. 3. 2010, 20:44

Přístup k databázi

Pro práci s databází potřebujete přiložit hlavičkový soubor "Database/DatabaseEnv.h" ten vám následně umožní pracovat se všemi databázemi. Každé připojení do db je realizováno třídou DatabaseMysql/DatabasePostgre a jednotlivé proměnné jsou WorldDatabase, CharacterDatabase, loginDatabase. Základní metody, které máte k dispozici jsou Query, Execute, jejiž parametrem je SQL řetězec. Execute slouží k vykonání dotazu, který nevrací výsledek, návratovou hodnotou je tedy bool - úspěch dotazu. Query pak vrací ukazatel na QueryResult, pokud je dotaz chybý nebo není vrácen žádný záznam, návratová hodnota je NULL. Pokud je navrácen QueryResult* je nutné po ukončení práce uvolnit alokovanou pamět operátorem delete. Nad metodami Query a Execute jsou vystaveny metody PQuery a PExecute, které umožňují zadání dotazů ve formátu shodném jako je využití funkcí printf, sprintf.

Příklad:

uint32 limit = 100; 

// ukázka PQuery
QueryResult* result = WorldDatabase.PQuery("SELECT name, level FROM characters WHERE money > %u", limit);
if (!result) // pokud výsledek neobsahuje žádný záznam
    return;

do {
    // získáme data z řádku
    Field* fields = result->Fetch();

    printf("Hrac %s ma level %u \n", 
        fields[0].GetString(),
        fields[1].GetUInt32()); 

} while (result->NextRow());
delete result;

Pracujeme s packety

Práce s packety může být pro určité problémy velmi vhodná, ale je nutné brát v potaz, že takovéto změny při změně patche klienta je nutné zkontrolovat, jestli nedošlo ke změně ve struktuře packetu. Nebudu zde podrobně rozepisovat, jak funguje komunikace wow server - klient. Pro nás je důležité, že každý packet má identifikátor (opcode - číslo), který určuje jeho použití. Všechna data uvnitř packetu jsou následně naházena za sebe, pokud tedy chceme využít nějaký packet je nejdříve nutné si najít konstantu označující daný opcode a prohledat zdrojové kódy, abychom zjistili strukturu packetu (nebo sniffovat komunikaci). Vlastní vytvoření packetu je poté vytvoření instance třídy WorldPacket (není potřeba dynamická alokace paměti). WorldPacket je vystaven na ByteBuffer-u a pro naplnění daty (i extrakci) stačí využít přetížené operátory << (vložení dat) a >> (vybrání dat). Vlastní odeslání pak konkrétnímu hráči zařídí metoda WorldSession::SendPacket (pokud pracujete s objektem Player, session lze získat pomocí GetSession). Pokud potřebuje odeslat packet hráčům v blízkém okolí využijte SendMessageToSet/SendMessageToSetInRange. Celosvětově to pak lze zařídit pomocí World::SendGlobalMessage nebo SendZoneMessage.

//               opcode,              rezervovaná velikost paměti (urychluje zpracování)
WorldPacket data(SMSG_SUMMON_REQUEST, 8+4+4);

// při vkládání dat je nutné dbát na správný datový typ!
data << uint64(m_caster->GetGUID());     
data << uint32(m_caster->GetZoneId());
data << uint32(MAX_PLAYER_SUMMON_DELAY*IN_MILISECONDS);

// send packet vyžaduje ukazatel
// data jsou zde odeslána jen konkrétnímu hráči
player->GetSession()->SendPacket(&data);

Když hráč umře, vyhraje duel,...

Hodně lidí chce dát předmět, peníze, honory a další po některé z těchto událostí, ale neví kde co hledat.

  • Zabití hráče: Unit.cpp - Unit::DealDamage
  • Vyhraný duel: Player.cpp - Player::DuelComplete
  • Po přihlášení do hry: CharacterHandler.cpp - WorldSession::HandlePlayerLogin
  • Napsání zprávy do chatu (libovolný kanál/způsob): ChatHandler.cpp - WorldSession::HandleMessagechatOpcode


Když hráč zabije hráče

Pro možnost přidání peněz nebo předmětu za zabití hráče. Otevřete soubor Unit.cpp a najděte metodu DealDamage(). Poté nalezněte podmínku if (health <= damage) a blok pod touto podmínkou zpracovává zabití nepřítele. Pro ověření, že hráč zabil hráče stačí jednoduchá podmínka if(GetTypeId() == TYPEID_PLAYER && pVictim->GetTypeId() == TYPEID_PLAYER). Pokud chcete ověřit i zda-li hráč byl z druhé frakce přidejte podmínku ((Player*)this)->GetTeam() != ((Player*)pVictim)->GetTeam(), případně přidat vlastní další podmínky (ověření levelu, opakovaného zabíjení,...)

  • Pro udělení pěnez stačí požít funkci Player::ModifyMoney(<o kolik>);
  • Pro přidání itemu lze použít:
#define LOOT_ITEM_ID 9014555 /* entry předmětu */
#define LOOT_ITEM_COUNT 1 /* počet předmětů, které obdrží */

if(GetTypeId() == TYPEID_PLAYER && pVictim->GetTypeId() == TYPEID_PLAYER && ((Player*)this)->GetTeam() != ((Player*)pVictim)->GetTeam())
{
    Player *me = (Player*)this;
    ItemPosCountVec dest;
    uint32 no_space_count = 0, count = LOOT_ITEM_COUNT;
    uint8 msg = me->CanStoreNewItem( NULL_BAG, NULL_SLOT, dest, LOOT_ITEM_ID, count, &no_space_count);
    if( msg != EQUIP_ERR_OK )
        count -= no_space_count;
   
    if (count != 0 && !dest.empty())
    {
        Item* item = me->StoreNewItem( dest, LOOT_ITEM_ID, true, Item::GenerateItemRandomPropertyId(LOOT_ITEM_ID));
        if(item)
            me->SendNewItem(item,count,false,false);
    }
}

Vytvoření vlastního příkazu

Na vytvoření nového příkazu je nutné definovat ho na několika místech.

  1. Soubor Chat.h musí obsahovat jeho hlavičku ve třídě ChatHandler s viditelností protected (někde mezi půlkou až koncem souboru). Návratová hodnota každého příkazu je bool a vstupními argumenty je const char*. Jako šablonu lze tedy vždy použít: bool Handle<NázevPříkazu>Command(const char *args);
  2. Je nutné vytvořit kód příkazu, vzhledem ke struktuře přístupových práv v MaNGOSu (rozdělených na 0,1,2,3) se příkazy řadí do souborů Level<0-3>.cpp, případně debugcmds.cpp (na umístění nezáleží, je nutné pouze, aby pro funkce které příkaz vyžaduje byly na začátku definovány odpovídající hlavičkové soubory, poté je možno tělo metody umístit na kterékoliv místo v těchto souborech). Návratová hodnota je true pokud vše proběhlo v pořádku, při návratu false je oznámen Syntax error.
  3. Zbývá nastavit na jaký .příkaz bude ve hře tento kód vykonán. Toto se nastavuje v souboru Chat.cpp v poli commandTable (nebo jiném pokud chcete zařadit do nějaké podkategorie příkazů nebo vytvořit vlastní). Řádek pak obsahuje: { "příkazVeHře", SEC_<přístopová práva>, <true/false>, &ChatHandler::Handle<NázevPříkazu>Command, "", <NULL nebo název dalšího pole příkazů> }, Prázdný řetězec slouží jako pole pro nápovědu, která je načtena z databáze. <True/false> udává, zda-li je příkaz možné vyvolat i z konzole mangose (při true), pokud příkaz není napsán tak, aby fungoval i z konzole může dojít ke crashi (pro zde uvedené příkazy dávejte false, pokud není uvedeno jinak). Nový příkaz bude fungovat i bez záznamu v db v tabulce commands, ale nebude obsahovat nápovědu. (na konci každého pole je nutné ponechat null-ový záznam, vlastní řádek s definicí příkazu je možno vložit kamkoliv do požadovaného pole příkazů)

Příkazy

Připojení do fronty na battleground

Pro MaNGOS bez Arena patche

Hlavička Chat.h:

bool HandleBgCommand(const char* args);

Chat.cpp:

{ "bg",      SEC_PLAYER,   false,   &ChatHandler::HandleBgCommand,       "", NULL },

Level0.cpp:

// na začátku souboru musí být hlavička:
#include "BattleGround.h"

// tělo příkazu - umístit někam do souboru (např. na konec za vše ostatní)
bool ChatHandler::HandleBgCommand(const char* args)
{
    if (!*args)
        return false;

    uint8 bgTypeId = 0;

    if (stricmp(args, "wsg") == 0) { bgTypeId = 2; }
    else if (stricmp(args, "ab" ) == 0) { bgTypeId = 3; }
    else if (stricmp(args, "eye") == 0) { bgTypeId = 7; }

    if (bgTypeId >= MAX_BATTLEGROUND_TYPES || !bgTypeId)
        return false;

    Player* me = m_session->GetPlayer();

    if (me->InBattleGround() || !me->CanJoinToBattleground())
        return false;

    BattleGround *bg = sBattleGroundMgr.GetBattleGround(bgTypeId);
    if (!bg)
        return false;

    if (me->InBattleGroundQueueForBattleGroundType(bgTypeId))
        return false;

    uint32 queueSlot = me->AddBattleGroundQueueId(bgTypeId);
    if (queueSlot == PLAYER_MAX_BATTLEGROUND_QUEUES)
        return false;

    me->SetBattleGroundEntryPoint(me->GetMapId(), me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), me->GetOrientation());

    WorldPacket data;
    sBattleGroundMgr.BuildBattleGroundStatusPacket(&data, bg, me->GetTeam(), queueSlot, STATUS_WAIT_QUEUE, 0, 0);
    m_session->SendPacket(&data);

    sBattleGroundMgr.m_BattleGroundQueues[bgTypeId].AddPlayer(me, bgTypeId);
    return true;
}

Pouštění hudby všem hráčům

V souboru debugcmds.cpp najděte metodu ChatHandler::HandlePlaySound2Command a uvnitř nahraďte

    m_session->GetPlayer()->PlaySound(soundid, false);

za

    WorldPacket data(SMSG_PLAY_SOUND, 4);
    data << soundid;
    
    sWorld.SendGlobalMessage(&data);

Poté ve hře můžete pustit všem hudbu pomocí příkazu .debug ps <id hudby>


Portnutí všech hráčů za GM

Hráč může portnutí odmítnout, ti kdo přijmou budou portnuti na GM, které příkaz použilo +- 1yard do okolí a 2y nad (aby nedošlo k propadnutí pod zem)

bool ChatHandler::HandlePortAllToMe(const char* args)
{
    HashMapHolder<Player>::MapType map = ObjectAccessor::Instance().GetPlayers();
    Player* me = m_session->GetPlayer();

    float x,y,z;
    me->GetPosition(x, y, z); // resp. GetClosePoint() to, ale nenajde gameobjekty ve vzduchu.
    z += 2.0f;
    WorldPacket data(SMSG_SUMMON_REQUEST, 8+4+4);
    data << uint64(me->GetGUID());                    // summoner guid
    data << uint32(me->GetZoneId());                  // summoner zone
    data << uint32(MAX_PLAYER_SUMMON_DELAY*1000);     // auto decline after msecs

    for(HashMapHolder<Player>::MapType::iterator it = map.begin(); it != map.end(); it++)
    {
        if (it->second == me)
            continue;

        it->second->SetSummonPoint(me->GetMapId(), x + (rand_norm()*2-1), y + (rand_norm()*2-1), z);
        it->second->GetSession()->SendPacket(&data);
    }
    return true;
}