/* * psitem.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 #include "util/log.h" #include "util/psstring.h" #include "util/serverconsole.h" #include "psitem.h" #include "pscharacter.h" #include "pssectorinfo.h" #include "../playergroup.h" #include "../globals.h" #include "../psserver.h" #include "../entitymanager.h" #include "util/eventmanager.h" #include "util/psdatabase.h" #include "../cachemanager.h" #include "../gem.h" #include "../events.h" #include "../spawnmanager.h" #include "pstrade.h" #include "psmerchantinfo.h" #if SAVE_DEBUG #include #endif #if SAVE_TRACER #include #endif /** * This class handles auto-removal of transient objects * (objects placed in the world by players, basically). */ class psItemRemovalEvent : public psGameEvent { protected: PS_ID item_to_remove; public: psItemRemovalEvent(int delayticks,PS_ID gemID) : psGameEvent(0,delayticks*1000,"psItemRemovalEvent") { item_to_remove = gemID; } virtual void Trigger() { printf("Removing object %u now.\n",item_to_remove); // cannot store pointer because object may have already been removed and ptr not valid gemObject *obj = GEMSupervisor::GetSingleton().FindObject(item_to_remove); if (obj) { psItem *item = obj->GetItem(); if (item) { EntityManager::GetSingleton().RemoveActor(obj); item->Destroy(); // obj is deleted in RemoveActor } } } }; /* psContainerIterator * * */ psContainerIterator::psContainerIterator(psItem *container) { UseContainerItem(container); } psContainerIterator::~psContainerIterator() { } bool psContainerIterator::HasNext() { if (current_container && next_position!=PSITEM_MAX_CONTAINER_SLOTS) return true; return false; } psItem *psContainerIterator::Next() { psItem *nextitem; if (!current_container || next_position==PSITEM_MAX_CONTAINER_SLOTS) return NULL; nextitem=current_container->container_data.contained_item_ptr[next_position]; // Set the next_position marker to the next valid contained item, or PSITEM_MAX_CONTAINER_SLOTS if no items for (next_position++;next_positioncontainer_data.contained_item_ptr[next_position]!=NULL) break; } return nextitem; } void psContainerIterator::UseContainerItem(psItem *container) { current_container=container; // Set the next_position marker to the next valid contained item, or PSITEM_MAX_CONTAINER_SLOTS if no items for (next_position=0;next_positioncontainer_data.contained_item_ptr[next_position]!=NULL) break; } } // Definition of the itempool for psItems PoolAllocator psItem::itempool; void *psItem::operator new(size_t allocSize) { CS_ASSERT(allocSize<=sizeof(psItem)); return (void *)itempool.CallFromNew(); } void psItem::operator delete(void *releasePtr) { itempool.CallFromDelete((psItem *)releasePtr); } // These are declared static in the class to ensure that there is only 1 instance in the entire system csRef psItem::global_itemid_mutex=NULL; uint32 psItem::global_itemid=0; csRef psItem::global_uniqueitemid_mutex=NULL; uint32 psItem::global_uniqueitemid=0; psItem::psItem() : gItem(NULL), transformationEvent(NULL), loaded(false), pendingsave(false) { int i; uid=0; creator=0; // parentid=0; loc_in_parent=0; sum_weight=0.0f; item_in_use=false; for (i=0;iUnregisterCallback(this); gItem = NULL; } char * psItem::GetCreatorName() { switch(creator) { case ITEM_DB_LOAD : return "created by Load()"; case ITEM_CLONE : return "created by Copy()"; case ITEM_CACHE : return "created by cacheManager with Load()"; //created by CacheManager case ITEM_MONEY : return "created by psserverchar::MakeMoneyItem"; //created by psserverchar::MakeMoneyItem case ITEM_BASIC : return "created by psItemStats::InstantiateBasicItem"; //created by psitemstats::instantiateBasicItem default : return "some unknown code"; } } void psItem::UpdateItemQuality(uint32 id, float qual) { if (!id || id==ID_DONT_SAVE_ITEM) return; Debug3(LOG_USER,id,"UpdateItemQuality(%u,%1.2f)\n",id, qual); int ret = db->Command("update item_instances set item_quality=%1.2f where id=%u",qual, id); if (ret == 0) { Error3("Could not update item quality. SQL was <%s> and error was <%s>",db->GetLastQuery(),db->GetLastError()); } } // Functions that manipulate psItem Data bool psItem::Load(iResultRow& row,uint32 &parentid) { // Begin filling in the item properties // Item Unique ID # SetUID(row.GetUInt32("id")); CS_ASSERT(uid != 0); parentid = row.GetUInt32("parent_item_id"); loc_in_parent = row.GetInt("location_in_parent"); // Stack count (will be 0 if NULL - either means this is a non stackable item) stack_count=(unsigned short)row.GetInt("stack_count"); if (row.GetInt("creator_mark_id")) SetCrafterID(row.GetInt("creator_mark_id")); if (row.GetInt("guild_mark_id")) SetGuildID(row.GetInt("guild_mark_id")); if (row.GetFloat("decay_resistance")) SetDecayResistance(row.GetFloat("decay_resistance")); else SetDecayResistance(0); // Flags psString flagstr(row["flags"]); if (flagstr.FindSubString("LOCKED",0,true)!=-1) { flags |= PSITEM_FLAG_LOCKED; } if (flagstr.FindSubString("LOCKABLE",0,true)!=-1) { flags |= PSITEM_FLAG_LOCKABLE; } if (flagstr.FindSubString("KEY",0,true)!=-1) { flags |= PSITEM_FLAG_KEY; } if (flagstr.FindSubString("PURIFIED",0,true)!=-1) { flags |= PSITEM_FLAG_PURIFIED; } if (flagstr.FindSubString("PURIFYING",0,true)!=-1) { flags |= PSITEM_FLAG_PURIFIED; } if (flagstr.FindSubString("NOPICKUP",0,true)!=-1) { flags |= PSITEM_FLAG_NOPICKUP; } if (flagstr.FindSubString("TRANSIENT",0,true)!=-1) { flags |= PSITEM_FLAG_TRANSIENT; } // Lockpick stuff SetLockStrength(row.GetInt("lock_str")); SetLockpickSkill((PSSKILL)row.GetInt("lock_skill")); // load openableLocks psString olstr(row["openable_locks"]); psString w; olstr.GetWordNumber(1, w); for (int n = 2; w.Length(); olstr.GetWordNumber(n++, w)) { if (w == "SKEL") openableLocks.Push(KEY_SKELETON); else { unsigned int u; sscanf(w.GetData(), "%u", &u); openableLocks.Push(u); } } unsigned int stats_id=row.GetUInt32("item_stats_id_standard"); psItemStats *stats=CacheManager::GetSingleton().GetBasicItemStatsByID(stats_id); if (!stats) { Error3("Item with id %s has unresolvable basic item stats id %u",row["id"],stats_id); return false; } SetBaseStats(stats); if (row.GetFloat("item_quality")) { SetItemQuality(row.GetFloat("item_quality")); } else { SetItemQuality(GetMaxItemQuality()); } // Set the crafted quality for this item. crafted_quality = row.GetFloat("crafted_quality"); if ( row.GetUInt32("char_id_owner") == 0 ) { psSectorInfo *itemsector=CacheManager::GetSingleton().GetSectorInfoByID(row.GetInt("loc_sector_id")); if (!itemsector && row.GetUInt32("parent_item_id") == 0 ) { csString error; error.Format("Item %s(%s) Could not be loaded\nIt is in sector id %s which does not resolve\n", GetName(), row["id"], row["loc_sector_id"] ); Error1( error ); return false; } float x,y,z,yrot; x = row.GetFloat("loc_x"); y = row.GetFloat("loc_y"); z = row.GetFloat("loc_z"); yrot = row.GetFloat("loc_yrot"); SetLocationInWorld(itemsector,x,y,z,yrot); } else { owningCharacterID = row.GetUInt32("char_id_owner"); } // TODO:Modifiers loaded and resolved later // Unique item handling, stats specified are deltas to standard stats stats_id = row.GetUInt32("item_stats_id_unique"); if (stats_id) { Result result(db->Select("SELECT * from item_stats where id=%u",stats_id)); if (!result.IsValid()) { Error3("Item with id %s has unresolvable unique item stats id %u",row["id"],stats_id); return false; } psItemStats *stats=new psItemStats; if (!stats->ReadItemStats(result[0])) { Error3("Item with id %s has unique item stats that cannot be parsed (unique item stats id %u)",row["id"],stats_id); delete stats; return false; } SetUniqueStats(stats); } SetCreator(ITEM_DB_LOAD); //Loaded(); // we really should be loaded after Load(), eh? return true; } void psItem::Save(bool children) { if (loaded && !pendingsave) { #if SAVE_TRACER csCallStack* stack = csCallStackHelper::CreateCallStack(0,true); if (stack) { /// Store the function that queued this save (check this with a debugger if Commit() fails) last_save_queued_from = stack->GetEntryAll(1,true); #if SAVE_DEBUG printf("\n%s::Save() for '%s', queued from stack:\n", typeid(*(item)).name(), GetName() ); stack->Print(); printf("\n"); #endif stack->Free(); } else { Bug2("Could not get call stack for %p!",this); last_save_queued_from = "ERROR: csCallStackHelper::CreateCallStack(0,true) returned NULL!"; } #elif SAVE_DEBUG printf("%s::Save() for '%s' queued\n", typeid(*(item)).name(), GetName() ); #endif pendingsave = true; Commit(children); } #if SAVE_DEBUG else if (loaded) printf("%s::Save() for '%s' skipped\n", typeid(*(GetSafeReference()->item)).name(), GetName() ); #endif } void psItem::Commit(bool children) { if (!pendingsave || uid == ID_DONT_SAVE_ITEM) return; pendingsave = false; if (!loaded) return; if (children) { if (current_stats->GetIsContainer()) { for (int i=0;iSave(true); } } } psStringArray fields; if (GetUID()==0) { // New Item, need a UID const char *fieldnames[]= { "id", "char_id_owner", "stack_count", "item_quality", "crafted_quality", "decay_resistance", "creator_mark_id", "guild_mark_id", "flags", "item_stats_id_standard", "item_stats_id_unique", "location", "parent_item_id", "location_in_parent", "equipped_slot", "loc_x", "loc_y", "loc_z", "loc_yrot", "loc_sector_id", "lock_str", "lock_skill", "openable_locks" }; // Get a unique ID for this item. SetUID(GetNextItemUID()); // Error getting a unique ID, this should never happen CS_ASSERT_MSG("GetNextItemUID() returned 0!",GetUID()!=0); if (GetUID()==0) return; // We have to add the id to the fields before we add the rest of the field values fields.FormatPush("%u",GetUID()); GetFieldArray(fields); // TODO: FIXME: BUG: We should check the return below, but since we can only return a 32 bit value on an insert // we will get every possible value back at different times for success. We need a GenericInsert (without ID) // that returns a bool. // This can only return a 32 bit value db->GenericInsertWithID("item_instances",fieldnames,fields); item_quality_original = item_quality; } else { // Existing Item, update const char *fieldnames[]= { "char_id_owner", "stack_count", "item_quality", "crafted_quality", "decay_resistance", "creator_mark_id", "guild_mark_id", "flags", "item_stats_id_standard", "item_stats_id_unique", "location", "parent_item_id", "location_in_parent", "equipped_slot", "loc_x", "loc_y", "loc_z", "loc_yrot", "loc_sector_id", "lock_str", "lock_skill", "openable_locks" }; GetFieldArray(fields); // Save this entry csString uid; uid.Format("%u",GetUID()); if ( !db->GenericUpdateWithID("item_instances","id",uid,fieldnames,fields) ) { Error2("Failed to save item instance %u!", GetUID() ); } else item_quality_original = item_quality; } } void psItem::GetFieldArray(psStringArray& fields) { // Owning character ID fields.FormatPush("%u",owning_character?owning_character->GetCharacterID():0); // Stack count fields.FormatPush("%u",GetStackCount()); // Item quality fields.FormatPush("%1.2f",GetItemQuality()); // Crafted Quality fields.FormatPush("%1.2f", GetMaxItemQuality()); fields.FormatPush("%1.2f",GetDecayResistance()); // Crafter ID if (GetIsCrafterIDValid()) fields.FormatPush("%u",GetCrafterID()); else fields.Push(NULL); // Guild ID if (GetIsGuildIDValid()) fields.FormatPush("%u",GetGuildID()); else fields.Push(NULL); // Flags csString flagString = ""; // Thise two are actualy glyhs things and should be moved to psGlyph // if a generic way of updating flags are implemented. if (flags & PSITEM_FLAG_PURIFIED) { if (flagString != "") flagString.Append(","); flagString.Append("PURIFIED"); } if (flags & PSITEM_FLAG_PURIFYING) { if (flagString != "") flagString.Append(","); flagString.Append("PURIFYING"); } if (flags & PSITEM_FLAG_LOCKED) { if (flagString != "") flagString.Append(","); flagString.Append("LOCKED"); } if (flags & PSITEM_FLAG_LOCKABLE) { if (flagString != "") flagString.Append(","); flagString.Append("LOCKABLE"); } if (flags & PSITEM_FLAG_KEY) { if (flagString != "") flagString.Append(","); flagString.Append("KEY"); } if (flags & PSITEM_FLAG_NOPICKUP) { if (flagString != "") flagString.Append(","); flagString.Append("NOPICKUP"); } if (flags & PSITEM_FLAG_TRANSIENT) { if (flagString != "") flagString.Append(","); flagString.Append("TRANSIENT"); } fields.Push(flagString); // item_stats_id_standard - base stats if non unique, unique stats if unique fields.FormatPush("%u",GetBaseStats()->GetUID()); // item_stats_id_unique fields.FormatPush("%u",0); // Owner stuff psCharacter *owner=GetOwningCharacter(); bool equipped = false; int slotlocation = 0; // location if (!owner) { if ( GetParentItem() ) { fields.Push("C"); } else { fields.Push(NULL); } } else { // Find the root item, this makes location searches faster psItem *rootitem=this; while (rootitem->GetParentItem()!=NULL) rootitem=rootitem->GetParentItem(); // Find the general area if ( GetParentItem() ) { fields.Push("C"); } else if ((slotlocation=(int)owner->Inventory().FindItemInTopLevelEquipment(rootitem))!=-1) { equipped=true; fields.Push("E"); } else if ((slotlocation=owner->Inventory().FindItemInTopLevelBulk(rootitem))!=-1) { fields.Push("I"); } else CS_ASSERT(!"Attempted to save item in NULL location."); } // Container stuff if (!GetParentItem()) { fields.Push(NULL); if ( owner ) { if (!equipped) { fields.FormatPush("%d",slotlocation); fields.Push(NULL); } else { fields.Push(NULL); fields.FormatPush("%s",CacheManager::GetSingleton().slotName.GetName(slotlocation)); } } else { fields.Push(NULL); fields.Push(NULL); } } else { fields.FormatPush("%u",GetParentItem()->GetUID()); fields.FormatPush("%u",GetLocInParent()); fields.Push("0"); } if (owner || GetParentItem()) { fields.Push(NULL); fields.Push(NULL); fields.Push(NULL); fields.Push(NULL); fields.Push(NULL); } else // Item is not held or in something; must be in the world { float locx,locy,locz,locyrot; psSectorInfo *sectorinfo; GetLocationInWorld(§orinfo,locx,locy,locz,locyrot); if ( sectorinfo ) { fields.FormatPush("%1.2f",locx); fields.FormatPush("%1.2f",locy); fields.FormatPush("%1.2f",locz); fields.FormatPush("%1.2f",locyrot); fields.FormatPush("%u",sectorinfo->uid); } else // Item is nowhere; cannot be saved { Error3("Item %s(%u) could not be saved because it is in a null location and has no parent or character owner", GetName(), GetUID() ); CS_ASSERT(!"Attempt to save item without a parent in a NULL position"); } } fields.FormatPush("%d",GetLockStrength()); fields.FormatPush("%d",(int)GetLockpickSkill()); // push openableLocks csString openableLocksString = ""; csArray::Iterator iter = openableLocks.GetIterator(); while(iter.HasNext()) { csString tmp; unsigned int n = iter.Next(); if (openableLocksString != "") openableLocksString.Append(" "); // Space to sparate since GetWordNumber is used to decode if (n == KEY_SKELETON) openableLocksString.Append("SKEL"); else { tmp.Format( "%u", n); openableLocksString.Append(tmp); } } fields.Push(openableLocksString); } void psItem::ForceSaveIfNew() { if (GetUID()==0) { // No UID. Force a save now to generate one. pendingsave = true; Commit(); } else { // Already saved. Queue normal save. Save(); } } bool psItem::GetIsCrafterIDValid() { return (flags & PSITEM_FLAG_CRAFTER_ID_IS_VALID); } void psItem::SetIsCrafterIDValid(bool v) { if (v) flags=flags | PSITEM_FLAG_CRAFTER_ID_IS_VALID; else flags=flags & ~PSITEM_FLAG_CRAFTER_ID_IS_VALID; } bool psItem::GetIsGuildIDValid() { return (flags & PSITEM_FLAG_GUILD_ID_IS_VALID) ? true : false; } void psItem::SetIsGuildIDValid(bool v) { if (v) flags=flags | PSITEM_FLAG_GUILD_ID_IS_VALID; else flags=flags & ~PSITEM_FLAG_GUILD_ID_IS_VALID; } bool psItem::GetIsUnique() const { return (flags & PSITEM_FLAG_UNIQUE_ITEM) ? true : false; } void psItem::SetUID(uint32 v) { uid=v; } void psItem::SetStackCount(unsigned short v) { // This adjusts the weight of parents as well AdjustSumWeight(current_stats->GetWeight() * (v-stack_count)); stack_count=v; } void psItem::SetCrafterID(unsigned int v) { SetIsCrafterIDValid(true); crafter_id=v; } void psItem::SetGuildID(unsigned int v) { SetIsGuildIDValid(true); guild_id=v; } float psItem::GetMaxItemQuality() { if ( crafted_quality == -1 ) return current_stats->GetQuality(); else return crafted_quality; } void psItem::SetMaxItemQuality(float v) { crafted_quality = v; } float psItem::GetItemQuality() { return item_quality; } void psItem::SetItemQuality(float v) { item_quality = v; item_quality_original = v; } void psItem::SetDecayResistance(float v) { decay_resistance=v; } float psItem::AddDecay(float severityFactor) { if (!this) return 0; item_quality -= base_stats->GetDecayRate() * severityFactor * (1.0F-decay_resistance); if (item_quality < 0) item_quality = 0; // printf("Item %s quality decayed by %1.2f to %1.2f.\n", GetName(), base_stats->GetDecayRate()*severityFactor*(1.0F-decay_resistance), item_quality); return item_quality; } int psItem::GetRequiredRepairTool() { if (GetCategory()) return GetCategory()->repair_tool_stat_id; else return 0; } bool psItem::GetRequiredRepairToolConsumed() { if (GetCategory()) return GetCategory()->repair_tool_consumed; else return false; } void psItem::GetLocationInWorld(psSectorInfo **sectorinfo,float &loc_x,float &loc_y,float &loc_z,float &loc_yrot) { *sectorinfo=location.loc_sectorinfo; loc_x=location.loc_x; loc_y=location.loc_y; loc_z=location.loc_z; loc_yrot=location.loc_yrot; } void psItem::SetLocationInWorld(psSectorInfo *sectorinfo,float loc_x,float loc_y,float loc_z,float loc_yrot) { location.loc_sectorinfo=sectorinfo; location.loc_x=loc_x; location.loc_y=loc_y; location.loc_z=loc_z; location.loc_yrot=loc_yrot; } csString psItem::GetQuantityName() { psString name = GetName(); return GetQuantityName(name,stack_count); } csString psItem::GetQuantityName(psString name, int stack_count) { if (name.IsEmpty()) return "???"; csString list; if (stack_count == 1) { list.Format("%s %s", (name.IsVowel(0))?"an":"a", name.GetData() ); } else { name.Plural(); // Get plural form of the name list.Format("%d %s", stack_count, name.GetData() ); } return list; } void psItem::SetOwningCharacter(psCharacter *owner) { int i; // No need to go through resetting all the owner pointers if they are already set if (owning_character==owner) return; owning_character=owner; if ( owner ) owningCharacterID = owner->characterid; else owningCharacterID = 0; if (current_stats->GetIsContainer()) { for (i=0;iSetOwningCharacter(owner); } } } bool psItem::GetIsContainerEmpty() { if (!GetIsContainer()) return true; for (int i=0;iGetSumSize(); return size; } psItem *psItem::GetItemInSlot(unsigned int slot) { if (!(current_stats->GetIsContainer()) || slot>=PSITEM_MAX_CONTAINER_SLOTS) return NULL; return container_data.contained_item_ptr[slot]; } bool psItem::TakeOutOfContainer() { unsigned int parent_idx; if (!container_parent) return false; // This object is not contained in any other object for (parent_idx=0;parent_idxcontainer_data.contained_item_ptr[parent_idx]==this) { // TODO: Adjust sum properties if need be - weight, etc container_parent->container_data.contained_item_ptr[parent_idx]=NULL; container_parent->AdjustSumWeight(-sum_weight); container_parent=NULL; owning_character=NULL; loc_in_parent=PSITEM_MAX_CONTAINER_SLOTS; // Cannot save this item because it is in a limbo state. The caller // must call save when the item is in a good state. //Save(); // Save the parent in it's new state. return true; } } // This should not happen. Error3("Item ID %u has parent with ID %u, but parent does not contain child.",GetUID(),container_parent->GetUID()); CS_ASSERT("Item parent does not know about item child in psItem::TakeOutOfContainer()"); container_parent=NULL; owning_character=NULL; loc_in_parent=PSITEM_MAX_CONTAINER_SLOTS; return true; // The parent claims it does not contain this object? } bool psItem::AddItemToContainer(psItem *addme, bool test, psCharacter* useOwner) { unsigned int location_idx; // Fail if this is not a container if (!(GetIsContainer())) { Error3( "%s(%u) is not a container", GetName(), GetUID() ); return false; } //we can't put item into itself if (addme==this || IsIndirectParent(addme)) { Error3( "Cannot add %s(%u) in itself", GetName(), GetUID() ); return false; } // Check size if (GetContainerMaxSize() < addme->GetSize() || addme->GetSize()==65535) { csString error; error.Format( "Item %s(%u) will not fit inside container %s(%u)\nMax Size = %d Item Size = %d\n", addme->GetName(), addme->GetUID(), GetName(), GetUID(), GetContainerMaxSize(), addme->GetSize() ); Error1( error ); return false; } // Remove item if it's in something else if(!test) addme->TakeOutOfContainer(); for (location_idx=0;location_idxcontainer_parent=this; addme->loc_in_parent=location_idx; container_data.contained_item_ptr[location_idx]=addme; if ( useOwner ) addme->SetOwningCharacter(useOwner); else addme->SetOwningCharacter(owning_character); AdjustSumWeight(addme->GetSumWeight()); return true; } } Error3("Item %s(%u) could not be added at all", addme->GetName(), addme->GetUID() ); // Doesn't fit there either return false; } bool psItem::AddItemToContainer(psItem *addme, unsigned int slot, bool test) { // Fail if this is not a container or slot is out of range if (!(GetIsContainer()) || slot>=PSITEM_MAX_CONTAINER_SLOTS) { Error3( "%s(%u) is not a container", GetName(), GetUID() ); return false; } //we can't put item into itself if (addme==this || IsIndirectParent(addme)) { Error3( "%s(%u) is not a container", GetName(), GetUID() ); return false; } // Check size if (GetContainerMaxSize() < addme->GetSize() || addme->GetSize()==65535) { csString error; error.Format( "Item %s(%u) will not fit inside container %s(%u)\nMax Size = %d Item Size = %d\n", addme->GetName(), addme->GetUID(), GetName(), GetUID(), GetContainerMaxSize(), addme->GetSize() ); Error1( error ); return false; } // Fail if the slot is not free and not stackable if (container_data.contained_item_ptr[slot]!=NULL) { csString error; error.Format("%s(%u) already has an item %s(%u) in slot %d.", GetName(), GetUID(), container_data.contained_item_ptr[slot]->GetName(), container_data.contained_item_ptr[slot]->GetUID(), slot ); return false; } if(test) return true; // Remove item if it's in something else addme->TakeOutOfContainer(); addme->container_parent=this; addme->loc_in_parent=slot; container_data.contained_item_ptr[slot]=addme; addme->SetOwningCharacter(owning_character); AdjustSumWeight(addme->GetSumWeight()); return true; } bool psItem::SwapItemsInContainer(unsigned int fromSlot, unsigned int toSlot) { // Fail if this is not a container if (!GetIsContainer()) return false; // Fail if fromSlot is out of range if (fromSlot>=PSITEM_MAX_CONTAINER_SLOTS) return false; // Fail if toSlot is out of range if (toSlot>=PSITEM_MAX_CONTAINER_SLOTS) return false; // swap 'em psItem* toItem = container_data.contained_item_ptr[toSlot]; psItem* fromItem = container_data.contained_item_ptr[fromSlot]; container_data.contained_item_ptr[toSlot] = fromItem; container_data.contained_item_ptr[fromSlot] = toItem; // only set items that exist if (toItem) toItem->SetLocInParent(fromSlot); if (fromItem) fromItem->SetLocInParent(toSlot); return true; } bool psItem::MoveItemsInContainer(unsigned int fromSlot, unsigned int toSlot, int count) { psItem * item; item = RemoveItemsInContainer(fromSlot, count); if (item == NULL) return false; // If the put fails put the item back if ( PutItemsInContainer(toSlot, item, owning_character) != PS_CONT_OK ) { if ( PutItemsInContainer(fromSlot, item, owning_character) != PS_CONT_OK) Error1("psItem::MoveItemsInContainer - reinsertion failed"); } return true; } psItem *psItem::RemoveItemsInContainer(unsigned int fromSlot, int count) { // Fail if fromSlot is out of range if (fromSlot >= PSITEM_MAX_CONTAINER_SLOTS) return NULL; psItem* fromItem = container_data.contained_item_ptr[fromSlot]; if (fromItem==NULL) return NULL; // Fail if move was more then current count if (count < 0 || count > fromItem->GetStackCount() ) return NULL; // check if the item is pickable if (fromItem->flags & PSITEM_FLAG_NOPICKUP) return NULL; // Do full move if ( count == fromItem->GetStackCount() ) { container_data.contained_item_ptr[fromSlot]=NULL; fromItem->container_parent = NULL; return fromItem; } // Do partial move else { psItem* newItem = fromItem->SplitStack((unsigned short)count); if (!newItem) return NULL; newItem->container_parent = NULL; return newItem; } return NULL; } int psItem::PutItemsInContainer(unsigned int toSlot,psItem *item, psCharacter* owner, bool test) { // Fail if toSlot is out of range or item NULL if (toSlot>=PSITEM_MAX_CONTAINER_SLOTS) return PS_CONT_ERR_OUT_RANGE; if (item==NULL) return PS_CONT_ERR_NO_ITEM; //we can't put item into itself if (item==this || IsIndirectParent(item)) return PS_CONT_ERR_SELF; if ( GetContainerMaxSize() < item->GetCurrentStats()->GetSize() ) return PS_CONT_ERR_TO_BIG; psItem* toItem = container_data.contained_item_ptr[toSlot]; // If the slot is already occupied, we will try to stack items if (toItem!=NULL) { // Fail if toItem has other real owner if ((toItem->GetOwningCharacter() != owner) && (toItem->GetOwningCharacter() != 0)) return PS_CONT_ERR_NOT_OWNER; // Check if can't stack if (!item->CheckStackableWith(*toItem)) return PS_CONT_ERR_NO_STACK; // Combine the stacks. int remainder = toItem->CombineStack(item, test); // Handle a remainder during the stacking attempt if (remainder != 0) return remainder; } // otherwise put the item else { if(test) return PS_CONT_OK; container_data.contained_item_ptr[toSlot] = item; item->SetLocInParent( toSlot); //item->SetOwningCharacter( GetOwningCharacter() ); if (owner) item->SetOwningCharacter( owner); item->SetParent( this ); } return PS_CONT_OK; } float psItem::AdjustSumWeight(float delta) { sum_weight += delta; if (container_parent != NULL) container_parent->AdjustSumWeight(delta); else if (owning_character != NULL && GetIsContainer()) // Top level container in character's inventory owning_character->Inventory().ReassessInventoryDimensions(); // Update inventory return sum_weight; } float psItem::RecalculateFullWeight() { sum_weight=GetWeight(); if (current_stats->GetIsContainer()) { for (int i=0;iRecalculateFullWeight(); } } return sum_weight; } void psItem::SetUniqueStats(psItemStats *statptr) { // Consider weight changes float weight_delta=0.0f; // If base_stats is NULL then this is a new item. No chickens here, just us egg. if (base_stats!=NULL) weight_delta-=GetWeight(); if (current_stats==base_stats) current_stats=statptr; base_stats=statptr; // Clear the "uses a stock basic item" flag and set the "uses a unique item stats entry" flag flags &= ~PSITEM_FLAG_USES_BASIC_ITEM; flags |= PSITEM_FLAG_UNIQUE_ITEM; if (current_stats!=base_stats) RecalcCurrentStats(); weight_delta+=GetWeight(); if (weight_delta!=0.0f) AdjustSumWeight(weight_delta); } void psItem::SetBaseStats(psItemStats *statptr) { // Consider weight changes float weight_delta=0.0f; // If base_stats is NULL then this is a new item. No chickens here, just us egg. if (base_stats!=NULL) { weight_delta-=GetWeight(); // Consider the possibility of this previously being a unique item if (flags & PSITEM_FLAG_UNIQUE_ITEM) { // Note we delete here and then quickly reassign base_stats below after a pointer comparison delete base_stats; flags |= PSITEM_FLAG_USES_BASIC_ITEM; flags &= ~PSITEM_FLAG_UNIQUE_ITEM; } } // Consider quality SetItemQuality(statptr->GetQuality()); if (current_stats==base_stats) current_stats=statptr; base_stats=statptr; if (current_stats!=base_stats) RecalcCurrentStats(); weight_delta+=GetWeight(); if (weight_delta!=0.0f) AdjustSumWeight(weight_delta); } void psItem::SetCurrentStats(psItemStats *statptr) { current_stats=statptr; } void psItem::RecalcCurrentStats() { int i; bool has_modifiers=false; /* If there are no modifiers and no effects, then current_stats should equal base_stats. * If the current stats pointer is not the same as the base stats or any of the modifiers * then it may need adjustment. * */ if (current_stats==base_stats) return; for (i=0;iCloneTemporary(); * } * // Add this modifier's stats to the current_stats */ return true; } psItemStats *psItem::GetModifier(int index) { if (index<0 || index>=PSITEM_MAX_MODIFIERS) return NULL; return modifiers[index]; } bool psItem::CheckStackableWith(const psItem& otheritem) const { int i; // TODO: Should unique items ever be stackable? if (GetIsUnique()) return false; int purifyStatus = GetPurifyStatus(); if (purifyStatus == 1) // purifying glyphs cannot be stacked return false; int otherPurifyStatus = otheritem.GetPurifyStatus(); if (purifyStatus != otherPurifyStatus) // glyphs with different purification status cannot be stacked return false; if (!GetIsStackable() || !otheritem.GetIsStackable()) return false; /* Conditions that must be met for stacking: * 1) Base item stats point to the same entry * 2) If there are any modifiers they must point to the same entry in the same order * 3) No effects can be applied. * 4) Crafting attributes match */ if (GetBaseStats()!=otheritem.GetBaseStats()) // If these have different base stats it doesn't matter about modifiers - they cant be combined return false; // TODO: Instead of checking each modifier, we can compare the resulting stats and see if they are equivalent // This requires implementing a comparison function in psItemStats for (i=0;iSetStackCount(newstackcount); newitem->SetLoaded(); // Item is fully created return newitem; } void psItem::Copy(psItem * target) { printf("Copying item '%s' clone it. Owner is %s.\n", GetName(), owning_character ? owning_character->GetCharName() : "None" ); // The location in world is the same target->SetLocationInWorld(location.loc_sectorinfo,location.loc_x,location.loc_y,location.loc_z,location.loc_yrot); // Base stats are the same target->SetBaseStats(GetBaseStats()); // The decay is the same. target->SetDecayResistance(decay_resistance); // The qualities are the same. target->SetItemQuality(item_quality); target->SetMaxItemQuality(crafted_quality); // The flags are the same target->flags = flags; // The crafter is the same. target->crafter_id = crafter_id; // The guild_id is the same. target->guild_id = guild_id; // Current stats are rebuilt; int i; for (i=0;iAddModifier(modifiers[i]); } target->SetOwningCharacter( owning_character); SetCreator(ITEM_CLONE); } psItem *psItem::SplitStack(unsigned short newstackcount) { // Cannot split into a stack of 0 or a stack of the same amount or more than there already are if (newstackcount<1 || newstackcount>=GetStackCount()) return NULL; psItem *newitem = Copy(newstackcount); if (newitem == NULL) return NULL; // Adjust stack count of source SetStackCount(stack_count-newstackcount); printf("Split Stack to make a new stack of %d items.\n", newstackcount); return newitem; } int psItem::CombineStack(psItem *& stackme, bool test) { // Ensure stacking compatability. if (!CheckStackableWith(*stackme)) return -1; // TODO: Implement maximum stack count here. // This just avoids an overflow at 65535 count. // We're working with an unsigned short so this is a bit funky. // We can't just add and check <65536 because it could roll over during the add unsigned short maxmove = 65535 - stack_count; if (maxmove < stackme->GetStackCount()) { int unmoved = stackme->GetStackCount() - maxmove; if(test) return unmoved; // Move a partial stack stackme->SetStackCount(unmoved); SetStackCount(stack_count+maxmove); // Didn't completely move the old stack return unmoved; } else if (maxmove == 0) return -1; else { if(test) return 0; // If stacked item is from a spawn, we want to keep its spawning rules if (stackme->schedule) { if (!schedule) // Absorb schedule SetScheduledItem( stackme->schedule ); stackme->schedule = NULL; // Prevent deleting of shedule later in delete in RemoveInstance } // Average the qualities and set stack count int newStackCount = stack_count+stackme->GetStackCount(); float newQuality = ((GetItemQuality()*GetStackCount())+(stackme->GetItemQuality()*stackme->GetStackCount()))/newStackCount; SetItemQuality(newQuality); SetStackCount(newStackCount); CacheManager::GetSingleton().RemoveInstance(stackme); // Stacks completely merged return 0; } } // Functions that call into the appropriate psItemStats object int psItem::GetAttackAnimID(psCharacter *pschar,iCelEntity *entity) { PSSKILL skill = current_stats->Weapon().Skill(PSITEMSTATS_WEAPONSKILL_INDEX_0); unsigned int curr_level = pschar->GetSkills()->GetSkillRank(skill); return current_stats->GetAttackAnimID(curr_level,entity); } bool psItem::GetIsMeleeWeapon() { return current_stats->GetIsMeleeWeapon(); } bool psItem::GetIsRangeWeapon() { return current_stats->GetIsRangeWeapon(); } bool psItem::GetIsAmmo() { return current_stats->GetIsAmmo(); } bool psItem::GetIsShield() { return current_stats->GetIsShield(); } bool psItem::GetIsContainer() { return current_stats->GetIsContainer(); } bool psItem::GetCanTransform() { return current_stats->GetCanTransform(); } bool psItem::GetUsesAmmo() { return current_stats->GetUsesAmmo(); } bool psItem::GetIsStackable() const { return current_stats->GetIsStackable(); } const char *psItem::GetName() { return current_stats->GetName(); } const char *psItem::GetDescription() { return current_stats->GetDescription(); } PSITEMSTATS_WEAPONTYPE psItem::GetWeaponType() { return current_stats->Weapon().Type(); } PSSKILL psItem::GetWeaponSkill(PSITEMSTATS_WEAPONSKILL_INDEX index) { return current_stats->Weapon().Skill(index); } float psItem::GetLatency() { return current_stats->Weapon().Latency(); } float psItem::GetDamage(PSITEMSTATS_DAMAGETYPE dmgtype) { return current_stats->Weapon().Damage(dmgtype); } float psItem::GetExtraDamagePercent(PSITEMSTATS_DAMAGETYPE dmgtype) { return current_stats->Weapon().ExtraDamagePercent(dmgtype); } PSITEMSTATS_AMMOTYPE psItem::GetAmmoType() { return current_stats->Ammunition().AmmoType(); } float psItem::GetPenetration() { return current_stats->Weapon().Penetration(); } float psItem::GetUntargetedBlockValue() { return current_stats->Weapon().UntargetedBlockValue(); } float psItem::GetTargetedBlockValue() { return current_stats->Weapon().TargetedBlockValue(); } float psItem::GetCounterBlockValue() { return current_stats->Weapon().CounterBlockValue(); } PSITEMSTATS_ARMORTYPE psItem::GetArmorType() { return current_stats->Armor().Type(); } float psItem::GetDamageProtection(PSITEMSTATS_DAMAGETYPE dmgtype) { return current_stats->Armor().Protection(dmgtype); } float psItem::GetHardness() { return current_stats->Armor().Hardness(); } PSITEMSTATS_STAT psItem::GetWeaponAttributeBonusType(PSITEMSTATS_STAT_BONUS_INDEX index) { return current_stats->Weapon().AttributeBonusType(index); } float psItem::GetWeaponAttributeBonus(PSITEMSTATS_STAT stat) { if (GetWeaponAttributeBonusType(PSITEMSTATS_STAT_BONUS_INDEX_0)== stat) return GetWeaponAttributeBonusMax(PSITEMSTATS_STAT_BONUS_INDEX_0); else if (GetWeaponAttributeBonusType(PSITEMSTATS_STAT_BONUS_INDEX_1)== stat) return GetWeaponAttributeBonusMax(PSITEMSTATS_STAT_BONUS_INDEX_1); else if (GetWeaponAttributeBonusType(PSITEMSTATS_STAT_BONUS_INDEX_2)== stat) return GetWeaponAttributeBonusMax(PSITEMSTATS_STAT_BONUS_INDEX_2); else return 0.0F; } float psItem::GetWeaponAttributeBonusMax(PSITEMSTATS_STAT_BONUS_INDEX index) { return current_stats->Weapon().AttributeBonusMax(index); } float psItem::GetWeight() { if (GetIsStackable()) return (current_stats->GetWeight() * GetStackCount()); else return current_stats->GetWeight(); } unsigned short psItem::GetSize() { return current_stats->GetSize(); } unsigned short psItem::GetContainerMaxSize() { return current_stats->GetContainerMaxSize(); } PSITEMSTATS_SLOTLIST psItem::GetValidSlots() { return current_stats->GetValidSlots(); } bool psItem::FitsInSlots(PSITEMSTATS_SLOTLIST slotmask) { return current_stats->FitsInSlots(slotmask); } float psItem::GetDecayResistance() { return decay_resistance; } psMoney& psItem::GetPrice() { return current_stats->GetPrice(); } psMoney psItem::GetSellPrice() { // Merchants like 20% profit. So we take the total price, // multiply with 0.8 and make a new money with that amount // as trias. return psMoney (int (current_stats->GetPrice().GetTotal () * 0.8)); } psItemCategory * psItem::GetCategory() { return current_stats->GetCategory(); } float psItem::GetVisibleDistance() { return current_stats->GetVisibleDistance(); } // Removed until future implementation to avoid confusion /* unsigned int psItem::GetMeshIndex() { return current_stats->GetMeshIndex(); } unsigned int psItem::GetTextureIndex() { return current_stats->GetTextureIndex(); } unsigned int psItem::GetTexturePartIndex() { return current_stats->GetTexturePartIndex(); } */ const char *psItem::GetMeshName() { return current_stats->GetMeshName(); } const char *psItem::GetTextureName() { return current_stats->GetTextureName(); } const char *psItem::GetPartName() { return current_stats->GetPartName(); } const char *psItem::GetPartMeshName() { return current_stats->GetPartMeshName(); } const char *psItem::GetImageName() { return current_stats->GetImageName(); } const char *psItem::GetSound() { return current_stats->GetSound(); } uint32 psItem::GetNextItemUID() { uint32 newid; if (global_itemid_mutex==NULL) { // This is the first call to initialize. Setup the mutex and the global_itemid count global_itemid_mutex=csMutex::Create(false); CS_ASSERT(global_itemid_mutex!=NULL); if (global_itemid_mutex==NULL) return 0; if (global_itemid_mutex->LockWait()) { Result result(db->Select("SELECT MAX(id) maxid from item_instances")); global_itemid=result[0].GetUInt32("maxid")+1; global_itemid_mutex->Release(); } } if (global_itemid_mutex->LockWait()) { newid=global_itemid++; global_itemid_mutex->Release(); return newid; } CS_ASSERT(false); // Could not obtain mutex lock return 0; } uint32 psItem::GetNextUniqueItemStatsUID() { uint32 newid; if (global_uniqueitemid_mutex==NULL) { // This is the first call to initialize. Setup the mutex and the global_itemid count global_uniqueitemid_mutex=csMutex::Create(false); CS_ASSERT(global_uniqueitemid_mutex!=NULL); if (global_uniqueitemid_mutex==NULL) return 0; if (global_uniqueitemid_mutex->LockWait()) { Result result(db->Select("SELECT MAX(id) maxid from item_stats")); global_uniqueitemid=result[0].GetUInt32("maxid")+1; global_uniqueitemid_mutex->Release(); } } if (global_uniqueitemid_mutex->LockWait()) { newid=global_uniqueitemid++; global_uniqueitemid_mutex->Release(); return newid; } CS_ASSERT(false); // Could not obtain mutex lock return 0; } float psItem::GetArmorVSWeaponResistance(psItemStats* armor) { return CacheManager::GetSingleton().GetArmorVSWeaponResistance(armor, current_stats); } double psItem::GetProperty(const char *ptr) { if (!strcasecmp(ptr,"Skill1")) { return GetWeaponSkill((PSITEMSTATS_WEAPONSKILL_INDEX)0); } else if (!strcasecmp(ptr,"Skill2")) { return GetWeaponSkill((PSITEMSTATS_WEAPONSKILL_INDEX)1); } else if (!strcasecmp(ptr,"Skill3")) { return GetWeaponSkill((PSITEMSTATS_WEAPONSKILL_INDEX)2); } else if (!strcasecmp(ptr,"Quality")) { return GetItemQuality(); } else if (!strcasecmp(ptr,"MaxQuality")) { return GetMaxItemQuality(); } else if (!strcasecmp(ptr,"WeaponCBV")) { return GetCounterBlockValue(); } else if (!strcasecmp(ptr,"Hardness")) { return GetHardness(); } else if (!strcasecmp(ptr,"Penetration")) { return GetPenetration(); } else if (!strcasecmp(ptr,"DamageSlash")) { return GetDamage(PSITEMSTATS_DAMAGETYPE_SLASH); } else if (!strcasecmp(ptr,"ProtectSlash")) { return GetDamageProtection(PSITEMSTATS_DAMAGETYPE_SLASH); } else if (!strcasecmp(ptr,"ExtraDamagePctSlash")) { return GetExtraDamagePercent(PSITEMSTATS_DAMAGETYPE_SLASH); } else if (!strcasecmp(ptr,"DamageBlunt")) { return GetDamage(PSITEMSTATS_DAMAGETYPE_BLUNT); } else if (!strcasecmp(ptr,"ProtectBlunt")) { return GetDamageProtection(PSITEMSTATS_DAMAGETYPE_BLUNT); } else if (!strcasecmp(ptr,"ExtraDamagePctBlunt")) { return GetExtraDamagePercent(PSITEMSTATS_DAMAGETYPE_BLUNT); } else if (!strcasecmp(ptr,"DamagePierce")) { return GetDamage(PSITEMSTATS_DAMAGETYPE_PIERCE); } else if (!strcasecmp(ptr,"ProtectPierce")) { return GetDamageProtection(PSITEMSTATS_DAMAGETYPE_PIERCE); } else if (!strcasecmp(ptr,"ExtraDamagePctPierce")) { return GetExtraDamagePercent(PSITEMSTATS_DAMAGETYPE_PIERCE); } else if (!strcasecmp(ptr,"StrMalus")) { return GetWeaponAttributeBonus(PSITEMSTATS_STAT_STRENGTH); } else if (!strcasecmp(ptr,"AgiMalus")) { return GetWeaponAttributeBonus(PSITEMSTATS_STAT_AGILITY); } else if (!strcasecmp(ptr,"Weight")) { return GetWeight(); } else if (!strcasecmp(ptr,"MentalFactor")) { int temp = GetWeaponSkill((PSITEMSTATS_WEAPONSKILL_INDEX)0); return ( (double)CacheManager::GetSingleton().GetSkillByID((temp<0)?0:temp)->mental_factor / 100.0 ); } else if (!strcasecmp(ptr,"RequiredRepairSkill")) { printf("%s=%d\n",ptr,base_stats->GetCategory()->repair_skill_id); return base_stats->GetCategory()->repair_skill_id; } else if (!strcasecmp(ptr,"SalePrice")) { printf("%s=%d\n",ptr,base_stats->GetPrice().GetTotal()); return base_stats->GetPrice().GetTotal(); } else { CPrintf(CON_ERROR, "psItem::GetProperty(%s) failed\n",ptr); return 0; } } void psItem::SetIsLocked(bool v) { if (v) flags=flags | PSITEM_FLAG_LOCKED; else flags=flags & ~PSITEM_FLAG_LOCKED; } void psItem::SetIsLockable(bool v) { if (v) flags=flags | PSITEM_FLAG_LOCKABLE; else flags=flags & ~PSITEM_FLAG_LOCKABLE; } void psItem::SetIsKey(bool v) { if (v) flags = flags | PSITEM_FLAG_KEY; else flags = flags & ~PSITEM_FLAG_KEY; } void psItem::SetLockpickSkill(PSSKILL v) { lockpickSkill = v; } void psItem::SetLockStrength(unsigned int v) { lockStrength = v; } bool psItem::CanOpenLock(uint32 id) { return openableLocks.Find(id) != csArrayItemNotFound || openableLocks.Find(KEY_SKELETON) != csArrayItemNotFound; } void psItem::AddOpenableLock(uint32 v) { if (openableLocks.Find(v) == csArrayItemNotFound) openableLocks.Push(v); } void psItem::MakeSkeleton(bool b) { if (b) AddOpenableLock(KEY_SKELETON); else RemoveOpenableLock(KEY_SKELETON); } bool psItem::GetIsSkeleton() { return openableLocks.Find(KEY_SKELETON) != csArrayItemNotFound; } void psItem::RemoveOpenableLock(uint32 v) { size_t n = openableLocks.Find(v); if (n != csArrayItemNotFound) openableLocks.DeleteIndexFast(n); } void psItem::ClearOpenableLocks() { openableLocks.DeleteAll(); } void psItem::SetLocInParent(unsigned int location) { loc_in_parent = location; } void psItem::SetParent( psItem* item ) { container_parent = item; } bool psItem::IsIndirectParent(psItem * item) { psItem * parent = container_parent; while (parent != NULL && parent != item) parent = parent->container_parent; return parent == item; } /********************************************************/ psItemSet::~psItemSet() { while (set.Length()) delete set.Pop(); } void psItemSet::Add(psItem *item,uint32 parentid) { set.Push(item); parents.Push(parentid); } bool psItemSet::ResolveAllParents() { // Resolve parent-child relationships for (size_t i=0; iGetUID()==parents[i]) { found = true; success = set[pidx]->AddItemToContainer(set[i],set[i]->GetLocInParent() ); if (!success) { Error6("Item %s(%u) could not be added inside it's parent %s(%u) in location %d\n", set[i]->GetName(), set[i]->GetUID(), set[pidx]->GetName(), set[pidx]->GetUID(), set[i]->GetLocInParent() ); } else set[pidx]->Save(true); // On to the next unresolved item break; } } // Delete item because it could not be resolved if(!success) { if(!found) Error3("Could not find parent for item %s(%u)\n", set[i]->GetName(), set[i]->GetUID()); delete set[i]; set[i] = NULL; } } } return true; } void psItemSet::Release() { set.DeleteAll(); // Empty the set. This will not delete the items themselves. parents.DeleteAll(); } psItem *psItemSet::Get(size_t n) { if (n < set.Length()) return set.Get(n); else return NULL; } void psItem::SetIsPickupable(bool v) { if (!v) flags=flags | PSITEM_FLAG_NOPICKUP; else flags=flags & ~PSITEM_FLAG_NOPICKUP; } void psItem::SetIsTransient(bool v) { if (!v) flags=flags | PSITEM_FLAG_TRANSIENT; else flags=flags & ~PSITEM_FLAG_TRANSIENT; } bool psItem::CheckRequirements( psCharacter* charData, csString& resp ) { return base_stats->CheckRequirements( charData, resp ); } void psItem::ItemAboutToMove() { ScheduleRespawn(); } void psItem::ScheduleRespawn() { if(!schedule) return; // Transfer the spawn rules to the new item if(!schedule->WantToDie()) // removed? { psItemSpawnEvent* event = new psItemSpawnEvent(schedule); psserver->GetEventManager()->Push(event); } else { // Want to die, delete it delete schedule; } // Remove this shedule for this item, since we don't want an item in for example // the inventory to call respawn when it's dropped and picked up again schedule = NULL; } psScheduledItem::psScheduledItem(int id,uint32 itemID,csVector3& position, psSectorInfo* sector, int interval,int maxrnd) { spawnID = id; this->itemID = itemID; this->pos = position; this->sector = sector; this->interval = interval; this->maxrnd = maxrnd; wantToDie= false; printf("Item spawn (%u) created\n",itemID); } psItem* psScheduledItem::CreateItem() // Spawns the item { if(wantToDie) return NULL; Notify2(LOG_SPAWN,"Spawning item (%u)\n",itemID); psItemStats *stats = CacheManager::GetSingleton().GetBasicItemStatsByID(itemID); if (stats==NULL) { Error2("Could not find basic stats with ID %u for item spawn.\n",itemID); } else { psItem *item = stats->InstantiateBasicItem(); if (item) { // Create the item item->SetLocationInWorld(GetSector(),GetPosition().x, GetPosition().y, GetPosition().z, 0); if ( !EntityManager::GetSingleton().CreateItem(item,false) ) { delete item; return NULL; } // Transfer the spawning rules for it to pass it forward psScheduledItem* newsch = new psScheduledItem(*this); item->SetScheduledItem(newsch); lastSpawn = csGetTicks(); item->SetLoaded(); // Item is fully created item->Save(); // First save return item; } } return NULL; } void psScheduledItem::UpdatePosition(csVector3& position, const char *sector) { if(wantToDie) return; pos = position; db->Command("UPDATE hunt_locations SET x='%f', y='%f', z='%f', sector='%s' WHERE id='%d'", pos.x,pos.y,pos.z,sector,spawnID); } void psScheduledItem::ChangeIntervals(int newint, int newrand) { if(wantToDie) return; interval = newint; maxrnd = newrand; db->Command("UPDATE hunt_locations SET interval='%d', max_random='%d' WHERE id='%d'", interval,maxrnd,spawnID); } void psScheduledItem::Remove() { db->Command("DELETE FROM hunt_locations WHERE id='%d'",spawnID); wantToDie = true; } int psScheduledItem::MakeInterval() { int rnd = (int)psserver->GetRandom(maxrnd); return interval + rnd; } bool psItem::Destroy() { // csString itemid; if (this->GetIsContainer()) { for (int i=0;iGetItemInSlot(i); if (child) child->Destroy(); } } if (this->GetIsUnique()) { // TODO: Delete unique item entry } if (!this->Delete()) { Error3("Failed to delete item ID %u. Error '%s'.",this->GetUID(),db->GetLastError()); return false; } return true; } bool psItem::Delete() { if ( db->Command("DELETE FROM item_instances where id='%u'",this->uid)!=1) return false; uid = ID_DONT_SAVE_ITEM; // prevent update attempts when key is -1 unsigned return true; } void psItem::ScheduleRemoval() { this->SetFlags(this->GetFlags() | PSITEM_FLAG_TRANSIENT); int randomized_interval = psserver->rng->Get(REMOVAL_INTERVAL_RANGE); psItemRemovalEvent *event = new psItemRemovalEvent(REMOVAL_INTERVAL_MINIMUM + randomized_interval, this->uid ); psserver->GetEventManager()->Push(event); printf("Scheduling removal of object for %d ticks from now.\n",REMOVAL_INTERVAL_MINIMUM + randomized_interval); } void psItem::DeleteObjectCallback(iDeleteNotificationObject * object) { if (gItem) { gItem->UnregisterCallback(this); gItem = NULL; } } void psItem::SetGemObject(gemItem *object) { // Unregister previous callbacks if the current gItem is not NULL if (gItem) gItem->UnregisterCallback(this); // Set the new gItem and register callback gItem = object; if (gItem) gItem->RegisterCallback(this); }