Merge remote branch 'scrawl/spellwindow'
commit
2bbbff2b75
@ -0,0 +1,449 @@
|
||||
#include "spellwindow.hpp"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#include "../mwworld/world.hpp"
|
||||
#include "../mwworld/player.hpp"
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwmechanics/spells.hpp"
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/spellsuccess.hpp"
|
||||
#include "../mwsound/soundmanager.hpp"
|
||||
|
||||
#include "window_manager.hpp"
|
||||
#include "inventorywindow.hpp"
|
||||
#include "confirmationdialog.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
bool sortSpells(const std::string& left, const std::string& right)
|
||||
{
|
||||
const ESM::Spell* a = MWBase::Environment::get().getWorld()->getStore().spells.find(left);
|
||||
const ESM::Spell* b = MWBase::Environment::get().getWorld()->getStore().spells.find(right);
|
||||
|
||||
int cmp = a->name.compare(b->name);
|
||||
return cmp < 0;
|
||||
}
|
||||
|
||||
bool sortItems(const MWWorld::Ptr& left, const MWWorld::Ptr& right)
|
||||
{
|
||||
int cmp = MWWorld::Class::get(left).getName(left).compare(
|
||||
MWWorld::Class::get(right).getName(right));
|
||||
return cmp < 0;
|
||||
}
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
SpellWindow::SpellWindow(WindowManager& parWindowManager)
|
||||
: WindowPinnableBase("openmw_spell_window_layout.xml", parWindowManager)
|
||||
, mHeight(0)
|
||||
, mWidth(0)
|
||||
{
|
||||
getWidget(mSpellView, "SpellView");
|
||||
getWidget(mEffectBox, "EffectsBox");
|
||||
|
||||
setCoord(498, 300, 302, 300);
|
||||
|
||||
updateSpells();
|
||||
|
||||
mMainWidget->castType<MyGUI::Window>()->eventWindowChangeCoord += MyGUI::newDelegate(this, &SpellWindow::onWindowResize);
|
||||
}
|
||||
|
||||
void SpellWindow::onPinToggled()
|
||||
{
|
||||
mWindowManager.setSpellVisibility(!mPinned);
|
||||
}
|
||||
|
||||
void SpellWindow::open()
|
||||
{
|
||||
updateSpells();
|
||||
}
|
||||
|
||||
void SpellWindow::updateSpells()
|
||||
{
|
||||
const int spellHeight = 18;
|
||||
|
||||
mHeight = 0;
|
||||
while (mSpellView->getChildCount())
|
||||
MyGUI::Gui::getInstance().destroyWidget(mSpellView->getChildAt(0));
|
||||
|
||||
// retrieve all player spells, divide them into Powers and Spells and sort them
|
||||
std::vector<std::string> spellList;
|
||||
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
|
||||
MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player);
|
||||
MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
|
||||
MWMechanics::Spells& spells = stats.mSpells;
|
||||
|
||||
// the following code switches between selected enchanted item and selected spell (only one of these
|
||||
// can be active at a time)
|
||||
std::string selectedSpell = spells.getSelectedSpell();
|
||||
MWWorld::Ptr selectedItem;
|
||||
if (store.getSelectedEnchantItem() != store.end())
|
||||
{
|
||||
selectedSpell = "";
|
||||
selectedItem = *store.getSelectedEnchantItem();
|
||||
|
||||
bool allowSelectedItem = true;
|
||||
|
||||
// if the selected item can be equipped, make sure that it actually is equipped
|
||||
std::pair<std::vector<int>, bool> slots;
|
||||
slots = MWWorld::Class::get(selectedItem).getEquipmentSlots(selectedItem);
|
||||
if (!slots.first.empty())
|
||||
{
|
||||
bool equipped = false;
|
||||
for (int i=0; i < MWWorld::InventoryStore::Slots; ++i)
|
||||
{
|
||||
if (store.getSlot(i) != store.end() && *store.getSlot(i) == selectedItem)
|
||||
{
|
||||
equipped = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!equipped)
|
||||
allowSelectedItem = false;
|
||||
}
|
||||
|
||||
if (!allowSelectedItem)
|
||||
{
|
||||
store.setSelectedEnchantItem(store.end());
|
||||
spells.setSelectedSpell("");
|
||||
mWindowManager.unsetSelectedSpell();
|
||||
selectedItem = MWWorld::Ptr();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it)
|
||||
{
|
||||
spellList.push_back(*it);
|
||||
}
|
||||
|
||||
std::vector<std::string> powers;
|
||||
std::vector<std::string>::iterator it = spellList.begin();
|
||||
while (it != spellList.end())
|
||||
{
|
||||
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().spells.find(*it);
|
||||
if (spell->data.type == ESM::Spell::ST_Power)
|
||||
{
|
||||
powers.push_back(*it);
|
||||
it = spellList.erase(it);
|
||||
}
|
||||
else if (spell->data.type == ESM::Spell::ST_Ability)
|
||||
{
|
||||
// abilities are always active and don't show in the spell window.
|
||||
it = spellList.erase(it);
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
std::sort(powers.begin(), powers.end(), sortSpells);
|
||||
std::sort(spellList.begin(), spellList.end(), sortSpells);
|
||||
|
||||
// retrieve player's enchanted items
|
||||
std::vector<MWWorld::Ptr> items;
|
||||
for (MWWorld::ContainerStoreIterator it(store.begin()); it != store.end(); ++it)
|
||||
{
|
||||
std::string enchantId = MWWorld::Class::get(*it).getEnchantment(*it);
|
||||
if (enchantId != "")
|
||||
{
|
||||
// only add items with "Cast once" or "Cast on use"
|
||||
const ESM::Enchantment* enchant = MWBase::Environment::get().getWorld()->getStore().enchants.find(enchantId);
|
||||
int type = enchant->data.type;
|
||||
if (type != ESM::Enchantment::CastOnce
|
||||
&& type != ESM::Enchantment::WhenUsed)
|
||||
continue;
|
||||
|
||||
items.push_back(*it);
|
||||
}
|
||||
}
|
||||
std::sort(items.begin(), items.end(), sortItems);
|
||||
|
||||
|
||||
int height = estimateHeight(items.size() + powers.size() + spellList.size());
|
||||
bool scrollVisible = height > mSpellView->getHeight();
|
||||
mWidth = mSpellView->getWidth() - (scrollVisible ? 18 : 0);
|
||||
|
||||
// powers
|
||||
addGroup("#{sPowers}", "");
|
||||
|
||||
for (std::vector<std::string>::const_iterator it = powers.begin(); it != powers.end(); ++it)
|
||||
{
|
||||
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().spells.find(*it);
|
||||
MyGUI::Button* t = mSpellView->createWidget<MyGUI::Button>("SpellText",
|
||||
MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
|
||||
t->setCaption(spell->name);
|
||||
t->setTextAlign(MyGUI::Align::Left);
|
||||
t->setUserString("ToolTipType", "Spell");
|
||||
t->setUserString("Spell", *it);
|
||||
t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel);
|
||||
t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected);
|
||||
|
||||
if (*it == selectedSpell)
|
||||
t->setStateSelected(true);
|
||||
|
||||
mHeight += spellHeight;
|
||||
}
|
||||
|
||||
// other spells
|
||||
addGroup("#{sSpells}", "#{sCostChance}");
|
||||
for (std::vector<std::string>::const_iterator it = spellList.begin(); it != spellList.end(); ++it)
|
||||
{
|
||||
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().spells.find(*it);
|
||||
MyGUI::Button* t = mSpellView->createWidget<MyGUI::Button>("SpellText",
|
||||
MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
|
||||
t->setCaption(spell->name);
|
||||
t->setTextAlign(MyGUI::Align::Left);
|
||||
t->setUserString("ToolTipType", "Spell");
|
||||
t->setUserString("Spell", *it);
|
||||
t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel);
|
||||
t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected);
|
||||
t->setStateSelected(*it == selectedSpell);
|
||||
|
||||
// cost / success chance
|
||||
MyGUI::Button* costChance = mSpellView->createWidget<MyGUI::Button>("SpellText",
|
||||
MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
|
||||
std::string cost = boost::lexical_cast<std::string>(spell->data.cost);
|
||||
std::string chance = boost::lexical_cast<std::string>(int(MWMechanics::getSpellSuccessChance(*it, player)));
|
||||
costChance->setCaption(cost + "/" + chance);
|
||||
costChance->setTextAlign(MyGUI::Align::Right);
|
||||
costChance->setNeedMouseFocus(false);
|
||||
costChance->setStateSelected(*it == selectedSpell);
|
||||
|
||||
|
||||
mHeight += spellHeight;
|
||||
}
|
||||
|
||||
|
||||
// enchanted items
|
||||
addGroup("#{sMagicItem}", "#{sCostCharge}");
|
||||
|
||||
for (std::vector<MWWorld::Ptr>::const_iterator it = items.begin(); it != items.end(); ++it)
|
||||
{
|
||||
MWWorld::Ptr item = *it;
|
||||
|
||||
const ESM::Enchantment* enchant = MWBase::Environment::get().getWorld()->getStore().enchants.find(MWWorld::Class::get(item).getEnchantment(item));
|
||||
|
||||
// check if the item is currently equipped (will display in a different color)
|
||||
bool equipped = false;
|
||||
for (int i=0; i < MWWorld::InventoryStore::Slots; ++i)
|
||||
{
|
||||
if (store.getSlot(i) != store.end() && *store.getSlot(i) == item)
|
||||
{
|
||||
equipped = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
MyGUI::Button* t = mSpellView->createWidget<MyGUI::Button>(equipped ? "SpellText" : "SpellTextUnequipped",
|
||||
MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
|
||||
t->setCaption(MWWorld::Class::get(item).getName(item));
|
||||
t->setTextAlign(MyGUI::Align::Left);
|
||||
t->setUserData(item);
|
||||
t->setUserString("ToolTipType", "ItemPtr");
|
||||
t->setUserString("Equipped", equipped ? "true" : "false");
|
||||
t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onEnchantedItemSelected);
|
||||
t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel);
|
||||
t->setStateSelected(item == selectedItem);
|
||||
|
||||
// cost / charge
|
||||
MyGUI::Button* costCharge = mSpellView->createWidget<MyGUI::Button>(equipped ? "SpellText" : "SpellTextUnequipped",
|
||||
MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
|
||||
|
||||
std::string cost = boost::lexical_cast<std::string>(enchant->data.cost);
|
||||
std::string charge = boost::lexical_cast<std::string>(enchant->data.charge); /// \todo track current charge
|
||||
if (enchant->data.type == ESM::Enchantment::CastOnce)
|
||||
{
|
||||
// this is Morrowind behaviour
|
||||
cost = "100";
|
||||
charge = "100";
|
||||
}
|
||||
|
||||
costCharge->setCaption(cost + "/" + charge);
|
||||
costCharge->setTextAlign(MyGUI::Align::Right);
|
||||
costCharge->setNeedMouseFocus(false);
|
||||
costCharge->setStateSelected(item == selectedItem);
|
||||
|
||||
mHeight += spellHeight;
|
||||
}
|
||||
|
||||
mSpellView->setCanvasSize(mSpellView->getWidth(), std::max(mSpellView->getHeight(), mHeight));
|
||||
}
|
||||
|
||||
void SpellWindow::addGroup(const std::string &label, const std::string& label2)
|
||||
{
|
||||
if (mSpellView->getChildCount() > 0)
|
||||
{
|
||||
MyGUI::ImageBox* separator = mSpellView->createWidget<MyGUI::ImageBox>("MW_HLine",
|
||||
MyGUI::IntCoord(4, mHeight, mWidth-8, 18),
|
||||
MyGUI::Align::Left | MyGUI::Align::Top);
|
||||
separator->setNeedMouseFocus(false);
|
||||
mHeight += 18;
|
||||
}
|
||||
|
||||
MyGUI::TextBox* groupWidget = mSpellView->createWidget<MyGUI::TextBox>("SandBrightText",
|
||||
MyGUI::IntCoord(0, mHeight, mWidth, 24),
|
||||
MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch);
|
||||
groupWidget->setCaptionWithReplacing(label);
|
||||
groupWidget->setTextAlign(MyGUI::Align::Left);
|
||||
groupWidget->setNeedMouseFocus(false);
|
||||
|
||||
if (label2 != "")
|
||||
{
|
||||
MyGUI::TextBox* groupWidget2 = mSpellView->createWidget<MyGUI::TextBox>("SandBrightText",
|
||||
MyGUI::IntCoord(0, mHeight, mWidth-4, 24),
|
||||
MyGUI::Align::Left | MyGUI::Align::Top);
|
||||
groupWidget2->setCaptionWithReplacing(label2);
|
||||
groupWidget2->setTextAlign(MyGUI::Align::Right);
|
||||
groupWidget2->setNeedMouseFocus(false);
|
||||
}
|
||||
|
||||
mHeight += 24;
|
||||
}
|
||||
|
||||
void SpellWindow::onWindowResize(MyGUI::Window* _sender)
|
||||
{
|
||||
updateSpells();
|
||||
}
|
||||
|
||||
void SpellWindow::onEnchantedItemSelected(MyGUI::Widget* _sender)
|
||||
{
|
||||
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
|
||||
MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
|
||||
MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player);
|
||||
MWMechanics::Spells& spells = stats.mSpells;
|
||||
MWWorld::Ptr item = *_sender->getUserData<MWWorld::Ptr>();
|
||||
|
||||
// retrieve ContainerStoreIterator to the item
|
||||
MWWorld::ContainerStoreIterator it = store.begin();
|
||||
for (; it != store.end(); ++it)
|
||||
{
|
||||
if (*it == item)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(it != store.end());
|
||||
|
||||
// equip, if it can be equipped and is not already equipped
|
||||
if (_sender->getUserString("Equipped") == "false"
|
||||
&& !MWWorld::Class::get(item).getEquipmentSlots(item).first.empty())
|
||||
{
|
||||
// sound
|
||||
MWBase::Environment::get().getSoundManager()->playSound(MWWorld::Class::get(item).getUpSoundId(item), 1.0, 1.0);
|
||||
|
||||
// Note: can't use Class::use here because enchanted scrolls for example would then open the scroll window instead of equipping
|
||||
|
||||
/// \todo the following code is pretty much copy&paste from ActionEquip, put it in a function?
|
||||
// slots that this item can be equipped in
|
||||
std::pair<std::vector<int>, bool> slots = MWWorld::Class::get(item).getEquipmentSlots(item);
|
||||
|
||||
// equip the item in the first free slot
|
||||
for (std::vector<int>::const_iterator slot=slots.first.begin();
|
||||
slot!=slots.first.end(); ++slot)
|
||||
{
|
||||
// if all slots are occupied, replace the last slot
|
||||
if (slot == --slots.first.end())
|
||||
{
|
||||
store.equip(*slot, it);
|
||||
break;
|
||||
}
|
||||
|
||||
if (store.getSlot(*slot) == store.end())
|
||||
{
|
||||
// slot is not occupied
|
||||
store.equip(*slot, it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/// \todo scripts?
|
||||
|
||||
// since we changed equipping status, update the inventory window
|
||||
mWindowManager.getInventoryWindow()->drawItems();
|
||||
}
|
||||
|
||||
store.setSelectedEnchantItem(it);
|
||||
spells.setSelectedSpell("");
|
||||
mWindowManager.setSelectedEnchantItem(item, 100); /// \todo track charge %
|
||||
|
||||
updateSpells();
|
||||
}
|
||||
|
||||
void SpellWindow::onSpellSelected(MyGUI::Widget* _sender)
|
||||
{
|
||||
std::string spellId = _sender->getUserString("Spell");
|
||||
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
|
||||
MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
|
||||
MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player);
|
||||
MWMechanics::Spells& spells = stats.mSpells;
|
||||
|
||||
if (MyGUI::InputManager::getInstance().isShiftPressed())
|
||||
{
|
||||
// delete spell, if allowed
|
||||
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().spells.find(spellId);
|
||||
if (spell->data.flags & ESM::Spell::F_Always
|
||||
|| spell->data.type == ESM::Spell::ST_Power)
|
||||
{
|
||||
mWindowManager.messageBox("#{sDeleteSpellError}", std::vector<std::string>());
|
||||
}
|
||||
else
|
||||
{
|
||||
// ask for confirmation
|
||||
mSpellToDelete = spellId;
|
||||
ConfirmationDialog* dialog = mWindowManager.getConfirmationDialog();
|
||||
std::string question = mWindowManager.getGameSettingString("sQuestionDeleteSpell", "Delete %s?");
|
||||
question = boost::str(boost::format(question) % spell->name);
|
||||
dialog->open(question);
|
||||
dialog->eventOkClicked.clear();
|
||||
dialog->eventOkClicked += MyGUI::newDelegate(this, &SpellWindow::onDeleteSpellAccept);
|
||||
dialog->eventCancelClicked.clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
spells.setSelectedSpell(spellId);
|
||||
store.setSelectedEnchantItem(store.end());
|
||||
mWindowManager.setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player)));
|
||||
}
|
||||
|
||||
updateSpells();
|
||||
}
|
||||
|
||||
int SpellWindow::estimateHeight(int numSpells) const
|
||||
{
|
||||
int height = 0;
|
||||
height += 24 * 3 + 18 * 2; // group headings
|
||||
height += numSpells * 18;
|
||||
return height;
|
||||
}
|
||||
|
||||
void SpellWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel)
|
||||
{
|
||||
if (mSpellView->getViewOffset().top + _rel*0.3 > 0)
|
||||
mSpellView->setViewOffset(MyGUI::IntPoint(0, 0));
|
||||
else
|
||||
mSpellView->setViewOffset(MyGUI::IntPoint(0, mSpellView->getViewOffset().top + _rel*0.3));
|
||||
}
|
||||
|
||||
void SpellWindow::onDeleteSpellAccept()
|
||||
{
|
||||
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
|
||||
MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
|
||||
MWMechanics::Spells& spells = stats.mSpells;
|
||||
|
||||
if (spells.getSelectedSpell() == mSpellToDelete)
|
||||
{
|
||||
spells.setSelectedSpell("");
|
||||
mWindowManager.unsetSelectedSpell();
|
||||
}
|
||||
|
||||
spells.remove(mSpellToDelete);
|
||||
|
||||
updateSpells();
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
#ifndef MWGUI_SPELLWINDOW_H
|
||||
#define MWGUI_SPELLWINDOW_H
|
||||
|
||||
#include "window_pinnable_base.hpp"
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class SpellWindow : public WindowPinnableBase
|
||||
{
|
||||
public:
|
||||
SpellWindow(WindowManager& parWindowManager);
|
||||
|
||||
void updateSpells();
|
||||
|
||||
protected:
|
||||
MyGUI::ScrollView* mSpellView;
|
||||
MyGUI::Widget* mEffectBox;
|
||||
|
||||
int mHeight;
|
||||
int mWidth;
|
||||
|
||||
std::string mSpellToDelete;
|
||||
|
||||
void addGroup(const std::string& label, const std::string& label2);
|
||||
|
||||
int estimateHeight(int numSpells) const;
|
||||
|
||||
void onWindowResize(MyGUI::Window* _sender);
|
||||
void onEnchantedItemSelected(MyGUI::Widget* _sender);
|
||||
void onSpellSelected(MyGUI::Widget* _sender);
|
||||
void onMouseWheel(MyGUI::Widget* _sender, int _rel);
|
||||
void onDeleteSpellAccept();
|
||||
|
||||
virtual void onPinToggled();
|
||||
virtual void open();
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,99 @@
|
||||
#ifndef MWMECHANICS_SPELLSUCCESS_H
|
||||
#define MWMECHANICS_SPELLSUCCESS_H
|
||||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
#include "../mwworld/world.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
|
||||
#include "npcstats.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
inline int spellSchoolToSkill(int school)
|
||||
{
|
||||
std::map<int, int> schoolSkillMap; // maps spell school to skill id
|
||||
schoolSkillMap[0] = 11; // alteration
|
||||
schoolSkillMap[1] = 13; // conjuration
|
||||
schoolSkillMap[3] = 12; // illusion
|
||||
schoolSkillMap[2] = 10; // destruction
|
||||
schoolSkillMap[4] = 14; // mysticism
|
||||
schoolSkillMap[5] = 15; // restoration
|
||||
assert(schoolSkillMap.find(school) != schoolSkillMap.end());
|
||||
return schoolSkillMap[school];
|
||||
}
|
||||
|
||||
inline int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor)
|
||||
{
|
||||
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().spells.find(spellId);
|
||||
NpcStats& stats = MWWorld::Class::get(actor).getNpcStats(actor);
|
||||
|
||||
// determine the spell's school
|
||||
// this is always the school where the player's respective skill is the least advanced
|
||||
// out of all the magic effects' schools
|
||||
const std::vector<ESM::ENAMstruct>& effects = spell->effects.list;
|
||||
int school = -1;
|
||||
int skillLevel = -1;
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator it = effects.begin();
|
||||
it != effects.end(); ++it)
|
||||
{
|
||||
const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().magicEffects.find(it->effectID);
|
||||
int _school = effect->data.school;
|
||||
int _skillLevel = stats.mSkill[spellSchoolToSkill(_school)].getModified();
|
||||
|
||||
if (school == -1)
|
||||
{
|
||||
school = _school;
|
||||
skillLevel = _skillLevel;
|
||||
}
|
||||
else if (_skillLevel < skillLevel)
|
||||
{
|
||||
school = _school;
|
||||
skillLevel = _skillLevel;
|
||||
}
|
||||
}
|
||||
|
||||
return school;
|
||||
}
|
||||
|
||||
|
||||
// UESP wiki / Morrowind/Spells:
|
||||
// Chance of success is (Spell's skill * 2 + Willpower / 5 + Luck / 10 - Spell cost - Sound magnitude) * (Current fatigue + Maximum Fatigue * 1.5) / Maximum fatigue * 2
|
||||
/**
|
||||
* @param spellId ID of spell
|
||||
* @param actor calculate spell success chance for this actor (depends on actor's skills)
|
||||
* @attention actor has to be an NPC and not a creature!
|
||||
* @return success chance from 0 to 100 (in percent)
|
||||
*/
|
||||
inline float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor)
|
||||
{
|
||||
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().spells.find(spellId);
|
||||
|
||||
if (spell->data.flags & ESM::Spell::F_Always // spells with this flag always succeed (usually birthsign spells)
|
||||
|| spell->data.type == ESM::Spell::ST_Power) // powers always succeed, but can be cast only once per day
|
||||
return 100.0;
|
||||
|
||||
NpcStats& stats = MWWorld::Class::get(actor).getNpcStats(actor);
|
||||
CreatureStats& creatureStats = MWWorld::Class::get(actor).getCreatureStats(actor);
|
||||
|
||||
int skillLevel = stats.mSkill[getSpellSchool(spellId, actor)].getModified();
|
||||
|
||||
// Sound magic effect (reduces spell casting chance)
|
||||
int soundMagnitude = creatureStats.mMagicEffects.get (MWMechanics::EffectKey (48)).mMagnitude;
|
||||
|
||||
int willpower = creatureStats.mAttributes[ESM::Attribute::Willpower].getModified();
|
||||
int luck = creatureStats.mAttributes[ESM::Attribute::Luck].getModified();
|
||||
int currentFatigue = creatureStats.mDynamic[2].getCurrent();
|
||||
int maxFatigue = creatureStats.mDynamic[2].getModified();
|
||||
int spellCost = spell->data.cost;
|
||||
|
||||
// There we go, all needed variables are there, lets go
|
||||
float chance = (float(skillLevel * 2) + float(willpower)/5.0 + float(luck)/ 10.0 - spellCost - soundMagnitude) * (float(currentFatigue + maxFatigue * 1.5)) / float(maxFatigue * 2.0);
|
||||
|
||||
chance = std::max(0.0f, std::min(100.0f, chance)); // clamp to 0 .. 100
|
||||
|
||||
return chance;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<MyGUI type="Layout">
|
||||
<Widget type="Window" skin="MW_Window_Pinnable" layer="Windows" position="0 0 300 600" name="_Main">
|
||||
|
||||
<!-- Effect box-->
|
||||
<Widget type="Widget" skin="MW_Box" position="8 8 268 24" align="Left Top HStretch">
|
||||
<Widget type="Widget" skin="" position="4 4 260 16" align="Left Top Stretch" name="EffectsBox">
|
||||
</Widget>
|
||||
</Widget>
|
||||
|
||||
<!-- Spell list -->
|
||||
<Widget type="Widget" skin="MW_Box" position="8 38 268 518" align="Left Top Stretch">
|
||||
<Widget type="ScrollView" skin="MW_ScrollView" position="4 4 260 510" align="Left Top Stretch" name="SpellView">
|
||||
<Property key="CanvasAlign" value="Left Top"/>
|
||||
</Widget>
|
||||
</Widget>
|
||||
|
||||
</Widget>
|
||||
</MyGUI>
|
Loading…
Reference in New Issue