/* * psserverchar.cpp * * Copyright (C) 2002 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. * * Communicates with the client side version of charmanager. */ #include #include #include #include #include "util/serverconsole.h" #include "util/psxmlparser.h" #include "util/log.h" #include "util/mathscript.h" #include "util/psconst.h" #include "util/eventmanager.h" #include "net/message.h" #include "net/messages.h" #include "net/msghandler.h" #include "net/charmessages.h" #include "engine/celbase.h" #include "engine/drmessage.h" #include "bulkobjects/pscharacter.h" #include "bulkobjects/pscharacterloader.h" #include "bulkobjects/psitem.h" #include "bulkobjects/pstrade.h" #include "bulkobjects/psraceinfo.h" #include "bulkobjects/pssectorinfo.h" #include "bulkobjects/psmerchantinfo.h" #include "bulkobjects/psactionlocationinfo.h" #include "globals.h" #include "psserverchar.h" #include "client.h" #include "playergroup.h" #include "clients.h" #include "entitymanager.h" #include "util/psdatabase.h" #include "psserver.h" #include "chatmanager.h" #include "groupmanager.h" #include "spellmanager.h" #include "workmanager.h" #include "netmanager.h" #include "cachemanager.h" #include "progressionmanager.h" #include "creationmanager.h" #include "exchangemanager.h" #include "actionmanager.h" #include "serverstatus.h" ///This expresses in seconds how many days the char hasn't logon. 60 days, at the moment. #define MAX_DAYS_NO_LOGON 5184000 psServerCharManager::psServerCharManager() { slotManager = NULL; } psServerCharManager::~psServerCharManager() { if (psserver->GetEventManager()) { psserver->GetEventManager()->Unsubscribe(this, MSGTYPE_CHAR_INFO); psserver->GetEventManager()->Unsubscribe(this, MSGTYPE_GUIINVENTORY); psserver->GetEventManager()->Unsubscribe(this, MSGTYPE_GUIMERCHANT); psserver->GetEventManager()->Unsubscribe(this, MSGTYPE_CHAR_CREATE_UPLOAD); psserver->GetEventManager()->Unsubscribe(this, MSGTYPE_VIEW_ITEM); psserver->GetEventManager()->Unsubscribe(this, MSGTYPE_CRAFT_INFO); psserver->GetEventManager()->Unsubscribe(this, MSGTYPE_WRITE_BOOK); } delete slotManager; slotManager = NULL; } bool psServerCharManager::Initialize( ClientConnectionSet* ccs) { clients = ccs; psserver->GetEventManager()->Subscribe(this, MSGTYPE_CHAR_INFO,REQUIRE_READY_CLIENT); psserver->GetEventManager()->Subscribe(this, MSGTYPE_GUIINVENTORY,NO_VALIDATION); psserver->GetEventManager()->Subscribe(this, MSGTYPE_GUIMERCHANT,REQUIRE_READY_CLIENT|REQUIRE_ALIVE); psserver->GetEventManager()->Subscribe(this, MSGTYPE_CHAR_CREATE_UPLOAD,NO_VALIDATION); psserver->GetEventManager()->Subscribe(this, MSGTYPE_VIEW_ITEM,REQUIRE_READY_CLIENT); psserver->GetEventManager()->Subscribe(this, MSGTYPE_CRAFT_INFO, REQUIRE_READY_CLIENT); psserver->GetEventManager()->Subscribe(this, MSGTYPE_WRITE_BOOK, REQUIRE_READY_CLIENT); slotManager = new SlotManager; if ( !(slotManager && slotManager->Initialize()) ) return false; return true; } void psServerCharManager::ViewItem( MsgEntry* me ) { psViewItemDescription mesg(me); Client* client = clients->Find(me->clientnum); ViewItem(client, mesg.containerID, mesg.slotID, mesg.parentContainerID); } void psServerCharManager::ViewItem(Client* client, int containerID, int slotID, int parentContainerID) { psItem* item = slotManager->FindItem( client, containerID, slotID, parentContainerID ); if ( !item ) { psActionLocation *action = psserver->GetActionManager()->FindAction( containerID ); if ( !action ) { Error3( "No item/action : %d, %d", containerID, slotID ); return; } else { if ( action->response.StartsWith( "", false ) ) { // load response into XML doc csRef doc = ParseString( action->response ); if(!doc) { Error1("Parse error in action response"); return; } csRef root = doc->GetRoot(); if(!root) { Error1("No XML root in action response"); return; } csRef topNode = root->GetNode( "Examine" ); if(!topNode) { Error1("No tag in action response"); return; } // Check for container csRef containerNode, descriptionNode, gameboardNode; containerNode = topNode->GetNode( "Container" ); if ( containerNode ) { uint32 instance_id = (uint32)containerNode->GetAttributeValueAsInt( "ID" ); gemItem* realItem = GEMSupervisor::GetSingleton().FindItemEntity( instance_id ); if (!realItem) { Error3("Invalid instance ID %u in action location %s", instance_id, action->name.GetDataSafe()); return; } item = realItem->GetItem(); if ( !item ) { CPrintf (CON_ERROR, "Invalid ItemID in Action Location Response.\n"); return; } SendActionContents( client, action, topNode, item ); } // Check for minigames else if ((gameboardNode = topNode->GetNode("GameBoard"))) { descriptionNode = topNode->GetNode("Description"); if (descriptionNode) { psViewItemDescription mesg(client->GetClientNum(), action->name, descriptionNode->GetContentsValue(), csString(""), 0); mesg.SendMessage(); } else { Error2("Action location %s XML response is not a valid response", action->name.GetData()); return; } } else if (item->GetBaseStats()->GetIllumination().Length() == 0) // Not a container, show description or map { descriptionNode = topNode->GetNode( "Description" ); if ( descriptionNode ) { psViewItemDescription mesg( client->GetClientNum(), action->name, descriptionNode->GetContentsValue(), csString( "" ) , 0 ); mesg.SendMessage(); } else { CPrintf (CON_ERROR, "Action Location XML response is not a valid response. \n"); return; } } else // Illumination { descriptionNode = topNode->GetNode( "Description" ); if ( descriptionNode ) { psIlluminationMessage msg( client->GetClientNum(), item->GetBaseStats()->GetIllumination() ); msg.SendMessage(); } else { CPrintf (CON_ERROR, "Action Location XML response is not a valid response. \n"); return; } } } else { psViewItemDescription mesg( client->GetClientNum(), action->name, action->response, csString( "" ) , 0 ); mesg.SendMessage(); } } } else { if ( item->GetIsContainer() ) { SendItemContents( client, item, containerID, slotID ); } //for now, we pretend that /examine reads. When we implement /read, this will change else if( item->GetBaseStats()->GetIsReadable() ) SendBookText( client, item ); else if (item->GetBaseStats()->GetIllumination().Length() == 0) // Not a container, show description or map SendItemDescription( client, item ); else // Illumination { psIlluminationMessage msg( client->GetClientNum(), item->GetBaseStats()->GetIllumination() ); msg.SendMessage(); } } } void psServerCharManager::HandleBookWrite(MsgEntry* me, Client* client){ psWriteBookMessage mesg(me); // CPrintf(CON_DEBUG, "Handling: %s\n",mesg.ToString(NULL).GetDataSafe()); //if we're getting this, it's gotta be a request or a save if(mesg.messagetype == mesg.REQUEST){ psItem* item = slotManager->FindItem( client, mesg.containerID, mesg.slotID, mesg.parentContainerID ); //is it a writable book? if(item && item->GetBaseStats()->GetIsWriteable() ){ //We could maybe let the work manager know that we're busy writing something //or track that this is the book we're working on, and only allow saves to a //book that was opened for writing. This would be a good thing. //Also check for other writing in progress csString theText(item->GetBaseStats()->GetDescription()); psWriteBookMessage resp(client->GetClientNum(), theText, true, mesg.slotID, mesg.containerID, mesg.parentContainerID); resp.SendMessage(); // CPrintf(CON_DEBUG, "Sent: %s\n",resp.ToString(NULL).GetDataSafe()); } else { //construct error message indicating that the item is not editable csString err("You cannot write on this item"); psWriteBookMessage resp(client->GetClientNum(),err, false, mesg.slotID, mesg.containerID, mesg.parentContainerID); resp.SendMessage(); // CPrintf(CON_DEBUG, "Sent: %s\n",resp.ToString(NULL).GetDataSafe()); } } else if (mesg.messagetype == mesg.SAVE){ // CPrintf(CON_DEBUG, "Attempt to save book in slot id %d\n",mesg.slotID); //something like: // psItem* item = slotManager->FindItem( client, mesg.containerID, mesg.slotID, mesg.parentContainerID ); // or psItem* item = (find the player)->GetCurrentWritingItem(); // item->SetBookText(mesg.content); // clear current writing item } } void psServerCharManager::HandleMessage( MsgEntry* me, Client *client ) { client = clients->FindAny(me->clientnum); if (!client) { CPrintf (CON_ERROR, "***Couldn't find clientnum in serverchar HandleMessage\n"); return; } switch ( me->GetType() ) { case MSGTYPE_GUIINVENTORY: { HandleInventoryMessage(me); return; } case MSGTYPE_VIEW_ITEM: { ViewItem( me ); break; } //Not yet implemented, using /examine for now case MSGTYPE_READ_BOOK: { ViewItem( me ); break; } case MSGTYPE_WRITE_BOOK: { HandleBookWrite( me, client ); break; } case MSGTYPE_CHAR_INFO: { HandleCharInfo( me, client ); return; } case MSGTYPE_GUIMERCHANT: { HandleMerchantMessage(me, client); break; } case MSGTYPE_CHAR_CREATE_UPLOAD: { HandleUploadMessage( me, client ); break; } case MSGTYPE_CRAFT_INFO: { HandleCraftInfo( me, client ); break; } } } //--------------------------------------------------------------------------- /** * This handles all formats of inventory message. */ //--------------------------------------------------------------------------- bool psServerCharManager::HandleInventoryMessage( MsgEntry* me ) { if ( !me ) return false; psGUIInventoryMessage incoming(me); int fromClientNumber = me->clientnum; switch ( incoming.command ) { case psGUIInventoryMessage::REQUEST: case psGUIInventoryMessage::UPDATE_REQUEST: { SendInventory(fromClientNumber, (static_cast(incoming.command)==psGUIInventoryMessage::UPDATE_REQUEST)); break; } } return true; } bool psServerCharManager::SendInventory( int clientNum, bool sendUpdatesOnly ) { psGUIInventoryMessage* outgoing; Client* client = clients->Find(clientNum); if (client==NULL) return false; int toClientNumber = clientNum; int itemCount, itemRemovedCount; unsigned int z; psCharacter *chardata=client->GetCharacterData(); if (chardata==NULL) return false; psInventoryCacheServer *inventoryCache = chardata->Inventory().GetInventoryCacheServer(); if (inventoryCache==NULL) return false; // send inventory updates only: but only if the server also // believes the client's inventory cache is valid if (sendUpdatesOnly && (inventoryCache->GetCacheStatus() == psCache::VALID)) { // count items that we are going to send, including vacated slots itemCount = 0; itemRemovedCount = 0; for ( z = 0; z < PSCHARACTER_SLOT_COUNT; z++ ) { if (inventoryCache->HasEquipSlotModified(z)) { if (chardata->Inventory().GetEquipmentItem(z) != NULL) itemCount++; else itemRemovedCount++; } } for (z=0;zHasBulkSlotModified(z)) { if (chardata->Inventory().GetBulkItem(z) != NULL) itemCount++; else itemRemovedCount++; } } outgoing = new psGUIInventoryMessage(toClientNumber, psGUIInventoryMessage::UPDATE_LIST, itemCount, itemRemovedCount, chardata->Inventory().MaxWeight() ); if (!outgoing) return false; for ( z = 0; z < PSCHARACTER_SLOT_COUNT; z++ ) { psItem *item = chardata->Inventory().GetEquipmentItem(z); if ( item && inventoryCache->HasEquipSlotModified(z) ) { csString name; outgoing->AddItem( item->GetName(), CONTAINER_INVENTORY_EQUIPMENT, z, item->GetStackCount(), item->GetSumWeight(), item->GetSumSize(), item->GetImageName(), item->GetPurifyStatus()); inventoryCache->ClearEquipSlot(z); } } for (z=0;zInventory().GetBulkItem(z); if ( item && inventoryCache->HasBulkSlotModified(z) ) { csString name; outgoing->AddItem( item->GetName(), CONTAINER_INVENTORY_BULK, z, item->GetStackCount(), item->GetSumWeight(), item->GetSumSize(), item->GetImageName(), item->GetPurifyStatus()); inventoryCache->ClearBulkSlot(z); } } for ( z = 0; z < PSCHARACTER_SLOT_COUNT; z++ ) { psItem *item = chardata->Inventory().GetEquipmentItem(z); if ( !item && inventoryCache->HasEquipSlotModified(z) ) { outgoing->AddEmptySlot( CONTAINER_INVENTORY_EQUIPMENT, z); inventoryCache->ClearEquipSlot(z); } } for (z=0;zInventory().GetBulkItem(z); if ( !item && inventoryCache->HasBulkSlotModified(z) ) { outgoing->AddEmptySlot( CONTAINER_INVENTORY_BULK, z); inventoryCache->ClearBulkSlot(z); } } } // send the entire inventory else { // count items that we are going to send itemCount = 0; for ( z = 0; z < PSCHARACTER_SLOT_COUNT; z++ ) if (chardata->Inventory().GetEquipmentItem(z) != NULL) itemCount++; for (z=0;zInventory().GetBulkItem(z) != NULL) itemCount++; outgoing = new psGUIInventoryMessage(toClientNumber, psGUIInventoryMessage::LIST, itemCount, 0, chardata->Inventory().MaxWeight() ); if (!outgoing) return false; for ( z = 0; z < PSCHARACTER_SLOT_COUNT; z++ ) { psItem *item = chardata->Inventory().GetEquipmentItem(z); if ( item ) { csString name; outgoing->AddItem( item->GetName(), CONTAINER_INVENTORY_EQUIPMENT, z, item->GetStackCount(), item->GetSumWeight(), item->GetSumSize(), item->GetImageName(), item->GetPurifyStatus()); } } for (z=0;zInventory().GetBulkItem(z); if ( item ) { csString name; outgoing->AddItem( item->GetName(), CONTAINER_INVENTORY_BULK, z, item->GetStackCount(), item->GetSumWeight(), item->GetSumSize(), item->GetImageName(), item->GetPurifyStatus()); } } inventoryCache->ClearAllSlots(); } outgoing->AddMoney(chardata->Money()); if (outgoing->valid) { outgoing->msg->ClipToCurrentSize(); psserver->GetEventManager()->SendMessage(outgoing->msg); // server now can believe the clients inventory cache is upto date inventoryCache->SetCacheStatus(psCache::VALID); } else { Bug2("Could not create valid psGUIInventoryMessage for client %u.\n",toClientNumber); } return true; } bool psServerCharManager::UpdateItemViews( int clientNum ) { Client* client = clients->Find(clientNum); // If inventory window is up, update it SendInventory( clientNum ); // If glyph window is up, update it psserver->GetSpellManager()->SendGlyphs(client); return true; } bool psServerCharManager::IsBanned(const char* name) { // Check if the name is banned csString nName = NormalizeCharacterName(name); for(int i = 0;i < (int)CacheManager::GetSingleton().GetBadNamesCount(); i++) { csString name = CacheManager::GetSingleton().GetBadName(i); // Name already normalized if(name == nName) return true; } return false; } int psServerCharManager::HasConnected( csString name ) { int secondsLastLogin; secondsLastLogin = 0; //Query to the db that calculates already the amount of seconds since the last login. Result result(db->Select("SELECT last_login, UNIX_TIMESTAMP() - UNIX_TIMESTAMP(last_login) as seconds_since_last_login FROM characters WHERE name = '%s'", name.GetData() )); //There is no character with such a name. if (!result.IsValid() || result.Count() == 0) { return 1; } //We check when the char was last online. secondsLastLogin = result[0].GetInt(1); if ( secondsLastLogin > MAX_DAYS_NO_LOGON )//the result is major than 2 month { return 2; } //The char has connected recently. return 0; } void psServerCharManager::HandleCraftInfo( MsgEntry* me, Client* client ) { psCharacter* chardata = client->GetCharacterData(); if ( !chardata ) return; psItem* mindPattern = chardata->Inventory().GetEquipmentItem(PSCHARACTER_SLOT_MIND); if ( !mindPattern ) return; psItemStats* itemStats = mindPattern->GetCurrentStats(); psTradePatterns* pattern = CacheManager::GetSingleton().GetTradePatternByItemID( itemStats->GetUID() ); if ( pattern == NULL ) return; psMsgCraftingInfo* craftMsg = CacheManager::GetSingleton().GetCraftInfo( pattern->GetId() ); if ( craftMsg ) { craftMsg->msg->clientnum = me->clientnum; craftMsg->SendMessage(); } } void psServerCharManager::HandleUploadMessage( MsgEntry* me, Client *client ) { Debug1( LOG_NEWCHAR, me->clientnum,"New Character is being created" ); psCharUploadMessage upload(me); if (!upload.valid) { Debug2(LOG_NET,me->clientnum,"Received unparsable psUploadMessage from client %u.",me->clientnum); return; } int acctID = client->GetAccountID(); if ( !acctID ) { Error2( "Player account %d could not be located", acctID ); psCharRejectedMessage reject(me->clientnum); psserver->GetEventManager()->Broadcast(reject.msg, NetBase::BC_FINALPACKET); psserver->RemovePlayer (me->clientnum,"Could not find your account."); return; } // Check to see if the player already has 4 accounts; csString query; query.Format( "Select id from characters where account_id=%d", acctID ); Result result (db->Select( query ) ); if ( result.IsValid() && result.Count() >= CHARACTERS_ALLOWED ) { psserver->RemovePlayer (me->clientnum,"At your character limit."); return; } csString playerName = upload.name; csString lastName = upload.lastname; playerName = NormalizeCharacterName(playerName); lastName = NormalizeCharacterName(lastName); // Check banned names if(IsBanned(playerName)) { csString error; error.Format("The name %s is banned", playerName.GetData() ); psCharRejectedMessage reject(me->clientnum, psCharRejectedMessage::RESERVED_NAME, (char*)error.GetData() ); reject.SendMessage(); return; } if(IsBanned(lastName)) { csString error; error.Format("The lastname %s is banned", lastName.GetData() ); psCharRejectedMessage reject(me->clientnum, psCharRejectedMessage::RESERVED_NAME, (char*)error.GetData() ); reject.SendMessage(); return; } Debug3( LOG_NEWCHAR, me->clientnum,"Got player firstname (%s) and lastname (%s)\n",playerName.GetData(), lastName.GetData() ); /////////////////////////////////////////////////////////////// // Check to see if the player name is valid /////////////////////////////////////////////////////////////// if ( playerName.Length() == 0 || !FilterName(playerName) ) { psCharRejectedMessage reject(me->clientnum, psCharRejectedMessage::NON_LEGAL_NAME, "The name you specifed is not a legal player name." ); psserver->GetEventManager()->SendMessage(reject.msg); return; } if ( lastName.Length() != 0 && !FilterName(lastName) ) { psCharRejectedMessage reject(me->clientnum, psCharRejectedMessage::NON_LEGAL_NAME, "The name you specifed is not a legal lastname." ); psserver->GetEventManager()->SendMessage(reject.msg); return; } Debug2( LOG_NEWCHAR, me->clientnum,"Checking player firstname '%s'..\n",playerName.GetData()); /////////////////////////////////////////////////////////////// // Check to see if the character name is unique in 'characters'. /////////////////////////////////////////////////////////////// if ( !IsUnique( playerName ) ) { psCharRejectedMessage reject(me->clientnum, psCharRejectedMessage::NON_UNIQUE_NAME, "The firstname you specifed is not unique." ); psserver->GetEventManager()->SendMessage(reject.msg); return; } if (lastName.Length()) { Debug2( LOG_NEWCHAR, me->clientnum,"Checking player lastname '%s'..\n",lastName.GetData()); if ( !IsLastNameUnique( lastName ) ) { psCharRejectedMessage reject(me->clientnum, psCharRejectedMessage::NON_UNIQUE_NAME, "The lastname you specifed is not unique." ); psserver->GetEventManager()->SendMessage(reject.msg); return; } } /////////////////////////////////////////////////////////////// // Check to see if the character name is on the reserve list. /////////////////////////////////////////////////////////////// int reservedName = IsReserved( playerName, acctID ); if ( reservedName == NAME_RESERVED ) { csString error; error.Format("The name %s is reserved", playerName.GetData() ); psCharRejectedMessage reject(me->clientnum, psCharRejectedMessage::RESERVED_NAME, (char*)error.GetData() ); psserver->GetEventManager()->SendMessage(reject.msg); return; } csString error; if ( psserver->charCreationManager->Validate( upload, error) == false ) { error.Append(", your creation choices are invalid." ); psCharRejectedMessage reject(me->clientnum, psCharRejectedMessage::INVALID_CREATION, (char*)error.GetData() ); reject.SendMessage(); return; } /////////////////////////////////////////////////////////////// // Create the psCharacter structure for the player. /////////////////////////////////////////////////////////////// psCharacter *chardata=new psCharacter(); chardata->SetCharType(PSCHARACTER_TYPE_PLAYER); chardata->SetFullName(playerName,lastName); chardata->SetDescription(upload.bio); psRaceInfo *raceinfo=CacheManager::GetSingleton().GetRaceInfoByNameGender( (PSCHARACTER_RACE)upload.race, (PSCHARACTER_GENDER)upload.gender); if (raceinfo==NULL) { Error3("Invalid race/gender combination on character creation: Race='%d' Gender='%d'", upload.race, upload.gender ); psCharRejectedMessage reject(me->clientnum); psserver->GetEventManager()->Broadcast(reject.msg, NetBase::BC_FINALPACKET); psserver->RemovePlayer (me->clientnum,"Player tried to create an invalid race/gender."); delete chardata; return; } chardata->SetRaceInfo(raceinfo); chardata->SetHitPoints(50.0); chardata->SetHitPointsMax(0.0); float x,y,z,yrot; const char *sectorname; raceinfo->GetStartingLocation(x,y,z,yrot,sectorname); if (sectorname==NULL) { Error1("Race has NULL starting sector" ); psCharRejectedMessage reject(me->clientnum); psserver->GetEventManager()->Broadcast(reject.msg, NetBase::BC_FINALPACKET); psserver->RemovePlayer (me->clientnum,"No starting Sector."); delete chardata; return; } psSectorInfo *sectorinfo=CacheManager::GetSingleton().GetSectorInfoByName(sectorname); if (sectorinfo==NULL) { Error2("Unresolvable starting sector='%s'", sectorname ); psCharRejectedMessage reject(me->clientnum); psserver->GetEventManager()->Broadcast(reject.msg, NetBase::BC_FINALPACKET); psserver->RemovePlayer (me->clientnum,"No starting Sector."); delete chardata; return; } chardata->SetLocationInWorld(sectorinfo,x,y,z,yrot); psTrait * trait; CPrintf(CON_DEBUG, "Trait: %d\n", upload.selectedFace ); trait = CacheManager::GetSingleton().GetTraitByID(upload.selectedFace); if ( trait ) chardata->SetTraitForLocation( trait->location, trait ); trait = CacheManager::GetSingleton().GetTraitByID(upload.selectedHairStyle); if ( trait ) chardata->SetTraitForLocation( trait->location, trait ); trait = CacheManager::GetSingleton().GetTraitByID(upload.selectedBeardStyle); if ( trait ) chardata->SetTraitForLocation( trait->location, trait ); trait = CacheManager::GetSingleton().GetTraitByID(upload.selectedHairColour); if ( trait ) chardata->SetTraitForLocation( trait->location, trait ); trait = CacheManager::GetSingleton().GetTraitByID(upload.selectedSkinColour); if ( trait ) chardata->SetTraitForLocation( trait->location, trait ); csString filename; filename.Format("/planeshift/models/%s/%s.cal3d",raceinfo->mesh_name,raceinfo->mesh_name); gemActor *actor = new gemActor( chardata, raceinfo->mesh_name, filename, EntityManager::GetSingleton().FindSector(sectorinfo->name), csVector3(x,y,z),yrot, client->GetClientNum(),chardata->GetCharacterID()); actor->SetupCharData(); if( !upload.verify ) { if (!psServer::CharacterLoader.NewCharacterData(acctID,chardata)) { Error1("Character could not be created."); psCharRejectedMessage reject(me->clientnum); psserver->GetEventManager()->Broadcast(reject.msg, NetBase::BC_FINALPACKET); psserver->RemovePlayer (me->clientnum,"Your character could not be created in the database."); delete chardata; return; } } // Check to see if a path name was set. If so we will use that to generate // the character starting stats and skills. if ( upload.path != "None" ) { // Progression Event name is PATH_PathName csString name("PATH_"); name.Append(upload.path); ProgressionEvent* event = psserver->GetProgressionManager()->FindEvent( name.GetData() ); if ( event ) { // The script uses the race base character points to calculate starting stats. event->SetValue("CharPoints", (double)raceinfo->initialCP ); psserver->GetProgressionManager()->ProcessEvent( event, actor, actor ); } } else { //int cpUsage = psserver->charCreationManager->CalculateCPChoice( upload.choices ) + // psserver->charCreationManager->CalculateCPLife(upload.lifeEvents ); for ( size_t ci = 0; ci < upload.choices.Length(); ci++ ) { psCharCreationManager::CreationChoice* choice = psserver->charCreationManager->FindChoice( upload.choices[ci] ); if ( choice ) { csString script (choice->eventScript.GetData() ); csString name( psserver->charCreationManager->FindChoice( upload.choices[ci] )->name.GetData() ); Debug3( LOG_NEWCHAR, me->clientnum,"Choice: %s Creation Script: %s", name.GetData(), script.GetData() ); if ( choice->choiceArea == FATHER_JOB || choice->choiceArea == MOTHER_JOB ) { ProgressionEvent* event = psserver->GetProgressionManager()->FindEvent( script.GetData() ); if ( event ) { int modifier = (choice->choiceArea == FATHER_JOB) ? upload.fatherMod : upload.motherMod; if ( modifier > 3 || modifier < 1 ) { modifier = 1; } event->SetValue("ParentStatus", (double)modifier ); } } psserver->GetProgressionManager()->ProcessEvent( script, actor, actor ); } else { Debug2( LOG_NEWCHAR, me->clientnum,"Character Choice %d not found\n", upload.choices[ci] ); } } for ( size_t li = 0; li < upload.lifeEvents.Length(); li++ ) { csString script(psserver->charCreationManager->FindLifeEvent( upload.lifeEvents[li] )->eventScript.GetData() ); Debug2( LOG_NEWCHAR, me->clientnum,"LifeEvent Script: %s", script.GetData() ); psserver->GetProgressionManager()->ProcessEvent( script, actor, actor ); } } if ( !upload.verify ) { if ( reservedName == NAME_RESERVED_FOR_YOU ) { AssignScript( chardata ); } // This function recalculates the Max HP, Mana and Stamina of the new character chardata->RecalculateStats(); // Make sure the new player have HP, Mana and Samina that was calculated chardata->SetHitPoints(chardata->GetHitPointsMax()); chardata->SetMana(chardata->GetManaMax()); chardata->SetStamina(chardata->GetStaminaMax(true),true); chardata->SetStamina(chardata->GetStaminaMax(false),false); psServer::CharacterLoader.SaveCharacterData( chardata, actor ); Debug1(LOG_NEWCHAR,me->clientnum,"Player Creation Complete"); // Remove cached objects to make sure that the client gets a fresh character // list from the database if it logs out and in within 2 minutes. iCachedObject *obj = CacheManager::GetSingleton().RemoveFromCache(CacheManager::GetSingleton().MakeCacheName("list",client->GetAccountID())); if (obj) { obj->ProcessCacheTimeout(); obj->DeleteSelf(); } obj = CacheManager::GetSingleton().RemoveFromCache(CacheManager::GetSingleton().MakeCacheName("auth",client->GetAccountID())); if (obj) { obj->ProcessCacheTimeout(); obj->DeleteSelf(); } // Here everything is ok client->SetPlayerID(chardata->GetCharacterID()); client->SetName(playerName); psCharApprovedMessage app(me->clientnum); if (app.valid) psserver->GetEventManager()->SendMessage(app.msg); else Bug2("Could not create valid psCharApprovedMessage for client %u.\n",me->clientnum); } else { psCharVerificationMesg mesg( me->clientnum ); int z; for ( z = 0; z < PSITEMSTATS_STAT_COUNT; z++ ) { int value = chardata->GetAttributes()->GetStat( (PSITEMSTATS_STAT)z); if ( value > 0 ) { mesg.AddStat( value, CacheManager::GetSingleton().Attribute2String((PSITEMSTATS_STAT)z)); } } for ( z = 0; z < PSSKILL_COUNT; z++ ) { int rank = chardata->GetSkills()->GetSkillRank( (PSSKILL)z, false ); psSkillInfo* info = CacheManager::GetSingleton().GetSkillByID(z); csString name("Not found"); if ( info ) name.Replace( info->name ); if ( rank > 0 ) { mesg.AddSkill(rank, name); } } mesg.Construct(); mesg.SendMessage(); } delete actor; if (!upload.verify) { // Remove char data from the cache iCachedObject *obj = CacheManager::GetSingleton().RemoveFromCache(CacheManager::GetSingleton().MakeCacheName("char", chardata->GetCharacterID())); if (obj) { obj->ProcessCacheTimeout(); obj->DeleteSelf(); } } } bool psServerCharManager::FilterName(const char* name) { if (name == NULL) return false; size_t len = strlen(name); if ( (strspn(name,"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") != len) ) return false; if (((int)strspn(((const char*)name)+1, (const char*)"abcdefghijklmnopqrstuvwxyz") != len - 1)) return false; return true; } void psServerCharManager::HandleCharInfo( MsgEntry* me, Client *client ) { psCharInfoRequestMessage irmsg(me); psCharInfoRequestMessage::type command = psCharInfoRequestMessage::SKILL_REQUEST_ALL; if ( irmsg.command == command ) { psCharacter *chardata=client->GetCharacterData(); if (chardata==NULL) return; int i; csString outData(""); csString tempBuffer; for (i=0;iname); tempBuffer.Format( "", escpxml.GetData(), chardata->GetSkills()->GetSkillRank((PSSKILL)i)); outData.Append(tempBuffer); } } outData.Append(""); psCharInfoRequestMessage msg(me->clientnum, command, outData); if (msg.valid) psserver->GetEventManager()->SendMessage(msg.msg); else { Bug2("Could not create valid psCharInfoRequestMessage for client %u.\n",me->clientnum); } } } void psServerCharManager::BeginTrading(Client * client, gemObject * target, const csString & type) { psCharacter * merchant = NULL; int clientnum = client->GetClientNum(); psCharacter* character = client->GetCharacterData(); // Make sure that we are not busy with something else if (client->GetActor()->GetMode() != PSCHARACTER_MODE_PEACE) { psserver->SendSystemError(client->GetClientNum(), "You cannot trade because you are already busy."); return; } merchant = target->GetCharacterData(); if(!merchant) { psserver->SendSystemInfo(client->GetClientNum(), "Merchant not found."); return; } if (client->GetActor()->RangeTo(target) > RANGE_TO_SELECT) { psserver->SendSystemInfo(client->GetClientNum(), "You are not in range to trade with %s.",merchant->GetCharName()); return; } if (!target->IsAlive()) { psserver->SendSystemInfo(client->GetClientNum(), "Can't trade with a dead merchant."); return; } if (!merchant->IsMerchant()) { psserver->SendSystemInfo(client->GetClientNum(), "%s isn't a merchant.",merchant->GetCharName()); return; } psserver->SendSystemInfo(client->GetClientNum(), "You started trading with %s.",merchant->GetCharName()); if (type == "SELL") { csString commandData; commandData.Format("", merchant->GetCharacterID(),psGUIMerchantMessage::SELL); psGUIMerchantMessage msg1(clientnum,psGUIMerchantMessage::MERCHANT,commandData); msg1.SendMessage(); character->SetTradingStatus(psCharacter::SELLING,merchant); } else { csString commandData; commandData.Format("", merchant->GetCharacterID(),psGUIMerchantMessage::BUY); psGUIMerchantMessage msg1(clientnum,psGUIMerchantMessage::MERCHANT,commandData); psserver->GetEventManager()->SendMessage(msg1.msg); character->SetTradingStatus(psCharacter::BUYING,merchant); } // Build category list csString categoryList(""); csString buff; psMerchantInfo * merchantInfo = merchant->GetMerchantInfo(); for ( size_t z = 0; z < merchantInfo->categories.Length(); z++ ) { psItemCategory * category = merchantInfo->categories[z]; csString escpxml = EscpXML(category->name); buff.Format("",category->id, escpxml.GetData()); categoryList.Append(buff); } categoryList.Append(""); psGUIMerchantMessage msg2(clientnum,psGUIMerchantMessage::CATEGORIES,categoryList.GetData()); if (msg2.valid) psserver->GetEventManager()->SendMessage(msg2.msg); else { Bug2("Could not create valid psGUIMerchantMessage for client %u.\n",clientnum); } SendPlayerMoney(client); } void psServerCharManager::HandleMerchantMessage( MsgEntry* me, Client *client ) { psCharacter* character = client->GetCharacterData(); psGUIMerchantMessage msg(me); if (!msg.valid) { Debug2(LOG_NET,me->clientnum,"Received unparsable psGUIMerchantMessage from client %u.\n",me->clientnum); return; } // CPrintf(CON_DEBUG, "psServerCharManager::HandleMerchantMessage (%s, %d,%s)\n", // (const char*)client->GetName(),msg.command, (const char*)msg.commandData); switch (msg.command) { // This handles the initial request to buy or sell from a merchant. // A list of categories that this merchant handles is sent case psGUIMerchantMessage::REQUEST: { csRef exchangeNode = ParseString(msg.commandData, "R"); if (!exchangeNode) return; csRef attr = exchangeNode->GetAttribute("TARGET"); csString type = exchangeNode->GetAttributeValue("TYPE"); gemObject * target = NULL; if (attr) { csString targetName = attr->GetValue(); target = GEMSupervisor::GetSingleton().FindObject(targetName); if (!target) { psserver->SendSystemInfo(client->GetClientNum(), "Merchant '%s' not found.", targetName.GetData()); return; } } else { target = client->GetTargetObject(); if (!target) { psserver->SendSystemInfo(client->GetClientNum(), "You have no target selected."); return; } } BeginTrading(client, target, type); break; } // This handles case psGUIMerchantMessage::CATEGORY: { csRef merchantNode = ParseString (msg.commandData, "C"); if (!merchantNode) return; psCharacter * merchant; psMerchantInfo * merchantInfo; if (VerifyTrade(client, character,&merchant,&merchantInfo, "category","",merchantNode->GetAttributeValueAsInt("ID"))) { csString category = merchantNode->GetAttributeValue("CATEGORY"); psItemCategory * itemCategory = merchantInfo->FindCategory(category); if (!itemCategory) { CPrintf(CON_DEBUG, "Player %s fails to get items in category %s. Unkown category!\n", character->GetCharName(), (const char*)category); return; } if (!merchant->GetActor()->IsAlive()) { psserver->SendSystemInfo(client->GetClientNum(), "You can't trade with a dead merchant."); return; } // Send item list for given category if (character->GetTradingStatus() == psCharacter::BUYING) { SendMerchantItems( client, merchant, itemCategory ); } else { SendPlayerItems( client, itemCategory ); } } break; } case psGUIMerchantMessage::BUY: { csRef merchantNode = ParseString(msg.commandData, "T"); if (!merchantNode) return; csString itemName = merchantNode->GetAttributeValue("ITEM"); int count = merchantNode->GetAttributeValueAsInt("COUNT"); int merchantID = merchantNode->GetAttributeValueAsInt("ID"); uint32 itemID = (uint32)merchantNode->GetAttributeValueAsInt("ITEM_ID"); psCharacter * merchant; psMerchantInfo * merchantInfo; if (VerifyTrade(client, character,&merchant,&merchantInfo, "buy",itemName,merchantID)) { psItem * item = merchant->Inventory().FindInBulk(itemID); if (!item || count > item->GetStackCount()) { psserver->SendSystemError(client->GetClientNum(), "Merchant does not have %i %s.", count, itemName.GetData()); return; } if (!merchant->GetActor()->IsAlive()) { psserver->SendSystemError(client->GetClientNum(),"That merchant is dead"); return; } psMoney price = item->GetPrice(); psMoney money = character->Money(); if (price*count > money) { psserver->SendSystemError(client->GetClientNum(),"You need more money"); return; } int canFit = (int)character->Inventory().CanFit(item); // count that actually fit into buyer's inventory if (count > canFit) { count = canFit; // Notify the buyer that their inventory is full. (will purchase what fits) psserver->SendSystemError(client->GetClientNum(),"Your inventory is full"); if (count <= 0) return; } psItem * newitem = item->Copy(count); if (newitem == NULL) { Error2("Error: failed to create item %s.", itemName.GetData()); psserver->SendSystemError(client->GetClientNum(), "Error: failed to create item %s.", itemName.GetData()); return; } newitem->SetCreator(ITEM_CLONE); // If we managed to buy some items, we pay some money if ( character->Inventory().PutInBulk(newitem, false, false) == 0) { psMoney cost; cost = (price * count).Normalized(); character->SetMoney(money - cost); psserver->SendSystemOK( client->GetClientNum(), "You bought %d %s for %s a total of %d Trias.", count, itemName.GetData(), cost.ToUserString().GetDataSafe(),cost.GetTotal()); psBuyEvent evt( character->GetCharacterID(), merchant->GetCharacterID(), item->GetName(), count, (int)item->GetCurrentStats()->GetQuality(), cost.GetTotal() ); evt.FireEvent(); newitem->Save(); } else { // No empty or stackable slot in bulk or any container psserver->SendSystemError(client->GetClientNum(),"You're carrying too many items"); CacheManager::GetSingleton().RemoveInstance(newitem); return; } csString buf; buf.Format("%s, %s, %s, \"%s\", %d, %d", client->GetName(), merchant->name.GetData(), "Buy", itemName.GetData(), count, (price * count).GetTotal()); psserver->GetLogCSV()->Write(CSV_EXCHANGES, buf); // Update client views SendPlayerMoney( client ); SendMerchantItems( client, merchant, item->GetCategory() ); // Update all client views UpdateItemViews( client->GetClientNum() ); } break; } case psGUIMerchantMessage::SELL: { csRef merchantNode = ParseString(msg.commandData, "T"); if (!merchantNode) return; csString itemName = merchantNode->GetAttributeValue("ITEM"); int count = merchantNode->GetAttributeValueAsInt("COUNT"); int merchantID = merchantNode->GetAttributeValueAsInt("ID"); psCharacter * merchant; psMerchantInfo * merchantInfo; if (VerifyTrade(client, character,&merchant,&merchantInfo, "sell",itemName,merchantID)) { uint32 itemID =(uint32) merchantNode->GetAttributeValueAsInt("ITEM_ID"); psItem * item = character->Inventory().FindInBulk(itemID); if (!item) return; if (!merchant->GetActor()->IsAlive()) { psserver->SendSystemError(client->GetClientNum(), "You can't trade with a dead merchant."); return; } psMoney price = item->GetSellPrice(); psMoney money = character->Money(); count = MIN(count, item->GetStackCount()); item = character->Inventory().RemoveBulk(character->Inventory().FindItemInTopLevelBulk(item), count); if (item == NULL) { Error3("RemoveItemInBulk failed while selling to merchant %s %i", item->GetName(), count); return; } psMoney cost; cost = (price * count).Normalized(); character->SetMoney(money + cost); psserver->SendSystemOK( client->GetClientNum(), "You sold %d %s for %s a total of %d Trias.", count, itemName.GetData(), cost.ToUserString().GetDataSafe(),cost.GetTotal()); // Record psSellEvent evt( character->GetCharacterID(), merchant->GetCharacterID(), item->GetName(), count, (int)item->GetCurrentStats()->GetQuality(), cost.GetTotal() ); evt.FireEvent(); ServerStatus::sold_items += count; ServerStatus::sold_value += (price * count).GetTotal(); csString buf; buf.Format("%s, %s, %s, \"%s\", %d, %d", client->GetName(), merchant->name.GetData(), "Sell", itemName.GetData(), count, (price * count).GetTotal()); psserver->GetLogCSV()->Write(CSV_EXCHANGES, buf); // Update client views SendPlayerMoney( client ); SendPlayerItems( client, item->GetCategory() ); // items are not currently given to merchant, they are just destroyed CacheManager::GetSingleton().RemoveInstance(item); // Update all client views UpdateItemViews( client->GetClientNum() ); } break; } case psGUIMerchantMessage::VIEW: { csRef merchantNode = ParseString(msg.commandData, "V"); if (!merchantNode) return; csString itemName = merchantNode->GetAttributeValue("ITEM"); int merchantID = merchantNode->GetAttributeValueAsInt("ID"); uint32 itemID = (uint32)merchantNode->GetAttributeValueAsInt("ITEM_ID"); int tradeCommand = merchantNode->GetAttributeValueAsInt("TRADE_CMD"); psCharacter * merchant; psMerchantInfo * merchantInfo; if (VerifyTrade(client, character,&merchant,&merchantInfo, "view",itemName,merchantID)) { if (!merchant->GetActor()->IsAlive()) { psserver->SendSystemInfo(client->GetClientNum(), "You can't trade with a dead merchant."); return; } psItem * item; if (tradeCommand == psGUIMerchantMessage::SELL) item = character->Inventory().FindInBulk(itemID); else item = merchant->Inventory().FindInBulk(itemID); if (!item) { CPrintf(CON_DEBUG, "Player %s failed to view item %s. No item!\n", client->GetName(), (const char*)itemName); return; } // check for container items if (item->GetIsContainer()) { SendItemContents(client, item); } else { SendItemDescription(client, item); } } break; } case psGUIMerchantMessage::CANCEL: { character->SetTradingStatus(psCharacter::NOT_TRADING,0); /* psGUIMerchantMessage newmsg(me->clientnum,psGUIMerchantMessage::CANCEL,""); if (newmsg.valid) { psserver->GetEventManager()->SendMessage(newmsg.msg); } else { Bug2("Could not create valid psGUIMerchantMessage for client %u.\n",me->clientnum); }*/ break; } } } bool psServerCharManager::VerifyTrade( Client * client, psCharacter * character, psCharacter ** merchant, psMerchantInfo ** info, const char * trade,const char * itemName, unsigned int merchantID) { *merchant = character->GetMerchant(); if (!*merchant) { CPrintf(CON_DEBUG, "Player %s failed to %s item %s. No merchant!\n", trade, character->GetCharName(), itemName); return false; } *info = (*merchant)->GetMerchantInfo(); if (!*info) { CPrintf(CON_DEBUG, "Player %s failed to %s item %s. No merchant info!\n", trade, character->GetCharName(), itemName); return false; } // Check if player is trading with this merchant. if (character->GetTradingStatus() == psCharacter::NOT_TRADING) { CPrintf(CON_DEBUG, "Player %s failed to %s item %s. No trading status!\n", trade, character->GetCharName(), itemName); return false; } // Check if this is correct merchant if (merchantID != (*merchant)->GetCharacterID()) { CPrintf(CON_DEBUG, "Player %s failed to %s item %s. Different merchant!\n", trade, character->GetCharName(), itemName); return false; } // Check range if (character->GetActor()->RangeTo((*merchant)->GetActor()) > RANGE_TO_SELECT) { psserver->SendSystemInfo(client->GetClientNum(),"Merchant is out of range."); CPrintf(CON_DEBUG, "Player %s failed to %s item %s. Out of range!\n", trade, character->GetCharName(), itemName); return false; } return true; } void psServerCharManager::SendOutPlaySoundMessage( int clientnum, const char* itemsound, const char* action ) { if (clientnum == 0 || itemsound == NULL || action == NULL) return; csString sound = itemsound; if (sound == "item.nosound") return; sound += "."; sound += action; Debug3(LOG_SOUND,clientnum,"Sending sound %s to client %d", sound.GetData(), clientnum); MsgEntry* msg = new MsgEntry( sound.Length() + 1 ); msg->SetType(MSGTYPE_PLAYSOUND); msg->clientnum = clientnum; msg->Add(sound); psserver->GetEventManager()->SendMessage(msg); // TODO: Sounds should really be multicasted, so others can hear them // psserver->GetEventManager()->Multicast(msg, fromClient->GetActor()->GetMulticastClients(), 0, range ); } void psServerCharManager::SendOutEquipmentMessages( unsigned int client, int slotName, psItem* item, int equiped ) { SendOutEquipmentMessages( clients->Find(client), slotName, item, equiped ); } void psServerCharManager::SendOutEquipmentMessages( Client* fromClient, int slotName, psItem* item, int equiped ) { if (fromClient == NULL || item == NULL) return; SendOutEquipmentMessages( fromClient->GetActor(), slotName, item, equiped ); } void psServerCharManager::SendOutEquipmentMessages( gemActor* actor, int slotName, psItem* item, int equiped ) { PS_ID eid = actor->GetEntity()->GetID(); csString mesh = item->GetMeshName(); csString part = item->GetPartName(); csString texture = item->GetTextureName(); csString partMesh = item->GetPartMeshName(); /* Weild: mesh in a slot (no part or texture) * Wear: mesh for standalone; texture on a part when worn * * We'll send the info the client needs, and it figures the rest out. */ if (part.Length() && texture.Length()) mesh.Clear(); psEquipmentMessage msg( 0, eid, equiped, slotName, mesh, part, texture, partMesh ); CS_ASSERT( msg.valid ); psserver->GetEventManager()->Multicast( msg.msg, actor->GetMulticastClients(), 0, // Multicast to all without exception PROX_LIST_ANY_RANGE ); } void psServerCharManager::SendPlayerMoney( Client *client ) { csString buff; if (client->GetCharacterData()==NULL) return; psMoney money=client->GetCharacterData()->Money(); csString money_str = money.ToString(); buff.Format("",money_str.GetData()); psGUIMerchantMessage msg(client->GetClientNum(), psGUIMerchantMessage::MONEY,buff); psserver->GetEventManager()->SendMessage(msg.msg); } bool psServerCharManager::SendItemDescription( Client *client, psItem *item) { if (item==NULL) return false; unsigned int stackCount = item->GetStackCount(); csString itemInfo, weight, itemQuality, itemCategory; psItemStats* itemStats = item->GetCurrentStats(); if(item->GetIsLocked()) { itemInfo = "This item is locked\n\n"; } itemCategory.Format( "Category: %s", itemStats->GetCategory()->name.GetData() ); weight.Format("\nWeight: %.2f", item->GetSumWeight() ); itemQuality.Format("\nQuality: %.0f/%.0f", item->GetItemQuality(),item->GetMaxItemQuality() ); itemInfo += itemCategory+weight+itemQuality; // Item was crafted if ( item->GetCrafterID() != 0 ) { // Crafter and guild names csString crafterInfo; psCharacter* charData = psServer::CharacterLoader.QuickLoadCharacterData( item->GetCrafterID(), true ); if ( charData ) { crafterInfo.Format( "\n\nCrafter: %s", charData->GetCharFullName()); itemInfo += crafterInfo; } // Item was crafted by a guild member if ( item->GetGuildID() != 0 ) { csString guildInfo; psGuildInfo* guild = CacheManager::GetSingleton().FindGuild( item->GetGuildID() ); if ( guild ) { guildInfo.Format( "\nGuild: %s", guild->GetName().GetData()); itemInfo += guildInfo; } } } // Item is a weapon if ( item->GetCategory()->id == 1 ) { csString speed, damage; // Weapon Speed speed.Format( "\n\nSpeed: %.2f", itemStats->Weapon().Latency() ); // Weapon Damage Type damage = "\n\nDamage:"; float dmgSlash, dmgBlunt, dmgPierce; dmgSlash = item->GetDamage(PSITEMSTATS_DAMAGETYPE_SLASH); dmgBlunt = item->GetDamage(PSITEMSTATS_DAMAGETYPE_BLUNT); dmgPierce = item->GetDamage(PSITEMSTATS_DAMAGETYPE_PIERCE); // Only worth printing if their value is not zero if ( dmgSlash ) damage += csString().Format( "\n Slash: %.2f", dmgSlash ); if ( dmgBlunt ) damage += csString().Format( "\n Blunt: %.2f", dmgBlunt ); if ( dmgPierce ) damage += csString().Format( "\n Pierce: %.2f", dmgPierce ); itemInfo+= speed + damage; } itemInfo += "\n\nDescription: "; itemInfo += item->GetDescription(); psViewItemDescription outgoing( client->GetClientNum(), item->GetName(), itemInfo.GetData(), item->GetImageName(), stackCount ); if ( outgoing.valid ) psserver->GetEventManager()->SendMessage(outgoing.msg); else { Bug2("Could not create valid psViewItemDescription for client %u.\n",client->GetClientNum()); return false; } return true; } bool psServerCharManager::SendItemContents( Client *client, psItem *item, int parentContainer, int containerID) { if (item==NULL) return false; if (item->GetIsLocked()) return SendItemDescription(client,item); csString name( item->GetName() ); csString icon( item->GetImageName() ); csString desc( item->GetDescription() ); desc.AppendFmt("\n\nWeight: %.2f\nCapacity: %u/%u", item->GetSumWeight(), item->GetContainedSize(), item->GetContainerMaxSize() ); psViewItemDescription outgoing( client->GetClientNum(), name, desc, icon, 0, IS_CONTAINER, parentContainer ); if (item->GetGemObject() != NULL ) outgoing.containerID = item->GetGemObject()->GetEntity()->GetID(); else outgoing.containerID = containerID; FillContainerMsg( item, client, outgoing ); outgoing.ConstructMsg(); psserver->GetEventManager()->SendMessage(outgoing.msg); return true; } bool psServerCharManager::SendActionContents( Client *client, psActionLocation *action, csRef topNode, psItem *item, int parentContainer, int containerID) { if ( action == NULL ) return false; if ( item == NULL ) return false; //if(item->GetIsLocked()) // return SendItemDescription(client,item); csString name( action->name ); csString desc( item->GetDescription() ); csString icon( item->GetImageName() ); csRef description = topNode->GetNode( "Description" ); if ( description ) { desc = description->GetContentsValue(); } bool isContainer = item->GetIsContainer(); psViewItemDescription outgoing( client->GetClientNum(), name, desc, icon, 0, isContainer, parentContainer ); /* REMOVED: was probably there to avoid a crash, remove after some testing. if (action->GetGemObject() != NULL ) outgoing.containerID = action->GetGemObject()->GetEntity()->GetID(); else outgoing.containerID = containerID; */ outgoing.containerID = item->GetGemObject()->GetEntity()->GetID(); if ( isContainer ) { FillContainerMsg( item, client, outgoing ); outgoing.ConstructMsg(); } outgoing.SendMessage(); return true; } void psServerCharManager::FillContainerMsg( psItem* item, Client* client, psViewItemDescription& outgoing ) { psContainerIterator* it = new psContainerIterator(item); while (it->HasNext()) { psItem* child = it->Next(); // No owner... Add item visible to everybody. if ( child->GetOwningCharacterID() == 0 ) { outgoing.AddContents( child->GetName(), child->GetImageName(), child->GetLocInParent(), child->GetStackCount()); } // The owner of the item is the player. else if ( child->GetOwningCharacterID() == client->GetCharacterData()->characterid ) { outgoing.AddContents( child->GetName(), child->GetImageName(), child->GetLocInParent(), child->GetStackCount()); } // otherwise it's somebody else's stuff else { const char *icon = "/this/art/gui/slottaken_icon.png" ; outgoing.AddContents( child->GetName(), icon, child->GetLocInParent(), -1); } } delete it; } bool psServerCharManager::SendBookText( Client *client, psItem *item) { if (item==NULL) return false; csString name( item->GetName() ); csString desc( item->GetDescription() ); if(item->GetIsLocked()) { desc = "This item is locked\n"; } //determine whether to display the 'write' button //and send the appropriate information if so uint32 slotID = item->GetLocInParent(); uint32 containerID = item->GetParentID(); uint32 parentContainerID = 0; bool shouldWrite = false; psReadBookTextMessage outgoing( client->GetClientNum(), name, desc, shouldWrite, slotID, containerID, parentContainerID ); if (outgoing.valid) { psserver->GetEventManager()->SendMessage(outgoing.msg); } else { Bug2("Could not create valid psReadBookText for client %u.\n",client->GetClientNum()); return false; } return true; } bool psServerCharManager::SendMerchantItems( Client *client, psCharacter* merchant, psItemCategory* category) { csArray items = merchant->Inventory().GetItemsInCategory(category); // Build item list csString buff(""); csString item; for ( size_t z = 0; z < items.Length(); z++ ) { csString escpxml_name = EscpXML(items[z]->GetName()); csString escpxml_imagename = EscpXML(items[z]->GetImageName()); item.Format("", items[z]->GetUID(), escpxml_name.GetData(), escpxml_imagename.GetData(), items[z]->GetPrice().GetTotal(), items[z]->GetStackCount()); buff.Append(item); } buff.Append(""); psGUIMerchantMessage msg4(client->GetClientNum(),psGUIMerchantMessage::ITEMS,buff.GetData()); if (msg4.valid) psserver->GetEventManager()->SendMessage(msg4.msg); else { Bug2("Could not create valid psGUIMerchantMessage for client %u.\n",client->GetClientNum()); } return true; } bool psServerCharManager::SendPlayerItems( Client *client, psItemCategory* category) { csArray items = client->GetCharacterData()->Inventory().GetItemsInCategory(category); // Build item list csString buff(""); csString item; csString itemID; for ( size_t z = 0; z < items.Length(); z++ ) { itemID.Format("%u",items[z]->GetUID()); csString purified; if (items[z]->GetPurifyStatus() == 2) purified = "yes"; else purified = "no"; csString escpxml_name = EscpXML(items[z]->GetName()); csString escpxml_imagename = EscpXML(items[z]->GetImageName()); item.Format("", itemID.GetDataSafe(), escpxml_name.GetData(), escpxml_imagename.GetData(), items[z]->GetSellPrice().GetTotal(), items[z]->GetStackCount(), purified.GetData()); buff.Append(item); } buff.Append(""); psGUIMerchantMessage msg4(client->GetClientNum(),psGUIMerchantMessage::ITEMS,buff.GetData()); if (msg4.valid) psserver->GetEventManager()->SendMessage(msg4.msg); else { Bug2("Could not create valid psGUIMerchantMessage for client %u.\n",client->GetClientNum()); } return true; } bool psServerCharManager::VerifyGoal(Client* client, psCharacter* character, psItem* goal) { // glyph items can't be goals if (goal->GetCurrentStats()->GetIsGlyph()) return false; return true; } bool psServerCharManager::IsUnique( const char* name ) { csString escape; db->Escape( escape, name ); // Check to see if name already exists in character database. csString query; query.Format( "Select id from characters where name='%s'", escape.GetData() ); Result result (db->Select( query ) ); if ( result.IsValid() && result.Count() >= 1 ) return false; else return true; } bool psServerCharManager::IsLastNameUnique( const char* lastname ) { if (lastname && strlen(lastname)) { // Check to see if name already exists in character database. csString query; csString escape; db->Escape(escape, lastname); query.Format( "Select id from characters where lastname='%s'", escape.GetData() ); Result result (db->Select( query ) ); if ( result.IsValid() && result.Count() >= 1 ) return false; else return true; } else return true; // blank last name is now allowed. } int psServerCharManager::IsReserved( const char* name, int acctID ) { // Check to see if this name is reserved. Does this check by comparing // the email address of the account with that stored in the migration table. csString query; csString escape; db->Escape( escape, name ); query.Format( "SELECT m.email FROM migration m WHERE m.username='%s'", escape.GetData() ); Result result (db->Select( query ) ); if ( result.IsValid() && result.Count() == 1 ) { csString savedEmail( result[0][0] ); query.Format( "SELECT username FROM accounts WHERE id=%d\n", acctID ); Result result2(db->Select( query ) ); if ( result2.IsValid() && result2.Count() == 1 ) { csString email( result2[0][0] ); if ( savedEmail.CompareNoCase(email) ) return NAME_RESERVED_FOR_YOU; else return NAME_RESERVED; } } return NAME_AVAILABLE; } void psServerCharManager::AssignScript( psCharacter* character ) { csString query; csString escape; db->Escape( escape, character->GetCharName() ); query.Format( "SELECT script FROM migration where username='%s' AND done !='Y'", escape.GetData()); Result result (db->Select( query ) ); if ( result.IsValid() && result.Count() == 1 ) { csString script(result[0][0]); character->AppendProgressionScript( script ); printf("Doing it: %s\n",character->progressionScript.GetData()); } query.Format("UPDATE migration SET done='Y' WHERE username='%s'",escape.GetData()); db->Command(query); } //------------------------------------------------------------------------------ SlotManager::~SlotManager() { psserver->GetEventManager()->Unsubscribe( this, MSGTYPE_SLOT_MOVEMENT ); } bool SlotManager::Initialize() { psserver->GetEventManager()->Subscribe( this, MSGTYPE_SLOT_MOVEMENT, REQUIRE_READY_CLIENT ); return true; } void SlotManager::HandleMessage( MsgEntry* me, Client *fromClient ) { // This is the only type of message we should handle if ( me->GetType() != MSGTYPE_SLOT_MOVEMENT ) { return; } psSlotMovementMsg mesg( me ); // If stacks are less than 1 then we should not do anything server wise. // Infact we should probably log as some client is doing something wrong. if ( mesg.stackCount < 1 ) { Error2("Got a slot movement message with stack count 0 from client %s.",fromClient->GetName()); return; } if ( !fromClient->GetCharacterData() ) { Error2("Could not find Character Data for client: %s", fromClient->GetName() ); return; } psTransaction* tRemove = StartTransaction( fromClient, mesg ); // Did we actually find an item to move? if ( tRemove == NULL ) { Error2("Could not find item in specified slot for client: %s", fromClient->GetName() ); return; } if ( !fromClient->IsAlive() ) { psserver->SendSystemError(fromClient->GetClientNum(),"Cannot do that when you are dead!"); return; } transItem *tItem = dynamic_cast (tRemove); if (tItem && tItem->GetItem() && tItem->GetItem()->IsInUse() ) { psserver->SendSystemError(me->clientnum, "You cannot do anything with that item while you are using it."); return; } else if (mesg.toParentContainer != CONTAINER_INVENTORY_BULK && mesg.toContainer == CONTAINER_EXCHANGE_OFFERING) { PlaceItemInExchange(fromClient, mesg, tRemove); } else { PlaceHoldingItem(fromClient, mesg, tRemove); } // Actually do the move and check at the same time tRemove->Execute(); psserver->GetCharManager()->SendInventory(me->clientnum); } psItem* SlotManager::FindItem( Client* client, int containerID, int slotID, int parentID ) { // Container inside something if ( parentID != 0 ) { psItem* container = FindItem(client,parentID,containerID); if ( container ) return container->GetItemInSlot(slotID); else Error3("Could not find container %d inside parent %d", containerID, parentID); } switch ( containerID ) { case CONTAINER_INVENTORY_BULK: { return client->GetCharacterData()->Inventory().GetBulkItem( slotID ); } case CONTAINER_INVENTORY_EQUIPMENT: { return client->GetCharacterData()->Inventory().GetEquipmentItem( slotID ); } case CONTAINER_EXCHANGE_OFFERING: { Exchange* exchange = psserver->GetExchangeManager()->GetExchange( client->ExchangeID() ); if (!exchange) return NULL; // Container is different from each perspective if (exchange->GetStarterClient() == client) return exchange->GetStarterOffer(slotID); else return exchange->GetTargetOffer(slotID); } case CONTAINER_EXCHANGE_RECEIVING: { Exchange* exchange = psserver->GetExchangeManager()->GetExchange( client->ExchangeID() ); if (!exchange) return NULL; // Container is different from each perspective if (exchange->GetStarterClient() == client) return exchange->GetTargetOffer(slotID); else return exchange->GetStarterOffer(slotID); } default: // Find container in world { gemObject* object = GEMSupervisor::GetSingleton().FindObject( containerID ); if ( object ) return object->GetItem(); else { Error2("No Object %d found", containerID ); return NULL; } } } } psItem* SlotManager::FindContainer( Client* client, int containerID, int parentContainerID ) { // Is this container in another container? if ( parentContainerID != 0 ) { psItem* container = FindItem(client,parentContainerID,containerID); if (container) return container; else Error3("Could not find container %d inside parent %d", containerID, parentContainerID); } // Find container in world gemObject* object = GEMSupervisor::GetSingleton().FindObject( containerID ); psItem* container = NULL; if ( !object ) { Error2("No Object %d found", containerID ); return NULL; } else { container = object->GetItem(); if ( !container ) { psActionLocation *action = psserver->GetActionManager()->FindAction( containerID ); if ( !action ) { Error2( "No item/action : %d", containerID ); return NULL; } else { if ( action->response.StartsWith( "", false ) ) { // load response into XML doc csRef doc = ParseString( action->response ); if(!doc) { Error1("Parse error in action response"); return NULL; } csRef root = doc->GetRoot(); if(!root) { Error1("No XML root in action response"); return NULL; } csRef topNode = root->GetNode( "Examine" ); if(!topNode) { Error1("No tag in action response"); return NULL; } csRef containerNode, descriptionNode; containerNode = topNode->GetNode( "Container" ); if ( containerNode ) { //item = slotManager->FindItem( client, containerNode->GetAttributeValueAsInt( "ID" ), 0, 0 ); // if it's an 'Item' // Get psItem and check uid // set item = currentGem; // Get all entities (gemObjects) csHash& gems = GEMSupervisor::GetSingleton().GetAllGEMS(); csHash::GlobalIterator i(gems.GetIterator()); gemObject* obj; int id = containerNode->GetAttributeValueAsInt( "ID" ); // Iterate through each one while ( i.HasNext() ) { obj = i.Next(); if (obj) { gemItem *gItem = dynamic_cast(obj); if (gItem) { psItem *pItem = gItem->GetItem(); if ( pItem->GetUID() == id ) { container = pItem; break; } } } } if ( !container ) { Error1("Container PS item could not be found"); return NULL; } else { return container; } } } } } else { if ( !container->GetIsContainer() ) { Error1("This thing is not a container"); return NULL; } else { return container; } } } return NULL; } psTransaction* SlotManager::StartTransaction( Client* fromClient, psSlotMovementMsg& mesg ) { psTransaction* transac = NULL; psCharacter* charData = fromClient->GetCharacterData(); /* // Check to see if this is from a container in the inventory. if ( mesg.fromParentContainer != 0 ) { holdingContainer = FindContainer( fromClient, mesg.fromContainer, mesg.fromParentContainer ); if ( holdingContainer ) holdingItem = holdingContainer->RemoveItemsInContainer( mesg.fromSlot, mesg.stackCount ); else Error3("Could not find container %d inside parent %d", mesg.fromContainer, mesg.fromParentContainer); } // The item came from elsewhere so figure out where. else { */ switch ( mesg.fromContainer ) { case CONTAINER_INVENTORY_MONEY: { if ( charData->Money().Get(mesg.fromSlot) - mesg.stackCount < 0 ) { Error2("Client %s is trying to cheat by grabbing too much money", charData->GetCharName() ); break; } psMoney money; money.Adjust(mesg.fromSlot, mesg.stackCount); transac = new transMoney(money, charData, false); break; } case CONTAINER_INVENTORY_BULK: { transac = new transItem(CONTAINER_INVENTORY_BULK, mesg.fromSlot, mesg.stackCount, charData); break; } case CONTAINER_INVENTORY_EQUIPMENT: { transac = new transItem(CONTAINER_INVENTORY_EQUIPMENT, mesg.fromSlot, 1, charData); break; } case CONTAINER_EXCHANGE_OFFERING: { Exchange* exchange = psserver->exchangemanager->GetExchange( fromClient->ExchangeID() ); if ( !exchange ) { break; } transac = new transItem(CONTAINER_EXCHANGE_OFFERING, mesg.fromSlot, mesg.stackCount, charData); break; } case CONTAINER_OFFERING_MONEY: { Exchange* exchange = psserver->exchangemanager->GetExchange( fromClient->ExchangeID() ); if ( !exchange ) break; int n = exchange->GetMoneyCount(fromClient, mesg.fromSlot); if (n < mesg.stackCount) { Error2("Client %s is trying to cheat by grabbing too much money from exchange", charData->GetCharName() ); break; } psMoney money; money.Adjust(mesg.fromSlot, mesg.stackCount); transac = new transMoney(money, charData, true); break; } default: { psItem *holdingContainer = FindContainer( fromClient, mesg.fromContainer, mesg.fromParentContainer ); if ( holdingContainer && holdingContainer->GetGemObject() ) // Container exists in world { // Check range ignoring Y co-ordinate if ( fromClient->GetActor()->RangeTo(holdingContainer->GetGemObject(), true) > RANGE_TO_USE ) { psserver->SendSystemInfo(fromClient->GetClientNum(),"You are not in range to remove from %s.",holdingContainer->GetName()); psserver->GetCharManager()->ViewItem(fromClient, mesg.fromContainer, mesg.fromSlot, mesg.fromParentContainer); break; } transac = new transItem((Slot_Containers) mesg.fromContainer, mesg.fromSlot, mesg.stackCount, charData, mesg.fromParentContainer); } else if (holdingContainer) { transac = new transItem((Slot_Containers) mesg.fromContainer, mesg.fromSlot, mesg.stackCount, charData, mesg.fromParentContainer); } break; } } // } return transac; } psItem* SlotManager::MakeMoneyItem( int slot, int stackCount ) { psItem* holdingItem = new psItem(); holdingItem->SetCreator(ITEM_MONEY); csString type("Tria"); switch( slot ) { case MONEY_TRIAS: type="Tria"; break; case MONEY_HEXAS: type="Hexa"; break; case MONEY_OCTAS: type="Octa"; break; case MONEY_CIRCLES: type="Circle"; break; default: Error2("Unknown fromSlot: %d", slot ); break; } psItemStats * stat = CacheManager::GetSingleton().GetBasicItemStatsByName(type); if ( !stat ) { Error2("Could not load basic item stat for %s", type.GetData() ); } else { holdingItem->SetBaseStats(stat); holdingItem->SetStackCount( stackCount ); } holdingItem->SetLoaded(); // Loaded and ready holdingItem->Save(); return holdingItem; } int SlotManager::PlaceItemInEquipment(psCharacter& character, int toSlot, psItem* item, bool test ) { Client* fromClient = character.GetActor()->GetClient(); int remainder = -1; csArray fitsIn; // -1 means add to any slot or consume if ( toSlot == -1 ) { if ( item->GetBaseStats()->GetIsConsumable() ) { remainder = Consume(item, character, test); if(remainder != -1) return remainder; } //If not consumable then get the list of equipment //slots this item can go in and iterate over them all //to find the first ( if any ) empty one. fitsIn = item->GetBaseStats()->GetSlots(); } else { // See if we're trying to drop an item into an equipped container if ( character.Inventory().PutInEquipped(toSlot, item, test) ) return 0; // Otherwise, just push in the slot that was picked. fitsIn.Push( toSlot ); } // Only ammo or equiped stackable can be stacked inside an inventory slot. if ( item->GetStackCount() > 1 && !(item->GetBaseStats()->GetFlags() & PSITEMSTATS_FLAG_IS_EQUIP_STACKABLE)) { psserver->SendSystemError(character.GetActor()->GetClientID(),"Cannot equip %d %s's in a slot!", item->GetStackCount(), item->GetName() ); return -1; } for ( size_t idx = 0; idx < fitsIn.Length(); idx++ ) { toSlot = fitsIn[idx]; psItem* toItem = character.Inventory().GetEquipmentItem( toSlot ); // If there is an item already here then this is not allowed for equipment. // The better thing would be to do a swap but that is more complex and // will be a future item. if ( !toItem ) { if ( item->FitsInSlots( CacheManager::GetSingleton().slotMap[toSlot]) ) { if ( character.Inventory().EquipIn(toSlot, item, test) ) { if(test) return 0; psserver->GetCharManager()->SendOutEquipmentMessages( fromClient, toSlot, item, psEquipmentMessage::EQUIP); psserver->GetCharManager()->SendOutPlaySoundMessage(fromClient->GetClientNum(), item->GetSound(), "equip" ); csString script = item->GetBaseStats()->GetProgressionEventEquip(); if ( script.Length() > 0 ) { gemActor *actor = character.GetActor(); psserver->GetProgressionManager()->ProcessEvent(script, actor ); } return 0; } else if(test) return -1; } } } return -1; } void SlotManager::PlaceHoldingItem( Client* fromClient, psSlotMovementMsg& mesg, psTransaction *tRemove ) { /* This code prevents the default condition of the below switch to be called for free standing containers. It needs to be fixed to support equipped containers. if ( mesg.toParentContainer != 0 ) { psItem* container = FindContainer( fromClient, mesg.toContainer, mesg.toParentContainer ); if ( container ) { PS_CONTAINER_ERROR result = container->PutItemsInContainer( mesg.toSlot, holdingItem, charData); if ( result == PS_CONT_OK ) { holdingItem = NULL; psserver->GetCharManager()->SendItemContents( fromClient, container, mesg.toParentContainer, mesg.toContainer ); } else { SendContainerError( result, fromClient ); } } else Error3("Could not find container %d inside parent %d", mesg.fromContainer, mesg.fromParentContainer); } else */ { switch ( mesg.toContainer ) { case CONTAINER_INVENTORY_MONEY: { if ( mesg.fromContainer == CONTAINER_INVENTORY_MONEY || mesg.fromContainer == CONTAINER_OFFERING_MONEY ) { transMoney *tMoney = dynamic_cast(tRemove); tMoney->To(CONTAINER_INVENTORY_MONEY); } break; } case CONTAINER_OFFERING_MONEY: { if ( mesg.fromContainer == CONTAINER_INVENTORY_MONEY || mesg.fromContainer == CONTAINER_OFFERING_MONEY ) { Exchange* exchange = psserver->exchangemanager->GetExchange( fromClient->ExchangeID() ); if ( !exchange ) { Error1( "Client tried to drop to non existant exchange" ); } else { transMoney *tMoney = dynamic_cast(tRemove); tMoney->To(CONTAINER_OFFERING_MONEY); } } break; } case CONTAINER_WORLD: { tRemove->To(CONTAINER_WORLD); break; } case CONTAINER_INVENTORY_BULK: { tRemove->To(CONTAINER_INVENTORY_BULK, mesg.toSlot); break; } case CONTAINER_INVENTORY_EQUIPMENT: { // Stick item into equipment. tRemove->To(CONTAINER_INVENTORY_EQUIPMENT, mesg.toSlot); break; } // We assume that the incomming ID is for a generic container and try to // put an item in that. default: { psItem* toContainer = FindContainer( fromClient, mesg.toContainer, mesg.toParentContainer ); if ( toContainer ) { // Check range ignoring Y co-ordinate if ( toContainer->GetGemObject() && fromClient->GetActor()->RangeTo(toContainer->GetGemObject(), true) > RANGE_TO_USE ) { psserver->SendSystemError(fromClient->GetClientNum(),"You are not in range to use %s.",toContainer->GetName()); break; } tRemove->To((Slot_Containers) mesg.toContainer, mesg.toSlot, mesg.toParentContainer); } break; } } } } void SlotManager::PlaceItemInExchange(Client* fromClient, psSlotMovementMsg & mesg, psTransaction * tRemove) { Exchange* exchange = psserver->exchangemanager->GetExchange( fromClient->ExchangeID() ); if ( !exchange ) { Error1( "Client tried to drop to non existing exchange" ); return; } if ( mesg.fromContainer != CONTAINER_INVENTORY_MONEY && mesg.fromContainer != CONTAINER_OFFERING_MONEY ) { tRemove->To(CONTAINER_EXCHANGE_OFFERING, mesg.toSlot); } else { /*It means we are trying to place money in the wrong place! We place money then in the right slots*/ transMoney *tMoney = dynamic_cast(tRemove); tRemove->To(CONTAINER_EXCHANGE_OFFERING); } } void SlotManager::UnequipHolding( int fromSlot, psItem* item, psCharacter* from) { Client *fromClient = from->GetActor()->GetClient(); psserver->GetCharManager()->SendOutPlaySoundMessage(fromClient->GetClientNum(), item->GetSound(), "unequip" ); psserver->GetCharManager()->SendOutEquipmentMessages(fromClient, fromSlot, item, psEquipmentMessage::DEEQUIP); csString script = item->GetBaseStats()->GetProgressionEventUnEquip(); csString equip_script = item->GetBaseStats()->GetProgressionEventEquip(); if (script.IsEmpty() && !equip_script.IsEmpty()) { Notify2(LOG_SCRIPT, "Item \"%s\" has no prg_evt_unequip script. Using \"undo_equip\"", item->GetName()); script = "undo_equip"; } gemActor *actor = from->GetActor(); if ( script == "undo_equip" ) { csString equip = item->GetBaseStats()->GetProgressionEventEquip(); if ( !equip.IsEmpty() ) { ProgressionEvent *ev = psserver->GetProgressionManager()->FindEvent(equip); psserver->GetProgressionManager()->ProcessEvent(ev, actor, NULL, true); } } else if (!script.IsEmpty()) { psserver->GetProgressionManager()->ProcessEvent(script, actor ); } } int SlotManager::Consume( psItem* item, psCharacter& charData, bool test ) { if ( item ) { csString script = item->GetBaseStats()->GetProgressionEventEquip(); if ( !script.IsEmpty() ) { gemActor *actor = charData.GetActor(); int count = item->GetStackCount(); if(!test) psserver->GetProgressionManager()->ProcessEvent(script, actor ); // Place the rest of the stack back to where it came from. if ( item->GetStackCount() > 1 ) { if(test) return count - 1; item->SetStackCount( count-1); // don't save this because it is not in any inventory slot } // Destroy the item object if the last of the stack was consumed else if ( count == 1 ) { if(test) return 0; // Make sure the item is no longer in the database. item->Delete(); delete item; return 0; } } } return -1; } void SlotManager::SendContainerError( int result, Client* fromClient, int containerID, int slotID, int parentContainerID ) { csString errorMessage = "No Error"; switch ( result ) { case PS_CONT_ERR_OUT_RANGE: { errorMessage = "Not a valid slot inside!"; break; } case PS_CONT_ERR_NO_ITEM: { errorMessage = "No item was given"; break; } case PS_CONT_ERR_SELF: { errorMessage = "Cannot place item inside itself!"; break; } case PS_CONT_ERR_TO_BIG: { errorMessage = "That item is too big to fit inside"; break; } case PS_CONT_ERR_NOT_OWNER: { errorMessage = "You don't own that!"; break; } case PS_CONT_ERR_NO_STACK: { errorMessage = "Cannot stack that item here!"; break; } default: { if(result > 0) errorMessage = "You will have items left over!"; break; } } psserver->GetCharManager()->ViewItem(fromClient, containerID, slotID, parentContainerID); psserver->SendSystemError(fromClient->GetClientNum(), errorMessage ); } void psTransaction::ToWorld(psItem *item) { Client *fromClient = from->GetActor()->GetClient(); int clientNum = fromClient->GetClientNum(); item->SetOwningCharacter(NULL); // Drop item in front of the character from->DropItem( item, item->IsTransient(), from->GetActor()->GetAngle() + PI /* + PI/4 * (mesg.toSlot ? -1 : 1) */ ); // Record psSellEvent evt( from->GetCharacterID(), 0, item->GetName(), item->GetStackCount(), (int)item->GetCurrentStats()->GetQuality(), 0 // 0 = Dropped, i.e "gave" it away ); evt.FireEvent(); csString buf; buf.Format("%s, %s, %s, \"%s\", %d, %d", fromClient->GetName(), "World", "Drop", item->GetName(), 0, 0); psserver->GetLogCSV()->Write(CSV_EXCHANGES, buf); // Update all client views psserver->GetCharManager()->UpdateItemViews(clientNum); } void psTransaction::ToBulk(psItem *item) { from->Inventory().PutInBulk(item, false, true, toSlot); } void psTransaction::ToEquipment(psItem *item) { SlotManager::PlaceItemInEquipment(*from, toSlot, item, false); } void psTransaction::ToContainer(psItem *item) { // Update the container and resend contents to player. Client *fromClient = from->GetActor()->GetClient(); psItem* container = SlotManager::FindContainer( fromClient, eToSlot, toContainer ); container->PutItemsInContainer( toSlot, item, from, false ); container->Save(true); //psserver->GetCharManager()->SendItemContents( fromClient, toContainer ); if (!container->GetGemObject()) { // Containers in character's inventory need different code to handle them psserver->GetCharManager()->ViewItem( fromClient, -1, toContainer, 0); } else { // Detect if this is an actionlocation container entity id's will not match if ( container->GetGemObject()->GetEntity()->GetID() != toContainer ) { psserver->GetCharManager()->ViewItem( fromClient, toContainer, 0 , 0 ); } else { psserver->GetCharManager()->ViewItem( fromClient, container->GetGemObject()->GetEntity()->GetID(), 0 , 0 ); } // check to see if this container will start an auto transform of item(s) psItem *item = container->GetItemInSlot(toSlot); psserver->GetWorkManager()->StartAutoWork( fromClient, container, item, item->GetStackCount()); } } transMoney::transMoney(const psMoney& amount, psCharacter *_from, bool exchange) : amount(amount), fromExchange(exchange) { from = _from; } void psTransaction::To(Slot_Containers slot, int _toSlot, int _toContainer) { toSet = true; eToSlot = slot; toSlot = _toSlot; toContainer = _toContainer; } void transMoney::Execute(void) { if(!toSet) return; psItem *item; // Store the manufactured items int remainder; if(eToSlot == CONTAINER_INVENTORY_BULK || eToSlot == CONTAINER_INVENTORY_EQUIPMENT) { bool validated = false; for(int i = 0;i < 4;i++) { int count = amount.Get(i); if(count > 0) { item = SlotManager::MakeMoneyItem(i, amount.Get(i)); if(eToSlot == CONTAINER_INVENTORY_BULK) { remainder = from->Inventory().PutInBulk(item, true, true, toSlot); if(remainder != 0) { psserver->SendSystemInfo(from->GetActor()->GetClientID(), "You cannot place this object in your inventory!" ); psserver->SendSystemError(from->GetActor()->GetClientID(), from->Inventory().lastError.GetData() ); } } else if(eToSlot == CONTAINER_INVENTORY_EQUIPMENT) remainder = SlotManager::PlaceItemInEquipment(*from, toSlot, item, true); // Only allow one type of money to be transferred at a time if(remainder == 0) { amount.Set(0, 0, 0, 0); amount.Set(i, count); validated = true; break; } } } if(!validated) return; } if(!fromExchange) from->AdjustMoney(-amount); else { Exchange* exchange = psserver->exchangemanager->GetExchange( from->GetActor()->GetClient()->ExchangeID() ); exchange->AdjustMoney(from->GetActor()->GetClient(), -amount); } switch(eToSlot) { case CONTAINER_OFFERING_MONEY: case CONTAINER_EXCHANGE_OFFERING: { Exchange* exchange = psserver->exchangemanager->GetExchange( from->GetActor()->GetClient()->ExchangeID() ); exchange->AdjustMoney(from->GetActor()->GetClient(), amount); } break; case CONTAINER_INVENTORY_MONEY: from->AdjustMoney(amount); psserver->GetCharManager()->UpdateItemViews(from->GetActor()->GetClientID()); break; case CONTAINER_WORLD: for(int i = 0;i < 4;i++) { if(amount.Get(i) > 0) { item = SlotManager::MakeMoneyItem(i, amount.Get(i)); ToWorld(item); item->Save(); } } break; case CONTAINER_INVENTORY_BULK: for(int i = 0;i < 4;i++) { if(amount.Get(i) > 0) { item = SlotManager::MakeMoneyItem(i, amount.Get(i)); ToBulk(item); item->Save(); } } break; case CONTAINER_INVENTORY_EQUIPMENT: for(int i = 0;i < 4;i++) { if(amount.Get(i) > 0) { item = SlotManager::MakeMoneyItem(i, amount.Get(i)); ToEquipment(item); item->Save(); } } break; } } transItem::transItem(Slot_Containers fromSlotType, int fromSlot, int quantity, psCharacter *_from, int fromContainer) : eFromSlot(fromSlotType), fromSlot(fromSlot), quantity(quantity), fromContainer(fromContainer) { from = _from; } psItem *transItem::GetItem() { psItem *item = NULL; switch(eFromSlot) { case CONTAINER_INVENTORY_BULK: item = from->Inventory().GetBulkItem(fromSlot); break; case CONTAINER_INVENTORY_EQUIPMENT: item = from->Inventory().GetEquipmentItem( fromSlot ); break; case CONTAINER_EXCHANGE_OFFERING: { Exchange *exchange = psserver->exchangemanager->GetExchange( from->GetActor()->GetClient()->ExchangeID() ); item = exchange->GetItem(from->GetActor()->GetClient(), fromSlot); } break; default: { psItem* holdingContainer = SlotManager::FindContainer( from->GetActor()->GetClient(), eFromSlot, fromContainer ); if ( holdingContainer) { item = holdingContainer->GetItemInSlot(fromSlot); } } } return item; } bool transItem::Validate(psItem *item, int& remainder) { Client *fromClient = from->GetActor()->GetClient(); int origStackCount = -1; Exchange *exchange = NULL; remainder = -1; if(!item) { if(eFromSlot == eToSlot && fromSlot == toSlot && fromContainer == toContainer) return false; switch(eFromSlot) { case CONTAINER_INVENTORY_BULK: item = from->Inventory().GetBulkItem(fromSlot); if(!item) break; origStackCount = item->GetStackCount(); item->SetStackCount(quantity); break; case CONTAINER_INVENTORY_EQUIPMENT: item = from->Inventory().GetEquipmentItem( fromSlot ); break; case CONTAINER_EXCHANGE_OFFERING: exchange = psserver->exchangemanager->GetExchange( fromClient->ExchangeID() ); if(!exchange) break; item = exchange->GetItem(fromClient, fromSlot); if(!item) break; origStackCount = item->GetStackCount(); item->SetStackCount(quantity); break; default: { psItem* holdingContainer = SlotManager::FindContainer( fromClient, eFromSlot, fromContainer ); if ( holdingContainer) { item = holdingContainer->GetItemInSlot(fromSlot); } } } } if(!item) return false; switch(eToSlot) { case CONTAINER_EXCHANGE_OFFERING: exchange = psserver->exchangemanager->GetExchange( fromClient->ExchangeID() ); remainder = exchange->AddItem(fromClient, item, toSlot, true); break; case CONTAINER_WORLD: remainder = 0; break; case CONTAINER_INVENTORY_BULK: remainder = from->Inventory().PutInBulk(item, true, true, toSlot); if(remainder != 0) { psserver->SendSystemInfo(fromClient->GetClientNum(), "You cannot place this object in your inventory!" ); psserver->SendSystemError(fromClient->GetClientNum(), from->Inventory().lastError.GetData() ); } break; case CONTAINER_INVENTORY_EQUIPMENT: remainder = SlotManager::PlaceItemInEquipment(*from, toSlot, item, true); break; default: { psItem* container = SlotManager::FindContainer( fromClient, eToSlot, toContainer ); int result = container->PutItemsInContainer( toSlot, item, from, true ); if(result == PS_CONT_OK) remainder = 0; else { if(result > 0) remainder = result; else remainder = -1; SlotManager::SendContainerError( result, fromClient, eToSlot, toSlot, toContainer ); } } }; if(origStackCount != -1) { item->SetStackCount( origStackCount); item->Save(); } if(remainder == -1) return false; return true; } void transItem::Execute(void) { if(!toSet) return; psItem *item = NULL; Client *fromClient = from->GetActor()->GetClient(); int dummy; Exchange *exchange = NULL; int remainder = -1; if(!Validate(NULL, remainder)) return; switch(eFromSlot) { case CONTAINER_INVENTORY_BULK: item = from->Inventory().RemoveBulk( fromSlot, quantity - remainder ); break; case CONTAINER_INVENTORY_EQUIPMENT: item = from->Inventory().RemoveEquipment( fromSlot ); // stop any work being done on item removed from equip slot psserver->GetWorkManager()->StopWork(fromClient, item); SlotManager::UnequipHolding(fromSlot, item, from); break; case CONTAINER_EXCHANGE_OFFERING: exchange = psserver->exchangemanager->GetExchange( fromClient->ExchangeID() ); item = exchange->RemoveItem( fromClient, fromSlot, quantity - remainder, dummy ); break; default: { psItem* holdingContainer = SlotManager::FindContainer( fromClient, eFromSlot, fromContainer ); if ( holdingContainer) { if( holdingContainer->GetGemObject() ) // Container exists in world { item = holdingContainer->RemoveItemsInContainer( fromSlot, quantity - remainder ); holdingContainer->Save(); // stop any work being done on item removed from container psserver->GetWorkManager()->StopWork(fromClient, item); // If items left in the slot do some work. psItem * itemLeft = holdingContainer->GetItemInSlot(fromSlot); if ( itemLeft ) { // Stop any work going on with existing item. psserver->GetWorkManager()->StopWork(fromClient, itemLeft); // Try to autotransform what is left. psserver->GetWorkManager()->StartAutoWork(fromClient, holdingContainer, itemLeft, itemLeft->GetStackCount()); } } else { // Containers in character's inventory item = holdingContainer->RemoveItemsInContainer(fromSlot, quantity); holdingContainer->Save(); } } } } // We should have an item here since Validate() makes sure everything fits switch(eToSlot) { case CONTAINER_EXCHANGE_OFFERING: exchange = psserver->exchangemanager->GetExchange( fromClient->ExchangeID() ); exchange->AddItem(fromClient, item, toSlot, false); break; case CONTAINER_WORLD: ToWorld(item); break; case CONTAINER_INVENTORY_BULK: ToBulk(item); break; case CONTAINER_INVENTORY_EQUIPMENT: ToEquipment(item); break; default: ToContainer(item); }; // Don't save if item is in exchange limbo if(eToSlot != CONTAINER_EXCHANGE_OFFERING) item->Save(true); }