1353 lines
38 KiB
C++
1353 lines
38 KiB
C++
/*
|
|
* pc.cpp
|
|
* BoE
|
|
*
|
|
* Created by Celtic Minstrel on 24/04/09.
|
|
*
|
|
*/
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
#include <map>
|
|
#include <set>
|
|
#include <sstream>
|
|
#include <iostream>
|
|
#include <algorithm>
|
|
#include <utility>
|
|
|
|
#include "universe.hpp"
|
|
#include "oldstructs.hpp"
|
|
#include "mathutil.hpp"
|
|
#include "fileio/fileio.hpp"
|
|
#include "sounds.hpp"
|
|
|
|
extern short skill_bonus[21];
|
|
// A nice convenient bitset with just the low 30 bits set, for initializing spells
|
|
const uint32_t cPlayer::basic_spells = std::numeric_limits<uint32_t>::max() >> 2;
|
|
|
|
void cPlayer::import_legacy(legacy::pc_record_type old){
|
|
main_status = (eMainStatus) old.main_status;
|
|
name = old.name;
|
|
for(short i = 0; i < 19; i++) {
|
|
eSkill skill = eSkill(i);
|
|
skills[skill] = old.skills[i];
|
|
}
|
|
max_health = old.max_health;
|
|
cur_health = old.cur_health;
|
|
max_sp = old.max_sp;
|
|
cur_sp = old.cur_sp;
|
|
experience = old.experience;
|
|
skill_pts = old.skill_pts;
|
|
level = old.level;
|
|
// TODO: Why are advan and exp_adj commented out?
|
|
for(short i = 0; i < 15; i++){
|
|
status[(eStatus) i] = old.status[i];
|
|
eTrait trait = eTrait(i);
|
|
traits[trait] = old.traits[i];
|
|
}
|
|
for(short i = 0; i < 24; i++){
|
|
items[i].import_legacy(old.items[i]);
|
|
equip[i] = old.equip[i];
|
|
}
|
|
for(short i = 0; i < 62; i++){
|
|
priest_spells[i] = old.priest_spells[i];
|
|
mage_spells[i] = old.mage_spells[i];
|
|
}
|
|
which_graphic = old.which_graphic;
|
|
weap_poisoned.slot = old.weap_poisoned;
|
|
race = (eRace) old.race;
|
|
direction = eDirection(old.direction);
|
|
}
|
|
|
|
short cPlayer::get_tnl(){
|
|
short tnl = 100,store_per = 100;
|
|
// Omitting a race from this list gives it a value of 0, thanks to the defaulting implementation of operator[]
|
|
static std::map<const eRace, const int> rp = {{eRace::NEPHIL,12},{eRace::SLITH,20},{eRace::VAHNATAI,18}};
|
|
static std::map<const eTrait, const short> ap = {
|
|
{eTrait::TOUGHNESS,10}, {eTrait::MAGICALLY_APT,20}, {eTrait::AMBIDEXTROUS,8}, {eTrait::NIMBLE,10}, {eTrait::CAVE_LORE,4},
|
|
{eTrait::WOODSMAN,6}, {eTrait::GOOD_CONST,10}, {eTrait::HIGHLY_ALERT,7}, {eTrait::STRENGTH,12}, {eTrait::RECUPERATION,15},
|
|
{eTrait::SLUGGISH,-10}, {eTrait::MAGICALLY_INEPT,-8}, {eTrait::FRAIL,-8}, {eTrait::CHRONIC_DISEASE,-20}, {eTrait::BAD_BACK,-8},
|
|
{eTrait::PACIFIST,-40}, {eTrait::ANAMA,-10},
|
|
};
|
|
|
|
tnl = (tnl * (100 + rp[race])) / 100;
|
|
for(int i = 0; i < 17; i++) {
|
|
eTrait trait = eTrait(i);
|
|
if(traits[trait])
|
|
store_per = store_per + ap[trait];
|
|
}
|
|
|
|
tnl = (tnl * store_per) / 100;
|
|
|
|
return max(tnl, 10);
|
|
}
|
|
|
|
void cPlayer::avatar() {
|
|
heal(300);
|
|
cure(8);
|
|
status[eStatus::BLESS_CURSE] = 8;
|
|
status[eStatus::HASTE_SLOW] = 8;
|
|
status[eStatus::INVULNERABLE] = 3;
|
|
status[eStatus::MAGIC_RESISTANCE] = 8;
|
|
status[eStatus::WEBS] = 0;
|
|
status[eStatus::DISEASE] = 0;
|
|
if(status[eStatus::DUMB] > 0)
|
|
status[eStatus::DUMB] = 0;
|
|
status[eStatus::MARTYRS_SHIELD] = 8;
|
|
}
|
|
|
|
void cPlayer::drain_sp(int drain) {
|
|
int mu = skills[eSkill::MAGE_SPELLS];
|
|
int cl = skills[eSkill::PRIEST_SPELLS];
|
|
if(drain > 0) {
|
|
if(mu > 0 && cur_sp > 4)
|
|
drain = min(cur_sp, drain / 3);
|
|
else if(cl > 0 && cur_sp > 10)
|
|
drain = min(cur_sp, drain / 2);
|
|
cur_sp -= drain;
|
|
if(cur_sp < 0) cur_sp = 0;
|
|
}
|
|
}
|
|
|
|
void cPlayer::scare(int) {
|
|
// TODO: Not implemented
|
|
}
|
|
|
|
void cPlayer::void_sanctuary() {
|
|
if(status[eStatus::INVISIBLE] > 0 && print_result)
|
|
print_result("You become visible!");
|
|
iLiving::void_sanctuary();
|
|
}
|
|
|
|
void cPlayer::heal(int amt) {
|
|
if(!is_alive()) return;
|
|
if(cur_health >= max_health) return;
|
|
cur_health += amt;
|
|
if(cur_health > max_health)
|
|
cur_health = max_health;
|
|
if(cur_health < 0)
|
|
cur_health = 0;
|
|
}
|
|
|
|
void cPlayer::cure(int amt) {
|
|
if(!is_alive()) return;
|
|
if(status[eStatus::POISON] <= amt)
|
|
status[eStatus::POISON] = 0;
|
|
else status[eStatus::POISON] -= amt;
|
|
one_sound(51);
|
|
}
|
|
|
|
// if how_much < 0, bless
|
|
void cPlayer::curse(int how_much) {
|
|
if(!is_alive()) return;
|
|
if(how_much > 0)
|
|
how_much -= get_prot_level(eItemAbil::STATUS_PROTECTION,int(eStatus::BLESS_CURSE)) / 2;
|
|
apply_status(eStatus::BLESS_CURSE, -how_much);
|
|
if(print_result) {
|
|
if(how_much < 0)
|
|
print_result(" " + name + " blessed.");
|
|
else if(how_much > 0)
|
|
print_result(" " + name + " cursed.");
|
|
}
|
|
if(give_help) {
|
|
if(how_much > 0)
|
|
give_help(59,0);
|
|
else if(how_much > 0)
|
|
give_help(34,0);
|
|
}
|
|
}
|
|
|
|
void cPlayer::dumbfound(int how_much) {
|
|
if(!is_alive()) return;
|
|
short r1 = get_ran(1,0,90);
|
|
if(has_abil_equip(eItemAbil::WILL)) {
|
|
if(print_result)
|
|
print_result(" Ring of Will glows.");
|
|
r1 -= 10;
|
|
}
|
|
how_much -= get_prot_level(eItemAbil::STATUS_PROTECTION,int(eStatus::DUMB)) / 4;
|
|
if(r1 < level)
|
|
how_much -= 2;
|
|
if(how_much <= 0) {
|
|
if(print_result)
|
|
print_result(" " + name + " saved.");
|
|
return;
|
|
}
|
|
apply_status(eStatus::DUMB, how_much);
|
|
if(print_result)
|
|
print_result(" " + name + " dumbfounded.");
|
|
one_sound(67);
|
|
if(give_help)
|
|
give_help(28,0);
|
|
}
|
|
|
|
void cPlayer::disease(int how_much) {
|
|
if(!is_alive()) return;
|
|
short r1 = get_ran(1,1,100);
|
|
if(r1 < level * 2)
|
|
how_much -= 2;
|
|
if(how_much <= 0) {
|
|
if(print_result)
|
|
print_result(" " + name + " saved.");
|
|
return;
|
|
}
|
|
how_much -= get_prot_level(eItemAbil::STATUS_PROTECTION,int(eStatus::DISEASE)) / 2;
|
|
if(traits[eTrait::FRAIL] && how_much > 1)
|
|
how_much++;
|
|
if(traits[eTrait::FRAIL] && how_much == 1 && get_ran(1,0,1) == 0)
|
|
how_much++;
|
|
apply_status(eStatus::DISEASE, how_much);
|
|
if(print_result)
|
|
print_result(" " + name + " diseased.");
|
|
one_sound(66);
|
|
if(give_help)
|
|
give_help(29,0);
|
|
}
|
|
|
|
void cPlayer::sleep(eStatus what_type,int how_much,int adjust) {
|
|
if(what_type == eStatus::CHARM) return;
|
|
short level = 0;
|
|
if(!is_alive()) return;
|
|
if(how_much == 0) return;
|
|
|
|
if((what_type == eStatus::ASLEEP) &&
|
|
(race == eRace::UNDEAD || race == eRace::SKELETAL || race == eRace::SLIME ||
|
|
race == eRace::STONE || race == eRace::PLANT))
|
|
return;
|
|
if(what_type == eStatus::ASLEEP || what_type == eStatus::PARALYZED) {
|
|
how_much -= get_prot_level(eItemAbil::WILL) / 2;
|
|
level = get_prot_level(eItemAbil::FREE_ACTION);
|
|
how_much -= (what_type == eStatus::ASLEEP) ? level : level * 300;
|
|
how_much -= get_prot_level(eItemAbil::STATUS_PROTECTION,int(what_type)) / 4;
|
|
} else if(what_type == eStatus::FORCECAGE)
|
|
how_much -= 1 + get_prot_level(eItemAbil::STATUS_PROTECTION,int(what_type)) / 8;
|
|
|
|
short r1 = get_ran(1,1,100) + adjust;
|
|
if(what_type == eStatus::FORCECAGE)
|
|
r1 -= stat_adj(eSkill::MAGE_LORE);
|
|
if(r1 < 30 + level * 2)
|
|
how_much = -1;
|
|
if(what_type == eStatus::ASLEEP && (traits[eTrait::HIGHLY_ALERT] || status[eStatus::ASLEEP] < 0))
|
|
how_much = -1;
|
|
if(how_much <= 0) {
|
|
if(print_result)
|
|
print_result(" " + name + " resisted.");
|
|
return;
|
|
}
|
|
status[what_type] = how_much;
|
|
if(print_result) {
|
|
if(what_type == eStatus::ASLEEP)
|
|
print_result(" " + name + " falls asleep.");
|
|
else if(what_type == eStatus::FORCECAGE)
|
|
print_result(" " + name + " is trapped!");
|
|
else print_result(" " + name + " paralyzed.");
|
|
}
|
|
if(what_type == eStatus::ASLEEP)
|
|
play_sound(96);
|
|
else play_sound(90);
|
|
if(what_type != eStatus::FORCECAGE)
|
|
ap = 0;
|
|
if(give_help) {
|
|
if(what_type == eStatus::ASLEEP)
|
|
give_help(30,0);
|
|
else if(what_type == eStatus::PARALYZED)
|
|
give_help(32,0);
|
|
else if(what_type == eStatus::FORCECAGE)
|
|
give_help(46,0);
|
|
}
|
|
}
|
|
|
|
// if how_much < 0, haste
|
|
void cPlayer::slow(int how_much) {
|
|
if(!is_alive()) return;
|
|
if(how_much > 0)
|
|
how_much -= get_prot_level(eItemAbil::STATUS_PROTECTION,int(eStatus::HASTE_SLOW)) / 2;
|
|
apply_status(eStatus::HASTE_SLOW, -how_much);
|
|
if(print_result) {
|
|
if(how_much < 0)
|
|
print_result(" " + name + " hasted.");
|
|
else if(how_much > 0)
|
|
print_result(" " + name + " slowed.");
|
|
}
|
|
if(give_help)
|
|
give_help(35,0);
|
|
}
|
|
|
|
void cPlayer::web(int how_much) {
|
|
if(!is_alive()) return;
|
|
if(how_much > 0)
|
|
how_much -= get_prot_level(eItemAbil::STATUS_PROTECTION,int(eStatus::WEBS)) / 2;
|
|
apply_status(eStatus::WEBS, how_much);
|
|
if(print_result)
|
|
print_result(" " + name + " webbed.");
|
|
one_sound(17);
|
|
give_help(31,0);
|
|
}
|
|
|
|
void cPlayer::acid(int how_much) {
|
|
if(!is_alive()) return;
|
|
if(has_abil_equip(eItemAbil::STATUS_PROTECTION,int(eStatus::ACID))) {
|
|
if(print_result)
|
|
print_result(" " + name + " resists acid.");
|
|
return;
|
|
}
|
|
status[eStatus::ACID] += how_much;
|
|
if(print_result)
|
|
print_result(" " + name + " covered with acid!");
|
|
one_sound(42);
|
|
}
|
|
|
|
void cPlayer::restore_sp(int amt) {
|
|
if(!is_alive()) return;
|
|
if(cur_sp >= max_sp) return;
|
|
cur_sp += amt;
|
|
if(cur_sp > max_sp)
|
|
cur_sp = max_sp;
|
|
if(cur_sp < 0)
|
|
cur_sp = 0;
|
|
}
|
|
|
|
void cPlayer::poison(int how_much) {
|
|
if(!is_alive()) return;
|
|
how_much -= get_prot_level(eItemAbil::STATUS_PROTECTION,int(eStatus::POISON)) / 2;
|
|
how_much -= get_prot_level(eItemAbil::FULL_PROTECTION) / 3;
|
|
|
|
if(traits[eTrait::FRAIL] && how_much > 1)
|
|
how_much++;
|
|
if(traits[eTrait::FRAIL] && how_much == 1 && get_ran(1,0,1) == 0)
|
|
how_much++;
|
|
|
|
if(how_much > 0) {
|
|
apply_status(eStatus::POISON, how_much);
|
|
if(print_result)
|
|
print_result(" " + name + " poisoned.");
|
|
one_sound(17);
|
|
give_help(33,0);
|
|
}
|
|
}
|
|
|
|
short cPlayer::stat_adj(eSkill which) const {
|
|
// This is one place where we use the base skill level instead of the adjusted skill level
|
|
// Using the adjusted skill level here would alter the original mechanics of stat-boosting items
|
|
short tr = skill_bonus[skills[which]];
|
|
if(which == eSkill::INTELLIGENCE) {
|
|
if(traits[eTrait::MAGICALLY_APT])
|
|
tr++;
|
|
}
|
|
if(which == eSkill::STRENGTH) {
|
|
if(traits[eTrait::STRENGTH])
|
|
tr++;
|
|
if(race == eRace::VAHNATAI)
|
|
tr -= 2;
|
|
}
|
|
// TODO: Use ability strength?
|
|
if(has_abil_equip(eItemAbil::BOOST_STAT,int(which)))
|
|
tr++;
|
|
return tr;
|
|
}
|
|
|
|
bool cPlayer::is_alive() const {
|
|
return main_status == eMainStatus::ALIVE;
|
|
}
|
|
|
|
bool cPlayer::is_friendly() const {
|
|
// Currently players can't be charmed, but that might change in the future
|
|
if(status[eStatus::CHARM] > 0)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool cPlayer::is_friendly(const iLiving& other) const {
|
|
if(status[eStatus::CHARM] > 0) {
|
|
if(other.is_friendly()) return false;
|
|
// TODO: If charmed players becomes a thing, they should match the attitude (A or B) of whoever charmed them
|
|
if(const cCreature* monst = dynamic_cast<const cCreature*>(&other))
|
|
return monst->attitude == eAttitude::HOSTILE_A;
|
|
// If we get here, the other is also a charmed player.
|
|
return true;
|
|
}
|
|
return other.is_friendly();
|
|
}
|
|
|
|
bool cPlayer::is_shielded() const {
|
|
if(status[eStatus::MARTYRS_SHIELD] > 0)
|
|
return true;
|
|
if(get_prot_level(eItemAbil::MARTYRS_SHIELD) > 0)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
int cPlayer::get_shared_dmg(int base) const {
|
|
int martyr1 = status[eStatus::MARTYRS_SHIELD];
|
|
int martyr2 = get_prot_level(eItemAbil::MARTYRS_SHIELD);
|
|
if(martyr1 + martyr2 <= 0) return 0;
|
|
if(get_ran(1,1,20) < martyr2) return base + max(1, martyr2 / 5);
|
|
return base;
|
|
}
|
|
|
|
location cPlayer::get_loc() const {
|
|
if(party && (combat_pos.x < 0 || combat_pos.y < 0))
|
|
return party->get_loc();
|
|
return combat_pos;
|
|
}
|
|
|
|
int cPlayer::get_health() const {
|
|
return cur_health;
|
|
}
|
|
|
|
int cPlayer::get_magic() const {
|
|
return cur_sp;
|
|
}
|
|
|
|
int cPlayer::get_level() const {
|
|
return level;
|
|
}
|
|
|
|
void cPlayer::sort_items() {
|
|
using it = eItemType;
|
|
static std::map<eItemType, const short> item_priority = {
|
|
{it::NO_ITEM, 20}, {it::ONE_HANDED, 8}, {it::TWO_HANDED, 8}, {it::GOLD, 20}, {it::BOW, 9},
|
|
{it::ARROW, 9}, {it::THROWN_MISSILE, 3}, {it::POTION, 2}, {it::SCROLL, 1}, {it::WAND, 0},
|
|
{it::TOOL, 7}, {it::FOOD, 20}, {it::SHIELD, 10}, {it::ARMOR, 10}, {it::HELM, 10},
|
|
{it::GLOVES, 10}, {it::SHIELD_2, 10}, {it::BOOTS, 10}, {it::RING, 5}, {it::NECKLACE, 6},
|
|
{it::WEAPON_POISON, 4}, {it::NON_USE_OBJECT, 11}, {it::PANTS, 12}, {it::CROSSBOW, 9}, {it::BOLTS, 9},
|
|
{it::MISSILE_NO_AMMO, 9}, {it::QUEST, 20}, {it::SPECIAL, 20}
|
|
};
|
|
bool no_swaps = false;
|
|
|
|
while(!no_swaps) {
|
|
no_swaps = true;
|
|
for(int i = 0; i < 23; i++)
|
|
if(item_priority[items[i + 1].variety] <
|
|
item_priority[items[i].variety]) {
|
|
no_swaps = false;
|
|
std::swap(items[i + 1], items[i]);
|
|
bool temp_equip = equip[i];
|
|
equip[i] = equip[i + 1];
|
|
equip[i + 1] = temp_equip;
|
|
if(weap_poisoned.slot == i + 1)
|
|
weap_poisoned.slot--;
|
|
else if(weap_poisoned.slot == i)
|
|
weap_poisoned.slot++;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool cPlayer::give_item(cItem item, int flags) {
|
|
if(main_status != eMainStatus::ALIVE)
|
|
return false;
|
|
|
|
bool do_print = flags & GIVE_DO_PRINT;
|
|
bool allow_overload = flags & GIVE_ALLOW_OVERLOAD;
|
|
int equip_type = flags & GIVE_EQUIP_FORCE;
|
|
|
|
if(item.variety == eItemType::NO_ITEM)
|
|
return true;
|
|
if(item.variety == eItemType::GOLD) {
|
|
if(!party) return false;
|
|
party->gold += item.item_level;
|
|
if(do_print && print_result)
|
|
print_result("You get some gold.");
|
|
return true;
|
|
}
|
|
if(item.variety == eItemType::FOOD) {
|
|
if(!party) return false;
|
|
party->food += item.item_level;
|
|
if(do_print && print_result)
|
|
print_result("You get some food.");
|
|
return true;
|
|
}
|
|
if(item.variety == eItemType::SPECIAL) {
|
|
if(!party) return false;
|
|
party->spec_items.insert(item.item_level);
|
|
if(do_print && print_result)
|
|
print_result("You get a special item.");
|
|
return true;
|
|
}
|
|
if(item.variety == eItemType::QUEST) {
|
|
if(!party) return false;
|
|
party->active_quests[item.item_level] = cJob(party->calc_day());
|
|
if(do_print && print_result)
|
|
print_result("You get a quest.");
|
|
return true;
|
|
}
|
|
if(!allow_overload && item.item_weight() > free_weight()) {
|
|
if(do_print && print_result) {
|
|
//beep(); // TODO: This is a game event, so it should have a game sound, not a system alert.
|
|
print_result("Item too heavy to carry.");
|
|
}
|
|
return false;
|
|
}
|
|
cInvenSlot free_space = has_space();
|
|
if(!free_space || main_status != eMainStatus::ALIVE)
|
|
return false;
|
|
else {
|
|
item.property = false;
|
|
item.contained = false;
|
|
item.held = false;
|
|
*free_space = item;
|
|
|
|
if(do_print && print_result) {
|
|
std::ostringstream announce;
|
|
announce << " " << name << " gets ";
|
|
if(!item.ident)
|
|
announce << item.name;
|
|
else announce << item.full_name;
|
|
announce << '.';
|
|
print_result(announce.str());
|
|
}
|
|
|
|
if(equip_type != 0 && (*item.variety).equip_count) {
|
|
if(!equip_item(free_space.slot, false) && equip_type != GIVE_EQUIP_SOFT) {
|
|
eItemCat exclude = (*item.variety).exclusion;
|
|
int rem1 = items.size(), rem2 = items.size();
|
|
for(int i = 0; i < items.size(); i++) {
|
|
if(i == free_space.slot) continue;
|
|
if(!equip[i]) continue;
|
|
eItemCat check_exclude = (*items[i].variety).exclusion;
|
|
if(exclude != check_exclude) continue;
|
|
if(exclude == eItemCat::MISC && item.variety != items[i].variety)
|
|
continue;
|
|
if(exclude == eItemCat::HANDS) {
|
|
if(rem1 == items.size()) {
|
|
if(item.variety == eItemType::ONE_HANDED || item.variety == eItemType::TWO_HANDED || rem2 < items.size())
|
|
rem1 = i;
|
|
if(rem1 < items.size()) continue;
|
|
}
|
|
if(rem2 == items.size()) {
|
|
if(item.variety == eItemType::SHIELD || item.variety == eItemType::SHIELD_2 || rem1 < items.size())
|
|
rem2 = i;
|
|
}
|
|
} else if(rem1 < items.size())
|
|
rem1 = i;
|
|
}
|
|
bool can_rem1 = rem1 < items.size() && (!items[rem1].cursed || equip_type == GIVE_EQUIP_FORCE);
|
|
bool can_rem2 = rem2 < items.size() && (!items[rem2].cursed || equip_type == GIVE_EQUIP_FORCE);
|
|
if(exclude == eItemCat::HANDS) {
|
|
if((*item.variety).num_hands == 2 && can_rem1 && can_rem2) {
|
|
equip[rem1] = false;
|
|
equip[rem2] = false;
|
|
} else if((*item.variety).num_hands == 1) {
|
|
if(can_rem1) equip[rem1] = false;
|
|
else if(can_rem2) equip[rem2] = false;
|
|
}
|
|
if((rem1 == weap_poisoned.slot && !equip[rem1]) || (rem2 == weap_poisoned.slot && !equip[rem2])) {
|
|
status[eStatus::POISONED_WEAPON] = 0;
|
|
weap_poisoned.clear();
|
|
}
|
|
} else if(can_rem1)
|
|
equip[rem1] = false;
|
|
equip_item(free_space.slot, false);
|
|
}
|
|
}
|
|
|
|
combine_things();
|
|
sort_items();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool cPlayer::equip_item(int which_item, bool do_print) {
|
|
const cItem& item = items[which_item];
|
|
if((*item.variety).equip_count == 0) {
|
|
if(do_print && print_result)
|
|
print_result("Equip: Can't equip this item.");
|
|
return false;
|
|
}
|
|
unsigned short num_this_type = 0, hands_occupied = 0;
|
|
for(int i = 0; i < items.size(); i++)
|
|
if(equip[i]) {
|
|
if(items[i].variety == item.variety)
|
|
num_this_type++;
|
|
hands_occupied += (*items[i].variety).num_hands;
|
|
}
|
|
|
|
eItemCat equip_item_type = (*item.variety).exclusion;
|
|
// Now if missile is already equipped, no more missiles
|
|
if(equip_item_type == eItemCat::MISSILE_AMMO || equip_item_type == eItemCat::MISSILE_WEAPON) {
|
|
for(int i = 0; i < items.size(); i++)
|
|
if(equip[i] && (*items[i].variety).exclusion == equip_item_type) {
|
|
if(do_print && print_result) {
|
|
print_result("Equip: You have something of this type");
|
|
print_result(" equipped.");
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
size_t hands_free = 2 - hands_occupied;
|
|
if(hands_free < (*item.variety).num_hands) {
|
|
if(do_print && print_result)
|
|
print_result("Equip: Not enough free hands");
|
|
return false;
|
|
} else if((*item.variety).equip_count <= num_this_type) {
|
|
if(do_print && print_result)
|
|
print_result("Equip: Can't equip another");
|
|
return false;
|
|
}
|
|
equip[which_item] = true;
|
|
if(do_print && print_result)
|
|
print_result("Equip: OK");
|
|
return true;
|
|
}
|
|
|
|
bool cPlayer::unequip_item(int which_item, bool do_print) {
|
|
if(!equip[which_item]) {
|
|
if(do_print && print_result)
|
|
print_result("Equip: Not equipped");
|
|
return false;
|
|
}
|
|
if(items[which_item].cursed) {
|
|
if(do_print && print_result)
|
|
print_result("Equip: Item is cursed.");
|
|
return false;
|
|
}
|
|
equip[which_item] = false;
|
|
if(do_print && print_result)
|
|
print_result("Equip: Unequipped");
|
|
if(weap_poisoned.slot == which_item && status[eStatus::POISONED_WEAPON] > 0) {
|
|
if(do_print && print_result)
|
|
print_result(" Poison lost.");
|
|
status[eStatus::POISONED_WEAPON] = 0;
|
|
weap_poisoned.clear();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::pair<cInvenSlot, cInvenSlot> cPlayer::get_weapons() {
|
|
cInvenSlot weap1 = has_type_equip(eItemType::ONE_HANDED);
|
|
if(weap1) {
|
|
equip[weap1.slot] = false;
|
|
cInvenSlot weap2 = has_type_equip(eItemType::ONE_HANDED);
|
|
equip[weap1.slot] = true;
|
|
return std::make_pair(weap1, weap2);
|
|
} else {
|
|
cInvenSlot weap2 = has_type_equip(eItemType::TWO_HANDED);
|
|
return std::make_pair(weap2, weap1);
|
|
}
|
|
}
|
|
|
|
short cPlayer::max_weight() const {
|
|
return 100 + (15 * min(skill(eSkill::STRENGTH),20)) + (traits[eTrait::STRENGTH] * 30)
|
|
+ (traits[eTrait::BAD_BACK] * -50) + (race == eRace::VAHNATAI) * -25;
|
|
}
|
|
|
|
short cPlayer::cur_weight() const {
|
|
short weight = 0;
|
|
bool airy = false,heavy = false;
|
|
|
|
for(int i = 0; i < items.size(); i++)
|
|
if(items[i].variety != eItemType::NO_ITEM) {
|
|
weight += items[i].item_weight();
|
|
if(items[i].ability == eItemAbil::LIGHTER_OBJECT)
|
|
airy = true;
|
|
if(items[i].ability == eItemAbil::HEAVIER_OBJECT)
|
|
heavy = true;
|
|
}
|
|
if(airy)
|
|
weight -= 30;
|
|
if(heavy)
|
|
weight += 30;
|
|
if(weight < 0)
|
|
weight = 0;
|
|
return weight;
|
|
}
|
|
|
|
short cPlayer::free_weight() const {
|
|
return max_weight() - cur_weight();
|
|
}
|
|
|
|
short cPlayer::armor_encumbrance() const {
|
|
short total = 0;
|
|
for(short i = 0; i < items.size(); i++) {
|
|
if(equip[i]) total += items[i].awkward;
|
|
}
|
|
return total;
|
|
}
|
|
|
|
short cPlayer::total_encumbrance(const std::array<short, 51>& reduce_chance) const {
|
|
short total = 0;
|
|
|
|
short burden = free_weight();
|
|
if(burden < 0) total += burden / -10;
|
|
|
|
for(short i = 0; i < items.size(); i++)
|
|
if(equip[i]) {
|
|
short item_encumbrance = items[i].awkward;
|
|
if(items[i].ability == eItemAbil::ENCUMBERING)
|
|
item_encumbrance += items[i].abil_strength;
|
|
if(item_encumbrance == 1 && get_ran(1,0,130) < reduce_chance[skill(eSkill::DEFENSE)])
|
|
item_encumbrance--;
|
|
if(item_encumbrance > 1 && get_ran(1,0,70) < reduce_chance[skill(eSkill::DEFENSE)])
|
|
item_encumbrance--;
|
|
total += item_encumbrance;
|
|
}
|
|
return total;
|
|
}
|
|
|
|
cInvenSlot cPlayer::has_space() {
|
|
for(int i = 0; i < items.size(); i++) {
|
|
if(items[i].variety == eItemType::NO_ITEM)
|
|
return cInvenSlot(*this, i);
|
|
}
|
|
return cInvenSlot(*this);
|
|
}
|
|
|
|
const cInvenSlot cPlayer::has_space() const {
|
|
return const_cast<cPlayer*>(this)->has_space();
|
|
}
|
|
|
|
void cPlayer::combine_things() {
|
|
for(int i = 0; i < items.size(); i++) {
|
|
if(items[i].variety != eItemType::NO_ITEM && items[i].type_flag > 0 && items[i].ident) {
|
|
for(int j = i + 1; j < items.size(); j++)
|
|
if(items[j].variety != eItemType::NO_ITEM && items[j].type_flag == items[i].type_flag && items[j].ident) {
|
|
if(print_result) print_result("(items combined)");
|
|
short test = items[i].charges + items[j].charges;
|
|
if(test > 125) {
|
|
items[i].charges = 125;
|
|
if(print_result) print_result("(Can have at most 125 of any item.");
|
|
}
|
|
else items[i].charges += items[j].charges;
|
|
if(equip[j]) {
|
|
equip[i] = true;
|
|
equip[j] = false;
|
|
}
|
|
take_item(j);
|
|
}
|
|
}
|
|
if(items[i].variety != eItemType::NO_ITEM && items[i].charges < 0)
|
|
items[i].charges = 1;
|
|
}
|
|
}
|
|
|
|
short cPlayer::get_prot_level(eItemAbil abil, short dat) const {
|
|
int sum = 0;
|
|
for(int i = 0; i < items.size(); i++) {
|
|
if(items[i].variety == eItemType::NO_ITEM) continue;
|
|
if(items[i].ability != abil) continue;
|
|
if(!equip[i]) continue;
|
|
if(dat >= 0 && dat != items[i].abil_data.value) continue;
|
|
sum += items[i].abil_strength;
|
|
}
|
|
return sum; // TODO: Should we return -1 if the sum is 0?
|
|
}
|
|
|
|
template<typename Fcn>
|
|
cInvenSlot cPlayer::find_item_matching(Fcn fcn) {
|
|
for(short i = 0; i < items.size(); i++)
|
|
if(items[i].variety != eItemType::NO_ITEM && fcn(i, items[i]))
|
|
return cInvenSlot(*this, i);
|
|
return cInvenSlot(*this);
|
|
}
|
|
|
|
cInvenSlot cPlayer::has_abil_equip(eItemAbil abil,short dat) {
|
|
return find_item_matching([this,abil,dat](int i, const cItem& item) {
|
|
if(item.variety == eItemType::NO_ITEM) return false;
|
|
if(item.ability != abil) return false;
|
|
if(!equip[i]) return false;
|
|
if(dat >= 0 && dat != item.abil_data.value) return false;
|
|
return true;
|
|
});
|
|
}
|
|
|
|
const cInvenSlot cPlayer::has_abil_equip(eItemAbil abil,short dat) const {
|
|
return const_cast<cPlayer*>(this)->has_abil_equip(abil,dat);
|
|
}
|
|
|
|
cInvenSlot cPlayer::has_abil(eItemAbil abil,short dat) {
|
|
return find_item_matching([this,abil,dat](int, const cItem& item) {
|
|
if(item.variety == eItemType::NO_ITEM) return false;
|
|
if(item.ability != abil) return false;
|
|
if(dat >= 0 && dat != item.abil_data.value) return false;
|
|
return true;
|
|
});
|
|
}
|
|
|
|
const cInvenSlot cPlayer::has_abil(eItemAbil abil,short dat) const {
|
|
return const_cast<cPlayer*>(this)->has_abil(abil,dat);
|
|
}
|
|
|
|
cInvenSlot cPlayer::has_type_equip(eItemType type) {
|
|
return find_item_matching([this,type](int i, const cItem& item) {
|
|
return equip[i] && item.variety == type;
|
|
});
|
|
}
|
|
|
|
const cInvenSlot cPlayer::has_type_equip(eItemType type) const {
|
|
return const_cast<cPlayer*>(this)->has_type_equip(type);
|
|
}
|
|
|
|
cInvenSlot cPlayer::has_type(eItemType type) {
|
|
return find_item_matching([type](int, const cItem& item) {
|
|
return item.variety == type;
|
|
});
|
|
}
|
|
|
|
const cInvenSlot cPlayer::has_type(eItemType type) const {
|
|
return const_cast<cPlayer*>(this)->has_type(type);
|
|
}
|
|
|
|
cInvenSlot cPlayer::has_class_equip(unsigned int item_class) {
|
|
return find_item_matching([this,item_class](int i, const cItem& item) {
|
|
return equip[i] && item.special_class == item_class;
|
|
});
|
|
}
|
|
|
|
const cInvenSlot cPlayer::has_class_equip(unsigned int item_class) const {
|
|
return const_cast<cPlayer*>(this)->has_class_equip(item_class);
|
|
}
|
|
|
|
cInvenSlot cPlayer::has_class(unsigned int item_class) {
|
|
return find_item_matching([item_class](int, const cItem& item) {
|
|
return item.special_class == item_class;
|
|
});
|
|
}
|
|
|
|
const cInvenSlot cPlayer::has_class(unsigned int item_class) const {
|
|
return const_cast<cPlayer*>(this)->has_class(item_class);
|
|
}
|
|
|
|
cInvenSlot::operator bool() const {
|
|
return slot < owner.items.size();
|
|
}
|
|
|
|
bool cInvenSlot::operator !() const {
|
|
return slot >= owner.items.size();
|
|
}
|
|
|
|
cItem* cInvenSlot::operator->() {
|
|
return &owner.items[slot];
|
|
}
|
|
|
|
const cItem* cInvenSlot::operator->() const {
|
|
return &owner.items[slot];
|
|
}
|
|
|
|
cItem& cInvenSlot::operator*() {
|
|
return owner.items[slot];
|
|
}
|
|
|
|
const cItem& cInvenSlot::operator*() const {
|
|
return owner.items[slot];
|
|
}
|
|
|
|
short cPlayer::skill(eSkill skill) const {
|
|
int bulk_bonus = 0;
|
|
if(skill >= eSkill::EDGED_WEAPONS && skill <= eSkill::DEFENSE)
|
|
bulk_bonus = get_prot_level(eItemAbil::BOOST_WAR);
|
|
else if(skill >= eSkill::MAGE_SPELLS && skill <= eSkill::ITEM_LORE)
|
|
bulk_bonus = get_prot_level(eItemAbil::BOOST_MAGIC);
|
|
return min(20, skills[skill] + get_prot_level(eItemAbil::BOOST_STAT, int(skill))) + bulk_bonus;
|
|
}
|
|
|
|
eBuyStatus cPlayer::ok_to_buy(short cost,cItem item) const {
|
|
if(!party) return eBuyStatus::NO_SPACE;
|
|
if(item.variety == eItemType::SPECIAL) {
|
|
if(party->spec_items.count(item.item_level))
|
|
return eBuyStatus::HAVE_LOTS;
|
|
} else if(item.variety == eItemType::QUEST) {
|
|
if(party->active_quests[item.item_level].status != eQuestStatus::AVAILABLE)
|
|
return eBuyStatus::HAVE_LOTS;
|
|
} else if(item.variety != eItemType::GOLD && item.variety != eItemType::FOOD) {
|
|
for(int i = 0; i < items.size(); i++)
|
|
if(items[i].variety != eItemType::NO_ITEM && items[i].type_flag == item.type_flag && items[i].charges > 123)
|
|
return eBuyStatus::HAVE_LOTS;
|
|
|
|
if(!has_space())
|
|
return eBuyStatus::NO_SPACE;
|
|
if(item.item_weight() > free_weight()) {
|
|
return eBuyStatus::TOO_HEAVY;
|
|
}
|
|
}
|
|
if(cost > party->gold)
|
|
return eBuyStatus::NEED_GOLD;
|
|
return eBuyStatus::OK;
|
|
}
|
|
|
|
void cPlayer::take_item(int which_item) {
|
|
if(weap_poisoned.slot == which_item && status[eStatus::POISONED_WEAPON] > 0) {
|
|
if(print_result) print_result(" Poison lost.");
|
|
status[eStatus::POISONED_WEAPON] = 0;
|
|
weap_poisoned.clear();
|
|
}
|
|
if(weap_poisoned.slot > which_item && status[eStatus::POISONED_WEAPON] > 0)
|
|
weap_poisoned.slot--;
|
|
|
|
for(int i = which_item; i < 23; i++) {
|
|
items[i] = items[i + 1];
|
|
equip[i] = equip[i + 1];
|
|
}
|
|
items[23] = cItem();
|
|
equip[23] = false;
|
|
}
|
|
|
|
void cPlayer::remove_charge(int which_item) {
|
|
if(items[which_item].charges > 0) {
|
|
items[which_item].charges--;
|
|
if(items[which_item].charges == 0) {
|
|
take_item(which_item);
|
|
}
|
|
}
|
|
}
|
|
|
|
void cPlayer::finish_create() {
|
|
// Start items
|
|
switch(race) {
|
|
case eRace::HUMAN:
|
|
items[0] = cItem(ITEM_KNIFE);
|
|
items[1] = cItem(ITEM_BUCKLER);
|
|
break;
|
|
case eRace::NEPHIL:
|
|
items[0] = cItem(ITEM_BOW);
|
|
items[1] = cItem(ITEM_ARROW);
|
|
break;
|
|
case eRace::SLITH:
|
|
items[0] = cItem(ITEM_POLEARM);
|
|
items[1] = cItem(ITEM_HELM);
|
|
break;
|
|
case eRace::VAHNATAI:
|
|
// TODO: Should they have a robe instead of a knife?
|
|
items[0] = cItem(ITEM_KNIFE);
|
|
items[1] = cItem(ITEM_RAZORDISK);
|
|
break;
|
|
default: break; // Silence compiler warning
|
|
// It's impossible to reach this point, anyway.
|
|
// The only way to get a PC of other races is with the Create PC node,
|
|
// which doesn't call this.
|
|
}
|
|
equip[0] = true;
|
|
equip[1] = true;
|
|
// Racial stat adjustments
|
|
if(race == eRace::NEPHIL)
|
|
skills[eSkill::DEXTERITY] += 2;
|
|
if(race == eRace::SLITH) {
|
|
skills[eSkill::STRENGTH] += 2;
|
|
skills[eSkill::INTELLIGENCE] += 1;
|
|
}
|
|
if(race == eRace::VAHNATAI) {
|
|
skills[eSkill::INTELLIGENCE] += 2;
|
|
// The Vahnatai signature spells
|
|
if(skills[eSkill::MAGE_SPELLS] >= 4)
|
|
mage_spells[34] = mage_spells[35] = true;
|
|
}
|
|
// Bonus spell points for spellcasters
|
|
max_sp += skills[eSkill::MAGE_SPELLS] * 3 + skills[eSkill::PRIEST_SPELLS] * 3;
|
|
cur_sp = max_sp;
|
|
// If they trained in mage spells but then decided to be Anama, give them back the skill points.
|
|
// But do it here so that they still get the bonus spell points from that training.
|
|
if(traits[eTrait::ANAMA] && skills[eSkill::MAGE_SPELLS] > 0) {
|
|
skill_pts += 6 * skills[eSkill::MAGE_SPELLS];
|
|
skills[eSkill::MAGE_SPELLS] = 0;
|
|
}
|
|
}
|
|
|
|
cPlayer::cPlayer(cParty& party) : party(&party), weap_poisoned(*this) {
|
|
main_status = eMainStatus::ABSENT;
|
|
name = "\n";
|
|
|
|
skills[eSkill::STRENGTH] = 1;
|
|
skills[eSkill::DEXTERITY] = 1;
|
|
skills[eSkill::INTELLIGENCE] = 1;
|
|
cur_health = 6;
|
|
max_health = 6;
|
|
cur_sp = 0;
|
|
max_sp = 0;
|
|
experience = 0;
|
|
skill_pts = 65;
|
|
level = 1;
|
|
std::fill(items.begin(), items.end(), cItem());
|
|
equip.reset();
|
|
|
|
priest_spells = basic_spells;
|
|
mage_spells = basic_spells;
|
|
which_graphic = 0;
|
|
|
|
race = eRace::HUMAN;
|
|
direction = DIR_N;
|
|
combat_pos = {-1,-1};
|
|
|
|
unique_id = party.next_pc_id++;
|
|
}
|
|
|
|
cPlayer::cPlayer(cParty& party,ePartyPreset key,short slot) : cPlayer(party) {
|
|
main_status = eMainStatus::ALIVE;
|
|
unique_id = slot + 1000;
|
|
party.next_pc_id = max(unique_id + 1, party.next_pc_id);
|
|
if(key == PARTY_DEBUG){
|
|
switch(slot) {
|
|
case 0:
|
|
name = "Gunther";
|
|
break;
|
|
case 1:
|
|
name = "Yanni";
|
|
break;
|
|
case 2:
|
|
name = "Mandolin";
|
|
break;
|
|
case 3:
|
|
name = "Pete";
|
|
break;
|
|
case 4:
|
|
name = "Vraiment";
|
|
break;
|
|
case 5:
|
|
name = "Goo";
|
|
break;
|
|
}
|
|
skills[eSkill::STRENGTH] = 20;
|
|
skills[eSkill::DEXTERITY] = 20;
|
|
skills[eSkill::INTELLIGENCE] = 20;
|
|
for(short i = 3; i < 19; i++) {
|
|
eSkill skill = eSkill(i);
|
|
skills[skill] = 8;
|
|
}
|
|
cur_health = 60;
|
|
max_health = 60;
|
|
cur_sp = 90;
|
|
max_sp = 90;
|
|
experience = 0;
|
|
skill_pts = 60;
|
|
level = 1;
|
|
std::fill(items.begin(), items.end(), cItem());
|
|
equip.reset();
|
|
|
|
priest_spells.set();
|
|
mage_spells.set();
|
|
which_graphic = slot * 3 + 1; // 1, 4, 7, 10, 13, 16
|
|
if(slot == 2) which_graphic++;
|
|
|
|
for(short i = 0; i < 10; i++) {
|
|
eTrait trait = eTrait(i);
|
|
traits[trait] = true;
|
|
}
|
|
|
|
race = eRace::HUMAN;
|
|
direction = DIR_N;
|
|
}else if(key == PARTY_DEFAULT){
|
|
static std::map<eSkill, short> pc_stats[6] = {
|
|
{
|
|
{eSkill::STRENGTH,8}, {eSkill::DEXTERITY,6}, {eSkill::INTELLIGENCE,2},
|
|
{eSkill::EDGED_WEAPONS,6}, {eSkill::ITEM_LORE,1}, {eSkill::ASSASSINATION,2},
|
|
}, {
|
|
{eSkill::STRENGTH,8}, {eSkill::DEXTERITY,7}, {eSkill::INTELLIGENCE,2},
|
|
{eSkill::POLE_WEAPONS,6}, {eSkill::THROWN_MISSILES,3}, {eSkill::DEFENSE,3},
|
|
{eSkill::POISON,2},
|
|
}, {
|
|
{eSkill::STRENGTH,8}, {eSkill::DEXTERITY,6}, {eSkill::INTELLIGENCE,2},
|
|
{eSkill::EDGED_WEAPONS,3}, {eSkill::BASHING_WEAPONS,3}, {eSkill::ARCHERY,2},
|
|
{eSkill::DISARM_TRAPS,4}, {eSkill::LOCKPICKING,4}, {eSkill::POISON,2}, {eSkill::LUCK,1},
|
|
}, {
|
|
{eSkill::STRENGTH,3}, {eSkill::DEXTERITY,2}, {eSkill::INTELLIGENCE,6},
|
|
{eSkill::EDGED_WEAPONS,2}, {eSkill::THROWN_MISSILES,2},
|
|
{eSkill::MAGE_SPELLS,3}, {eSkill::MAGE_LORE,3}, {eSkill::ITEM_LORE,1},
|
|
}, {
|
|
{eSkill::STRENGTH,2}, {eSkill::DEXTERITY,2}, {eSkill::INTELLIGENCE,6},
|
|
{eSkill::EDGED_WEAPONS,3}, {eSkill::THROWN_MISSILES,2},
|
|
{eSkill::MAGE_SPELLS,2}, {eSkill::PRIEST_SPELLS,1}, {eSkill::MAGE_LORE,4},
|
|
{eSkill::LUCK,1},
|
|
}, {
|
|
{eSkill::STRENGTH,2}, {eSkill::DEXTERITY,2}, {eSkill::INTELLIGENCE,6},
|
|
{eSkill::BASHING_WEAPONS,2}, {eSkill::THROWN_MISSILES,2}, {eSkill::DEFENSE,1},
|
|
{eSkill::PRIEST_SPELLS,3}, {eSkill::MAGE_LORE,3}, {eSkill::ALCHEMY,2},
|
|
},
|
|
};
|
|
static const short pc_health[6] = {22,24,24,16,16,18};
|
|
static const short pc_sp[6] = {0,0,0,20,20,21};
|
|
static const short pc_graphics[6] = {3,32,29,16,23,14};
|
|
static const short pc_race[6] = {0,2,1,0,0,0};
|
|
static const std::set<eTrait> pc_t[6] = {
|
|
{eTrait::AMBIDEXTROUS, eTrait::GOOD_CONST, eTrait::MAGICALLY_INEPT},
|
|
{eTrait::TOUGHNESS, eTrait::WOODSMAN, eTrait::SLUGGISH},
|
|
{eTrait::NIMBLE, eTrait::FRAIL},
|
|
{eTrait::MAGICALLY_APT},
|
|
{eTrait::CAVE_LORE, eTrait::GOOD_CONST, eTrait::HIGHLY_ALERT, eTrait::BAD_BACK},
|
|
{eTrait::MAGICALLY_APT},
|
|
};
|
|
|
|
main_status = eMainStatus::ALIVE;
|
|
switch(slot) {
|
|
case 0:
|
|
name = "Jenneke";
|
|
break;
|
|
case 1:
|
|
name = "Thissa";
|
|
break;
|
|
case 2:
|
|
name = "Frrrrrr";
|
|
break;
|
|
case 3:
|
|
name = "Adrianna";
|
|
break;
|
|
case 4:
|
|
name = "Feodoric";
|
|
break;
|
|
case 5:
|
|
name = "Michael";
|
|
break;
|
|
|
|
}
|
|
|
|
for(short i = 0; i < 19; i++) {
|
|
eSkill skill = eSkill(i);
|
|
skills[skill] = pc_stats[slot][skill];
|
|
}
|
|
cur_health = pc_health[slot];
|
|
max_health = pc_health[slot];
|
|
experience = 0;
|
|
skill_pts = 0;
|
|
level = 1;
|
|
|
|
std::fill(items.begin(), items.end(), cItem());
|
|
equip.reset();
|
|
cur_sp = pc_sp[slot];
|
|
max_sp = pc_sp[slot];
|
|
for(short i = 0; i < 15; i++) {
|
|
eTrait trait = eTrait(i);
|
|
traits[trait] = pc_t[slot].count(trait);
|
|
}
|
|
|
|
race = (eRace) pc_race[slot];
|
|
direction = DIR_N;
|
|
|
|
which_graphic = pc_graphics[slot];
|
|
}
|
|
}
|
|
|
|
cPlayer::cPlayer(const cPlayer& other)
|
|
: iLiving(other)
|
|
, party(other.party)
|
|
, main_status(other.main_status)
|
|
, name(other.name)
|
|
, skills(other.skills)
|
|
, max_health(other.max_health)
|
|
, cur_health(other.cur_health)
|
|
, max_sp(other.max_sp)
|
|
, cur_sp(other.cur_sp)
|
|
, experience(other.experience)
|
|
, skill_pts(other.skill_pts)
|
|
, level(other.level)
|
|
, items(other.items)
|
|
, equip(other.equip)
|
|
, priest_spells(other.priest_spells)
|
|
, mage_spells(other.mage_spells)
|
|
, which_graphic(other.which_graphic)
|
|
, weap_poisoned(*this, other.weap_poisoned.slot)
|
|
, traits(other.traits)
|
|
, race(other.race)
|
|
, unique_id(party->next_pc_id++)
|
|
, last_cast(other.last_cast)
|
|
, combat_pos(other.combat_pos)
|
|
, parry(other.parry)
|
|
, last_attacked(nullptr)
|
|
{}
|
|
|
|
cPlayer::cPlayer(cPlayer&& other) : weap_poisoned(*this, other.weap_poisoned.slot) {
|
|
swap(other);
|
|
}
|
|
|
|
void cPlayer::swap(cPlayer& other) {
|
|
std::swap(party, other.party);
|
|
std::swap(main_status, other.main_status);
|
|
std::swap(name, other.name);
|
|
std::swap(skills, other.skills);
|
|
std::swap(max_health, other.max_health);
|
|
std::swap(cur_health, other.cur_health);
|
|
std::swap(max_sp, other.max_sp);
|
|
std::swap(cur_sp, other.cur_sp);
|
|
std::swap(experience, other.experience);
|
|
std::swap(skill_pts, other.skill_pts);
|
|
std::swap(level, other.level);
|
|
std::swap(items, other.items);
|
|
std::swap(equip, other.equip);
|
|
std::swap(priest_spells, other.priest_spells);
|
|
std::swap(mage_spells, other.mage_spells);
|
|
std::swap(which_graphic, other.which_graphic);
|
|
std::swap(weap_poisoned.slot, other.weap_poisoned.slot);
|
|
std::swap(traits, other.traits);
|
|
std::swap(race, other.race);
|
|
std::swap(unique_id, other.unique_id);
|
|
std::swap(last_cast, other.last_cast);
|
|
std::swap(combat_pos, other.combat_pos);
|
|
std::swap(parry, other.parry);
|
|
std::swap(last_attacked, other.last_attacked);
|
|
}
|
|
|
|
void operator += (eMainStatus& stat, eMainStatus othr){
|
|
if(othr == eMainStatus::SPLIT)
|
|
stat = (eMainStatus) (10 + (int)stat);
|
|
else if(stat == eMainStatus::SPLIT)
|
|
stat = (eMainStatus) (10 + (int)othr);
|
|
}
|
|
|
|
void operator -= (eMainStatus& stat, eMainStatus othr){
|
|
if(othr == eMainStatus::SPLIT)
|
|
stat = (eMainStatus) (-10 + (int)stat);
|
|
else if(stat == eMainStatus::SPLIT)
|
|
stat = (eMainStatus) (-10 + (int)othr);
|
|
}
|
|
|
|
void cPlayer::writeTo(std::ostream& file) const {
|
|
file << "UID " << unique_id << '\n';
|
|
file << "STATUS -1 " << main_status << '\n';
|
|
file << "NAME " << maybe_quote_string(name) << '\n';
|
|
file << "SKILL " << eSkill::MAX_HP << ' ' << max_health << '\n';
|
|
if(max_sp > 0)
|
|
file << "SKILL " << eSkill::MAX_SP << ' ' << max_sp << '\n';
|
|
for(auto p : skills) {
|
|
if(p.second > 0)
|
|
file << "SKILL " << int(p.first) << ' ' << p.second << '\n';
|
|
}
|
|
file << "HEALTH " << cur_health << '\n';
|
|
file << "MANA " << cur_sp << '\n';
|
|
file << "EXPERIENCE " << experience << '\n';
|
|
file << "SKILLPTS " << skill_pts << '\n';
|
|
file << "LEVEL " << level << '\n';
|
|
auto status = this->status;
|
|
for(int i = 0; i < 15; i++) {
|
|
eStatus stat = (eStatus) i;
|
|
if(status[stat] != 0)
|
|
file << "STATUS " << i << ' ' << status.at(stat) << '\n';
|
|
}
|
|
for(int i = 0; i < equip.size(); i++)
|
|
if(equip[i])
|
|
file << "EQUIP " << i << '\n';
|
|
for(int i = 0; i < 62; i++)
|
|
if(mage_spells[i])
|
|
file << "MAGE " << i << '\n';
|
|
for(int i = 0; i < 62; i++)
|
|
if(priest_spells[i])
|
|
file << "PRIEST " << i << '\n';
|
|
auto traits = this->traits;
|
|
for(int i = 0; i < 62; i++) {
|
|
eTrait trait = eTrait(i);
|
|
if(traits[trait])
|
|
file << "TRAIT " << i << '\n';
|
|
}
|
|
file << "ICON " << which_graphic << '\n';
|
|
file << "RACE " << race << '\n';
|
|
file << "DIRECTION " << direction << '\n';
|
|
if(weap_poisoned)
|
|
file << "POISON " << weap_poisoned.slot << '\n';
|
|
file << '\f';
|
|
for(int i = 0; i < items.size(); i++)
|
|
if(items[i].variety != eItemType::NO_ITEM){
|
|
file << "ITEM " << i << '\n';
|
|
items[i].writeTo(file);
|
|
file << '\f';
|
|
}
|
|
}
|
|
|
|
void cPlayer::readFrom(std::istream& file){
|
|
std::istringstream bin, sin;
|
|
std::string cur;
|
|
getline(file, cur, '\f');
|
|
bin.str(cur);
|
|
|
|
// Clear some data that is not always present
|
|
equip.reset();
|
|
mage_spells.reset();
|
|
priest_spells.reset();
|
|
weap_poisoned.clear();
|
|
status.clear();
|
|
traits.clear();
|
|
skills.clear();
|
|
cur_sp = max_sp = ap = 0;
|
|
|
|
while(getline(bin, cur)) { // continue as long as no error, such as eof, occurs
|
|
sin.str(cur);
|
|
sin >> cur;
|
|
if(cur == "STATUS"){
|
|
eStatus i;
|
|
sin >> i;
|
|
if(i == eStatus::MAIN) sin >> main_status;
|
|
else sin >> status[i];
|
|
}else if(cur == "NAME")
|
|
name = read_maybe_quoted_string(sin);
|
|
else if(cur == "SKILL"){
|
|
eSkill skill;
|
|
sin >> skill;
|
|
switch(skill) {
|
|
case eSkill::MAX_HP:
|
|
sin >> max_health;
|
|
break;
|
|
case eSkill::MAX_SP:
|
|
sin >> max_sp;
|
|
break;
|
|
case eSkill::CUR_HP:
|
|
sin >> cur_health;
|
|
break;
|
|
case eSkill::CUR_SP:
|
|
sin >> cur_sp;
|
|
break;
|
|
case eSkill::CUR_XP:
|
|
sin >> experience;
|
|
break;
|
|
case eSkill::CUR_SKILL:
|
|
break;
|
|
case eSkill::CUR_LEVEL:
|
|
sin >> level;
|
|
break;
|
|
default:
|
|
sin >> skills[skill];
|
|
}
|
|
}else if(cur == "HEALTH")
|
|
sin >> cur_health;
|
|
else if(cur == "MANA")
|
|
sin >> cur_sp;
|
|
else if(cur == "EXPERIENCE")
|
|
sin >> experience;
|
|
else if(cur == "SKILLPTS")
|
|
sin >> skill_pts;
|
|
else if(cur == "LEVEL")
|
|
sin >> level;
|
|
else if(cur == "STATUS"){
|
|
eStatus i;
|
|
sin >> i;
|
|
sin >> status[i];
|
|
} else if(cur == "UID") {
|
|
sin >> unique_id;
|
|
if(party)
|
|
party->next_pc_id = max(unique_id + 1, party->next_pc_id);
|
|
}else if(cur == "EQUIP"){
|
|
int i;
|
|
sin >> i;
|
|
equip[i] = true;
|
|
}else if(cur == "MAGE"){
|
|
int i;
|
|
sin >> i;
|
|
mage_spells[i] = true;
|
|
}else if(cur == "PRIEST"){
|
|
int i;
|
|
sin >> i;
|
|
priest_spells[i] = true;
|
|
}else if(cur == "TRAIT"){
|
|
eTrait trait;
|
|
sin >> trait;
|
|
traits[trait] = true;
|
|
}else if(cur == "ICON")
|
|
sin >> which_graphic;
|
|
else if(cur == "DIRECTION")
|
|
sin >> direction;
|
|
else if(cur == "RACE")
|
|
sin >> race;
|
|
else if(cur == "POISON")
|
|
sin >> weap_poisoned.slot;
|
|
sin.clear();
|
|
}
|
|
bin.clear();
|
|
while(getline(file, cur, '\f')) {
|
|
bin.str(cur);
|
|
bin >> cur;
|
|
if(cur == "ITEM") {
|
|
int i;
|
|
bin >> i;
|
|
items[i].readFrom(bin);
|
|
}
|
|
bin.clear();
|
|
}
|
|
}
|
|
|
|
void(* cPlayer::give_help)(short,short) = nullptr;
|