1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-01-15 22:19:54 +00:00
openmw-tes3mp/apps/openmw/mwgui/race.cpp
scrawl e8662bea31 Change the way that image origin is converted to OpenGL's lower-left convention
Flip the texture coordinates instead of flipping textures.

This simplifies the TextureManager (no need to worry if the caller wants flipping or not), should make it easier to generalize & multithread it.
2016-02-05 21:03:11 +01:00

446 lines
15 KiB
C++

#include "race.hpp"
#include <MyGUI_ListBox.h>
#include <MyGUI_ImageBox.h>
#include <MyGUI_RenderManager.h>
#include <MyGUI_Gui.h>
#include <osg/Texture2D>
#include <boost/format.hpp>
#include <components/myguiplatform/myguitexture.hpp>
#include "../mwworld/esmstore.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwrender/characterpreview.hpp"
#include "tooltips.hpp"
namespace
{
int wrap(int index, int max)
{
if (index < 0)
return max - 1;
else if (index >= max)
return 0;
else
return index;
}
bool sortRaces(const std::pair<std::string, std::string>& left, const std::pair<std::string, std::string>& right)
{
return left.second.compare(right.second) < 0;
}
}
namespace MWGui
{
RaceDialog::RaceDialog(osgViewer::Viewer* viewer, Resource::ResourceSystem* resourceSystem)
: WindowModal("openmw_chargen_race.layout")
, mViewer(viewer)
, mResourceSystem(resourceSystem)
, mGenderIndex(0)
, mFaceIndex(0)
, mHairIndex(0)
, mCurrentAngle(0)
, mPreviewDirty(true)
{
// Centre dialog
center();
setText("AppearanceT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu1", "Appearance"));
getWidget(mPreviewImage, "PreviewImage");
getWidget(mHeadRotate, "HeadRotate");
mHeadRotate->setScrollRange(1000);
mHeadRotate->setScrollPosition(500);
mHeadRotate->setScrollViewPage(50);
mHeadRotate->setScrollPage(50);
mHeadRotate->setScrollWheelPage(50);
mHeadRotate->eventScrollChangePosition += MyGUI::newDelegate(this, &RaceDialog::onHeadRotate);
// Set up next/previous buttons
MyGUI::Button *prevButton, *nextButton;
setText("GenderChoiceT", MWBase::Environment::get().getWindowManager()->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", MWBase::Environment::get().getWindowManager()->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", MWBase::Environment::get().getWindowManager()->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", MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu5", "Race"));
getWidget(mRaceList, "RaceList");
mRaceList->setScrollVisible(true);
mRaceList->eventListSelectAccept += MyGUI::newDelegate(this, &RaceDialog::onAccept);
mRaceList->eventListChangePosition += MyGUI::newDelegate(this, &RaceDialog::onSelectRace);
setText("SkillsT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sBonusSkillTitle", "Skill Bonus"));
getWidget(mSkillList, "SkillList");
setText("SpellPowerT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu7", "Specials"));
getWidget(mSpellPowerList, "SpellPowerList");
MyGUI::Button* backButton;
getWidget(backButton, "BackButton");
backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onBackClicked);
MyGUI::Button* okButton;
getWidget(okButton, "OKButton");
okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", ""));
okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onOkClicked);
updateRaces();
updateSkills();
updateSpellPowers();
}
void RaceDialog::setNextButtonShow(bool shown)
{
MyGUI::Button* okButton;
getWidget(okButton, "OKButton");
if (shown)
okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", ""));
else
okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", ""));
}
void RaceDialog::open()
{
WindowModal::open();
updateRaces();
updateSkills();
updateSpellPowers();
mPreviewImage->setRenderItemTexture(NULL);
mPreview.reset(NULL);
mPreviewTexture.reset(NULL);
mPreview.reset(new MWRender::RaceSelectionPreview(mViewer, mResourceSystem));
mPreview->rebuild();
mPreview->setAngle (mCurrentAngle);
mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture()));
mPreviewImage->setRenderItemTexture(mPreviewTexture.get());
mPreviewImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f));
const ESM::NPC& proto = mPreview->getPrototype();
setRaceId(proto.mRace);
recountParts();
for (unsigned int i=0; i<mAvailableHeads.size(); ++i)
{
if (Misc::StringUtils::ciEqual(mAvailableHeads[i], proto.mHead))
mFaceIndex = i;
}
for (unsigned int i=0; i<mAvailableHairs.size(); ++i)
{
if (Misc::StringUtils::ciEqual(mAvailableHairs[i], proto.mHair))
mHairIndex = i;
}
mPreviewDirty = true;
size_t initialPos = mHeadRotate->getScrollRange()/2+mHeadRotate->getScrollRange()/10;
mHeadRotate->setScrollPosition(initialPos);
onHeadRotate(mHeadRotate, initialPos);
}
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 (Misc::StringUtils::ciEqual(*mRaceList->getItemDataAt<std::string>(i), raceId))
{
mRaceList->setIndexSelected(i);
break;
}
}
updateSkills();
updateSpellPowers();
}
void RaceDialog::close()
{
mPreviewImage->setRenderItemTexture(NULL);
mPreviewTexture.reset(NULL);
mPreview.reset(NULL);
}
// 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* scroll, size_t _position)
{
float angle = (float(_position) / (scroll->getScrollRange()-1) - 0.5f) * 3.14f * 2;
mPreview->setAngle (angle);
mCurrentAngle = angle;
}
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, mAvailableHeads.size());
updatePreview();
}
void RaceDialog::onSelectNextFace(MyGUI::Widget*)
{
mFaceIndex = wrap(mFaceIndex + 1, mAvailableHeads.size());
updatePreview();
}
void RaceDialog::onSelectPreviousHair(MyGUI::Widget*)
{
mHairIndex = wrap(mHairIndex - 1, mAvailableHairs.size());
updatePreview();
}
void RaceDialog::onSelectNextHair(MyGUI::Widget*)
{
mHairIndex = wrap(mHairIndex + 1, mAvailableHairs.size());
updatePreview();
}
void RaceDialog::onSelectRace(MyGUI::ListBox* _sender, size_t _index)
{
if (_index == MyGUI::ITEM_NONE)
return;
const std::string *raceId = mRaceList->getItemDataAt<std::string>(_index);
if (Misc::StringUtils::ciEqual(mCurrentRaceId, *raceId))
return;
mCurrentRaceId = *raceId;
recountParts();
updatePreview();
updateSkills();
updateSpellPowers();
}
void RaceDialog::onAccept(MyGUI::ListBox *_sender, size_t _index)
{
onSelectRace(_sender, _index);
if(mRaceList->getIndexSelected() == MyGUI::ITEM_NONE)
return;
eventDone(this);
}
void RaceDialog::getBodyParts (int part, std::vector<std::string>& out)
{
out.clear();
const MWWorld::Store<ESM::BodyPart> &store =
MWBase::Environment::get().getWorld()->getStore().get<ESM::BodyPart>();
for (MWWorld::Store<ESM::BodyPart>::iterator it = store.begin(); it != store.end(); ++it)
{
const ESM::BodyPart& bodypart = *it;
if (bodypart.mData.mFlags & ESM::BodyPart::BPF_NotPlayable)
continue;
if (bodypart.mData.mType != ESM::BodyPart::MT_Skin)
continue;
if (bodypart.mData.mPart != static_cast<ESM::BodyPart::MeshPart>(part))
continue;
if (mGenderIndex != (bodypart.mData.mFlags & ESM::BodyPart::BPF_Female))
continue;
bool firstPerson = (bodypart.mId.size() >= 3)
&& bodypart.mId[bodypart.mId.size()-3] == '1'
&& bodypart.mId[bodypart.mId.size()-2] == 's'
&& bodypart.mId[bodypart.mId.size()-1] == 't';
if (firstPerson)
continue;
if (Misc::StringUtils::ciEqual(bodypart.mRace, mCurrentRaceId))
out.push_back(bodypart.mId);
}
}
void RaceDialog::recountParts()
{
getBodyParts(ESM::BodyPart::MP_Hair, mAvailableHairs);
getBodyParts(ESM::BodyPart::MP_Head, mAvailableHeads);
mFaceIndex = 0;
mHairIndex = 0;
}
// update widget content
void RaceDialog::updatePreview()
{
ESM::NPC record = mPreview->getPrototype();
record.mRace = mCurrentRaceId;
record.setIsMale(mGenderIndex == 0);
record.mHead = mAvailableHeads[mFaceIndex];
record.mHair = mAvailableHairs[mHairIndex];
try
{
mPreview->setPrototype(record);
}
catch (std::exception& e)
{
std::cerr << "Error creating preview: " << e.what() << std::endl;
}
}
void RaceDialog::updateRaces()
{
mRaceList->removeAllItems();
const MWWorld::Store<ESM::Race> &races =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>();
std::vector<std::pair<std::string, std::string> > items; // ID, name
MWWorld::Store<ESM::Race>::iterator it = races.begin();
for (; it != races.end(); ++it)
{
bool playable = it->mData.mFlags & ESM::Race::Playable;
if (!playable) // Only display playable races
continue;
items.push_back(std::make_pair(it->mId, it->mName));
}
std::sort(items.begin(), items.end(), sortRaces);
int index = 0;
for (std::vector<std::pair<std::string, std::string> >::const_iterator it = items.begin(); it != items.end(); ++it)
{
mRaceList->addItem(it->second, it->first);
if (Misc::StringUtils::ciEqual(it->first, mCurrentRaceId))
mRaceList->setIndexSelected(index);
++index;
}
}
void RaceDialog::updateSkills()
{
for (std::vector<MyGUI::Widget*>::iterator it = mSkillItems.begin(); it != mSkillItems.end(); ++it)
{
MyGUI::Gui::getInstance().destroyWidget(*it);
}
mSkillItems.clear();
if (mCurrentRaceId.empty())
return;
Widgets::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<ESM::Race>().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<Widgets::MWSkill>("MW_StatNameValue", coord1, MyGUI::Align::Default,
std::string("Skill") + MyGUI::utility::toString(i));
skillWidget->setSkillNumber(skillId);
skillWidget->setSkillValue(Widgets::MWSkill::SkillValue(static_cast<float>(race->mData.mBonus[i].mBonus)));
ToolTips::createSkillToolTip(skillWidget, skillId);
mSkillItems.push_back(skillWidget);
coord1.top += lineHeight;
}
}
void RaceDialog::updateSpellPowers()
{
for (std::vector<MyGUI::Widget*>::iterator it = mSpellPowerItems.begin(); it != mSpellPowerItems.end(); ++it)
{
MyGUI::Gui::getInstance().destroyWidget(*it);
}
mSpellPowerItems.clear();
if (mCurrentRaceId.empty())
return;
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<ESM::Race>().find(mCurrentRaceId);
std::vector<std::string>::const_iterator it = race->mPowers.mList.begin();
std::vector<std::string>::const_iterator end = race->mPowers.mList.end();
for (int i = 0; it != end; ++it)
{
const std::string &spellpower = *it;
Widgets::MWSpellPtr spellPowerWidget = mSpellPowerList->createWidget<Widgets::MWSpell>("MW_StatName", coord, MyGUI::Align::Default, std::string("SpellPower") + MyGUI::utility::toString(i));
spellPowerWidget->setSpellId(spellpower);
spellPowerWidget->setUserString("ToolTipType", "Spell");
spellPowerWidget->setUserString("Spell", spellpower);
mSpellPowerItems.push_back(spellPowerWidget);
coord.top += lineHeight;
++i;
}
}
const ESM::NPC& RaceDialog::getResult() const
{
return mPreview->getPrototype();
}
}