-
-
Save Natureknight/41ef2cf5d83e2c55684f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From e91cb611a8da376ee380b2629a814b3bd49f45a0 Mon Sep 17 00:00:00 2001 | |
From: Natureknight <vallez8244@gmail.com> | |
Date: Tue, 22 Dec 2015 13:13:22 +0200 | |
Subject: [PATCH] Implementation of feature which allows players to simply copy | |
gear or talents (or both) from another player ingame | |
--- | |
sql/updates/custom/world/22_12_2015.sql | 4 + | |
src/server/game/Scripting/ScriptLoader.cpp | 2 + | |
src/server/scripts/Custom/copyGearCommands.cpp | 485 +++++++++++++++++++++++++ | |
3 files changed, 491 insertions(+) | |
create mode 100644 sql/updates/custom/world/22_12_2015.sql | |
create mode 100644 src/server/scripts/Custom/copyGearCommands.cpp | |
diff --git a/sql/updates/custom/world/22_12_2015.sql b/sql/updates/custom/world/22_12_2015.sql | |
new file mode 100644 | |
index 0000000..dc0a282 | |
--- /dev/null | |
+++ b/sql/updates/custom/world/22_12_2015.sql | |
@@ -0,0 +1,4 @@ | |
+INSERT INTO `command` (`name`, `security`, `help`) VALUES ('copy', 0, 'Syntax: .copy Gives a list of available subcommands for copying another player\'s gear/talents'); | |
+INSERT INTO `command` (`name`, `security`, `help`) VALUES ('copy gear', 0, 'Syntax: .copy gear - Copies the gear of your target completely, including gems and enchants'); | |
+INSERT INTO `command` (`name`, `security`, `help`) VALUES ('copy talents', 0, 'Syntax: .copy talents - Copies the talents and glyphs of your target'); | |
+INSERT INTO `command` (`name`, `security`, `help`) VALUES ('copy all', 0, 'Syntax: .copy all - Copies the whole gear, talents, enchants, gems, glyphs of your target'); | |
diff --git a/src/server/game/Scripting/ScriptLoader.cpp b/src/server/game/Scripting/ScriptLoader.cpp | |
index 45644d1..65060cd 100644 | |
--- a/src/server/game/Scripting/ScriptLoader.cpp | |
+++ b/src/server/game/Scripting/ScriptLoader.cpp | |
@@ -1259,6 +1259,7 @@ void AddBattlegroundScripts() | |
void AddSC_Arena_AntiDraw(); | |
void AddSC_transmog_multivendor(); | |
void AddSC_mount_launcher(); | |
+ void AddSC_CopyGearCommands(); | |
#endif | |
void AddCustomScripts() | |
@@ -1298,5 +1299,6 @@ void AddCustomScripts() | |
AddSC_Arena_AntiDraw(); | |
AddSC_transmog_multivendor(); | |
AddSC_mount_launcher(); | |
+ AddSC_CopyGearCommands(); | |
#endif | |
} | |
diff --git a/src/server/scripts/Custom/copyGearCommands.cpp b/src/server/scripts/Custom/copyGearCommands.cpp | |
new file mode 100644 | |
index 0000000..9a10475 | |
--- /dev/null | |
+++ b/src/server/scripts/Custom/copyGearCommands.cpp | |
@@ -0,0 +1,485 @@ | |
+///////////////////////////////////////////////////////////////////////////// | |
+// ____ __ __ __ ___ // | |
+// / __ )____ _/ /_/ /_/ /__ / | ________ ____ ____ ______ // | |
+// / __ / __ `/ __/ __/ / _ \/ /| | / ___/ _ \/ __ \/ __ `/ ___/ // | |
+// / /_/ / /_/ / /_/ /_/ / __/ ___ |/ / / __/ / / / /_/ (__ ) // | |
+// /_____/\__,_/\__/\__/_/\___/_/ |_/_/ \___/_/ /_/\__,_/____/ // | |
+// Developed by Natureknight for BattleArenas.no-ip.org // | |
+// Copyright (C) 2015 Natureknight/JessiqueBA // | |
+// battlearenas.no-ip.org // | |
+///////////////////////////////////////////////////////////////////////////// | |
+ | |
+#include "ScriptPCH.h" | |
+ | |
+enum ArmorSpells | |
+{ | |
+ PLATE_MAIL = 750, | |
+ ARMOR_MAIL = 8737 | |
+}; | |
+ | |
+enum WeaponProficiencies | |
+{ | |
+ BLOCK = 107, | |
+ BOWS = 264, | |
+ CROSSBOWS = 5011, | |
+ DAGGERS = 1180, | |
+ FIST_WEAPONS = 15590, | |
+ GUNS = 266, | |
+ ONE_H_AXES = 196, | |
+ ONE_H_MACES = 198, | |
+ ONE_H_SWORDS = 201, | |
+ POLEARMS = 200, | |
+ SHOOT = 5019, | |
+ STAVES = 227, | |
+ TWO_H_AXES = 197, | |
+ TWO_H_MACES = 199, | |
+ TWO_H_SWORDS = 202, | |
+ WANDS = 5009, | |
+ THROW_WAR = 2567 | |
+}; | |
+ | |
+class CopyGearCommands : public CommandScript | |
+{ | |
+public: | |
+ CopyGearCommands() : CommandScript("CopyGearCommands") { } | |
+ | |
+ ChatCommand* GetCommands() const | |
+ { | |
+ static ChatCommand HandleCopyGearCommandTable[] = | |
+ { | |
+ { "gear", SEC_PLAYER, false, &HandleCopyGearCommand, "", NULL }, | |
+ { "talents", SEC_PLAYER, false, &HandleCopyTalentsCommand, "", NULL }, | |
+ { "all", SEC_PLAYER, false, &HandleCopyGearAndTalentsCommand, "", NULL }, | |
+ { NULL, 0, false, NULL, "", NULL } | |
+ }; | |
+ | |
+ static ChatCommand commandTable[] = | |
+ { | |
+ { "copy", SEC_PLAYER, true, NULL, "", HandleCopyGearCommandTable }, | |
+ { NULL, 0, false, NULL, "", NULL } | |
+ }; | |
+ return commandTable; | |
+ } | |
+ | |
+ static bool HandleCopyGearCommand(ChatHandler* handler, const char* args) | |
+ { | |
+ std::string name; | |
+ Player* player = handler->GetSession()->GetPlayer(); | |
+ Player* target; | |
+ char* targetName = strtok((char*)args, " "); // Get entered name | |
+ | |
+ if (targetName) | |
+ { | |
+ name = targetName; | |
+ normalizePlayerName(name); | |
+ target = sObjectAccessor->FindPlayerByName(name.c_str()); | |
+ } | |
+ else // If no name was entered - use target | |
+ { | |
+ target = handler->getSelectedPlayer(); | |
+ if (target) | |
+ name = target->GetName(); | |
+ } | |
+ | |
+ if (target) | |
+ { | |
+ if (target == player) | |
+ { | |
+ handler->GetSession()->SendAreaTriggerMessage("You can't copy your own gear."); | |
+ return false; | |
+ } | |
+ | |
+ // Restrict copying from the opposite faction | |
+ if (player->GetTeam() != target->GetTeam()) | |
+ { | |
+ handler->GetSession()->SendAreaTriggerMessage("You can only copy talents from players thats in opposite faction."); | |
+ return false; | |
+ } | |
+ | |
+ CopyGear(player, target); | |
+ handler->GetSession()->SendAreaTriggerMessage("Successfuly copied gear of %s", | |
+ target->GetName()); | |
+ | |
+ LearnPlateMailSpells(player); | |
+ player->UpdateSkillsForLevel(); | |
+ LearnWeaponSkills(player); | |
+ player->SaveToDB(); | |
+ return true; | |
+ } | |
+ else | |
+ player->GetSession()->SendAreaTriggerMessage("You must have a target."); | |
+ | |
+ return true; | |
+ } | |
+ | |
+ static bool HandleCopyTalentsCommand(ChatHandler* handler, const char* args) | |
+ { | |
+ std::string name; | |
+ Player* player = handler->GetSession()->GetPlayer(); | |
+ Player* target; | |
+ char* targetName = strtok((char*)args, " "); // Get entered name | |
+ | |
+ if (targetName) | |
+ { | |
+ name = targetName; | |
+ normalizePlayerName(name); | |
+ target = sObjectAccessor->FindPlayerByName(name.c_str()); | |
+ } | |
+ else // If no name was entered - use target | |
+ { | |
+ target = handler->getSelectedPlayer(); | |
+ if (target && target != player) | |
+ name = target->GetName(); | |
+ else | |
+ { | |
+ handler->GetSession()->SendAreaTriggerMessage("You can't copy your own gear/talents."); | |
+ return false; | |
+ } | |
+ } | |
+ | |
+ if (target) | |
+ { | |
+ if (target == player) | |
+ { | |
+ handler->GetSession()->SendAreaTriggerMessage("You can't copy your own talents."); | |
+ return false; | |
+ } | |
+ | |
+ CopyTalents(player, target); | |
+ handler->GetSession()->SendAreaTriggerMessage("Successfuly copied talents of %s", | |
+ target->GetName()); | |
+ | |
+ player->UpdateSkillsForLevel(); | |
+ player->SaveToDB(); | |
+ return true; | |
+ } | |
+ else | |
+ player->GetSession()->SendAreaTriggerMessage("You must have a target."); | |
+ | |
+ return true; | |
+ } | |
+ | |
+ static bool HandleCopyGearAndTalentsCommand(ChatHandler* handler, const char* args) | |
+ { | |
+ std::string name; | |
+ Player* player = handler->GetSession()->GetPlayer(); | |
+ Player* target; | |
+ char* targetName = strtok((char*)args, " "); // Get entered name | |
+ | |
+ if (targetName) | |
+ { | |
+ name = targetName; | |
+ normalizePlayerName(name); | |
+ target = sObjectAccessor->FindPlayerByName(name.c_str()); | |
+ } | |
+ else // If no name was entered - use target | |
+ { | |
+ target = handler->getSelectedPlayer(); | |
+ if (target && target != player) | |
+ name = target->GetName(); | |
+ else | |
+ { | |
+ handler->GetSession()->SendAreaTriggerMessage("You can't copy your own gear/talents."); | |
+ return false; | |
+ } | |
+ } | |
+ | |
+ if (target) | |
+ { | |
+ if (target == player) | |
+ { | |
+ handler->GetSession()->SendAreaTriggerMessage("You can't copy your own gear/talents."); | |
+ return false; | |
+ } | |
+ | |
+ // Restrict copying from the opposite faction | |
+ if (player->GetTeam() != target->GetTeam()) | |
+ { | |
+ handler->GetSession()->SendAreaTriggerMessage("You can only copy talents from players thats in opposite faction."); | |
+ return false; | |
+ } | |
+ | |
+ CopyGear(player, target); | |
+ CopyTalents(player, target); | |
+ handler->GetSession()->SendAreaTriggerMessage("Successfuly copied gear and talents of %s", | |
+ target->GetName()); | |
+ | |
+ LearnPlateMailSpells(player); | |
+ LearnWeaponSkills(player); | |
+ player->UpdateSkillsForLevel(); | |
+ player->SaveToDB(); | |
+ return true; | |
+ } | |
+ else | |
+ player->GetSession()->SendAreaTriggerMessage("You must have a target."); | |
+ | |
+ return true; | |
+ } | |
+ | |
+private: | |
+ static void ApplyBonus(Player* player, Item* item, EnchantmentSlot slot, uint32 bonusEntry) | |
+ { | |
+ if (!item) | |
+ return; | |
+ | |
+ if (!bonusEntry || bonusEntry == 0) | |
+ return; | |
+ | |
+ player->ApplyEnchantment(item, slot, false); | |
+ item->SetEnchantment(slot, bonusEntry, 0, 0); | |
+ player->ApplyEnchantment(item, slot, true); | |
+ } | |
+ | |
+ // Get the gear from the selected player | |
+ static void CopyGear(Player* player, Player* target) | |
+ { | |
+ for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; i++) | |
+ { | |
+ // Delete previous items if equipped | |
+ player->RemoveItem(INVENTORY_SLOT_BAG_0, i, true); | |
+ | |
+ if (Item* equippedItem = target->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) | |
+ { | |
+ // TODO: Implement checks for items here | |
+ | |
+ player->EquipNewItem(i, target->GetItemByPos(INVENTORY_SLOT_BAG_0, i)->GetEntry(), true); | |
+ ApplyBonus(player, player->GetItemByPos(INVENTORY_SLOT_BAG_0, i), | |
+ PERM_ENCHANTMENT_SLOT, target->GetItemByPos(INVENTORY_SLOT_BAG_0, i)->GetEnchantmentId(PERM_ENCHANTMENT_SLOT)); | |
+ ApplyBonus(player, player->GetItemByPos(INVENTORY_SLOT_BAG_0, i), | |
+ SOCK_ENCHANTMENT_SLOT, target->GetItemByPos(INVENTORY_SLOT_BAG_0, i)->GetEnchantmentId(SOCK_ENCHANTMENT_SLOT)); | |
+ ApplyBonus(player, player->GetItemByPos(INVENTORY_SLOT_BAG_0, i), | |
+ SOCK_ENCHANTMENT_SLOT_2, target->GetItemByPos(INVENTORY_SLOT_BAG_0, i)->GetEnchantmentId(SOCK_ENCHANTMENT_SLOT_2)); | |
+ ApplyBonus(player, player->GetItemByPos(INVENTORY_SLOT_BAG_0, i), | |
+ SOCK_ENCHANTMENT_SLOT_3, target->GetItemByPos(INVENTORY_SLOT_BAG_0, i)->GetEnchantmentId(SOCK_ENCHANTMENT_SLOT_3)); | |
+ ApplyBonus(player, player->GetItemByPos(INVENTORY_SLOT_BAG_0, i), | |
+ BONUS_ENCHANTMENT_SLOT, target->GetItemByPos(INVENTORY_SLOT_BAG_0, i)->GetEnchantmentId(BONUS_ENCHANTMENT_SLOT)); | |
+ ApplyBonus(player, player->GetItemByPos(INVENTORY_SLOT_BAG_0, i), | |
+ PRISMATIC_ENCHANTMENT_SLOT, target->GetItemByPos(INVENTORY_SLOT_BAG_0, i)->GetEnchantmentId(PRISMATIC_ENCHANTMENT_SLOT)); | |
+ } | |
+ } | |
+ } | |
+ | |
+ static void CopyTalents(Player* player, Player* target) | |
+ { | |
+ // Reset current talents first | |
+ if (player->GetFreeTalentPoints() < 71) | |
+ { | |
+ player->resetTalents(true); | |
+ player->SendTalentsInfoData(false); | |
+ } | |
+ | |
+ target->SaveToDB(); | |
+ | |
+ QueryResult result = CharacterDatabase.PQuery("SELECT spell FROM character_talent WHERE guid = '%u' " | |
+ "AND spec = '%u';", target->GetGUID(), target->GetActiveSpec()); | |
+ | |
+ if (!result) | |
+ { | |
+ player->GetSession()->SendAreaTriggerMessage("%s doesn't have any talents yet.", | |
+ target->GetName()); | |
+ return; | |
+ } | |
+ | |
+ do | |
+ { | |
+ Field* fields = result->Fetch(); | |
+ uint32 spell = fields[0].GetUInt32(); | |
+ | |
+ player->learnSpell(spell, false); | |
+ player->AddTalent(spell, player->GetActiveSpec(), true); | |
+ | |
+ } while (result->NextRow()); | |
+ | |
+ | |
+ CopyGlyphs(player, target); | |
+ player->SetFreeTalentPoints(0); | |
+ player->SendTalentsInfoData(false); | |
+ } | |
+ | |
+ static void CopyGlyphs(Player* player, Player* target) | |
+ { | |
+ QueryResult result = CharacterDatabase.PQuery("SELECT glyph1, glyph2, glyph3, glyph4, glyph5, glyph6 " | |
+ "FROM character_glyphs WHERE guid = '%u' AND spec = '%u';", target->GetGUID(), target->GetActiveSpec()); | |
+ | |
+ for (uint8 slot = 0; slot < MAX_GLYPH_SLOT_INDEX; ++slot) | |
+ { | |
+ if (!result) | |
+ continue; | |
+ | |
+ Field* fields = result->Fetch(); | |
+ uint32 glyph1 = fields[0].GetUInt32(); | |
+ uint32 glyph2 = fields[1].GetUInt32(); | |
+ uint32 glyph3 = fields[2].GetUInt32(); | |
+ uint32 glyph4 = fields[3].GetUInt32(); | |
+ uint32 glyph5 = fields[4].GetUInt32(); | |
+ uint32 glyph6 = fields[5].GetUInt32(); | |
+ | |
+ uint32 tempGlyph; // Temp | |
+ | |
+ switch (slot) | |
+ { | |
+ case 0: tempGlyph = glyph1; break; | |
+ case 1: tempGlyph = glyph2; break; | |
+ case 2: tempGlyph = glyph3; break; | |
+ case 3: tempGlyph = glyph4; break; | |
+ case 4: tempGlyph = glyph5; break; | |
+ case 5: tempGlyph = glyph6; break; | |
+ default: | |
+ break; | |
+ } | |
+ | |
+ if (GlyphPropertiesEntry const* gp = sGlyphPropertiesStore.LookupEntry(tempGlyph)) | |
+ { | |
+ if (uint32 oldGlyph = player->GetGlyph(slot)) | |
+ { | |
+ player->RemoveAurasDueToSpell(sGlyphPropertiesStore.LookupEntry(oldGlyph)->SpellId); | |
+ player->SetGlyph(slot, 0); | |
+ } | |
+ player->CastSpell(player, gp->SpellId, true); | |
+ player->SetGlyph(slot, tempGlyph); | |
+ } | |
+ } | |
+ } | |
+ | |
+ static void LearnWeaponSkills(Player* player) | |
+ { | |
+ WeaponProficiencies wepSkills[] = { | |
+ BLOCK, BOWS, CROSSBOWS, DAGGERS, FIST_WEAPONS, GUNS, ONE_H_AXES, ONE_H_MACES, | |
+ ONE_H_SWORDS, POLEARMS, SHOOT, STAVES, TWO_H_AXES, TWO_H_MACES, TWO_H_SWORDS, WANDS, THROW_WAR | |
+ }; | |
+ | |
+ uint32 size = 17; | |
+ | |
+ for (uint32 i = 0; i < size; ++i) | |
+ if (player->HasSpell(wepSkills[i])) | |
+ continue; | |
+ | |
+ switch (player->getClass()) | |
+ { | |
+ case CLASS_WARRIOR: | |
+ player->learnSpell(THROW_WAR, false); | |
+ player->learnSpell(TWO_H_SWORDS, false); | |
+ player->learnSpell(TWO_H_MACES, false); | |
+ player->learnSpell(TWO_H_AXES, false); | |
+ player->learnSpell(STAVES, false); | |
+ player->learnSpell(POLEARMS, false); | |
+ player->learnSpell(ONE_H_SWORDS, false); | |
+ player->learnSpell(ONE_H_MACES, false); | |
+ player->learnSpell(ONE_H_AXES, false); | |
+ player->learnSpell(GUNS, false); | |
+ player->learnSpell(FIST_WEAPONS, false); | |
+ player->learnSpell(DAGGERS, false); | |
+ player->learnSpell(CROSSBOWS, false); | |
+ player->learnSpell(BOWS, false); | |
+ player->learnSpell(BLOCK, false); | |
+ break; | |
+ case CLASS_PRIEST: | |
+ player->learnSpell(WANDS, false); | |
+ player->learnSpell(STAVES, false); | |
+ player->learnSpell(SHOOT, false); | |
+ player->learnSpell(ONE_H_MACES, false); | |
+ player->learnSpell(DAGGERS, false); | |
+ break; | |
+ case CLASS_PALADIN: | |
+ player->learnSpell(TWO_H_SWORDS, false); | |
+ player->learnSpell(TWO_H_MACES, false); | |
+ player->learnSpell(TWO_H_AXES, false); | |
+ player->learnSpell(POLEARMS, false); | |
+ player->learnSpell(ONE_H_SWORDS, false); | |
+ player->learnSpell(ONE_H_MACES, false); | |
+ player->learnSpell(ONE_H_AXES, false); | |
+ player->learnSpell(BLOCK, false); | |
+ break; | |
+ case CLASS_ROGUE: | |
+ player->learnSpell(ONE_H_SWORDS, false); | |
+ player->learnSpell(ONE_H_MACES, false); | |
+ player->learnSpell(ONE_H_AXES, false); | |
+ player->learnSpell(GUNS, false); | |
+ player->learnSpell(FIST_WEAPONS, false); | |
+ player->learnSpell(DAGGERS, false); | |
+ player->learnSpell(CROSSBOWS, false); | |
+ player->learnSpell(BOWS, false); | |
+ break; | |
+ case CLASS_DEATH_KNIGHT: | |
+ player->learnSpell(TWO_H_SWORDS, false); | |
+ player->learnSpell(TWO_H_MACES, false); | |
+ player->learnSpell(TWO_H_AXES, false); | |
+ player->learnSpell(POLEARMS, false); | |
+ player->learnSpell(ONE_H_SWORDS, false); | |
+ player->learnSpell(ONE_H_MACES, false); | |
+ player->learnSpell(ONE_H_AXES, false); | |
+ break; | |
+ case CLASS_MAGE: | |
+ player->learnSpell(WANDS, false); | |
+ player->learnSpell(STAVES, false); | |
+ player->learnSpell(SHOOT, false); | |
+ player->learnSpell(ONE_H_SWORDS, false); | |
+ player->learnSpell(DAGGERS, false); | |
+ break; | |
+ case CLASS_SHAMAN: | |
+ player->learnSpell(TWO_H_MACES, false); | |
+ player->learnSpell(TWO_H_AXES, false); | |
+ player->learnSpell(STAVES, false); | |
+ player->learnSpell(ONE_H_MACES, false); | |
+ player->learnSpell(ONE_H_AXES, false); | |
+ player->learnSpell(FIST_WEAPONS, false); | |
+ player->learnSpell(DAGGERS, false); | |
+ player->learnSpell(BLOCK, false); | |
+ break; | |
+ case CLASS_HUNTER: | |
+ player->learnSpell(THROW_WAR, false); | |
+ player->learnSpell(TWO_H_SWORDS, false); | |
+ player->learnSpell(TWO_H_AXES, false); | |
+ player->learnSpell(STAVES, false); | |
+ player->learnSpell(POLEARMS, false); | |
+ player->learnSpell(ONE_H_SWORDS, false); | |
+ player->learnSpell(ONE_H_AXES, false); | |
+ player->learnSpell(GUNS, false); | |
+ player->learnSpell(FIST_WEAPONS, false); | |
+ player->learnSpell(DAGGERS, false); | |
+ player->learnSpell(CROSSBOWS, false); | |
+ player->learnSpell(BOWS, false); | |
+ break; | |
+ case CLASS_DRUID: | |
+ player->learnSpell(TWO_H_MACES, false); | |
+ player->learnSpell(STAVES, false); | |
+ player->learnSpell(POLEARMS, false); | |
+ player->learnSpell(ONE_H_MACES, false); | |
+ player->learnSpell(FIST_WEAPONS, false); | |
+ player->learnSpell(DAGGERS, false); | |
+ break; | |
+ case CLASS_WARLOCK: | |
+ player->learnSpell(WANDS, false); | |
+ player->learnSpell(STAVES, false); | |
+ player->learnSpell(SHOOT, false); | |
+ player->learnSpell(ONE_H_SWORDS, false); | |
+ player->learnSpell(DAGGERS, false); | |
+ break; | |
+ default: | |
+ break; | |
+ } | |
+ } | |
+ | |
+ static void LearnPlateMailSpells(Player* player) | |
+ { | |
+ switch (player->getClass()) | |
+ { | |
+ case CLASS_WARRIOR: | |
+ case CLASS_PALADIN: | |
+ case CLASS_DEATH_KNIGHT: | |
+ player->learnSpell(PLATE_MAIL, true); | |
+ break; | |
+ case CLASS_SHAMAN: | |
+ case CLASS_HUNTER: | |
+ player->learnSpell(ARMOR_MAIL, true); | |
+ break; | |
+ default: | |
+ break; | |
+ } | |
+ } | |
+}; | |
+ | |
+void AddSC_CopyGearCommands() | |
+{ | |
+ new CopyGearCommands(); | |
+} | |
\ No newline at end of file | |
-- | |
2.1.1.1.g1fb337f |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment