#include "race.hpp" #include #include #include #include #include #include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "widgets.hpp" #include "tooltips.hpp" using namespace MWGui; using namespace Widgets; RaceDialog::RaceDialog(MWBase::WindowManager& parWindowManager) : WindowBase("openmw_chargen_race.layout", parWindowManager) , mGenderIndex(0) , mFaceIndex(0) , mHairIndex(0) , mFaceCount(10) , mHairCount(14) , mCurrentAngle(0) { // Centre dialog center(); setText("AppearanceT", mWindowManager.getGameSettingString("sRaceMenu1", "Appearance")); getWidget(mPreviewImage, "PreviewImage"); getWidget(mHeadRotate, "HeadRotate"); mHeadRotate->setScrollRange(50); mHeadRotate->setScrollPosition(25); mHeadRotate->setScrollViewPage(10); mHeadRotate->eventScrollChangePosition += MyGUI::newDelegate(this, &RaceDialog::onHeadRotate); // Set up next/previous buttons MyGUI::ButtonPtr prevButton, nextButton; setText("GenderChoiceT", mWindowManager.getGameSettingString("sRaceMenu2", "Change Sex")); getWidget(prevButton, "PrevGenderButton"); getWidget(nextButton, "NextGenderButton"); prevButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectPreviousGender); nextButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectNextGender); setText("FaceChoiceT", mWindowManager.getGameSettingString("sRaceMenu3", "Change Face")); getWidget(prevButton, "PrevFaceButton"); getWidget(nextButton, "NextFaceButton"); prevButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectPreviousFace); nextButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectNextFace); setText("HairChoiceT", mWindowManager.getGameSettingString("sRaceMenu4", "Change Hair")); getWidget(prevButton, "PrevHairButton"); getWidget(nextButton, "NextHairButton"); prevButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectPreviousHair); nextButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectNextHair); setText("RaceT", mWindowManager.getGameSettingString("sRaceMenu4", "Race")); getWidget(mRaceList, "RaceList"); mRaceList->setScrollVisible(true); mRaceList->eventListSelectAccept += MyGUI::newDelegate(this, &RaceDialog::onSelectRace); mRaceList->eventListMouseItemActivate += MyGUI::newDelegate(this, &RaceDialog::onSelectRace); mRaceList->eventListChangePosition += MyGUI::newDelegate(this, &RaceDialog::onSelectRace); setText("SkillsT", mWindowManager.getGameSettingString("sBonusSkillTitle", "Skill Bonus")); getWidget(mSkillList, "SkillList"); setText("SpellPowerT", mWindowManager.getGameSettingString("sRaceMenu7", "Specials")); getWidget(mSpellPowerList, "SpellPowerList"); MyGUI::ButtonPtr backButton; getWidget(backButton, "BackButton"); backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onBackClicked); MyGUI::ButtonPtr okButton; getWidget(okButton, "OKButton"); okButton->setCaption(mWindowManager.getGameSettingString("sOK", "")); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onOkClicked); updateRaces(); updateSkills(); updateSpellPowers(); } void RaceDialog::setNextButtonShow(bool shown) { MyGUI::ButtonPtr okButton; getWidget(okButton, "OKButton"); if (shown) okButton->setCaption(mWindowManager.getGameSettingString("sNext", "")); else okButton->setCaption(mWindowManager.getGameSettingString("sOK", "")); } void RaceDialog::open() { updateRaces(); updateSkills(); updateSpellPowers(); mPreview = new MWRender::RaceSelectionPreview(); mPreview->setup(); mPreview->update (0); const ESM::NPC proto = mPreview->getPrototype(); setRaceId(proto.mRace); recountParts(); std::string index = proto.mHead.substr(proto.mHead.size() - 2, 2); mFaceIndex = boost::lexical_cast(index) - 1; index = proto.mHair.substr(proto.mHair.size() - 2, 2); mHairIndex = boost::lexical_cast(index) - 1; mPreviewImage->setImageTexture ("CharacterHeadPreview"); } void RaceDialog::setRaceId(const std::string &raceId) { mCurrentRaceId = raceId; mRaceList->setIndexSelected(MyGUI::ITEM_NONE); size_t count = mRaceList->getItemCount(); for (size_t i = 0; i < count; ++i) { if (boost::iequals(*mRaceList->getItemDataAt(i), raceId)) { mRaceList->setIndexSelected(i); MyGUI::ButtonPtr okButton; getWidget(okButton, "OKButton"); break; } } updateSkills(); updateSpellPowers(); } int wrap(int index, int max) { if (index < 0) return max - 1; else if (index >= max) return 0; else return index; } int countParts(const std::string &part, const std::string &race, bool male) { const MWWorld::Store &store = MWBase::Environment::get().getWorld()->getStore().get(); std::string prefix = "b_n_" + race + ((male) ? "_m_" : "_f_") + part; std::string suffix; suffix.reserve(prefix.size() + 3); int count = -1; do { ++count; suffix = "_" + (boost::format("%02d") % (count + 1)).str(); } while (store.search(prefix + suffix) != 0); if (count == 0 && part == "hair") { count = -1; do { ++count; suffix = (boost::format("%02d") % (count + 1)).str(); } while (store.search(prefix + suffix) != 0); } return count; } void RaceDialog::close() { delete mPreview; mPreview = 0; } // widget controls void RaceDialog::onOkClicked(MyGUI::Widget* _sender) { if(mRaceList->getIndexSelected() == MyGUI::ITEM_NONE) return; eventDone(this); } void RaceDialog::onBackClicked(MyGUI::Widget* _sender) { eventBack(); } void RaceDialog::onHeadRotate(MyGUI::ScrollBar*, size_t _position) { float angle = (float(_position) / 49.f - 0.5) * 3.14 * 2; float diff = angle - mCurrentAngle; mPreview->update (diff); mCurrentAngle += diff; } void RaceDialog::onSelectPreviousGender(MyGUI::Widget*) { mGenderIndex = wrap(mGenderIndex - 1, 2); recountParts(); updatePreview(); } void RaceDialog::onSelectNextGender(MyGUI::Widget*) { mGenderIndex = wrap(mGenderIndex + 1, 2); recountParts(); updatePreview(); } void RaceDialog::onSelectPreviousFace(MyGUI::Widget*) { mFaceIndex = wrap(mFaceIndex - 1, mFaceCount); updatePreview(); } void RaceDialog::onSelectNextFace(MyGUI::Widget*) { mFaceIndex = wrap(mFaceIndex + 1, mFaceCount); updatePreview(); } void RaceDialog::onSelectPreviousHair(MyGUI::Widget*) { mHairIndex = wrap(mHairIndex - 1, mHairCount); updatePreview(); } void RaceDialog::onSelectNextHair(MyGUI::Widget*) { mHairIndex = wrap(mHairIndex + 1, mHairCount); updatePreview(); } void RaceDialog::onSelectRace(MyGUI::ListBox* _sender, size_t _index) { if (_index == MyGUI::ITEM_NONE) return; MyGUI::ButtonPtr okButton; getWidget(okButton, "OKButton"); const std::string *raceId = mRaceList->getItemDataAt(_index); if (boost::iequals(mCurrentRaceId, *raceId)) return; mCurrentRaceId = *raceId; recountParts(); updatePreview(); updateSkills(); updateSpellPowers(); } void RaceDialog::recountParts() { mFaceIndex = 0; mHairIndex = 0; mFaceCount = countParts("head", mCurrentRaceId, mGenderIndex == 0); mHairCount = countParts("hair", mCurrentRaceId, mGenderIndex == 0); } // update widget content void RaceDialog::updatePreview() { ESM::NPC record = mPreview->getPrototype(); record.mRace = mCurrentRaceId; record.setIsMale(mGenderIndex == 0); std::string prefix = "b_n_" + mCurrentRaceId + ((record.isMale()) ? "_m_" : "_f_"); std::string headIndex = (boost::format("%02d") % (mFaceIndex + 1)).str(); std::string hairIndex = (boost::format("%02d") % (mHairIndex + 1)).str(); record.mHead = prefix + "head_" + headIndex; record.mHair = prefix + "hair_" + hairIndex; const MWWorld::Store &parts = MWBase::Environment::get().getWorld()->getStore().get(); if (parts.search(record.mHair) == 0) { record.mHair = prefix + "hair" + hairIndex; } mPreview->setPrototype(record); } void RaceDialog::updateRaces() { mRaceList->removeAllItems(); const MWWorld::Store &races = MWBase::Environment::get().getWorld()->getStore().get(); int index = 0; MWWorld::Store::iterator it = races.begin(); for (; it != races.end(); ++it) { bool playable = it->mData.mFlags & ESM::Race::Playable; if (!playable) // Only display playable races continue; mRaceList->addItem(it->mName, it->mId); if (boost::iequals(it->mId, mCurrentRaceId)) mRaceList->setIndexSelected(index); ++index; } } void RaceDialog::updateSkills() { for (std::vector::iterator it = mSkillItems.begin(); it != mSkillItems.end(); ++it) { MyGUI::Gui::getInstance().destroyWidget(*it); } mSkillItems.clear(); if (mCurrentRaceId.empty()) return; MWSkillPtr skillWidget; const int lineHeight = 18; MyGUI::IntCoord coord1(0, 0, mSkillList->getWidth(), 18); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Race *race = store.get().find(mCurrentRaceId); int count = sizeof(race->mData.mBonus)/sizeof(race->mData.mBonus[0]); // TODO: Find a portable macro for this ARRAYSIZE? for (int i = 0; i < count; ++i) { int skillId = race->mData.mBonus[i].mSkill; if (skillId < 0 || skillId > ESM::Skill::Length) // Skip unknown skill indexes continue; skillWidget = mSkillList->createWidget("MW_StatNameValue", coord1, MyGUI::Align::Default, std::string("Skill") + boost::lexical_cast(i)); skillWidget->setWindowManager(&mWindowManager); skillWidget->setSkillNumber(skillId); skillWidget->setSkillValue(MWSkill::SkillValue(race->mData.mBonus[i].mBonus)); ToolTips::createSkillToolTip(skillWidget, skillId); mSkillItems.push_back(skillWidget); coord1.top += lineHeight; } } void RaceDialog::updateSpellPowers() { for (std::vector::iterator it = mSpellPowerItems.begin(); it != mSpellPowerItems.end(); ++it) { MyGUI::Gui::getInstance().destroyWidget(*it); } mSpellPowerItems.clear(); if (mCurrentRaceId.empty()) return; MWSpellPtr spellPowerWidget; const int lineHeight = 18; MyGUI::IntCoord coord(0, 0, mSpellPowerList->getWidth(), 18); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Race *race = store.get().find(mCurrentRaceId); std::vector::const_iterator it = race->mPowers.mList.begin(); std::vector::const_iterator end = race->mPowers.mList.end(); for (int i = 0; it != end; ++it) { const std::string &spellpower = *it; spellPowerWidget = mSpellPowerList->createWidget("MW_StatName", coord, MyGUI::Align::Default, std::string("SpellPower") + boost::lexical_cast(i)); spellPowerWidget->setWindowManager(&mWindowManager); spellPowerWidget->setSpellId(spellpower); spellPowerWidget->setUserString("ToolTipType", "Spell"); spellPowerWidget->setUserString("Spell", spellpower); mSpellPowerItems.push_back(spellPowerWidget); coord.top += lineHeight; ++i; } }