forked from mirror/openmw-tes3mp
added the success formula, and spell deleting (shift+click)
This commit is contained in:
parent
30461438f6
commit
48ba293e88
3 changed files with 160 additions and 13 deletions
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
#include <boost/lexical_cast.hpp>
|
#include <boost/lexical_cast.hpp>
|
||||||
|
#include <boost/format.hpp>
|
||||||
|
|
||||||
#include "../mwworld/world.hpp"
|
#include "../mwworld/world.hpp"
|
||||||
#include "../mwworld/player.hpp"
|
#include "../mwworld/player.hpp"
|
||||||
|
@ -9,10 +10,12 @@
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwmechanics/spells.hpp"
|
#include "../mwmechanics/spells.hpp"
|
||||||
#include "../mwmechanics/creaturestats.hpp"
|
#include "../mwmechanics/creaturestats.hpp"
|
||||||
|
#include "../mwmechanics/spellsuccess.hpp"
|
||||||
#include "../mwsound/soundmanager.hpp"
|
#include "../mwsound/soundmanager.hpp"
|
||||||
|
|
||||||
#include "window_manager.hpp"
|
#include "window_manager.hpp"
|
||||||
#include "inventorywindow.hpp"
|
#include "inventorywindow.hpp"
|
||||||
|
#include "confirmationdialog.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
@ -175,6 +178,7 @@ namespace MWGui
|
||||||
t->setTextAlign(MyGUI::Align::Left);
|
t->setTextAlign(MyGUI::Align::Left);
|
||||||
t->setUserString("ToolTipType", "Spell");
|
t->setUserString("ToolTipType", "Spell");
|
||||||
t->setUserString("Spell", *it);
|
t->setUserString("Spell", *it);
|
||||||
|
t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel);
|
||||||
t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected);
|
t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected);
|
||||||
|
|
||||||
if (*it == selectedSpell)
|
if (*it == selectedSpell)
|
||||||
|
@ -194,18 +198,19 @@ namespace MWGui
|
||||||
t->setTextAlign(MyGUI::Align::Left);
|
t->setTextAlign(MyGUI::Align::Left);
|
||||||
t->setUserString("ToolTipType", "Spell");
|
t->setUserString("ToolTipType", "Spell");
|
||||||
t->setUserString("Spell", *it);
|
t->setUserString("Spell", *it);
|
||||||
|
t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel);
|
||||||
t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected);
|
t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected);
|
||||||
|
t->setStateSelected(*it == selectedSpell);
|
||||||
if (*it == selectedSpell)
|
|
||||||
t->setStateSelected(true);
|
|
||||||
|
|
||||||
// cost / success chance
|
// cost / success chance
|
||||||
MyGUI::TextBox* costChance = mSpellView->createWidget<MyGUI::TextBox>("SpellText",
|
MyGUI::Button* costChance = mSpellView->createWidget<MyGUI::Button>("SpellText",
|
||||||
MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
|
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 cost = boost::lexical_cast<std::string>(spell->data.cost);
|
||||||
costChance->setCaption(cost + "/" + "100"); /// \todo
|
std::string chance = boost::lexical_cast<std::string>(int(MWMechanics::getSpellSuccessChance(*it, player)));
|
||||||
|
costChance->setCaption(cost + "/" + chance);
|
||||||
costChance->setTextAlign(MyGUI::Align::Right);
|
costChance->setTextAlign(MyGUI::Align::Right);
|
||||||
costChance->setNeedMouseFocus(false);
|
costChance->setNeedMouseFocus(false);
|
||||||
|
costChance->setStateSelected(*it == selectedSpell);
|
||||||
|
|
||||||
|
|
||||||
mHeight += spellHeight;
|
mHeight += spellHeight;
|
||||||
|
@ -219,6 +224,8 @@ namespace MWGui
|
||||||
{
|
{
|
||||||
MWWorld::Ptr item = *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)
|
// check if the item is currently equipped (will display in a different color)
|
||||||
bool equipped = false;
|
bool equipped = false;
|
||||||
for (int i=0; i < MWWorld::InventoryStore::Slots; ++i)
|
for (int i=0; i < MWWorld::InventoryStore::Slots; ++i)
|
||||||
|
@ -238,18 +245,26 @@ namespace MWGui
|
||||||
t->setUserString("ToolTipType", "ItemPtr");
|
t->setUserString("ToolTipType", "ItemPtr");
|
||||||
t->setUserString("Equipped", equipped ? "true" : "false");
|
t->setUserString("Equipped", equipped ? "true" : "false");
|
||||||
t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onEnchantedItemSelected);
|
t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onEnchantedItemSelected);
|
||||||
|
t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel);
|
||||||
t->setStateSelected(item == selectedItem);
|
t->setStateSelected(item == selectedItem);
|
||||||
|
|
||||||
// cost / charge
|
// cost / charge
|
||||||
MyGUI::TextBox* costCharge = mSpellView->createWidget<MyGUI::TextBox>(equipped ? "SpellText" : "SpellTextUnequipped",
|
MyGUI::Button* costCharge = mSpellView->createWidget<MyGUI::Button>(equipped ? "SpellText" : "SpellTextUnequipped",
|
||||||
MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
|
MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
|
||||||
|
|
||||||
const ESM::Enchantment* enchant = MWBase::Environment::get().getWorld()->getStore().enchants.find(MWWorld::Class::get(item).getEnchantment(item));
|
|
||||||
std::string cost = boost::lexical_cast<std::string>(enchant->data.cost);
|
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
|
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->setCaption(cost + "/" + charge);
|
||||||
costCharge->setTextAlign(MyGUI::Align::Right);
|
costCharge->setTextAlign(MyGUI::Align::Right);
|
||||||
costCharge->setNeedMouseFocus(false);
|
costCharge->setNeedMouseFocus(false);
|
||||||
|
costCharge->setStateSelected(item == selectedItem);
|
||||||
|
|
||||||
mHeight += spellHeight;
|
mHeight += spellHeight;
|
||||||
}
|
}
|
||||||
|
@ -312,8 +327,9 @@ namespace MWGui
|
||||||
}
|
}
|
||||||
assert(it != store.end());
|
assert(it != store.end());
|
||||||
|
|
||||||
// equip, if it is not already equipped
|
// equip, if it can be equipped and is not already equipped
|
||||||
if (_sender->getUserString("Equipped") == "false")
|
if (_sender->getUserString("Equipped") == "false"
|
||||||
|
&& !MWWorld::Class::get(item).getEquipmentSlots(item).first.empty())
|
||||||
{
|
{
|
||||||
// sound
|
// sound
|
||||||
MWBase::Environment::get().getSoundManager()->playSound(MWWorld::Class::get(item).getUpSoundId(item), 1.0, 1.0);
|
MWBase::Environment::get().getSoundManager()->playSound(MWWorld::Class::get(item).getUpSoundId(item), 1.0, 1.0);
|
||||||
|
@ -324,7 +340,6 @@ namespace MWGui
|
||||||
// slots that this item can be equipped in
|
// slots that this item can be equipped in
|
||||||
std::pair<std::vector<int>, bool> slots = MWWorld::Class::get(item).getEquipmentSlots(item);
|
std::pair<std::vector<int>, bool> slots = MWWorld::Class::get(item).getEquipmentSlots(item);
|
||||||
|
|
||||||
|
|
||||||
// equip the item in the first free slot
|
// equip the item in the first free slot
|
||||||
for (std::vector<int>::const_iterator slot=slots.first.begin();
|
for (std::vector<int>::const_iterator slot=slots.first.begin();
|
||||||
slot!=slots.first.end(); ++slot)
|
slot!=slots.first.end(); ++slot)
|
||||||
|
@ -362,8 +377,33 @@ namespace MWGui
|
||||||
MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player);
|
MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player);
|
||||||
MWMechanics::Spells& spells = stats.mSpells;
|
MWMechanics::Spells& spells = stats.mSpells;
|
||||||
|
|
||||||
spells.setSelectedSpell(_sender->getUserString("Spell"));
|
if (MyGUI::InputManager::getInstance().isShiftPressed())
|
||||||
store.setSelectedEnchantItem(store.end());
|
{
|
||||||
|
// delete spell, if allowed
|
||||||
|
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().spells.find(_sender->getUserString("Spell"));
|
||||||
|
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 = _sender->getUserString("Spell");
|
||||||
|
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(_sender->getUserString("Spell"));
|
||||||
|
store.setSelectedEnchantItem(store.end());
|
||||||
|
}
|
||||||
|
|
||||||
updateSpells();
|
updateSpells();
|
||||||
}
|
}
|
||||||
|
@ -375,4 +415,23 @@ namespace MWGui
|
||||||
height += numSpells * 18;
|
height += numSpells * 18;
|
||||||
return height;
|
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;
|
||||||
|
|
||||||
|
spells.remove(mSpellToDelete);
|
||||||
|
|
||||||
|
updateSpells();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,14 +19,19 @@ namespace MWGui
|
||||||
int mHeight;
|
int mHeight;
|
||||||
int mWidth;
|
int mWidth;
|
||||||
|
|
||||||
|
std::string mSpellToDelete;
|
||||||
|
|
||||||
void addGroup(const std::string& label, const std::string& label2);
|
void addGroup(const std::string& label, const std::string& label2);
|
||||||
|
|
||||||
int estimateHeight(int numSpells) const;
|
int estimateHeight(int numSpells) const;
|
||||||
|
|
||||||
virtual void onPinToggled();
|
|
||||||
void onWindowResize(MyGUI::Window* _sender);
|
void onWindowResize(MyGUI::Window* _sender);
|
||||||
void onEnchantedItemSelected(MyGUI::Widget* _sender);
|
void onEnchantedItemSelected(MyGUI::Widget* _sender);
|
||||||
void onSpellSelected(MyGUI::Widget* _sender);
|
void onSpellSelected(MyGUI::Widget* _sender);
|
||||||
|
void onMouseWheel(MyGUI::Widget* _sender, int _rel);
|
||||||
|
void onDeleteSpellAccept();
|
||||||
|
|
||||||
|
virtual void onPinToggled();
|
||||||
virtual void open();
|
virtual void open();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
83
apps/openmw/mwmechanics/spellsuccess.hpp
Normal file
83
apps/openmw/mwmechanics/spellsuccess.hpp
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
#ifndef MWMECHANICS_SPELLSUCCESS_H
|
||||||
|
#define MWMECHANICS_SPELLSUCCESS_H
|
||||||
|
|
||||||
|
#include "../mwworld/ptr.hpp"
|
||||||
|
#include "../mwworld/world.hpp"
|
||||||
|
#include "../mwbase/environment.hpp"
|
||||||
|
|
||||||
|
#include "npcstats.hpp"
|
||||||
|
|
||||||
|
namespace MWMechanics
|
||||||
|
{
|
||||||
|
// 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)
|
||||||
|
*/
|
||||||
|
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)
|
||||||
|
return 100.0;
|
||||||
|
|
||||||
|
NpcStats& stats = MWWorld::Class::get(actor).getNpcStats(actor);
|
||||||
|
CreatureStats& creatureStats = MWWorld::Class::get(actor).getCreatureStats(actor);
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
// 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 skill = -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;
|
||||||
|
assert(schoolSkillMap.find(school) != schoolSkillMap.end());
|
||||||
|
int _skillLevel = stats.mSkill[schoolSkillMap[school]].getModified();
|
||||||
|
|
||||||
|
if (skill == -1)
|
||||||
|
{
|
||||||
|
skill = schoolSkillMap[school];
|
||||||
|
skillLevel = _skillLevel;
|
||||||
|
}
|
||||||
|
else if (_skillLevel < skillLevel)
|
||||||
|
{
|
||||||
|
skill = schoolSkillMap[school];
|
||||||
|
skillLevel = _skillLevel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
Loading…
Reference in a new issue