/* * pscharacter.cpp * * Copyright (C) 2001 Atomic Blue (info@planeshift.it, http://www.atomicblue.org) * * * 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 (version 2 of the License) * 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ #include #include #include #include // Define this so the slot ID to string mapping is compiled into object form exactly once #define PSCHARACTER_CPP #include "../psserver.h" #include "util/psdatabase.h" #include "pscharacter.h" #include "pscharacterloader.h" #include "../psserverchar.h" #include "../globals.h" #include "util/log.h" #include "../exchangemanager.h" #include "../spellmanager.h" #include "../workmanager.h" #include "../marriagemanager.h" #include "../cachemanager.h" #include "../npcmanager.h" #include "psglyph.h" #include "psquest.h" #include "dictionary.h" #include "psraceinfo.h" #include "psguildinfo.h" #include "psmerchantinfo.h" #include "pstrainerinfo.h" #include "util/psxmlparser.h" #include "util/serverconsole.h" #include "util/mathscript.h" #include "util/log.h" #include "../playergroup.h" #include "../events.h" #include "servervitals.h" #include "../progressionmanager.h" #include "../chatmanager.h" #include "../commandmanager.h" // The sizes and scripts need balancing. For now, maxSize is disabled. #define ENABLE_MAX_CAPACITY 0 const char *psCharacter::characterTypeName[] = { "player", "npc", "pet" }; psCharacterInventory::psCharacterInventory() { int i; for (i=0; iInstantiateBasicItem(); equipment[PSCHARACTER_SLOT_LEFTHAND].EquipmentFlags |= PSCHARACTER_EQUIPMENTFLAG_AUTOATTACK | PSCHARACTER_EQUIPMENTFLAG_ATTACKIFEMPTY; equipment[PSCHARACTER_SLOT_RIGHTHAND].default_if_empty = fist->InstantiateBasicItem(); equipment[PSCHARACTER_SLOT_RIGHTHAND].EquipmentFlags |= PSCHARACTER_EQUIPMENTFLAG_AUTOATTACK | PSCHARACTER_EQUIPMENTFLAG_ATTACKIFEMPTY; // base clothes and natural armor are loaded when setting race. owner = NULL; doRestrictions = false; loaded = false; } void psCharacterInventory::Clear() { unsigned int slot; // Delete all instances of default_if_empty items for (slot=0;slotGetMathScriptEngine()->FindScript("CalculateMaxCarryWeight"); if ( maxCarryWeight ) { MathScriptVar* actorvar = maxCarryWeight->GetOrCreateVar("Actor"); actorvar->SetObject(owner); maxCarryWeight->Execute(); MathScriptVar* carry = maxCarryWeight->GetVar("MaxCarry"); maxWeight = carry->GetValue(); } // The max total size that a player can carry static MathScript *maxCarryAmount = NULL; if ( !maxCarryAmount ) maxCarryAmount = psserver->GetMathScriptEngine()->FindScript("CalculateMaxCarryAmount"); if ( maxCarryAmount ) { MathScriptVar* actorvar = maxCarryAmount->GetOrCreateVar("Actor"); actorvar->SetObject(owner); maxCarryAmount->Execute(); MathScriptVar* carryAmount = maxCarryAmount->GetVar("MaxAmount"); maxSize = carryAmount->GetValue(); } ReassessInventoryDimensions(); bool needToUpdateInv = false; bool droppedSomething = false; int cnum = 0; if (owner->GetActor()) cnum = owner->GetActor()->GetClientID(); // Drop items if can't hold everything anymore for (int i=PSCHARACTER_BULK_COUNT-1; i>=0; i--) { psItem* item = GetBulkItem(i); if (!item) continue; bool canHoldWeight = HasWeight(0); bool canHoldSize = HasSpace(0); if ( canHoldWeight && canHoldSize ) break; // Can hold everything // Make sure we don't drop weightless or sizeless items // to fix overweight or oversize, respectively if (canHoldWeight) { // Can hold weight, but not size; only drop due to size if (item->GetSumSize() < EPSILON) continue; // Won't help } else // canHoldSize { // Can hold size, but not weight; only drop due to weight if (item->GetSumWeight() < EPSILON) continue; // Won't help } psItem* removed = RemoveBulk(i); CS_ASSERT(removed); owner->DropItem(removed,removed->IsTransient()); needToUpdateInv = true; droppedSomething = true; } // Check requirements of the items equiped for (int i=0; iCheckRequirements(owner,msg) || !HasWeight(0) || !HasSpace(0) ) { int slot = item->GetLocInParent(); psItem* removed = RemoveEquipment(slot); if (removed) { if (msg != "None") { psserver->SendSystemInfo(cnum,"You can no longer keep %s equipped. %s", removed->GetName(), msg.GetData() ); } psserver->GetCharManager()->SendOutEquipmentMessages( cnum, slot, removed, psEquipmentMessage::DEEQUIP ); needToUpdateInv = true; // Try to deequip if ( PutInBulk(removed) == 0 ) continue; // Not any space left there either, drop on ground owner->DropItem(removed,removed->IsTransient()); droppedSomething = true; } } } if (needToUpdateInv) psserver->GetCharManager()->SendInventory( cnum ); if (droppedSomething) psserver->SendSystemError(cnum,"Your inventory is full!"); } bool psCharacterInventory::Load() { return Load(owner->GetCharacterID()); } bool psCharacterInventory::Load(unsigned int use_id) { doRestrictions = (owner->GetCharType() == PSCHARACTER_TYPE_PLAYER); Result items(db->Select("SELECT * from item_instances where char_id_owner=%u",use_id)); if ( items.IsValid() ) { int count = items.Count(); psItemSet* set = new psItemSet; for ( int i = 0; i < count; i++ ) { unsigned int stats_id = items[i].GetUInt32("item_stats_id_standard"); psItemStats *stats = CacheManager::GetSingleton().GetBasicItemStatsByID(stats_id); if ( !stats ) { Bug3("Error! Item %s could not be loaded. Check item_stats id=%d\n",items[i]["id"], stats_id); continue; } psItem* item; if (stats->GetIsGlyph()) { item = new psGlyph(); } else { item = new psItem(); } uint32 parentid; if ( !item->Load(items[i],parentid) ) { Bug1("Error! Item could not be loaded. Skipping.\n"); delete item; continue; } if ( !PlaceLoadedItem(items[i]["location"],items[i]["equipped_slot"],item) ) { Bug5("Item %s(%u) could not be loaded for %s(%d). Skipping this item.\n", item->GetName(), item->GetUID(), owner->GetCharName(), owner->GetCharacterID() ); delete item; continue; } set->Add(item,parentid); } set->ResolveAllParents(); for (size_t i=0; i < set->GetSize(); i++) if (set->Get(i)) set->Get(i)->SetLoaded(); loaded = true; // Begin dimension checking ReassessInventoryDimensions(); set->Release(); delete set; return true; } else return false; } bool psCharacterInventory::QuickLoad(unsigned int use_id) { Result items(db->Select("SELECT id, item_stats_id_standard, equipped_slot FROM item_instances WHERE (char_id_owner=%u AND location='E')",use_id)); if ( items.IsValid() ) { for ( int i=0; i < (int)items.Count(); i++ ) { unsigned int stats_id = items[i].GetUInt32("item_stats_id_standard"); psItemStats *stats = CacheManager::GetSingleton().GetBasicItemStatsByID(stats_id); if ( !stats ) { Bug2("Error! Item %s could not be loaded. Skipping.\n", items[i]["id"] ); continue; } // Quick load; we just need to know what it looks like psItem* item = stats->InstantiateBasicItem(); if ( !PlaceLoadedItem("E",items[i]["equipped_slot"],item) ) { Bug5("Item %s(%s) could not be quick loaded for %s(%d). Skipping this item.\n", item->GetName(), items[i]["id"], owner->GetCharName(), owner->GetCharacterID() ); delete item; continue; } } // Items are not flagged as 'loaded', and can not be saved return true; } else return false; } bool psCharacterInventory::PlaceLoadedItem(const char *loc, const char *slot, psItem * & item) { if (loc == NULL) { csString error; error.Format("Item %s(%u) Could not be placed in %s(%d) inventory into slot %s because it's location in parent was NULL", item->GetName(), item->GetUID(), owner->GetCharName(), owner->GetCharacterID(), slot ); Bug1(error); return false; } csString location(loc); // Location should be either EQUIPED, INVENTORY or BULK // This item is equiped in a character equipment slot (weapon, armor) if (location == "E") { int eslot = CacheManager::GetSingleton().slotName.GetID( slot ); if (!EquipIn(eslot,item)) { csString error; error.Format("\nCharacter: %s(%d)\nLocation: %s Slot: %s\nItem: %s(%u)\n", owner->GetCharName(), owner->GetCharacterID(),loc, slot, item->GetName(), item->GetUID() ); Error1( error ); // Item may have been equippable due to a buff // Attempt to preserve this item by moving it to bulk return PutInBulk(item,false, false, ANY_BULK_SLOT) == 0; } } // This item is in the player's BULK slots. else if (location == "I") { // Find out which slot and put it there. if ( PutInBulk(item, false, false, item->GetLocInParent()) == -1) { // An equipped item may have stolen it's slot, so we try any if( PutInBulk(item, false, true, ANY_BULK_SLOT) == -1) { // Could not put the item in the slot. This means the index was out of range or an item is // already in the slot. // An examination of the player's inventory should show which is the case. csString error; error.Format("\nCharacter: %s(%d)\nLocation: %s Slot: %d\nItem: %s(%u)\nError:%s\n", owner->GetCharName(), owner->GetCharacterID(),loc, item->GetLocInParent(), item->GetName(), item->GetUID(),lastError.GetDataSafe()); Error1( error ); return false; } } } else if (location == "C") { // Do nothing } else { Error4("Item ID %u in inventory of character id %u has unknown location %s.",item->GetUID(),owner->GetCharacterID(),loc); return false; } return true; } int psCharacterInventory::PutInBulk(psItem * & item, bool test, bool stack, int slot) { if (slot=PSCHARACTER_BULK_COUNT) { lastError.Format("Bulkslot %d is out of bounds!", slot ); return -1; } if (item==NULL) { lastError.Format("Item is null!"); return -1; } // Can it fit here? if ( !item->FitsInSlots(PSITEMSTATS_SLOT_BULK) ) { lastError.Format("%s does not fit in this slot", item->GetName() ); return -1; } if ( !HasWeight(item->GetSumWeight()) ) { lastError.Format("The %s is too heavy for you to carry", item->GetName() ); return -1; } if ( !HasSpace(item->GetSumSize()) ) { lastError.Format("The %s is too big for you to carry", item->GetName() ); return -1; } /** If we were passed ANY_BULK_SLOT or ANY_EMPTY_BULK_SLOT we need to scan the possible * slots and try to find a valid place to put this item. (non-negative slot number) * Otherwise, we'll just skip over these and attempt to place in the given slot. If * that fails, we'll return false and possibly call this again specifying ANY_BULK_SLOT. */ int i; // First see if we can stack this with an existing stack in inventory if (stack && slot == ANY_BULK_SLOT && item->GetIsStackable()) { for (i=0; iCheckStackableWith(*item)) { slot = i; break; } } } // Next check the main bulk slots if (slot < 0) { for (i=0; iGetIsContainer()) { slot = i; break; } } } if (slot < 0) { lastError.Format("Cannot find slot to put %s into", item->GetName() ); return -1; // Item does not fit } if (!test) item->ItemAboutToMove(); if (bulk[slot] == NULL) // Empty slot { // There is no remainder, the item fits completely if(test) return 0; bulk[slot] = item; item->SetLocInParent(slot); item->SetOwningCharacter(owner); item->Save(true); AddDimensionsToInventory(item); } else if (bulk[slot]->GetIsContainer()) // Container { if(!bulk[slot]->AddItemToContainer(item, test)) { lastError.Format("No space in container to put %s into", item->GetName() ); return -1; } else if(test) return 0; AddDimensionsToInventory(item); bulk[slot]->Save(true); } else // Existing stack { if (!test) { item->SetOwningCharacter(owner); AddDimensionsToInventory(item); } int remainder = bulk[slot]->CombineStack(item, test); if(test) return remainder; bulk[slot]->Save(true); if (remainder != 0) // Handle a remainder during the stacking attempt { SubtractDimensionsFromInventory(item); // Nope, didn't work; take it back item->Save(true); /* Return false which will let the caller know that the psItem it wanted to * place still needs to be placed or destroyed. In this case its stack count * may have been modified, but that doesn't matter * - the remainder still has to be placed. */ return remainder; } else // Stacked { item = bulk[slot]; // Point to new location } } inventoryCacheServer.BulkSlotModified(slot); // update cache return 0; } EQUIPMENTDATA_TYPE & psCharacterInventory::GetEquipmentObject(int slot) { return equipment[slot]; } psItem *psCharacterInventory::GetEquipmentItem(int slot) { if (slot<0 || slot>=PSCHARACTER_SLOT_COUNT) return NULL; return equipment[slot].item; } psItem *psCharacterInventory::RemoveEquipment(int slot) { if (slot<0 || slot>=PSCHARACTER_SLOT_COUNT) return NULL; psItem* currentitem=equipment[slot].item; if (currentitem==NULL) return NULL; if (currentitem->IsInUse()) return NULL; equipment[slot].item=NULL; // currentitem->SetOwningCharacter(NULL, true); SubtractDimensionsFromInventory(currentitem); owner->CalculateEquipmentModifiers(); inventoryCacheServer.EquipmentSlotModified(slot); // update cache return currentitem; } bool psCharacterInventory::PutInEquipped(int slot, psItem * item, bool test) { if (slot<0 || slot>=PSCHARACTER_SLOT_COUNT) return false; if (equipment[slot].item == NULL || item == NULL) return false; if ( equipment[slot].item->GetIsContainer() ) { if ( !equipment[slot].item->AddItemToContainer(item, test) ) return false; else if (test) return true; AddDimensionsToInventory(item); equipment[slot].item->Save(true); return true; } return false; } bool psCharacterInventory::EquipIn(int slot, psItem * item, bool test) { if (slot<0 || slot>=PSCHARACTER_SLOT_COUNT) return false; /* Do not put an item into a slot that is already occupied - doing so will lose a reference to the item. * Even if it wouldn't, it's a bug! The old item should be removed first. */ if (equipment[slot].item!=NULL || item==NULL) return false; csString resp; if ( !item->CheckRequirements( owner, resp ) ) { if(test) return false; // If this is a PC, try to preseve the item if (owner->IsNPC() || owner->IsPet()) { // Inform that NPC failed to equip item Error4("On loading: NPC/PET %s (%d) tried to equip %s but failed the item Requirements:", owner->GetCharFullName(), owner->GetCharacterID(), item->GetName() ); } if (owner->GetActor()) psserver->SendSystemInfo(owner->GetActor()->GetClientID(),resp); // It is ALWAYS the caller's responsibility to dispose the item. return false; } if(test) return true; equipment[slot].item=item; item->SetOwningCharacter(owner); item->SetLocInParent(slot); AddDimensionsToInventory(item); owner->CalculateEquipmentModifiers(); inventoryCacheServer.EquipmentSlotModified(slot); // update cache item->Save(true); return true; } psItem *psCharacterInventory::GetBulkItem(int bulkslot) { if (bulkslot<0 || bulkslot>=PSCHARACTER_BULK_COUNT) return NULL; return bulk[bulkslot]; } psItem *psCharacterInventory::RemoveBulk(int bulkslot, int count ) { if (bulkslot<0 || bulkslot>=PSCHARACTER_BULK_COUNT) return NULL; psItem* currentItem = bulk[bulkslot]; if (currentItem==NULL) return NULL; // default -1 is to drop all in stack. if ( count == -1 ) count = currentItem->GetStackCount(); if (count<0 || count > currentItem->GetStackCount()) return NULL; inventoryCacheServer.BulkSlotModified(bulkslot); // update cache // Remove ALL items in stack if ( count == currentItem->GetStackCount() ) { // currentItem->SetOwningCharacter(NULL, true); // Disabled due to Save() assert, KWF bulk[bulkslot] = NULL; SubtractDimensionsFromInventory(currentItem); return currentItem; } else { psItem* newItem = currentItem->SplitStack((unsigned short)count); currentItem->Save(); if (!newItem) return NULL; SubtractDimensionsFromInventory(newItem); return newItem; } } void psCharacterInventory::RunEquipScripts() { for (int equipslot=0;equipslotGetBaseStats()->GetProgressionEventEquip(); if ( script.Length() > 0 ) { gemActor *actor = owner->GetActor(); psserver->GetProgressionManager()->ProcessEvent(script, actor ); } } } } unsigned int psCharacterInventory::TotalStackOfItem(psItemStats* item) { unsigned int count = 0; for (int bulkslot=0;bulkslotGetBaseStats() == item) count += (unsigned int)inside->GetStackCount(); } } for (int equipslot=0;equipslotGetBaseStats() == item) count += (unsigned int)inside->GetStackCount(); } } return count; } psItem *psCharacterInventory::FindInBulk(uint32 itemID) { for (int bulkslot = 0; bulkslot < PSCHARACTER_BULK_COUNT; bulkslot++) { if (bulk[bulkslot]!=NULL) { if (bulk[bulkslot]->GetUID() == itemID) return bulk[bulkslot]; } } return NULL; } bool psCharacterInventory::HaveKeyForLock(uint32 lock) { if (equipment[PSCHARACTER_SLOT_LEFTHAND].item && equipment[PSCHARACTER_SLOT_LEFTHAND].item->CanOpenLock(lock)) return true; if (equipment[PSCHARACTER_SLOT_RIGHTHAND].item && equipment[PSCHARACTER_SLOT_RIGHTHAND].item->CanOpenLock(lock)) return true; for (int bulkslot = 0; bulkslot < PSCHARACTER_BULK_COUNT; bulkslot++) { if (bulk[bulkslot]!=NULL) { if (bulk[bulkslot]->CanOpenLock(lock)) return (bulk[bulkslot] ? true : false); } } return false; } bool psCharacterInventory::Attackable(int slot) { // Slot out of range if (slot<0 || slot>=PSCHARACTER_SLOT_COUNT) return false; // TODO: Should the combat/peace toggle affect this? // The autoattack or singleattack flags must be set if ((equipment[slot].EquipmentFlags & PSCHARACTER_EQUIPMENTFLAG_AUTOATTACK) || (equipment[slot].EquipmentFlags & PSCHARACTER_EQUIPMENTFLAG_SINGLEATTACK)) { // Check if the slot is empty and can attack when empty if (equipment[slot].item==NULL && (equipment[slot].EquipmentFlags & PSCHARACTER_EQUIPMENTFLAG_ATTACKIFEMPTY)) return true; // Otherwise the slot must have an item in it if (equipment[slot].item==NULL) return false; // If the item is a melee weapon, it's OK if (equipment[slot].item->GetIsMeleeWeapon()) return true; if (equipment[slot].item->GetIsRangeWeapon()) { // Ranged weapons not supported right now /* // Check for ammo usage if (!inventory.equipment[slot].item->GetUsesAmmo()) return true; // Find some ammo - first check the same slot if (!inventory.equipment[slot].item->GetIsAmmo()) return true; // Check other equipment slots for (int ammocheck=0;ammocheckGetIsAmmo() && inventory.equipment[ammocheck].item->GetAmmoType() == inventory.equipment[slot].item->GetAmmoType() && inventory.equipment[ammocheck].item->GetStackCount()>0) return true; } // TODO: Check inventory */ } } return false; } bool psCharacterInventory::AutoAttackable(int slot) { // Slot out of range if (slot<0 || slot>=PSCHARACTER_SLOT_COUNT) return false; return (equipment[slot].EquipmentFlags & PSCHARACTER_EQUIPMENTFLAG_AUTOATTACK); } bool psCharacterInventory::SingleAttackable(int slot) { // Slot out of range if (slot<0 || slot>=PSCHARACTER_SLOT_COUNT) return false; return (equipment[slot].EquipmentFlags & PSCHARACTER_EQUIPMENTFLAG_SINGLEATTACK) ? true : false; } psItem *psCharacterInventory::GetEffectiveWeaponInSlot(int slot) { // Slot out of range if (slot<0 || slot>=PSCHARACTER_SLOT_COUNT) return NULL; // if there is a weapon in the slot, return it if (equipment[slot].item!=NULL) { if (equipment[slot].item->GetIsMeleeWeapon() || equipment[slot].item->GetIsRangeWeapon()) return equipment[slot].item; else return NULL; } // right hand and left hand can attack also if no weapon is there // the default_if_empty is the fist weapon if (equipment[slot].EquipmentFlags & PSCHARACTER_EQUIPMENTFLAG_ATTACKIFEMPTY) return equipment[slot].default_if_empty; return NULL; } psItem *psCharacterInventory::GetEffectiveArmorInSlot(int slot) { // Slot out of range if (slot<0 || slot>=PSCHARACTER_SLOT_COUNT) return NULL; // if there is an item in the slot, return it if (equipment[slot].item!=NULL) return equipment[slot].item; // hit armor locations should use the base armor if none is present // the default_if_empty is the basecloth armor return equipment[slot].default_if_empty; } int psCharacterInventory::FindItemInTopLevelEquipment(psItem *item) { for (int i=0;iGetCurrentStats() == stats) return i; } return -1; } int psCharacterInventory::FindItemInTopLevelEquipmentWithStats(psItemStats *stats) { for (int i=0;iGetCurrentStats() == stats) return i; } return -1; } bool psCharacterInventory::HasSpace( float requiredSpace ) { if ( !doRestrictions ) return true; #if !ENABLE_MAX_CAPACITY return true; #endif return (totalSize + requiredSpace <= maxSize ); } bool psCharacterInventory::HasWeight( float requiredWeight ) { if ( !doRestrictions ) return true; return (totalWeight + requiredWeight <= maxWeight ); } size_t psCharacterInventory::CanFit(psItem* item) { if ( !doRestrictions ) return 65535; // Fit any ammount if ( !HasWeight(0) || !HasSpace(0) ) return 0; // Can't fit any size_t w = 65535; size_t s = 65535; float individualWeight = item->GetBaseStats()->GetWeight(); if ( individualWeight > 0.01 ) w = (size_t)((maxWeight-totalWeight)/individualWeight); #if ENABLE_MAX_CAPACITY unsigned short individualSize = item->GetBaseStats()->GetSize(); if ( individualSize > 0 ) s = (size_t)((maxSize-totalSize)/individualSize); #endif return (wGetStackCount(); // TODO: count children totalWeight += item->RecalculateFullWeight(); // Total weight of stack including children if (item->GetParentItem() == NULL) // Sizes may be nested; don't add children to the total totalSize += item->GetSumSize(); // Total size of all in this stack } } void psCharacterInventory::SubtractDimensionsFromInventory(psItem *item) { if (loaded) // Make sure we're ready to calculate { totalCount -= item->GetStackCount(); // TODO: count children totalWeight -= item->RecalculateFullWeight(); // Total weight of stack including children if (item->GetParentItem() == NULL) // Sizes may be nested; don't add children to the total totalSize -= item->GetSumSize(); // Total size of all in this stack } } int psCharacterInventory::HasEmptyBulkSlots() { int count = 0; for (int i=0; i psCharacter::characterpool; const char * psCharacter::player_mode_to_str[] = {"unknown","peace","combat","spell casting","working","dead","sitting","exhausted"}; void *psCharacter::operator new(size_t allocSize) { // Debug3(LOG_CHARACTER,"%i %i", allocSize,sizeof(psCharacter)); // CS_ASSERT(allocSize<=sizeof(psCharacter)); return (void *)characterpool.CallFromNew(); } void psCharacter::operator delete(void *releasePtr) { characterpool.CallFromDelete((psCharacter *)releasePtr); } psCharacter::psCharacter() : guildinfo(NULL), accountid(0), npc_masterid(0), powerScript(NULL), maxRealmScript(NULL), attributes(this), modifiers(this), skills(this), loaded(false) { characterType = PSCHARACTER_TYPE_UNKNOWN; helmGroup = ""; help_event_flags = 0; duel_points = 0; situation_wum = 0.0f; effect_wum = 0.0f; memset(advantage_bitfield,0,sizeof(advantage_bitfield)); characterid = 0; familiar_id = 0; owner_id = 0; animal_affinity = ""; override_max_hp = 0.0f; override_max_mana = 0.0f; inventory.SetOwner(this); memset(&money,0,sizeof(money)); name = lastname = fullname = " "; SetSpouseName( "" ); isMarried = false; raceinfo = NULL; combat_stance = PSCHARACTER_STANCE_NORMAL; vitals = new psServerVitals(this); // workInfo = new WorkInformation(); loot_category_id = 0; loot_money = 0; location.loc_sector = NULL; location.loc_x = 0.0f; location.loc_y = 0.0f; location.loc_z = 0.0f; location.loc_yrot = 0.0f; spawn_loc = location; for (int i=0;iGetMathScriptEngine()->FindScript("StaminaBase"); if( !staminaCalc) Warning1(LOG_CHARACTER, "Can't find math script StaminaBase!"); } psCharacter::~psCharacter() { if (guildinfo) guildinfo->Disconnect(this); inventory.Clear(); // First force and update of the DB of all QuestAssignments before deleting // every assignment. UpdateQuestAssignments(true); while (assigned_quests.Length() ) delete assigned_quests.Pop() ; delete vitals; vitals = NULL; // delete workInfo; } void psCharacter::SetActor( gemActor* newActor ) { actor = newActor; if (actor) inventory.RunEquipScripts(); } bool psCharacter::Load(iResultRow& row) { // TODO: Link in account ID? SetCharacterID(row.GetInt("id")); SetAccount(row.GetInt("account_id")); SetCharType( row.GetUInt32("character_type") ); SetFullName(row["name"], row["lastname"]); SetOldLastName( row["old_lastname"] ); unsigned int raceid = row.GetUInt32("racegender_id"); psRaceInfo *raceinfo = CacheManager::GetSingleton().GetRaceInfoByID(raceid); if (!raceinfo) { Error3("Character ID %s has unknown race id %s.",row["id"],row["racegender_id"]); return false; } SetRaceInfo(raceinfo); //Assign the Helm Group Result helmResult(db->Select("SELECT helm FROM race_info WHERE race_id=%d", raceid)); helmGroup = helmResult[0]["helm"]; SetDescription(row["description"]); attributes.SetStat(PSITEMSTATS_STAT_STRENGTH,(unsigned int)row.GetFloat("base_strength"), false); attributes.SetStat(PSITEMSTATS_STAT_AGILITY,(unsigned int)row.GetFloat("base_agility"), false); attributes.SetStat(PSITEMSTATS_STAT_ENDURANCE,(unsigned int)row.GetFloat("base_endurance"), false); attributes.SetStat(PSITEMSTATS_STAT_INTELLIGENCE,(unsigned int)row.GetFloat("base_intelligence"), false); attributes.SetStat(PSITEMSTATS_STAT_WILL,(unsigned int)row.GetFloat("base_will"), false); attributes.SetStat(PSITEMSTATS_STAT_CHARISMA,(unsigned int)row.GetFloat("base_charisma"), false); // NPC fields here npc_spawnruleid = row.GetUInt32("npc_spawn_rule"); npc_masterid = row.GetUInt32("npc_master_id"); // This substitution allows us to make 100 orcs which are all copies of the stats, traits and equipment // from a single master instance. uint32_t use_id = (npc_masterid)?npc_masterid:characterid; SetHitPointsMax(row.GetFloat("base_hitpoints_max")); override_max_hp = GetHitPointsMax(); SetManaMax(row.GetFloat("base_mana_max")); override_max_mana = GetManaMax(); if (!LoadSkills(use_id)) { Error2("Cannot load skills for Character ID %u.",characterid); return false; } RecalculateStats(); SetHitPoints(row.GetFloat("mod_hitpoints")); SetMana(row.GetFloat("mod_mana")); SetStamina(row.GetFloat("stamina_physical"),true); SetStamina(row.GetFloat("stamina_mental"),false); vitals->SetOrigVitals(); // This saves them as loaded state for restoring later without hitting db, npc death resurrect. lastlogintime = row["last_login"]; faction_standings = row["faction_standings"]; csString script = row["progression_script"]; if (script != "") progressionScript.Format("%s",script.GetData()); SetMoney(psMoney( row.GetInt("money_circles"), row.GetInt("money_octas"), row.GetInt("money_hexas"), row.GetInt("money_trias"))); psSectorInfo *sectorinfo=CacheManager::GetSingleton().GetSectorInfoByID(row.GetUInt32("loc_sector_id")); if (sectorinfo==NULL) { Error3("Character ID %u has unresolvable sector id %u.",characterid,row.GetUInt32("loc_sector_id")); return false; } SetLocationInWorld(sectorinfo, row.GetFloat("loc_x"), row.GetFloat("loc_y"), row.GetFloat("loc_z"), row.GetFloat("loc_yrot") ); spawn_loc = location; // Guild fields here guildinfo = CacheManager::GetSingleton().FindGuild(row.GetUInt32("guild_member_of")); if (guildinfo) guildinfo->Connect(this); // Loot rule here loot_category_id = row.GetInt("npc_addl_loot_category_id"); impervious_to_attack = (row["npc_impervious_ind"][0]=='Y') ? ALWAYS_IMPERVIOUS : 0; duel_points = row.GetFloat("duel_points"); // Familiar Fields here animal_affinity = row[ "animal_affinity" ]; //owner_id = row.GetUInt32( "owner_id" ); help_event_flags = row.GetUInt32("help_event_flags"); if (!LoadTraits(use_id)) { Error2("Cannot load traits for Character ID %u.",characterid); return false; } if (!LoadAdvantages(use_id)) { Error2("Cannot load advantages for Character ID %u.",characterid); return false; } // This data is loaded only if it's a player, not an NPC if ( !IsNPC() && !IsPet() ) { if (!LoadQuestAssignments()) { Error2("Cannot load quest assignments for Character ID %u.",characterid); return false; } } if (npc_masterid && use_id != npc_masterid ) { // also load character specific items if (!inventory.Load(characterid)) { Error2("Cannot load character specific items for Character ID %u.",characterid); return false; } } else { inventory.Load(); } if ( !LoadRelationshipInfo( characterid ) ) // Buddies, Marriage Info, Familiars { return false; } // Load merchant info csRef merchant = csPtr(new psMerchantInfo()); if (merchant->Load(use_id)) { merchantInfo = merchant; } // Load trainer info csRef trainer = csPtr(new psTrainerInfo()); if (trainer->Load(use_id)) { trainerInfo = trainer; } if (!LoadSpells(use_id)) { Error2("Cannot load spells for Character ID %u.",characterid); return false; } timeconnected = row.GetUInt32("time_connected_sec"); startTimeThisSession = csGetTicks(); // Load Experience Points W and Progression Points X SetExperiencePoints(row.GetUInt32("experience_points")); SetProgressionPoints(row.GetUInt32("progression_points"),false); // Load the kill exp kill_exp = row.GetUInt32("kill_exp"); // Load the math script powerScript = psserver->GetMathScriptEngine()->FindScript("CalculatePowerLevel"); if ( !powerScript ) { Warning1(LOG_CHARACTER, "Can't find math script CalculatePowerLevel!"); return false; } maxRealmScript = psserver->GetMathScriptEngine()->FindScript("MaxRealm"); if ( !maxRealmScript ) { Warning1(LOG_CHARACTER, "Can't find math script MaxRealm!"); return false; } loaded = true; return true; } bool psCharacter::QuickLoad(iResultRow& row, bool noInventory) { SetCharacterID(row.GetInt("id")); SetFullName(row["name"], row["lastname"]); unsigned int raceid = row.GetUInt32("racegender_id"); psRaceInfo *raceinfo = CacheManager::GetSingleton().GetRaceInfoByID(raceid); if (!raceinfo) { Error3("Character ID %s has unknown race id %s.",row["id"],row["racegender_id"]); return false; } if (!noInventory) { SetRaceInfo(raceinfo); Result result(db->Select("SELECT base_strength,base_agility, base_endurance, base_intelligence, base_will, base_charisma from characters where id=%u LIMIT 1",characterid)); attributes.SetStat(PSITEMSTATS_STAT_STRENGTH,(unsigned int)result[0].GetFloat("base_strength"), false); attributes.SetStat(PSITEMSTATS_STAT_AGILITY,(unsigned int)result[0].GetFloat("base_agility"), false); attributes.SetStat(PSITEMSTATS_STAT_ENDURANCE,(unsigned int)result[0].GetFloat("base_endurance"), false); attributes.SetStat(PSITEMSTATS_STAT_INTELLIGENCE,(unsigned int)result[0].GetFloat("base_intelligence"), false); attributes.SetStat(PSITEMSTATS_STAT_WILL,(unsigned int)result[0].GetFloat("base_will"), false); attributes.SetStat(PSITEMSTATS_STAT_CHARISMA,(unsigned int)result[0].GetFloat("base_charisma"), false); if (!LoadSkills(characterid)) { Error2("Cannot load skills for Character ID %u.",characterid); return false; } Result helmResult(db->Select("SELECT helm FROM race_info WHERE race_id=%d", raceid)); helmGroup = helmResult[0]["helm"]; if (!LoadTraits(characterid)) { Error2("Cannot load traits for Character ID %u.",characterid); return false; } // Load equipped items inventory.QuickLoad(characterid); } return true; } bool psCharacter::LoadRelationshipInfo( unsigned int characterid ) { Result has_a( db->Select( "SELECT a.*, b.name AS 'buddy_name' FROM character_relationships a, characters b WHERE a.related_id = b.id AND a.character_id = %u", characterid ) ); Result of_a( db->Select( "SELECT a.*, b.name AS 'buddy_name' FROM character_relationships a, characters b WHERE a.character_id = b.id AND a.related_id = %u", characterid ) ); if ( !LoadFamiliar( has_a, of_a ) ) { Error2("Cannot load familiar info for Character ID %u.",characterid); return false; } if ( !LoadMarriageInfo( has_a ) ) { Error2("Cannot load Marriage Info for Character ID %u.",characterid); return false; } if ( !LoadBuddies( has_a, of_a ) ) { Error2("Cannot load buddies for Character ID %u.",characterid); return false; } return true; } bool psCharacter::LoadBuddies( Result& myBuddies, Result& buddyOf ) { unsigned int x; if ( !myBuddies.IsValid() ) return true; for ( x = 0; x < myBuddies.Count(); x++ ) { if ( strcmp( myBuddies[x][ "relationship_type" ], "buddy" ) == 0 ) { Buddy newBud; newBud.name = myBuddies[x][ "buddy_name" ]; newBud.playerID = myBuddies[x].GetUInt32( "related_id" ); buddyList.Insert( 0, newBud ); } } // Load all the people that I am a buddy of. This is used to inform these people // of when I log in/out. for (x = 0; x < buddyOf.Count(); x++ ) { if ( strcmp( buddyOf[x][ "relationship_type" ], "buddy" ) == 0 ) { buddyOfList.Insert( 0, buddyOf[x].GetUInt32( "character_id" ) ); } } return true; } bool psCharacter::LoadMarriageInfo( Result& result) { //Result result( db->Select("SELECT * FROM character_marriage_details" // " WHERE character_id=%d", characterid)); if ( !result.IsValid() ) { Error3("Could not load marriage info for character %d. Error was: %s", characterid, db->GetLastError() ); return false; } for ( unsigned int x = 0; x < result.Count(); x++ ) { if ( strcmp( result[x][ "relationship_type" ], "spouse" ) == 0 ) { const char* spouseName = result[x]["spousename"]; if ( spouseName == NULL ) return true; SetSpouseName( spouseName ); Notify2( LOG_MARRIAGE, "Successfully loaded marriage info for %s", name.GetData() ); break; } } return true; } bool psCharacter::LoadFamiliar( Result& pet, Result& owner ) { familiar_id = 0; owner_id = 0; if ( !pet.IsValid() ) { Error3("Could not load pet info for character %d. Error was: %s", characterid, db->GetLastError() ); return false; } if ( !owner.IsValid() ) { Error3("Could not load owner info for character %d. Error was: %s", characterid, db->GetLastError() ); return false; } unsigned int x; for ( x = 0; x < pet.Count(); x++ ) { if ( strcmp( pet[x][ "relationship_type" ], "familiar" ) == 0 ) { familiar_id = pet[x].GetInt( "related_id" ); Notify2( LOG_MARRIAGE, "Successfully loaded familair for %s", name.GetData() ); break; } } for ( x = 0; x < owner.Count(); x++ ) { if ( strcmp( owner[x][ "relationship_type" ], "familiar" ) == 0 ) { owner_id = owner[x].GetInt( "character_id" ); Notify2( LOG_MARRIAGE, "Successfully loaded owner for %s", name.GetData() ); break; } } return true; } void psCharacter::SetLastLoginTime(const char *last_login, bool save ) { csString timeStr; if ( !last_login ) { time_t curr=time(0); tm* localtm = localtime(&curr); timeStr.Format("%d-%02d-%02d %02d:%02d:%02d", localtm->tm_year+1900, localtm->tm_mon+1, localtm->tm_mday, localtm->tm_hour, localtm->tm_min, localtm->tm_sec); } else { timeStr = last_login; } this->lastlogintime = timeStr; if ( save ) { //Store in database if(!db->Command("UPDATE characters SET last_login='%s' WHERE id='%d'", timeStr.GetData(), this->GetCharacterID())) { Error2( "Last login storage: DB Error: %s\n", db->GetLastError() ); return; } } } csString psCharacter::GetLastLoginTime() { return this->lastlogintime; } bool psCharacter::LoadSpells(unsigned int use_id) { // Load spells in asc since we use push to create the spell list. Result spells(db->Select("SELECT * from player_spells where player_id=%u order by spell_slot asc",use_id)); if (spells.IsValid()) { int i,count=spells.Count(); for (i=0;iSelect("SELECT * from character_advantages where character_id=%u",use_id)); if (adv.IsValid()) { unsigned int i; for (i=0;iSelect("SELECT * from character_skills where character_id=%u",use_id)); for ( int z = 0; z < PSSKILL_COUNT; z++ ) { skills.SetSkillInfo( (PSSKILL)z, CacheManager::GetSingleton().GetSkillByID((PSSKILL)z), false ); } if (skillResult.IsValid()) { unsigned int i; for (i=0;iSelect("SELECT * from character_traits where character_id=%u",use_id)); if (traits.IsValid()) { unsigned int i; for (i=0;ilocation,trait); } return true; } else return false; } void psCharacter::AddSpell(psSpell * spell) { spellList.Push(spell); } void psCharacter::SetFullName(const char* newFirstName, const char* newLastName) { if ( !newFirstName ) { Error1( "Null passed as first name..." ); return; } // Error3( "SetFullName( %s, %s ) called...", newFirstName, newLastName ); // Update fist, last & full name if ( strlen(newFirstName) ) { name = newFirstName; fullname = name; } if ( newLastName ) { lastname = newLastName; if ( strlen(newLastName) ) { fullname += " "; fullname += lastname; } } //Error2( "New fullname is now: %s", fullname.GetData() ); } void psCharacter::SetRaceInfo(psRaceInfo *rinfo) { raceinfo=rinfo; if ( !rinfo ) return; attributes.SetStat(PSITEMSTATS_STAT_STRENGTH,(unsigned int)rinfo->GetBaseAttribute(PSITEMSTATS_STAT_STRENGTH), false ); attributes.SetStat(PSITEMSTATS_STAT_AGILITY,(unsigned int)rinfo->GetBaseAttribute(PSITEMSTATS_STAT_AGILITY), false); attributes.SetStat(PSITEMSTATS_STAT_ENDURANCE,(unsigned int)rinfo->GetBaseAttribute(PSITEMSTATS_STAT_ENDURANCE), false); attributes.SetStat(PSITEMSTATS_STAT_INTELLIGENCE,(unsigned int)rinfo->GetBaseAttribute(PSITEMSTATS_STAT_INTELLIGENCE), false); attributes.SetStat(PSITEMSTATS_STAT_WILL,(unsigned int)rinfo->GetBaseAttribute(PSITEMSTATS_STAT_WILL), false); attributes.SetStat(PSITEMSTATS_STAT_CHARISMA,(unsigned int)rinfo->GetBaseAttribute(PSITEMSTATS_STAT_CHARISMA), false); // Load base clothes armor or natural armor psItemStats *armoritem; if (rinfo->natural_armor_id==0) { armoritem = CacheManager::GetSingleton().GetBasicItemStatsByName("basecloths"); } else { armoritem = CacheManager::GetSingleton().GetBasicItemStatsByID(rinfo->natural_armor_id); } if (armoritem==NULL) { Error3( "Couldn't load base armor ID: %u for char: %u!", rinfo->natural_armor_id, characterid ); return; } inventory.GetEquipmentObject(PSCHARACTER_SLOT_ARMS).default_if_empty = armoritem->InstantiateBasicItem(); inventory.GetEquipmentObject(PSCHARACTER_SLOT_BOOTS).default_if_empty = armoritem->InstantiateBasicItem(); inventory.GetEquipmentObject(PSCHARACTER_SLOT_GLOVES).default_if_empty = armoritem->InstantiateBasicItem(); inventory.GetEquipmentObject(PSCHARACTER_SLOT_HEAD).default_if_empty = armoritem->InstantiateBasicItem(); inventory.GetEquipmentObject(PSCHARACTER_SLOT_TORSO).default_if_empty = armoritem->InstantiateBasicItem(); inventory.GetEquipmentObject(PSCHARACTER_SLOT_LEGS).default_if_empty = armoritem->InstantiateBasicItem(); } void psCharacter::SetFamiliarID( int v ) { csString sql; familiar_id = v; sql.Format( "insert into character_relationships values ( %d, %d, 'familiar', '' )", characterid, familiar_id ); if( !db->Command( sql ) ) { Error3( "Couldn't execute SQL %s!, Character %u's pet relationship is not saved.", sql.GetData(), characterid ); } }; void psCharacter::AddAdvantage( PSCHARACTER_ADVANTAGE advantage) { if (advantage<0 || advantage>=PSCHARACTER_ADVANTAGE_COUNT) return; // Get the index into the advantages array. 32 bits per entry. int advantage_index=(int)advantage/32; // Get the bit offset in the advantage entry, and use it to generate a single bit bitmask. unsigned int advantage_bitmask=1 << ((unsigned int)advantage % 32); // Set the bit advantage_bitfield[advantage_index]|=advantage_bitmask; } void psCharacter::RemoveAdvantage( PSCHARACTER_ADVANTAGE advantage) { if (advantage<0 || advantage>=PSCHARACTER_ADVANTAGE_COUNT) return; // Get the index into the advantages array. 32 bits per entry. int advantage_index=(int)advantage/32; // Get the bit offset in the advantage entry, and use it to generate an inverse bitmask. unsigned int advantage_bitmask=~( 1 << ((unsigned int)advantage % 32)); // Clear the bit advantage_bitfield[advantage_index]&=advantage_bitmask; } bool psCharacter::HasAdvantage ( PSCHARACTER_ADVANTAGE advantage) { if (advantage<0 || advantage>=PSCHARACTER_ADVANTAGE_COUNT) return false; // Get the index into the advantages array. 32 bits per entry. int advantage_index=(int)advantage/32; // Get the bit offset in the advantage entry, and use it to generate a single bit bitmask. unsigned int advantage_bitmask=1 << ((unsigned int)advantage % 32); // This will return a value other than 0 if the bit is set. return advantage_bitfield[advantage_index] & advantage_bitmask ? true : false; } int psCharacter::GetExperiencePoints() // W { return vitals->GetExp(); } void psCharacter::SetExperiencePoints(int W) { vitals->SetExp(W); } /* * Will adde W to the experience points. While the number * of experience points are greater than needed points * for progression points the experience points are transformed * into progression points. * @return Return the number of progression points gained. */ int psCharacter::AddExperiencePoints(int W) { int pp = 0; int exp = vitals->GetExp(); int progP = vitals->GetPP(); exp += W; bool updatedPP = false; while (exp >= 200) { exp -= 200; progP++; pp++; updatedPP = true; } vitals->SetExp(exp); if(updatedPP) SetProgressionPoints(progP,true); return pp; } void psCharacter::SetSpouseName( const char* name ) { if ( !name ) return; spouseName = name; if ( !strcmp(name,"") ) isMarried = false; else isMarried = true; } unsigned int psCharacter::GetProgressionPoints() // X { return vitals->GetPP(); } void psCharacter::SetProgressionPoints(unsigned int X,bool save) { if (save) { Debug2(LOG_SKILLXP,GetCharacterID(),"Updating PP points to %u\n",X); // Update the DB csString sql; sql.Format("UPDATE characters SET progression_points = '%u' WHERE id ='%u'",X,GetCharacterID()); if(!db->Command(sql)) { Error3("Couldn't execute SQL %s!, Character %u's PP points are NOT saved",sql.GetData(),GetCharacterID()); } } vitals->SetPP( X ); } void psCharacter::UseProgressionPoints(unsigned int X) { SetProgressionPoints(vitals->GetPP()-X,true); } void psCharacter::InterruptSpellCasting() { if (spellCasting != NULL) spellCasting->Interrupt(); SetSpellCasting(NULL); } void psCharacter::SetTradeWork(psWorkGameEvent * event) { workEvent = event; /* if (workEvent != NULL) { SetMode(PSCHARACTER_MODE_WORK,0); } else { SetMode(PSCHARACTER_MODE_PEACE,0); } */ } /* void psCharacter::InterruptTradeWork() { if (workEvent) { // SetMode(PSCHARACTER_MODE_PEACE,0); // workInfo->Reset(); } } */ float psCharacter::GetPowerLevel( PSSKILL skill ) { float waySkillRank = (float)skills.GetSkillRank(skill); MathScriptVar* waySkillVar = powerScript->GetVar("WaySkill"); MathScriptVar* kVar = powerScript->GetVar("KFactor"); MathScriptVar* powerLevel = powerScript->GetVar("PowerLevel"); if(!kVar) { Warning1(LOG_SPELLS,"Couldn't find the KFactor var in powerlevel script!\n"); return 0.0f; } if(!waySkillVar) { Warning1(LOG_SPELLS,"Couldn't find the WaySkill var in powerlevel script!\n"); return 0.0f; } if(!powerLevel) { Warning1(LOG_SPELLS,"Couldn't find the PowerLevel var in powerlevel script!\n"); return 0.0f; } kVar->SetValue((double)KFactor); waySkillVar->SetValue((double)waySkillRank); powerScript->Execute(); return (float)powerLevel->GetValue(); } int psCharacter::GetMaxAllowedRealm( PSSKILL skill ) { int waySkillRank = skills.GetSkillRank(skill); if (waySkillRank == 0) // zero skill = no casting return 0; if (!maxRealmScript) { Error1("No \"MaxRealm\" script!"); return 0; } MathScriptVar* waySkillVar = maxRealmScript->GetVar("WaySkill"); MathScriptVar* maxRealmVar = maxRealmScript->GetVar("MaxRealm"); if(!waySkillVar) { Warning1(LOG_SPELLS,"Couldn't find the WaySkill var in MaxRealm script!"); return 0; } if(!maxRealmVar) { Warning1(LOG_SPELLS,"Couldn't find the MaxRealm var in MaxRealm script!"); return 0; } waySkillVar->SetValue((double)waySkillRank); maxRealmScript->Execute(); return (int)maxRealmVar->GetValue(); } bool psCharacter::CheckMagicKnowledge( PSSKILL skill, int realm ) { Skill * skillRec = skills.GetSkill(skill); if (!skillRec) return false; if (GetMaxAllowedRealm(skill) >= realm) return true; // Special case for rank 0 people just starting. if (skillRec->rank==0 && !skillRec->CanTrain() && realm==1) return true; else return false; } float psCharacter::GetPowerLevel() { // When casting a spell the spell casting event is stored in spellCasting. // This function is only legal to call when a spell is casted. if (spellCasting == NULL) { Error2("Character %s isn't casting a spell and GetPowerLevel is called",GetCharName()); return 0.0; } return GetPowerLevel(spellCasting->spell->GetSkill()); } const char * psCharacter::GetModeStr() { return player_mode_to_str[player_mode]; } void psCharacter::SetMode(PSCHARACTER_MODE newmode) { if (player_mode == newmode) return; uint8_t movemode; switch ( newmode ) { default: case PSCHARACTER_MODE_UNKNOWN: { Error3("Unhandled mode: %d switching to %d",player_mode,newmode); return; } case PSCHARACTER_MODE_PEACE: case PSCHARACTER_MODE_SPELL_CASTING: case PSCHARACTER_MODE_WORK: case PSCHARACTER_MODE_EXHAUSTED: { static uint8_t modeID = CacheManager::GetSingleton().GetCharModeID("normal"); movemode = modeID; break; } case PSCHARACTER_MODE_COMBAT: { static uint8_t modeID = CacheManager::GetSingleton().GetCharModeID("combat"); movemode = modeID; break; } case PSCHARACTER_MODE_DEAD: { static uint8_t modeID = CacheManager::GetSingleton().GetCharModeID("dead"); movemode = modeID; break; } case PSCHARACTER_MODE_SIT: { static uint8_t modeID = CacheManager::GetSingleton().GetCharModeID("sit"); movemode = modeID; break; } } actor->SetAllowedToMove(newmode != PSCHARACTER_MODE_DEAD && newmode != PSCHARACTER_MODE_SIT && newmode != PSCHARACTER_MODE_EXHAUSTED); actor->SetAlive(newmode != PSCHARACTER_MODE_DEAD); actor->SetMovementMode(movemode); if (newmode != PSCHARACTER_MODE_COMBAT) SetCombatStance(PSCHARACTER_STANCE_NONE); //cancel ongoing work if(player_mode == PSCHARACTER_MODE_WORK){ if (workEvent) { workEvent->Interrupt(); workEvent = NULL; } } switch( newmode ) { case PSCHARACTER_MODE_PEACE: case PSCHARACTER_MODE_SIT: case PSCHARACTER_MODE_EXHAUSTED: SetStaminaRegenerationStill(); // start stamina regen break; case PSCHARACTER_MODE_SPELL_CASTING: case PSCHARACTER_MODE_COMBAT: case PSCHARACTER_MODE_DEAD: SetStaminaRegenerationNone(); // no stamina regen while busy break; case PSCHARACTER_MODE_WORK: break; // work manager sets it's own rates } player_mode = newmode; } void psCharacter::SetCombatStance(PSCHARACTER_STANCE stance) { // NPCs don't have stances yet if (actor->GetClientID()==0) return; // Stance never changes from PSCHARACTER_STANCE_NORMAL for NPCs if (combat_stance == stance) return; combat_stance = stance; Debug3(LOG_COMBAT,GetCharacterID(),"Setting stance to %d for %s",stance,actor->GetName()); psStanceMessage msg(actor->GetClientID(),stance); msg.SendMessage(); } bool psCharacter::MoveToInventory( psItem * & itemdata ) { if (itemdata==NULL) return false; if ( itemdata->GetBaseStats()->IsMoney() ) { SetMoney( itemdata ); return true; } int cnum = actor->GetClientID(); if ( inventory.PutInBulk(itemdata) == 0 ) { // If the item can be put in the in the inventory play a sound psserver->GetCharManager()->SendOutPlaySoundMessage(cnum, itemdata->GetSound(), "pickup" ); // Refresh the player's inventory psserver->GetCharManager()->SendInventory(cnum); if (IsNPC() || IsPet()) { psserver->GetNPCManager()->QueueInventoryPerception(GetActor(),itemdata,true); } return true; } else { // The calling function is responsible for dropping or deleting this item. return false; } } void psCharacter::DropItem( psItem * & item, bool transient, float angle ) { if (!item) return; int bulkslot = inventory.FindItemInTopLevelBulk(item); int equipslot = inventory.FindItemInTopLevelEquipment(item); // If we're dropping from inventory, we should propperly remove it. if ( bulkslot > -1 ) item = inventory.RemoveBulk(bulkslot); else if ( equipslot > -1 ) item = inventory.RemoveEquipment(equipslot); if (angle < 0) // If not given an angle, drop in a random direction angle = psserver->rng->Get()*2*PI; else // Randomise slightly { angle += psserver->rng->Get()*(PI / 2) - (PI / 4); if(angle < 0) angle += 2 * PI; else if (angle > 2 * PI) angle -= 2 * PI; } item->SetOwningCharacter(NULL); item->SetLocationInWorld( location.loc_sector, location.loc_x + sin(angle), location.loc_y, location.loc_z + cos(angle), angle ); EntityManager::GetSingleton().CreateItem( item, transient ); // Play the drop item sound for this item psserver->GetCharManager()->SendOutPlaySoundMessage( actor->GetClientID(), item->GetSound(), "drop" ); // If this item is a purified glyph, we need to reset it for the next character if ( item->GetPurifyStatus() ) dynamic_cast(item)->UnPurify(); item->Save(true); // Announce drop (disabled pending a drop animation) //psSystemMessage newmsg(actor->GetClientID(), MSG_INFO, "%s dropped %s", name.GetData(), item->GetQuantityName().GetData() ); //newmsg.Multicast(actor->GetMulticastClients(),0,RANGE_TO_SELECT); } void psCharacter::CalculateEquipmentModifiers() { modifiers.Clear(); // Loop through every holding item for(int i = 0; i < PSCHARACTER_SLOT_COUNT; i++) { psItem *currentitem = NULL; currentitem=inventory.GetEquipmentItem(i); if(!currentitem) continue; // Check for attr bonuses for(int z = 0; z < PSITEMSTATS_STAT_BONUS_INDEX_COUNT; z++) { PSITEMSTATS_STAT_BONUS_INDEX stat; if(z == 0) stat = PSITEMSTATS_STAT_BONUS_INDEX_0; else if( z == 1) stat = PSITEMSTATS_STAT_BONUS_INDEX_1; else if( z == 2) stat = PSITEMSTATS_STAT_BONUS_INDEX_2; float bonus = currentitem->GetWeaponAttributeBonusMax(stat); // Add to right var modifiers.AddToStat(currentitem->GetWeaponAttributeBonusType(stat), (int)bonus); } } } void psCharacter::AddInventoryToLoot() { for (unsigned int n = 0; n < PSCHARACTER_BULK_COUNT; n++) { psItem *item = inventory.GetBulkItem(n); if (item) { AddLootItem(item->GetCurrentStats()); } } } void psCharacter::AddLootItem(psItemStats *item) { if(!item){ Error2("Attempted to add 'null' loot item to character %s, ignored.",fullname.GetDataSafe()); } else { loot_pending.Push(item); } } size_t psCharacter::GetLootItems(psLootMessage& msg,int entity,int cnum) { // adds inventory to loot. TEMPORARLY REMOVED. see KillNPC() //if ( loot_pending.Length() == 0 ) // AddInventoryToLoot(); if (loot_pending.Length() ) { csString loot; loot.Append(""); for (size_t i=0; iGetImageName()); csString escpxml_name = EscpXML(loot_pending[i]->GetName()); item.Format("
  • ", escpxml_imagename.GetData(), escpxml_name.GetData(), loot_pending[i]->GetUID()); loot.Append(item); } loot.Append("
    "); Debug3(LOG_COMBAT, GetCharacterID(), "Loot was %s for %s\n",loot.GetData(), name.GetData()); msg.Populate(entity,loot,cnum); } return loot_pending.Length(); } bool psCharacter::RemoveLootItem(int id) { size_t x; for (x=0; xGetUID() == (uint32) id) { loot_pending.DeleteIndex(x); return true; } } return false; } void psCharacter::ClearLoot() { loot_pending.DeleteAll(); loot_money = 0; } void psCharacter::SetMoney(psMoney m) { money=m; SaveMoney(); } void psCharacter::SetMoney( psItem *& itemdata ) { /// Check to see if the item is a money item and treat as a special case. if ( itemdata->GetBaseStats()->GetFlags() & PSITEMSTATS_FLAG_TRIA ) money.AdjustTrias( itemdata->GetStackCount() ); if ( itemdata->GetBaseStats()->GetFlags() & PSITEMSTATS_FLAG_HEXA ) money.AdjustHexas( itemdata->GetStackCount() ); if ( itemdata->GetBaseStats()->GetFlags() & PSITEMSTATS_FLAG_OCTA ) money.AdjustOctas( itemdata->GetStackCount() ); if ( itemdata->GetBaseStats()->GetFlags() & PSITEMSTATS_FLAG_CIRCLE ) money.AdjustCircles( itemdata->GetStackCount() ); itemdata->ItemAboutToMove(); CacheManager::GetSingleton().RemoveInstance(itemdata); } void psCharacter::AdjustMoney(psMoney m) { money.Adjust( MONEY_TRIAS, m.GetTrias() ); money.Adjust( MONEY_HEXAS, m.GetHexas() ); money.Adjust( MONEY_OCTAS, m.GetOctas() ); money.Adjust( MONEY_CIRCLES, m.GetCircles() ); SaveMoney(); } void psCharacter::SaveMoney() { if(!loaded) return; psMoney & m = money; psString sql; sql.AppendFmt("update characters set money_circles=%u, money_trias=%u, money_hexas=%u, money_octas=%u where id=%u", m.GetCircles(), m.GetTrias(), m.GetHexas(), m.GetOctas(), GetCharacterID()); if (db->Command(sql) != 1) { Error3 ("Couldn't save character's money to database.\nCommand was " "<%s>.\nError returned was <%s>\n",db->GetLastQuery(),db->GetLastError()); } } void psCharacter::ResetStats() { vitals->ResetVitals(); } void psCharacter::CombatDrain(int slot) { float mntdrain = 1.0f; float phydrain = 1.0f; static MathScript *costScript = NULL; if (!costScript) costScript = psserver->GetMathScriptEngine()->FindScript("StaminaCombat"); if ( costScript ) { // Output MathScriptVar* PhyDrain = costScript->GetVar("PhyDrain"); MathScriptVar* MntDrain = costScript->GetVar("MntDrain"); // Input MathScriptVar* actor = costScript->GetOrCreateVar("Actor"); MathScriptVar* weapon = costScript->GetOrCreateVar("Weapon"); if(!PhyDrain || !MntDrain) { Error1("Couldn't find the PhyDrain output var in StaminaCombat script!"); return; } // Input the data actor->SetObject(this); weapon->SetObject(inventory.GetEquipmentObject(slot).item); costScript->Execute(); // Get the output phydrain = PhyDrain->GetValue(); mntdrain = MntDrain->GetValue(); } AdjustStamina(-phydrain, true); AdjustStamina(-mntdrain, false); } float psCharacter::GetHitPointsMax() { return vitals->GetVital(VITAL_HITPOINTS).max; } float psCharacter::GetManaMax() { return vitals->GetVital(VITAL_MANA).max; } float psCharacter::GetStaminaMax(bool pys) { if(pys) return vitals->GetVital(VITAL_PYSSTAMINA).max; else return vitals->GetVital(VITAL_MENSTAMINA).max; } float psCharacter::AdjustHitPoints(float adjust) { return AdjustVital(VITAL_HITPOINTS, DIRTY_VITAL_HP,adjust); } void psCharacter::SetHitPoints(float v) { SetVital(VITAL_HITPOINTS, DIRTY_VITAL_HP,v); } float psCharacter::AdjustHitPointsMax(float adjust) { vitals->DirtyVital(VITAL_HITPOINTS, DIRTY_VITAL_HP_MAX).max += adjust; return vitals->GetVital(VITAL_HITPOINTS).max; } void psCharacter::SetHitPointsMax(float v) { CS_ASSERT_MSG("Negative Max HP!", v > -0.01f); vitals->DirtyVital(VITAL_HITPOINTS, DIRTY_VITAL_HP_MAX).max = v; } float psCharacter::GetHP() { return vitals->GetHP(); } float psCharacter::GetMana() { return vitals->GetMana(); } float psCharacter::GetStamina(bool pys) { return vitals->GetStamina(pys); } float psCharacter::AdjustVital( int vitalName, int dirtyFlag, float adjust ) { vitals->DirtyVital(vitalName, dirtyFlag).value += adjust; if (vitals->GetVital(vitalName).value < 0) vitals->GetVital(vitalName).value = 0; if (vitals->GetVital(vitalName).value > vitals->GetVital(vitalName).max) vitals->GetVital(vitalName).value = vitals->GetVital(vitalName).max; return vitals->GetVital(vitalName).value; } float psCharacter::SetVital( int vitalName, int dirtyFlag, float value ) { vitals->DirtyVital(vitalName, dirtyFlag).value = value; if (vitals->GetVital(vitalName).value < 0) vitals->GetVital(vitalName).value = 0; if (vitals->GetVital(vitalName).value > vitals->GetVital(vitalName).max) vitals->GetVital(vitalName).value = vitals->GetVital(vitalName).max; return vitals->GetVital(vitalName).value; } bool psCharacter::UpdateStatDRData(csTicks now) { bool res = vitals->Update(now); // if HP dropped to zero, provoke the killing process if (GetHP() == 0 && actor != NULL && actor->IsAlive()) actor->Kill(NULL); return res; } bool psCharacter::GetStatDRData(MsgEntry* me, int flags) { return vitals->ConstructDRData(me,flags); } float psCharacter::AdjustHitPointsRate(float adjust) { vitals->DirtyVital(VITAL_HITPOINTS, DIRTY_VITAL_HP_RATE).drRate += adjust; return vitals->GetVital(VITAL_HITPOINTS).drRate; } void psCharacter::SetHitPointsRate(float v) { vitals->DirtyVital(VITAL_HITPOINTS, DIRTY_VITAL_HP).drRate = v; } float psCharacter::AdjustMana(float adjust) { return AdjustVital(VITAL_MANA, DIRTY_VITAL_MANA,adjust); } void psCharacter::SetMana(float v) { SetVital(VITAL_MANA, DIRTY_VITAL_MANA,v); } float psCharacter::AdjustManaMax(float adjust) { vitals->DirtyVital(VITAL_MANA, DIRTY_VITAL_MANA_MAX).max += adjust; return vitals->GetVital(VITAL_MANA).max; } void psCharacter::SetManaMax(float v) { CS_ASSERT_MSG("Negative Max MP!", v > -0.01f); vitals->DirtyVital(VITAL_MANA, DIRTY_VITAL_MANA_MAX).max = v; } float psCharacter::AdjustManaRate(float adjust) { vitals->DirtyVital(VITAL_MANA, DIRTY_VITAL_MANA_RATE).drRate += adjust; return vitals->GetVital(VITAL_MANA).drRate; } void psCharacter::SetManaRate(float v) { vitals->DirtyVital(VITAL_MANA, DIRTY_VITAL_MANA_RATE).drRate = v; } float psCharacter::AdjustStamina(float adjust,bool pys) { if(pys) return AdjustVital(VITAL_PYSSTAMINA, DIRTY_VITAL_PYSSTAMINA,adjust); else return AdjustVital(VITAL_MENSTAMINA, DIRTY_VITAL_MENSTAMINA,adjust); } void psCharacter::SetStamina(float v,bool pys) { if(pys) SetVital(VITAL_PYSSTAMINA, DIRTY_VITAL_PYSSTAMINA,v); else SetVital(VITAL_MENSTAMINA, DIRTY_VITAL_MENSTAMINA,v); } float psCharacter::AdjustStaminaMax(float adjust,bool pys) { if(pys) { vitals->DirtyVital(VITAL_PYSSTAMINA, DIRTY_VITAL_PYSSTAMINA_MAX).max += adjust; return vitals->GetVital(VITAL_PYSSTAMINA).max; } else { vitals->DirtyVital(VITAL_MENSTAMINA, DIRTY_VITAL_MENSTAMINA_MAX).max += adjust; return vitals->GetVital(VITAL_MENSTAMINA).max; } } void psCharacter::SetStaminaRegenerationNone(bool physical,bool mental) { if(physical) SetStaminaRate(0.0f,true); if(mental) SetStaminaRate(0.0f,false); } void psCharacter::SetStaminaRegenerationWalk(bool physical,bool mental) { if(physical) SetStaminaRate(GetRaceInfo()->baseRegen[PSRACEINFO_STAMINA_PHYSICAL_WALK],true); if(mental) SetStaminaRate(GetRaceInfo()->baseRegen[PSRACEINFO_STAMINA_MENTAL_WALK] ,false); } void psCharacter::SetStaminaRegenerationStill(bool physical,bool mental) { if(physical) SetStaminaRate(GetRaceInfo()->baseRegen[PSRACEINFO_STAMINA_PHYSICAL_STILL],true); if(mental) SetStaminaRate(GetRaceInfo()->baseRegen[PSRACEINFO_STAMINA_MENTAL_STILL] ,false); } void psCharacter::SetStaminaRegenerationWork(int skill) { //Gms don't want to lose stamina when testing if (actor->nevertired) return; int factor = CacheManager::GetSingleton().GetSkillByID(skill)->mental_factor; SetStaminaRate(-5.0*(100-factor)/100, true); SetStaminaRate(-5.0*factor/100, false); } void psCharacter::CalculateMaxStamina() { if(!staminaCalc) { CPrintf(CON_ERROR,"Called CalculateMaxStamina without mathscript!!!!"); return; } // Set all the skills vars staminaCalc->GetOrCreateVar("STR") ->SetValue(attributes.GetStat(PSITEMSTATS_STAT_STRENGTH)); staminaCalc->GetOrCreateVar("END") ->SetValue(attributes.GetStat(PSITEMSTATS_STAT_ENDURANCE)); staminaCalc->GetOrCreateVar("AGI") ->SetValue(attributes.GetStat(PSITEMSTATS_STAT_AGILITY)); staminaCalc->GetOrCreateVar("INT") ->SetValue(attributes.GetStat(PSITEMSTATS_STAT_INTELLIGENCE)); staminaCalc->GetOrCreateVar("WILL")->SetValue(attributes.GetStat(PSITEMSTATS_STAT_WILL)); staminaCalc->GetOrCreateVar("CHA") ->SetValue(attributes.GetStat(PSITEMSTATS_STAT_CHARISMA)); // Calculate staminaCalc->Execute(); // Set the max values SetStaminaMax((float)staminaCalc->GetOrCreateVar("BasePhy")->GetValue(),true); SetStaminaMax((float)staminaCalc->GetOrCreateVar("BaseMen")->GetValue(),false); } void psCharacter::SetStaminaMax(float v,bool pys) { if (v < 0.0) v =0.1f; if(pys) vitals->DirtyVital(VITAL_PYSSTAMINA, DIRTY_VITAL_PYSSTAMINA_MAX).max = v; else vitals->DirtyVital(VITAL_MENSTAMINA, DIRTY_VITAL_MENSTAMINA_MAX).max = v; } float psCharacter::AdjustStaminaRate(float adjust,bool pys) { if(pys) { vitals->DirtyVital(VITAL_PYSSTAMINA, DIRTY_VITAL_PYSSTAMINA_RATE).drRate += adjust; return vitals->GetVital(VITAL_PYSSTAMINA).drRate; } else { vitals->DirtyVital(VITAL_MENSTAMINA, DIRTY_VITAL_MENSTAMINA_RATE).drRate += adjust; return vitals->GetVital(VITAL_MENSTAMINA).drRate; } } void psCharacter::SetStaminaRate(float v,bool pys) { if(pys) vitals->DirtyVital(VITAL_PYSSTAMINA, DIRTY_VITAL_PYSSTAMINA_RATE).drRate = v; else vitals->DirtyVital(VITAL_MENSTAMINA, DIRTY_VITAL_MENSTAMINA_RATE).drRate = v; } void psCharacter::NotifyAttackPerformed(int slot,csTicks timeofattack) { psItem *Weapon; // Slot out of range if (slot<0 || slot>=PSCHARACTER_SLOT_COUNT) return; // TODO: Reduce ammo if this is an ammunition using weapon // Reset next attack time Weapon=Inventory().GetEffectiveWeaponInSlot(slot); if (Weapon!=NULL) inventory.GetEquipmentObject(slot).NextSwingTime=csTicks(timeofattack+ (Weapon->GetLatency() * 1000.0f)); //drain stamina on player attacks if(actor->GetClientID() && !actor->nevertired) CombatDrain(slot); } csTicks psCharacter::GetSlotNextAttackTime(int slot) { // Slot out of range if (slot<0 || slot>=PSCHARACTER_SLOT_COUNT) return (csTicks)0; return inventory.GetEquipmentObject(slot).NextSwingTime; } // AVPRO= attack Value progression (this is to change the progression of all the calculation in AV for now it is equal to 1) #define AVPRO 1 float psCharacter::GetAttackValueModifier() { return attackValueModifier; } void psCharacter::AdjustAttackValueModifier(float mul) { attackValueModifier *= mul; } float psCharacter::GetDefenseValueModifier() { return defenseValueModifier; } void psCharacter::AdjustDefenseValueModifier(float mul) { defenseValueModifier *= mul; } float psCharacter::GetMeleeDefensiveDamageModifier() { return meleeDefensiveDamageModifier; } void psCharacter::AdjustMeleeDefensiveDamageModifier(float mul) { meleeDefensiveDamageModifier *= mul; } float psCharacter::GetTargetedBlockValueForWeaponInSlot(int slot) { psItem *weapon=Inventory().GetEffectiveWeaponInSlot(slot); if (weapon==NULL) return 0.0f; return weapon->GetTargetedBlockValue(); } float psCharacter::GetUntargetedBlockValueForWeaponInSlot(int slot) { psItem *weapon=Inventory().GetEffectiveWeaponInSlot(slot); if (weapon==NULL) return 0.0f; return weapon->GetUntargetedBlockValue(); } float psCharacter::GetTotalTargetedBlockValue() { float blockval=0.0f; int slot; for (slot=0;slotGetCounterBlockValue(); } #define ARMOR_USES_SKILL(slot,skill) inventory.GetEquipmentItem(slot)==NULL?inventory.GetEquipmentObject(slot).default_if_empty->GetArmorType()==skill:inventory.GetEquipmentItem(slot)->GetArmorType()==skill #define CALCULATE_ARMOR_FOR_SLOT(slot) \ if (ARMOR_USES_SKILL(slot,PSITEMSTATS_ARMORTYPE_LIGHT)) light_p+=1.0f/6.0f; \ if (ARMOR_USES_SKILL(slot,PSITEMSTATS_ARMORTYPE_MEDIUM)) med_p+=1.0f/6.0f; \ if (ARMOR_USES_SKILL(slot,PSITEMSTATS_ARMORTYPE_HEAVY)) heavy_p+=1.0f/6.0f; float psCharacter::GetDodgeValue() { float heavy_p,med_p,light_p; float asdm; // hold the % of each type of armor worn heavy_p=med_p=light_p=0.0f; CALCULATE_ARMOR_FOR_SLOT(PSCHARACTER_SLOT_HEAD); CALCULATE_ARMOR_FOR_SLOT(PSCHARACTER_SLOT_TORSO); CALCULATE_ARMOR_FOR_SLOT(PSCHARACTER_SLOT_ARMS); CALCULATE_ARMOR_FOR_SLOT(PSCHARACTER_SLOT_GLOVES); CALCULATE_ARMOR_FOR_SLOT(PSCHARACTER_SLOT_LEGS); CALCULATE_ARMOR_FOR_SLOT(PSCHARACTER_SLOT_BOOTS); // multiplies for skill heavy_p *= skills.GetSkillRank(PSSKILL_HEAVYARMOR); med_p *= skills.GetSkillRank(PSSKILL_MEDIUMARMOR); light_p *= skills.GetSkillRank(PSSKILL_LIGHTARMOR); // armor skill defense mod asdm=heavy_p+med_p+light_p; // if total skill is 0, give a little chance anyway to defend himself if (asdm==0) asdm=0.2F; // for now return just asdm return asdm; /* // MADM= Martial Arts Defense Mod=martial arts skill-(weight carried+ AGI malus of the armor +DEX malus of the armor) min 0 // TODO: fix this to use armor agi malus madm=GetSkillRank(PSSKILL_MARTIALARTS)-inventory.weight; if (madm<0.0f) madm=0.0f; // Active Dodge Value =ADV = (ASDM + agiDmod + MADM)*ADVP)^0.4 // Out of Melee Defense Value=OMDV = (agiDmod+(ASDM\10)*PDVP)^0.4 // TODO: Use passive dv here when the conditions are finalized that it should be used (archery, casting, more?) dv=asdm + AgiMod + madm; dv=pow(dv,(float)0.6); return dv; */ } void psCharacter::PracticeArmorSkills(unsigned int practice, int attackLocation) { psItem *armor = inventory.GetEffectiveArmorInSlot(attackLocation); switch (armor->GetArmorType()) { case PSITEMSTATS_ARMORTYPE_LIGHT: skills.AddSkillPractice(PSSKILL_LIGHTARMOR,practice); break; case PSITEMSTATS_ARMORTYPE_MEDIUM: skills.AddSkillPractice(PSSKILL_MEDIUMARMOR,practice); break; case PSITEMSTATS_ARMORTYPE_HEAVY: skills.AddSkillPractice(PSSKILL_HEAVYARMOR,practice); break; default: break; } } void psCharacter::PracticeWeaponSkills(unsigned int practice) { int slot; for (slot=0;slotGetWeaponSkill((PSITEMSTATS_WEAPONSKILL_INDEX)index); if (skill != PSSKILL_NONE) skills.AddSkillPractice(skill,practice); } } void psCharacter::SetTraitForLocation(PSTRAIT_LOCATION location,psTrait *trait) { if (location<0 || location>=PSTRAIT_LOCATION_COUNT) return; traits[location]=trait; } psTrait *psCharacter::GetTraitForLocation(PSTRAIT_LOCATION location) { if (location<0 || location>=PSTRAIT_LOCATION_COUNT) return NULL; return traits[location]; } void psCharacter::GetLocationInWorld(psSectorInfo *§orinfo,float &loc_x,float &loc_y,float &loc_z,float &loc_yrot) { sectorinfo=location.loc_sector; loc_x=location.loc_x; loc_y=location.loc_y; loc_z=location.loc_z; loc_yrot=location.loc_yrot; } void psCharacter::SetLocationInWorld(psSectorInfo *sectorinfo,float loc_x,float loc_y,float loc_z,float loc_yrot) { psSectorInfo *oldsector = location.loc_sector; location.loc_sector=sectorinfo; location.loc_x=loc_x; location.loc_y=loc_y; location.loc_z=loc_z; location.loc_yrot=loc_yrot; if (oldsector!=NULL && oldsector!=sectorinfo) { if ( dynamic_cast(actor) == NULL ) // NOT an NPC so it's ok to save location info SaveLocationInWorld(); } } void psCharacter::SaveLocationInWorld() { if(!loaded) return; st_location & l = location; psString sql; sql.AppendFmt("update characters set loc_x=%10.2f, loc_y=%10.2f, loc_z=%10.2f, loc_yrot=%10.2f, loc_sector_id=%u where id=%u", l.loc_x, l.loc_y, l.loc_z, l.loc_yrot, l.loc_sector->uid, characterid); if (db->Command(sql) != 1) { Error3 ("Couldn't save character's position to database.\nCommand was " "<%s>.\nError returned was <%s>\n",db->GetLastQuery(),db->GetLastError()); } } psSpell * psCharacter::GetSpellByName(const csString& spellName) { for (size_t i=0; i < spellList.Length(); i++) { if (spellList[i]->GetName() == spellName) return spellList[i]; } return NULL; } psSpell * psCharacter::GetSpellByIdx(int index) { if (index < 0 || (size_t)index >= spellList.Length()) return NULL; return spellList[index]; } csString psCharacter::GetXMLSpellList() { csString buff = ""; for (size_t i=0; i < spellList.Length(); i++) { buff.Append(spellList[i]->SpellToXML()); } buff.Append(""); return buff; } bool psCharacter::SetTradingStopped(bool stopped) { bool old = tradingStopped; tradingStopped=stopped; return old; } // Check if player and target is ready to do a exchange // - Not fighting // - Not casting spell // - Not exchanging with a third player // - Not player stopped trading // - Not trading with a merchant bool psCharacter::ReadyToExchange() { return (//TODO: Test for fighting && //TODO: Test for casting spell // !exchangeMgr.IsValid() && !tradingStopped && tradingStatus == NOT_TRADING); } csArray psCharacterInventory::GetItemsInCategory(psItemCategory * category) { csArray items; for (int i = 0; i < PSCHARACTER_BULK_COUNT; i ++) { if (bulk[i]) { if (bulk[i]->GetCategory() == category) { items.Push(bulk[i]); } } } return items; } void psCharacter::MakeTextureString( csString& traits) { // initialize string traits = ""; // cycle through and add entries for each part for (unsigned int i=0;iToXML(true); traits.Append(buff); trait = trait->next_trait; } } // terminate string traits.Append(""); Notify2( LOG_CHARACTER, "Traits string: %s", (const char*)traits ); } void psCharacter::MakeEquipmentString( csString& equipment ) { equipment = ""; equipment.AppendFmt("%s", EscpXML(helmGroup).GetData()); for (int i=0; iGetMeshName() ); csString part = EscpXML( item->GetPartName() ); csString texture = EscpXML( item->GetTextureName() ); csString partMesh = EscpXML( item->GetPartMeshName() ); //if (part.Length() && texture.Length()) // Worn //{ // equipment.AppendFmt("", part.GetData(), texture.GetData() ); //} //else if (slot.Length() && mesh.Length()) // Weilded //{ equipment.AppendFmt("", slot.GetData(), mesh.GetData(), part.GetData(), texture.GetData(), partMesh.GetData() ); //} } equipment.Append(""); Notify2( LOG_CHARACTER, "Equipment string: %s", equipment.GetData() ); } bool psCharacter::AppendCharacterSelectData(psAuthApprovedMessage& auth) { csString traits; csString equipment; MakeTextureString( traits ); MakeEquipmentString( equipment ); auth.AddCharacter(fullname, raceinfo->name, raceinfo->mesh_name, traits, equipment); return true; } QuestAssignment *psCharacter::IsQuestAssigned(int id) { for (size_t i=0; iquest.IsValid() && assigned_quests[i]->quest->GetID() == id && assigned_quests[i]->status != PSQUEST_DELETE) return assigned_quests[i]; } return NULL; } size_t psCharacter::GetAssignedQuests(psQuestListMessage& questmsg,int cnum) { if (assigned_quests.Length() ) { csString quests; quests.Append(""); csArray::Iterator iter = assigned_quests.GetIterator(); while(iter.HasNext()) { QuestAssignment* assigned_quest = iter.Next(); // exclude deleted if (assigned_quest->status == PSQUEST_DELETE || !assigned_quest->quest.IsValid()) continue; // exclude substeps if (assigned_quest->quest->GetParentQuest()) continue; csString item; csString escpxml_image = EscpXML(assigned_quest->quest->GetImage()); csString escpxml_name = EscpXML(assigned_quest->quest->GetName()); item.Format("", escpxml_image.GetData(), escpxml_name.GetData(), assigned_quest->quest->GetID(), assigned_quest->status ); quests.Append(item); } quests.Append(""); Debug2(LOG_QUESTS, GetCharacterID(), "QuestMsg was %s\n",quests.GetData() ); questmsg.Populate(quests,cnum); } return assigned_quests.Length(); } QuestAssignment *psCharacter::AssignQuest(psQuest *quest, int assigner_id) { CS_ASSERT( quest ); // Must not be NULL QuestAssignment *q = IsQuestAssigned(quest->GetID() ); if (!q) // make new entry if needed, reuse if old { q = new QuestAssignment; q->quest = quest; q->status = PSQUEST_DELETE; assigned_quests.Push(q); } if (q->status != PSQUEST_ASSIGNED) { q->dirty = true; q->status = PSQUEST_ASSIGNED; q->lockout_end = 0; q->assigner_id = assigner_id; // assign any skipped parent quests if (quest->GetParentQuest() && !IsQuestAssigned(quest->GetParentQuest()->GetID())) AssignQuest(quest->GetParentQuest(),assigner_id ); // assign any skipped sub quests csHash::GlobalIterator it = CacheManager::GetSingleton().GetQuestIterator(); while (it.HasNext()) { psQuest * q = it.Next(); if (q->GetParentQuest()) { if (q->GetParentQuest()->GetID() == quest->GetID()) AssignQuest(q,assigner_id); } } q->quest->SetQuestLastActivatedTime( csGetTicks() ); Debug3(LOG_QUESTS, GetCharacterID(), "Assigned quest '%s' to player '%s'\n",quest->GetName(),GetCharName() ); UpdateQuestAssignments(); } else { Debug3(LOG_QUESTS, GetCharacterID(), "Did not assign %s quest to %s because it was already assigned.\n",quest->GetName(),GetCharName() ); } return q; } bool psCharacter::CompleteQuest(psQuest *quest) { CS_ASSERT( quest ); // Must not be NULL QuestAssignment *q = IsQuestAssigned( quest->GetID() ); QuestAssignment *parent = NULL; // substeps are not assigned, so the above check fails for substeps. // in this case we check if the parent quest is assigned if (!q && quest->GetParentQuest()) { parent = IsQuestAssigned(quest->GetParentQuest()->GetID() ); } // create an assignment for the substep if parent is valid if (parent) { q = AssignQuest(quest,parent->assigner_id); } if (q) { if (q->status == PSQUEST_DELETE || q->status == PSQUEST_COMPLETE) { Debug3(LOG_QUESTS, GetCharacterID(), "Player '%s' has already completed quest '%s'. No credit.\n",GetCharName(),quest->GetName() ); return false; // already completed, so no credit here } q->dirty = true; q->status = PSQUEST_COMPLETE; // completed q->lockout_end = csGetTicks() + q->quest->GetPlayerLockoutTime(); // Complete all substeps if this is the parent quest if (!q->quest->GetParentQuest()) { // assign any skipped sub quests csHash::GlobalIterator it = CacheManager::GetSingleton().GetQuestIterator(); while (it.HasNext()) { psQuest * q = it.Next(); if (q->GetParentQuest()) { if (q->GetParentQuest()->GetID() == quest->GetID()) CompleteQuest(q); } } } Debug3(LOG_QUESTS, GetCharacterID(), "Player '%s' just completed quest '%s'.\n",GetCharName(),quest->GetName() ); UpdateQuestAssignments(); return true; } return false; } void psCharacter::DiscardQuest(QuestAssignment *q) { CS_ASSERT( q ); // Must not be NULL if (q->status != PSQUEST_DELETE) { q->dirty = true; q->status = PSQUEST_DELETE; // discarded q->lockout_end = csGetTicks() + q->quest->GetPlayerLockoutTime(); // assignment entry will be deleted after expiration Debug3(LOG_QUESTS, GetCharacterID(), "Player '%s' just discarded quest '%s'.\n",GetCharName(),q->quest->GetName() ); UpdateQuestAssignments(); } else { Debug3(LOG_QUESTS, GetCharacterID(), "Did not discard %s quest for player %s because it was already discarded.\n",q->quest->GetName(),GetCharName() ); } } bool psCharacter::CheckQuestAssigned(psQuest *quest) { CS_ASSERT( quest ); // Must not be NULL QuestAssignment* questAssignment = IsQuestAssigned( quest->GetID() ); if ( questAssignment ) { if ( questAssignment->status == PSQUEST_ASSIGNED) return true; } else Warning4( LOG_QUESTS, "No QuestAssignment for quest '%s'(%d) for player '%s'.\n", quest->GetName(),quest->GetID(), GetCharName() ); return false; } bool psCharacter::CheckQuestCompleted(psQuest *quest) { CS_ASSERT( quest ); // Must not be NULL QuestAssignment* questAssignment = IsQuestAssigned( quest->GetID() ); if ( questAssignment ) { if ( questAssignment->status == PSQUEST_COMPLETE) return true; } else Warning4( LOG_QUESTS, "QuestAssignment for quest '%s'(%d) not found for player '%s'.\n", quest->GetName(),quest->GetID(), GetCharName() ); return false; } bool psCharacter::CheckQuestAvailable(psQuest *quest,int assigner_id) { CS_ASSERT( quest ); // Must not be NULL csTicks now = csGetTicks(); bool quest_started = false; if (quest->GetParentQuest()) quest = quest->GetParentQuest(); bool notify = false; if (GetActor()->GetClient()) { notify = CacheManager::GetSingleton().GetCommandManager()->Validate(GetActor()->GetClient()->GetSecurityLevel(), "quest notify"); } for (size_t i=0; iquest.IsValid() && assigned_quests[i]->assigner_id == assigner_id && assigned_quests[i]->quest->GetID() != quest->GetID() && assigned_quests[i]->quest->GetParentQuest() == NULL && assigned_quests[i]->status == PSQUEST_ASSIGNED) { if (notify) { psserver->SendSystemInfo(GetActor()->GetClientID(), "GM NOTICE: Quest found, but you already have one assigned from same NPC"); } return false; // Cannot have multiple quests from the same guy } // Character have this quest if (assigned_quests[i]->quest.IsValid() && assigned_quests[i]->quest->GetID() == quest->GetID()) { // Still in lockout if (assigned_quests[i]->lockout_end > now) { if (notify) { if (GetActor()->questtester) // GM flag { psserver->SendSystemInfo(GetActor()->GetClientID(), "GM NOTICE: Quest (%s) found and player lockout time has been overridden.", quest->GetName()); return true; // Quest is available for GM } else psserver->SendSystemInfo(GetActor()->GetClientID(), "GM NOTICE: Quest (%s) found but player lockout time hasn't elapsed yet. %d seconds remaining.", quest->GetName(), (assigned_quests[i]->lockout_end - now)/1000 ); } return false; // Cannot have the same quest while in player lockout time. } if (assigned_quests[i]->status == PSQUEST_ASSIGNED) { quest_started = true; } } } // If player dosn't have this quest check if quest have lockout if (!quest_started && quest->GetQuestLastActivatedTime() && quest->GetQuestLastActivatedTime() + quest->GetQuestLockoutTime() > now) { if (notify) { if (GetActor()->questtester) // GM flag { psserver->SendSystemInfo(GetActor()->GetClientID(), "GM NOTICE: Quest(%s) found; quest lockout time has been overrided", quest->GetName()); return true; // Quest is available for GM } else psserver->SendSystemInfo(GetActor()->GetClientID(), "GM NOTICE: Quest(%s) found, but quest lockout time hasn't elapsed yet", quest->GetName()); } return false; // Cannot start this quest while in quest lockout time. } return true; // Quest is available } bool psCharacter::CheckResponsePrerequisite(NpcResponse *resp) { CS_ASSERT( resp ); // Must not be NULL return resp->CheckPrerequisite(this); } int psCharacter::NumberOfQuestsCompleted(csString category) { int count=0; for (size_t i=0; iquest.IsValid() && assigned_quests[i]->quest->GetParentQuest() == NULL && assigned_quests[i]->status == PSQUEST_COMPLETE && assigned_quests[i]->quest->GetCategory() == category) { count++; } } return count; } bool psCharacter::UpdateQuestAssignments(bool force_update) { csTicks now = csGetTicks(); for (size_t i=0; iquest.IsValid() && (q->dirty || force_update)) { int r; // will delete the quest only after the expiration time, so the player cannot get it again immediately if (q->status == PSQUEST_DELETE && (!q->quest->GetPlayerLockoutTime() || !q->lockout_end || (q->lockout_end < now))) // delete { r = db->Command("delete from character_quests" " where player_id=%d" " and quest_id=%d", characterid,q->quest->GetID() ); delete assigned_quests[i]; assigned_quests.DeleteIndex(i); i--; // reincremented in loop continue; } // Update or create a new entry in DB // Store remaining time to DB. Than remaining time has to be added to current time // at load from DB. csTicks remaining_time = 0; if (q->lockout_end && q->lockout_end > now) remaining_time = q->lockout_end - now; r = db->Command("update character_quests " "set status='%c'," "remaininglockout=%ld " " where player_id=%d" " and quest_id=%d", q->status, remaining_time, characterid, q->quest->GetID() ); if (!r) // no update done { r = db->Command("insert into character_quests" "(player_id, assigner_id, quest_id, status, remaininglockout) " "values (%d, %d, %d, '%c', %d)", characterid, q->assigner_id, q->quest->GetID(), q->status, remaining_time); if (r == -1) { Error2("Could not insert character_quest row. Error was <%s>.",db->GetLastError() ); } else { Debug3(LOG_QUESTS, GetCharacterID(), "Inserted quest info for player %d, quest %d.\n",characterid,assigned_quests[i]->quest->GetID() ); } } else { Debug3(LOG_QUESTS, GetCharacterID(), "Updated quest info for player %d, quest %d.\n",characterid,assigned_quests[i]->quest->GetID() ); } assigned_quests[i]->dirty = false; } } return true; } bool psCharacter::LoadQuestAssignments() { Result result(db->Select("select * from character_quests" " where player_id=%d", characterid)); if (!result.IsValid()) { Error3("Could not load quest assignments for character %d. Error was: %s",characterid, db->GetLastError() ); return false; } csTicks now = csGetTicks(); for (unsigned int i=0; idirty = false; q->quest = CacheManager::GetSingleton().GetQuestByID( result[i].GetInt("quest_id") ); q->status = result[i]["status"][0]; q->lockout_end = now + result[i].GetInt("remaininglockout"); q->assigner_id = result[i].GetInt("assigner_id"); if (!q->quest) { Error3("Quest %d for player %d not found!", result[i].GetInt("quest_id"), characterid ); delete q; return false; } // Sanity check to see if time for completion is withing // lockout time. if (q->lockout_end > now + q->quest->GetPlayerLockoutTime()) q->lockout_end = now + q->quest->GetPlayerLockoutTime(); Debug5(LOG_QUESTS, characterid, "Loaded quest %-40.40s, status %c, lockout %u, for player %s.\n", q->quest->GetName(),q->status, ( q->lockout_end > now ? q->lockout_end-now:0),GetCharFullName()); assigned_quests.Push(q); } return true; } psGuildLevel * psCharacter::GetGuildLevel() { if (guildinfo == NULL) return 0; psGuildMember * membership = guildinfo->FindMember((unsigned int)characterid); if (membership == NULL) return 0; return membership->guildlevel; } void psCharacter::RemoveBuddy( unsigned int buddyID ) { for ( size_t x = 0; x < buddyList.Length(); x++ ) { if ( buddyList[x].playerID == buddyID ) { buddyList.DeleteIndex(x); return; } } } void psCharacter::BuddyOf( unsigned int buddyID ) { if ( buddyOfList.Find( buddyID ) == csArrayItemNotFound ) { buddyOfList.Push( buddyID ); } } void psCharacter::NotBuddyOf( unsigned int buddyID ) { buddyOfList.Delete( buddyID ); } bool psCharacter::AddBuddy( unsigned int buddyID, csString& buddyName ) { // Cannot addself to buddy list if ( buddyID == characterid ) return false; for ( size_t x = 0; x < buddyList.Length(); x++ ) { if ( buddyList[x].playerID == buddyID ) { return true; } } Buddy b; b.name = buddyName; b.playerID = buddyID; buddyList.Push( b ); return true; } double psCharacter::GetProperty(const char *ptr) { if (!strcasecmp(ptr,"AttackerTargeted")) { return true; // return (attacker_targeted) ? 1 : 0; } else if (!strcasecmp(ptr,"TotalTargetedBlockValue")) { return GetTotalTargetedBlockValue(); } else if (!strcasecmp(ptr,"TotalUntargetedBlockValue")) { return GetTotalUntargetedBlockValue(); } else if (!strcasecmp(ptr,"DodgeValue")) { return GetDodgeValue(); } else if (!strcasecmp(ptr,"KillExp")) { return kill_exp; } else if (!strcasecmp(ptr,"getAttackValueModifier")) { return attackValueModifier; } else if (!strcasecmp(ptr,"getDefenseValueModifier")) { return defenseValueModifier; } else if (!strcasecmp(ptr,"Strength")) { return attributes.GetStat(PSITEMSTATS_STAT_STRENGTH,true); } else if (!strcasecmp(ptr,"Agility")) { return attributes.GetStat(PSITEMSTATS_STAT_AGILITY,true); } else if (!strcasecmp(ptr,"Endurance")) { return attributes.GetStat(PSITEMSTATS_STAT_ENDURANCE,true); } else if (!strcasecmp(ptr,"Intelligence")) { return attributes.GetStat(PSITEMSTATS_STAT_INTELLIGENCE,true); } else if (!strcasecmp(ptr,"Will")) { return attributes.GetStat(PSITEMSTATS_STAT_WILL,true); } else if (!strcasecmp(ptr,"Charisma")) { return attributes.GetStat(PSITEMSTATS_STAT_CHARISMA,true); } else if (!strcasecmp(ptr,"BaseStrength")) { return attributes.GetStat(PSITEMSTATS_STAT_STRENGTH,false); } else if (!strcasecmp(ptr,"BaseAgility")) { return attributes.GetStat(PSITEMSTATS_STAT_AGILITY,false); } else if (!strcasecmp(ptr,"BaseEndurance")) { return attributes.GetStat(PSITEMSTATS_STAT_ENDURANCE,false); } else if (!strcasecmp(ptr,"BaseIntelligence")) { return attributes.GetStat(PSITEMSTATS_STAT_INTELLIGENCE,false); } else if (!strcasecmp(ptr,"BaseWill")) { return attributes.GetStat(PSITEMSTATS_STAT_WILL,false); } else if (!strcasecmp(ptr,"BaseCharisma")) { return attributes.GetStat(PSITEMSTATS_STAT_CHARISMA,false); } else if (!strcasecmp(ptr,"AllArmorStrMalus")) { return modifiers.GetStat(PSITEMSTATS_STAT_STRENGTH); } else if (!strcasecmp(ptr,"AllArmorAgiMalus")) { return modifiers.GetStat(PSITEMSTATS_STAT_AGILITY); } else if (!strcasecmp(ptr,"CombatStance")) { return GetCombatStance(); } Error2("Requested psCharacter property not found '%s'", ptr); return 0; } /** A skill can only be trained if the player requires points for it. */ bool psCharacter::CanTrain( PSSKILL skill ) { return skills.CanTrain( skill ); } void psCharacter::Train( PSSKILL skill, int yIncrease ) { // Did we train stats? if( skill == PSSKILL_AGI || skill == PSSKILL_CHA || skill == PSSKILL_END || skill == PSSKILL_INT || skill == PSSKILL_WILL || skill == PSSKILL_STR ) { Skill* cskill = skills.GetSkill(skill); skills.Train( skill, yIncrease ); int know = cskill->y; int cost = cskill->yCost; // We ranked up if(know >= cost) { cskill->rank++; cskill->y = 0; int after = cskill->rank; cskill->CalculateCosts(this); // Insert into the stats switch(skill) { case PSSKILL_AGI: attributes.SetStat(PSITEMSTATS_STAT_AGILITY,after); break; case PSSKILL_CHA: attributes.SetStat(PSITEMSTATS_STAT_CHARISMA,after); break; case PSSKILL_END: attributes.SetStat(PSITEMSTATS_STAT_ENDURANCE,after); break; case PSSKILL_INT: attributes.SetStat(PSITEMSTATS_STAT_INTELLIGENCE,after); break; case PSSKILL_WILL: attributes.SetStat(PSITEMSTATS_STAT_WILL,after); break; case PSSKILL_STR: attributes.SetStat(PSITEMSTATS_STAT_STRENGTH,after); break; default: break; } // Save stats if(GetActor()->GetClientID() != 0) { const char *fieldnames[]= { "base_strength", "base_agility", "base_endurance", "base_intelligence", "base_will", "base_charisma" }; psStringArray fieldvalues; fieldvalues.FormatPush("%d",attributes.GetStat(PSITEMSTATS_STAT_STRENGTH, false)); fieldvalues.FormatPush("%d",attributes.GetStat(PSITEMSTATS_STAT_AGILITY, false)); fieldvalues.FormatPush("%d",attributes.GetStat(PSITEMSTATS_STAT_ENDURANCE, false)); fieldvalues.FormatPush("%d",attributes.GetStat(PSITEMSTATS_STAT_INTELLIGENCE, false)); fieldvalues.FormatPush("%d",attributes.GetStat(PSITEMSTATS_STAT_WILL, false)); fieldvalues.FormatPush("%d",attributes.GetStat(PSITEMSTATS_STAT_CHARISMA, false)); csString id; id = GetCharacterID(); if(!db->GenericUpdateWithID("characters","id",id,fieldnames,fieldvalues)) { Error2("Couldn't save stats for character %u!\n",GetCharacterID()); } } // When a stat is ranked up, hp, mana and stamina are recalculated RecalculateStats(); } } else { skills.Train( skill, yIncrease ); // Normal training psServer::CharacterLoader.UpdateCharacterSkill( GetCharacterID(), skill, skills.GetSkillPractice((PSSKILL)skill), skills.GetSkillKnowledge((PSSKILL)skill), skills.GetSkillRank((PSSKILL)skill) ); RecalculateStats(); } } /*-----------------------------------------------------------------*/ void Skill::CalculateCosts(psCharacter* user) { if(!info || !user) return; // Calc the new Y/Z cost MathScript* costScript; csString scriptName; if(info->id < PSSKILL_AGI || info->id > PSSKILL_WILL) scriptName = "CalculateSkillCosts"; else scriptName = "CalculateStatCosts"; costScript = psserver->GetMathScriptEngine()->FindScript(scriptName); if (!costScript) { Error2("Couldn't find script %s!",scriptName.GetData()); return; } // Output MathScriptVar* yCostVar = costScript->GetVar("YCost"); MathScriptVar* zCostVar = costScript->GetVar("ZCost"); // Input MathScriptVar* baseCostVar = costScript->GetOrCreateVar("BaseCost"); MathScriptVar* skillRankVar = costScript->GetOrCreateVar("SkillRank"); MathScriptVar* skillIDVar = costScript->GetOrCreateVar("SkillID"); MathScriptVar* practiceVar = costScript->GetOrCreateVar("PracticeFactor"); MathScriptVar* mentalVar = costScript->GetOrCreateVar("MentalFactor"); MathScriptVar* actorVar = costScript->GetOrCreateVar("Actor"); if(!yCostVar) { Warning2(LOG_SKILLXP,"Couldn't find the YCost var in %s script!\n",scriptName.GetData()); return; } if(!zCostVar) { Warning2(LOG_SKILLXP,"Couldn't find the ZCost var in %s script!\n",scriptName.GetData()); return; } // Input the data skillRankVar->SetValue((double)rank); skillIDVar->SetValue((double)info->id); practiceVar->SetValue((double)info->practice_factor); mentalVar->SetValue((double)info->mental_factor); baseCostVar->SetValue((double)info->baseCost); actorVar->SetObject(user); // Execute costScript->Execute(); // Get the output yCost = (int)yCostVar->GetValue(); zCost = (int)zCostVar->GetValue(); // Make sure the y values is clamped to the cost. Otherwise Practice may always // fail. if (y > yCost) y = yCost; if ( z > zCost ) z = zCost; } void Skill::Train( int yIncrease ) { y+=yIncrease; if ( y > yCost ) y = yCost; } bool Skill::Practice( unsigned int amount, unsigned int& actuallyAdded,psCharacter* user ) { bool rankup = false; // Practice can take place if ( yCost == y ) { z+=amount; if ( z >= zCost ) { rank++; z = 0; y = 0; actuallyAdded = z - zCost; rankup = true; // Reset the costs for Y/Z CalculateCosts(user); } else { actuallyAdded = amount; } } else { actuallyAdded = 0; } return rankup; } int FindGlyphSlot(const csArray & slots, psItemStats * glyphType, int purifyStatus) { int slotNum; for (slotNum=0; slotNum < (int)slots.Length(); slotNum++) if ( (slots[slotNum].glyphType == glyphType) && (slots[slotNum].purifyStatus == purifyStatus) ) return slotNum; return -1; } void psCharacter::AddGlyphInSlotList(psGlyph* glyph, csArray & slots) { int slotNum; slotNum = FindGlyphSlot(slots, glyph->GetBaseStats(), glyph->GetPurifyStatus()); if (slotNum == -1) { slotNum = (int)slots.Length(); slots.SetLength(slotNum+1); slots[slotNum].glyphType = glyph->GetBaseStats(); slots[slotNum].purifyStatus = glyph->GetPurifyStatus(); slots[slotNum].count = 0; } slots[slotNum].count += glyph->GetStackCount(); } void psCharacter::CreateGlyphList(bool purifiedOnly, csArray & slots) { int bulkNum; for (bulkNum=0; bulkNum < PSCHARACTER_BULK_COUNT; bulkNum++) { psItem * item = inventory.GetBulkItem(bulkNum); //We need to search the glyphs in the containers in the inventory. if (item && item->GetIsContainer() && !item->GetIsContainerEmpty()) { psContainerIterator* it = new psContainerIterator(item); while (it->HasNext()) { psGlyph* glyphInContainer = dynamic_cast (it->Next()); //If one of the item of the content is a glyph, add to the slot list if (glyphInContainer && (glyphInContainer->GetPurifyStatus()==2 || !purifiedOnly)) { AddGlyphInSlotList(glyphInContainer, slots); } } delete it; } else //Then we check if the object in the slot is a glyph. { psGlyph * glyph = dynamic_cast (inventory.GetBulkItem(bulkNum)); if (glyph != NULL) { if (glyph->GetPurifyStatus()==2 || !purifiedOnly) { AddGlyphInSlotList(glyph, slots); } } } } } bool psCharacter::HasGlyphs(const glyphList_t & glyphsToCheck) { csArray glyphsInBulk; size_t i; int slotNum; CreateGlyphList(true, glyphsInBulk); for (i=0; i < glyphsToCheck.Length(); i++) { slotNum = FindGlyphSlot(glyphsInBulk, glyphsToCheck[i], 2); if (slotNum == -1) return false; if (glyphsInBulk[slotNum].count == 0) return false; glyphsInBulk[slotNum].count --; } return true; } void psCharacter::SetSkillRank( PSSKILL which, int rank) { skills.SetSkillRank(which, rank); if (which == PSSKILL_AGI ) attributes.SetStat(PSITEMSTATS_STAT_AGILITY, rank); else if( which == PSSKILL_CHA ) attributes.SetStat(PSITEMSTATS_STAT_CHARISMA, rank); else if( which == PSSKILL_END ) attributes.SetStat(PSITEMSTATS_STAT_ENDURANCE, rank); else if( which == PSSKILL_INT ) attributes.SetStat(PSITEMSTATS_STAT_INTELLIGENCE, rank); else if( which == PSSKILL_STR ) attributes.SetStat(PSITEMSTATS_STAT_STRENGTH, rank); else if( which == PSSKILL_WILL) attributes.SetStat(PSITEMSTATS_STAT_WILL,rank); } unsigned int psCharacter::GetCharLevel() { return ( attributes.GetStat(PSITEMSTATS_STAT_STRENGTH) + attributes.GetStat(PSITEMSTATS_STAT_ENDURANCE) + attributes.GetStat(PSITEMSTATS_STAT_AGILITY) + attributes.GetStat(PSITEMSTATS_STAT_INTELLIGENCE) + attributes.GetStat(PSITEMSTATS_STAT_WILL) + attributes.GetStat(PSITEMSTATS_STAT_CHARISMA) ) / 6; } //This function recalculates Hp, Mana, and Stamina when needed (char creation, combats, training sessions) void psCharacter::RecalculateStats() { // Calculate current Max Mana level: static MathScript *maxManaScript; if (!maxManaScript) { // Max Mana Script isn't loaded, so load it maxManaScript = psserver->GetMathScriptEngine()->FindScript("CalculateMaxMana"); CS_ASSERT(maxManaScript != NULL); } if ( maxManaScript ) { MathScriptVar* actorvar = maxManaScript->GetOrCreateVar("Actor"); actorvar->SetObject(this); if (override_max_mana) { SetManaMax(override_max_mana); } else { maxManaScript->Execute(); MathScriptVar* manaValue = maxManaScript->GetVar("MaxMana"); SetManaMax(manaValue->GetValue()); } } // Calculate current Max HP level: static MathScript *maxHPScript; if (!maxHPScript) { // Max HP Script isn't loaded, so load it maxHPScript = psserver->GetMathScriptEngine()->FindScript("CalculateMaxHP"); CS_ASSERT(maxHPScript != NULL); } if ( maxHPScript ) { MathScriptVar* actorvar = maxHPScript->GetOrCreateVar("Actor"); actorvar->SetObject(this); if (override_max_hp) { SetHitPointsMax(override_max_hp); } else { maxHPScript->Execute(); MathScriptVar* HPValue = maxHPScript->GetVar("MaxHP"); SetHitPointsMax(HPValue->GetValue()); } } // The max weight that a player can carry inventory.CalculateStats(); // Stamina CalculateMaxStamina(); // Speed // if (GetActor()) // GetActor()->UpdateAllSpeedModifiers(); } //TODO: Make this not return a temp csString, but fix in place csString NormalizeCharacterName(const csString & name) { csString normName = name; normName.Downcase(); normName.Trim(); if (normName.Length() > 0) normName.SetAt(0,toupper(normName.GetAt(0))); return normName; } unsigned int StatSet::GetStat(PSITEMSTATS_STAT attrib, bool withBuff) { if (attrib<0 || attrib>=PSITEMSTATS_STAT_COUNT) return 0; int buff = withBuff?stats[attrib].rankBuff:0; int result = (int)stats[attrib].rank + buff; return result; } int StatSet::GetBuffVal( PSITEMSTATS_STAT attrib ) { if (attrib<0 || attrib>=PSITEMSTATS_STAT_COUNT) return 0; return stats[attrib].rankBuff; } void StatSet::BuffStat( PSITEMSTATS_STAT attrib, int buffAmount ) { if (attrib<0 || attrib>=PSITEMSTATS_STAT_COUNT) return; // should buffed stat go negative, we set it to zero if (int(stats[attrib].rank) + stats[attrib].rankBuff + buffAmount < 0) stats[attrib].rankBuff = -(int)stats[attrib].rank; else stats[attrib].rankBuff+=buffAmount; self->RecalculateStats(); } void StatSet::SetStat(PSITEMSTATS_STAT attrib, unsigned int val, bool recalculatestats) { if (attrib<0 || attrib>=PSITEMSTATS_STAT_COUNT) return; stats[attrib].rank=val; if (recalculatestats) self->RecalculateStats(); } void StatSet::AddToStat(PSITEMSTATS_STAT attrib, unsigned int delta) { if (attrib<0 || attrib>=PSITEMSTATS_STAT_COUNT) return; stats[attrib].rank += delta; self->RecalculateStats(); } int SkillSet::AddSkillPractice(PSSKILL skill, unsigned int val) { unsigned int added; bool rankUp; csString name = ""; rankUp = AddToSkillPractice(skill,val, added); psSkillInfo* skillInfo = CacheManager::GetSingleton().GetSkillByID(skill); if ( skillInfo ) { // Save skill and practice only when the level reached a new rank // this is done to avoid saving to db each time a player hits // an opponent if(rankUp && self->GetActor()->GetClientID() != 0) { psServer::CharacterLoader.UpdateCharacterSkill( self->GetCharacterID(), skill, GetSkillPractice((PSSKILL)skill), GetSkillKnowledge((PSSKILL)skill), GetSkillRank((PSSKILL)skill) ); } name = skillInfo->name; Debug5(LOG_SKILLXP,self->GetActor()->GetClientID(),"Adding %d points to skill %s to character %s (%d)\n",val,skillInfo->name.GetData(), self->GetCharFullName(), self->GetActor()->GetClientID()); } else { Debug4(LOG_SKILLXP,self->GetActor()->GetClientID(),"WARNING! Skill practise to unknown skill(%d) for character %s (%d)\n", (int)skill, self->GetCharFullName(), self->GetActor()->GetClientID()); } if ( added > 0 ) { psZPointsGainedEvent evt( self->GetActor(), skillInfo->name, added, rankUp ); evt.FireEvent(); } return added; } Skill * SkillSet::GetSkill( PSSKILL which ) { if (which<0 || which>=PSSKILL_COUNT) return NULL; else return & skills[which]; } unsigned int SkillSet::GetBestSkillValue( bool withBuffer ) { unsigned int max=0; for (int i=0; iid; if( skill == PSSKILL_AGI || skill == PSSKILL_CHA || skill == PSSKILL_END || skill == PSSKILL_INT || skill == PSSKILL_WILL || skill == PSSKILL_STR ) continue; // Jump past the stats, we only want the skills unsigned int rank = skills[i].rank + (withBuffer?skills[i].rankBuff:0); if (rank > max) max = rank; } return max; } unsigned int SkillSet::GetBestSkillSlot( bool withBuffer ) { unsigned int max = 0; unsigned int i = 0; for (; i max) max = rank; } if (i == PSSKILL_COUNT) return (unsigned int)~0; else return i; } void SkillSet::Calculate() { for ( int z = 0; z < PSSKILL_COUNT; z++ ) { skills[z].CalculateCosts(self); } self->RecalculateStats(); } bool SkillSet::CanTrain( PSSKILL skill ) { if (skill<0 || skill>=PSSKILL_COUNT) return false; else { return skills[skill].CanTrain(); } } void SkillSet::Train( PSSKILL skill, int yIncrease ) { if (skill<0 ||skill>=PSSKILL_COUNT) return; else { skills[skill].Train( yIncrease ); } } void SkillSet::SetSkillInfo( PSSKILL which, psSkillInfo* info, bool recalculatestats ) { if (which<0 || which>=PSSKILL_COUNT) return; else { skills[which].info = info; skills[which].CalculateCosts(self); } if (recalculatestats) self->RecalculateStats(); } void SkillSet::SetSkillRank( PSSKILL which, int rank, bool recalculatestats ) { if (which<0 || which>=PSSKILL_COUNT) return; skills[which].rank = rank; skills[which].CalculateCosts(self); if (recalculatestats) self->RecalculateStats(); } void SkillSet::BuffSkillRank( PSSKILL which, int buff ) { if (which<0 || which>=PSSKILL_COUNT) return; skills[which].Buff( buff ); self->RecalculateStats(); } void SkillSet::SetSkillKnowledge( PSSKILL which, int y_value ) { if (which<0 || which>=PSSKILL_COUNT) return; skills[which].y = y_value; } void SkillSet::SetSkillPractice(PSSKILL which,int z_value) { if (which<0 || which>=PSSKILL_COUNT) return; skills[which].z = z_value; } bool SkillSet::AddToSkillPractice(PSSKILL skill, unsigned int val, unsigned int& added ) { if (skill<0 || skill>=PSSKILL_COUNT) return 0; bool rankup = false; rankup = skills[skill].Practice( val, added, self ); return rankup; } unsigned int SkillSet::GetSkillPractice(PSSKILL skill) { if (skill<0 || skill>=PSSKILL_COUNT) return 0; return skills[skill].z; } unsigned int SkillSet::GetSkillKnowledge(PSSKILL skill) { if (skill<0 || skill>=PSSKILL_COUNT) return 0; return skills[skill].y; } unsigned int SkillSet::GetSkillRank( PSSKILL skill, bool withBuffer ) { if (skill<0 || skill>=PSSKILL_COUNT) return 0; int buff = withBuffer?skills[skill].rankBuff:0; int result = (int)skills[skill].rank + buff; return (unsigned int)result; } Skill& SkillSet::Get(PSSKILL skill) { return skills[skill]; }