gdle/Source/Player.cpp
2018-12-26 02:52:36 -06:00

4625 lines
No EOL
132 KiB
C++

#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 <chrono>
#include <algorithm>
#include <functional>
#define PLAYER_SAVE_INTERVAL 180.0
#define PLAYER_HEALTH_POLL_INTERVAL 5.0
DEFINE_PACK(SalvageResult)
{
pWriter->Write<DWORD>(material);
pWriter->Write<double>(workmanship);
pWriter->Write<int>(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<DWORD>(0x02C1);
AddSpellToSpellbook.Write<DWORD>(id);
AddSpellToSpellbook.Write<DWORD>(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<DWORD, double>::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<DWORD, double>::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<DWORD> expired;
expired.push_back(666);
if (m_Qualities._enchantment_reg)
{
m_Qualities._enchantment_reg->RemoveEnchantments(&expired);
}
BinaryWriter expireMessage;
expireMessage.Write<DWORD>(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<CWeenieObject *> &removeList, std::vector<CWeenieObject *> &alwaysDropList, std::vector<CWeenieObject *> &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<CWeenieObject *> removeList;
std::vector<CWeenieObject *> alwaysDropList;
std::vector<CWeenieObject *> 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 <int, CWeenieObject *> itemValueList;
std::map<std::string, int> 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<std::string, int>(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<int, CWeenieObject *>(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<std::string, int>(objName, 0));
// reduce value by half for all previous items with same name
value /= 1 << itemNameList[objName];
itemNameList[objName]++;
}
itemValueList.insert(std::pair<int, CWeenieObject *>(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<DWORD>(0x1A7);
msg.Write<int>(error); // no error
SendNetMessage(&msg, PRIVATE_MSG, TRUE, FALSE);
}
void CPlayerWeenie::NotifyCommenceAttack()
{
BinaryWriter msg;
msg.Write<DWORD>(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<DWORD>(0x1B1);
msg.WriteString(name);
msg.Write<DWORD>(dmgType);
msg.Write<double>(healthPercent);
msg.Write<DWORD>(health);
msg.Write<DWORD>(crit);
msg.Write<DWORD64>(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<DWORD>(0x1B2);
msg.WriteString(name);
msg.Write<DWORD>(dmgType);
msg.Write<double>(healthPercent);
msg.Write<DWORD>(health);
msg.Write<int>(hitPart);
msg.Write<DWORD>(crit);
msg.Write<DWORD>(attackConditions);
msg.Write<DWORD>(0); // probably DWORD align
SendNetMessage(&msg, PRIVATE_MSG, TRUE, FALSE);
}
void CPlayerWeenie::NotifyKillerEvent(const char *text)
{
BinaryWriter msg;
msg.Write<DWORD>(0x1AD);
msg.WriteString(text);
SendNetMessage(&msg, PRIVATE_MSG, TRUE, FALSE);
}
void CPlayerWeenie::NotifyVictimEvent(const char *text)
{
BinaryWriter msg;
msg.Write<DWORD>(0x1AC);
msg.WriteString(text);
SendNetMessage(&msg, PRIVATE_MSG, TRUE, FALSE);
}
void CPlayerWeenie::NotifyUseDone(int error)
{
BinaryWriter msg;
msg.Write<DWORD>(0x1C7);
msg.Write<int>(error);
SendNetMessage(&msg, PRIVATE_MSG, TRUE, FALSE);
}
void CPlayerWeenie::NotifyWeenieError(int error)
{
BinaryWriter msg;
msg.Write<DWORD>(0x28A);
msg.Write<int>(error);
SendNetMessage(&msg, PRIVATE_MSG, TRUE, FALSE);
}
void CPlayerWeenie::NotifyWeenieErrorWithString(int error, const char *text)
{
BinaryWriter msg;
msg.Write<DWORD>(0x28B);
msg.Write<int>(error);
msg.WriteString(text);
SendNetMessage(&msg, PRIVATE_MSG, TRUE, FALSE);
}
void CPlayerWeenie::NotifyInventoryFailedEvent(DWORD object_id, int error)
{
BinaryWriter msg;
msg.Write<DWORD>(0xA0);
msg.Write<DWORD>(object_id);
msg.Write<int>(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<CWeenieObject*, CWeenieObject*, bool>
{
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<DWORD>(0x274); // Message Type
confirmCrafting.Write<DWORD>(0x05); // Confirm type (craft)
confirmCrafting.Write<int>(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<STypeInt, int> 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<STypeBool, BOOL> 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<STypeFloat, double> 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<STypeString, std::string> 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<STypeDID, DWORD> 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<STypeIID, DWORD> 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<STypeInt, int> 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<STypeBool, BOOL> 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<STypeFloat, double> 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<STypeString, std::string> 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<STypeDID, DWORD> 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<STypeIID, DWORD> 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<DWORD> 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<MaterialType, SalvageInfo> salvageMap;
std::list<CWeenieObject *> itemsToDestroy;
//Lists for the success message
PackableList<SalvageResult> salvageResults;
PackableList<DWORD> 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<DWORD>(0x02B4);
salvageMsg.Write<DWORD>(0x28); // SkillID: Salvaging
notSalvagable.Pack(&salvageMsg); // PackableList<ObjectID>
salvageResults.Pack(&salvageMsg); // PackableList<SalvageResult>
salvageMsg.Write<int>(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<CharacterSquelch_t> 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<ShortCutManager>(_playerModule.shortcuts_, module.shortcuts_);
CloneMemberPointerData<PackableHashTable<DWORD, long>>(_playerModule.desired_comps_, module.desired_comps_);
CloneMemberPointerData<GenericQualitiesData>(_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<int>(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<int>(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<int>(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;
}