/* * Copyright 2007-2009 Ben Boeckel * * 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 3 of the License, or * (at your option) 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 . */ // Header include #include "Creature.h" // Sigencore includes #include "Containment.h" // Sigscript includes #include #include #include #include #include #include #include // Sigcore includes #include // Qt includes #include // C includes #include using namespace Sigcore; using namespace Sigmod; using namespace Sigscript; using namespace Sigencore; QMap Creature::m_expCache; Creature::Creature(SpeciesWrapper* species, const int level, Containment* containment, const bool suppressInitialization) : Config(containment), m_game(containment->game()), m_containment(containment), m_species(species), m_gender(Undecided), m_level(level), m_levelExp(calcLevelExperience(species->growth(), level)), m_id(QUuid::createUuid()) { for (int i = 0; i < (ST_SpecialDefense - ST_HP + 1); ++i) { m_dv[i] = -1; m_statExp[i] = -1; } for (int i = 0; i < (ST_SpecialDefense - ST_Attack + 1); ++i) m_stages[i] = 0; if (!suppressInitialization) completeData(); } Creature::~Creature() { // TODO } QUuid Creature::id() const { return m_id; } GameWrapper* Creature::game() const { return m_game; } bool Creature::setContainment(Containment* containment) { if (m_containment && m_containment->removeMember(this)) { m_containment = containment; return true; } return false; } Containment* Creature::containment() const { return m_containment; } SpeciesWrapper* Creature::species() const { return m_species; } void Creature::setName(const QString& name) { m_name = name; emit(nameChanged(m_name)); } QString Creature::name() const { return m_name; } bool Creature::setGender(const Gender gender) { if (!m_game->rules()->genderAllowed()) return false; const Fraction genderFactor = m_species->genderFactor(); switch (gender) { case Male: if (1 <= genderFactor) return false; break; case Female: if (genderFactor <= 0) return false; break; case Genderless: if (0 <= genderFactor) return false; break; default: return false; } m_gender = gender; emit(genderChanged(m_gender)); return true; } Creature::Gender Creature::gender() const { return m_gender; } bool Creature::setLevel(const int level) { if ((level <= 0) || (m_game->rules()->maxLevel() < level)) return false; m_level = level; m_levelExp = calcLevelExperience(m_species->growth(), m_level); emit(levelChanged(level)); emit(levelExperienceChanged(m_levelExp)); return true; } bool Creature::giveLevels(const int levels) { return setLevel(m_level + levels); } int Creature::level() const { return m_level; } int Creature::calcLevel(const Species::Style growth, const long long levelExp) { int level = -1; while (calcLevelExperience(growth, level + 1) < levelExp) ++level; return level; } bool Creature::setLevelExperience(const long long levelExp) { if (levelExp < 0) return false; const int level = calcLevel(m_species->growth(), levelExp); if ((level <= 0) || (m_game->rules()->maxLevel() < level)) return false; m_levelExp = levelExp; if (level != m_level) { m_level = level; emit(levelChanged(level)); } emit(levelExperienceChanged(m_levelExp)); return true; } bool Creature::giveLevelExperience(const long long levelExp) { return setLevelExperience(m_levelExp + levelExp); } long long Creature::levelExperience() const { return m_levelExp; } long long Creature::calcLevelExperience(const Species::Style growth, const int level) { if (m_expCache.contains(StyleLevel(growth, level))) return m_expCache[StyleLevel(growth, level)]; const long long square = level * level; const long long cube = square * level; long long exp = -1; switch (growth) { case Species::Fluctuating: if (level <= 15) exp = cube * ((24 + (level + 1) / 3) / 50.); else if (level <= 35) exp = cube * ((14 + level) / 50.); else if (level <= 100) exp = cube * ((32 + (level / 2)) / 50.); // TODO: better way for further growth? else if (level <= 102) exp = cube * (23 + level) / 75; else exp = 5 * cube / 3; break; case Species::Fading: exp = 6 * cube / 5 - 15 * square + 100 * level - 140; break; case Species::Slow: exp = 5 * cube / 4; break; case Species::Normal: exp = cube; break; case Species::Fast: exp = 4 * cube / 5; break; case Species::Erratic: { const double p[] = {0.000, 0.008, 0.014}; if (level <= 50) exp = cube * ((100 - level) / 50.); else if (level <= 68) exp = cube * ((150 - level) / 100.); else if (level <= 98) exp = cube * (1.274 - (level / 3) / 50. - p[level % 3]); else if (level <= 100) exp = cube * ((160 - level) / 100.); // TODO: better way for further growth? else exp = 3 * cube / 5; break; } default: break; } if (0 <= exp) m_expCache[StyleLevel(growth, level)] = exp; return exp; } bool Creature::setCurrentHp(const int hp) { if ((hp < 0) || (statValue(ST_HP) < hp)) return false; m_currentHp = hp; emit(currentHpChanged(m_currentHp)); if (!m_currentHp) emit(knockedOut()); return true; } bool Creature::changeCurrentHp(const int hp) { return setCurrentHp(m_currentHp + hp); } int Creature::currentHp() const { return m_currentHp; } bool Creature::setDv(const Stat stat, const int dv) { if ((stat == ST_SpecialDefense) && !(m_game->rules()->specialSplit() && m_game->rules()->specialDVSplit())) return false; m_dv[stat] = dv; emit(dvChanged(stat, dv)); return true; } int Creature::dv(const Stat stat) const { if ((stat == ST_Accuracy) || (stat == ST_Evasion)) return false; Stat actualStat = stat; if (stat == ST_SpecialDefense) { if (m_game->rules()->specialSplit() && m_game->rules()->specialDVSplit()) actualStat = ST_Special; else return false; } int dv = 0; if (valueOfType(QString("stat-dv-%1").arg((m_game->rules()->specialSplit() ? StatGSCStr : StatRBYStr)[actualStat]), &dv) && (dv < (m_game->rules()->specialDVSplit() ? 32 : 16))) return dv; return m_dv[actualStat]; } bool Creature::setStatExperience(const Stat stat, const long long statExp) { if ((stat == ST_Accuracy) || (stat == ST_Evasion) || ((stat == ST_SpecialDefense) && !m_game->rules()->specialSplit())) return false; if (0 < m_game->rules()->maxTotalEV()) { int evSum = statExp; for (int i = ST_HP; i <= (m_game->rules()->specialSplit() ? ST_Special : ST_SpecialDefense); ++i) { if (i != stat) evSum += m_statExp[i]; } if ((m_game->rules()->maxEVPerStat() < statExp) || (m_game->rules()->maxTotalEV() < evSum)) return false; } m_statExp[stat] = statExp; emit(statExperienceChanged(stat, m_statExp[stat])); return true; } bool Creature::giveStatExperience(const Stat stat, const long long statExp) { return setStatExperience(stat, m_statExp[stat] + statExp); } long long Creature::statExperience(const Stat stat) const { long long exp = m_statExp[stat]; valueOfType(QString("stat-experience-%1").arg((m_game->rules()->specialSplit() ? StatGSCStr : StatRBYStr)[stat]), &exp); return exp; } int Creature::statValue(const Stat stat) const { Fraction multiplier; if (stat != ST_HP) { foreach (NatureWrapper* nature, m_natures) multiplier *= nature->stat(stat); } return calcStat(m_game, stat, m_level, m_species->baseStat(stat), dv(stat), statExperience(stat), multiplier); } int Creature::calcStat(GameWrapper* game, const Stat stat, const int level, const int baseStat, const int dv, const int statExp, const Fraction& multiplier) { int statValue = statExp; if (!game->rules()->maxTotalEV() && statValue) statValue = sqrt(statValue - 1) + 1; statValue >>= 2; statValue += baseStat << 1; if (game->rules()->specialDVSplit()) statValue += dv << 1; else statValue += dv; statValue *= double(level) / game->rules()->maxLevel(); if (stat == ST_HP) statValue += 10 + level; else statValue += 5; return statValue * multiplier; } void Creature::recalcStats() { // FIXME? recalcStat(ST_HP); recalcStat(ST_Attack); recalcStat(ST_Defense); recalcStat(ST_Speed); if (m_game->rules()->specialSplit()) { recalcStat(ST_SpecialAttack); recalcStat(ST_SpecialDefense); } else recalcStat(ST_Special); } void Creature::recalcStat(const Stat stat) { // FIXME? emit(statValueChanged(stat, statValue(stat))); } bool Creature::addAbility(AbilityWrapper* ability) { if ((m_abilities.size() < m_game->rules()->maxAbilities()) && !m_abilities.contains(ability)) { m_abilities.append(ability); emit(abilityAdded(ability)); return true; } return false; } bool Creature::removeAbility(AbilityWrapper* ability) { if (m_abilities.contains(ability)) { m_abilities.removeOne(ability); emit(abilityRemoved(ability)); return true; } return false; } QList Creature::abilities() const { return m_abilities; } bool Creature::hasAbility(AbilityWrapper* ability) const { return m_abilities.contains(ability); } bool Creature::addItems(ItemWrapper* item, const int count, const bool allOrNothing) { const int addWeight = count * item->weight(); int end = 0; if (count < 0) { const int diffWeight = itemWeight() + addWeight; const int diffCount = qMax(0, itemCount() + count); if ((0 < diffWeight) && (0 < diffCount)) end = count; else if (!allOrNothing) { const int weightUnderflow = qAbs(diffWeight); const int countUnderflow = diffCount; end = count + qMax((weightUnderflow / item->weight()) - !!(weightUnderflow % item->weight()), countUnderflow); } } else if (0 < count) { const int diffWeight = m_species->weight() - (itemWeight() + addWeight); const int diffCount = m_game->rules()->maxHeldItems() - (itemCount() + count); if ((0 < diffWeight) && (0 < diffCount)) end = count; else if (!allOrNothing) { const int weightOverflow = qAbs(diffWeight); const int countOverflow = diffCount; end = count - qMax((weightOverflow / item->weight()) - !!(countOverflow % item->weight()), countOverflow); } } end = qBound(qMin(0, count), end, qMax(count, 0)); if (end) { m_items[item] += end; if (!m_items[item]) m_items.remove(item); emit(itemsAdded(item, end)); } return count - end; } QList Creature::items() const { return m_items.keys(); } int Creature::itemWeight() const { int weight = 0; QList items = m_items.keys(); foreach (ItemWrapper* curItem, items) weight += curItem->weight() * m_items[curItem]; return weight; } int Creature::itemCount() const { int count = 0; QList items = m_items.keys(); foreach (ItemWrapper* curItem, items) count += m_items[curItem]; return count; } int Creature::hasItem(ItemWrapper* item) const { return m_items.count(item); } bool Creature::addMove(MoveWrapper* move) { bool canLearn = false; for (int i = 0; !canLearn && (i < m_species->moveCount()); ++i) { if (m_species->move(i)->move() == move) canLearn = true; } if (canLearn && (m_moves.size() < m_game->rules()->maxMoves()) && !m_moves.contains(move)) { m_moves.append(move); emit(moveAdded(move)); return true; } return false; } bool Creature::removeMove(MoveWrapper* move) { if (m_moves.contains(move) && (1 < m_moves.size())) { m_moves.removeOne(move); emit(moveRemoved(move)); return true; } return false; } QList Creature::moves() const { return m_moves; } bool Creature::hasMove(MoveWrapper* move) const { return m_moves.contains(move); } bool Creature::addNature(NatureWrapper* nature) { if ((m_natures.size() < m_game->rules()->maxNatures()) && !m_natures.contains(nature)) { m_natures.append(nature); emit(natureAdded(nature)); return true; } return false; } bool Creature::removeNature(NatureWrapper* nature) { if (m_natures.contains(nature)) { m_natures.removeOne(nature); emit(natureRemoved(nature)); return true; } return false; } QList Creature::natures() const { return m_natures; } bool Creature::hasNature(NatureWrapper* nature) const { return m_natures.contains(nature); } bool Creature::addStatus(StatusWrapper* status) { if (!m_status.contains(status)) { m_status.append(status); emit(statusAdded(status)); return true; } return false; } bool Creature::removeStatus(StatusWrapper* status) { if (m_status.contains(status)) { m_status.removeOne(status); emit(statusRemoved(status)); return true; } return false; } QList Creature::status() const { return m_status; } bool Creature::hasStatus(StatusWrapper* status) const { return m_status.contains(status); } void Creature::completeData() { // TODO if (!m_name.isEmpty()) m_name = m_species->name(); if (m_gender == Undecided) { if (!m_game->rules()->genderAllowed() || (m_species->genderFactor() < 0)) m_gender = Genderless; else m_gender = (m_species->genderFactor().poll() ? Female : Male); } completeStats(); completeAbilities(); completeItems(); completeMoves(); completeNatures(); makeConnections(); emit(initialized()); } void Creature::completeStats() { if (m_game->rules()->specialSplit() && m_game->rules()->specialDVSplit()) { for (int i = ST_HP; i <= ST_SpecialDefense; ++i) m_dv[i] = qrand() & 31; } else { for (int i = ST_Attack; i <= ST_Special; ++i) m_dv[i] = qrand() & 15; m_dv[ST_HP] = ((m_dv[ST_Attack] & 1) << 3) + ((m_dv[ST_Defense] & 1) << 2) + ((m_dv[ST_Speed] & 1) << 1) + (m_dv[ST_Special] & 1); } } void Creature::completeAbilities() { Hat abilityHat = m_species->abilityHat(); while ((m_abilities.size() < game()->rules()->maxAbilities()) && abilityHat.count()) addAbility(abilityHat.takeAndClear()); } void Creature::completeItems() { Hat itemHat = m_species->itemHat(); int i = 0; while ((i < m_game->rules()->maxHeldItems()) && itemHat.count()) { if (m_species->itemChance().poll()) { ItemWrapper* item = itemHat.pick(); if (addItems(item, 1)) ++i; else itemHat.setCount(item, 0); } else ++i; } } void Creature::completeMoves() { QMap moves; for (int i = 0; i < m_species->moveCount(); ++i) { SpeciesMoveWrapper* move = m_species->move(i); bool isWild = false; const int level = (valueOfType("wild", &isWild) && isWild) ? move->wild() : move->level(); if ((0 <= level) && (level <= m_level)) moves[level] = move->move(); } // TODO: give the moves to the creature } void Creature::completeNatures() { Hat natureHat = game()->natureHat(); while ((m_natures.size() < m_game->rules()->maxNatures()) && natureHat.count()) addNature(natureHat.takeAndClear()); } void Creature::makeConnections() { connect(this, SIGNAL(levelChanged(int)), SLOT(recalcStats())); connect(this, SIGNAL(natureAdded(NatureWrapper*)), SLOT(recalcStats())); connect(this, SIGNAL(natureRemoved(NatureWrapper*)), SLOT(recalcStats())); connect(this, SIGNAL(dvChanged(Stat, int)), SLOT(recalcStat(Stat))); connect(this, SIGNAL(statExperienceChanged(Stat, long long)), SLOT(recalcStat(Stat))); }