1 #include "review.hpp"
2 
3 #include <cmath>
4 
5 #include <MyGUI_ScrollView.h>
6 #include <MyGUI_ImageBox.h>
7 #include <MyGUI_Gui.h>
8 
9 #include "../mwbase/environment.hpp"
10 #include "../mwbase/world.hpp"
11 #include "../mwbase/windowmanager.hpp"
12 #include "../mwworld/esmstore.hpp"
13 #include "../mwmechanics/autocalcspell.hpp"
14 
15 #include "tooltips.hpp"
16 
17 namespace
18 {
adjustButtonSize(MyGUI::Button * button)19     void adjustButtonSize(MyGUI::Button *button)
20     {
21         // adjust size of button to fit its text
22         MyGUI::IntSize size = button->getTextSize();
23         button->setSize(size.width + 24, button->getSize().height);
24     }
25 }
26 
27 namespace MWGui
28 {
ReviewDialog()29     ReviewDialog::ReviewDialog()
30         : WindowModal("openmw_chargen_review.layout"),
31           mUpdateSkillArea(false)
32     {
33         // Centre dialog
34         center();
35 
36         // Setup static stats
37         MyGUI::Button* button;
38         getWidget(mNameWidget, "NameText");
39         getWidget(button, "NameButton");
40         adjustButtonSize(button);
41         button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onNameClicked);
42 
43         getWidget(mRaceWidget, "RaceText");
44         getWidget(button, "RaceButton");
45         adjustButtonSize(button);
46         button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onRaceClicked);
47 
48         getWidget(mClassWidget, "ClassText");
49         getWidget(button, "ClassButton");
50         adjustButtonSize(button);
51         button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onClassClicked);
52 
53         getWidget(mBirthSignWidget, "SignText");
54         getWidget(button, "SignButton");
55         adjustButtonSize(button);
56         button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onBirthSignClicked);
57 
58         // Setup dynamic stats
59         getWidget(mHealth, "Health");
60         mHealth->setTitle(MWBase::Environment::get().getWindowManager()->getGameSettingString("sHealth", ""));
61         mHealth->setValue(45, 45);
62 
63         getWidget(mMagicka, "Magicka");
64         mMagicka->setTitle(MWBase::Environment::get().getWindowManager()->getGameSettingString("sMagic", ""));
65         mMagicka->setValue(50, 50);
66 
67         getWidget(mFatigue, "Fatigue");
68         mFatigue->setTitle(MWBase::Environment::get().getWindowManager()->getGameSettingString("sFatigue", ""));
69         mFatigue->setValue(160, 160);
70 
71         // Setup attributes
72 
73         Widgets::MWAttributePtr attribute;
74         for (int idx = 0; idx < ESM::Attribute::Length; ++idx)
75         {
76             getWidget(attribute, std::string("Attribute") + MyGUI::utility::toString(idx));
77             mAttributeWidgets.insert(std::make_pair(static_cast<int>(ESM::Attribute::sAttributeIds[idx]), attribute));
78             attribute->setAttributeId(ESM::Attribute::sAttributeIds[idx]);
79             attribute->setAttributeValue(Widgets::MWAttribute::AttributeValue());
80         }
81 
82         // Setup skills
83         getWidget(mSkillView, "SkillView");
84         mSkillView->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel);
85 
86         for (int i = 0; i < ESM::Skill::Length; ++i)
87         {
88             mSkillValues.insert(std::make_pair(i, MWMechanics::SkillValue()));
89             mSkillWidgetMap.insert(std::make_pair(i, static_cast<MyGUI::TextBox*> (nullptr)));
90         }
91 
92         MyGUI::Button* backButton;
93         getWidget(backButton, "BackButton");
94         backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onBackClicked);
95 
96         MyGUI::Button* okButton;
97         getWidget(okButton, "OKButton");
98         okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onOkClicked);
99     }
100 
onOpen()101     void ReviewDialog::onOpen()
102     {
103         WindowModal::onOpen();
104         mUpdateSkillArea = true;
105     }
106 
onFrame(float)107     void ReviewDialog::onFrame(float /*duration*/)
108     {
109         if (mUpdateSkillArea)
110         {
111             updateSkillArea();
112             mUpdateSkillArea = false;
113         }
114     }
115 
setPlayerName(const std::string & name)116     void ReviewDialog::setPlayerName(const std::string &name)
117     {
118         mNameWidget->setCaption(name);
119     }
120 
setRace(const std::string & raceId)121     void ReviewDialog::setRace(const std::string &raceId)
122     {
123         mRaceId = raceId;
124 
125         const ESM::Race *race =
126             MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().search(mRaceId);
127         if (race)
128         {
129             ToolTips::createRaceToolTip(mRaceWidget, race);
130             mRaceWidget->setCaption(race->mName);
131         }
132 
133         mUpdateSkillArea = true;
134     }
135 
setClass(const ESM::Class & class_)136     void ReviewDialog::setClass(const ESM::Class& class_)
137     {
138         mKlass = class_;
139         mClassWidget->setCaption(mKlass.mName);
140         ToolTips::createClassToolTip(mClassWidget, mKlass);
141     }
142 
setBirthSign(const std::string & signId)143     void ReviewDialog::setBirthSign(const std::string& signId)
144     {
145         mBirthSignId = signId;
146 
147         const ESM::BirthSign *sign =
148             MWBase::Environment::get().getWorld()->getStore().get<ESM::BirthSign>().search(mBirthSignId);
149         if (sign)
150         {
151             mBirthSignWidget->setCaption(sign->mName);
152             ToolTips::createBirthsignToolTip(mBirthSignWidget, mBirthSignId);
153         }
154 
155         mUpdateSkillArea = true;
156     }
157 
setHealth(const MWMechanics::DynamicStat<float> & value)158     void ReviewDialog::setHealth(const MWMechanics::DynamicStat<float>& value)
159     {
160         int current = std::max(0, static_cast<int>(value.getCurrent()));
161         int modified = static_cast<int>(value.getModified());
162 
163         mHealth->setValue(current, modified);
164         std::string valStr =  MyGUI::utility::toString(current) + " / " + MyGUI::utility::toString(modified);
165         mHealth->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr);
166     }
167 
setMagicka(const MWMechanics::DynamicStat<float> & value)168     void ReviewDialog::setMagicka(const MWMechanics::DynamicStat<float>& value)
169     {
170         int current = std::max(0, static_cast<int>(value.getCurrent()));
171         int modified = static_cast<int>(value.getModified());
172 
173         mMagicka->setValue(current, modified);
174         std::string valStr =  MyGUI::utility::toString(current) + " / " + MyGUI::utility::toString(modified);
175         mMagicka->setUserString("Caption_HealthDescription", "#{sMagDesc}\n" + valStr);
176     }
177 
setFatigue(const MWMechanics::DynamicStat<float> & value)178     void ReviewDialog::setFatigue(const MWMechanics::DynamicStat<float>& value)
179     {
180         int current = static_cast<int>(value.getCurrent());
181         int modified = static_cast<int>(value.getModified());
182 
183         mFatigue->setValue(current, modified);
184         std::string valStr =  MyGUI::utility::toString(current) + " / " + MyGUI::utility::toString(modified);
185         mFatigue->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr);
186     }
187 
setAttribute(ESM::Attribute::AttributeID attributeId,const MWMechanics::AttributeValue & value)188     void ReviewDialog::setAttribute(ESM::Attribute::AttributeID attributeId, const MWMechanics::AttributeValue& value)
189     {
190         std::map<int, Widgets::MWAttributePtr>::iterator attr = mAttributeWidgets.find(static_cast<int>(attributeId));
191         if (attr == mAttributeWidgets.end())
192             return;
193 
194         if (attr->second->getAttributeValue() != value)
195         {
196             attr->second->setAttributeValue(value);
197             mUpdateSkillArea = true;
198         }
199     }
200 
setSkillValue(ESM::Skill::SkillEnum skillId,const MWMechanics::SkillValue & value)201     void ReviewDialog::setSkillValue(ESM::Skill::SkillEnum skillId, const MWMechanics::SkillValue& value)
202     {
203         mSkillValues[skillId] = value;
204         MyGUI::TextBox* widget = mSkillWidgetMap[skillId];
205         if (widget)
206         {
207             float modified = static_cast<float>(value.getModified()), base = static_cast<float>(value.getBase());
208             std::string text = MyGUI::utility::toString(std::floor(modified));
209             std::string state = "normal";
210             if (modified > base)
211                 state = "increased";
212             else if (modified < base)
213                 state = "decreased";
214 
215             widget->setCaption(text);
216             widget->_setWidgetState(state);
217         }
218 
219         mUpdateSkillArea = true;
220     }
221 
configureSkills(const std::vector<int> & major,const std::vector<int> & minor)222     void ReviewDialog::configureSkills(const std::vector<int>& major, const std::vector<int>& minor)
223     {
224         mMajorSkills = major;
225         mMinorSkills = minor;
226 
227         // Update misc skills with the remaining skills not in major or minor
228         std::set<int> skillSet;
229         std::copy(major.begin(), major.end(), std::inserter(skillSet, skillSet.begin()));
230         std::copy(minor.begin(), minor.end(), std::inserter(skillSet, skillSet.begin()));
231         mMiscSkills.clear();
232         for (const int skill : ESM::Skill::sSkillIds)
233         {
234             if (skillSet.find(skill) == skillSet.end())
235                 mMiscSkills.push_back(skill);
236         }
237 
238         mUpdateSkillArea = true;
239     }
240 
addSeparator(MyGUI::IntCoord & coord1,MyGUI::IntCoord & coord2)241     void ReviewDialog::addSeparator(MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2)
242     {
243         MyGUI::ImageBox* separator = mSkillView->createWidget<MyGUI::ImageBox>("MW_HLine", MyGUI::IntCoord(10, coord1.top, coord1.width + coord2.width - 4, 18), MyGUI::Align::Default);
244         separator->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel);
245 
246         mSkillWidgets.push_back(separator);
247 
248         coord1.top += separator->getHeight();
249         coord2.top += separator->getHeight();
250     }
251 
addGroup(const std::string & label,MyGUI::IntCoord & coord1,MyGUI::IntCoord & coord2)252     void ReviewDialog::addGroup(const std::string &label, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2)
253     {
254         MyGUI::TextBox* groupWidget = mSkillView->createWidget<MyGUI::TextBox>("SandBrightText", MyGUI::IntCoord(0, coord1.top, coord1.width + coord2.width, coord1.height), MyGUI::Align::Default);
255         groupWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel);
256         groupWidget->setCaption(label);
257         mSkillWidgets.push_back(groupWidget);
258 
259         int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2;
260         coord1.top += lineHeight;
261         coord2.top += lineHeight;
262     }
263 
addValueItem(const std::string & text,const std::string & value,const std::string & state,MyGUI::IntCoord & coord1,MyGUI::IntCoord & coord2)264     MyGUI::TextBox* ReviewDialog::addValueItem(const std::string& text, const std::string &value, const std::string& state, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2)
265     {
266         MyGUI::TextBox* skillNameWidget;
267         MyGUI::TextBox* skillValueWidget;
268 
269         skillNameWidget = mSkillView->createWidget<MyGUI::TextBox>("SandText", coord1, MyGUI::Align::Default);
270         skillNameWidget->setCaption(text);
271         skillNameWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel);
272 
273         skillValueWidget = mSkillView->createWidget<MyGUI::TextBox>("SandTextRight", coord2, MyGUI::Align::Default);
274         skillValueWidget->setCaption(value);
275         skillValueWidget->_setWidgetState(state);
276         skillValueWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel);
277 
278         mSkillWidgets.push_back(skillNameWidget);
279         mSkillWidgets.push_back(skillValueWidget);
280 
281         int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2;
282         coord1.top += lineHeight;
283         coord2.top += lineHeight;
284 
285         return skillValueWidget;
286     }
287 
addItem(const std::string & text,MyGUI::IntCoord & coord1,MyGUI::IntCoord & coord2)288     void ReviewDialog::addItem(const std::string& text, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2)
289     {
290         MyGUI::TextBox* skillNameWidget;
291 
292         skillNameWidget = mSkillView->createWidget<MyGUI::TextBox>("SandText", coord1 + MyGUI::IntSize(coord2.width, 0), MyGUI::Align::Default);
293         skillNameWidget->setCaption(text);
294         skillNameWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel);
295 
296         mSkillWidgets.push_back(skillNameWidget);
297 
298         int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2;
299         coord1.top += lineHeight;
300         coord2.top += lineHeight;
301     }
302 
addItem(const ESM::Spell * spell,MyGUI::IntCoord & coord1,MyGUI::IntCoord & coord2)303     void ReviewDialog::addItem(const ESM::Spell* spell, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2)
304     {
305         Widgets::MWSpellPtr widget = mSkillView->createWidget<Widgets::MWSpell>("MW_StatName", coord1 + MyGUI::IntSize(coord2.width, 0), MyGUI::Align::Default);
306         widget->setSpellId(spell->mId);
307         widget->setUserString("ToolTipType", "Spell");
308         widget->setUserString("Spell", spell->mId);
309         widget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel);
310 
311         mSkillWidgets.push_back(widget);
312 
313         int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2;
314         coord1.top += lineHeight;
315         coord2.top += lineHeight;
316     }
317 
addSkills(const SkillList & skills,const std::string & titleId,const std::string & titleDefault,MyGUI::IntCoord & coord1,MyGUI::IntCoord & coord2)318     void ReviewDialog::addSkills(const SkillList &skills, const std::string &titleId, const std::string &titleDefault, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2)
319     {
320         // Add a line separator if there are items above
321         if (!mSkillWidgets.empty())
322         {
323             addSeparator(coord1, coord2);
324         }
325 
326         addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString(titleId, titleDefault), coord1, coord2);
327 
328         for (const int& skillId : skills)
329         {
330             if (skillId < 0 || skillId >= ESM::Skill::Length) // Skip unknown skill indexes
331                 continue;
332             assert(skillId >= 0 && skillId < ESM::Skill::Length);
333             const std::string &skillNameId = ESM::Skill::sSkillNameIds[skillId];
334             const MWMechanics::SkillValue &stat = mSkillValues.find(skillId)->second;
335             int base = stat.getBase();
336             int modified = stat.getModified();
337 
338             std::string state = "normal";
339             if (modified > base)
340                 state = "increased";
341             else if (modified < base)
342                 state = "decreased";
343             MyGUI::TextBox* widget = addValueItem(MWBase::Environment::get().getWindowManager()->getGameSettingString(skillNameId, skillNameId), MyGUI::utility::toString(static_cast<int>(modified)), state, coord1, coord2);
344 
345             for (int i=0; i<2; ++i)
346             {
347                 ToolTips::createSkillToolTip(mSkillWidgets[mSkillWidgets.size()-1-i], skillId);
348             }
349 
350             mSkillWidgetMap[skillId] = widget;
351         }
352     }
353 
updateSkillArea()354     void ReviewDialog::updateSkillArea()
355     {
356         for (MyGUI::Widget* skillWidget : mSkillWidgets)
357         {
358             MyGUI::Gui::getInstance().destroyWidget(skillWidget);
359         }
360         mSkillWidgets.clear();
361 
362         const int valueSize = 40;
363         MyGUI::IntCoord coord1(10, 0, mSkillView->getWidth() - (10 + valueSize) - 24, 18);
364         MyGUI::IntCoord coord2(coord1.left + coord1.width, coord1.top, valueSize, coord1.height);
365 
366         if (!mMajorSkills.empty())
367             addSkills(mMajorSkills, "sSkillClassMajor", "Major Skills", coord1, coord2);
368 
369         if (!mMinorSkills.empty())
370             addSkills(mMinorSkills, "sSkillClassMinor", "Minor Skills", coord1, coord2);
371 
372         if (!mMiscSkills.empty())
373             addSkills(mMiscSkills, "sSkillClassMisc", "Misc Skills", coord1, coord2);
374 
375         // starting spells
376         std::vector<std::string> spells;
377 
378         const ESM::Race* race = nullptr;
379         if (!mRaceId.empty())
380             race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(mRaceId);
381 
382         int skills[ESM::Skill::Length];
383         for (int i=0; i<ESM::Skill::Length; ++i)
384             skills[i] = mSkillValues.find(i)->second.getBase();
385 
386         int attributes[ESM::Attribute::Length];
387         for (int i=0; i<ESM::Attribute::Length; ++i)
388             attributes[i] = mAttributeWidgets[i]->getAttributeValue().getBase();
389 
390         std::vector<std::string> selectedSpells = MWMechanics::autoCalcPlayerSpells(skills, attributes, race);
391         for (std::string& spellId : selectedSpells)
392         {
393             std::string lower = Misc::StringUtils::lowerCase(spellId);
394             if (std::find(spells.begin(), spells.end(), lower) == spells.end())
395                 spells.push_back(lower);
396         }
397 
398         if (race)
399         {
400             for (const std::string& spellId : race->mPowers.mList)
401             {
402                 std::string lower = Misc::StringUtils::lowerCase(spellId);
403                 if (std::find(spells.begin(), spells.end(), lower) == spells.end())
404                     spells.push_back(lower);
405             }
406         }
407 
408         if (!mBirthSignId.empty())
409         {
410             const ESM::BirthSign* sign = MWBase::Environment::get().getWorld()->getStore().get<ESM::BirthSign>().find(mBirthSignId);
411             for (const std::string& spellId : sign->mPowers.mList)
412             {
413                 std::string lower = Misc::StringUtils::lowerCase(spellId);
414                 if (std::find(spells.begin(), spells.end(), lower) == spells.end())
415                     spells.push_back(lower);
416             }
417         }
418 
419         if (!mSkillWidgets.empty())
420             addSeparator(coord1, coord2);
421         addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString("sTypeAbility", "Abilities"), coord1, coord2);
422         for (std::string& spellId : spells)
423         {
424             const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellId);
425             if (spell->mData.mType == ESM::Spell::ST_Ability)
426                 addItem(spell, coord1, coord2);
427         }
428 
429         addSeparator(coord1, coord2);
430         addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString("sTypePower", "Powers"), coord1, coord2);
431         for (std::string& spellId : spells)
432         {
433             const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellId);
434             if (spell->mData.mType == ESM::Spell::ST_Power)
435                 addItem(spell, coord1, coord2);
436         }
437 
438         addSeparator(coord1, coord2);
439         addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString("sTypeSpell", "Spells"), coord1, coord2);
440         for (std::string& spellId : spells)
441         {
442             const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellId);
443             if (spell->mData.mType == ESM::Spell::ST_Spell)
444                 addItem(spell, coord1, coord2);
445         }
446 
447         // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden
448         mSkillView->setVisibleVScroll(false);
449         mSkillView->setCanvasSize (mSkillView->getWidth(), std::max(mSkillView->getHeight(), coord1.top));
450         mSkillView->setVisibleVScroll(true);
451     }
452 
453     // widget controls
454 
onOkClicked(MyGUI::Widget * _sender)455     void ReviewDialog::onOkClicked(MyGUI::Widget* _sender)
456     {
457         eventDone(this);
458     }
459 
onBackClicked(MyGUI::Widget * _sender)460     void ReviewDialog::onBackClicked(MyGUI::Widget* _sender)
461     {
462         eventBack();
463     }
464 
onNameClicked(MyGUI::Widget * _sender)465     void ReviewDialog::onNameClicked(MyGUI::Widget* _sender)
466     {
467         eventActivateDialog(NAME_DIALOG);
468     }
469 
onRaceClicked(MyGUI::Widget * _sender)470     void ReviewDialog::onRaceClicked(MyGUI::Widget* _sender)
471     {
472         eventActivateDialog(RACE_DIALOG);
473     }
474 
onClassClicked(MyGUI::Widget * _sender)475     void ReviewDialog::onClassClicked(MyGUI::Widget* _sender)
476     {
477         eventActivateDialog(CLASS_DIALOG);
478     }
479 
onBirthSignClicked(MyGUI::Widget * _sender)480     void ReviewDialog::onBirthSignClicked(MyGUI::Widget* _sender)
481     {
482         eventActivateDialog(BIRTHSIGN_DIALOG);
483     }
484 
onMouseWheel(MyGUI::Widget * _sender,int _rel)485     void ReviewDialog::onMouseWheel(MyGUI::Widget* _sender, int _rel)
486     {
487         if (mSkillView->getViewOffset().top + _rel*0.3 > 0)
488             mSkillView->setViewOffset(MyGUI::IntPoint(0, 0));
489         else
490             mSkillView->setViewOffset(MyGUI::IntPoint(0, static_cast<int>(mSkillView->getViewOffset().top + _rel*0.3)));
491     }
492 
493 }
494