mirror of
https://gitlab.com/Scribble/gdlenhanced.git
synced 2026-08-17 14:23:04 -04:00
3952 lines
No EOL
111 KiB
C++
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
|
|
} |