|
|
|
#include "spellcreationdialog.hpp"
|
|
|
|
|
|
|
|
#include <MyGUI_ImageBox.h>
|
|
|
|
#include <MyGUI_Gui.h>
|
|
|
|
|
|
|
|
#include <components/esm/records.hpp>
|
|
|
|
#include <components/widgets/list.hpp>
|
|
|
|
|
|
|
|
/*
|
|
|
|
Start of tes3mp addition
|
|
|
|
|
|
|
|
Include additional headers for multiplayer purposes
|
|
|
|
*/
|
|
|
|
#include "../mwmp/Main.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
|
|
|
#include "../mwmp/Networking.hpp"
|
|
|
|
#include "../mwmp/Worldstate.hpp"
|
|
|
|
/*
|
|
|
|
End of tes3mp addition
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "../mwbase/windowmanager.hpp"
|
|
|
|
#include "../mwbase/mechanicsmanager.hpp"
|
|
|
|
#include "../mwbase/environment.hpp"
|
|
|
|
#include "../mwbase/world.hpp"
|
|
|
|
|
|
|
|
#include "../mwworld/containerstore.hpp"
|
|
|
|
#include "../mwworld/class.hpp"
|
|
|
|
#include "../mwworld/esmstore.hpp"
|
|
|
|
|
|
|
|
#include "../mwmechanics/spellcasting.hpp"
|
|
|
|
#include "../mwmechanics/spells.hpp"
|
|
|
|
#include "../mwmechanics/creaturestats.hpp"
|
|
|
|
#include "../mwmechanics/actorutil.hpp"
|
|
|
|
|
|
|
|
#include "tooltips.hpp"
|
|
|
|
#include "class.hpp"
|
|
|
|
#include "widgets.hpp"
|
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
|
|
|
|
bool sortMagicEffects (short id1, short id2)
|
|
|
|
{
|
|
|
|
const MWWorld::Store<ESM::GameSetting> &gmst =
|
|
|
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
|
|
|
|
|
|
|
return gmst.find(ESM::MagicEffect::effectIdToString (id1))->getString()
|
|
|
|
< gmst.find(ESM::MagicEffect::effectIdToString (id2))->getString();
|
|
|
|
}
|
|
|
|
|
|
|
|
void init(ESM::ENAMstruct& effect)
|
|
|
|
{
|
|
|
|
effect.mArea = 0;
|
|
|
|
effect.mDuration = 0;
|
|
|
|
effect.mEffectID = -1;
|
|
|
|
effect.mMagnMax = 0;
|
|
|
|
effect.mMagnMin = 0;
|
|
|
|
effect.mRange = 0;
|
|
|
|
effect.mSkill = -1;
|
|
|
|
effect.mAttribute = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace MWGui
|
|
|
|
{
|
|
|
|
|
|
|
|
EditEffectDialog::EditEffectDialog()
|
|
|
|
: WindowModal("openmw_edit_effect.layout")
|
|
|
|
, mEditing(false)
|
|
|
|
, mMagicEffect(NULL)
|
|
|
|
, mConstantEffect(false)
|
|
|
|
{
|
|
|
|
init(mEffect);
|
|
|
|
init(mOldEffect);
|
|
|
|
|
|
|
|
getWidget(mCancelButton, "CancelButton");
|
|
|
|
getWidget(mOkButton, "OkButton");
|
|
|
|
getWidget(mDeleteButton, "DeleteButton");
|
|
|
|
getWidget(mRangeButton, "RangeButton");
|
|
|
|
getWidget(mMagnitudeMinValue, "MagnitudeMinValue");
|
|
|
|
getWidget(mMagnitudeMaxValue, "MagnitudeMaxValue");
|
|
|
|
getWidget(mDurationValue, "DurationValue");
|
|
|
|
getWidget(mAreaValue, "AreaValue");
|
|
|
|
getWidget(mMagnitudeMinSlider, "MagnitudeMinSlider");
|
|
|
|
getWidget(mMagnitudeMaxSlider, "MagnitudeMaxSlider");
|
|
|
|
getWidget(mDurationSlider, "DurationSlider");
|
|
|
|
getWidget(mAreaSlider, "AreaSlider");
|
|
|
|
getWidget(mEffectImage, "EffectImage");
|
|
|
|
getWidget(mEffectName, "EffectName");
|
|
|
|
getWidget(mAreaText, "AreaText");
|
|
|
|
getWidget(mDurationBox, "DurationBox");
|
|
|
|
getWidget(mAreaBox, "AreaBox");
|
|
|
|
getWidget(mMagnitudeBox, "MagnitudeBox");
|
|
|
|
|
|
|
|
mRangeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onRangeButtonClicked);
|
|
|
|
mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onOkButtonClicked);
|
|
|
|
mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onCancelButtonClicked);
|
|
|
|
mDeleteButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditEffectDialog::onDeleteButtonClicked);
|
|
|
|
|
|
|
|
mMagnitudeMinSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onMagnitudeMinChanged);
|
|
|
|
mMagnitudeMaxSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onMagnitudeMaxChanged);
|
|
|
|
mDurationSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onDurationChanged);
|
|
|
|
mAreaSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onAreaChanged);
|
|
|
|
}
|
|
|
|
|
|
|
|
void EditEffectDialog::setConstantEffect(bool constant)
|
|
|
|
{
|
|
|
|
mConstantEffect = constant;
|
|
|
|
}
|
|
|
|
|
|
|
|
void EditEffectDialog::onOpen()
|
|
|
|
{
|
|
|
|
WindowModal::onOpen();
|
|
|
|
center();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool EditEffectDialog::exit()
|
|
|
|
{
|
|
|
|
if(mEditing)
|
|
|
|
eventEffectModified(mOldEffect);
|
|
|
|
else
|
|
|
|
eventEffectRemoved(mEffect);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void EditEffectDialog::newEffect (const ESM::MagicEffect *effect)
|
|
|
|
{
|
|
|
|
bool allowSelf = (effect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0;
|
|
|
|
bool allowTouch = (effect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect;
|
|
|
|
bool allowTarget = (effect->mData.mFlags & ESM::MagicEffect::CastTarget) && !mConstantEffect;
|
|
|
|
|
|
|
|
if (!allowSelf && !allowTouch && !allowTarget)
|
|
|
|
return; // TODO: Show an error message popup?
|
|
|
|
|
|
|
|
setMagicEffect(effect);
|
|
|
|
mEditing = false;
|
|
|
|
|
|
|
|
mDeleteButton->setVisible (false);
|
|
|
|
|
|
|
|
mEffect.mRange = ESM::RT_Self;
|
|
|
|
if (!allowSelf)
|
|
|
|
mEffect.mRange = ESM::RT_Touch;
|
|
|
|
if (!allowTouch)
|
|
|
|
mEffect.mRange = ESM::RT_Target;
|
|
|
|
mEffect.mMagnMin = 1;
|
|
|
|
mEffect.mMagnMax = 1;
|
|
|
|
mEffect.mDuration = 1;
|
|
|
|
mEffect.mArea = 0;
|
|
|
|
mEffect.mSkill = -1;
|
|
|
|
mEffect.mAttribute = -1;
|
|
|
|
eventEffectAdded(mEffect);
|
|
|
|
|
|
|
|
onRangeButtonClicked(mRangeButton);
|
|
|
|
|
|
|
|
mMagnitudeMinSlider->setScrollPosition (0);
|
|
|
|
mMagnitudeMaxSlider->setScrollPosition (0);
|
|
|
|
mAreaSlider->setScrollPosition (0);
|
|
|
|
mDurationSlider->setScrollPosition (0);
|
|
|
|
|
|
|
|
mDurationValue->setCaption("1");
|
|
|
|
mMagnitudeMinValue->setCaption("1");
|
|
|
|
mMagnitudeMaxValue->setCaption("- 1");
|
|
|
|
mAreaValue->setCaption("0");
|
|
|
|
|
|
|
|
setVisible(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void EditEffectDialog::editEffect (ESM::ENAMstruct effect)
|
|
|
|
{
|
|
|
|
const ESM::MagicEffect* magicEffect =
|
|
|
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effect.mEffectID);
|
|
|
|
|
|
|
|
setMagicEffect(magicEffect);
|
|
|
|
mOldEffect = effect;
|
|
|
|
mEffect = effect;
|
|
|
|
mEditing = true;
|
|
|
|
|
|
|
|
mDeleteButton->setVisible (true);
|
|
|
|
|
|
|
|
mMagnitudeMinSlider->setScrollPosition (effect.mMagnMin-1);
|
|
|
|
mMagnitudeMaxSlider->setScrollPosition (effect.mMagnMax-1);
|
|
|
|
mAreaSlider->setScrollPosition (effect.mArea);
|
|
|
|
mDurationSlider->setScrollPosition (effect.mDuration-1);
|
|
|
|
|
|
|
|
if (mEffect.mRange == ESM::RT_Self)
|
|
|
|
mRangeButton->setCaptionWithReplacing ("#{sRangeSelf}");
|
|
|
|
else if (mEffect.mRange == ESM::RT_Target)
|
|
|
|
mRangeButton->setCaptionWithReplacing ("#{sRangeTarget}");
|
|
|
|
else if (mEffect.mRange == ESM::RT_Touch)
|
|
|
|
mRangeButton->setCaptionWithReplacing ("#{sRangeTouch}");
|
|
|
|
|
|
|
|
onMagnitudeMinChanged (mMagnitudeMinSlider, effect.mMagnMin-1);
|
|
|
|
onMagnitudeMaxChanged (mMagnitudeMinSlider, effect.mMagnMax-1);
|
|
|
|
onAreaChanged (mAreaSlider, effect.mArea);
|
|
|
|
onDurationChanged (mDurationSlider, effect.mDuration-1);
|
|
|
|
eventEffectModified(mEffect);
|
|
|
|
|
|
|
|
updateBoxes();
|
|
|
|
}
|
|
|
|
|
|
|
|
void EditEffectDialog::setMagicEffect (const ESM::MagicEffect *effect)
|
|
|
|
{
|
|
|
|
mEffectImage->setImageTexture(MWBase::Environment::get().getWindowManager()->correctIconPath(effect->mIcon));
|
|
|
|
|
|
|
|
mEffectName->setCaptionWithReplacing("#{"+ESM::MagicEffect::effectIdToString (effect->mIndex)+"}");
|
|
|
|
|
|
|
|
mEffect.mEffectID = effect->mIndex;
|
|
|
|
|
|
|
|
mMagicEffect = effect;
|
|
|
|
|
|
|
|
updateBoxes();
|
|
|
|
}
|
|
|
|
|
|
|
|
void EditEffectDialog::updateBoxes()
|
|
|
|
{
|
|
|
|
static int startY = mMagnitudeBox->getPosition().top;
|
|
|
|
int curY = startY;
|
|
|
|
|
|
|
|
mMagnitudeBox->setVisible (false);
|
|
|
|
mDurationBox->setVisible (false);
|
|
|
|
mAreaBox->setVisible (false);
|
|
|
|
|
|
|
|
if (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude))
|
|
|
|
{
|
|
|
|
mMagnitudeBox->setPosition(mMagnitudeBox->getPosition().left, curY);
|
|
|
|
mMagnitudeBox->setVisible (true);
|
|
|
|
curY += mMagnitudeBox->getSize().height;
|
|
|
|
}
|
|
|
|
if (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)&&mConstantEffect==false)
|
|
|
|
{
|
|
|
|
mDurationBox->setPosition(mDurationBox->getPosition().left, curY);
|
|
|
|
mDurationBox->setVisible (true);
|
|
|
|
curY += mDurationBox->getSize().height;
|
|
|
|
}
|
|
|
|
if (mEffect.mRange != ESM::RT_Self)
|
|
|
|
{
|
|
|
|
mAreaBox->setPosition(mAreaBox->getPosition().left, curY);
|
|
|
|
mAreaBox->setVisible (true);
|
|
|
|
//curY += mAreaBox->getSize().height;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void EditEffectDialog::onRangeButtonClicked (MyGUI::Widget* sender)
|
|
|
|
{
|
|
|
|
mEffect.mRange = (mEffect.mRange+1)%3;
|
|
|
|
|
|
|
|
// cycle through range types until we find something that's allowed
|
|
|
|
// does not handle the case where nothing is allowed (this should be prevented before opening the Add Effect dialog)
|
|
|
|
bool allowSelf = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0;
|
|
|
|
bool allowTouch = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect;
|
|
|
|
bool allowTarget = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTarget) && !mConstantEffect;
|
|
|
|
if (mEffect.mRange == ESM::RT_Self && !allowSelf)
|
|
|
|
mEffect.mRange = (mEffect.mRange+1)%3;
|
|
|
|
if (mEffect.mRange == ESM::RT_Touch && !allowTouch)
|
|
|
|
mEffect.mRange = (mEffect.mRange+1)%3;
|
|
|
|
if (mEffect.mRange == ESM::RT_Target && !allowTarget)
|
|
|
|
mEffect.mRange = (mEffect.mRange+1)%3;
|
|
|
|
|
|
|
|
if(mEffect.mRange == ESM::RT_Self)
|
|
|
|
{
|
|
|
|
mAreaSlider->setScrollPosition(0);
|
|
|
|
onAreaChanged(mAreaSlider,0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mEffect.mRange == ESM::RT_Self)
|
|
|
|
mRangeButton->setCaptionWithReplacing ("#{sRangeSelf}");
|
|
|
|
else if (mEffect.mRange == ESM::RT_Target)
|
|
|
|
mRangeButton->setCaptionWithReplacing ("#{sRangeTarget}");
|
|
|
|
else if (mEffect.mRange == ESM::RT_Touch)
|
|
|
|
mRangeButton->setCaptionWithReplacing ("#{sRangeTouch}");
|
|
|
|
|
|
|
|
updateBoxes();
|
|
|
|
eventEffectModified(mEffect);
|
|
|
|
}
|
|
|
|
|
|
|
|
void EditEffectDialog::onDeleteButtonClicked (MyGUI::Widget* sender)
|
|
|
|
{
|
|
|
|
setVisible(false);
|
|
|
|
|
|
|
|
eventEffectRemoved(mEffect);
|
|
|
|
}
|
|
|
|
|
|
|
|
void EditEffectDialog::onOkButtonClicked (MyGUI::Widget* sender)
|
|
|
|
{
|
|
|
|
setVisible(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void EditEffectDialog::onCancelButtonClicked (MyGUI::Widget* sender)
|
|
|
|
{
|
|
|
|
setVisible(false);
|
|
|
|
exit();
|
|
|
|
}
|
|
|
|
|
|
|
|
void EditEffectDialog::setSkill (int skill)
|
|
|
|
{
|
|
|
|
mEffect.mSkill = skill;
|
|
|
|
eventEffectModified(mEffect);
|
|
|
|
}
|
|
|
|
|
|
|
|
void EditEffectDialog::setAttribute (int attribute)
|
|
|
|
{
|
|
|
|
mEffect.mAttribute = attribute;
|
|
|
|
eventEffectModified(mEffect);
|
|
|
|
}
|
|
|
|
|
|
|
|
void EditEffectDialog::onMagnitudeMinChanged (MyGUI::ScrollBar* sender, size_t pos)
|
|
|
|
{
|
|
|
|
mMagnitudeMinValue->setCaption(MyGUI::utility::toString(pos+1));
|
|
|
|
mEffect.mMagnMin = pos+1;
|
|
|
|
|
|
|
|
// trigger the check again (see below)
|
|
|
|
onMagnitudeMaxChanged(mMagnitudeMaxSlider, mMagnitudeMaxSlider->getScrollPosition ());
|
|
|
|
eventEffectModified(mEffect);
|
|
|
|
}
|
|
|
|
|
|
|
|
void EditEffectDialog::onMagnitudeMaxChanged (MyGUI::ScrollBar* sender, size_t pos)
|
|
|
|
{
|
|
|
|
// make sure the max value is actually larger or equal than the min value
|
|
|
|
size_t magnMin = std::abs(mEffect.mMagnMin); // should never be < 0, this is just here to avoid the compiler warning
|
|
|
|
if (pos+1 < magnMin)
|
|
|
|
{
|
|
|
|
pos = mEffect.mMagnMin-1;
|
|
|
|
sender->setScrollPosition (pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
mEffect.mMagnMax = pos+1;
|
|
|
|
|
|
|
|
mMagnitudeMaxValue->setCaption("- " + MyGUI::utility::toString(pos+1));
|
|
|
|
|
|
|
|
eventEffectModified(mEffect);
|
|
|
|
}
|
|
|
|
|
|
|
|
void EditEffectDialog::onDurationChanged (MyGUI::ScrollBar* sender, size_t pos)
|
|
|
|
{
|
|
|
|
mDurationValue->setCaption(MyGUI::utility::toString(pos+1));
|
|
|
|
mEffect.mDuration = pos+1;
|
|
|
|
eventEffectModified(mEffect);
|
|
|
|
}
|
|
|
|
|
|
|
|
void EditEffectDialog::onAreaChanged (MyGUI::ScrollBar* sender, size_t pos)
|
|
|
|
{
|
|
|
|
mAreaValue->setCaption(MyGUI::utility::toString(pos));
|
|
|
|
mEffect.mArea = pos;
|
|
|
|
eventEffectModified(mEffect);
|
|
|
|
}
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
SpellCreationDialog::SpellCreationDialog()
|
|
|
|
: WindowBase("openmw_spellcreation_dialog.layout")
|
|
|
|
, EffectEditorBase(EffectEditorBase::Spellmaking)
|
|
|
|
{
|
|
|
|
getWidget(mNameEdit, "NameEdit");
|
|
|
|
getWidget(mMagickaCost, "MagickaCost");
|
|
|
|
getWidget(mSuccessChance, "SuccessChance");
|
|
|
|
getWidget(mAvailableEffectsList, "AvailableEffects");
|
|
|
|
getWidget(mUsedEffectsView, "UsedEffects");
|
|
|
|
getWidget(mPriceLabel, "PriceLabel");
|
|
|
|
getWidget(mBuyButton, "BuyButton");
|
|
|
|
getWidget(mCancelButton, "CancelButton");
|
|
|
|
|
|
|
|
mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellCreationDialog::onCancelButtonClicked);
|
|
|
|
mBuyButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellCreationDialog::onBuyButtonClicked);
|
|
|
|
mNameEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &SpellCreationDialog::onAccept);
|
|
|
|
|
|
|
|
setWidgets(mAvailableEffectsList, mUsedEffectsView);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpellCreationDialog::setPtr (const MWWorld::Ptr& actor)
|
|
|
|
{
|
|
|
|
mPtr = actor;
|
|
|
|
mNameEdit->setCaption("");
|
|
|
|
|
|
|
|
startEditing();
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpellCreationDialog::onCancelButtonClicked (MyGUI::Widget* sender)
|
|
|
|
{
|
|
|
|
MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_SpellCreation);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpellCreationDialog::onBuyButtonClicked (MyGUI::Widget* sender)
|
|
|
|
{
|
|
|
|
if (mEffects.size() <= 0)
|
|
|
|
{
|
|
|
|
MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage30}");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mNameEdit->getCaption () == "")
|
|
|
|
{
|
|
|
|
MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage10}");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mMagickaCost->getCaption() == "0")
|
|
|
|
{
|
|
|
|
MWBase::Environment::get().getWindowManager()->messageBox ("#{sEnchantmentMenu8}");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
MWWorld::Ptr player = MWMechanics::getPlayer();
|
|
|
|
int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId);
|
|
|
|
|
|
|
|
if (MyGUI::utility::parseInt(mPriceLabel->getCaption()) > playerGold)
|
|
|
|
{
|
|
|
|
MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage18}");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mSpell.mName = mNameEdit->getCaption();
|
|
|
|
|
|
|
|
int price = MyGUI::utility::parseInt(mPriceLabel->getCaption());
|
|
|
|
|
|
|
|
player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player);
|
|
|
|
|
|
|
|
// add gold to NPC trading gold pool
|
|
|
|
MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr);
|
|
|
|
npcStats.setGoldPool(npcStats.getGoldPool() + price);
|
|
|
|
|
|
|
|
MWBase::Environment::get().getWindowManager()->playSound ("Mysticism Hit");
|
|
|
|
|
[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)
|
|
|
|
|
|
|
|
Don't create a record and don't add the spell to the player's spellbook;
|
|
|
|
instead just send its record to the server and expect the server to add it
|
|
|
|
to the player's spellbook
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->createRecord(mSpell);
|
|
|
|
|
|
|
|
MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
|
|
|
|
MWMechanics::Spells& spells = stats.getSpells();
|
[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
|
|
|
spells.add(spell->mId);
|
|
|
|
*/
|
[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
|
|
|
|
|
|
|
mwmp::Main::get().getNetworking()->getWorldstate()->sendSpellRecord(&mSpell);
|
|
|
|
/*
|
|
|
|
End of tes3mp addition
|
|
|
|
*/
|
|
|
|
|
|
|
|
MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_SpellCreation);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpellCreationDialog::onAccept(MyGUI::EditBox *sender)
|
|
|
|
{
|
|
|
|
onBuyButtonClicked(sender);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpellCreationDialog::onOpen()
|
|
|
|
{
|
|
|
|
center();
|
|
|
|
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mNameEdit);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpellCreationDialog::onReferenceUnavailable ()
|
|
|
|
{
|
|
|
|
MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Dialogue);
|
|
|
|
MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_SpellCreation);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpellCreationDialog::notifyEffectsChanged ()
|
|
|
|
{
|
|
|
|
if (mEffects.empty())
|
|
|
|
{
|
|
|
|
mMagickaCost->setCaption("0");
|
|
|
|
mPriceLabel->setCaption("0");
|
|
|
|
mSuccessChance->setCaption("0");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
float y = 0;
|
|
|
|
|
|
|
|
const MWWorld::ESMStore &store =
|
|
|
|
MWBase::Environment::get().getWorld()->getStore();
|
|
|
|
|
|
|
|
for (std::vector<ESM::ENAMstruct>::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it)
|
|
|
|
{
|
|
|
|
const ESM::ENAMstruct& effect = *it;
|
|
|
|
|
|
|
|
y += std::max(1.f, MWMechanics::calcEffectCost(effect));
|
|
|
|
|
|
|
|
if (effect.mRange == ESM::RT_Target)
|
|
|
|
y *= 1.5;
|
|
|
|
}
|
|
|
|
|
|
|
|
ESM::EffectList effectList;
|
|
|
|
effectList.mList = mEffects;
|
|
|
|
mSpell.mEffects = effectList;
|
|
|
|
mSpell.mData.mCost = int(y);
|
|
|
|
mSpell.mData.mType = ESM::Spell::ST_Spell;
|
|
|
|
mSpell.mData.mFlags = 0;
|
|
|
|
|
|
|
|
mMagickaCost->setCaption(MyGUI::utility::toString(int(y)));
|
|
|
|
|
|
|
|
float fSpellMakingValueMult =
|
|
|
|
store.get<ESM::GameSetting>().find("fSpellMakingValueMult")->getFloat();
|
|
|
|
|
|
|
|
int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, static_cast<int>(y * fSpellMakingValueMult),true);
|
|
|
|
|
|
|
|
mPriceLabel->setCaption(MyGUI::utility::toString(int(price)));
|
|
|
|
|
|
|
|
float chance = MWMechanics::calcSpellBaseSuccessChance(&mSpell, MWMechanics::getPlayer(), NULL);
|
|
|
|
|
|
|
|
int intChance = std::min(100, int(chance));
|
|
|
|
mSuccessChance->setCaption(MyGUI::utility::toString(intChance));
|
|
|
|
}
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
EffectEditorBase::EffectEditorBase(Type type)
|
|
|
|
: mAvailableEffectsList(NULL)
|
|
|
|
, mUsedEffectsView(NULL)
|
|
|
|
, mAddEffectDialog()
|
|
|
|
, mSelectAttributeDialog(NULL)
|
|
|
|
, mSelectSkillDialog(NULL)
|
|
|
|
, mSelectedEffect(0)
|
|
|
|
, mSelectedKnownEffectId(0)
|
|
|
|
, mConstantEffect(false)
|
|
|
|
, mType(type)
|
|
|
|
{
|
|
|
|
mAddEffectDialog.eventEffectAdded += MyGUI::newDelegate(this, &EffectEditorBase::onEffectAdded);
|
|
|
|
mAddEffectDialog.eventEffectModified += MyGUI::newDelegate(this, &EffectEditorBase::onEffectModified);
|
|
|
|
mAddEffectDialog.eventEffectRemoved += MyGUI::newDelegate(this, &EffectEditorBase::onEffectRemoved);
|
|
|
|
|
|
|
|
mAddEffectDialog.setVisible (false);
|
|
|
|
}
|
|
|
|
|
|
|
|
EffectEditorBase::~EffectEditorBase()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void EffectEditorBase::startEditing ()
|
|
|
|
{
|
|
|
|
// get the list of magic effects that are known to the player
|
|
|
|
|
|
|
|
MWWorld::Ptr player = MWMechanics::getPlayer();
|
|
|
|
MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
|
|
|
|
MWMechanics::Spells& spells = stats.getSpells();
|
|
|
|
|
|
|
|
std::vector<short> knownEffects;
|
|
|
|
|
|
|
|
for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it)
|
|
|
|
{
|
|
|
|
const ESM::Spell* spell = it->first;
|
|
|
|
|
|
|
|
// only normal spells count
|
|
|
|
if (spell->mData.mType != ESM::Spell::ST_Spell)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const std::vector<ESM::ENAMstruct>& list = spell->mEffects.mList;
|
|
|
|
for (std::vector<ESM::ENAMstruct>::const_iterator it2 = list.begin(); it2 != list.end(); ++it2)
|
|
|
|
{
|
|
|
|
const ESM::MagicEffect * effect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(it2->mEffectID);
|
|
|
|
|
|
|
|
// skip effects that do not allow spellmaking/enchanting
|
|
|
|
int requiredFlags = (mType == Spellmaking) ? ESM::MagicEffect::AllowSpellmaking : ESM::MagicEffect::AllowEnchanting;
|
|
|
|
if (!(effect->mData.mFlags & requiredFlags))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (std::find(knownEffects.begin(), knownEffects.end(), it2->mEffectID) == knownEffects.end())
|
|
|
|
knownEffects.push_back(it2->mEffectID);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::sort(knownEffects.begin(), knownEffects.end(), sortMagicEffects);
|
|
|
|
|
|
|
|
mAvailableEffectsList->clear ();
|
|
|
|
|
|
|
|
int i=0;
|
|
|
|
for (std::vector<short>::const_iterator it = knownEffects.begin(); it != knownEffects.end(); ++it)
|
|
|
|
{
|
|
|
|
mAvailableEffectsList->addItem(MWBase::Environment::get().getWorld ()->getStore ().get<ESM::GameSetting>().find(
|
|
|
|
ESM::MagicEffect::effectIdToString (*it))->getString());
|
|
|
|
mButtonMapping[i] = *it;
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
mAvailableEffectsList->adjustSize ();
|
|
|
|
mAvailableEffectsList->scrollToTop();
|
|
|
|
|
|
|
|
for (std::vector<short>::const_iterator it = knownEffects.begin(); it != knownEffects.end(); ++it)
|
|
|
|
{
|
|
|
|
std::string name = MWBase::Environment::get().getWorld ()->getStore ().get<ESM::GameSetting>().find(
|
|
|
|
ESM::MagicEffect::effectIdToString (*it))->getString();
|
|
|
|
MyGUI::Widget* w = mAvailableEffectsList->getItemWidget(name);
|
|
|
|
|
|
|
|
ToolTips::createMagicEffectToolTip (w, *it);
|
|
|
|
}
|
|
|
|
|
|
|
|
mEffects.clear();
|
|
|
|
updateEffectsView ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void EffectEditorBase::setWidgets (Gui::MWList *availableEffectsList, MyGUI::ScrollView *usedEffectsView)
|
|
|
|
{
|
|
|
|
mAvailableEffectsList = availableEffectsList;
|
|
|
|
mUsedEffectsView = usedEffectsView;
|
|
|
|
|
|
|
|
mAvailableEffectsList->eventWidgetSelected += MyGUI::newDelegate(this, &EffectEditorBase::onAvailableEffectClicked);
|
|
|
|
}
|
|
|
|
|
|
|
|
void EffectEditorBase::onSelectAttribute ()
|
|
|
|
{
|
|
|
|
const ESM::MagicEffect* effect =
|
|
|
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(mSelectedKnownEffectId);
|
|
|
|
|
|
|
|
mAddEffectDialog.newEffect(effect);
|
|
|
|
mAddEffectDialog.setAttribute (mSelectAttributeDialog->getAttributeId());
|
|
|
|
MWBase::Environment::get().getWindowManager ()->removeDialog (mSelectAttributeDialog);
|
|
|
|
mSelectAttributeDialog = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void EffectEditorBase::onSelectSkill ()
|
|
|
|
{
|
|
|
|
const ESM::MagicEffect* effect =
|
|
|
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(mSelectedKnownEffectId);
|
|
|
|
|
|
|
|
mAddEffectDialog.newEffect(effect);
|
|
|
|
mAddEffectDialog.setSkill (mSelectSkillDialog->getSkillId());
|
|
|
|
MWBase::Environment::get().getWindowManager ()->removeDialog (mSelectSkillDialog);
|
|
|
|
mSelectSkillDialog = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void EffectEditorBase::onAttributeOrSkillCancel ()
|
|
|
|
{
|
|
|
|
if (mSelectSkillDialog)
|
|
|
|
MWBase::Environment::get().getWindowManager ()->removeDialog (mSelectSkillDialog);
|
|
|
|
if (mSelectAttributeDialog)
|
|
|
|
MWBase::Environment::get().getWindowManager ()->removeDialog (mSelectAttributeDialog);
|
|
|
|
|
|
|
|
mSelectSkillDialog = 0;
|
|
|
|
mSelectAttributeDialog = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void EffectEditorBase::onAvailableEffectClicked (MyGUI::Widget* sender)
|
|
|
|
{
|
|
|
|
if (mEffects.size() >= 8)
|
|
|
|
{
|
|
|
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage28}");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int buttonId = *sender->getUserData<int>();
|
|
|
|
mSelectedKnownEffectId = mButtonMapping[buttonId];
|
|
|
|
|
|
|
|
const ESM::MagicEffect* effect =
|
|
|
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(mSelectedKnownEffectId);
|
|
|
|
|
|
|
|
if (effect->mData.mFlags & ESM::MagicEffect::TargetSkill)
|
|
|
|
{
|
|
|
|
delete mSelectSkillDialog;
|
|
|
|
mSelectSkillDialog = new SelectSkillDialog();
|
|
|
|
mSelectSkillDialog->eventCancel += MyGUI::newDelegate(this, &SpellCreationDialog::onAttributeOrSkillCancel);
|
|
|
|
mSelectSkillDialog->eventItemSelected += MyGUI::newDelegate(this, &SpellCreationDialog::onSelectSkill);
|
|
|
|
mSelectSkillDialog->setVisible (true);
|
|
|
|
}
|
|
|
|
else if (effect->mData.mFlags & ESM::MagicEffect::TargetAttribute)
|
|
|
|
{
|
|
|
|
delete mSelectAttributeDialog;
|
|
|
|
mSelectAttributeDialog = new SelectAttributeDialog();
|
|
|
|
mSelectAttributeDialog->eventCancel += MyGUI::newDelegate(this, &SpellCreationDialog::onAttributeOrSkillCancel);
|
|
|
|
mSelectAttributeDialog->eventItemSelected += MyGUI::newDelegate(this, &SpellCreationDialog::onSelectAttribute);
|
|
|
|
mSelectAttributeDialog->setVisible (true);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for (std::vector<ESM::ENAMstruct>::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it)
|
|
|
|
{
|
|
|
|
if (it->mEffectID == mSelectedKnownEffectId)
|
|
|
|
{
|
|
|
|
MWBase::Environment::get().getWindowManager()->messageBox ("#{sOnetypeEffectMessage}");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mAddEffectDialog.newEffect(effect);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void EffectEditorBase::onEffectModified (ESM::ENAMstruct effect)
|
|
|
|
{
|
|
|
|
mEffects[mSelectedEffect] = effect;
|
|
|
|
|
|
|
|
updateEffectsView();
|
|
|
|
}
|
|
|
|
|
|
|
|
void EffectEditorBase::onEffectRemoved (ESM::ENAMstruct effect)
|
|
|
|
{
|
|
|
|
mEffects.erase(mEffects.begin() + mSelectedEffect);
|
|
|
|
updateEffectsView();
|
|
|
|
}
|
|
|
|
|
|
|
|
void EffectEditorBase::updateEffectsView ()
|
|
|
|
{
|
|
|
|
MyGUI::EnumeratorWidgetPtr oldWidgets = mUsedEffectsView->getEnumerator ();
|
|
|
|
MyGUI::Gui::getInstance ().destroyWidgets (oldWidgets);
|
|
|
|
|
|
|
|
MyGUI::IntSize size(0,0);
|
|
|
|
|
|
|
|
int i = 0;
|
|
|
|
for (std::vector<ESM::ENAMstruct>::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it)
|
|
|
|
{
|
|
|
|
Widgets::SpellEffectParams params;
|
|
|
|
params.mEffectID = it->mEffectID;
|
|
|
|
params.mSkill = it->mSkill;
|
|
|
|
params.mAttribute = it->mAttribute;
|
|
|
|
params.mDuration = it->mDuration;
|
|
|
|
params.mMagnMin = it->mMagnMin;
|
|
|
|
params.mMagnMax = it->mMagnMax;
|
|
|
|
params.mRange = it->mRange;
|
|
|
|
params.mArea = it->mArea;
|
|
|
|
params.mIsConstant = mConstantEffect;
|
|
|
|
|
|
|
|
MyGUI::Button* button = mUsedEffectsView->createWidget<MyGUI::Button>("", MyGUI::IntCoord(0, size.height, 0, 24), MyGUI::Align::Default);
|
|
|
|
button->setUserData(i);
|
|
|
|
button->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellCreationDialog::onEditEffect);
|
|
|
|
button->setNeedMouseFocus (true);
|
|
|
|
|
|
|
|
Widgets::MWSpellEffectPtr effect = button->createWidget<Widgets::MWSpellEffect>("MW_EffectImage", MyGUI::IntCoord(0,0,0,24), MyGUI::Align::Default);
|
|
|
|
|
|
|
|
effect->setNeedMouseFocus (false);
|
|
|
|
effect->setSpellEffect (params);
|
|
|
|
|
|
|
|
effect->setSize(effect->getRequestedWidth (), 24);
|
|
|
|
button->setSize(effect->getRequestedWidth (), 24);
|
|
|
|
|
|
|
|
size.width = std::max(size.width, effect->getRequestedWidth ());
|
|
|
|
size.height += 24;
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Canvas size must be expressed with HScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden
|
|
|
|
mUsedEffectsView->setVisibleHScroll(false);
|
|
|
|
mUsedEffectsView->setCanvasSize(size);
|
|
|
|
mUsedEffectsView->setVisibleHScroll(true);
|
|
|
|
|
|
|
|
notifyEffectsChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void EffectEditorBase::onEffectAdded (ESM::ENAMstruct effect)
|
|
|
|
{
|
|
|
|
mEffects.push_back(effect);
|
|
|
|
mSelectedEffect=mEffects.size()-1;
|
|
|
|
|
|
|
|
updateEffectsView();
|
|
|
|
}
|
|
|
|
|
|
|
|
void EffectEditorBase::onEditEffect (MyGUI::Widget *sender)
|
|
|
|
{
|
|
|
|
int id = *sender->getUserData<int>();
|
|
|
|
|
|
|
|
mSelectedEffect = id;
|
|
|
|
|
|
|
|
mAddEffectDialog.editEffect (mEffects[id]);
|
|
|
|
mAddEffectDialog.setVisible (true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void EffectEditorBase::setConstantEffect(bool constant)
|
|
|
|
{
|
|
|
|
mAddEffectDialog.setConstantEffect(constant);
|
|
|
|
mConstantEffect = constant;
|
|
|
|
}
|
|
|
|
}
|