gdle/Source/Player.cpp
2018-06-21 23:32:21 +01:00

3952 lines
No EOL
111 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"
#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(AsPlayer());
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, m_iPKActivity-30);
_logoutTime = _beginLogoutTime + 5.0;
ChangeCombatMode(NONCOMBAT_COMBAT_MODE, false);
LeaveFellowship();
if (m_pTradeManager)
{
m_pTradeManager->CloseTrade(AsPlayer());
m_pTradeManager = NULL;
}
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 + 60.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();
}
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)
{
std::shared_ptr<CWeenieObject> pTarget = g_pWorld->FindWithinPVS(GetPointer<CWeenieObject>(), m_LastHealthRequest);
if ( pTarget )
{
if (std::shared_ptr<CMonsterWeenie> pMonster = pTarget->AsMonster())
{
SendNetMessage(HealthUpdate(pMonster), PRIVATE_MSG, TRUE, TRUE);
m_NextHealthUpdate = Timer::cur_time + PLAYER_HEALTH_POLL_INTERVAL;
return;
}
}
RemoveLastHealthRequest();
}
}
bool CPlayerWeenie::IsBusy()
{
if (IsRecalling() || IsLoggingOut() || CWeenieObject::IsBusy())
return true;
return false;
}
const double AWARENESS_TIMEOUT = 20.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(std::shared_ptr<CWeenieObject> pEntity, bool bForceUpdate)
{
if (!pEntity)
{
return;
}
#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 (std::shared_ptr<CPhysicsObj> pChild = pEntity->children->objects.array_data[i].lock())
{
if (std::shared_ptr<CWeenieObject> pChildWeenie = pChild->GetPointer<CWeenieObject>())
{
if (BinaryWriter *CM = std::dynamic_pointer_cast<CWeenieObject>(pEntity)->CreateMessage())
{
SendNetMessage(CM, OBJECT_MSG);
}
}
}
}
}
if (pEntity == GetPointer<CWeenieObject>())
{
// make aware of inventory too
for (auto item : m_Wielded)
{
MakeAware(item.lock());
}
for (auto item : m_Items)
{
MakeAware(item.lock());
}
for (auto item : m_Packs)
{
std::shared_ptr<CWeenieObject> pItem = item.lock();
MakeAware(pItem);
if (std::shared_ptr<CContainerWeenie> container = pItem->AsContainer())
{
container->MakeAwareViewContent(GetPointer<CWeenieObject>());
}
}
}
}
void CPlayerWeenie::LoginCharacter(void)
{
DWORD SC[2];
SC[0] = 0xF746;
SC[1] = GetID();
SendNetMessage(SC, sizeof(SC), 10);
BinaryWriter *LC = ::LoginCharacter(AsPlayer());
SendNetMessage(LC->GetData(), LC->GetSize(), PRIVATE_MSG, TRUE);
delete LC;
}
void CPlayerWeenie::ExitPortal()
{
if (_isFirstPortalInSession)
{
TryToUnloadAllegianceXP(true);
_isFirstPortalInSession = false;
}
if (std::shared_ptr<CPhysicsObj> pPhysObj = _phys_obj.lock())
{
pPhysObj->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()
{
if (m_LastAssessed != 0)
{
std::shared_ptr<CWeenieObject> pObject = g_pWorld->FindWithinPVS(GetPointer<CWeenieObject>(), m_LastAssessed);
if (pObject && !pObject->AsPlayer() && !pObject->m_bDontClear) {
std::string name = pObject->GetName();
g_pWorld->RemoveEntity(pObject);
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();
//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, 1000000000);
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(std::vector<std::weak_ptr<CWeenieObject>> items, std::vector<std::shared_ptr<CWeenieObject> > &removeList, std::vector<std::shared_ptr<CWeenieObject> > &alwaysDropList, std::vector<std::shared_ptr<CWeenieObject> > &allValidItems)
{
for (auto item : items)
{
std::shared_ptr<CWeenieObject> pItem = item.lock();
if (!pItem)
{
continue;
}
if (pItem->m_Qualities.id == W_COINSTACK_CLASS)
continue;
else if (pItem->IsDestroyedOnDeath())
removeList.push_back(pItem);
else if (pItem->IsDroppedOnDeath())
alwaysDropList.push_back(pItem);
else if (!pItem->IsBonded())
allValidItems.push_back(pItem);
}
}
//TODO Add nonwielded handling for level > 11 && level < 35
void CPlayerWeenie::CalculateAndDropDeathItems(std::shared_ptr<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);
std::shared_ptr<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 * 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() / 2);
pCorpse->SpawnInContainer(W_COINSTACK_CLASS, coinConsumed);
}
std::vector<std::shared_ptr<CWeenieObject> > removeList;
std::vector<std::shared_ptr<CWeenieObject> > alwaysDropList;
std::vector<std::shared_ptr<CWeenieObject> > allValidItems;
addItemsToDropLists(m_Wielded, removeList, alwaysDropList, allValidItems);
addItemsToDropLists(m_Items, removeList, alwaysDropList, allValidItems);
for (auto packAsWeenie : m_Packs)
{
std::shared_ptr<CWeenieObject> pItem = packAsWeenie.lock();
if (!pItem)
{
continue;
}
std::shared_ptr<CContainerWeenie> pack = pItem->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, std::shared_ptr<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, std::shared_ptr<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, std::shared_ptr<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, static_cast<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 (std::shared_ptr<CCorpseWeenie> pCorpse = _pendingCorpse.lock())
{
//make the player corpse visible.
pCorpse->m_Qualities.RemoveBool(VISIBILITY_BOOL);
pCorpse->NotifyBoolStatUpdated(VISIBILITY_BOOL, false);
pCorpse->NotifyObjectCreated(false);
pCorpse->Save();
_pendingCorpse = std::weak_ptr<CCorpseWeenie>();
}
}
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();
if (killer_id != GetID())
{
if (std::shared_ptr<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);
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 (std::shared_ptr<CCorpseWeenie> pCorpse = _pendingCorpse.lock())
{
CalculateAndDropDeathItems(pCorpse, 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);
//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(std::shared_ptr<CWeenieObject> other)
{
if (GetPointer<CWeenieObject>() != 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(std::shared_ptr<CWeenieObject> source, std::shared_ptr<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 (item->m_Qualities.id == W_COINSTACK_CLASS)
RecalculateCoinAmount();
return result;
}
/*
void CPlayerWeenie::HandleKilledEvent(std::shared_ptr<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(std::shared_ptr<CWeenieObject> killer, DAMAGE_TYPE damageType)
{
}
void CPlayerWeenie::OnDealtDamage(std::shared_ptr<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::binary_function<std::shared_ptr<CWeenieObject>, std::shared_ptr<CWeenieObject>, bool>
{
bool operator()(std::shared_ptr<CWeenieObject> left, std::shared_ptr<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(std::shared_ptr<CWeenieObject> pTool, std::shared_ptr<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
std::shared_ptr<CWeenieObject> pTool = m_pCraftingTool.lock();
std::shared_ptr<CWeenieObject> pTarget = m_pCraftingTarget.lock();
if (!pTool || !pTarget )
{
// no queued crafting op
return WERROR_NONE;
}
int toolType = pTool->InqIntQuality(ITEM_TYPE_INT, 0);
switch (toolType)
{
case TYPE_MANASTONE:
{
// TODO: move this logic to ManaStone.cpp
int targetType = pTarget->InqIntQuality(ITEM_TYPE_INT, 0);
if (!(targetType & TYPE_ITEM) && !(targetType & TYPE_CREATURE)) {
SendText(csprintf("That's not a valid target for the %s", pTool->GetName().c_str()), LTT_DEFAULT); //todo: made up message, confirm if it's correct
break;
}
int manaStoneCurrentMana = pTool->InqIntQuality(ITEM_CUR_MANA_INT, -1, TRUE);
double manaStoneDestroyChance = pTool->InqFloatQuality(MANA_STONE_DESTROY_CHANCE_FLOAT, -1, TRUE);
double manaStoneEfficiency = pTool->InqFloatQuality(ITEM_EFFICIENCY_FLOAT, -1, TRUE);
int targetCurrentMana = pTarget->InqIntQuality(ITEM_CUR_MANA_INT, -1, TRUE);
int targetMaxMana = pTarget->InqIntQuality(ITEM_MAX_MANA_INT, -1, TRUE);
if (manaStoneCurrentMana <= 0) {
if (targetType &TYPE_CREATURE) {
//player attempting to use an uncharged mana stone on themselves...what do we do
SendText("Despite your best efforts, you fail to destroy yourself.", LTT_DEFAULT); // TODO: better text
break;
}
if (targetCurrentMana <= 0) {
SendText(csprintf("The %s has no mana to drain.", pTarget->GetName().c_str()), LTT_DEFAULT); //todo: made up message, confirm if it's correct
break;
}
if (!pTarget->InqBoolQuality(RETAINED_BOOL, FALSE)) {
int drainedMana = round(targetCurrentMana * manaStoneEfficiency);
pTool->m_Qualities.SetInt(ITEM_CUR_MANA_INT, drainedMana);
pTool->m_Qualities.SetInt(UI_EFFECTS_INT, 1);
pTool->NotifyIntStatUpdated(ITEM_CUR_MANA_INT, false);
pTool->NotifyIntStatUpdated(UI_EFFECTS_INT, false);
pTarget->Remove();
RecalculateEncumbrance();
// The Mana Stone drains 4,434 points of mana from the Pocket Watch.
// The Pocket Watch is destroyed.
SendText(csprintf("The %s drains %s points of mana from the %s.\nThe %s is destroyed.", pTool->GetName().c_str(), FormatNumberString(drainedMana).c_str(), pTarget->GetName().c_str(), pTarget->GetName().c_str()), LTT_DEFAULT);
}
else {
SendText("Retained items can't be drained.", LTT_DEFAULT); //todo: made up message, confirm if it's correct
break;
}
}
else { // mana stone is being emptied, not filled
int remainingMana;
if (targetType & TYPE_CREATURE) { // manastone being emptied into the user
if (pTarget->id != id) {
// somehow not using on self. shouldn't be possible with the client. Made up message.
SendText(csprintf("You cannot use the %s on other creatures or players.", pTool->GetName().c_str()), LTT_DEFAULT);
break;
}
priority_queue<std::shared_ptr<CWeenieObject>, vector<std::shared_ptr<CWeenieObject>>, CompareManaNeeds > itemsNeedingMana, itemsStillNeedingMana; // MIN heaps sorted by mana deficit
for (auto wielded : m_Wielded)
{
std::shared_ptr<CWeenieObject> pItem = wielded.lock();
if (!pItem)
{
continue;
}
int curMana = pItem->InqIntQuality(ITEM_CUR_MANA_INT, 0, TRUE);
int maxMana = pItem->InqIntQuality(ITEM_MAX_MANA_INT, 0, TRUE);
int deficit = maxMana - curMana;
if (deficit > 0) {
itemsNeedingMana.push(pItem);
}
}
if (itemsNeedingMana.empty()) {
SendText("None of your items need mana", LTT_DEFAULT); // What's the correct text?
break;
}
int manaToDistribute = manaStoneCurrentMana;
int manaDistributed = 0;
std::set<std::shared_ptr<CWeenieObject>> objectsReceivingMana; // for the chatmessage at the end
while (manaToDistribute > 0 && !(itemsNeedingMana.empty())) {
std::shared_ptr<CWeenieObject> itemNeedingLeastMana = itemsNeedingMana.top();
int deficit = itemNeedingLeastMana->InqIntQuality(ITEM_MAX_MANA_INT, 0, TRUE) - itemNeedingLeastMana->InqIntQuality(ITEM_CUR_MANA_INT, 0, TRUE);
unsigned int manaToApplyToEach = 0;
try
{
if (deficit * itemsNeedingMana.size() >= manaToDistribute) {
// applying smallest deficit to all items would require more mana than available for distribution
manaToApplyToEach = manaToDistribute / static_cast<unsigned int>(itemsNeedingMana.size());
}
else {
manaToApplyToEach = deficit;
}
}
catch(...)
{
SERVER_ERROR << "Error in UseEx for mana stones";
}
manaToApplyToEach = max(1, manaToApplyToEach); // for when manaToDistribute / itemsNeedingMana rounds down to 0, apply 1 mana until we're out
while (!itemsNeedingMana.empty()) {
std::shared_ptr<CWeenieObject> item = itemsNeedingMana.top();
int itemMaxMana = item->InqIntQuality(ITEM_MAX_MANA_INT, 0, TRUE);
itemsNeedingMana.pop();
if (manaToDistribute <= 0) {
// from when we rounded up manaToApplyToEach from 0 to 1, once we're out of mana we want to finish this while loop without applying any more mana to items
itemsStillNeedingMana.push(item);
continue;
}
unsigned int newManaAmount = item->InqIntQuality(ITEM_CUR_MANA_INT, 0, TRUE) + manaToApplyToEach;
manaToDistribute -= manaToApplyToEach;
manaDistributed += manaToApplyToEach;
if (newManaAmount < itemMaxMana) {
itemsStillNeedingMana.push(item);
}
if (manaToApplyToEach > 0) { // handle case when you use a stone that doesn't even have enough mana to give 1 to each deficit item
objectsReceivingMana.insert(item);
}
item->m_Qualities.SetInt(ITEM_CUR_MANA_INT, min(newManaAmount, itemMaxMana));
item->NotifyIntStatUpdated(ITEM_CUR_MANA_INT, false); // todo: is second positional arg correct?
}
itemsNeedingMana.swap(itemsStillNeedingMana);
}
remainingMana = manaToDistribute;
std::stringstream ss;
ss << csprintf("The %s gives %s points of mana to the following items: ", pTool->GetName().c_str(), FormatNumberString(manaDistributed).c_str());
std::set<std::shared_ptr<CWeenieObject>>::iterator it = objectsReceivingMana.begin();
while (it != objectsReceivingMana.end()) {
if (it != objectsReceivingMana.begin()) {
ss << ", ";
}
std::shared_ptr<CWeenieObject> item = *it;
ss << item->GetName();
it++;
}
SendText(ss.str().c_str(), LTT_DEFAULT);
int remainingDeficit = 0;
while (!itemsNeedingMana.empty()) {
remainingDeficit += (itemsNeedingMana.top()->InqIntQuality(ITEM_MAX_MANA_INT, 0, TRUE) - itemsNeedingMana.top()->InqIntQuality(ITEM_CUR_MANA_INT, 0, TRUE));
itemsNeedingMana.pop();
}
if (remainingDeficit == 0) {
SendText("Your items are fully charged.", LTT_DEFAULT);
}
else {
SendText(csprintf("You need %s more mana to fully charge your items.", FormatNumberString(remainingDeficit).c_str()), LTT_DEFAULT);
}
}
else { // manastone being emptied into an item
if (targetMaxMana <= 0)
{
SendText(csprintf("The %s does not require mana.", pTarget->GetName().c_str()), LTT_DEFAULT); //todo: made up message, confirm if it's correct
break;
}
else if (targetCurrentMana >= targetMaxMana)
{
SendText(csprintf("The %s is already fully charged.", pTarget->GetName().c_str()), LTT_DEFAULT); //todo: made up message, confirm if it's correct
break;
}
else
{
int manaNeeded = targetMaxMana - targetCurrentMana;
if (manaStoneCurrentMana >= manaNeeded)
{
pTarget->m_Qualities.SetInt(ITEM_CUR_MANA_INT, targetMaxMana);
pTarget->NotifyIntStatUpdated(ITEM_CUR_MANA_INT, false);
remainingMana = manaStoneCurrentMana - manaNeeded;
SendText(csprintf("The %s gives %s mana to the %s.", pTool->GetName().c_str(), FormatNumberString(manaNeeded).c_str(), pTarget->GetName().c_str()), LTT_DEFAULT); //todo: made up message, confirm if it's correct
SendText(csprintf("The %s is fully charged.", pTarget->GetName().c_str()), LTT_DEFAULT); //todo: made up message, confirm if it's correct
}
else
{
int newManaAmount = targetCurrentMana + manaStoneCurrentMana;
int manaStillNeeded = targetMaxMana - newManaAmount;
remainingMana = 0;
pTarget->m_Qualities.SetInt(ITEM_CUR_MANA_INT, newManaAmount);
pTarget->NotifyIntStatUpdated(ITEM_CUR_MANA_INT, false);
SendText(csprintf("The %s gives %s mana to the %s.", pTool->GetName().c_str(), FormatNumberString(manaStoneCurrentMana).c_str(), pTarget->GetName().c_str()), LTT_DEFAULT); //todo: made up message, confirm if it's correct
SendText(csprintf("You need %s more mana to fully charge the %s.", FormatNumberString(manaStillNeeded).c_str(), pTarget->GetName().c_str()), LTT_DEFAULT); //todo: made up message, confirm if it's correct
}
}
}
if (manaStoneDestroyChance >= 1.0 || Random::RollDice(0.0, 1.0) <= manaStoneDestroyChance) {
SendText(csprintf("The %s is destroyed.", pTool->GetName().c_str()), LTT_DEFAULT);
pTool->Remove();
RecalculateEncumbrance();
}
else {
pTool->m_Qualities.SetInt(ITEM_CUR_MANA_INT, remainingMana);
pTool->NotifyIntStatUpdated(ITEM_CUR_MANA_INT, false);
if (remainingMana == 0)
{
pTool->m_Qualities.SetInt(UI_EFFECTS_INT, 0);
pTool->NotifyIntStatUpdated(UI_EFFECTS_INT, false);
}
}
}
break;
}
default:
{
CCraftOperation *op = g_pPortalDataEx->GetCraftOperation(pTool->m_Qualities.id, pTarget->m_Qualities.id);
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
std::shared_ptr<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);
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);
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;
}
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)
std::shared_ptr<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;
}
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 << " S/F:" << (successChance ? "TRUE" : "FALSE");
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 (requiredStamina == 0)
AdjustStamina(-5); //if we don't have any stamina usage specified let's use 5.
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)
std::shared_ptr<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;
}
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 << " S/F:" << (successChance ? "TRUE" : "FALSE");
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 (requiredStamina == 0)
AdjustStamina(-5); //if we don't have any stamina usage specified let's use 5.
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 = std::weak_ptr<CWeenieObject>();
m_pCraftingTarget = std::weak_ptr<CWeenieObject>();
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, std::shared_ptr<CWeenieObject> pTool, std::shared_ptr<CWeenieObject> pTarget)
{
std::shared_ptr<CWeenieObject> requirementTarget;
switch (index)
{
case 0:
requirementTarget = pTarget;
break;
case 1:
requirementTarget = pTool;
break;
case 2:
requirementTarget = GetPointer<CWeenieObject>();
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, std::shared_ptr<CWeenieObject> pTool, std::shared_ptr<CWeenieObject> pTarget, std::shared_ptr<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);
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));
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, std::shared_ptr<CWeenieObject> pTool, std::shared_ptr<CWeenieObject> pTarget, std::shared_ptr<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;
std::shared_ptr<CWeenieObject> modificationSource = NULL;
switch (intMod._unk) //this is a guess
{
case 0:
modificationSource = GetPointer<CWeenieObject>();
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;
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);
}
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;
std::shared_ptr<CWeenieObject> modificationSource = NULL;
switch (boolMod._unk) //this is a guess
{
case 0:
modificationSource = GetPointer<CWeenieObject>();
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;
std::shared_ptr<CWeenieObject> modificationSource = NULL;
switch (floatMod._unk) //this is a guess
{
case 0:
modificationSource = GetPointer<CWeenieObject>();
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;
std::shared_ptr<CWeenieObject> modificationSource = NULL;
switch (stringMod._unk) //this is a guess
{
case 0:
modificationSource = GetPointer<CWeenieObject>();
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;
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);
}
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;
std::shared_ptr<CWeenieObject> modificationSource = NULL;
switch (didMod._unk) //this is a guess
{
case 0:
modificationSource = GetPointer<CWeenieObject>();
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;
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);
}
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;
std::shared_ptr<CWeenieObject> modificationSource = NULL;
switch (iidMod._unk) //this is a guess
{
case 0:
modificationSource = GetPointer<CWeenieObject>();
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);
}
}
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;
std::shared_ptr<CWeenieObject> pTool = g_pWorld->FindObject(toolId);
if (!pTool)
return;
if (pTool->GetWorldTopLevelOwner() != GetPointer<CWeenieObject>())
{
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<std::shared_ptr<CWeenieObject> > itemsToDestroy;
//Lists for the success message
PackableList<SalvageResult> salvageResults;
PackableList<DWORD> notSalvagable;
for (auto itemId : items)
{
std::shared_ptr<CWeenieObject> pItem = g_pWorld->FindObject(itemId);
if (!pItem)
{
NotifyWeenieError(WERROR_SALVAGE_INVALID_LOOT_LIST);
return;
}
if (pItem->GetWorldTopLevelOwner() != GetPointer<CWeenieObject>())
{
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;
std::shared_ptr<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()
{
//Temporary as a way to fix existing characters
if (m_Qualities._skillStatsTable)
{
for (PackableHashTableWithJson<STypeSkill, Skill>::iterator entry = m_Qualities._skillStatsTable->begin(); entry != m_Qualities._skillStatsTable->end(); entry++)
{
Skill skill = entry->second;
if (skill._sac == SKILL_ADVANCEMENT_CLASS::SPECIALIZED_SKILL_ADVANCEMENT_CLASS)
m_Qualities.SetSkillLevel(entry->first, 10);
else if (skill._sac == SKILL_ADVANCEMENT_CLASS::TRAINED_SKILL_ADVANCEMENT_CLASS)
m_Qualities.SetSkillLevel(entry->first, 5);
else
m_Qualities.SetSkillLevel(entry->first, 0);
}
}
//End of temporary code
g_pAllegianceManager->SetWeenieAllegianceQualities(GetPointer<CWeenieObject>());
m_Qualities.SetFloat(LOGIN_TIMESTAMP_FLOAT, Timer::cur_time);
// 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);
}
// 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)
{
std::shared_ptr<CWeenieObject> pItem = wielded.lock();
if (!pItem)
{
continue;
}
if (pItem->InqIntQuality(ITEM_CUR_MANA_INT, 0, true) > 0)
{
double manaRate = 0.0f;
if (pItem->m_Qualities.InqFloat(MANA_RATE_FLOAT, manaRate, TRUE) && manaRate != 0.0)
pItem->_nextManaUse = Timer::cur_time + (-manaRate * 1000);
}
}
if (!InqBoolQuality(FIRST_ENTER_WORLD_DONE_BOOL, FALSE))
{
for (auto item : m_Items)
{
std::shared_ptr<CWeenieObject> pItem = item.lock();
if (!pItem)
{
continue;
}
if (pItem->m_Qualities.id == W_TUTORIALBOOK_CLASS)
{
pItem->Use(AsPlayer());
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::HandleItemManaRequest(DWORD itemId)
{
if (!itemId)
return;
std::shared_ptr<CWeenieObject> item = FindValidNearbyItem(itemId, 60);
if (!item)
return;
m_pClient->SendNetMessage(ItemManaUpdate(item), PRIVATE_MSG, TRUE, TRUE);
}
void CPlayerWeenie::UpdateModuleFromClient(PlayerModule &module)
{
bool bOldShowHelm = ShowHelm();
_playerModule = module;
if (bOldShowHelm != ShowHelm())
{
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);
}
CWandSpellUseEvent::CWandSpellUseEvent(DWORD wandId, DWORD targetId)
{
_wandId = wandId;
_targetId = targetId;
}
void CWandSpellUseEvent::OnReadyToUse()
{
std::shared_ptr<CWeenieObject> pWeenie = _weenie.lock();
if (!pWeenie)
{
return;
}
std::shared_ptr<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 && pWeenie->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)
pWeenie->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 = pWeenie->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;
}
pWeenie->MakeSpellcastingManager()->m_bCasting = true;
if (motion)
ExecuteUseAnimation(motion);
else
OnUseAnimSuccess(motion);
}
void CWandSpellUseEvent::OnUseAnimSuccess(DWORD motion)
{
std::shared_ptr<CWeenieObject> pWeenie = _weenie.lock();
if (!pWeenie)
{
return;
}
if (_newManaValue != -1)
{
std::shared_ptr<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);
}
pWeenie->MakeSpellcastingManager()->CastSpellInstant(_targetId, _spellId);
pWeenie->DoForcedStopCompletely();
Done();
}
void CWandSpellUseEvent::Cancel(DWORD error)
{
std::shared_ptr<CWeenieObject> pWeenie = _weenie.lock();
if (!pWeenie)
{
return;
}
pWeenie->MakeSpellcastingManager()->m_bCasting = false;
CUseEventData::Cancel(error);
}
void CWandSpellUseEvent::Done(DWORD error)
{
std::shared_ptr<CWeenieObject> pWeenie = _weenie.lock();
if (!pWeenie)
{
return;
}
pWeenie->MakeSpellcastingManager()->m_bCasting = false;
CUseEventData::Done(error);
}
void CLifestoneRecallUseEvent::OnReadyToUse()
{
std::shared_ptr<CWeenieObject> pWeenie = _weenie.lock();
if (!pWeenie)
{
return;
}
pWeenie->ChangeCombatMode(NONCOMBAT_COMBAT_MODE, false);
ExecuteUseAnimation(Motion_LifestoneRecall);
pWeenie->SendText(csprintf("%s is recalling to the lifestone.", pWeenie->GetName().c_str()), LTT_RECALL);
}
void CLifestoneRecallUseEvent::OnUseAnimSuccess(DWORD motion)
{
std::shared_ptr<CWeenieObject> pWeenie = _weenie.lock();
if (!pWeenie)
{
return;
}
pWeenie->TeleportToLifestone();
Done();
}
void CHouseRecallUseEvent::OnReadyToUse()
{
std::shared_ptr<CWeenieObject> pWeenie = _weenie.lock();
if (!pWeenie)
{
return;
}
pWeenie->ChangeCombatMode(NONCOMBAT_COMBAT_MODE, false);
ExecuteUseAnimation(Motion_HouseRecall);
pWeenie->SendText(csprintf("%s is recalling home.", pWeenie->GetName().c_str()), LTT_RECALL);
}
void CHouseRecallUseEvent::OnUseAnimSuccess(DWORD motion)
{
std::shared_ptr<CWeenieObject> pWeenie = _weenie.lock();
if (!pWeenie)
{
return;
}
pWeenie->TeleportToHouse();
Done();
}
void CMansionRecallUseEvent::OnReadyToUse()
{
std::shared_ptr<CWeenieObject> pWeenie = _weenie.lock();
if (!pWeenie)
{
return;
}
pWeenie->ChangeCombatMode(NONCOMBAT_COMBAT_MODE, false);
ExecuteUseAnimation(Motion_HouseRecall);
pWeenie->SendText(csprintf("%s is recalling to the Allegiance housing.", pWeenie->GetName().c_str()), LTT_RECALL);
}
void CMansionRecallUseEvent::OnUseAnimSuccess(DWORD motion)
{
std::shared_ptr<CWeenieObject> pWeenie = _weenie.lock();
if (!pWeenie)
{
return;
}
pWeenie->TeleportToMansion();
Done();
}
void CMarketplaceRecallUseEvent::OnReadyToUse()
{
std::shared_ptr<CWeenieObject> pWeenie = _weenie.lock();
if (!pWeenie)
{
return;
}
pWeenie->ChangeCombatMode(NONCOMBAT_COMBAT_MODE, false);
ExecuteUseAnimation(Motion_MarketplaceRecall);
pWeenie->SendText(csprintf("%s is going to the Marketplace.", pWeenie->GetName().c_str()), LTT_RECALL);
}
void CMarketplaceRecallUseEvent::OnUseAnimSuccess(DWORD motion)
{
std::shared_ptr<CWeenieObject> pWeenie = _weenie.lock();
if (!pWeenie)
{
return;
}
if (pWeenie->IsDead() || pWeenie->IsInPortalSpace())
{
Cancel();
return;
}
pWeenie->Movement_Teleport(Position(0x016C01BC, Vector(49.11f, -31.22f, 0.005f), Quaternion(0.7009f, 0, 0, -0.7132f)));
Done();
}
void CAllegianceHometownRecallUseEvent::OnReadyToUse()
{
std::shared_ptr<CWeenieObject> pWeenie = _weenie.lock();
if (!pWeenie)
{
return;
}
pWeenie->ChangeCombatMode(NONCOMBAT_COMBAT_MODE, false);
ExecuteUseAnimation(Motion_AllegianceHometownRecall);
pWeenie->SendText(csprintf("%s is going to the Allegiance hometown.", pWeenie->GetName().c_str()), LTT_RECALL);
}
void CAllegianceHometownRecallUseEvent::OnUseAnimSuccess(DWORD motion)
{
std::shared_ptr<CWeenieObject> pWeenie = _weenie.lock();
if (!pWeenie)
{
return;
}
pWeenie->TeleportToAllegianceHometown();
Done();
}
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()
{
for (auto &character : GetClient()->GetCharacters())
{
if (character.weenie_id == id)
{
if (DWORD houseID = InqDIDQuality(HOUSEID_DID, 0))
return houseID;
}
else
{
std::shared_ptr<CWeenieObject> otherCharacter = CWeenieObject::Load(character.weenie_id);
if (DWORD houseID = otherCharacter->InqDIDQuality(HOUSEID_DID, 0))
{
return houseID;
}
}
}
return 0;
}
TradeManager* CPlayerWeenie::GetTradeManager()
{
return m_pTradeManager;
}
void CPlayerWeenie::SetTradeManager(TradeManager * tradeManager)
{
m_pTradeManager = tradeManager;
}
void CPlayerWeenie::ReleaseContainedItemRecursive(std::shared_ptr<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(AsPlayer(), 2); // EnteredCombat
}
}
void CPlayerWeenie::MarkForDestroy()
{
m_dDestroyTime = Timer::cur_time + 2; // give some time to wrap up
}