mirror of
https://github.com/mangosfour/server.git
synced 2025-12-13 22:37:03 +00:00
More work on pet spells, typo fix
This commit is contained in:
parent
7adf2001f3
commit
d48844a699
9 changed files with 146 additions and 123 deletions
|
|
@ -892,6 +892,8 @@ void WorldSession::HandleUpdateAccountData(WorldPacket &recv_data)
|
|||
uint32 type, timestamp, decompressedSize;
|
||||
recv_data >> type >> timestamp >> decompressedSize;
|
||||
|
||||
sLog.outDebug("UAD: type %u, time %u, decompressedSize %u", type, timestamp, decompressedSize);
|
||||
|
||||
if(type > NUM_ACCOUNT_DATA_TYPES)
|
||||
return;
|
||||
|
||||
|
|
@ -943,6 +945,8 @@ void WorldSession::HandleRequestAccountData(WorldPacket& recv_data)
|
|||
uint32 type;
|
||||
recv_data >> type;
|
||||
|
||||
sLog.outDebug("RAD: type %u", type);
|
||||
|
||||
if(type > NUM_ACCOUNT_DATA_TYPES)
|
||||
return;
|
||||
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@ Pet::Pet(PetType type) : Creature()
|
|||
|
||||
m_auraUpdateMask = 0;
|
||||
|
||||
m_loading = false;
|
||||
|
||||
// pets always have a charminfo, even if they are not actually charmed
|
||||
CharmInfo* charmInfo = InitCharmInfo(this);
|
||||
|
||||
|
|
@ -101,6 +103,8 @@ void Pet::RemoveFromWorld()
|
|||
|
||||
bool Pet::LoadPetFromDB( Unit* owner, uint32 petentry, uint32 petnumber, bool current )
|
||||
{
|
||||
m_loading = true;
|
||||
|
||||
uint32 ownerid = owner->GetGUIDLow();
|
||||
|
||||
QueryResult *result;
|
||||
|
|
@ -341,6 +345,7 @@ bool Pet::LoadPetFromDB( Unit* owner, uint32 petentry, uint32 petnumber, bool cu
|
|||
}
|
||||
}
|
||||
|
||||
m_loading = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -1277,7 +1282,8 @@ bool Pet::addSpell(uint16 spell_id, uint16 active, PetSpellState state, uint16 s
|
|||
ToggleAutocast(itr->first, false);
|
||||
|
||||
oldspell_id = itr->first;
|
||||
removeSpell(itr->first);
|
||||
//removeSpell(itr->first);
|
||||
unlearnSpell(itr->first);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -1299,16 +1305,6 @@ bool Pet::addSpell(uint16 spell_id, uint16 active, PetSpellState state, uint16 s
|
|||
newspell->slotId = tmpslot;
|
||||
m_spells[spell_id] = newspell;
|
||||
|
||||
if(GetOwner()->GetTypeId() == TYPEID_PLAYER)
|
||||
{
|
||||
if(!IsPassiveSpell(spell_id))
|
||||
{
|
||||
WorldPacket data(SMSG_PET_LEARNED_SPELL, 2);
|
||||
data << uint16(spell_id);
|
||||
((Player*)GetOwner())->GetSession()->SendPacket(&data);
|
||||
}
|
||||
}
|
||||
|
||||
if (IsPassiveSpell(spell_id))
|
||||
CastSpell(this, spell_id, true);
|
||||
else if(state == PETSPELL_NEW)
|
||||
|
|
@ -1326,6 +1322,16 @@ bool Pet::learnSpell(uint16 spell_id)
|
|||
if (!addSpell(spell_id))
|
||||
return false;
|
||||
|
||||
if(GetOwner()->GetTypeId() == TYPEID_PLAYER)
|
||||
{
|
||||
if(!m_loading)
|
||||
{
|
||||
WorldPacket data(SMSG_PET_LEARNED_SPELL, 2);
|
||||
data << uint16(spell_id);
|
||||
((Player*)GetOwner())->GetSession()->SendPacket(&data);
|
||||
}
|
||||
}
|
||||
|
||||
Unit* owner = GetOwner();
|
||||
if(owner->GetTypeId() == TYPEID_PLAYER)
|
||||
((Player*)owner)->PetSpellInitialize();
|
||||
|
|
@ -1343,18 +1349,36 @@ void Pet::learnLevelupSpells()
|
|||
if(itr->ReqLevel <= getLevel())
|
||||
learnSpell(itr->SpellId);
|
||||
else
|
||||
removeSpell(itr->SpellId);
|
||||
unlearnSpell(itr->SpellId);
|
||||
}
|
||||
}
|
||||
|
||||
void Pet::removeSpell(uint16 spell_id)
|
||||
bool Pet::unlearnSpell(uint16 spell_id)
|
||||
{
|
||||
if(removeSpell(spell_id))
|
||||
{
|
||||
if(GetOwner()->GetTypeId() == TYPEID_PLAYER)
|
||||
{
|
||||
if(!m_loading)
|
||||
{
|
||||
WorldPacket data(SMSG_PET_UNLEARNED_SPELL, 2);
|
||||
data << uint16(spell_id);
|
||||
((Player*)GetOwner())->GetSession()->SendPacket(&data);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Pet::removeSpell(uint16 spell_id)
|
||||
{
|
||||
PetSpellMap::iterator itr = m_spells.find(spell_id);
|
||||
if (itr == m_spells.end())
|
||||
return;
|
||||
return false;
|
||||
|
||||
if(itr->second->state == PETSPELL_REMOVED)
|
||||
return;
|
||||
return false;
|
||||
|
||||
if(itr->second->state == PETSPELL_NEW)
|
||||
{
|
||||
|
|
@ -1366,15 +1390,7 @@ void Pet::removeSpell(uint16 spell_id)
|
|||
|
||||
RemoveAurasDueToSpell(spell_id);
|
||||
|
||||
if(GetOwner()->GetTypeId() == TYPEID_PLAYER)
|
||||
{
|
||||
if(!IsPassiveSpell(spell_id))
|
||||
{
|
||||
WorldPacket data(SMSG_PET_UNLEARNED_SPELL, 2);
|
||||
data << uint16(spell_id);
|
||||
((Player*)GetOwner())->GetSession()->SendPacket(&data);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Pet::_removeSpell(uint16 spell_id)
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ struct PetSpell
|
|||
{
|
||||
uint16 slotId;
|
||||
uint16 active;
|
||||
|
||||
PetSpellState state : 16;
|
||||
PetSpellType type : 16;
|
||||
};
|
||||
|
|
@ -191,7 +192,8 @@ class Pet : public Creature
|
|||
bool addSpell(uint16 spell_id,uint16 active = ACT_DECIDE, PetSpellState state = PETSPELL_NEW, uint16 slot_id=0xffff, PetSpellType type = PETSPELL_NORMAL);
|
||||
bool learnSpell(uint16 spell_id);
|
||||
void learnLevelupSpells();
|
||||
void removeSpell(uint16 spell_id);
|
||||
bool unlearnSpell(uint16 spell_id);
|
||||
bool removeSpell(uint16 spell_id);
|
||||
bool _removeSpell(uint16 spell_id);
|
||||
|
||||
PetSpellMap m_spells;
|
||||
|
|
@ -222,6 +224,7 @@ class Pet : public Creature
|
|||
int32 m_duration; // time until unsummon (used mostly for summoned guardians and not used for controlled pets)
|
||||
int32 m_bonusdamage;
|
||||
uint64 m_auraUpdateMask;
|
||||
bool m_loading;
|
||||
|
||||
DeclinedName *m_declinedname;
|
||||
|
||||
|
|
|
|||
|
|
@ -155,7 +155,6 @@ void WorldSession::HandlePetAction( WorldPacket & recv_data )
|
|||
}
|
||||
break;
|
||||
case ACT_DISABLED: //0x8100 spell (disabled), ignore
|
||||
case ACT_CAST: //0x0100
|
||||
case ACT_ENABLED: //0xc100 spell
|
||||
{
|
||||
Unit* unit_target;
|
||||
|
|
@ -349,7 +348,7 @@ void WorldSession::HandlePetSetAction( WorldPacket & recv_data )
|
|||
sLog.outDetail( "Player %s has changed pet spell action. Position: %u, Spell: %u, State: 0x%X\n", _player->GetName(), position, spell_id, act_state);
|
||||
|
||||
//if it's act for spell (en/disable/cast) and there is a spell given (0 = remove spell) which pet doesn't know, don't add
|
||||
if(!((act_state == ACT_ENABLED || act_state == ACT_DISABLED || act_state == ACT_CAST) && spell_id && !pet->HasSpell(spell_id)))
|
||||
if(!((act_state == ACT_ENABLED || act_state == ACT_DISABLED) && spell_id && !pet->HasSpell(spell_id)))
|
||||
{
|
||||
//sign for autocast
|
||||
if(act_state == ACT_ENABLED && spell_id)
|
||||
|
|
@ -519,7 +518,8 @@ void WorldSession::HandlePetUnlearnOpcode(WorldPacket& recvPacket)
|
|||
{
|
||||
uint32 spell_id = itr->first; // Pet::removeSpell can invalidate iterator at erase NEW spell
|
||||
++itr;
|
||||
pet->removeSpell(spell_id);
|
||||
//pet->removeSpell(spell_id);
|
||||
pet->unlearnSpell(spell_id);
|
||||
}
|
||||
|
||||
for(uint8 i = 0; i < 10; i++)
|
||||
|
|
|
|||
|
|
@ -2341,7 +2341,6 @@ void Player::SendInitialSpells()
|
|||
continue;
|
||||
|
||||
data << uint16(itr->first);
|
||||
//data << uint16(itr->second->slotId);
|
||||
data << uint16(0); // it's not slot id
|
||||
|
||||
spellCount +=1;
|
||||
|
|
@ -2368,13 +2367,13 @@ void Player::SendInitialSpells()
|
|||
data << uint16(sEntry->Category); // spell category
|
||||
if(sEntry->Category) // may be wrong, but anyway better than nothing...
|
||||
{
|
||||
data << uint32(0);
|
||||
data << uint32(cooldown);
|
||||
data << uint32(0); // cooldown
|
||||
data << uint32(cooldown); // category cooldown
|
||||
}
|
||||
else
|
||||
{
|
||||
data << uint32(cooldown);
|
||||
data << uint32(0);
|
||||
data << uint32(cooldown); // cooldown
|
||||
data << uint32(0); // category cooldown
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -16002,68 +16001,67 @@ void Player::PetSpellInitialize()
|
|||
{
|
||||
Pet* pet = GetPet();
|
||||
|
||||
if(pet)
|
||||
if(!pet)
|
||||
return;
|
||||
|
||||
sLog.outDebug("Pet Spells Groups");
|
||||
|
||||
CharmInfo *charmInfo = pet->GetCharmInfo();
|
||||
|
||||
WorldPacket data(SMSG_PET_SPELLS, 8+4+4+4+10*4);
|
||||
data << uint64(pet->GetGUID());
|
||||
data << uint32(pet->GetCreatureInfo()->family); // creature family (required for pet talents)
|
||||
data << uint32(0);
|
||||
data << uint8(charmInfo->GetReactState()) << uint8(charmInfo->GetCommandState()) << uint16(0);
|
||||
|
||||
// action bar loop
|
||||
for(uint32 i = 0; i < 10; i++)
|
||||
{
|
||||
uint8 addlist = 0;
|
||||
|
||||
sLog.outDebug("Pet Spells Groups");
|
||||
|
||||
CreatureInfo const *cinfo = pet->GetCreatureInfo();
|
||||
|
||||
if(pet->isControlled() && (pet->getPetType() == HUNTER_PET || cinfo && cinfo->type == CREATURE_TYPE_DEMON && getClass() == CLASS_WARLOCK))
|
||||
{
|
||||
for(PetSpellMap::iterator itr = pet->m_spells.begin();itr != pet->m_spells.end();itr++)
|
||||
{
|
||||
if(itr->second->state == PETSPELL_REMOVED)
|
||||
continue;
|
||||
++addlist;
|
||||
}
|
||||
}
|
||||
|
||||
// first line + actionbar + spellcount + spells + last adds
|
||||
WorldPacket data(SMSG_PET_SPELLS, 16+40+1+4*addlist+25);
|
||||
|
||||
CharmInfo *charmInfo = pet->GetCharmInfo();
|
||||
|
||||
//16
|
||||
data << uint64(pet->GetGUID());
|
||||
data << uint32(pet->GetCreatureInfo()->family); // creature family (required for pet talents)
|
||||
data << uint8(charmInfo->GetReactState()) << uint8(charmInfo->GetCommandState()) << uint16(0);
|
||||
data << uint32(0);
|
||||
|
||||
for(uint32 i = 0; i < 10; i++) //40
|
||||
{
|
||||
data << uint16(charmInfo->GetActionBarEntry(i)->SpellOrAction) << uint16(charmInfo->GetActionBarEntry(i)->Type);
|
||||
}
|
||||
|
||||
data << uint8(addlist); //1
|
||||
|
||||
if(addlist && pet->isControlled())
|
||||
{
|
||||
for (PetSpellMap::iterator itr = pet->m_spells.begin(); itr != pet->m_spells.end(); ++itr)
|
||||
{
|
||||
if(itr->second->state == PETSPELL_REMOVED)
|
||||
continue;
|
||||
|
||||
data << uint16(itr->first);
|
||||
data << uint16(itr->second->active); // pet spell active state isn't boolean
|
||||
}
|
||||
}
|
||||
|
||||
//data << uint8(0x01) << uint32(0x6010) << uint32(0x01) << uint32(0x05) << uint16(0x00); //15
|
||||
uint8 count = 3; //1+8+8+8=25
|
||||
|
||||
// if count = 0, then end of packet...
|
||||
data << count;
|
||||
// uint32 value is spell id...
|
||||
// uint64 value is constant 0, unknown...
|
||||
data << uint32(0x6010) << uint64(0); // if count = 1, 2 or 3
|
||||
//data << uint32(0x5fd1) << uint64(0); // if count = 2
|
||||
data << uint32(0x8e8c) << uint64(0); // if count = 3
|
||||
data << uint32(0x8e8b) << uint64(0); // if count = 3
|
||||
|
||||
GetSession()->SendPacket(&data);
|
||||
data << uint32(charmInfo->GetActionBarEntry(i)->Raw);
|
||||
}
|
||||
|
||||
size_t spellsCountPos = data.wpos();
|
||||
|
||||
// spells count
|
||||
uint8 addlist = 0;
|
||||
data << uint8(addlist); // placeholder
|
||||
|
||||
if(pet->isControlled() && ((pet->getPetType() == HUNTER_PET) || ((pet->GetCreatureInfo()->type == CREATURE_TYPE_DEMON) && (getClass() == CLASS_WARLOCK))))
|
||||
{
|
||||
// spells loop
|
||||
for (PetSpellMap::iterator itr = pet->m_spells.begin(); itr != pet->m_spells.end(); ++itr)
|
||||
{
|
||||
if(itr->second->state == PETSPELL_REMOVED)
|
||||
continue;
|
||||
|
||||
data << uint16(itr->first);
|
||||
data << uint16(itr->second->active); // pet spell active state isn't boolean
|
||||
++addlist;
|
||||
}
|
||||
}
|
||||
|
||||
data.put<uint8>(spellsCountPos, addlist);
|
||||
|
||||
uint8 cooldownsCount = pet->m_CreatureSpellCooldowns.size() + pet->m_CreatureCategoryCooldowns.size();
|
||||
data << uint8(cooldownsCount);
|
||||
|
||||
for(CreatureSpellCooldowns::const_iterator itr = pet->m_CreatureSpellCooldowns.begin(); itr != pet->m_CreatureSpellCooldowns.end(); ++itr)
|
||||
{
|
||||
data << uint16(itr->first); // spellid
|
||||
data << uint16(0); // unk
|
||||
data << uint32(itr->second); // cooldown
|
||||
data << uint32(0); // category cooldown
|
||||
}
|
||||
|
||||
for(CreatureSpellCooldowns::const_iterator itr = pet->m_CreatureCategoryCooldowns.begin(); itr != pet->m_CreatureCategoryCooldowns.end(); ++itr)
|
||||
{
|
||||
data << uint16(itr->first); // spellid
|
||||
data << uint16(0); // unk
|
||||
data << uint32(0); // cooldown
|
||||
data << uint32(itr->second); // category cooldown
|
||||
}
|
||||
|
||||
GetSession()->SendPacket(&data);
|
||||
}
|
||||
|
||||
void Player::PossessSpellInitialize()
|
||||
|
|
@ -16087,8 +16085,8 @@ void Player::PossessSpellInitialize()
|
|||
//16
|
||||
data << uint64(charm->GetGUID());
|
||||
data << uint32(0x00000000);
|
||||
data << uint8(0) << uint8(0) << uint16(0);
|
||||
data << uint32(0);
|
||||
data << uint8(0) << uint8(0) << uint16(0);
|
||||
|
||||
for(uint32 i = 0; i < 10; i++) //40
|
||||
{
|
||||
|
|
@ -16097,11 +16095,8 @@ void Player::PossessSpellInitialize()
|
|||
|
||||
data << uint8(addlist); //1
|
||||
|
||||
uint8 count = 3;
|
||||
data << count;
|
||||
data << uint32(0x6010) << uint64(0); // if count = 1, 2 or 3
|
||||
data << uint32(0x8e8c) << uint64(0); // if count = 3
|
||||
data << uint32(0x8e8b) << uint64(0); // if count = 3
|
||||
uint8 count = 0;
|
||||
data << uint8(count); // cooldowns count
|
||||
|
||||
GetSession()->SendPacket(&data);
|
||||
}
|
||||
|
|
@ -16138,15 +16133,14 @@ void Player::CharmSpellInitialize()
|
|||
|
||||
WorldPacket data(SMSG_PET_SPELLS, 16+40+1+4*addlist+25);// first line + actionbar + spellcount + spells + last adds
|
||||
|
||||
data << (uint64)charm->GetGUID() << uint32(0x00000000);
|
||||
|
||||
data << uint64(charm->GetGUID());
|
||||
data << uint32(0x00000000);
|
||||
data << uint32(0);
|
||||
if(charm->GetTypeId() != TYPEID_PLAYER)
|
||||
data << uint8(charmInfo->GetReactState()) << uint8(charmInfo->GetCommandState());
|
||||
else
|
||||
data << uint8(0) << uint8(0);
|
||||
|
||||
data << uint16(0);
|
||||
data << uint32(0);
|
||||
|
||||
for(uint32 i = 0; i < 10; i++) //40
|
||||
{
|
||||
|
|
@ -16168,11 +16162,8 @@ void Player::CharmSpellInitialize()
|
|||
}
|
||||
}
|
||||
|
||||
uint8 count = 3;
|
||||
data << count;
|
||||
data << uint32(0x6010) << uint64(0); // if count = 1, 2 or 3
|
||||
data << uint32(0x8e8c) << uint64(0); // if count = 3
|
||||
data << uint32(0x8e8b) << uint64(0); // if count = 3
|
||||
uint8 count = 0;
|
||||
data << uint8(count); // cooldowns count
|
||||
|
||||
GetSession()->SendPacket(&data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ Quest::Quest(Field * questRecord)
|
|||
ReqItemId[i] = questRecord[39+i].GetUInt32();
|
||||
|
||||
for (int i = 0; i < QUEST_OBJECTIVES_COUNT; ++i)
|
||||
ReqItemCount[i] = questRecord[42+i].GetUInt32();
|
||||
ReqItemCount[i] = questRecord[43+i].GetUInt32();
|
||||
|
||||
for (int i = 0; i < QUEST_SOURCE_ITEM_IDS_COUNT; ++i)
|
||||
ReqSourceId[i] = questRecord[47+i].GetUInt32();
|
||||
|
|
|
|||
|
|
@ -9897,7 +9897,7 @@ void CharmInfo::InitEmptyActionBar()
|
|||
{
|
||||
for(uint32 x = 1; x < 10; ++x)
|
||||
{
|
||||
PetActionBar[x].Type = ACT_CAST;
|
||||
PetActionBar[x].Type = ACT_ENABLED;
|
||||
PetActionBar[x].SpellOrAction = 0;
|
||||
}
|
||||
PetActionBar[0].Type = ACT_COMMAND;
|
||||
|
|
@ -9916,7 +9916,7 @@ void CharmInfo::InitPossessCreateSpells()
|
|||
if (IsPassiveSpell(((Creature*)m_unit)->m_spells[x]))
|
||||
m_unit->CastSpell(m_unit, ((Creature*)m_unit)->m_spells[x], true);
|
||||
else
|
||||
AddSpellToAB(0, ((Creature*)m_unit)->m_spells[x], ACT_CAST);
|
||||
AddSpellToAB(0, ((Creature*)m_unit)->m_spells[x], ACT_ENABLED);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -9959,7 +9959,7 @@ void CharmInfo::InitCharmCreateSpells()
|
|||
if(onlyselfcast || !IsPositiveSpell(spellId)) //only self cast and spells versus enemies are autocastable
|
||||
newstate = ACT_DISABLED;
|
||||
else
|
||||
newstate = ACT_CAST;
|
||||
newstate = ACT_ENABLED;
|
||||
|
||||
AddSpellToAB(0, spellId, newstate);
|
||||
}
|
||||
|
|
@ -9970,7 +9970,7 @@ bool CharmInfo::AddSpellToAB(uint32 oldid, uint32 newid, ActiveStates newstate)
|
|||
{
|
||||
for(uint8 i = 0; i < 10; i++)
|
||||
{
|
||||
if((PetActionBar[i].Type == ACT_DISABLED || PetActionBar[i].Type == ACT_ENABLED || PetActionBar[i].Type == ACT_CAST) && PetActionBar[i].SpellOrAction == oldid)
|
||||
if((PetActionBar[i].Type == ACT_DISABLED || PetActionBar[i].Type == ACT_ENABLED) && PetActionBar[i].SpellOrAction == oldid)
|
||||
{
|
||||
PetActionBar[i].SpellOrAction = newid;
|
||||
if(!oldid)
|
||||
|
|
|
|||
|
|
@ -593,8 +593,18 @@ struct CleanDamage
|
|||
|
||||
struct UnitActionBarEntry
|
||||
{
|
||||
uint32 Type;
|
||||
uint32 SpellOrAction;
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
uint16 Type;
|
||||
uint16 SpellOrAction;
|
||||
};
|
||||
struct
|
||||
{
|
||||
uint32 Raw;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
#define MAX_DECLINED_NAME_CASES 5
|
||||
|
|
@ -616,13 +626,12 @@ enum CurrentSpellTypes
|
|||
|
||||
enum ActiveStates
|
||||
{
|
||||
ACT_ENABLED = 0xC100,
|
||||
ACT_DISABLED = 0x8100,
|
||||
ACT_COMMAND = 0x0700,
|
||||
ACT_REACTION = 0x0600,
|
||||
ACT_CAST = 0x0100,
|
||||
ACT_PASSIVE = 0x0000,
|
||||
ACT_DECIDE = 0x0001
|
||||
ACT_PASSIVE = 0x0100, // 0x0100 - passive
|
||||
ACT_DISABLED = 0x8100, // 0x8000 - castable
|
||||
ACT_ENABLED = 0xC100, // 0x4000 | 0x8000 - auto cast + castable
|
||||
ACT_COMMAND = 0x0700, // 0x0100 | 0x0200 | 0x0400
|
||||
ACT_REACTION = 0x0600, // 0x0200 | 0x0400
|
||||
ACT_DECIDE = 0x0001 // what is it?
|
||||
};
|
||||
|
||||
enum ReactStates
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
#ifndef _UPDATEFIELDS_AUTO_H
|
||||
#define _UPDATEFIELDS_AUTO_H
|
||||
|
||||
// Auto generated for version 3, 0, 3, 9155
|
||||
// Auto generated for version 3, 0, 3, 9183
|
||||
|
||||
enum EObjectFields
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue