/* * The ManaPlus Client * Copyright (C) 2004-2009 The Mana World Development Team * Copyright (C) 2009-2010 The Mana Developers * Copyright (C) 2011-2013 The ManaPlus Developers * * This file is part of The ManaPlus Client. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "gui/windows/charcreatedialog.h" #include "main.h" #include "input/keydata.h" #include "input/keyevent.h" #include "gui/windows/okdialog.h" #include "gui/widgets/button.h" #include "gui/widgets/label.h" #include "gui/widgets/playerbox.h" #include "gui/widgets/radiobutton.h" #include "gui/widgets/slider.h" #include "gui/widgets/textfield.h" #include "net/net.h" #include "resources/iteminfo.h" #include "resources/db/chardb.h" #include "resources/db/itemdb.h" #include "utils/gettext.h" #include "debug.h" extern int serverVersion; static const Being::Action actions[] = { Being::STAND, Being::SIT, Being::MOVE, Being::ATTACK, Being::DEAD }; static const uint8_t directions[] = { Being::DOWN, Being::RIGHT, Being::UP, Being::LEFT }; CharCreateDialog::CharCreateDialog(CharSelectDialog *const parent, const int slot) : // TRANSLATORS: char create dialog name Window(_("New Character"), true, parent, "charcreate.xml"), gcn::ActionListener(), gcn::KeyListener(), mCharSelectDialog(parent), mNameField(new TextField(this, "")), // TRANSLATORS: char create dialog label mNameLabel(new Label(this, _("Name:"))), // TRANSLATORS: This is a narrow symbol used to denote 'next'. // You may change this symbol if your language uses another. // TRANSLATORS: char create dialog button mNextHairColorButton(new Button(this, _(">"), "nextcolor", this)), // TRANSLATORS: This is a narrow symbol used to denote 'previous'. // You may change this symbol if your language uses another. // TRANSLATORS: char create dialog button mPrevHairColorButton(new Button(this, _("<"), "prevcolor", this)), // TRANSLATORS: char create dialog label mHairColorLabel(new Label(this, _("Hair color:"))), mHairColorNameLabel(new Label(this, "")), // TRANSLATORS: char create dialog button mNextHairStyleButton(new Button(this, _(">"), "nextstyle", this)), // TRANSLATORS: char create dialog button mPrevHairStyleButton(new Button(this, _("<"), "prevstyle", this)), // TRANSLATORS: char create dialog label mHairStyleLabel(new Label(this, _("Hair style:"))), mHairStyleNameLabel(new Label(this, "")), mNextRaceButton(nullptr), mPrevRaceButton(nullptr), mRaceLabel(nullptr), mRaceNameLabel(nullptr), mNextLookButton(nullptr), mPrevLookButton(nullptr), mLookLabel(nullptr), mLookNameLabel(nullptr), // TRANSLATORS: char create dialog button mActionButton(new Button(this, _("^"), "action", this)), // TRANSLATORS: char create dialog button mRotateButton(new Button(this, _(">"), "rotate", this)), // TRANSLATORS: char create dialog button mMale(new RadioButton(this, _("Male"), "gender")), // TRANSLATORS: char create dialog button mFemale(new RadioButton(this, _("Female"), "gender")), // TRANSLATORS: char create dialog button mOther(new RadioButton(this, _("Other"), "gender")), mAttributeSlider(), mAttributeLabel(), mAttributeValue(), mAttributesLeft(new Label(this, // TRANSLATORS: char create dialog label strprintf(_("Please distribute %d points"), 99))), mMaxPoints(0), mUsedPoints(0), // TRANSLATORS: char create dialog button mCreateButton(new Button(this, _("Create"), "create", this)), // TRANSLATORS: char create dialog button mCancelButton(new Button(this, _("Cancel"), "cancel", this)), mRace(0), mLook(0), mMinLook(CharDB::getMinLook()), mMaxLook(CharDB::getMaxLook()), mPlayer(new Being(0, ActorSprite::PLAYER, static_cast<uint16_t>(mRace), nullptr)), mPlayerBox(new PlayerBox(mPlayer, "charcreate_playerbox.xml", "charcreate_selectedplayerbox.xml")), mHairStyle(0), mHairColor(0), mSlot(slot), maxHairColor(CharDB::getMaxHairColor()), minHairColor(CharDB::getMinHairColor()), maxHairStyle(CharDB::getMaxHairStyle()), minHairStyle(CharDB::getMinHairStyle()), mAction(0), mDirection(0) { setStickyButtonLock(true); setSticky(true); setWindowName("NewCharacter"); mPlayer->setGender(GENDER_MALE); const std::vector<int> &items = CharDB::getDefaultItems(); int i = 1; for (std::vector<int>::const_iterator it = items.begin(), it_end = items.end(); it != it_end; ++ it, i ++) { mPlayer->setSprite(i, *it); } if (!maxHairColor) maxHairColor = ColorDB::getHairSize(); if (!maxHairStyle) maxHairStyle = mPlayer->getNumOfHairstyles(); if (maxHairStyle) mHairStyle = (rand() % maxHairStyle) + minHairStyle; else mHairStyle = 0; if (maxHairColor) mHairColor = (rand() % maxHairColor) + minHairColor; else mHairColor = 0; mNameField->setMaximum(24); if (serverVersion >= 2) { // TRANSLATORS: char create dialog button mNextRaceButton = new Button(this, _(">"), "nextrace", this); // TRANSLATORS: char create dialog button mPrevRaceButton = new Button(this, _("<"), "prevrace", this); // TRANSLATORS: char create dialog label mRaceLabel = new Label(this, _("Race:")); mRaceNameLabel = new Label(this, ""); } if (serverVersion >= 9 && mMinLook < mMaxLook) { // TRANSLATORS: char create dialog button mNextLookButton = new Button(this, _(">"), "nextlook", this); // TRANSLATORS: char create dialog button mPrevLookButton = new Button(this, _("<"), "prevlook", this); // TRANSLATORS: char create dialog label mLookLabel = new Label(this, _("Look:")); mLookNameLabel = new Label(this, ""); } // Default to a Male character mMale->setSelected(true); mMale->setActionEventId("gender"); mFemale->setActionEventId("gender"); mOther->setActionEventId("gender"); mMale->addActionListener(this); mFemale->addActionListener(this); mOther->addActionListener(this); mPlayerBox->setWidth(74); mNameField->setActionEventId("create"); mNameField->addActionListener(this); const int w = 480; const int h = 350; setContentSize(w, h); mPlayerBox->setDimension(gcn::Rectangle(360, 0, 110, 90)); mActionButton->setPosition(385, 100); mRotateButton->setPosition(415, 100); mNameLabel->setPosition(5, 2); mNameField->setDimension( gcn::Rectangle(60, 2, 300, mNameField->getHeight())); const int leftX = 120; const int rightX = 300; const int labelX = 5; const int nameX = 145; int y = 30; mPrevHairColorButton->setPosition(leftX, y); mNextHairColorButton->setPosition(rightX, y); y += 5; mHairColorLabel->setPosition(labelX, y); mHairColorNameLabel->setPosition(nameX, y); y += 24; mPrevHairStyleButton->setPosition(leftX, y); mNextHairStyleButton->setPosition(rightX, y); y += 5; mHairStyleLabel->setPosition(labelX, y); mHairStyleNameLabel->setPosition(nameX, y); if (serverVersion >= 9 && mMinLook < mMaxLook) { y += 24; mPrevLookButton->setPosition(leftX, y); mNextLookButton->setPosition(rightX, y); y += 5; mLookLabel->setPosition(labelX, y); mLookNameLabel->setPosition(nameX, y); // 93 } if (serverVersion >= 2) { y += 24; mPrevRaceButton->setPosition(leftX, y); mNextRaceButton->setPosition(rightX, y); y += 5; mRaceLabel->setPosition(labelX, y); mRaceNameLabel->setPosition(nameX, y); } updateSliders(); setButtonsPosition(w, h); mMale->setPosition(30, 120); mFemale->setPosition(100, 120); mOther->setPosition(170, 120); add(mPlayerBox); add(mNameField); add(mNameLabel); add(mNextHairColorButton); add(mPrevHairColorButton); add(mHairColorLabel); add(mHairColorNameLabel); add(mNextHairStyleButton); add(mPrevHairStyleButton); add(mHairStyleLabel); add(mHairStyleNameLabel); add(mActionButton); add(mRotateButton); if (serverVersion >= 9 && mMinLook < mMaxLook) { add(mNextLookButton); add(mPrevLookButton); add(mLookLabel); add(mLookNameLabel); } if (serverVersion >= 2) { add(mNextRaceButton); add(mPrevRaceButton); add(mRaceLabel); add(mRaceNameLabel); } add(mAttributesLeft); add(mCreateButton); add(mCancelButton); add(mMale); add(mFemale); add(mOther); center(); setVisible(true); mNameField->requestFocus(); updateHair(); if (serverVersion >= 2) updateRace(); if (serverVersion >= 9 && mMinLook < mMaxLook) updateLook(); updatePlayer(); addKeyListener(this); } CharCreateDialog::~CharCreateDialog() { delete mPlayer; mPlayer = nullptr; if (Net::getCharServerHandler()) Net::getCharServerHandler()->setCharCreateDialog(nullptr); } void CharCreateDialog::action(const gcn::ActionEvent &event) { const std::string id = event.getId(); if (id == "create") { if ( #ifdef MANASERV_SUPPORT Net::getNetworkType() == ServerInfo::MANASERV || #endif getName().length() >= 4) { // Attempt to create the character mCreateButton->setEnabled(false); std::vector<int> atts; for (size_t i = 0, sz = mAttributeSlider.size(); i < sz; i++) { atts.push_back(static_cast<int>( mAttributeSlider[i]->getValue())); } #ifdef MANASERV_SUPPORT int characterSlot = mSlot; // On Manaserv, the slots start at 1, so we offset them. if (Net::getNetworkType() == ServerInfo::MANASERV) ++characterSlot; #else const int characterSlot = mSlot; #endif Net::getCharServerHandler()->newCharacter(getName(), characterSlot, mFemale->isSelected(), mHairStyle, mHairColor, static_cast<unsigned char>(mRace), mLook, atts); } else { // TRANSLATORS: char creation error new OkDialog(_("Error"), // TRANSLATORS: char creation error _("Your name needs to be at least 4 characters."), DIALOG_ERROR, true, this); } } else if (id == "cancel") { scheduleDelete(); } else if (id == "nextcolor") { mHairColor ++; updateHair(); } else if (id == "prevcolor") { mHairColor --; updateHair(); } else if (id == "nextstyle") { mHairStyle ++; updateHair(); } else if (id == "prevstyle") { mHairStyle --; updateHair(); } else if (id == "nextrace") { mRace ++; updateRace(); } else if (id == "prevrace") { mRace --; updateRace(); } else if (id == "nextlook") { mLook ++; updateLook(); } else if (id == "prevlook") { mLook --; updateLook(); } else if (id == "statslider") { updateSliders(); } else if (id == "gender") { if (mMale->isSelected()) mPlayer->setGender(GENDER_MALE); else mPlayer->setGender(GENDER_FEMALE); } else if (id == "action") { mAction ++; if (mAction >= 5) mAction = 0; updatePlayer(); } else if (id == "rotate") { mDirection ++; if (mDirection >= 4) mDirection = 0; updatePlayer(); } } std::string CharCreateDialog::getName() const { std::string name = mNameField->getText(); trim(name); return name; } void CharCreateDialog::updateSliders() { for (size_t i = 0, sz = mAttributeSlider.size(); i < sz; i++) { // Update captions mAttributeValue[i]->setCaption( toString(static_cast<int>(mAttributeSlider[i]->getValue()))); mAttributeValue[i]->adjustSize(); } // Update distributed points const int pointsLeft = mMaxPoints - getDistributedPoints(); if (pointsLeft == 0) { // TRANSLATORS: char create dialog label mAttributesLeft->setCaption(_("Character stats OK")); mCreateButton->setEnabled(true); } else { mCreateButton->setEnabled(false); if (pointsLeft > 0) { mAttributesLeft->setCaption( // TRANSLATORS: char create dialog label strprintf(_("Please distribute %d points"), pointsLeft)); } else { mAttributesLeft->setCaption( // TRANSLATORS: char create dialog label strprintf(_("Please remove %d points"), -pointsLeft)); } } mAttributesLeft->adjustSize(); } void CharCreateDialog::unlock() { mCreateButton->setEnabled(true); } int CharCreateDialog::getDistributedPoints() const { int points = 0; for (size_t i = 0, sz = mAttributeSlider.size(); i < sz; i++) points += static_cast<int>(mAttributeSlider[i]->getValue()); return points; } void CharCreateDialog::setAttributes(const StringVect &labels, const int available, const int min, const int max) { mMaxPoints = available; for (unsigned i = 0; i < mAttributeLabel.size(); i++) { remove(mAttributeLabel[i]); delete mAttributeLabel[i]; mAttributeLabel[i] = nullptr; remove(mAttributeSlider[i]); delete mAttributeSlider[i]; mAttributeSlider[i] = nullptr; remove(mAttributeValue[i]); delete mAttributeValue[i]; mAttributeValue[i] = nullptr; } mAttributeLabel.resize(labels.size()); mAttributeSlider.resize(labels.size()); mAttributeValue.resize(labels.size()); const int w = 480; const int h = 350; const int y = 118 + 29; for (unsigned i = 0, sz = static_cast<unsigned>(labels.size()); i < sz; i++) { mAttributeLabel[i] = new Label(this, labels[i]); mAttributeLabel[i]->setWidth(70); mAttributeLabel[i]->setPosition(5, y + i * 24); mAttributeLabel[i]->adjustSize(); add(mAttributeLabel[i]); mAttributeSlider[i] = new Slider(min, max); mAttributeSlider[i]->setDimension(gcn::Rectangle(140, y + i * 24, 150, 12)); mAttributeSlider[i]->setActionEventId("statslider"); mAttributeSlider[i]->addActionListener(this); add(mAttributeSlider[i]); mAttributeValue[i] = new Label(this, toString(min)); mAttributeValue[i]->setPosition(295, y + i * 24); add(mAttributeValue[i]); } updateSliders(); setButtonsPosition(w, h); } void CharCreateDialog::setFixedGender(const bool fixed, const Gender gender) { if (gender == GENDER_FEMALE) { mFemale->setSelected(true); mMale->setSelected(false); mOther->setSelected(false); } else if (gender == GENDER_MALE) { mFemale->setSelected(false); mMale->setSelected(true); mOther->setSelected(false); } else { mFemale->setSelected(false); mMale->setSelected(false); mOther->setSelected(true); } mPlayer->setGender(gender); if (fixed) { mMale->setVisible(false); mFemale->setVisible(false); mOther->setVisible(false); } } void CharCreateDialog::updateHair() { if (mHairStyle <= 0) mHairStyle = Being::getNumOfHairstyles() - 1; else mHairStyle %= Being::getNumOfHairstyles(); if (mHairStyle < static_cast<signed>(minHairStyle) || mHairStyle > static_cast<signed>(maxHairStyle)) { mHairStyle = minHairStyle; } const ItemInfo &item = ItemDB::get(-mHairStyle); mHairStyleNameLabel->setCaption(item.getName()); mHairStyleNameLabel->resizeTo(150, 150); if (ColorDB::getHairSize()) mHairColor %= ColorDB::getHairSize(); else mHairColor = 0; if (mHairColor < 0) mHairColor += ColorDB::getHairSize(); if (mHairColor < static_cast<signed>(minHairColor) || mHairColor > static_cast<signed>(maxHairColor)) { mHairColor = minHairColor; } mHairColorNameLabel->setCaption(ColorDB::getHairColorName(mHairColor)); mHairColorNameLabel->resizeTo(150, 150); mPlayer->setSprite(Net::getCharServerHandler()->hairSprite(), mHairStyle * -1, item.getDyeColorsString(mHairColor)); } void CharCreateDialog::updateRace() { if (mRace < 0) mRace = Being::getNumOfRaces() - 1; else if (mRace >= Being::getNumOfRaces()) mRace = 0; updateLook(); } void CharCreateDialog::updateLook() { const ItemInfo &item = ItemDB::get(-100 - mRace); const int sz = item.getColorsSize(); if (sz > 0 && serverVersion >= 9) { if (mLook < 0) mLook = sz - 1; if (mLook > mMaxLook) mLook = mMinLook; if (mLook >= sz) mLook = mMinLook; } else { mLook = 0; } mPlayer->setSubtype(static_cast<uint16_t>(mRace), mLook); if (mRaceNameLabel) { mRaceNameLabel->setCaption(item.getName()); mRaceNameLabel->resizeTo(150, 150); } if (mLookNameLabel) { mLookNameLabel->setCaption(item.getColorName(mLook)); mLookNameLabel->resizeTo(150, 150); } } void CharCreateDialog::logic() { BLOCK_START("CharCreateDialog::logic") if (mPlayer) mPlayer->logic(); BLOCK_END("CharCreateDialog::logic") } void CharCreateDialog::updatePlayer() { if (mPlayer) { mPlayer->setDirection(directions[mDirection]); mPlayer->setAction(actions[mAction], 0); } } void CharCreateDialog::keyPressed(gcn::KeyEvent &keyEvent) { const int actionId = static_cast<KeyEvent*>(&keyEvent)->getActionId(); switch (actionId) { case Input::KEY_GUI_CANCEL: keyEvent.consume(); action(gcn::ActionEvent(mCancelButton, mCancelButton->getActionEventId())); break; default: break; } } void CharCreateDialog::setButtonsPosition(const int w, const int h) { if (mainGraphics->getHeight() < 480) { mCreateButton->setPosition(340, 150); mCancelButton->setPosition(340, 160 + mCreateButton->getHeight()); } else { mCancelButton->setPosition( w / 2, h - 5 - mCancelButton->getHeight()); mCreateButton->setPosition( mCancelButton->getX() - 5 - mCreateButton->getWidth(), h - 5 - mCancelButton->getHeight()); } mAttributesLeft->setPosition(15, 260 + 29); }