/* * creationmanager.cpp - author: Andrew Craig * * Copyright (C) 2003 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 "psserver.h" #include "globals.h" #include "creationmanager.h" #include "util/psdatabase.h" #include "util/serverconsole.h" #include "util/eventmanager.h" #include "iserver/idal.h" #include "net/msghandler.h" #include "net/charmessages.h" #include "client.h" #include "clients.h" #include "psserverchar.h" #include "cachemanager.h" #include "bulkobjects/psraceinfo.h" #include "bulkobjects/pscharacterloader.h" ////////////////////////////////////////////////////////////////////////////// // SFC MACROS ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// psCharCreationManager::psCharCreationManager() { raceCPValues = 0; raceCPValuesLength = 0; } psCharCreationManager::~psCharCreationManager() { delete[] raceCPValues; } bool psCharCreationManager::Initialize( ) { if ( !( LoadCPValues() && LoadCreationChoices() && LoadLifeEvents()) ) return false; psserver->GetEventManager()->Subscribe(this, MSGTYPE_CHAR_CREATE_CP,REQUIRE_ANY_CLIENT); psserver->GetEventManager()->Subscribe(this, MSGTYPE_CHAR_CREATE_PARENTS,REQUIRE_ANY_CLIENT); psserver->GetEventManager()->Subscribe(this, MSGTYPE_CHAR_CREATE_CHILDHOOD,REQUIRE_ANY_CLIENT); psserver->GetEventManager()->Subscribe(this, MSGTYPE_CHAR_CREATE_LIFEEVENTS,REQUIRE_ANY_CLIENT); psserver->GetEventManager()->Subscribe(this, MSGTYPE_CHAR_CREATE_TRAITS,REQUIRE_ANY_CLIENT); psserver->GetEventManager()->Subscribe(this, MSGTYPE_CHAR_CREATE_NAME,REQUIRE_ANY_CLIENT); psserver->GetEventManager()->Subscribe(this, MSGTYPE_CHAR_DELETE,REQUIRE_ANY_CLIENT); // Other loaders are here. return true; } void psCharCreationManager::HandleMessage(MsgEntry *pMsg,Client *client) { switch ( pMsg->GetType() ) { /* This is a simple message requesting a CP value for a race * so we can handle the raw message data here. */ case MSGTYPE_CHAR_CREATE_CP: { MsgEntry *msg = new MsgEntry( 100 ); msg->SetType(MSGTYPE_CHAR_CREATE_CP); msg->clientnum = pMsg->clientnum; int raceID = pMsg->GetInt32(); // Add race ID msg->Add((int32_t)raceID); // Add Race CP points if (raceID>=0 && raceIDAdd( (int32_t)raceCPValues[raceID].value ); else { Error2("Invalid raceID %i", raceID); msg->Add((uint32_t)0); } msg->ClipToCurrentSize(); if (msg->overrun) { Bug1("psCharCreationManager::HandleMessage() would have overflowed message buffer.\n"); } else psserver->GetEventManager()->SendMessage(msg); return; } case MSGTYPE_CHAR_DELETE: { HandleCharDelete( pMsg, client ); return; } case MSGTYPE_CHAR_CREATE_NAME: { HandleName(pMsg,client); return; } case MSGTYPE_CHAR_CREATE_PARENTS: { HandleParents(pMsg,client); return; } case MSGTYPE_CHAR_CREATE_CHILDHOOD: { HandleChildhood(pMsg,client); return; } case MSGTYPE_CHAR_CREATE_LIFEEVENTS: { HandleLifeEvents(pMsg,client); return; } case MSGTYPE_CHAR_CREATE_TRAITS: { HandleTraits(pMsg,client); return; } } } bool psCharCreationManager::HandleCharDelete( MsgEntry* me, Client* client ) { psCharDeleteMessage msg(me); csString charName = msg.charName; if(!charName.Length()) { CPrintf(CON_WARNING,"Client %u sent malformed character name to deletion code!",client->GetClientNum()); return false; // No char } unsigned int characteruid = psserver->CharacterLoader.FindCharacterID( client->GetAccountID(), charName ); csString error; if ( psserver->CharacterLoader.AccountOwner( charName, client->GetAccountID() ) ) { // Found the char? if ( !characteruid) { psserver->SendSystemError(client->GetClientNum(),"Couldn't find character data!"); return false; } // Can we delete it? if (!psserver->CharacterLoader.DeleteCharacterData(characteruid,error)) { psserver->SendSystemError(client->GetClientNum(),"Error: %s",error.GetData()); return false; } // Remove cached objects to make sure that the client gets a fresh character // list from the database. 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(); } // Ok, we deleted it psCharDeleteMessage response( charName, me->clientnum ); response.SendMessage(); } else { CPrintf(CON_WARNING,"Character %s not deleted because of non-ownership\n", charName.GetData() ); psserver->SendSystemError(client->GetClientNum(),"You do not own that character!"); return false; } return true; } bool psCharCreationManager::LoadCPValues() { Result result(db->Select("SELECT race_id,initial_cp from race_info")); if ( !result.IsValid() || result.Count() == 0 ) return false; raceCPValuesLength = result.Count(); raceCPValues = new RaceCP[ raceCPValuesLength ]; for (unsigned int x = 0; x < result.Count(); x++ ) { raceCPValues[x].id = result[x].GetInt(0); raceCPValues[x].value = result[x].GetInt(1); } return true; } bool psCharCreationManager::LoadLifeEvents() { Result events( db->Select("SELECT * from char_create_life") ); if ( !events.IsValid() || events.Count() == 0 ) return false; for ( unsigned int x = 0; x < events.Count(); x++ ) { LifeEventChoiceServer* choice = new LifeEventChoiceServer; choice->id = events[x].GetInt(0); choice->name = events[x][1]; choice->description = events[x][2]; choice->cpCost = events[x].GetInt(3); choice->eventScript = events[x][4]; csString common = events[x][5]; choice->common = common.GetAt(0); Result adds( db->Select("SELECT adds_choice from char_create_life_relations WHERE adds_choice IS NOT NULL AND choice=%d", choice->id) ); if (!adds.IsValid()) return false; for ( unsigned int addIndex = 0; addIndex < adds.Count(); addIndex++ ) { choice->adds.Push( adds[addIndex].GetInt(0) ); } Result removes( db->Select("SELECT removes_choice from char_create_life_relations WHERE removes_choice IS NOT NULL AND choice=%d", choice->id) ); if (!removes.IsValid()) return false; for ( unsigned int removesIndex = 0; removesIndex < removes.Count(); removesIndex++ ) { choice->removes.Push( removes[removesIndex].GetInt(0) ); } lifeEvents.Push( choice ); } return true; } bool psCharCreationManager::LoadCreationChoices() { Result result( db->Select("SELECT * from character_creation") ); if ( !result.IsValid() || result.Count() == 0 ) return false; /* For all the possible choices: Create them Find out into what data set they should be pushed */ for ( unsigned int x = 0; x < result.Count(); x++ ) { CreationChoice* choice = new CreationChoice; choice->id = result[x].GetInt(0); choice->name = result[x][1]; choice->description = result[x][2]; choice->cpCost = result[x].GetInt(3); choice->eventScript = result[x][4]; choice->choiceArea = ConvertAreaToInt( result[x][5] ); switch (choice->choiceArea) { case FATHER_JOB: case MOTHER_JOB: case RELIGION: { parentData.Push( choice ); break; } case BIRTH_EVENT: case CHILD_ACTIVITY: case CHILD_HOUSE: case CHILD_SIBLINGS: case ZODIAC: { childhoodData.Push( choice ); break; } } } return true; } int psCharCreationManager::ConvertAreaToInt( const char* area ) { csString str( area ); if ( str == "ZODIAC" ) return ZODIAC; if ( str == "FATHER_JOB" ) return FATHER_JOB; if ( str == "MOTHER_JOB" ) return MOTHER_JOB; if ( str == "RELIGION" ) return RELIGION; if ( str == "BIRTH_EVENT" ) return BIRTH_EVENT; if ( str == "CHILD_ACTIVITY" ) return CHILD_ACTIVITY; if ( str == "CHILD_HOUSE" ) return CHILD_HOUSE; if ( str == "CHILD_SIBLINGS" ) return CHILD_SIBLINGS; return -1; } void psCharCreationManager::HandleName( MsgEntry* me, Client *client ) { psNameCheckMessage name; name.FromClient( me ); /// Check in migration/reserve table csString query; csString escape; if ( name.firstName.Length() == 0 ) { psNameCheckMessage response( me->clientnum, false, "Cannot have an empty first name!"); response.SendMessage(); return; } if (name.firstName.Length() > 27) { psNameCheckMessage response(me->clientnum, false, "First name is too long!"); response.SendMessage(); return; } if (name.lastName.Length() > 27) { psNameCheckMessage response(me->clientnum, false, "Last name is too long!"); response.SendMessage(); return; } db->Escape( escape, name.firstName ); query.Format( "SELECT * FROM migration m WHERE m.username='%s'", escape.GetData() ); Result result (db->Select( query ) ); if ( result.IsValid() && result.Count() == 1 ) { int acctID = client->GetAccountID(); int reservedName = psserver->GetCharManager()->IsReserved( name.firstName, acctID ); if ( reservedName == NAME_RESERVED ) { psNameCheckMessage response( me->clientnum, false, "Name is in reserved database"); response.SendMessage(); return; } } // Check uniqueness of the names if(!psserver->GetCharManager()->IsUnique(name.firstName)) { psNameCheckMessage response( me->clientnum, false, "First name is already in use"); response.SendMessage(); return; } if(!psserver->GetCharManager()->IsLastNameUnique(name.lastName)) { psNameCheckMessage response( me->clientnum, false, "Last name is already in use"); response.SendMessage(); return; } csString playerName = name.firstName; csString lastName = name.lastName; // Check if the name is banned if(psserver->GetCharManager()->IsBanned(playerName)) { csString error; error.Format("The name %s is banned", playerName.GetData() ); psNameCheckMessage reject(me->clientnum, false, error ); reject.SendMessage(); return; } if(psserver->GetCharManager()->IsBanned(lastName)) { csString error; error.Format("The lastname %s is banned", lastName.GetData() ); psNameCheckMessage reject(me->clientnum, false, error ); reject.SendMessage(); return; } psNameCheckMessage response( me->clientnum, true, "Ok"); response.SendMessage(); } void psCharCreationManager::HandleChildhood( MsgEntry* me,Client *client ) { psCreationChoiceMsg message(me); if ( !message.valid ) { Debug2( LOG_NET, me->clientnum,"Received unparsable Childhood request from client: %u\n", me->clientnum ); return; } psCreationChoiceMsg response( me->clientnum, (int) childhoodData.Length(), MSGTYPE_CHAR_CREATE_CHILDHOOD ); for (size_t x = 0; x < childhoodData.Length(); x++ ) { response.AddChoice( childhoodData[x]->id, childhoodData[x]->name, childhoodData[x]->description, childhoodData[x]->choiceArea, childhoodData[x]->cpCost ); } response.ConstructMessage(); if (response.valid) { response.SendMessage(); } else { Bug2("Failed to construct a valid psCreationChoiceMsg for client id %u.\n",me->clientnum); } } void psCharCreationManager::HandleParents( MsgEntry* me,Client *client ) { psCreationChoiceMsg message(me); if (!message.valid) { Debug2(LOG_NET,me->clientnum,"Received unparsable psCreationChoiceMsg from client id %u.\n",me->clientnum); return; } psCreationChoiceMsg response( me->clientnum, (int) parentData.Length(), MSGTYPE_CHAR_CREATE_PARENTS ); for (size_t x = 0; x < parentData.Length(); x++ ) { response.AddChoice( parentData[x]->id, parentData[x]->name, parentData[x]->description, parentData[x]->choiceArea, parentData[x]->cpCost ); } response.ConstructMessage(); if (response.valid) response.SendMessage(); else { Bug2("Failed to construct a valid psCreationChoiceMsg for client id %u.\n",me->clientnum); } } void psCharCreationManager::HandleLifeEvents( MsgEntry* me,Client *client ) { psLifeEventMsg message(me); if (!message.valid) { Debug2(LOG_NET,me->clientnum,"Received unparsable psLifeEventMsg from client id %u.\n",me->clientnum); return; } psLifeEventMsg response( me->clientnum ); for (size_t x = 0; x < lifeEvents.Length(); x++ ) { response.AddEvent( lifeEvents[x] ); } response.ConstructMessage(); if (response.valid) response.SendMessage(); else { Bug2("Failed to construct a valid psLifeEventMsg for client id %u.\n",me->clientnum); } } void psCharCreationManager::HandleTraits( MsgEntry* me,Client *client ) { psCreationChoiceMsg message(me); if (!message.valid) { Debug2(LOG_NET,me->clientnum,"Received unparsable psCreationChoiceMsg from client id %u.\n",me->clientnum); return; } csString str = ""; CacheManager::TraitIterator traits = CacheManager::GetSingleton().GetTraitIterator(); while (traits.HasNext()) { psTrait * trait = traits.Next(); if (!trait->onlyNPC) { str += trait->ToXML(); } } str += ""; // printf("Handle traits: %s\n",str.GetData()); psCharCreateTraitsMessage response(client->GetClientNum(), str); response.SendMessage(); } psCharCreationManager::LifeEventChoiceServer* psCharCreationManager::FindLifeEvent( int id ) { for (size_t x = 0; x < lifeEvents.Length(); x++ ) { if ( lifeEvents[x]->id == id ) return lifeEvents[x]; } return 0; } psCharCreationManager::CreationChoice* psCharCreationManager::FindChoice( int id ) { size_t x; for ( x = 0; x < childhoodData.Length(); x++ ) { if ( childhoodData[x]->id == id ) return childhoodData[x]; } for ( x = 0; x < parentData.Length(); x++ ) { if ( parentData[x]->id == id ) return parentData[x]; } return 0; } bool psCharCreationManager::Validate( psCharUploadMessage& mesg, csString& errorMsg ) { // Check for bad parent modifier. if ( mesg.fatherMod > 3 || mesg.fatherMod < 1 ) { Notify1( LOG_NEWCHAR, "New character tried to use invalid father modification" ); errorMsg = "Invalid father modification selected"; return false; } if ( mesg.motherMod > 3 || mesg.motherMod < 1 ) { Notify1( LOG_NEWCHAR, "New character tried to use invalid mother modification" ); errorMsg = "Invalid mother modification selected"; return false; } int cpCost = CalculateCPLife( mesg.lifeEvents ) + CalculateCPChoices( mesg.choices, mesg.fatherMod, mesg.motherMod ); psRaceInfo* race; race = CacheManager::GetSingleton().GetRaceInfoByNameGender( (PSCHARACTER_RACE)mesg.race, (PSCHARACTER_GENDER)mesg.gender ); if ( !race ) { errorMsg = "No race selected"; return false; } else { if ( cpCost > race->initialCP ) { Notify1( LOG_NEWCHAR, "New character exceeded CP allowance" ); errorMsg = "CP allowance exceeded."; return false; } } return true; } int psCharCreationManager::CalculateCPLife( csArray& events ) { int cpCost = 0; for ( size_t li = 0; li < events.Length(); li++ ) { psCharCreationManager::LifeEventChoiceServer *event; event = psserver->charCreationManager->FindLifeEvent( events[li] ); if ( event ) { cpCost+=event->cpCost; } } return cpCost; } int psCharCreationManager::CalculateCPChoices( csArray& choices, int fatherMod, int motherMod ) { int cpCost = 0; for ( size_t ci = 0; ci < choices.Length(); ci++ ) { psCharCreationManager::CreationChoice* choice = FindChoice( choices[ci] ); if ( choice ) { if ( choice->choiceArea == FATHER_JOB ) { cpCost+= choice->cpCost*fatherMod; } else if ( choice->choiceArea == MOTHER_JOB ) { cpCost+= choice->cpCost*motherMod; } else cpCost+= choice->cpCost; } } return cpCost; }