You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openmw-tes3mp/apps/openmw/mwmechanics/enchanting.cpp

339 lines
11 KiB
C++

12 years ago
#include "enchanting.hpp"
#include <components/misc/rng.hpp>
[General] Implement RecordDynamic packet, part 1 Spell, potion, enchantment, creature, NPC, armor, book, clothing, miscellaneous and weapon record data can now be sent in a RecordDynamic packet. Additionally, the packets include data related to associated magical effects (for spells, potions and enchantments), data related to default inventory contents (for creatures and NPCs) and data related to body parts affected (for armor and clothing). The server now has associated script functions for setting most of the details of the above, with the main exception being individual creature and NPC stats. Records can either be created entirely from scratch or can use an existing record (set via the baseId variable) as a starting point for their values. In the latter case, only the values that are specifically set override the starting values. Creature and NPC records also have an inventoryBaseId that can be used on top of the baseId to base their inventories on another existing record. The client's RecordHelper class has been heavily expanded to allow for the above mentioned functionality. When players create spells, potions and enchantments as part of regular gameplay, they send RecordDynamic packets that provide the server with the complete details of the records that should be created. When they create enchantments, they also provide the server with armor, book, clothing and weapon records corresponding to the items they've enchanted. This functionality added by this packet was originally supposed to be exclusive to the rewrite, but I've gone ahead and tried to provide it for the pre-rewrite in a way that can mostly be reused for the rewrite.
7 years ago
/*
Start of tes3mp addition
Include additional headers for multiplayer purposes
*/
#include <components/openmw-mp/Log.hpp>
#include "../mwmp/Main.hpp"
#include "../mwmp/Networking.hpp"
#include "../mwmp/Worldstate.hpp"
/*
End of tes3mp addition
*/
12 years ago
#include "../mwworld/manualref.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "creaturestats.hpp"
#include "npcstats.hpp"
#include "spellcasting.hpp"
#include "actorutil.hpp"
12 years ago
namespace MWMechanics
{
Enchanting::Enchanting()
: mCastStyle(ESM::Enchantment::CastOnce)
, mSelfEnchanting(false)
12 years ago
{}
void Enchanting::setOldItem(const MWWorld::Ptr& oldItem)
12 years ago
{
mOldItemPtr=oldItem;
if(!itemEmpty())
{
mObjectType = mOldItemPtr.getTypeName();
}
else
{
mObjectType="";
}
}
void Enchanting::setNewItemName(const std::string& s)
12 years ago
{
mNewItemName=s;
}
void Enchanting::setEffect(const ESM::EffectList& effectList)
12 years ago
{
mEffectList=effectList;
}
int Enchanting::getCastStyle() const
12 years ago
{
return mCastStyle;
12 years ago
}
void Enchanting::setSoulGem(const MWWorld::Ptr& soulGem)
12 years ago
{
mSoulGemPtr=soulGem;
}
bool Enchanting::create()
12 years ago
{
const MWWorld::Ptr& player = getPlayer();
MWWorld::ContainerStore& store = player.getClass().getContainerStore(player);
ESM::Enchantment enchantment;
enchantment.mData.mCharge = getGemCharge();
enchantment.mData.mAutocalc = 0;
enchantment.mData.mType = mCastStyle;
enchantment.mData.mCost = getBaseCastCost();
store.remove(mSoulGemPtr, 1, player);
//Exception for Azura Star, new one will be added after enchanting
if(Misc::StringUtils::ciEqual(mSoulGemPtr.get<ESM::Miscellaneous>()->mBase->mId, "Misc_SoulGem_Azura"))
store.add("Misc_SoulGem_Azura", 1, player);
if(mSelfEnchanting)
{
if(getEnchantChance() <= (Misc::Rng::roll0to99()))
return false;
mEnchanter.getClass().skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 2);
}
if(mCastStyle==ESM::Enchantment::ConstantEffect)
12 years ago
{
enchantment.mData.mCharge=0;
12 years ago
}
enchantment.mEffects = mEffectList;
// Apply the enchantment
const ESM::Enchantment *enchantmentPtr = MWBase::Environment::get().getWorld()->createRecord (enchantment);
[General] Implement RecordDynamic packet, part 1 Spell, potion, enchantment, creature, NPC, armor, book, clothing, miscellaneous and weapon record data can now be sent in a RecordDynamic packet. Additionally, the packets include data related to associated magical effects (for spells, potions and enchantments), data related to default inventory contents (for creatures and NPCs) and data related to body parts affected (for armor and clothing). The server now has associated script functions for setting most of the details of the above, with the main exception being individual creature and NPC stats. Records can either be created entirely from scratch or can use an existing record (set via the baseId variable) as a starting point for their values. In the latter case, only the values that are specifically set override the starting values. Creature and NPC records also have an inventoryBaseId that can be used on top of the baseId to base their inventories on another existing record. The client's RecordHelper class has been heavily expanded to allow for the above mentioned functionality. When players create spells, potions and enchantments as part of regular gameplay, they send RecordDynamic packets that provide the server with the complete details of the records that should be created. When they create enchantments, they also provide the server with armor, book, clothing and weapon records corresponding to the items they've enchanted. This functionality added by this packet was originally supposed to be exclusive to the rewrite, but I've gone ahead and tried to provide it for the pre-rewrite in a way that can mostly be reused for the rewrite.
7 years ago
/*
Start of tes3mp change (major)
Send the enchantment's record to the server
Don't add the new item to the player's inventory and instead expect the server to
add it
The applyEnchantment() method is where the record of the newly enchanted will be sent
to the server, causing the server to send back the player's inventory with the new item
included
*/
mwmp::Main::get().getNetworking()->getWorldstate()->sendEnchantmentRecord(enchantmentPtr);
store.remove(mOldItemPtr, 1, player);
if(!mSelfEnchanting)
payForEnchantment();
12 years ago
[General] Implement RecordDynamic packet, part 1 Spell, potion, enchantment, creature, NPC, armor, book, clothing, miscellaneous and weapon record data can now be sent in a RecordDynamic packet. Additionally, the packets include data related to associated magical effects (for spells, potions and enchantments), data related to default inventory contents (for creatures and NPCs) and data related to body parts affected (for armor and clothing). The server now has associated script functions for setting most of the details of the above, with the main exception being individual creature and NPC stats. Records can either be created entirely from scratch or can use an existing record (set via the baseId variable) as a starting point for their values. In the latter case, only the values that are specifically set override the starting values. Creature and NPC records also have an inventoryBaseId that can be used on top of the baseId to base their inventories on another existing record. The client's RecordHelper class has been heavily expanded to allow for the above mentioned functionality. When players create spells, potions and enchantments as part of regular gameplay, they send RecordDynamic packets that provide the server with the complete details of the records that should be created. When they create enchantments, they also provide the server with armor, book, clothing and weapon records corresponding to the items they've enchanted. This functionality added by this packet was originally supposed to be exclusive to the rewrite, but I've gone ahead and tried to provide it for the pre-rewrite in a way that can mostly be reused for the rewrite.
7 years ago
std::string newItemId = mOldItemPtr.getClass().applyEnchantment(mOldItemPtr, enchantmentPtr->mId, getGemCharge(), mNewItemName);
/*
End of tes3mp change (major)
*/
return true;
12 years ago
}
void Enchanting::nextCastStyle()
12 years ago
{
if (itemEmpty())
{
mCastStyle = ESM::Enchantment::WhenUsed;
12 years ago
return;
}
const bool powerfulSoul = getGemCharge() >= \
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find ("iSoulAmountForConstantEffect")->getInt();
if ((mObjectType == typeid(ESM::Armor).name()) || (mObjectType == typeid(ESM::Clothing).name()))
{ // Armor or Clothing
switch(mCastStyle)
12 years ago
{
case ESM::Enchantment::WhenUsed:
if (powerfulSoul)
mCastStyle = ESM::Enchantment::ConstantEffect;
return;
default: // takes care of Constant effect too
mCastStyle = ESM::Enchantment::WhenUsed;
return;
12 years ago
}
}
else if(mObjectType == typeid(ESM::Weapon).name())
{ // Weapon
switch(mCastStyle)
12 years ago
{
case ESM::Enchantment::WhenStrikes:
mCastStyle = ESM::Enchantment::WhenUsed;
return;
case ESM::Enchantment::WhenUsed:
if (powerfulSoul)
mCastStyle = ESM::Enchantment::ConstantEffect;
else
mCastStyle = ESM::Enchantment::WhenStrikes;
return;
default: // takes care of Constant effect too
mCastStyle = ESM::Enchantment::WhenStrikes;
return;
12 years ago
}
}
else if(mObjectType == typeid(ESM::Book).name())
{ // Scroll or Book
mCastStyle = ESM::Enchantment::CastOnce;
return;
12 years ago
}
// Fail case
mCastStyle = ESM::Enchantment::CastOnce;
12 years ago
}
/*
* Vanilla enchant cost formula:
*
* Touch/Self: (min + max) * baseCost * 0.025 * duration + area * baseCost * 0.025
* Target: 1.5 * ((min + max) * baseCost * 0.025 * duration + area * baseCost * 0.025)
* Constant eff: (min + max) * baseCost * 2.5 + area * baseCost * 0.025
*
* For multiple effects - cost of each effect is multiplied by number of effects that follows +1.
*
* Note: Minimal value inside formula for 'min' and 'max' is 1. So in vanilla:
* (0 + 0) == (1 + 0) == (1 + 1) => 2 or (2 + 0) == (1 + 2) => 3
*
* Formula on UESPWiki is not entirely correct.
*/
int Enchanting::getEnchantPoints() const
12 years ago
{
if (mEffectList.mList.empty())
// No effects added, cost = 0
return 0;
12 years ago
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
std::vector<ESM::ENAMstruct> mEffects = mEffectList.mList;
int enchantmentCost = 0;
float cost = 0;
12 years ago
for (std::vector<ESM::ENAMstruct>::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it)
{
float baseCost = (store.get<ESM::MagicEffect>().find(it->mEffectID))->mData.mBaseCost;
int magMin = std::max(1, it->mMagnMin);
int magMax = std::max(1, it->mMagnMax);
int area = std::max(1, it->mArea);
12 years ago
float magnitudeCost = (magMin + magMax) * baseCost * 0.05f;
if (mCastStyle == ESM::Enchantment::ConstantEffect)
12 years ago
{
magnitudeCost *= store.get<ESM::GameSetting>().find("fEnchantmentConstantDurationMult")->getFloat();
}
else
{
magnitudeCost *= it->mDuration;
12 years ago
}
float areaCost = area * 0.05f * baseCost;
12 years ago
const float fEffectCostMult = store.get<ESM::GameSetting>().find("fEffectCostMult")->getFloat();
cost += (magnitudeCost + areaCost) * fEffectCostMult;
cost = std::max(1.f, cost);
if (it->mRange == ESM::RT_Target)
cost *= 1.5;
enchantmentCost += static_cast<int>(cost);
12 years ago
}
return enchantmentCost;
12 years ago
}
int Enchanting::getBaseCastCost() const
{
if (mCastStyle == ESM::Enchantment::ConstantEffect)
return 0;
return getEnchantPoints();
}
int Enchanting::getEffectiveCastCost() const
{
int baseCost = getBaseCastCost();
MWWorld::Ptr player = getPlayer();
return getEffectiveEnchantmentCastCost(static_cast<float>(baseCost), player);
}
int Enchanting::getEnchantPrice() const
{
if(mEnchanter.isEmpty())
return 0;
float priceMultipler = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find ("fEnchantmentValueMult")->getFloat();
int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mEnchanter, static_cast<int>(getEnchantPoints() * priceMultipler), true);
return price;
}
int Enchanting::getGemCharge() const
12 years ago
{
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
if(soulEmpty())
return 0;
if(mSoulGemPtr.getCellRef().getSoul()=="")
12 years ago
return 0;
const ESM::Creature* soul = store.get<ESM::Creature>().search(mSoulGemPtr.getCellRef().getSoul());
if(soul)
return soul->mData.mSoul;
else
return 0;
12 years ago
}
int Enchanting::getMaxEnchantValue() const
12 years ago
{
if (itemEmpty())
return 0;
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
return static_cast<int>(mOldItemPtr.getClass().getEnchantmentPoints(mOldItemPtr) * store.get<ESM::GameSetting>().find("fEnchantmentMult")->getFloat());
12 years ago
}
bool Enchanting::soulEmpty() const
12 years ago
{
return mSoulGemPtr.isEmpty();
12 years ago
}
bool Enchanting::itemEmpty() const
12 years ago
{
return mOldItemPtr.isEmpty();
12 years ago
}
void Enchanting::setSelfEnchanting(bool selfEnchanting)
{
mSelfEnchanting = selfEnchanting;
}
void Enchanting::setEnchanter(const MWWorld::Ptr& enchanter)
{
mEnchanter = enchanter;
}
float Enchanting::getEnchantChance() const
{
const NpcStats& npcStats = mEnchanter.getClass().getNpcStats (mEnchanter);
float chance1 = (npcStats.getSkill (ESM::Skill::Enchant).getModified() +
(0.25f * npcStats.getAttribute (ESM::Attribute::Intelligence).getModified())
+ (0.125f * npcStats.getAttribute (ESM::Attribute::Luck).getModified()));
const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
float chance2 = 7.5f / (gmst.find("fEnchantmentChanceMult")->getFloat() * ((mCastStyle == ESM::Enchantment::ConstantEffect) ?
gmst.find("fEnchantmentConstantChanceMult")->getFloat() : 1.0f ))
* getEnchantPoints();
return (chance1-chance2);
}
void Enchanting::payForEnchantment() const
{
const MWWorld::Ptr& player = getPlayer();
MWWorld::ContainerStore& store = player.getClass().getContainerStore(player);
store.remove(MWWorld::ContainerStore::sGoldId, getEnchantPrice(), player);
// add gold to NPC trading gold pool
CreatureStats& enchanterStats = mEnchanter.getClass().getCreatureStats(mEnchanter);
enchanterStats.setGoldPool(enchanterStats.getGoldPool() + getEnchantPrice());
}
12 years ago
}