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