#include "StdAfx.h" #include "WeenieObject.h" #include "PhysicsObj.h" #include "Monster.h" #include "Player.h" #include "Client.h" #include "ClientEvents.h" #include "BinaryWriter.h" #include "ObjectMsgs.h" #include "Database.h" #include "Database2.h" #include "World.h" #include "WorldLandBlock.h" #include "ClientCommands.h" #include "ChatMsgs.h" #include "WeenieFactory.h" #include "InferredPortalData.h" #include "AllegianceManager.h" #include "Config.h" #include "SpellcastingManager.h" #include "Corpse.h" #include "House.h" #include "easylogging++.h" #include "Util.h" #include "ChessManager.h" #include "RandomRange.h" #include "AugmentationDevice.h" #include #include #include #define PLAYER_SAVE_INTERVAL 180.0 #define PLAYER_HEALTH_POLL_INTERVAL 5.0 DEFINE_PACK(SalvageResult) { pWriter->Write(material); pWriter->Write(workmanship); pWriter->Write(units); } DEFINE_UNPACK(SalvageResult) { UNFINISHED(); return false; } CPlayerWeenie::CPlayerWeenie(CClient *pClient, DWORD dwGUID, WORD instance_ts) { m_bDontClear = true; m_pClient = pClient; SetID(dwGUID); _instance_timestamp = instance_ts; m_Qualities.SetInt(CREATION_TIMESTAMP_INT, (int)time(NULL)); m_Qualities.SetFloat(CREATION_TIMESTAMP_FLOAT, Timer::cur_time); //if (pClient && pClient->GetAccessLevel() >= SENTINEL_ACCESS) // SetRadarBlipColor(Sentinel_RadarBlipEnum); //SetLoginPlayerQualities(); m_Qualities.SetInt(PHYSICS_STATE_INT, PhysicsState::HIDDEN_PS | PhysicsState::IGNORE_COLLISIONS_PS | PhysicsState::EDGE_SLIDE_PS | PhysicsState::GRAVITY_PS); // Human Female by Default // Used by physics object SetSetupID(0x2000001); // 0x0200004E); SetMotionTableID(0x9000001); SetSoundTableID(0x20000001); // 0x20000002); SetPETableID(0x34000004); SetScale(1.0f); // Moon location... loc_t( 0xEFEA0001, 0, 0, 0 ); // SetInitialPosition(Position(0x9722003A, Vector(168.354004f, 24.618000f, 102.005005f), Quaternion(-0.922790f, 0.000000, 0.000000, -0.385302f))); m_LastAssessed = 0; m_NextSave = Timer::cur_time + PLAYER_SAVE_INTERVAL; } CPlayerWeenie::~CPlayerWeenie() { LeaveFellowship(); if (m_pTradeManager) { m_pTradeManager->CloseTrade(this); m_pTradeManager = NULL; } CClientEvents *pEvents; if (m_pClient && (pEvents = m_pClient->GetEvents())) { pEvents->DetachPlayer(); } } void CPlayerWeenie::DetachClient() { m_pClient = NULL; } void CPlayerWeenie::SendNetMessage(void *_data, DWORD _len, WORD _group, BOOL _event) { if (m_pClient) m_pClient->SendNetMessage(_data, _len, _group, _event); } void CPlayerWeenie::SendNetMessage(BinaryWriter *_food, WORD _group, BOOL _event, BOOL del) { if (m_pClient) m_pClient->SendNetMessage(_food, _group, _event, del); } void CPlayerWeenie::AddSpellByID(DWORD id) { BinaryWriter AddSpellToSpellbook; AddSpellToSpellbook.Write(0x02C1); AddSpellToSpellbook.Write(id); AddSpellToSpellbook.Write(0x0); SendNetMessage(AddSpellToSpellbook.GetData(), AddSpellToSpellbook.GetSize(), EVENT_MSG, true); } bool CPlayerWeenie::IsAdvocate() { return GetAccessLevel() >= ADVOCATE_ACCESS; } bool CPlayerWeenie::IsSentinel() { return GetAccessLevel() >= SENTINEL_ACCESS; } bool CPlayerWeenie::IsAdmin() { return GetAccessLevel() >= ADMIN_ACCESS; } int CPlayerWeenie::GetAccessLevel() { if (!m_pClient) return BASIC_ACCESS; return m_pClient->GetAccessLevel(); } void CPlayerWeenie::BeginLogout() { if (IsLoggingOut()) return; _beginLogoutTime = max(Timer::cur_time + 5.0, (double)m_iPKActivity); _logoutTime = _beginLogoutTime + 5.0; ChangeCombatMode(NONCOMBAT_COMBAT_MODE, false); LeaveFellowship(); if (m_pTradeManager) { m_pTradeManager->CloseTrade(this); m_pTradeManager = NULL; } sChessManager->Quit(this); StopCompletely(0); } void CPlayerWeenie::OnLogout() { DoForcedMotion(Motion_LogOut); Save(); } void CPlayerWeenie::Tick() { CMonsterWeenie::Tick(); if (IsDead() && (!m_bReviveAfterAnim || get_minterp()->interpreted_state.forward_command != Motion_Dead)) { m_bReviveAfterAnim = false; //if (IsDead()) Revive(); } double pkTimestamp; if (m_Qualities.InqFloat(PK_TIMESTAMP_FLOAT, pkTimestamp, TRUE)) { if (pkTimestamp <= Timer::cur_time) { m_Qualities.SetInt(PLAYER_KILLER_STATUS_INT, PKStatusEnum::PK_PKStatus); NotifyIntStatUpdated(PLAYER_KILLER_STATUS_INT, false); m_Qualities.RemoveFloat(PK_TIMESTAMP_FLOAT); NotifyFloatStatUpdated(PK_TIMESTAMP_FLOAT); SendText("The power of Bael'Zharon flows through you, you are once more a player killer.", LTT_MAGIC); } } if (m_pClient) { if (m_NextSave <= Timer::cur_time) { if (!IsDead()) // && !IsBusyOrInAction()) { Save(); m_NextSave = Timer::cur_time + PLAYER_SAVE_INTERVAL; } } } if (m_fNextMakeAwareCacheFlush <= Timer::cur_time) { FlushMadeAwareof(); m_fNextMakeAwareCacheFlush = Timer::cur_time + 25.0; } if (IsLoggingOut() && _beginLogoutTime <= Timer::cur_time) { OnLogout(); _beginLogoutTime = Timer::cur_time + 999999; } if (IsLoggingOut() && _logoutTime <= Timer::cur_time) { // time to logout if (m_pClient && m_pClient->GetEvents()) { m_pClient->GetEvents()->OnLogoutCompleted(); } MarkForDestroy(); _logoutTime = DBL_MAX; } if (IsRecalling() && _recallTime <= Timer::cur_time) { _recallTime = -1.0; Movement_Teleport(_recallPos, false); } if (m_pTradeManager && m_fNextTradeCheck <= Timer::cur_time) { m_pTradeManager->CheckDistance(); m_fNextTradeCheck = Timer::cur_time + 2; } if (m_NextHealthUpdate <= Timer::cur_time) { CWeenieObject *pTarget = g_pWorld->FindWithinPVS(this, m_LastHealthRequest); if (pTarget) { SendNetMessage(HealthUpdate((CMonsterWeenie *)pTarget), PRIVATE_MSG, TRUE, TRUE); m_NextHealthUpdate = Timer::cur_time + PLAYER_HEALTH_POLL_INTERVAL; } else { RemoveLastHealthRequest(); } } } bool CPlayerWeenie::IsBusy() { if (IsRecalling() || IsLoggingOut() || CWeenieObject::IsBusy()) return true; return false; } const double AWARENESS_TIMEOUT = 25.0; bool CPlayerWeenie::AlreadyMadeAwareOf(DWORD object_id) { std::unordered_map::iterator entry = _objMadeAwareOf.find(object_id); if (entry != _objMadeAwareOf.end()) { if ((entry->second + AWARENESS_TIMEOUT) > Timer::cur_time) return true; } return false; } void CPlayerWeenie::SetMadeAwareOf(DWORD object_id) { _objMadeAwareOf[object_id] = Timer::cur_time; } void CPlayerWeenie::FlushMadeAwareof() { for (std::unordered_map::iterator i = _objMadeAwareOf.begin(); i != _objMadeAwareOf.end();) { if ((i->second + AWARENESS_TIMEOUT) <= Timer::cur_time) i = _objMadeAwareOf.erase(i); else i++; } } void CPlayerWeenie::MakeAware(CWeenieObject *pEntity, bool bForceUpdate) { #ifndef PUBLIC_BUILD int vis; if (pEntity->m_Qualities.InqBool(VISIBILITY_BOOL, vis) && !m_bAdminVision) return; #else int vis; if (pEntity->m_Qualities.InqBool(VISIBILITY_BOOL, vis)) return; #endif if (!bForceUpdate && AlreadyMadeAwareOf(pEntity->GetID())) return; SetMadeAwareOf(pEntity->GetID()); BinaryWriter *CM = pEntity->CreateMessage(); if (CM) { // LOG(Temp, Normal, "Sending object %X in cell %X\n", pEntity->GetID(), pEntity->m_Position.objcell_id); SendNetMessage(CM, OBJECT_MSG); } if (pEntity->children && pEntity->children->num_objects) { for (DWORD i = 0; i < pEntity->children->num_objects; i++) { if (CPhysicsObj *pChild = pEntity->children->objects.array_data[i]) { if (CWeenieObject *pChildWeenie = pChild->weenie_obj) { if (BinaryWriter *CM = pChildWeenie->CreateMessage()) { SendNetMessage(CM, OBJECT_MSG); } } } } } if (pEntity == this) { // make aware of inventory too for (auto item : m_Wielded) { MakeAware(item); } for (auto item : m_Items) { MakeAware(item); } for (auto item : m_Packs) { MakeAware(item); if (CContainerWeenie *container = item->AsContainer()) { container->MakeAwareViewContent(this); } } } } void CPlayerWeenie::LoginCharacter(void) { DWORD SC[2]; SC[0] = 0xF746; SC[1] = GetID(); SendNetMessage(SC, sizeof(SC), 10); BinaryWriter *LC = ::LoginCharacter(this); SendNetMessage(LC->GetData(), LC->GetSize(), PRIVATE_MSG, TRUE); delete LC; } void CPlayerWeenie::ExitPortal() { if (_isFirstPortalInSession) { TryToUnloadAllegianceXP(true); _isFirstPortalInSession = false; } if (_phys_obj) _phys_obj->ExitPortal(); } void CPlayerWeenie::SetLastHealthRequest(DWORD guid) { m_LastHealthRequest = guid; m_NextHealthUpdate = Timer::cur_time + PLAYER_HEALTH_POLL_INTERVAL; } void CPlayerWeenie::RemoveLastHealthRequest() { m_LastHealthRequest = 0; // nothing targeted so we don't want to send messages m_NextHealthUpdate = Timer::cur_time + 86400.0; } void CPlayerWeenie::RefreshTargetHealth() { m_NextHealthUpdate = 0; } void CPlayerWeenie::SetLastAssessed(DWORD guid) { m_LastAssessed = guid; } std::string CPlayerWeenie::RemoveLastAssessed(bool forced) { if (m_LastAssessed != 0) { CWeenieObject *pObject = g_pWorld->FindWithinPVS(this, m_LastAssessed); if (pObject != NULL && !pObject->AsPlayer()) { if (forced || !pObject->m_bDontClear) { std::string name = pObject->GetName(); pObject->MarkForDestroy(); m_LastAssessed = 0; return name; } } } return ""; } void CPlayerWeenie::OnDeathAnimComplete() { CMonsterWeenie::OnDeathAnimComplete(); if (m_bReviveAfterAnim) { m_bReviveAfterAnim = false; if (!g_pConfig->HardcoreMode()) { //clear damage sources on death m_aDamageSources.clear(); m_highestDamageSource = 0; m_totalDamageTaken = 0; //if (IsDead()) Revive(); } } } void CPlayerWeenie::UpdateVitaePool(DWORD pool) { m_Qualities.SetInt(VITAE_CP_POOL_INT, pool); NotifyIntStatUpdated(VITAE_CP_POOL_INT, true); } void CPlayerWeenie::ReduceVitae(float amount) { int level = InqIntQuality(LEVEL_INT, 1); double maxVitaePenalty; if (level < 15) maxVitaePenalty = min(max((level - 1) * 3, 1) / 100.0, 0.4); else maxVitaePenalty = 0.4; double minVitaeEnergy = 1.0 - maxVitaePenalty; Enchantment enchant; if (m_Qualities.InqVitae(&enchant)) enchant._smod.val -= amount; else { enchant._id = Vitae_SpellID; enchant.m_SpellSetID = 0; // ??? enchant._spell_category = 204; // Vitae_SpellCategory; enchant._power_level = 30; enchant._start_time = 0; enchant._duration = -1.0; enchant._caster = 0; enchant._degrade_modifier = 0; enchant._degrade_limit = 1; enchant._last_time_degraded = 0; enchant._smod.type = SecondAtt_EnchantmentType | Skill_EnchantmentType | MultipleStat_EnchantmentType | Multiplicative_EnchantmentType | Additive_Degrade_EnchantmentType | Vitae_EnchantmentType; enchant._smod.key = 0; enchant._smod.val = 1.0f - amount; } if (enchant._smod.val < minVitaeEnergy) enchant._smod.val = minVitaeEnergy; if (enchant._smod.val > 1.0f) enchant._smod.val = 1.0f; m_Qualities.UpdateEnchantment(&enchant); UpdateVitaeEnchantment(); CheckVitalRanges(); } void CPlayerWeenie::UpdateVitaeEnchantment() { Enchantment enchant; if (m_Qualities.InqVitae(&enchant)) { if (enchant._smod.val < 1.0f) { NotifyEnchantmentUpdated(&enchant); } else { PackableListWithJson expired; expired.push_back(666); if (m_Qualities._enchantment_reg) { m_Qualities._enchantment_reg->RemoveEnchantments(&expired); } BinaryWriter expireMessage; expireMessage.Write(0x2C5); expired.Pack(&expireMessage); SendNetMessage(&expireMessage, PRIVATE_MSG, TRUE, FALSE); EmitSound(Sound_SpellExpire, 1.0f, true); } } } void CPlayerWeenie::OnGivenXP(long long amount, bool allegianceXP) { if (m_Qualities.GetVitaeValue() < 1.0 && !allegianceXP) { DWORD64 vitae_pool = InqIntQuality(VITAE_CP_POOL_INT, 0) + min(amount, 1000000000ll); float new_vitae = 1.0; bool has_new_vitae = VitaeSystem::DetermineNewVitaeLevel(m_Qualities.GetVitaeValue(), InqIntQuality(DEATH_LEVEL_INT, 1), &vitae_pool, &new_vitae); UpdateVitaePool(vitae_pool); if (has_new_vitae) { if (new_vitae < 1.0f) { SendText("Your experience has reduced your Vitae penalty!", LTT_MAGIC); } Enchantment enchant; if (m_Qualities.InqVitae(&enchant)) { enchant._smod.val = new_vitae; m_Qualities.UpdateEnchantment(&enchant); } UpdateVitaeEnchantment(); CheckVitalRanges(); } } else { // there should be no vitae... UpdateVitaeEnchantment(); } } void addItemsToDropLists(PhysObjVector items, std::vector &removeList, std::vector &alwaysDropList, std::vector &allValidItems) { for (auto item : items) { if (item->m_Qualities.id == W_COINSTACK_CLASS) continue; else if (item->IsDestroyedOnDeath()) removeList.push_back(item); else if (item->IsDroppedOnDeath()) alwaysDropList.push_back(item); else if (!item->IsBonded()) allValidItems.push_back(item); } } //TODO Add nonwielded handling for level > 11 && level < 35 void CPlayerWeenie::CalculateAndDropDeathItems(CCorpseWeenie *pCorpse, DWORD killer_id) { if (!pCorpse) return; if (IsBonded()) //we're using the bonded property on the player to determine if they should or not drop items on death. return; int level = InqIntQuality(LEVEL_INT, 1); CWeenieObject *pKiller = g_pWorld->FindObject(killer_id); int amountOfItemsToDrop = 0; int augDropLess = InqIntQuality(AUGMENTATION_LESS_DEATH_ITEM_LOSS_INT, 0); // Take Death Item Augs into Consideration if (level <= 10) { amountOfItemsToDrop = 0; } else if (level <= 20) { amountOfItemsToDrop = 1; } else { amountOfItemsToDrop = floor(level / 20) + Random::GenInt(0, 2); } // either we can't find a killer (killed self?) or the killer is not a player if (!pKiller || !pKiller->_IsPlayer()) amountOfItemsToDrop = max(0, (amountOfItemsToDrop - (augDropLess * 5))); pCorpse->_begin_destroy_at = Timer::cur_time + max((60.0 * 5 * level), 60.0 * 60); //override corpse decay time to 5 minutes per level with a minimum of 1 hour. pCorpse->_shouldSave = true; pCorpse->m_bDontClear = true; DWORD coinConsumed = 0; if (level > 5) { coinConsumed = ConsumeCoin(RecalculateCoinAmount(W_COINSTACK_CLASS) / 2, W_COINSTACK_CLASS); pCorpse->SpawnInContainer(W_COINSTACK_CLASS, coinConsumed); } std::vector removeList; std::vector alwaysDropList; std::vector allValidItems; addItemsToDropLists(m_Wielded, removeList, alwaysDropList, allValidItems); addItemsToDropLists(m_Items, removeList, alwaysDropList, allValidItems); for (auto packAsWeenie : m_Packs) { CContainerWeenie *pack = packAsWeenie->AsContainer(); if (pack) { addItemsToDropLists(pack->m_Items, removeList, alwaysDropList, allValidItems); } } for (auto item : removeList) item->Remove(); for (auto item : alwaysDropList) FinishMoveItemToContainer(item, pCorpse, 0, true, true); // This section calculates the effective value for each item (halved for each previous dupe) std::multimap itemValueList; std::map itemNameList; std::string objName; for (auto item : allValidItems) { // if this item is stackable if (item->m_Qualities.GetInt(MAX_STACK_SIZE_INT, 0)) { int stack_sz = item->InqIntQuality(STACK_SIZE_INT, 0); int value = item->InqIntQuality(STACK_UNIT_VALUE_INT, 0); objName = item->InqStringQuality(NAME_STRING, objName); // initialise to 0 (this will silently fail if it already exists) itemNameList.insert(std::pair(objName, 0)); // reduce value by half for all previous items with same name value /= 1 << min(15, itemNameList[objName]); // 15 to prevent overflow itemNameList[objName] += stack_sz; for (int i = 0; i < stack_sz; i++) { itemValueList.insert(std::pair(value, item)); // reduce value by half again value /= 2; } } else // not a stack { int value = item->GetValue(); // from item->getName - if it has a material type then it's a randomly generated item and we don't want to add it to the dupe list if (item->InqIntQuality(MATERIAL_TYPE_INT, 0)) { objName = item->InqStringQuality(NAME_STRING, objName); // initialise to 0 (this will silently fail if it already exists) itemNameList.insert(std::pair(objName, 0)); // reduce value by half for all previous items with same name value /= 1 << itemNameList[objName]; itemNameList[objName]++; } itemValueList.insert(std::pair(value, item)); } } // cap the amount of items to drop to the number of items we can drop so the message properly ends with " and " amountOfItemsToDrop = min(amountOfItemsToDrop, (int)itemValueList.size()); int itemsLost = 0; std::string itemsLostText; if (coinConsumed) { itemsLostText = csprintf("You've lost %s Pyreal%s", FormatNumberString(coinConsumed).c_str(), coinConsumed > 1 ? "s" : ""); // increment itemsLost so the next item starts with " and your " or ", your" itemsLost++; amountOfItemsToDrop++; } else { itemsLostText = "You've lost "; } // START item dropping BY VALUE for (auto iter = itemValueList.rbegin(); iter != itemValueList.rend(); ++iter) { if (itemsLost >= amountOfItemsToDrop) { break; } if (iter->second->InqIntQuality(STACK_SIZE_INT, 1) > 1) { iter->second->DecrementStackNum(); pCorpse->SpawnCloneInContainer(iter->second, 1); } else { FinishMoveItemToContainer(iter->second, pCorpse, 0, true, true); } itemsLost++; if (amountOfItemsToDrop > 1 && itemsLost == amountOfItemsToDrop) itemsLostText.append(" and your "); else if (itemsLost > 1) itemsLostText.append(", your "); else itemsLostText.append("your "); itemsLostText.append(iter->second->GetName()); } itemsLostText.append("!"); DEATH_LOG << InqStringQuality(NAME_STRING, "") << "-" << itemsLostText; if (coinConsumed || itemsLost) SendText(itemsLostText.c_str(), LTT_DEFAULT); if (_pendingCorpse) { //make the player corpse visible. _pendingCorpse->m_Qualities.RemoveBool(VISIBILITY_BOOL); _pendingCorpse->NotifyBoolStatUpdated(VISIBILITY_BOOL, false); _pendingCorpse->NotifyObjectCreated(false); _pendingCorpse->Save(); _pendingCorpse = NULL; } } void CPlayerWeenie::OnDeath(DWORD killer_id) { _recallTime = -1.0; // cancel any portal recalls m_bReviveAfterAnim = true; CMonsterWeenie::OnDeath(killer_id); m_Qualities.SetFloat(DEATH_TIMESTAMP_FLOAT, Timer::cur_time); NotifyFloatStatUpdated(DEATH_TIMESTAMP_FLOAT); m_Qualities.SetInt(DEATH_LEVEL_INT, m_Qualities.GetInt(LEVEL_INT, 1)); NotifyIntStatUpdated(DEATH_LEVEL_INT, true); if ((m_Position.objcell_id & 0xFFFF) < 0x100) //outdoors { m_Qualities.SetPosition(LAST_OUTSIDE_DEATH_POSITION, m_Position); NotifyPositionStatUpdated(LAST_OUTSIDE_DEATH_POSITION, true); } UpdateVitaePool(0); ReduceVitae(0.05f); UpdateVitaeEnchantment(); ClearPKActivity(); bool isPkKill = false; if (killer_id != GetID()) { if (CWeenieObject *pKiller = g_pWorld->FindObject(killer_id)) { if (IsPK() && pKiller->_IsPlayer()) { m_Qualities.SetFloat(PK_TIMESTAMP_FLOAT, Timer::cur_time + g_pConfig->PKRespiteTime()); m_Qualities.SetInt(PLAYER_KILLER_STATUS_INT, PKStatusEnum::NPK_PKStatus); NotifyIntStatUpdated(PLAYER_KILLER_STATUS_INT, false); isPkKill = true; NotifyWeenieError(WERROR_PK_SWITCH_RESPITE); SendText("Bael'Zharon has granted you respite after your moment of weakness. You are temporarily no longer a player killer.", LTT_MAGIC); } } } // create corpse but make it invisible. _pendingCorpse = CreateCorpse(false); if (_pendingCorpse) { if (isPkKill) { _pendingCorpse->m_Qualities.SetBool(PK_KILLER_BOOL, 1); // flag the corpse as one made by a PK isPkKill = false; } CalculateAndDropDeathItems(_pendingCorpse, killer_id); } if (g_pConfig->HardcoreMode()) { OnDeathAnimComplete(); } } void CPlayerWeenie::NotifyAttackDone(int error) { BinaryWriter msg; msg.Write(0x1A7); msg.Write(error); // no error SendNetMessage(&msg, PRIVATE_MSG, TRUE, FALSE); } void CPlayerWeenie::NotifyCommenceAttack() { BinaryWriter msg; msg.Write(0x1B8); SendNetMessage(&msg, PRIVATE_MSG, TRUE, FALSE); } void CPlayerWeenie::OnMotionDone(DWORD motion, BOOL success) { CMonsterWeenie::OnMotionDone(motion, success); m_bChangingStance = false; //if (IsAttackMotion(motion) && success) //{ // NotifyAttackDone(); // /* // if (ShouldRepeatAttacks() && m_LastAttackTarget) // { // NotifyCommenceAttack(); // m_bChargingAttack = true; // m_ChargingAttackHeight = m_LastAttackHeight; // m_ChargingAttackTarget = m_LastAttackTarget; // m_ChargingAttackPower = m_LastAttackPower; // m_fChargeAttackStartTime = Timer::cur_time; // } // */ //} } void CPlayerWeenie::OnRegen(STypeAttribute2nd currentAttrib, int newAmount) { CMonsterWeenie::OnRegen(currentAttrib, newAmount); NotifyAttribute2ndStatUpdated(currentAttrib); } void CPlayerWeenie::NotifyAttackerEvent(const char *name, unsigned int dmgType, float healthPercent, unsigned int health, unsigned int crit, unsigned int attackConditions) { // when the player deals damage BinaryWriter msg; msg.Write(0x1B1); msg.WriteString(name); msg.Write(dmgType); msg.Write(healthPercent); msg.Write(health); msg.Write(crit); msg.Write(attackConditions); SendNetMessage(&msg, PRIVATE_MSG, TRUE, FALSE); // Update health of monster ASAP RefreshTargetHealth(); } void CPlayerWeenie::NotifyDefenderEvent(const char *name, unsigned int dmgType, float healthPercent, unsigned int health, BODY_PART_ENUM hitPart, unsigned int crit, unsigned int attackConditions) { // when the player receives damage BinaryWriter msg; msg.Write(0x1B2); msg.WriteString(name); msg.Write(dmgType); msg.Write(healthPercent); msg.Write(health); msg.Write(hitPart); msg.Write(crit); msg.Write(attackConditions); msg.Write(0); // probably DWORD align SendNetMessage(&msg, PRIVATE_MSG, TRUE, FALSE); } void CPlayerWeenie::NotifyKillerEvent(const char *text) { BinaryWriter msg; msg.Write(0x1AD); msg.WriteString(text); SendNetMessage(&msg, PRIVATE_MSG, TRUE, FALSE); } void CPlayerWeenie::NotifyVictimEvent(const char *text) { BinaryWriter msg; msg.Write(0x1AC); msg.WriteString(text); SendNetMessage(&msg, PRIVATE_MSG, TRUE, FALSE); } void CPlayerWeenie::NotifyUseDone(int error) { BinaryWriter msg; msg.Write(0x1C7); msg.Write(error); SendNetMessage(&msg, PRIVATE_MSG, TRUE, FALSE); } void CPlayerWeenie::NotifyWeenieError(int error) { BinaryWriter msg; msg.Write(0x28A); msg.Write(error); SendNetMessage(&msg, PRIVATE_MSG, TRUE, FALSE); } void CPlayerWeenie::NotifyWeenieErrorWithString(int error, const char *text) { BinaryWriter msg; msg.Write(0x28B); msg.Write(error); msg.WriteString(text); SendNetMessage(&msg, PRIVATE_MSG, TRUE, FALSE); } void CPlayerWeenie::NotifyInventoryFailedEvent(DWORD object_id, int error) { BinaryWriter msg; msg.Write(0xA0); msg.Write(object_id); msg.Write(error); SendNetMessage(&msg, PRIVATE_MSG, TRUE, FALSE); } bool CPlayerWeenie::ImmuneToDamage(CWeenieObject *other) { if (this != other) { if (other && other->AsPlayer()) { if (IsPK() && other->IsPK()) { } else if (IsPKLite() && other->IsPKLite()) { } else { PKStatusEnum selfStatus = (PKStatusEnum)InqIntQuality(PLAYER_KILLER_STATUS_INT, PKStatusEnum::Undef_PKStatus); PKStatusEnum otherStatus = (PKStatusEnum)other->InqIntQuality(PLAYER_KILLER_STATUS_INT, PKStatusEnum::Undef_PKStatus); if (selfStatus == PKStatusEnum::Baelzharon_PKStatus || otherStatus == PKStatusEnum::Baelzharon_PKStatus) { } else return true; } } } return CMonsterWeenie::ImmuneToDamage(other); } bool CPlayerWeenie::IsDead() { return CMonsterWeenie::IsDead(); } DWORD CPlayerWeenie::OnReceiveInventoryItem(CWeenieObject *source, CWeenieObject *item, DWORD desired_slot) { item->ReleaseFromAnyWeenieParent(false, true); item->SetWieldedLocation(INVENTORY_LOC::NONE_LOC); item->SetWeenieContainer(GetID()); item->ReleaseFromBlock(); DWORD result = Container_InsertInventoryItem(source->GetLandcell(), item, desired_slot); if (AsPlayer() && item->IsCurrency(item->m_Qualities.id)) RecalculateCoinAmount(item->m_Qualities.id); return result; } /* void CPlayerWeenie::HandleKilledEvent(CWeenieObject *victim, DAMAGE_TYPE damageType) { switch (damageType) { case BLUDGEON_DAMAGE_TYPE: { switch (Random::GenInt(0, 7)) { case 0: { NotifyKillerEvent(csprintf("You flatten %s's body with the force of your assault!", pTarget->GetName().c_str())); break; } case 1: { NotifyKillerEvent(csprintf("You beat %s to a lifeless pulp!", pTarget->GetName().c_str())); break; } case 2: { NotifyKillerEvent(csprintf("You smite %s mightily!", pTarget->GetName().c_str())); break; } case 3: { NotifyKillerEvent(csprintf("You knock %s into next Morningthaw!", pTarget->GetName().c_str())); break; } case 4: { NotifyKillerEvent(csprintf("%s is utterly destroyed by your attack!", pTarget->GetName().c_str())); break; } case 5: { NotifyKillerEvent(csprintf("%s catches your attack, with dire consequences!", pTarget->GetName().c_str())); break; } case 6: { NotifyKillerEvent(csprintf("The deadly force of your attack is so strong that %s's ancestors feel it!", pTarget->GetName().c_str())); break; } case 7: { NotifyKillerEvent(csprintf("The thunder of crushing %s is followed by the deafening silence of death!", pTarget->GetName().c_str())); break; } } } } } */ /* void CPlayerWeenie::NotifyVictimEvent(CWeenieObject *killer, DAMAGE_TYPE damageType) { } void CPlayerWeenie::OnDealtDamage(CWeenieObject *attacker, DAMAGE_TYPE damageType, unsigned int damage) { if (!pTarget->IsDead()) { NotifyAttackerEvent(pTarget->GetName().c_str(), 4, damageDone / (double)(pTarget->GetMaxHealth()), damageDone, 0, 0); } else { switch (Random::GenInt(0, 7)) { case 0: { NotifyKillerEvent(csprintf("You flatten %s's body with the force of your assault!", pTarget->GetName().c_str())); break; } case 1: { NotifyKillerEvent(csprintf("You beat %s to a lifeless pulp!", pTarget->GetName().c_str())); break; } case 2: { NotifyKillerEvent(csprintf("You smite %s mightily!", pTarget->GetName().c_str())); break; } case 3: { NotifyKillerEvent(csprintf("You knock %s into next Morningthaw!", pTarget->GetName().c_str())); break; } case 4: { NotifyKillerEvent(csprintf("%s is utterly destroyed by your attack!", pTarget->GetName().c_str())); break; } case 5: { NotifyKillerEvent(csprintf("%s catches your attack, with dire consequences!", pTarget->GetName().c_str())); break; } case 6: { NotifyKillerEvent(csprintf("The deadly force of your attack is so strong that %s's ancestors feel it!", pTarget->GetName().c_str())); break; } case 7: { NotifyKillerEvent(csprintf("The thunder of crushing %s is followed by the deafening silence of death!", pTarget->GetName().c_str())); break; } } } } */ void CPlayerWeenie::PreSpawnCreate() { } struct CompareManaNeeds //: public std::function { bool operator()(CWeenieObject* left, CWeenieObject* right) { // comparator for making a min-heap based on remaining mana return ((left->InqIntQuality(ITEM_MAX_MANA_INT, 0, TRUE) - left->InqIntQuality(ITEM_CUR_MANA_INT, -1, TRUE)) > (right->InqIntQuality(ITEM_MAX_MANA_INT, 0, TRUE) - right->InqIntQuality(ITEM_CUR_MANA_INT, -1, TRUE))); } }; int CPlayerWeenie::UseEx(CWeenieObject *pTool, CWeenieObject *pTarget) { // Save these for later as we may have to send a confirmation m_pCraftingTool = pTool; m_pCraftingTarget = pTarget; return UseEx(false); } int CPlayerWeenie::UseEx(bool bConfirmed) { // Load the saved crafting targets CWeenieObject *pTool = m_pCraftingTool; CWeenieObject *pTarget = m_pCraftingTarget; if (pTool == NULL || pTarget == NULL) { // no queued crafting op return WERROR_NONE; } if (pTool->m_Qualities.m_WeenieType == AugmentationDevice_WeenieType) pTool->AsAugmentationDevice()->UseEx(this, bConfirmed); int toolType = pTool->InqIntQuality(ITEM_TYPE_INT, 0); switch (toolType) { case TYPE_MANASTONE: { pTool->UseWith(this, pTarget); break; } default: { CCraftOperation *op = g_pPortalDataEx->GetCraftOperation(pTool->m_Qualities.id, pTarget->m_Qualities.id); if (!op) { //Try using our get alternative operation function for Rare Dyes, Infinite Leather, etc. op = TryGetAlternativeOperation(pTarget, pTool, op); if (!op) { //Try inversing the combination. op = g_pPortalDataEx->GetCraftOperation(pTarget->m_Qualities.id, pTool->m_Qualities.id); if (!op) return WERROR_NONE; //swap things around CWeenieObject *swapHelper = pTool; pTool = pTarget; pTarget = swapHelper; } } if (pTool->IsWielded() || pTarget->IsWielded()) return WERROR_CRAFT_ALL_OBJECTS_NOT_FROZEN; if (get_minterp()->interpreted_state.current_style != Motion_NonCombat) return WERROR_CRAFT_NOT_IN_PEACE_MODE; //if (GetWielded(WEAPON_LOC) != NULL || GetWielded(SHIELD_LOC) != NULL) // return WERROR_HANDS_NOT_FREE; int requiredHealth = 0; int requiredStamina = 0; int requiredMana = 0; for (int i = 0; i < 4; i++) { if (op->_mods[i]._RequiresHealth) requiredHealth += -op->_mods[i]._ModifyHealth; if (op->_mods[i]._RequiresStamina) requiredStamina += -op->_mods[i]._ModifyStamina; if (op->_mods[i]._RequiresMana) requiredMana += -op->_mods[i]._ModifyMana; } if (GetHealth() < requiredHealth) { SendText("You don't have enough health to do that.", LTT_CRAFT); return WERROR_CRAFT_FAILED_REQUIREMENTS; } if (requiredStamina != 0) { if (GetStamina() < requiredStamina) { //SendText("You don't have enough stamina to do that.", LTT_CRAFT); return WERROR_STAMINA_TOO_LOW; } } //else if (GetStamina() < 5) //I can't find a source but I'm pretty sure use actions always consumed some amount of stamina. //{ // //SendText("You don't have enough stamina to do that.", LTT_CRAFT); // return WERROR_STAMINA_TOO_LOW; //} if (GetMana() < requiredMana) { //SendText("You don't have enough mana to do that.", LTT_CRAFT); return WERROR_MAGIC_INSUFFICIENT_MANA; } //unk: always 0 //op->_mod unknown7 - known values: true, false - true on dying items? = Recalculate Icon? //op->_mod unknown9 - known values: 1 in stamping failures, attaching fetish of the dark idol //op->_mod unknown10 - always 0 if (!CheckUseRequirements(0, op, pTool, pTarget)) return WERROR_CRAFT_FAILED_REQUIREMENTS; if (!CheckUseRequirements(1, op, pTool, pTarget)) return WERROR_CRAFT_FAILED_REQUIREMENTS; if (!CheckUseRequirements(2, op, pTool, pTarget)) return WERROR_CRAFT_FAILED_REQUIREMENTS; int targetStackSize = pTarget->GetStackOrStructureNum(); int toolStackSize = pTool->GetStackOrStructureNum(); if (targetStackSize < op->_successConsumeTargetAmount || toolStackSize < op->_successConsumeToolAmount || targetStackSize < op->_failureConsumeTargetAmount || toolStackSize < op->_failureConsumeToolAmount) return WERROR_CRAFT_DONT_CONTAIN_EVERYTHING; DWORD skillLevel = 0; if (op->_skill != 0) { SKILL_ADVANCEMENT_CLASS sac; m_Qualities.InqSkillAdvancementClass((STypeSkill)op->_skill, sac); if (sac < TRAINED_SKILL_ADVANCEMENT_CLASS) return WERROR_CRAFT_NOT_HAVE_SKILL; InqSkill(op->_skill, skillLevel, FALSE); } double successChance; switch (op->_SkillCheckFormulaType) { case 0: if (skillLevel == 0) { // success = true successChance = 1.0; } else { successChance = GetSkillChance(skillLevel, op->_difficulty); } break; case 1: //tinkers case 2: //imbues { double toolWorkmanship = pTool->InqIntQuality(ITEM_WORKMANSHIP_INT, 0); double itemWorkmanship = pTarget->InqIntQuality(ITEM_WORKMANSHIP_INT, 0); toolWorkmanship /= (double)pTool->InqIntQuality(NUM_ITEMS_IN_MATERIAL_INT, 1); int amountOfTimesTinkered = pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0); if (amountOfTimesTinkered > 9) // Don't allow 10 tinked items to have any more tinkers/imbues (Ivory & Leather don't use this case) { return WERROR_NONE; } int salvageMod; if (op->_SkillCheckFormulaType == 1) { salvageMod = GetMaterialMod(pTool->InqIntQuality(MATERIAL_TYPE_INT, 0)); } else { salvageMod = 20; // All imbue materials have mod of 20 } int multiple = 1; double aDifficulty[10] = {1, 1.1, 1.3, 1.6, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5}; // Attempt difficulty numbers from Endy's Tinker Calc 2.3.2 double difficulty = aDifficulty[amountOfTimesTinkered]; if (toolWorkmanship >= itemWorkmanship) { multiple = 2; } successChance = GetSkillChance(skillLevel, ((int)floor(((5 * salvageMod) + (2 * itemWorkmanship * salvageMod) - (toolWorkmanship * multiple * salvageMod / 5)) * difficulty))); //Formulas from Endy's Tinkering Calculator if (op->_SkillCheckFormulaType == 2) // imbue { successChance /= 3; successChance = min(successChance, 0.33); if (m_Qualities.GetInt(AUGMENTATION_BONUS_IMBUE_CHANCE_INT, 0)) successChance += 0.05; } switch (pTool->m_Qualities.id) { case W_MATERIALRAREFOOLPROOFAQUAMARINE_CLASS: case W_MATERIALRAREFOOLPROOFBLACKGARNET_CLASS: case W_MATERIALRAREFOOLPROOFBLACKOPAL_CLASS: case W_MATERIALRAREFOOLPROOFEMERALD_CLASS: case W_MATERIALRAREFOOLPROOFFIREOPAL_CLASS: case W_MATERIALRAREFOOLPROOFIMPERIALTOPAZ_CLASS: case W_MATERIALRAREFOOLPROOFJET_CLASS: case W_MATERIALRAREFOOLPROOFPERIDOT_CLASS: case W_MATERIALRAREFOOLPROOFREDGARNET_CLASS: case W_MATERIALRAREFOOLPROOFSUNSTONE_CLASS: case W_MATERIALRAREFOOLPROOFWHITESAPPHIRE_CLASS: case W_MATERIALRAREFOOLPROOFYELLOWTOPAZ_CLASS: case W_MATERIALRAREFOOLPROOFZIRCON_CLASS: successChance = 1.0; } break; } default: SendText(csprintf("Unsupported skill check formula type: %u.", op->_SkillCheckFormulaType), LTT_CRAFT); return WERROR_CRAFT_DONT_CONTAIN_EVERYTHING; } // 0x80000000 : Use Crafting Chance of Success Dialog if (_playerModule.options_ & 0x80000000 && !bConfirmed ) { std::ostringstream sstrMessage; sstrMessage.precision(3); sstrMessage << "You have a " << successChance*100 << "% chance of using " << pTool->GetName() << " on " << pTarget->GetName() << "."; BinaryWriter confirmCrafting; confirmCrafting.Write(0x274); // Message Type confirmCrafting.Write(0x05); // Confirm type (craft) confirmCrafting.Write(0); // Sequence number?? confirmCrafting.WriteString(sstrMessage.str()); SendNetMessage(&confirmCrafting, PRIVATE_MSG, TRUE, FALSE); return WERROR_NONE; } double successRoll = Random::RollDice(0.0, 1.0); if (successRoll <= successChance) { // Results! // Create item(s) CWeenieObject *newItem = g_pWeenieFactory->CreateWeenieByClassID(op->_successWcid, NULL, false); if (op->_successAmount > 1) newItem->SetStackSize(op->_successAmount); // if we can't put the item in inventory if (op->_successWcid != 0 && !SpawnInContainer(newItem, op->_successAmount)) { SendText("Unable to create result!", LTT_ERROR); // Return before we delete the tool/target // TODO: is this to do with pack space? should check before the roll if so return WERROR_NONE; } if (op->_successMessage != "") SendText(op->_successMessage.c_str(), LTT_CRAFT); // Broadcast messages for tinkering switch (op->_SkillCheckFormulaType) { case 1: //tinkers case 2: //imbues { double toolWorkmanship = pTool->InqIntQuality(ITEM_WORKMANSHIP_INT, 0); double itemWorkmanship = pTarget->InqIntQuality(ITEM_WORKMANSHIP_INT, 0); if (pTool->InqIntQuality(ITEM_TYPE_INT, 0) == ITEM_TYPE::TYPE_TINKERING_MATERIAL) toolWorkmanship /= (double)pTool->InqIntQuality(NUM_ITEMS_IN_MATERIAL_INT, 1); int amountOfTimesTinkered = pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0); std::string text = csprintf("%s successfully applies the %s (workmanship %.2f) to the %s.", GetName().c_str(), pTool->GetName().c_str(), toolWorkmanship, pTarget->GetName().c_str()); if (!text.empty()) { g_pWorld->BroadcastLocal(GetLandcell(), text); } IMBUE_LOG << "P:" << InqStringQuality(NAME_STRING, "") << " SL:" << skillLevel << " T:" << pTarget->InqStringQuality(NAME_STRING, "") << " TW:" << itemWorkmanship << " TT:" << amountOfTimesTinkered << " M:" << pTool->InqStringQuality(NAME_STRING, "") << " MW:" << toolWorkmanship << " %:" << successChance << " Roll:" << successRoll; break; } } PerformUseModifications(0, op, pTool, pTarget, newItem); PerformUseModifications(1, op, pTool, pTarget, newItem); PerformUseModifications(2, op, pTool, pTarget, newItem); PerformUseModifications(3, op, pTool, pTarget, newItem); if (op->_successConsumeTargetChance == 1.0 || Random::RollDice(0.0, 1.0) <= op->_successConsumeTargetChance) { pTarget->DecrementStackNum(op->_successConsumeTargetAmount); if (!op->_successConsumeTargetMessage.empty()) SendText(op->_successConsumeTargetMessage.c_str(), LTT_CRAFT); } if (op->_successConsumeToolChance == 1.0 || Random::RollDice(0.0, 1.0) <= op->_successConsumeToolChance) { pTool->DecrementStackNum(op->_successConsumeToolAmount); if (!op->_successConsumeToolMessage.empty()) SendText(op->_successConsumeToolMessage.c_str(), LTT_CRAFT); } } else // Not successful { // Results! // Create item(s) CWeenieObject *newItem = g_pWeenieFactory->CreateWeenieByClassID(op->_failWcid, NULL, false); if (op->_failAmount > 1) newItem->SetStackSize(op->_failAmount); // if we can't put the item in inventory if (op->_failWcid != 0 && !SpawnInContainer(newItem, op->_failAmount)) { SendText("Unable to create result!", LTT_ERROR); // Return before we delete the tool/target // TODO: is this to do with pack space? should check before the roll if so return WERROR_NONE; } if (op->_failMessage != "") SendText(op->_failMessage.c_str(), LTT_CRAFT); // Broadcast messages for tinkering switch (op->_SkillCheckFormulaType) { case 1: //tinkers case 2: //imbues { double toolWorkmanship = pTool->InqIntQuality(ITEM_WORKMANSHIP_INT, 0); double itemWorkmanship = pTarget->InqIntQuality(ITEM_WORKMANSHIP_INT, 0); if (pTool->InqIntQuality(ITEM_TYPE_INT, 0) == ITEM_TYPE::TYPE_TINKERING_MATERIAL) toolWorkmanship /= (double)pTool->InqIntQuality(NUM_ITEMS_IN_MATERIAL_INT, 1); int amountOfTimesTinkered = pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0); std::string text = csprintf("%s fails to apply the %s (workmanship %.2f) to the %s. The target is destroyed.", GetName().c_str(), pTool->GetName().c_str(), toolWorkmanship, pTarget->GetName().c_str()); if (!text.empty()) { g_pWorld->BroadcastLocal(GetLandcell(), text); } IMBUE_LOG << "P:" << InqStringQuality(NAME_STRING, "") << " SL:" << skillLevel << " T:" << pTarget->InqStringQuality(NAME_STRING, "") << " TW:" << itemWorkmanship << " TT:" << amountOfTimesTinkered << " M:" << pTool->InqStringQuality(NAME_STRING, "") << " MW:" << toolWorkmanship << " %:" << successChance << " Roll:" << successRoll; break; } } PerformUseModifications(4, op, pTool, pTarget, newItem); PerformUseModifications(5, op, pTool, pTarget, newItem); PerformUseModifications(6, op, pTool, pTarget, newItem); PerformUseModifications(7, op, pTool, pTarget, newItem); if (op->_failureConsumeTargetChance == 1.0 || Random::RollDice(0.0, 1.0) <= op->_failureConsumeTargetChance) { pTarget->DecrementStackNum(op->_failureConsumeTargetAmount); if (!op->_failureConsumeTargetMessage.empty()) SendText(op->_failureConsumeTargetMessage.c_str(), LTT_CRAFT); } if (op->_failureConsumeToolChance == 1.0 || Random::RollDice(0.0, 1.0) <= op->_failureConsumeToolChance) { pTool->DecrementStackNum(op->_failureConsumeToolAmount); if (!op->_failureConsumeToolMessage.empty()) SendText(op->_failureConsumeToolMessage.c_str(), LTT_CRAFT); } // Your craft attempt fails. NotifyWeenieError(0x432); } // We don't need these anymore m_pCraftingTool = NULL; m_pCraftingTarget = NULL; RecalculateEncumbrance(); break; } } return WERROR_NONE; } int CPlayerWeenie::GetMaterialMod(int materialInt) { switch (materialInt) { case Gold_MaterialType: case Oak_MaterialType: { return 10; } case Ebony_MaterialType: case Teak_MaterialType: case Steel_MaterialType : case Satin_MaterialType: case Porcelain_MaterialType: case Mahogany_MaterialType: case Iron_MaterialType: case Green_Garnet_MaterialType: { return 12; } case Alabaster_MaterialType: case Brass_MaterialType: case Armoredillo_Hide_MaterialType: case Wool_MaterialType: case Velvet_MaterialType: case Reed_Shark_Hide_MaterialType: case Pine_MaterialType: case Opal_MaterialType: case Marble_MaterialType: case Linen_MaterialType: case Granite_MaterialType: case Ceramic_MaterialType: case Bronze_MaterialType: case Moonstone_MaterialType: { return 11; } case Bloodstone_MaterialType: case Rose_Quartz_MaterialType: case Red_Jade_MaterialType: case Malachite_MaterialType: case Lavender_Jade_MaterialType: case Hematite_MaterialType: case Citrine_MaterialType: case Carnelian_MaterialType: { return 25; } default: return 20; // Imbue material } } std::string CPlayerWeenie::ToUpperCase(string tName) { transform(tName.begin(), tName.end(), tName.begin(), ::toupper); return tName; } bool CPlayerWeenie::CheckUseRequirements(int index, CCraftOperation *op, CWeenieObject *pTool, CWeenieObject *pTarget) { CWeenieObject *requirementTarget; switch (index) { case 0: requirementTarget = pTarget; break; case 1: requirementTarget = pTool; break; case 2: requirementTarget = this; break; default: #ifdef _DEBUG assert(false); #endif return false; //should never happen } if (!op->_requirements[index]._intRequirement.empty()) { for each (TYPERequirement intRequirement in op->_requirements[index]._intRequirement) { int value = 0; bool exists = requirementTarget->m_Qualities.InqInt(intRequirement._stat, value, true, true); switch (intRequirement._operationType) { case 0: //> - unconfirmed if (value > intRequirement._value) { SendText(intRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 1: //<= if (value <= intRequirement._value) { SendText(intRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 2: //< if (value < intRequirement._value) { SendText(intRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 3: //>= if (value >= intRequirement._value) { SendText(intRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 4: //!= if (value != intRequirement._value) { SendText(intRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 5: //doesnt exist or != if (!exists || value != intRequirement._value) { SendText(intRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 6: //exists and == if (exists && value == intRequirement._value) { SendText(intRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 7: //doesnt exist if (!exists) { SendText(intRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 8: //exists and != (or maybe just exists) if (exists && value != intRequirement._value) { SendText(intRequirement._message.c_str(), LTT_CRAFT); return false; } break; default: #ifdef _DEBUG assert(false); #endif break; } } } if (!op->_requirements[index]._boolRequirement.empty()) { for each (TYPERequirement boolRequirement in op->_requirements[index]._boolRequirement) { int value = 0; bool exists = requirementTarget->m_Qualities.InqBool(boolRequirement._stat, value); switch (boolRequirement._operationType) { case 0: //> - unconfirmed if (value > boolRequirement._value) { SendText(boolRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 1: //<= if (value <= boolRequirement._value) { SendText(boolRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 2: //< if (value < boolRequirement._value) { SendText(boolRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 3: //>= if (value >= boolRequirement._value) { SendText(boolRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 4: //!= if (value != boolRequirement._value) { SendText(boolRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 5: //doesnt exist or != if (!exists || value != boolRequirement._value) { SendText(boolRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 6: //exists and == if (exists && value == boolRequirement._value) { SendText(boolRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 7: //doesnt exist if (!exists) { SendText(boolRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 8: //exists and != (or maybe just exists) if (exists && value != boolRequirement._value) { SendText(boolRequirement._message.c_str(), LTT_CRAFT); return false; } break; default: #ifdef _DEBUG assert(false); #endif break; } } } if (!op->_requirements[index]._floatRequirement.empty()) { for each (TYPERequirement floatRequirement in op->_requirements[index]._floatRequirement) { double value = 0; bool exists = requirementTarget->m_Qualities.InqFloat(floatRequirement._stat, value, true); switch (floatRequirement._operationType) { case 0: //> - unconfirmed if (value > floatRequirement._value) { SendText(floatRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 1: //<= if (value <= floatRequirement._value) { SendText(floatRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 2: //< if (value < floatRequirement._value) { SendText(floatRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 3: //>= if (value >= floatRequirement._value) { SendText(floatRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 4: //!= if (value != floatRequirement._value) { SendText(floatRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 5: //doesnt exist or != if (!exists || value != floatRequirement._value) { SendText(floatRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 6: //exists and == if (exists && value == floatRequirement._value) { SendText(floatRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 7: //doesnt exist if (!exists) { SendText(floatRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 8: //exists and != (or maybe just exists) if (exists && value != floatRequirement._value) { SendText(floatRequirement._message.c_str(), LTT_CRAFT); return false; } break; default: #ifdef _DEBUG assert(false); #endif break; } } } if (!op->_requirements[index]._stringRequirement.empty()) { for each (TYPERequirement stringRequirement in op->_requirements[index]._stringRequirement) { std::string value = ""; bool exists = requirementTarget->m_Qualities.InqString(stringRequirement._stat, value); switch (stringRequirement._operationType) { case 0: //> - unconfirmed if (value > stringRequirement._value) { SendText(stringRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 1: //<= if (value <= stringRequirement._value) { SendText(stringRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 2: //< if (value < stringRequirement._value) { SendText(stringRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 3: //>= if (value >= stringRequirement._value) { SendText(stringRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 4: //!= if (value != stringRequirement._value) { SendText(stringRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 5: //doesnt exist or != if (!exists || value != stringRequirement._value) { SendText(stringRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 6: //exists and == if (exists && value == stringRequirement._value) { SendText(stringRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 7: //doesnt exist if (!exists) { SendText(stringRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 8: //exists and != (or maybe just exists) if (exists && value != stringRequirement._value) { SendText(stringRequirement._message.c_str(), LTT_CRAFT); return false; } break; default: #ifdef _DEBUG assert(false); #endif break; } } } if (!op->_requirements[index]._didRequirement.empty()) { for each (TYPERequirement dIDRequirement in op->_requirements[index]._didRequirement) { DWORD value = 0; bool exists = requirementTarget->m_Qualities.InqDataID(dIDRequirement._stat, value); switch (dIDRequirement._operationType) { case 0: //> - unconfirmed if (value > dIDRequirement._value) { SendText(dIDRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 1: //<= if (value <= dIDRequirement._value) { SendText(dIDRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 2: //< if (value < dIDRequirement._value) { SendText(dIDRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 3: //>= if (value >= dIDRequirement._value) { SendText(dIDRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 4: //!= if (value != dIDRequirement._value) { SendText(dIDRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 5: //doesnt exist or != if (!exists || value != dIDRequirement._value) { SendText(dIDRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 6: //exists and == if (exists && value == dIDRequirement._value) { SendText(dIDRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 7: //doesnt exist if (!exists) { SendText(dIDRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 8: //exists and != (or maybe just exists) if (exists && value != dIDRequirement._value) { SendText(dIDRequirement._message.c_str(), LTT_CRAFT); return false; } break; default: #ifdef _DEBUG assert(false); #endif break; } } } if (!op->_requirements[index]._iidRequirement.empty()) { for each (TYPERequirement iIDRequirement in op->_requirements[index]._iidRequirement) { DWORD value = 0; bool exists = requirementTarget->m_Qualities.InqInstanceID(iIDRequirement._stat, value); switch (iIDRequirement._operationType) { case 0: //> - unconfirmed if (value > iIDRequirement._value) { SendText(iIDRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 1: //<= if (value <= iIDRequirement._value) { SendText(iIDRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 2: //< if (value < iIDRequirement._value) { SendText(iIDRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 3: //>= if (value >= iIDRequirement._value) { SendText(iIDRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 4: //!= if (value != iIDRequirement._value) { SendText(iIDRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 5: //doesnt exist or != if (!exists || value != iIDRequirement._value) { SendText(iIDRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 6: //exists and == if (exists && value == iIDRequirement._value) { SendText(iIDRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 7: //doesnt exist if (!exists) { SendText(iIDRequirement._message.c_str(), LTT_CRAFT); return false; } break; case 8: //exists and != (or maybe just exists) if (exists && value != iIDRequirement._value) { SendText(iIDRequirement._message.c_str(), LTT_CRAFT); return false; } break; default: #ifdef _DEBUG assert(false); #endif break; } } } return true; } void CPlayerWeenie::PerformUseModificationScript(DWORD scriptId, CCraftOperation *op, CWeenieObject *pTool, CWeenieObject *pTarget, CWeenieObject *pCreatedItem) { double currentVar = pTarget->InqFloatQuality(DAMAGE_VARIANCE_FLOAT, 0, TRUE); switch (scriptId) { case 0x3800000f: //flag stamp failure //not sure what is supposed to be done here. break; case 0x38000011: //steel pTarget->m_Qualities.SetInt(ARMOR_LEVEL_INT, pTarget->InqIntQuality(ARMOR_LEVEL_INT, 0, TRUE) + 20); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x38000012: //armoredillo hide pTarget->m_Qualities.SetFloat(ARMOR_MOD_VS_ACID_FLOAT, min(pTarget->InqFloatQuality(ARMOR_MOD_VS_ACID_FLOAT, 0, TRUE) + 0.4, 2.0)); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x38000013: //marble pTarget->m_Qualities.SetFloat(ARMOR_MOD_VS_BLUDGEON_FLOAT, min(pTarget->InqFloatQuality(ARMOR_MOD_VS_BLUDGEON_FLOAT, 0, TRUE) + 0.2, 2.0)); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x38000014: //wool pTarget->m_Qualities.SetFloat(ARMOR_MOD_VS_COLD_FLOAT, min(pTarget->InqFloatQuality(ARMOR_MOD_VS_COLD_FLOAT, 0, TRUE) + 0.4, 2.0)); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x38000015: //reedshark hide pTarget->m_Qualities.SetFloat(ARMOR_MOD_VS_ELECTRIC_FLOAT, min(pTarget->InqFloatQuality(ARMOR_MOD_VS_ELECTRIC_FLOAT, 0, TRUE) + 0.4, 2.0)); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x38000016: //ceramic pTarget->m_Qualities.SetFloat(ARMOR_MOD_VS_FIRE_FLOAT, min(pTarget->InqFloatQuality(ARMOR_MOD_VS_FIRE_FLOAT, 0, TRUE) + 0.4, 2.0)); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x38000017: //alabaster pTarget->m_Qualities.SetFloat(ARMOR_MOD_VS_PIERCE_FLOAT, min(pTarget->InqFloatQuality(ARMOR_MOD_VS_PIERCE_FLOAT, 0, TRUE) + 0.2, 2.0)); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x38000018: //bronze pTarget->m_Qualities.SetFloat(ARMOR_MOD_VS_SLASH_FLOAT, min(pTarget->InqFloatQuality(ARMOR_MOD_VS_SLASH_FLOAT, 0, TRUE) + 0.2, 2.0)); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x38000019: //linen { double newEncumbrance = pTarget->InqIntQuality(ENCUMB_VAL_INT, 1, TRUE); newEncumbrance = max(newEncumbrance * 0.85, 1.0); pTarget->m_Qualities.SetInt(ENCUMB_VAL_INT, (int)round(newEncumbrance)); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; } case 0x3800001a: //iron pTarget->m_Qualities.SetInt(DAMAGE_INT, pTarget->InqIntQuality(DAMAGE_INT, 0, TRUE) + 1); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x3800001b: //mahogany pTarget->m_Qualities.SetFloat(DAMAGE_MOD_FLOAT, pTarget->InqFloatQuality(DAMAGE_MOD_FLOAT, 0, TRUE) + 0.04); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x3800001c: //granite pTarget->m_Qualities.SetFloat(DAMAGE_VARIANCE_FLOAT, max(currentVar - (currentVar / 5), 0.0)); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x3800001d: //oak pTarget->m_Qualities.SetInt(WEAPON_TIME_INT, max(pTarget->InqIntQuality(WEAPON_TIME_INT, 0, TRUE) - 50, 0)); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x3800001e: //pine pTarget->m_Qualities.SetInt(VALUE_INT, max(pTarget->InqIntQuality(VALUE_INT, 0, TRUE) * 0.75, 1.0)); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x3800001f: //gold pTarget->m_Qualities.SetInt(VALUE_INT, pTarget->InqIntQuality(VALUE_INT, 0, TRUE) * 1.25); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x38000020: //brass pTarget->m_Qualities.SetFloat(WEAPON_DEFENSE_FLOAT, pTarget->InqFloatQuality(WEAPON_DEFENSE_FLOAT, 0, TRUE) + 0.01); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x38000021: //velvet pTarget->m_Qualities.SetFloat(WEAPON_OFFENSE_FLOAT, pTarget->InqFloatQuality(WEAPON_OFFENSE_FLOAT, 0, TRUE) + 0.01); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x38000023: //black opal pTarget->AddImbueEffect(ImbuedEffectType::CriticalStrike_ImbuedEffectType); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x38000024: //fire opal pTarget->AddImbueEffect(ImbuedEffectType::CripplingBlow_ImbuedEffectType); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x38000025: //sunstone pTarget->AddImbueEffect(ImbuedEffectType::ArmorRending_ImbuedEffectType); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x3800002e: //opal pTarget->m_Qualities.SetFloat(MANA_CONVERSION_MOD_FLOAT, pTarget->InqFloatQuality(MANA_CONVERSION_MOD_FLOAT, 0, TRUE) + 0.01); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x3800002f: //moonstone pTarget->m_Qualities.SetInt(ITEM_MAX_MANA_INT, max(pTarget->InqIntQuality(ITEM_MAX_MANA_INT, 0, TRUE) + 500, 0)); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x38000034: //silver { STypeSkill oldRequirement = (STypeSkill)pTarget->InqIntQuality(WIELD_SKILLTYPE_INT, 0); if (oldRequirement == MELEE_DEFENSE_SKILL) { pTarget->m_Qualities.SetInt(WIELD_SKILLTYPE_INT, MISSILE_DEFENSE_SKILL); pTarget->m_Qualities.SetDataID(ITEM_SKILL_LIMIT_DID, MISSILE_DEFENSE_SKILL); int oldValue = pTarget->InqIntQuality(WIELD_DIFFICULTY_INT, 0); int newValue; switch (oldValue) { case 200: newValue = 160; break; case 250: newValue = 205; break; case 300: newValue = 245; break; case 325: newValue = 270; break; case 350: newValue = 290; break; case 370: newValue = 305; break; case 400: newValue = 330; break; default: newValue = oldValue * 0.8; //todo: figure out the exact formula for this conversion. break; } pTarget->m_Qualities.SetInt(WIELD_DIFFICULTY_INT, newValue); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); } break; } case 0x38000035: //copper { STypeSkill oldRequirement = (STypeSkill)pTarget->InqIntQuality(WIELD_SKILLTYPE_INT, 0); if (oldRequirement == MISSILE_DEFENSE_SKILL) { pTarget->m_Qualities.SetInt(WIELD_SKILLTYPE_INT, MELEE_DEFENSE_SKILL); pTarget->m_Qualities.SetDataID(ITEM_SKILL_LIMIT_DID, MELEE_DEFENSE_SKILL); int oldValue = pTarget->InqIntQuality(WIELD_DIFFICULTY_INT, 0); int newValue; switch (oldValue) { case 160: newValue = 200; break; case 205: newValue = 250; break; case 245: newValue = 300; break; case 270: newValue = 325; break; case 290: newValue = 350; break; case 305: newValue = 370; break; case 330: newValue = 400; break; default: newValue = oldValue * 1.25; //todo: figure out the exact formula for this conversion. break; } pTarget->m_Qualities.SetInt(WIELD_DIFFICULTY_INT, newValue); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); } break; } case 0x38000036: //silk { int allegianceRank; if (pTarget->m_Qualities.InqInt(ITEM_ALLEGIANCE_RANK_LIMIT_INT, allegianceRank, TRUE)) { pTarget->m_Qualities.RemoveInt(ITEM_ALLEGIANCE_RANK_LIMIT_INT); int spellCraft = pTarget->InqIntQuality(ITEM_SPELLCRAFT_INT, 0, TRUE); pTarget->m_Qualities.SetInt(ITEM_DIFFICULTY_INT, pTarget->InqIntQuality(ITEM_DIFFICULTY_INT, 0, TRUE) + spellCraft); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); } break; } case 0x38000037: //zircon pTarget->AddImbueEffect(ImbuedEffectType::MagicDefense_ImbuedEffectType); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x38000038: //peridot pTarget->AddImbueEffect(ImbuedEffectType::MeleeDefense_ImbuedEffectType); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x38000039: //yellow topaz pTarget->AddImbueEffect(ImbuedEffectType::MissileDefense_ImbuedEffectType); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x3800003a: //emerald pTarget->AddImbueEffect(ImbuedEffectType::AcidRending_ImbuedEffectType); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x3800003b: //white sapphire pTarget->AddImbueEffect(ImbuedEffectType::BludgeonRending_ImbuedEffectType); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x3800003c: //aquamarine pTarget->AddImbueEffect(ImbuedEffectType::ColdRending_ImbuedEffectType); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x3800003d: //jet pTarget->AddImbueEffect(ImbuedEffectType::ElectricRending_ImbuedEffectType); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x3800003e: //red garnet pTarget->AddImbueEffect(ImbuedEffectType::FireRending_ImbuedEffectType); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x3800003f: //black garnet pTarget->AddImbueEffect(ImbuedEffectType::PierceRending_ImbuedEffectType); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x38000040: //imperial topaz pTarget->AddImbueEffect(ImbuedEffectType::SlashRending_ImbuedEffectType); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x38000041: //azurite, malachite, citrine, hematite, lavender jade, red jade, carnelian, lapis lazuli, agate, rose quartz, smokey quartz, bloodstone pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x38000042: //ebony, porcelain, teak { HeritageGroup heritageRequirement = (HeritageGroup)pTarget->InqIntQuality(HERITAGE_GROUP_INT, 0, TRUE); if (heritageRequirement != Invalid_HeritageGroup) { HeritageGroup heritageTarget; switch (pTool->InqIntQuality(MATERIAL_TYPE_INT, 0, TRUE)) { case MaterialType::Ebony_MaterialType: heritageTarget = Gharundim_HeritageGroup; break; case MaterialType::Porcelain_MaterialType: heritageTarget = Sho_HeritageGroup; break; case MaterialType::Teak_MaterialType: heritageTarget = Aluvian_HeritageGroup; break; default: return; } pTarget->m_Qualities.SetInt(HERITAGE_GROUP_INT, heritageTarget); switch (heritageTarget) { case Aluvian_HeritageGroup: pTarget->m_Qualities.SetString(ITEM_HERITAGE_GROUP_RESTRICTION_STRING, "Aluvian"); break; case Gharundim_HeritageGroup: pTarget->m_Qualities.SetString(ITEM_HERITAGE_GROUP_RESTRICTION_STRING, "Gharu'ndim"); break; case Sho_HeritageGroup: pTarget->m_Qualities.SetString(ITEM_HERITAGE_GROUP_RESTRICTION_STRING, "Sho"); break; } pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); } break; } case 0x38000043: //leather pTarget->m_Qualities.SetBool(RETAINED_BOOL, 1); break; case 0x38000044: //green garnet pTarget->m_Qualities.SetFloat(ELEMENTAL_DAMAGE_MOD_FLOAT, pTarget->InqFloatQuality(ELEMENTAL_DAMAGE_MOD_FLOAT, 0, TRUE) + 0.01); pTarget->m_Qualities.SetInt(NUM_TIMES_TINKERED_INT, pTarget->InqIntQuality(NUM_TIMES_TINKERED_INT, 0, TRUE) + 1); break; case 0x38000046: //fetish of the dark idol pTarget->AddImbueEffect(ImbuedEffectType::IgnoreSomeMagicProjectileDamage_ImbuedEffectType); break; default: SendText(csprintf("Tried to run unsupported modification script: %u.", scriptId), LTT_CRAFT); break; } } void CPlayerWeenie::PerformUseModifications(int index, CCraftOperation *op, CWeenieObject *pTool, CWeenieObject *pTarget, CWeenieObject *pCreatedItem) { if (op->_mods[index]._modificationScriptId) PerformUseModificationScript(op->_mods[index]._modificationScriptId, op, pTool, pTarget, pCreatedItem); if (op->_mods[index]._ModifyHealth) AdjustHealth(op->_mods[index]._ModifyHealth); if (op->_mods[index]._ModifyStamina) AdjustStamina(op->_mods[index]._ModifyStamina); if (op->_mods[index]._ModifyMana) AdjustMana(op->_mods[index]._ModifyMana); if (!op->_mods[index]._intMod.empty()) { for each (TYPEMod intMod in op->_mods[index]._intMod) { bool applyToCreatedItem = false; bool removeFromTarget = false; CWeenieObject *modificationSource = NULL; switch (intMod._unk) //this is a guess { case 0: modificationSource = this; break; case 1: modificationSource = pTool; break; case 60: //dying armor entries have a second entry to armor reduction that has -30 armor and _unk value of 60 //not sure what to do with that so we skip it. continue; break; default: #ifdef _DEBUG assert(false); #endif break; //should never happen } int value = pTarget->InqIntQuality(intMod._stat, 0, true); switch (intMod._operationType) { case 1: //= value = intMod._value; if (value == 0) removeFromTarget = true; break; case 2: //+ value += intMod._value; if (value < 0) value = 0; break; case 3: //copy value from modificationSource to target if (modificationSource) value = modificationSource->InqIntQuality(intMod._stat, 0); break; case 4: //copy value from modificationSource to created item applyToCreatedItem = true; if (modificationSource) value = modificationSource->InqIntQuality(intMod._stat, 0); break; case 7: //add spell { CSpellTable * pSpellTable = MagicSystem::GetSpellTable(); if (!intMod._stat || !pSpellTable || !pSpellTable->GetSpellBase(intMod._stat)) break; pTarget->m_Qualities.AddSpell(intMod._stat); break; } default: #ifdef _DEBUG assert(false); #endif break; } if (pCreatedItem && applyToCreatedItem) { pCreatedItem->m_Qualities.SetInt(intMod._stat, value); pCreatedItem->NotifyIntStatUpdated(intMod._stat, false); } if (removeFromTarget) { pTarget->m_Qualities.RemoveInt(intMod._stat); } else { pTarget->m_Qualities.SetInt(intMod._stat, value); pTarget->NotifyIntStatUpdated(intMod._stat, false); } } } if (!op->_mods[index]._boolMod.empty()) { for each (TYPEMod boolMod in op->_mods[index]._boolMod) { bool applyToCreatedItem = false; CWeenieObject *modificationSource = NULL; switch (boolMod._unk) //this is a guess { case 0: modificationSource = this; break; case 1: modificationSource = pTool; break; default: #ifdef _DEBUG assert(false); #endif break; //should never happen } int value = pTarget->InqBoolQuality(boolMod._stat, 0); switch (boolMod._operationType) { case 1: //= value = boolMod._value; break; case 2: //+ value += boolMod._value; break; case 3: //copy value from modificationSource to target if (modificationSource) value = modificationSource->InqBoolQuality(boolMod._stat, 0); break; case 4: //copy value from modificationSource to created item applyToCreatedItem = true; if (modificationSource) value = modificationSource->InqBoolQuality(boolMod._stat, 0); break; case 7: //add spell #ifdef _DEBUG assert(false); #endif break; default: #ifdef _DEBUG assert(false); #endif break; } if (pCreatedItem && applyToCreatedItem) { pCreatedItem->m_Qualities.SetBool(boolMod._stat, value); pCreatedItem->NotifyBoolStatUpdated(boolMod._stat, false); } else { pTarget->m_Qualities.SetBool(boolMod._stat, value); pTarget->NotifyBoolStatUpdated(boolMod._stat, false); } } } if (!op->_mods[index]._floatMod.empty()) { for each (TYPEMod floatMod in op->_mods[index]._floatMod) { bool applyToCreatedItem = false; CWeenieObject *modificationSource = NULL; switch (floatMod._unk) //this is a guess { case 0: modificationSource = this; break; case 1: modificationSource = pTool; break; default: #ifdef _DEBUG assert(false); #endif break; //should never happen } double value = pTarget->InqFloatQuality(floatMod._stat, 0, true); switch (floatMod._operationType) { case 1: //= value = floatMod._value; break; case 2: //+ value += floatMod._value; if (value < 0.0) value = 0.0; break; case 3: //copy value from modificationSource to target if (modificationSource) value = modificationSource->InqFloatQuality(floatMod._stat, 0); break; case 4: //copy value from modificationSource to created item applyToCreatedItem = true; if (modificationSource) value = modificationSource->InqFloatQuality(floatMod._stat, 0); break; case 7: //add spell #ifdef _DEBUG assert(false); #endif break; default: #ifdef _DEBUG assert(false); #endif break; } if (pCreatedItem && applyToCreatedItem) { pCreatedItem->m_Qualities.SetFloat(floatMod._stat, value); pCreatedItem->NotifyFloatStatUpdated(floatMod._stat, false); } else { pTarget->m_Qualities.SetFloat(floatMod._stat, value); pTarget->NotifyFloatStatUpdated(floatMod._stat, false); } } } if (!op->_mods[index]._stringMod.empty()) { for each (TYPEMod stringMod in op->_mods[index]._stringMod) { bool applyToCreatedItem = false; bool removeFromTarget = false; CWeenieObject *modificationSource = NULL; switch (stringMod._unk) //this is a guess { case 0: modificationSource = this; break; case 1: modificationSource = pTool; break; default: #ifdef _DEBUG assert(false); #endif break; //should never happen } std::string value = pTarget->InqStringQuality(stringMod._stat, ""); switch (stringMod._operationType) { case 1: //= value = stringMod._value; if (value == "") removeFromTarget = true; break; case 2: //+ value += stringMod._value; break; case 3: //copy value from modificationSource to target if (modificationSource) { switch (stringMod._stat) { case TINKER_NAME_STRING: case IMBUER_NAME_STRING: case CRAFTSMAN_NAME_STRING: value = this->InqStringQuality(NAME_STRING, ""); break; default: value = modificationSource->InqStringQuality(stringMod._stat, ""); break; } } break; case 4: //copy value from modificationSource to created item applyToCreatedItem = true; if (modificationSource) { switch (stringMod._stat) { case TINKER_NAME_STRING: case IMBUER_NAME_STRING: case CRAFTSMAN_NAME_STRING: value = this->InqStringQuality(NAME_STRING, ""); break; default: value = modificationSource->InqStringQuality(stringMod._stat, ""); break; } } break; case 7: //add spell #ifdef _DEBUG assert(false); #endif break; default: #ifdef _DEBUG assert(false); #endif break; } if (pCreatedItem && applyToCreatedItem) { pCreatedItem->m_Qualities.SetString(stringMod._stat, value); pCreatedItem->NotifyStringStatUpdated(stringMod._stat, false); } if (removeFromTarget) { pTarget->m_Qualities.RemoveString(stringMod._stat); } else { pTarget->m_Qualities.SetString(stringMod._stat, value); pTarget->NotifyStringStatUpdated(stringMod._stat, false); } } } if (!op->_mods[index]._didMod.empty()) { for each (TYPEMod didMod in op->_mods[index]._didMod) { bool applyToCreatedItem = false; bool removeFromTarget = false; CWeenieObject *modificationSource = NULL; switch (didMod._unk) //this is a guess { case 0: modificationSource = this; break; case 1: modificationSource = pTool; break; default: #ifdef _DEBUG assert(false); #endif break; //should never happen } DWORD value = pTarget->InqDIDQuality(didMod._stat, 0); switch (didMod._operationType) { case 1: //= value = didMod._value; if (value == 0) value = didMod._value; break; case 2: //+ value += didMod._value; break; case 3: //copy value from modificationSource to target if (modificationSource) value = modificationSource->InqDIDQuality(didMod._stat, 0); break; case 4: //copy value from modificationSource to created item applyToCreatedItem = true; if (modificationSource) value = modificationSource->InqDIDQuality(didMod._stat, 0); break; case 7: //add spell #ifdef _DEBUG assert(false); #endif break; default: #ifdef _DEBUG assert(false); #endif break; } if (pCreatedItem && applyToCreatedItem) { pCreatedItem->m_Qualities.SetDataID(didMod._stat, value); pCreatedItem->NotifyDIDStatUpdated(didMod._stat, false); } if (removeFromTarget) { pTarget->m_Qualities.RemoveDataID(didMod._stat); } else { pTarget->m_Qualities.SetDataID(didMod._stat, value); pTarget->NotifyDIDStatUpdated(didMod._stat, false); } } } if (!op->_mods[index]._iidMod.empty()) { for each (TYPEMod iidMod in op->_mods[index]._iidMod) { bool applyToCreatedItem = false; CWeenieObject *modificationSource = NULL; switch (iidMod._unk) //this is a guess { case 0: modificationSource = this; break; case 1: modificationSource = pTool; break; default: #ifdef _DEBUG assert(false); #endif break; //should never happen } DWORD value = pTarget->InqIIDQuality(iidMod._stat, 0); switch (iidMod._operationType) { case 1: //= value = iidMod._value; break; case 2: //+ value += iidMod._value; break; case 3: //copy value from modificationSource to target if (modificationSource) { switch (iidMod._stat) { case ALLOWED_WIELDER_IID: case ALLOWED_ACTIVATOR_IID: value = this->GetID(); break; default: value = modificationSource->InqIIDQuality(iidMod._stat, 0); break; } } break; case 4: //copy value from modificationSource to created item applyToCreatedItem = true; if (modificationSource) { switch (iidMod._stat) { case ALLOWED_WIELDER_IID: case ALLOWED_ACTIVATOR_IID: value = this->GetID(); break; default: value = modificationSource->InqIIDQuality(iidMod._stat, 0); break; } } break; case 7: //add spell #ifdef _DEBUG assert(false); #endif break; default: #ifdef _DEBUG assert(false); #endif break; } if (pCreatedItem && applyToCreatedItem) { pCreatedItem->m_Qualities.SetInstanceID(iidMod._stat, value); pCreatedItem->NotifyIIDStatUpdated(iidMod._stat, false); } else { pTarget->m_Qualities.SetInstanceID(iidMod._stat, value); pTarget->NotifyIIDStatUpdated(iidMod._stat, false); } } } if (op->_mods[index]._unknown7) //this is a guess { //update icon DWORD clothing_table_id = pTarget->InqDIDQuality(CLOTHINGBASE_DID, 0); DWORD palette_template_key = pTarget->InqIntQuality(PALETTE_TEMPLATE_INT, 0); if (clothing_table_id && palette_template_key) { if (!pTarget->InqBoolQuality(IGNORE_CLO_ICONS_BOOL, FALSE)) { if (ClothingTable *clothingTable = ClothingTable::Get(clothing_table_id)) { if (const CloPaletteTemplate *pPaletteTemplate = clothingTable->_paletteTemplatesHash.lookup(palette_template_key)) { if (pPaletteTemplate->iconID) { pTarget->m_Qualities.SetDataID(ICON_DID, pPaletteTemplate->iconID); pTarget->NotifyDIDStatUpdated(ICON_DID, false); } } ClothingTable::Release(clothingTable); } } } } } DWORD CPlayerWeenie::MaterialToSalvageBagId(MaterialType material) { switch (material) { case Ceramic_MaterialType: return W_MATERIALCERAMIC_CLASS; case Porcelain_MaterialType: return W_MATERIALPORCELAIN_CLASS; case Linen_MaterialType: return W_MATERIALLINEN_CLASS; case Satin_MaterialType: return W_MATERIALSATIN_CLASS; case Silk_MaterialType: return W_MATERIALSILK_CLASS; case Velvet_MaterialType: return W_MATERIALVELVET_CLASS; case Wool_MaterialType: return W_MATERIALWOOL_CLASS; case Agate_MaterialType: return W_MATERIALAGATE_CLASS; case Amber_MaterialType: return W_MATERIALAMBER_CLASS; case Amethyst_MaterialType: return W_MATERIALAMETHYST_CLASS; case Aquamarine_MaterialType: return W_MATERIALAQUAMARINE_CLASS; case Azurite_MaterialType: return W_MATERIALAZURITE_CLASS; case Black_Garnet_MaterialType: return W_MATERIALBLACKGARNET_CLASS; case Black_Opal_MaterialType: return W_MATERIALBLACKOPAL_CLASS; case Bloodstone_MaterialType: return W_MATERIALBLOODSTONE_CLASS; case Carnelian_MaterialType: return W_MATERIALCARNELIAN_CLASS; case Citrine_MaterialType: return W_MATERIALCITRINE_CLASS; case Diamond_MaterialType: return W_MATERIALDIAMOND_CLASS; case Emerald_MaterialType: return W_MATERIALEMERALD_CLASS; case Fire_Opal_MaterialType: return W_MATERIALFIREOPAL_CLASS; case Green_Garnet_MaterialType: return W_MATERIALGREENGARNET_CLASS; case Green_Jade_MaterialType: return W_MATERIALGREENJADE_CLASS; case Hematite_MaterialType: return W_MATERIALHEMATITE_CLASS; case Imperial_Topaz_MaterialType: return W_MATERIALIMPERIALTOPAZ_CLASS; case Jet_MaterialType: return W_MATERIALJET_CLASS; case Lapis_Lazuli_MaterialType: return W_MATERIALLAPISLAZULI_CLASS; case Lavender_Jade_MaterialType: return W_MATERIALLAVENDERJADE_CLASS; case Malachite_MaterialType: return W_MATERIALMALACHITE_CLASS; case Moonstone_MaterialType: return W_MATERIALMOONSTONE_CLASS; case Onyx_MaterialType: return W_MATERIALONYX_CLASS; case Opal_MaterialType: return W_MATERIALOPAL_CLASS; case Peridot_MaterialType: return W_MATERIALPERIDOT_CLASS; case Red_Garnet_MaterialType: return W_MATERIALREDGARNET_CLASS; case Red_Jade_MaterialType: return W_MATERIALREDJADE_CLASS; case Rose_Quartz_MaterialType: return W_MATERIALROSEQUARTZ_CLASS; case Ruby_MaterialType: return W_MATERIALRUBY_CLASS; case Sapphire_MaterialType: return W_MATERIALSAPPHIRE_CLASS; case Smoky_Quartz_MaterialType: return W_MATERIALSMOKYQUARTZ_CLASS; case Sunstone_MaterialType: return W_MATERIALSUNSTONE_CLASS; case Tiger_Eye_MaterialType: return W_MATERIALTIGEREYE_CLASS; case Tourmaline_MaterialType: return W_MATERIALTOURMALINE_CLASS; case Turquoise_MaterialType: return W_MATERIALTURQUOISE_CLASS; case White_Jade_MaterialType: return W_MATERIALWHITEJADE_CLASS; case White_Quartz_MaterialType: return W_MATERIALWHITEQUARTZ_CLASS; case White_Sapphire_MaterialType: return W_MATERIALWHITESAPPHIRE_CLASS; case Yellow_Garnet_MaterialType: return W_MATERIALYELLOWGARNET_CLASS; case Yellow_Topaz_MaterialType: return W_MATERIALYELLOWTOPAZ_CLASS; case Zircon_MaterialType: return W_MATERIALZIRCON_CLASS; case Ivory_MaterialType: return W_MATERIALIVORY_CLASS; case Leather_MaterialType: return W_MATERIALLEATHER_CLASS; case Armoredillo_Hide_MaterialType: return W_MATERIALARMOREDILLOHIDE_CLASS; case Gromnie_Hide_MaterialType: return W_MATERIALGROMNIEHIDE_CLASS; case Reed_Shark_Hide_MaterialType: return W_MATERIALREEDSHARKHIDE_CLASS; case Brass_MaterialType: return W_MATERIALBRASS_CLASS; case Bronze_MaterialType: return W_MATERIALBRONZE_CLASS; case Copper_MaterialType: return W_MATERIALCOPPER_CLASS; case Gold_MaterialType: return W_MATERIALGOLD_CLASS; case Iron_MaterialType: return W_MATERIALIRON_CLASS; case Pyreal_MaterialType: return W_MATERIALPYREAL_CLASS; case Silver_MaterialType: return W_MATERIALSILVER_CLASS; case Steel_MaterialType: return W_MATERIALSTEEL_CLASS; case Alabaster_MaterialType: return W_MATERIALALABASTER_CLASS; case Granite_MaterialType: return W_MATERIALGRANITE_CLASS; case Marble_MaterialType: return W_MATERIALMARBLE_CLASS; case Obsidian_MaterialType: return W_MATERIALOBSIDIAN_CLASS; case Sandstone_MaterialType: return W_MATERIALSANDSTONE_CLASS; case Serpentine_MaterialType: return W_MATERIALSERPENTINE_CLASS; case Ebony_MaterialType: return W_MATERIALEBONY_CLASS; case Mahogany_MaterialType: return W_MATERIALMAHOGANY_CLASS; case Oak_MaterialType: return W_MATERIALOAK_CLASS; case Pine_MaterialType: return W_MATERIALPINE_CLASS; case Teak_MaterialType: return W_MATERIALTEAK_CLASS; default: return 0; } } struct SalvageInfo { int amount = 0; int totalValue = 0; int totalWorkmanship = 0; //int itemsSalvagedCount = 0; //Discrete/Continous vars into the structure of salvageMap int itemsSalvagedCountCont = 0; int itemsSalvagedCountDiscrete = 0; }; void CPlayerWeenie::PerformSalvaging(DWORD toolId, PackableList items) { if (items.empty()) return; CWeenieObject *pTool = g_pWorld->FindObject(toolId); if (!pTool) return; if (pTool->GetWorldTopLevelOwner() != this) { NotifyWeenieError(WERROR_SALVAGE_DONT_OWN_TOOL); return; } //SALVAGING SKILL DETERMINES SALVAGE AMOUNT DWORD salvagingSkillValue; InqSkill(STypeSkill::SALVAGING_SKILL, salvagingSkillValue, false); DWORD highestTinkeringSkillValue; DWORD tinkeringSkills[4]; InqSkill(STypeSkill::ARMOR_APPRAISAL_SKILL, tinkeringSkills[0], false); InqSkill(STypeSkill::WEAPON_APPRAISAL_SKILL, tinkeringSkills[1], false); InqSkill(STypeSkill::MAGIC_ITEM_APPRAISAL_SKILL, tinkeringSkills[2], false); InqSkill(STypeSkill::ITEM_APPRAISAL_SKILL, tinkeringSkills[3], false); highestTinkeringSkillValue = max(max(max(tinkeringSkills[0], tinkeringSkills[1]), tinkeringSkills[2]), tinkeringSkills[3]); int numAugs = max(0, min(4, InqIntQuality(AUGMENTATION_BONUS_SALVAGE_INT, 0))); std::map salvageMap; std::list itemsToDestroy; //Lists for the success message PackableList salvageResults; PackableList notSalvagable; for (auto itemId : items) { CWeenieObject *pItem = g_pWorld->FindObject(itemId); if (!pItem) { NotifyWeenieError(WERROR_SALVAGE_INVALID_LOOT_LIST); return; } if (pItem->GetWorldTopLevelOwner() != this) { NotifyWeenieError(WERROR_SALVAGE_DONT_OWN_LOOT); notSalvagable.push_back(pItem->GetID()); continue; } bool isRetained = pItem->InqBoolQuality(RETAINED_BOOL, FALSE); ITEM_TYPE itemType = (ITEM_TYPE)pItem->InqIntQuality(ITEM_TYPE_INT, 0, TRUE); MaterialType material = (MaterialType)pItem->InqIntQuality(MATERIAL_TYPE_INT, 0, TRUE); int workmanship = pItem->InqIntQuality(ITEM_WORKMANSHIP_INT, 0, TRUE); int itemValue = pItem->InqIntQuality(VALUE_INT, 0, TRUE); if (workmanship < 1 || material == MaterialType::Undef_MaterialType || isRetained) { NotifyWeenieError(WERROR_SALVAGE_NOT_SUITABLE); notSalvagable.push_back(pItem->GetID()); continue; } itemsToDestroy.push_back(pItem); /*keep Discrete and Continous .itemsSalvaged Discrete for immediate calculations, Continous for reference sake and foolish number porn*/ if (itemType == ITEM_TYPE::TYPE_TINKERING_MATERIAL) { salvageMap[material].amount += pItem->InqIntQuality(STRUCTURE_INT, 1); salvageMap[material].totalValue += itemValue; salvageMap[material].itemsSalvagedCountCont += pItem->InqIntQuality(NUM_ITEMS_IN_MATERIAL_INT, 1); //salvageMap[material].itemsSalvagedCountDiscrete++; } else { int salvagingAmount = CalculateSalvageAmount(salvagingSkillValue, workmanship, numAugs); // tinkering can at best return the workmanship as the amount int tinkeringAmount = min(CalculateSalvageAmount(highestTinkeringSkillValue, workmanship, 0), workmanship); // We choose the one that gives best results int salvageAmount = max(salvagingAmount, tinkeringAmount); // formula taken from http://asheron.wikia.com/wiki/Salvaging/Value_Pre2013 int salvageValue = itemValue * ( salvagingSkillValue / 387.0 ) *(1 + numAugs * 0.25); salvageMap[material].totalValue += salvageValue; salvageMap[material].amount += salvageAmount; salvageMap[material].itemsSalvagedCountCont++; } salvageMap[material].totalWorkmanship += workmanship; } if (itemsToDestroy.empty()) return; // Check if we have enough pack space first! int numBags = 0; for (auto salvageEntry : salvageMap) { SalvageInfo salvageInfo = salvageEntry.second; numBags += ceil(salvageInfo.amount / 100.0); } if (numBags > Container_GetNumFreeMainPackSlots()) { SendText("Not enough pack space!", LTT_ERROR); return; } for (auto item : itemsToDestroy) item->Remove(); for (auto salvageEntry : salvageMap) { MaterialType material = salvageEntry.first; SalvageInfo salvageInfo = salvageEntry.second; int valuePerUnit = salvageInfo.totalValue / salvageInfo.amount; double workmanship = salvageInfo.totalWorkmanship/(double)salvageInfo.itemsSalvagedCountCont; int fullBagItems = ceil(salvageInfo.itemsSalvagedCountCont / (salvageInfo.amount / 100.0)); int remainingAmount = salvageInfo.amount; int remainingItems = salvageInfo.itemsSalvagedCountCont; while (remainingAmount > 0) { int amount = min(remainingAmount, 100); int numItems = min(max(1, remainingItems), fullBagItems); SpawnSalvageBagInContainer(material, amount, floor(numItems * workmanship), min(valuePerUnit * amount, 75000), numItems); SalvageResult salvageResult; salvageResult.material = material; salvageResult.units = amount; salvageResult.workmanship = floor(numItems * workmanship)/(double)numItems; salvageResults.push_back(salvageResult); remainingAmount -= 100; remainingItems -= fullBagItems; } } BinaryWriter salvageMsg; salvageMsg.Write(0x02B4); salvageMsg.Write(0x28); // SkillID: Salvaging notSalvagable.Pack(&salvageMsg); // PackableList salvageResults.Pack(&salvageMsg); // PackableList salvageMsg.Write(0); // int: Aug bonus - not implemented? SendNetMessage(&salvageMsg, PRIVATE_MSG, TRUE, FALSE); } // See http://web.archive.org/web/20170130213649/http://www.thejackcat.com/AC/Shopping/Crafts/Salvage_old.htm // and http://web.archive.org/web/20170130194012/http://www.thejackcat.com/AC/Shopping/Crafts/Salvage.htm int CPlayerWeenie::CalculateSalvageAmount(int salvagingSkill, int workmanship, int numAugs) { return 1 + floor( salvagingSkill/195.0 * workmanship * (1 + 0.25*numAugs) ); } bool CPlayerWeenie::SpawnSalvageBagInContainer(MaterialType material, int amount, int workmanship, int value, int numItems) { int salvageWcid = MaterialToSalvageBagId(material); if (salvageWcid == 0) return false; CWeenieObject *weenie = g_pWeenieFactory->CreateWeenieByClassID(salvageWcid, NULL, false); weenie->m_Qualities.SetString(NAME_STRING, csprintf("Salvage (%s)", FormatNumberString(amount).c_str())); //modern client prepends the salvage type automatically so we need to adapt to this. if (!weenie) return false; weenie->m_Qualities.SetInt(ITEM_WORKMANSHIP_INT, workmanship); weenie->m_Qualities.SetInt(VALUE_INT, value); weenie->m_Qualities.SetInt(STRUCTURE_INT, amount); weenie->m_Qualities.SetInt(NUM_ITEMS_IN_MATERIAL_INT, numItems); //weenie->m_Qualities.SetFloat(SalvageWorkmanship, ) //Which one of these is Vtank missing to make it not calculate the real workmanship //none. it has them all. where is SalvageWorkmanship double being calculated //NumberItemsSalvagedFrom = 170, // 0x000000AA //SalvageWorkmanship = 167772169, // 0x0A000009 // Divide SalvageWorkmanship by NumberItemsSalvagedFrom return SpawnInContainer(weenie); } void CPlayerWeenie::SetLoginPlayerQualities() { if (m_Qualities.GetIID(CONTAINER_IID, 0)) { m_Qualities.RemoveInstanceID(CONTAINER_IID); } if (g_pConfig->FixOldChars()) { int heritage = InqIntQuality(HERITAGE_GROUP_INT, 1); // fix scale and other misc things for different heritage groups switch (heritage) { case Lugian_HeritageGroup: { if (InqStringQuality(SEX_STRING, "") == "Male") m_Qualities.SetFloat(DEFAULT_SCALE_FLOAT, 1.3); else m_Qualities.SetFloat(DEFAULT_SCALE_FLOAT, 1.2); } break; case Empyrean_HeritageGroup: { if (InqStringQuality(SEX_STRING, "") == "Male") m_Qualities.SetFloat(DEFAULT_SCALE_FLOAT, 1.2); else m_Qualities.SetFloat(DEFAULT_SCALE_FLOAT, 1.15); } break; case Gearknight_HeritageGroup: { if (InqStringQuality(SEX_STRING, "") == "Male") m_Qualities.SetFloat(DEFAULT_SCALE_FLOAT, 1.2); else m_Qualities.SetFloat(DEFAULT_SCALE_FLOAT, 1.1); ObjDesc gk; GetObjDesc(gk); m_Qualities.SetDataID(HEAD_OBJECT_DID, gk.lastAPChange->part_id); } break; case Tumerok_HeritageGroup: { if (InqStringQuality(SEX_STRING, "") == "Male") m_Qualities.SetFloat(DEFAULT_SCALE_FLOAT, 1.1); } break; case Olthoi_HeritageGroup: case OlthoiAcid_HeritageGroup: { Position sanc = Position(g_pConfig->OlthoiStartPosition()); m_Qualities.SetPosition(SANCTUARY_POSITION, sanc); m_Qualities.SetBool((STypeBool)LOGIN_AT_LIFESTONE_BOOL, 1); m_Qualities.SetInt(PLAYER_KILLER_STATUS_INT, PK_PKStatus); // Olthoi are always Red if (m_Qualities.GetInt(HERITAGE_GROUP_INT, 1) == Olthoi_HeritageGroup) { m_Qualities.SetFloat(DEFAULT_SCALE_FLOAT, 0.9); m_Qualities.SetDataID(HEAD_OBJECT_DID, 0x010045f0); } else { m_Qualities.SetFloat(DEFAULT_SCALE_FLOAT, 0.6); m_Qualities.SetDataID(HEAD_OBJECT_DID, 0x01004616); } } break; default: break; } // add racial augmentations switch (heritage) { case Shadowbound_HeritageGroup: case Penumbraen_HeritageGroup: m_Qualities.SetInt(AUGMENTATION_CRITICAL_EXPERTISE_INT, 1); break; case Gearknight_HeritageGroup: m_Qualities.SetInt(AUGMENTATION_DAMAGE_REDUCTION_INT, 1); break; case Tumerok_HeritageGroup: m_Qualities.SetInt(AUGMENTATION_CRITICAL_POWER_INT, 1); break; case Lugian_HeritageGroup: { if (!m_Qualities.GetInt(AUGMENTATION_INCREASED_CARRYING_CAPACITY_INT, 0)) m_Qualities.SetInt(AUGMENTATION_INCREASED_CARRYING_CAPACITY_INT, 1); break; } case Empyrean_HeritageGroup: m_Qualities.SetInt(AUGMENTATION_INFUSED_LIFE_MAGIC_INT, 1); break; case Undead_HeritageGroup: m_Qualities.SetInt(AUGMENTATION_CRITICAL_DEFENSE_INT, 1); break; case Olthoi_HeritageGroup: case OlthoiAcid_HeritageGroup: break; // none default: // sho, aluv, gharu, viamont m_Qualities.SetInt(AUGMENTATION_JACK_OF_ALL_TRADES_INT, 1); break; } } //End of temporary code // Generate a random number of seconds between 1 second and 2 months of seconds and write int for the Real Time Rare drop system. if (g_pConfig->RealTimeRares() && !m_Qualities.GetInt(RARES_LOGIN_TIMESTAMP_INT, 0)) { int seconds = getRandomNumber(5184000); time_t t = chrono::system_clock::to_time_t(std::chrono::system_clock::now()); t += seconds; m_Qualities.SetInt(RARES_LOGIN_TIMESTAMP_INT, t); } g_pAllegianceManager->SetWeenieAllegianceQualities(this); auto loginTime = chrono::system_clock::to_time_t(chrono::system_clock::now()); m_Qualities.SetFloat(LOGIN_TIMESTAMP_FLOAT, loginTime); m_Qualities.SetInt(LOGIN_TIMESTAMP_INT, loginTime); // Position startPos = Position(0xDB75003B, Vector(186.000000f, 65.000000f, 36.088333f), Quaternion(1.000000, 0.000000, 0.000000, 0.000000)); // Position startPos = Position(0xA9B4001F, Vector(87.750603f, 147.722321f, 66.005005f), Quaternion(0.011819f, 0.000000, 0.000000, -0.999930f)); // Your location is: 0xC98C0028 [113.665604 190.259003 22.004999] -0.707107 0.000000 0.000000 -0.707107 Rithwic // Position startPos = Position(0xC98C0028, Vector(113.665604f, 190.259003f, 22.004999f), Quaternion(-0.707107f, 0.000000, 0.000000, -0.707107f)); // SetInitialPosition(startPos); // m_Qualities.SetPosition(SANCTUARY_POSITION, startPos); extern bool g_bStartOverride; extern Position g_StartPosition; if (g_bStartOverride) { SetInitialPosition(g_StartPosition); m_Qualities.SetPosition(SANCTUARY_POSITION, g_StartPosition); } Position m_initLocPosition; if (m_Qualities.InqPosition(LOCATION_POSITION, m_initLocPosition) && m_initLocPosition.objcell_id) //governs whether or not to log player in at lifestone. NOTE: In the absence of a "location position" a player is already logged in at sanc or instantiation position. { if (InqBoolQuality(LOGIN_AT_LIFESTONE_BOOL, FALSE)) { SetSanctuaryAsLogin(); m_Qualities.SetBool((STypeBool)LOGIN_AT_LIFESTONE_BOOL, 0); } else //If a player's location position matches the listed landblocks in restrictedlandblocks.json or login at lifestone is configured to true their login position will be changed (one time) to their sanctuary position. { DWORD LogoutLandBlock = (BLOCK_WORD(m_initLocPosition.objcell_id) * 65536); auto NoLogLandBlocks = g_pPortalDataEx->GetRestrictedLandblocks(); if ((NoLogLandBlocks.find(LogoutLandBlock) != NoLogLandBlocks.end()) || g_pConfig->LoginAtLS()) { SetSanctuaryAsLogin(); } } } // should never be in a fellowship when logging in, but let's be sure m_Qualities.RemoveString(FELLOWSHIP_STRING); m_Qualities.SetInt(ARMOR_LEVEL_INT, 0); m_Qualities.SetInt(PHYSICS_STATE_INT, PhysicsState::HIDDEN_PS | PhysicsState::IGNORE_COLLISIONS_PS | PhysicsState::EDGE_SLIDE_PS | PhysicsState::GRAVITY_PS); m_Qualities.SetBool(ROT_PROOF_BOOL, FALSE); m_Qualities.RemoveInt(RADARBLIP_COLOR_INT); if (atoi(g_pConfig->GetValue("player_killer_only", "0")) != 0) { double pkTimestamp; if (!m_Qualities.InqFloat(PK_TIMESTAMP_FLOAT, pkTimestamp, TRUE)) { m_Qualities.SetInt(PLAYER_KILLER_STATUS_INT, PK_PKStatus); } } if (IsAdvocate()) { m_Qualities.SetBool(IS_ADVOCATE_BOOL, TRUE); m_Qualities.SetBool(ADVOCATE_STATE_BOOL, TRUE); } else { m_Qualities.RemoveBool(IS_ADVOCATE_BOOL); m_Qualities.RemoveBool(ADVOCATE_STATE_BOOL); } if (IsSentinel()) { m_Qualities.SetBool(IS_SENTINEL_BOOL, TRUE); } else { m_Qualities.RemoveBool(IS_SENTINEL_BOOL); } if (IsAdmin()) { m_Qualities.SetBool(IS_ADMIN_BOOL, TRUE); m_Qualities.SetBool(IS_ARCH_BOOL, TRUE); m_Qualities.SetBool(SPELL_COMPONENTS_REQUIRED_BOOL, FALSE); m_Qualities.SetInt(BONDED_INT, 1); //do not drop items on death } else { m_Qualities.RemoveBool(IS_ADMIN_BOOL); m_Qualities.RemoveBool(IS_ARCH_BOOL); m_Qualities.SetBool(SPELL_COMPONENTS_REQUIRED_BOOL, TRUE); m_Qualities.SetInt(BONDED_INT, 0); //drop items on death } for (auto wielded : m_Wielded) { if (wielded->InqIntQuality(ITEM_CUR_MANA_INT, 0, true) > 0) { double manaRate = 0.0f; if (wielded->m_Qualities.InqFloat(MANA_RATE_FLOAT, manaRate, TRUE) && manaRate != 0.0) wielded->_nextManaUse = Timer::cur_time + (-manaRate * 1000); } } if (!InqBoolQuality(FIRST_ENTER_WORLD_DONE_BOOL, FALSE)) { for (auto item : m_Items) { if (item->m_Qualities.id == W_TUTORIALBOOK_CLASS) { item->Use(this); break; } } m_Qualities.SetBool(FIRST_ENTER_WORLD_DONE_BOOL, TRUE); } //check if we still own our house. if (DWORD houseId = InqDIDQuality(HOUSEID_DID, 0)) { CHouseData *houseData = g_pHouseManager->GetHouseData(houseId); if (houseData->_ownerId != GetID()) m_Qualities.SetDataID(HOUSEID_DID, 0); } } void CPlayerWeenie::SetSanctuaryAsLogin() { Position m_StartPosition; if (m_Qualities.InqPosition(SANCTUARY_POSITION, m_StartPosition) && m_StartPosition.objcell_id) { m_Qualities.SetPosition(LOCATION_POSITION, m_StartPosition); } } std::list CPlayerWeenie::GetSquelches() { return squelches; } void CPlayerWeenie::LoadSquelches() { squelches = g_pDBIO->GetCharacterSquelch(GetID()); } bool CPlayerWeenie::IsPlayerSquelched(const DWORD dwGUID, bool checkAccount) { for (auto sq : squelches) { if (sq.squelched_id == dwGUID) return TRUE; if (g_pDBIO->GetPlayerAccountId(dwGUID) == sq.account_id) return TRUE; } return FALSE; } void CPlayerWeenie::HandleItemManaRequest(DWORD itemId) { if (!itemId) return; CWeenieObject *item = FindValidNearbyItem(itemId, 60); if (!item) return; m_pClient->SendNetMessage(ItemManaUpdate(item), PRIVATE_MSG, TRUE, TRUE); } void CPlayerWeenie::UpdateModuleFromClient(PlayerModule &module) { _playerModule.options_ = module.options_; _playerModule.options2_ = module.options2_; _playerModule.spell_filters_ = module.spell_filters_; for (DWORD i = 0; i < 8; i++) _playerModule.favorite_spells_[i] = module.favorite_spells_[i]; CloneMemberPointerData(_playerModule.shortcuts_, module.shortcuts_); CloneMemberPointerData>(_playerModule.desired_comps_, module.desired_comps_); CloneMemberPointerData(_playerModule.m_pPlayerOptionsData, module.m_pPlayerOptionsData); UpdateModel(); } void CPlayerWeenie::LoadEx(CWeenieSave &save) { CContainerWeenie::LoadEx(save); if (save._playerModule) _playerModule = *save._playerModule; if (save._questTable) _questTable = *save._questTable; CheckVitalRanges(); } void CPlayerWeenie::SaveEx(CWeenieSave &save) { DebugValidate(); CContainerWeenie::SaveEx(save); SafeDelete(save._playerModule); save._playerModule = new PlayerModule; *save._playerModule = _playerModule; SafeDelete(save._questTable); save._questTable = new QuestTable; *save._questTable = _questTable; } bool CPlayerWeenie::ShowHelm() { if (_playerModule.options2_ & ShowHelm_CharacterOptions2) return true; return false; } bool CPlayerWeenie::InqQuest(const char *questName) { return _questTable.InqQuest(questName); } int CPlayerWeenie::InqTimeUntilOkayToComplete(const char *questName) { return _questTable.InqTimeUntilOkayToComplete(questName); } unsigned int CPlayerWeenie::InqQuestSolves(const char *questName) { return _questTable.InqQuestSolves(questName); } bool CPlayerWeenie::UpdateQuest(const char *questName) { return _questTable.UpdateQuest(questName); } void CPlayerWeenie::StampQuest(const char *questName) { _questTable.StampQuest(questName); } void CPlayerWeenie::IncrementQuest(const char *questName) { _questTable.IncrementQuest(questName); } void CPlayerWeenie::DecrementQuest(const char *questName) { _questTable.DecrementQuest(questName); } void CPlayerWeenie::EraseQuest(const char *questName) { _questTable.RemoveQuest(questName); } void CPlayerWeenie::SetQuestCompletions(const char *questName, int numCompletions) { _questTable.SetQuestCompletions(questName, numCompletions); } std::string CPlayerWeenie::Ktref(const char *questName) { return _questTable.Ktref(questName); } unsigned int CPlayerWeenie::InqQuestMax(const char *questName) { return _questTable.InqQuestMax(questName); } CWandSpellUseEvent::CWandSpellUseEvent(DWORD wandId, DWORD targetId) { _wandId = wandId; _targetId = targetId; } void CWandSpellUseEvent::OnReadyToUse() { if (_manager->_next_allowed_use > Timer::cur_time) { Cancel(); return; } CWeenieObject *wand = g_pWorld->FindObject(_wandId); if (!wand) { Cancel(); return; } _spellId = wand->InqDIDQuality(SPELL_DID, 0); if (!_spellId) { Cancel(); return; } STypeSkill skill = (STypeSkill)wand->InqDIDQuality(ITEM_SKILL_LIMIT_DID, 0); DWORD skillValue; if (skill != 0 && _weenie->InqSkill(skill, skillValue, false)) { if (skillValue < wand->InqIntQuality(ITEM_SKILL_LEVEL_LIMIT_INT, 0)) { SkillTable *pSkillTable = SkillSystem::GetSkillTable(); const SkillBase *pSkillBase = pSkillTable->GetSkillBase(skill); if (pSkillBase != NULL) _weenie->NotifyWeenieErrorWithString(WERROR_ACTIVATION_SKILL_TOO_LOW, pSkillBase->_name.c_str()); Cancel(); return; } } DWORD motion = wand->InqDIDQuality(USE_USER_ANIMATION_DID, 0); int manaCost = wand->InqIntQuality(ITEM_MANA_COST_INT, 0); if (manaCost > 0) { CSpellTable * pSpellTable = MagicSystem::GetSpellTable(); if (!pSpellTable) { Cancel(); return; } const CSpellBase *spell = pSpellTable->GetSpellBase(_spellId); if (!spell) { Cancel(); return; } DWORD manaConvSkill = _weenie->GetEffectiveManaConversionSkill(); int spellCraft = wand->InqIntQuality(ITEM_SPELLCRAFT_INT, 0); manaCost = GetManaCost(spellCraft, spell->_power, manaCost, manaConvSkill); int itemCurrentMana = wand->InqIntQuality(ITEM_CUR_MANA_INT, 0); if (manaCost > itemCurrentMana) { Cancel(WERROR_ACTIVATION_NOT_ENOUGH_MANA); return; } _newManaValue = itemCurrentMana - manaCost; } _weenie->MakeSpellcastingManager()->m_bCasting = true; _weenie->m_SpellcastingManager->m_SpellCastData.caster_id = _wandId; if (motion) ExecuteUseAnimation(motion); else OnUseAnimSuccess(motion); } void CWandSpellUseEvent::OnUseAnimSuccess(DWORD motion) { if (_newManaValue != -1) { CWeenieObject *wand = g_pWorld->FindObject(_wandId); if (!wand) { Cancel(); return; } wand->m_Qualities.SetInt(ITEM_CUR_MANA_INT, _newManaValue); wand->NotifyIntStatUpdated(ITEM_CUR_MANA_INT, false); } _weenie->MakeSpellcastingManager()->CastSpellInstant(_targetId, _spellId); _weenie->DoForcedStopCompletely(); _manager->_next_allowed_use = Timer::cur_time + 2.0; Done(); } void CWandSpellUseEvent::Cancel(DWORD error) { _weenie->MakeSpellcastingManager()->m_bCasting = false; CUseEventData::Cancel(error); } void CWandSpellUseEvent::Done(DWORD error) { _weenie->MakeSpellcastingManager()->m_bCasting = false; CUseEventData::Done(error); } void CLifestoneRecallUseEvent::OnReadyToUse() { SetupRecall(); _timeout = Timer::cur_time + 15.0; _weenie->ChangeCombatMode(NONCOMBAT_COMBAT_MODE, false); ExecuteUseAnimation(Motion_LifestoneRecall); g_pWorld->BroadcastLocal(_weenie->GetLandcell(), csprintf("%s is recalling to the lifestone.", _weenie->GetName().c_str())); } void CLifestoneRecallUseEvent::OnUseAnimSuccess(DWORD motion) { if (_weenie->IsDead() || _weenie->IsInPortalSpace() || _weenie->get_minterp()->interpreted_state.forward_command != Motion_Ready) { Cancel(WERROR_INTERRUPTED); return; } if (InMoveRange()) { _weenie->AdjustMana(_weenie->GetMana() * -0.5); _weenie->TeleportToLifestone(); Done(); } else Cancel(WERROR_MOVED_TOO_FAR); } void CHouseRecallUseEvent::OnReadyToUse() { SetupRecall(); _timeout = Timer::cur_time + 15.0; _weenie->ChangeCombatMode(NONCOMBAT_COMBAT_MODE, false); ExecuteUseAnimation(Motion_HouseRecall); g_pWorld->BroadcastLocal(_weenie->GetLandcell(), csprintf("%s is recalling home.", _weenie->GetName().c_str())); } void CHouseRecallUseEvent::OnUseAnimSuccess(DWORD motion) { if (_weenie->IsDead() || _weenie->IsInPortalSpace() || _weenie->get_minterp()->interpreted_state.forward_command != Motion_Ready) { Cancel(WERROR_INTERRUPTED); return; } if (InMoveRange()) { _weenie->TeleportToHouse(); Done(); } else Cancel(WERROR_MOVED_TOO_FAR); } void CMansionRecallUseEvent::OnReadyToUse() { SetupRecall(); _timeout = Timer::cur_time + 15.0; _weenie->ChangeCombatMode(NONCOMBAT_COMBAT_MODE, false); ExecuteUseAnimation(Motion_HouseRecall); g_pWorld->BroadcastLocal(_weenie->GetLandcell(), csprintf("%s is recalling to the Allegiance housing.", _weenie->GetName().c_str())); } void CMansionRecallUseEvent::OnUseAnimSuccess(DWORD motion) { if (_weenie->IsDead() || _weenie->IsInPortalSpace() || _weenie->get_minterp()->interpreted_state.forward_command != Motion_Ready) { Cancel(WERROR_INTERRUPTED); return; } if (InMoveRange()) { _weenie->TeleportToMansion(); Done(); } else Cancel(WERROR_MOVED_TOO_FAR); } void CMarketplaceRecallUseEvent::OnReadyToUse() { SetupRecall(); _timeout = Timer::cur_time + 18.0; _weenie->ChangeCombatMode(NONCOMBAT_COMBAT_MODE, false); ExecuteUseAnimation(Motion_MarketplaceRecall); g_pWorld->BroadcastLocal(_weenie->GetLandcell(), csprintf("%s is going to the Marketplace.", _weenie->GetName().c_str())); } void CMarketplaceRecallUseEvent::OnUseAnimSuccess(DWORD motion) { if (_weenie->IsDead() || _weenie->IsInPortalSpace() || _weenie->get_minterp()->interpreted_state.forward_command != Motion_Ready) { Cancel(WERROR_INTERRUPTED); return; } if (InMoveRange()) { _weenie->Movement_Teleport(Position(0x016C01BC, Vector(49.11f, -31.22f, 0.005f), Quaternion(0.7009f, 0, 0, -0.7132f))); Done(); } else Cancel(WERROR_MOVED_TOO_FAR); } void CPKArenaUseEvent::OnReadyToUse() { SetupRecall(); _timeout = Timer::cur_time + 18.0; _weenie->ChangeCombatMode(NONCOMBAT_COMBAT_MODE, false); ExecuteUseAnimation(Motion_PKArenaRecall); g_pWorld->BroadcastLocal(_weenie->GetLandcell(), csprintf("%s is going to the PK Arena.", _weenie->GetName().c_str())); } void CPKArenaUseEvent::OnUseAnimSuccess(DWORD motion) { if (_weenie->IsDead() || _weenie->IsInPortalSpace() || _weenie->get_minterp()->interpreted_state.forward_command != Motion_Ready) { Cancel(WERROR_INTERRUPTED); return; } Position *positions = new Position[5]; positions[0] = (Position(0x00660117, Vector(30, -50, 0.005f), Quaternion(1, 0, 0, 0))); positions[1] = (Position(0x00660106, Vector(10, 0, 0.005f), Quaternion(0.321023f, 0, 0, -0.947071f))); positions[2] = (Position(0x00660103, Vector(0, -30, 0.005f), Quaternion(0.714424f, 0, 0, -0.699713f))); positions[3] = (Position(0x0066011E, Vector(50, 0, 0.005f), Quaternion(-0.276474f, 0, 0, -0.961021f))); positions[4] = (Position(0x00660127, Vector(60, -30, 0.005f), Quaternion(0.731689f, 0, 0, 0.681639f))); int randomLoc = getRandomNumber(0, 4); if (InMoveRange()) { _weenie->Movement_Teleport(positions[randomLoc]); Done(); } else Cancel(WERROR_MOVED_TOO_FAR); } void CPKLArenaUseEvent::OnReadyToUse() { SetupRecall(); _timeout = Timer::cur_time + 18.0; _weenie->ChangeCombatMode(NONCOMBAT_COMBAT_MODE, false); ExecuteUseAnimation(Motion_PKArenaRecall); g_pWorld->BroadcastLocal(_weenie->GetLandcell(), csprintf("%s is going to the PKL Arena.", _weenie->GetName().c_str())); } void CPKLArenaUseEvent::OnUseAnimSuccess(DWORD motion) { if (_weenie->IsDead() || _weenie->IsInPortalSpace() || _weenie->get_minterp()->interpreted_state.forward_command != Motion_Ready) { Cancel(WERROR_INTERRUPTED); return; } Position *positions = new Position[5]; positions[0] = (Position(0x00670117, Vector(30, -50, 0.005f), Quaternion(1, 0, 0, 0))); positions[1] = (Position(0x00670106, Vector(10, 0, 0.005f), Quaternion(0.321023f, 0, 0, -0.947071f))); positions[2] = (Position(0x00670103, Vector(0, -30, 0.005f), Quaternion(0.714424f, 0, 0, -0.699713f))); positions[3] = (Position(0x0067011e, Vector(50, 0, 0.005f), Quaternion(-0.276474f, 0, 0, -0.961021f))); positions[4] = (Position(0x00670127, Vector(60, -30, 0.005f), Quaternion(0.731689f, 0, 0, 0.681639f))); int randomLoc = getRandomNumber(0, 4); if (InMoveRange()) { _weenie->Movement_Teleport(positions[randomLoc]); Done(); } else Cancel(WERROR_MOVED_TOO_FAR); } void CAllegianceHometownRecallUseEvent::OnReadyToUse() { SetupRecall(); _timeout = Timer::cur_time + 18.0; _weenie->ChangeCombatMode(NONCOMBAT_COMBAT_MODE, false); ExecuteUseAnimation(Motion_AllegianceHometownRecall); g_pWorld->BroadcastLocal(_weenie->GetLandcell(), csprintf("%s is going to the Allegiance hometown.", _weenie->GetName().c_str())); } void CAllegianceHometownRecallUseEvent::OnUseAnimSuccess(DWORD motion) { if (_weenie->IsDead() || _weenie->IsInPortalSpace() || _weenie->get_minterp()->interpreted_state.forward_command != Motion_Ready) { Cancel(WERROR_INTERRUPTED); return; } if (InMoveRange()) { _weenie->TeleportToAllegianceHometown(); Done(); } else Cancel(WERROR_MOVED_TOO_FAR); } void CPlayerWeenie::BeginRecall(const Position &targetPos) { EmitEffect(PS_Hide, 1.0f); _recallTime = Timer::cur_time + 2.0; _recallPos = targetPos; } void CPlayerWeenie::OnTeleported() { CWeenieObject::OnTeleported(); _recallTime = -1.0; // cancel any teleport } DWORD CPlayerWeenie::GetAccountHouseId() { if (GetClient()) { for (auto &character : GetClient()->GetCharacters()) { if (character.weenie_id == id) { if (DWORD houseID = InqDIDQuality(HOUSEID_DID, 0)) return houseID; } else { CWeenieObject *otherCharacter = CWeenieObject::Load(character.weenie_id); if (!otherCharacter) { WINLOG(Temp, Normal, "Failed to Load Character id: %u", character.weenie_id, " in GetAccountHouseID()"); SERVER_ERROR << "Failed to Load Character id: %u" << character.weenie_id << " in GetAccountHouseID()"; } else { if (DWORD houseID = otherCharacter->InqDIDQuality(HOUSEID_DID, 0)) { delete otherCharacter; return houseID; } delete otherCharacter; } } } } return 0; } TradeManager* CPlayerWeenie::GetTradeManager() { return m_pTradeManager; } void CPlayerWeenie::SetTradeManager(TradeManager * tradeManager) { m_pTradeManager = tradeManager; } void CPlayerWeenie::ReleaseContainedItemRecursive(CWeenieObject *item) { CContainerWeenie::ReleaseContainedItemRecursive(item); } void CPlayerWeenie::ChangeCombatMode(COMBAT_MODE mode, bool playerRequested) { CMonsterWeenie::ChangeCombatMode(mode, playerRequested); if (m_pTradeManager && mode != NONCOMBAT_COMBAT_MODE) { m_pTradeManager->CloseTrade(this, 2); // EnteredCombat } } void CPlayerWeenie::AddCorpsePermission(CPlayerWeenie * target) { if (GetID() != target->GetID()) // can't add yourself to permissions { if (!m_umCorpsePermissions.empty()) { for (auto it : m_umCorpsePermissions) { if (it.first == target->GetID()) { SendText(csprintf("%s is already permitted to loot your corpse!", target->GetName().c_str()), LTT_DEFAULT); return; } } } m_umCorpsePermissions.emplace(target->GetID(), static_cast(Timer::cur_time)); // add them to our permissions list SendText(csprintf("%s has been permitted to loot your corpse.", target->GetName().c_str()), LTT_DEFAULT); target->SendText(csprintf("You have been permitted to loot the corpse of %s.", GetName().c_str()), LTT_DEFAULT); target->m_umConsentList.emplace(GetID(), static_cast(Timer::cur_time)); } } void CPlayerWeenie::RemoveCorpsePermission(CPlayerWeenie * target) { if (GetID() != target->GetID()) // can't remove yourself from permissions { if (!m_umCorpsePermissions.empty()) { for (auto it : m_umCorpsePermissions) { if (it.first == target->GetID()) { m_umCorpsePermissions.erase(it.first); // remove them from our permissions list SendText(csprintf("%s is no longer permitted to loot your corpse.", target->GetName().c_str()), LTT_DEFAULT); target->SendText(csprintf("You are no longer permitted to loot the corpse of %s.", GetName().c_str()), LTT_DEFAULT); target->m_umConsentList.erase(GetID()); return; } } SendText(csprintf("%s isn't permitted to loot your corpse!", target->GetName().c_str()), LTT_DEFAULT); } else { SendText("Nobody is currently permitted to loot your corpse.", LTT_DEFAULT); } } } void CPlayerWeenie::UpdateCorpsePermissions() // Check whether any permissions have expired { if (!m_umCorpsePermissions.empty()) { for (auto it : m_umCorpsePermissions) { CPlayerWeenie *cp = g_pWorld->FindPlayer(it.first); if (it.second < static_cast(Timer::cur_time - 3600) || !cp) // If an hour has gone by or the player isn't online any more revoke permissions { RemoveCorpsePermission(cp); } } } } bool CPlayerWeenie::HasPermission(CPlayerWeenie * target) { if (!m_umCorpsePermissions.empty()) { for (auto it : m_umCorpsePermissions) { if (it.first == target->GetID()) return true; } } return false; } void CPlayerWeenie::ClearPermissions() { if (!m_umCorpsePermissions.empty()) { m_umCorpsePermissions.clear(); } } void CPlayerWeenie::RemoveConsent(CPlayerWeenie * target) { if (!m_umConsentList.empty()) { for (auto it : m_umConsentList) { if (it.first == target->GetID()) { CPlayerWeenie *cp = g_pWorld->FindPlayer(it.first); m_umConsentList.erase(it.first); // remove from our consent list SendText(csprintf("You have removed the corpse looting permission of %s.", target->GetName().c_str()), LTT_DEFAULT); cp->m_umCorpsePermissions.erase(GetID()); cp->SendText(csprintf("%s is no longer permitted to loot your corpse.", GetName().c_str()), LTT_DEFAULT); return; } } SendText(csprintf("You don't have corpse looting permission from %s.", target->GetName().c_str()), LTT_DEFAULT); } } void CPlayerWeenie::DisplayConsent() { if (!m_umConsentList.empty()) { SendText("You have corpse looting permission from:", LTT_DEFAULT); for (auto it : m_umConsentList) { CPlayerWeenie *cp = g_pWorld->FindPlayer(it.first); SendText(cp->GetName().c_str(), LTT_DEFAULT); } } else { SendText("You do not have permission to loot anyone's corpse.", LTT_DEFAULT); } } void CPlayerWeenie::ClearConsent(bool onLogout) { if (!m_umConsentList.empty()) { for (auto it : m_umConsentList) { CPlayerWeenie *cp = g_pWorld->FindPlayer(it.first); cp->m_umCorpsePermissions.erase(GetID()); SendText(csprintf("You are no longer permitted to loot the corpse of %s.", cp->GetName().c_str()), LTT_DEFAULT); cp->SendText(csprintf("%s is no longer permitted to loot your corpse.", GetName().c_str()), LTT_DEFAULT); } m_umConsentList.clear(); if (!onLogout) SendText("You have cleared your consent list. Players will have to permit you again to allow you access to their corpse.", LTT_DEFAULT); } else { if (!onLogout) SendText("You don't have any corpse consent to clear!", LTT_DEFAULT); } } void CPlayerWeenie::UpdatePKActivity() { m_iPKActivity = Timer::cur_time + 20; //Set LAST_PK_ATTACK_TIMESTAMP_FLOAT for use in CACQualities::JumpStaminaCost as m_iPKActivity is not available. m_Qualities.SetFloat(LAST_PK_ATTACK_TIMESTAMP_FLOAT, (double) m_iPKActivity); } CCraftOperation *CPlayerWeenie::TryGetAlternativeOperation(CWeenieObject *target, CWeenieObject *tool, CCraftOperation *op) { int coverage = target->m_Qualities.GetInt(LOCATIONS_INT, 0); switch (tool->m_Qualities.id) { case W_DYERAREETERNALFOOLPROOFBLUE_CLASS: case W_DYERAREETERNALFOOLPROOFBLACK_CLASS: case W_DYERAREETERNALFOOLPROOFBOTCHED_CLASS: case W_DYERAREETERNALFOOLPROOFDARKGREEN_CLASS: case W_DYERAREETERNALFOOLPROOFDARKRED_CLASS: case W_DYERAREETERNALFOOLPROOFDARKYELLOW_CLASS: case W_DYERAREETERNALFOOLPROOFLIGHTBLUE_CLASS: case W_DYERAREETERNALFOOLPROOFLIGHTGREEN_CLASS: case W_DYERAREETERNALFOOLPROOFPURPLE_CLASS: case W_DYERAREETERNALFOOLPROOFSILVER_CLASS: { //Get base dye recipe, set fail to false, etc. //Check if the item is armor/clothing and is Dyeable. if (target->m_Qualities.m_WeenieType != 2 || !target->m_Qualities.GetBool(DYABLE_BOOL, 0)) return NULL; //Grab dye recipe to use as a base. op = g_pPortalDataEx->_craftTableData._operations.lookup(3844); op->_difficulty = 0; op->_failAmount = 0; op->_failureConsumeToolAmount = 0; op->_failureConsumeToolChance = 0; op->_successAmount = 0; op->_successConsumeToolAmount = 0; op->_successConsumeToolChance = 0; op->_successWcid = 0; op->_failWcid = 0; break; } case W_MATERIALRAREETERNALIVORY_CLASS: { //Ivory Stuff here, grab ivory recipe, set fail to false, etc. //Check if the item is Ivoryable if (!target->m_Qualities.GetBool(IVORYABLE_BOOL, 0)) return NULL; //Grab ivory recipe to use as a base. op = g_pPortalDataEx->_craftTableData._operations.lookup(3977); op->_difficulty = 0; op->_failAmount = 0; op->_failureConsumeToolAmount = 0; op->_failureConsumeToolChance = 0; op->_successAmount = 0; op->_successConsumeToolAmount = 0; op->_successConsumeToolChance = 0; op->_successWcid = 0; op->_failWcid = 0; break; } case W_MATERIALRAREETERNALLEATHER_CLASS: { //Leather here, grab leather recipe, set fail to false, etc. //Check if the item is already retained or if it is not sellable. if (target->m_Qualities.GetBool(RETAINED_BOOL, 0) || !target->m_Qualities.GetBool(IS_SELLABLE_BOOL, 1)) return NULL; //Grab leather recipe to use as a base. op = g_pPortalDataEx->_craftTableData._operations.lookup(4426); op->_difficulty = 0; op->_failAmount = 0; op->_failureConsumeToolAmount = 0; op->_failureConsumeToolChance = 0; op->_successAmount = 0; op->_successConsumeToolAmount = 0; op->_successConsumeToolChance = 0; op->_successWcid = 0; op->_failWcid = 0; break; } case W_MATERIALIVORY_CLASS: { //Check if the item is Ivoryable if (!target->m_Qualities.GetBool(IVORYABLE_BOOL, 0)) return NULL; //Grab ivory recipe to use as a base. op = g_pPortalDataEx->_craftTableData._operations.lookup(3977); break; } case W_MATERIALLEATHER_CLASS: { //Check if the item is already retained or if it is not sellable. if (target->m_Qualities.GetBool(RETAINED_BOOL, 0) || !target->m_Qualities.GetBool(IS_SELLABLE_BOOL, 1)) return NULL; //Grab leather recipe to use as a base. op = g_pPortalDataEx->_craftTableData._operations.lookup(4426); break; } case W_MATERIALGOLD_CLASS: { //Check if the item has value and workmanship. if (!target->m_Qualities.GetInt(VALUE_INT, 0) || !target->m_Qualities.GetInt(ITEM_WORKMANSHIP_INT, 0)) return NULL; //Grab gold recipe to use as a base. op = g_pPortalDataEx->_craftTableData._operations.lookup(3851); break; } case W_MATERIALLINEN_CLASS: { //Check if the item has burden and workmanship. if (!target->m_Qualities.GetInt(ENCUMB_VAL_INT, 0) || !target->m_Qualities.GetInt(ITEM_WORKMANSHIP_INT, 0)) return NULL; //Grab linen recipe to use as a base. op = g_pPortalDataEx->_craftTableData._operations.lookup(3854); break; } case W_MATERIALMOONSTONE_CLASS: { //Check if the item has mana and workmanship. if (!target->m_Qualities.GetInt(ITEM_MAX_MANA_INT, 0) || !target->m_Qualities.GetInt(ITEM_WORKMANSHIP_INT, 0)) return NULL; //Grab moonstone recipe to use as a base. op = g_pPortalDataEx->_craftTableData._operations.lookup(3978); break; } case W_MATERIALPINE_CLASS: { //Check if the item has value and workmanship. if (!target->m_Qualities.GetInt(VALUE_INT, 0) || !target->m_Qualities.GetInt(ITEM_WORKMANSHIP_INT, 0)) return NULL; //Grab pine recipe to use as a base. op = g_pPortalDataEx->_craftTableData._operations.lookup(3858); break; } case W_MATERIALIRON_CLASS: case W_MATERIALGRANITE_CLASS: case W_MATERIALVELVET_CLASS: { //Check if the item is a melee weapon and has workmanship. if (target->m_Qualities.m_WeenieType != MeleeWeapon_WeenieType || !target->m_Qualities.GetInt(ITEM_WORKMANSHIP_INT, 0)) return NULL; //Grab correct recipe to use as a base. switch (tool->m_Qualities.id) { case W_MATERIALIRON_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(3853); break; case W_MATERIALGRANITE_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(3852); break; case W_MATERIALVELVET_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(3861); break; } break; } case W_MATERIALMAHOGANY_CLASS: { //Check if the item is a missile weapon and has workmanship. if (target->m_Qualities.m_WeenieType != MissileLauncher_WeenieType || !target->m_Qualities.GetInt(ITEM_WORKMANSHIP_INT, 0)) return NULL; //Grab mahog recipe to use as a base. op = g_pPortalDataEx->_craftTableData._operations.lookup(3855); break; } case W_MATERIALOPAL_CLASS: { //Check if the item is a caster and has workmanship. if (target->m_Qualities.m_WeenieType != Caster_WeenieType || !target->m_Qualities.GetInt(ITEM_WORKMANSHIP_INT, 0)) return NULL; //Grab opal recipe to use as a base. op = g_pPortalDataEx->_craftTableData._operations.lookup(3979); break; } case W_MATERIALGREENGARNET_CLASS: { //Check if the item is a caster and has workmanship. if (target->m_Qualities.m_WeenieType != Caster_WeenieType || !target->m_Qualities.GetInt(ITEM_WORKMANSHIP_INT, 0)) return NULL; //Grab green garnet recipe to use as a base. op = g_pPortalDataEx->_craftTableData._operations.lookup(5202); break; } case W_MATERIALBRASS_CLASS: { //Check if the item has workmanship. if (!target->m_Qualities.GetInt(ITEM_WORKMANSHIP_INT, 0)) return NULL; //Grab brass recipe to use as a base. op = g_pPortalDataEx->_craftTableData._operations.lookup(3848); break; } case W_MATERIALROSEQUARTZ_CLASS: case W_MATERIALREDJADE_CLASS: case W_MATERIALMALACHITE_CLASS: case W_MATERIALLAVENDERJADE_CLASS: case W_MATERIALHEMATITE_CLASS: case W_MATERIALBLOODSTONE_CLASS: case W_MATERIALAZURITE_CLASS: case W_MATERIALAGATE_CLASS: case W_MATERIALSMOKYQUARTZ_CLASS: case W_MATERIALCITRINE_CLASS: case W_MATERIALCARNELIAN_CLASS: { //Check if the item is a generic weenietype, jewelry item type, and has workmanship. if (target->m_Qualities.m_WeenieType != Generic_WeenieType || !target->m_Qualities.GetInt(ITEM_WORKMANSHIP_INT, 0) || target->m_Qualities.GetInt(ITEM_TYPE_INT, 0) != TYPE_JEWELRY) return NULL; //Grab correct recipe to use as a base. switch (tool->m_Qualities.id) { case W_MATERIALROSEQUARTZ_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(4446); break; case W_MATERIALREDJADE_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(4442); break; case W_MATERIALMALACHITE_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(4438); break; case W_MATERIALLAVENDERJADE_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(4441); break; case W_MATERIALHEMATITE_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(4440); break; case W_MATERIALBLOODSTONE_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(4448); break; case W_MATERIALAZURITE_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(4437); break; case W_MATERIALAGATE_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(4445); break; case W_MATERIALSMOKYQUARTZ_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(4447); break; case W_MATERIALCITRINE_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(4439); break; case W_MATERIALCARNELIAN_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(4443); break; } break; } case W_MATERIALSTEEL_CLASS: case W_MATERIALALABASTER_CLASS: case W_MATERIALBRONZE_CLASS: case W_MATERIALMARBLE_CLASS: case W_MATERIALARMOREDILLOHIDE_CLASS: case W_MATERIALCERAMIC_CLASS: case W_MATERIALWOOL_CLASS: case W_MATERIALREEDSHARKHIDE_CLASS: case W_MATERIALSILVER_CLASS: case W_MATERIALCOPPER_CLASS: { //Armor and shields //Check if the item is armor item type, has AL, and has workmanship. if (target->m_Qualities.GetInt(ITEM_TYPE_INT, 0) != TYPE_ARMOR || !target->m_Qualities.GetInt(ITEM_WORKMANSHIP_INT, 0) || !target->m_Qualities.GetInt(ARMOR_LEVEL_INT, 0)) return NULL; //Grab correct recipe to use as a base. switch (tool->m_Qualities.id) { case W_MATERIALSTEEL_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(3860); break; case W_MATERIALALABASTER_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(3846); break; case W_MATERIALBRONZE_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(3849); break; case W_MATERIALMARBLE_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(3856); break; case W_MATERIALARMOREDILLOHIDE_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(3847); break; case W_MATERIALCERAMIC_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(3850); break; case W_MATERIALWOOL_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(3862); break; case W_MATERIALREEDSHARKHIDE_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(3859); break; case W_MATERIALSILVER_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(4427); break; case W_MATERIALCOPPER_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(4428); break; } break; } case W_MATERIALPERIDOT_CLASS: case W_MATERIALYELLOWTOPAZ_CLASS: case W_MATERIALZIRCON_CLASS: case W_MATERIALRAREFOOLPROOFPERIDOT_CLASS: case W_MATERIALRAREFOOLPROOFYELLOWTOPAZ_CLASS: case W_MATERIALRAREFOOLPROOFZIRCON_CLASS: { //Armor only //Check if the item is clothing weenietype, armor item type, has AL, and has workmanship. if (target->m_Qualities.m_WeenieType != Clothing_WeenieType || target->m_Qualities.GetInt(ITEM_TYPE_INT, 0) != TYPE_ARMOR || !target->m_Qualities.GetInt(ITEM_WORKMANSHIP_INT, 0) || !target->m_Qualities.GetInt(ARMOR_LEVEL_INT, 0)) return NULL; //Grab correct recipe to use as a base. switch (tool->m_Qualities.id) { case W_MATERIALPERIDOT_CLASS: case W_MATERIALRAREFOOLPROOFPERIDOT_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(4435); break; case W_MATERIALYELLOWTOPAZ_CLASS: case W_MATERIALRAREFOOLPROOFYELLOWTOPAZ_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(4434); break; case W_MATERIALZIRCON_CLASS: case W_MATERIALRAREFOOLPROOFZIRCON_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(4433); break; } break; } case W_POTDYEDARKGREEN_CLASS: case W_POTDYEDARKRED_CLASS: case W_POTDYEDARKYELLOW_CLASS: case W_POTDYEWINTERBLUE_CLASS: case W_POTDYEWINTERGREEN_CLASS: case W_POTDYEWINTERSILVER_CLASS: case W_POTDYESPRINGBLACK_CLASS: case W_POTDYESPRINGBLUE_CLASS: case W_POTDYESPRINGPURPLE_CLASS: { //Check if the item is armor/clothing and is Dyeable. if (target->m_Qualities.m_WeenieType != Clothing_WeenieType || !target->m_Qualities.GetBool(DYABLE_BOOL, 0)) return NULL; //Grab dye recipe to use as a base. op = g_pPortalDataEx->_craftTableData._operations.lookup(3844); break; } //Regular and Foolproof imbues, use wcid to grab the operation. 100% chance is handled in Imbue code for foolproof. case W_MATERIALRAREFOOLPROOFAQUAMARINE_CLASS: case W_MATERIALAQUAMARINE_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(4436); break; case W_MATERIALRAREFOOLPROOFBLACKGARNET_CLASS: case W_MATERIALBLACKGARNET_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(4449); break; case W_MATERIALRAREFOOLPROOFBLACKOPAL_CLASS: case W_MATERIALBLACKOPAL_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(3863); break; case W_MATERIALRAREFOOLPROOFEMERALD_CLASS: case W_MATERIALEMERALD_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(4450); break; case W_MATERIALRAREFOOLPROOFFIREOPAL_CLASS: case W_MATERIALFIREOPAL_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(3864); break; case W_MATERIALRAREFOOLPROOFIMPERIALTOPAZ_CLASS: case W_MATERIALIMPERIALTOPAZ_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(4454); break; case W_MATERIALRAREFOOLPROOFJET_CLASS: case W_MATERIALJET_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(4451); break; case W_MATERIALRAREFOOLPROOFREDGARNET_CLASS: case W_MATERIALREDGARNET_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(4452); break; case W_MATERIALRAREFOOLPROOFSUNSTONE_CLASS: case W_MATERIALSUNSTONE_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(3865); break; case W_MATERIALRAREFOOLPROOFWHITESAPPHIRE_CLASS: case W_MATERIALWHITESAPPHIRE_CLASS: op = g_pPortalDataEx->_craftTableData._operations.lookup(4453); break; case 45683: // Left-hand tether op = g_pPortalDataEx->_craftTableData._operations.lookup(6798); break; case 45684: // Left-hand tether remover op = g_pPortalDataEx->_craftTableData._operations.lookup(6799); break; case 42979: // Core Plating Integrator op = g_pPortalDataEx->_craftTableData._operations.lookup(6800); if (op) { for (auto it = op->_mods[0]._stringMod.begin(); it != op->_mods[0]._stringMod.end(); it++) { if (it._Ptr->_Myval._stat64i32 == GEAR_PLATING_NAME_STRING) { switch (coverage) { case INVENTORY_LOC::HEAD_WEAR_LOC: it._Ptr->_Myval._value = "Core Helm Plating"; break; case INVENTORY_LOC::HAND_WEAR_LOC: it._Ptr->_Myval._value = "Core Gauntlet Plating"; break; case INVENTORY_LOC::FOOT_WEAR_LOC: it._Ptr->_Myval._value = "Core Solleret Plating"; break; case INVENTORY_LOC::UPPER_ARM_ARMOR_LOC: it._Ptr->_Myval._value = "Core Pauldron Plating"; break; case INVENTORY_LOC::LOWER_ARM_ARMOR_LOC: it._Ptr->_Myval._value = "Core Bracer Plating"; break; case INVENTORY_LOC::LOWER_LEG_ARMOR_LOC: it._Ptr->_Myval._value = "Core Greaves Plating"; break; case INVENTORY_LOC::ABDOMEN_ARMOR_LOC: it._Ptr->_Myval._value = "Core Girth Plating"; break; case INVENTORY_LOC::CHEST_ARMOR_LOC: it._Ptr->_Myval._value = "Core Chest Plating"; break; case INVENTORY_LOC::UPPER_LEG_ARMOR_LOC: it._Ptr->_Myval._value = "Core Tasset Plating"; break; case 0x0E: // Tunic - Abdomen, Chest, Upper Arms case 0x1E: // Shirt - Abdomen, Chest, Upper/Lower Arms it._Ptr->_Myval._value = "Core Upper Body Underplating"; break; case 0xC4: // Pants - Upper/Lower Legs it._Ptr->_Myval._value = "Core Lower Body Underplating"; break; case 0xDE: // Raiment - Abdomen, Upper/Lower Arms, Upper/Lower Legs it._Ptr->_Myval._value = "Core Raiment Underplating"; break; case 0x600: // Cuirass - Chest, Abdomen it._Ptr->_Myval._value = "Core Cuirass Plating"; break; case 0xE00: // Chainmail Shirt - Chest, Upper Arms, Abdomen case 0xA00: // Jaleh's Chain Shirt - Chest, Upper Arms it._Ptr->_Myval._value = "Core Shirt Plating"; break; case 0x1A00: // Amuli Coat - Chest, Upper/Lower Arms it._Ptr->_Myval._value = "Core Coat Plating"; break; case 0x1E00: // Hauberk - Chest, Upper/Lower Arms, Abdomen it._Ptr->_Myval._value = "Core Hauberk Plating"; break; case 0x6000: // Celdon Leggings - Upper/Lower Leg case 0x6400: // Amuli Leggings - Abdomen, Upper/Lower Leg it._Ptr->_Myval._value = "Core Leg Plating"; break; case 0x7F00: // Faran Robe - Chest, Abdomen, Upper/Lower Arms, Upper/Lower Legs, Feet case 0x7F01: // Faran Robe with Hood - Head + Robe case 0x7A00: // Swamp Lord's War Paint - Robe - Feet it._Ptr->_Myval._value = "Core Body Plating"; break; case 0x7F20: // Guise - All parts - Head case 0x7F21: // Full Guise - All parts it._Ptr->_Myval._value = "Core Guise Plating"; break; default: if (coverage < 0x100) // Clothing it._Ptr->_Myval._value = "Core Underplating"; else it._Ptr->_Myval._value = "Core Armor Plating"; break; } } } } break; case 43022: // Core Plating Deintegrator op = g_pPortalDataEx->_craftTableData._operations.lookup(6801); break; default: return NULL; } return op; }