/* * dictionary.cpp * * Copyright (C) 2001 Atomic Blue (info@planeshift.it, http://www.atomicblue.org) * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation (version 2 of the License) * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * */ #include #include "util/strutil.h" #include "util/log.h" #include #include #include "../iserver/idal.h" #include "dictionary.h" #include "psnpcdialog.h" #include "psquestprereqops.h" #include "util/psdatabase.h" #include "../playergroup.h" #include "../client.h" #include "../gem.h" #include "../globals.h" #include "../psserver.h" #include "../cachemanager.h" #include "../questmanager.h" #include "../entitymanager.h" #include "../progressionmanager.h" #include "../cachemanager.h" #include "../questionmanager.h" #include "net/messages.h" #include "pscharacter.h" #include "pscharacterloader.h" #include "psitem.h" #include "psitemstats.h" #include "util/serverconsole.h" #include "util/mathscript.h" #include "../tools/wordnet/wn.h" NPCDialogDict *dict; NPCDialogDict::NPCDialogDict() { dynamic_id = 1000000; } NPCDialogDict::~NPCDialogDict() { wnclose(); dict = NULL; } bool NPCDialogDict::Initialize(iDataConnection *db) { // Initialise WordNet if (wninit() != 0) { Error1("*****************************\nWordNet failed to initialize.\n" "******************************\n"); } if (LoadDisallowedWords(db)) { if (LoadSynonyms(db)) { if (LoadTriggerGroups(db)) { if (LoadTriggers(db)) { if (LoadResponses(db)) { return true; } else Error1("*********************************\nFailed to load Responses\n****************************\n"); } else Error1("****************************\nFailed to load Triggers\n****************************\n"); } else Error1("****************************\nFailed to load Trigger Groups\n************************\n"); } else Error1("****************************\nFailed to load Synonyms\n****************************\n"); } else Error1("****************************\nFailed to load Disallowed words\n****************************\n"); return false; } bool NPCDialogDict::LoadDisallowedWords(iDataConnection *db) { Result result(db->Select("select word from npc_disallowed_words")); if (!result.IsValid()) { Error1("Cannot load disallowed words into dictionary from database.\n"); Error1( db->GetLastError() ); return false; } for (unsigned int i=0; iGetData()); delete newword; } } return true; } NpcTerm* NPCDialogDict::AddTerm(const char *term) { NpcTerm * npc_term = FindTerm(term); if (npc_term) return npc_term; NpcTerm *newphrase = new NpcTerm(term); if (phrases.Insert(newphrase,TREE_OWNS_DATA)) { delete newphrase; return NULL; } return newphrase; } bool NPCDialogDict::LoadSynonyms(iDataConnection *db) { Result result(db->Select("select word," " synonym_of" " from npc_synonyms")); if (!result.IsValid()) { CPrintf(CON_ERROR, "Cannot load terms into dictionary from database.\n"); CPrintf(CON_ERROR, db->GetLastError()); return false; } for (unsigned int i=0; isynonym = AddTerm(synonym_of); } } return true; } void NPCDialogDict::AddWords(csString& trigger) { bool found = false; // Flag to mark if a disallowed word is found int wordnum=1; csString word("temp"); // if it's an exchange trigger just skip the following checks if (trigger.GetAt(0)=='<') return; while (word.Length()) { size_t pos = 0; word = GetWordNumber(trigger,wordnum++,&pos); if (word.Length()==0) continue; // Check this word in the trigger for being disallowed. If so, skip it. csString *disallowed = disallowed_words.Find(&word); if (disallowed) { CPrintf(CON_DEBUG,"Skipping disallowed word '%s' in trigger '%s' at position %d.\n", word.GetData(), trigger.GetData(), pos); trigger.DeleteAt(pos, word.Length() ); if (strstr(trigger.GetData()," ")) trigger.DeleteAt(strstr(trigger.GetData()," ")-trigger.GetData(),1); trigger.RTrim(); trigger.LTrim(); wordnum--; // Back up a word since we took one out. found = true; // Mark that we found a disallowed word. continue; } NpcTerm *found, key(word); found = phrases.Find(&key); if (found) { if (found->synonym ) { CPrintf(CON_WARNING, "Warning: Word %s in trigger '%s' is already a synonym for '%s'.\n", (const char *)found->term, (const char *)trigger, (const char *)found->synonym->term); } } else { // add word found = new NpcTerm(word); // CPrintf(CON_DEBUG, "Adding %s.\n",(const char *)word); if (phrases.Insert(found,TREE_OWNS_DATA)) { Error2("Found equal term(%s) in phrases\n",found->term.GetData()); delete found; found = NULL; } } } if (found) // If a dissallowd word was found. Print the new trigger text. { CPrintf(CON_DEBUG,"Trigger without dissallowed words: %s\n",trigger.GetDataSafe()); } } bool NPCDialogDict::LoadTriggerGroups(iDataConnection *db) { Debug1(LOG_STARTUP,0,"Loading Trigger Groups...\n"); // First add all the root entries so we find the parents later. Result result(db->Select("select id," " trigger_text" " from npc_trigger_groups" " where equivalent_to_id=0") ); if (!result.IsValid()) { CPrintf(CON_ERROR, "Cannot load trigger groups into dictionary from database.\n"); CPrintf(CON_ERROR, db->GetLastError()); return false; } for (unsigned int i=0; itext.GetData()); delete newtge; continue; } trigger_groups_by_id.Put(newtge->id,newtge); AddWords(newtge->text); // Make sure these trigger words are in known word list. Debug2(LOG_STARTUP,0,"Loaded <%s>\n",newtge->text.GetData() ); } // Now add all the child entries and attach to parents. Result result2(db->Select("select id," " trigger_text," " equivalent_to_id" " from npc_trigger_groups" " where equivalent_to_id<>0") ); if (!result2.IsValid()) { CPrintf(CON_ERROR, "Cannot load trigger groups into dictionary from database.\n"); CPrintf(CON_ERROR, db->GetLastError()); return false; } for (unsigned int i=0; itext.GetData()); delete newtge; continue; } trigger_groups_by_id.Put(newtge->id,newtge); AddWords(newtge->text); // Make sure these trigger words are in known word list. Debug3(LOG_STARTUP,0,"Loaded <%s> as child of <%s>\n",newtge->text.GetData(), parent->text.GetData() ); } else { Error3("Trigger group entry id=%d specified bad parent id of %d. Skipped.", result2[i].GetInt("id"),result2[i].GetInt("equivalent_to_id") ); } } return true; } bool NPCDialogDict::LoadTriggers(iDataConnection *db) { Debug1(LOG_STARTUP,0,"Loading Triggers...\n"); Result result(db->Select("select * from npc_triggers") ); if (!result.IsValid()) { CPrintf(CON_ERROR, "Cannot load triggers into dictionary from database.\n"); CPrintf(CON_ERROR, db->GetLastError()); return false; } for (unsigned int i=0; iLoad(result[i])) { Error2("Could not load trigger %s!\n",result[i]["id"]); delete newtrig; continue; } if (triggers.Insert(newtrig,TREE_OWNS_DATA)) { Error4("Found equal trigger %d (%s) in triggers, area %s\n",newtrig->id,newtrig->trigger.GetData(),newtrig->area.GetData()); delete newtrig; continue; } AddWords(newtrig->trigger); // Make sure these trigger words are in known word list. } return true; } bool NPCDialogDict::LoadResponses(iDataConnection *db) { Result result(db->Select("SELECT * FROM npc_responses")); if (!result.IsValid()) { CPrintf(CON_ERROR, "Cannot load responses into dictionary from database.\n"); CPrintf(CON_ERROR, db->GetLastError()); return false; } for (unsigned int i=0; iLoad(result[i])) { delete newresp; return false; } if (responses.Insert(newresp,TREE_OWNS_DATA)) { Error2("Found equal response(%s) in responses\n",newresp->response[0].GetData()); delete newresp; } } return true; } NpcTerm * NPCDialogDict::FindTerm(const char *term) { NpcTerm key(term); key.term.Downcase(); return phrases.Find(&key); } NpcTerm * NPCDialogDict::FindTermOrSynonym(const csString & term) { NpcTerm * termRec = FindTerm(term); if (termRec == NULL) { // If we don't have it and it is a noun, then search generalisations NpcTerm newTerm(term); if(!newTerm.IsNoun()) return NULL; size_t which = 1; const char* hypernym; while(true) { hypernym = newTerm.GetInterleavedHypernym(which); if(hypernym) { termRec = FindTerm(hypernym); if(termRec) return termRec; } else return NULL; which++; }; return NULL; } if (termRec->synonym) return termRec->synonym; else return termRec; } NpcResponse *NPCDialogDict::FindResponse(gemNPC * npc, const char *area, const char *trigger, int faction, int priorresponse, Client *client) { Debug7(LOG_NPC, client->GetClientNum(),"Entering NPCDialogDict::FindResponse(%s,%s,%s,%d,%d,%s)", npc->GetName(),area,trigger,faction,priorresponse,client->GetName()); NpcTrigger *trig; NpcTrigger key; key.area = area; key.trigger = trigger; key.priorresponseID = priorresponse; key.max_attitude = faction; key.min_attitude = faction; trig = triggers.Find(&key); if (trig) { Debug3(LOG_NPC, client->GetClientNum(),"NPCDialogDict::FindResponse consider trig(%d): '%s'", trig->id,trig->trigger.GetDataSafe()); } else { Debug1(LOG_NPC, client->GetClientNum(),"NPCDialogDict::FindResponse no trigger found"); return NULL; } csArray availableResponseList; // Check if not all responses is blocked(Not availabe in quests, Prequests not fullfitted,...) if (trig && !trig->HaveAvailableResponses(client,npc,this,&availableResponseList)) { Debug1(LOG_NPC, client->GetClientNum(),"NPCDialogDict::FindResponse no available responses found"); return NULL; } NpcResponse *resp; resp = FindResponse(trig->GetRandomResponse(availableResponseList)); // find one of the specified responses return resp; } NpcResponse *NPCDialogDict::FindResponse(int responseID) { NpcResponse key; key.id = responseID; return responses.Find(&key); } bool NPCDialogDict::CheckForTriggerGroup(csString& trigger) { NpcTriggerGroupEntry key(0,trigger); NpcTriggerGroupEntry *found = trigger_groups.Find(&key); if (found && found->parent) { // Trigger is child, so substitute parent trigger = found->parent->text; return true; } return false; } void NPCDialogDict::AddTrigger( iDataConnection* db, int triggerID ) { Result result(db->Select("SELECT * from npc_triggers WHERE id=%d", triggerID )); if (!result.IsValid() || result.Count()!=1) { Error2("Invalid trigger id %d in npc_triggers table.\n",triggerID); return; } NpcTrigger* newtrig = new NpcTrigger; if (!newtrig->Load(result[0])) { delete newtrig; return; } NpcTrigger* trig; if (trig = triggers.Insert( newtrig, TREE_OWNS_DATA )) { // There are already a trigger with this combination of // triggertext, KA, and prior respose so pushing the trigger // response on the same trigger. trig->responseIDlist.Push(newtrig->responseIDlist.Top()); delete newtrig; return; } AddWords( newtrig->trigger ); } void NPCDialogDict::AddResponse( iDataConnection* db, int databaseID ) { Result result(db->Select("SELECT * from npc_responses WHERE id=%d",databaseID ) ); if (!result.IsValid() || result.Count() != 1) { Error2("Invalid response id %d specified for npc_responses table.\n",databaseID); return; } NpcResponse *newresp = new NpcResponse; if (!newresp->Load(result[0])) { delete newresp; return; } if (responses.Insert(newresp,TREE_OWNS_DATA)) { Error2("Found equal response(%s) in responses\n",newresp->response[0].GetData()); delete newresp; } } NpcResponse *NPCDialogDict::AddResponse(const char *response_text, const char *pronoun_him, const char *pronoun_her, const char *pronoun_it, const char *pronoun_them, int &new_id, psQuest * quest) { NpcResponse *newresp = new NpcResponse; if (!new_id) new_id = dynamic_id++; newresp->id = new_id; newresp->response[0] = response_text; newresp->him = pronoun_him; newresp->her = pronoun_her; newresp->it = pronoun_it; newresp->them = pronoun_them; newresp->type = NpcResponse::VALID_RESPONSE; newresp->quest = quest; if (quest && quest->GetPrerequisite()) newresp->prerequisite = quest->GetPrerequisite()->Copy(); else newresp->prerequisite = NULL; newresp->ParseResponseScript(""); if (responses.Insert(newresp,TREE_OWNS_DATA)) { Error2("Found equal response(%s) in responses\n",newresp->response[0].GetData()); delete newresp; return NULL; } return newresp; // Make response available for script additions } void NPCDialogDict::DeleteTriggerResponse(NpcTrigger * trigger, int responseId) { NpcResponse dummy; dummy.id = responseId; responses.Delete(&dummy); if(trigger) { trigger->responseIDlist.Delete(responseId); if(trigger->responseIDlist.Length() == 0) triggers.Delete(trigger); } } NpcTrigger *NPCDialogDict::AddTrigger(const char *k_area,const char *mytrigger,int prior_response, int trigger_response) { NpcTrigger *trig; NpcTrigger key; // Both 0 and -1 can be used for no precodition, make sure we use -1 if (prior_response == 0) prior_response = -1; // If the trigger to be added is already present, then the trigger response // is just added as an alternative to the existing responses specified for // this trigger. These are chosen from randomly when triggering later. key.area = k_area; key.trigger = mytrigger; key.priorresponseID = prior_response; key.max_attitude = 0; key.min_attitude = 0; trig = triggers.Find(&key); if (trig) { Debug2(LOG_QUESTS,0,"Found existing trigger so adding response %d as an alternative response to it.\n",trigger_response); trig->responseIDlist.Push(trigger_response); return trig; } else { NpcTrigger *newtrig = new NpcTrigger; newtrig->id = 0; newtrig->area = k_area; newtrig->trigger = mytrigger; newtrig->priorresponseID = prior_response; newtrig->min_attitude = -100; newtrig->max_attitude = 100; newtrig->responseIDlist.Push(trigger_response); NpcTrigger* oldtrig; if (oldtrig = triggers.Insert( newtrig, TREE_OWNS_DATA )) { // There are already a trigger with this combination of // triggertext, KA, and prior respose so pushing the trigger // response on the same trigger. oldtrig->responseIDlist.Push(newtrig->responseIDlist.Top()); delete newtrig; return oldtrig; } AddWords( newtrig->trigger ); return newtrig; } } void PrintTrigger(NpcTrigger * trig) { CPrintf(CON_CMDOUTPUT ,"Trigger [%d] %s : \"%-60.60s\" %8d %4d %4d", trig->id,trig->area.GetData(),trig->trigger.GetDataSafe(),trig->priorresponseID, trig->min_attitude, trig->max_attitude); } void PrintResponse(NpcResponse * resp) { csString script = resp->GetResponseScript(); CPrintf(CON_CMDOUTPUT ,"Response [%8d] Script : %s\n",resp->id,script.GetData()); if (resp->quest) { CPrintf(CON_CMDOUTPUT ," Quest : %s\n",resp->quest->GetName()); } if (resp->prerequisite) { csString prereq = resp->prerequisite->GetScript(); if (prereq != "") { CPrintf(CON_CMDOUTPUT ," Prereq : %s\n",prereq.GetDataSafe()); } } if (resp->him.Length() || resp->her.Length() || resp->it.Length() || resp->them.Length()) { CPrintf(CON_CMDOUTPUT," Pronoun: him='%s' her='%s' it='%s' them='%s'\n", resp->him.GetDataSafe(),resp->her.GetDataSafe(), resp->it.GetDataSafe(),resp->them.GetDataSafe()); } for (int n = 0; n < MAX_RESP; n++) { if (resp->response[n].Length()) { CPrintf(CON_CMDOUTPUT ,"%21d) %s\n",n+1,resp->response[n].GetData()); } } } void NPCDialogDict::Print(const char *area) { CPrintf(CON_CMDOUTPUT ,"\n"); CPrintf(CON_CMDOUTPUT ,"NPC Dictionary\n"); CPrintf(CON_CMDOUTPUT ,"\n"); if (area!=NULL && strlen(area)) { CPrintf(CON_CMDOUTPUT ,"----------- Triggers/Responses of area %s----------\n",area); BinaryRBIterator trig_iter(&triggers); NpcTrigger * trig; for (trig = trig_iter.First(); trig; trig = ++trig_iter) { // filter on given area if (area!=NULL && strcmp(trig->area.GetDataSafe(),area)!=0) continue; PrintTrigger(trig); CPrintf(CON_CMDOUTPUT ,"\n"); for (size_t i = 0; i < trig->responseIDlist.Length(); i++) { NpcResponse * resp = dict->FindResponse(trig->responseIDlist[i]); if (resp) { PrintResponse(resp); } else CPrintf(CON_CMDOUTPUT ,"Response [%d]: Error. Response not found!!!\n",trig->responseIDlist[i]); } CPrintf(CON_CMDOUTPUT ,"\n"); } return; } CPrintf(CON_CMDOUTPUT ,"----------- All Triggers ----------\n"); BinaryRBIterator trig_iter(&triggers); NpcTrigger * trig; for (trig = trig_iter.First(); trig; trig = ++trig_iter) { PrintTrigger(trig); for (size_t i = 0; i < trig->responseIDlist.Length(); i++) { CPrintf(CON_CMDOUTPUT ," %d",trig->responseIDlist[i]); } CPrintf(CON_CMDOUTPUT ,"\n"); } CPrintf(CON_CMDOUTPUT ,"----------- All Responses ---------\n"); BinaryRBIterator resp_iter(&responses); NpcResponse * resp; for (resp = resp_iter.First(); resp; resp = ++resp_iter) { PrintResponse(resp); } CPrintf(CON_CMDOUTPUT ,"\n"); } bool NpcTerm::IsNoun() { char* baseform = morphstr(const_cast(term.GetData()), NOUN); if(!baseform) baseform = const_cast(term.GetData()); return in_wn(baseform, NOUN) != 0; } const char* NpcTerm::GetInterleavedHypernym(size_t which) { if(hypernymSynNet == NULL) BuildHypernymList(); if(which < hypernyms.Length()) return hypernyms.Get(which); else return NULL; } void NpcTerm::BuildHypernymList() { hypernymSynNet = findtheinfo_ds(const_cast(term.GetData()), NOUN, -HYPERPTR, ALLSENSES); // Hypernym 0 is the original word hypernyms.Put(0, term.GetData()); bool hit; SynsetPtr sense[50]; SynsetPtr current = sense[0] = hypernymSynNet; if(current == NULL) return; // We interleave hypernyms through the senses size_t sensecount = 0; while(current->nextss) { sensecount++; current = current->nextss; sense[sensecount] = current; } // Perform breadth-first search of hypernyms/word senses do { // Each iteration goes one level deeper hit = false; for(size_t j = 0; j <= sensecount; j++) { if(sense[sensecount] == NULL) continue; sense[sensecount] = sense[sensecount]->ptrlist; // We have found a hypernym if(sense[sensecount]) { hit = true; hypernyms.Push(*sense[sensecount]->words); } } } while(hit == true); } bool NpcTrigger::Load(iResultRow& row) { id = row.GetInt("id"); area = row["area"]; trigger = row["trigger"]; priorresponseID = row.GetInt("prior_response_required"); // Both 0 and -1 can be used for no precodition, make sure we use -1 if (priorresponseID == 0) priorresponseID = -1; min_attitude = row.GetInt("min_attitude_required"); max_attitude = row.GetInt("max_attitude_required"); responseIDlist.Push(row.GetInt("response_id")); return true; } bool NpcTrigger::HaveAvailableResponses(Client * client, gemNPC * npc, NPCDialogDict * dict, csArray *availableResponseList) { bool haveAvail = false; for (size_t n = 0; n < responseIDlist.Length(); n++) { NpcResponse * resp = dict->FindResponse(responseIDlist[n]); if (resp) { if (resp->quest || resp->prerequisite) { // Check if all prerequisites are true, and available(no lockout) if ((!resp->prerequisite || client->GetCharacterData()->CheckResponsePrerequisite(resp)) && (!resp->quest || client->GetCharacterData()->CheckQuestAvailable(resp->quest,npc->GetPlayerID()))) { Debug2(LOG_QUESTS,client->GetClientNum(),"Pushing quest response: %d\n",resp->id); // This is a available response that is connected to a available quest haveAvail = true; if (availableResponseList) availableResponseList->Push(resp->id); } } else { Debug2(LOG_QUESTS,client->GetClientNum(),"Pushing non quest response: %d\n",resp->id); // This is a available responses that isn't connected to a quest haveAvail = true; if (availableResponseList) availableResponseList->Push(resp->id); } } } return haveAvail; } int NpcTrigger::GetRandomResponse( const csArray &availableResponseList ) { if (availableResponseList.Length() > 1) return availableResponseList[ psserver->rng->Get( (uint32) availableResponseList.Length() ) ]; else return availableResponseList[0]; } bool NpcTrigger::operator==(NpcTrigger& other) const { return (area==other.area && trigger==other.trigger && priorresponseID==other.priorresponseID && other.min_attitude<=min_attitude && other.max_attitude>=max_attitude); }; bool NpcTrigger::operator<(NpcTrigger& other) const { if (strcmp(area,other.area)<0) return true; if (strcmp(area,other.area)>0) return false; if (strcmp(trigger,other.trigger)<0) return true; if (strcmp(trigger,other.trigger)>0) return false; // is not possible to do this check because when navigating the min/max are different //if (min_attitudeother.min_attitude) // return false; if (priorresponseIDrng->Get(max); } const char *NpcResponse::GetResponse() { if (active_quest == -1) { int i=100; while (i--) { int which = psserver->rng->Get(MAX_RESP); if (i < MAX_RESP) which = i; // Loop through on the last 5 attempts // just to be sure we find one. if (response[which].Length()) return response[which]; } return "5 blank responses!"; } else { return response[active_quest]; } } bool NpcResponse::ParseResponseScript(const char *xmlstr,bool insertBeginning) { if (!xmlstr || strcmp(xmlstr,"")==0) { SayResponseOp *op = new SayResponseOp(false); if (insertBeginning) script.Insert(0,op); else script.Push(op); return true; } int where=0; csRef xml = csPtr(new csTinyDocumentSystem); csRef doc = xml->CreateDocument(); const char* error = doc->Parse( xmlstr ); if ( error ) { Error3("Error: %s . In XML: %s", error, xmlstr ); return false; } csRef root = doc->GetRoot(); if(!root) { Error2("No xml root in %s", xmlstr); return false; } csRef topNode = root->GetNode("response"); if(!topNode) { CPrintf(CON_WARNING,"The npc_response ID %d doesn't have a valid script: %s\n",id,xmlstr); return true; // Return true to add the response, but without a vaild script } csRef iter = topNode->GetNodes(); while ( iter->HasNext() ) { csRef node = iter->Next(); if ( node->GetType() != CS_NODE_ELEMENT ) continue; if ( strcmp( node->GetValue(), "respond" ) == 0 || strcmp( node->GetValue(), "respondpublic" ) == 0) { SayResponseOp *op = new SayResponseOp(strcmp( node->GetValue(), "respondpublic" ) == 0); // true for public, false for private if (!op->Load(node)) { Error2("Could not load operation in script %d. Error in XML",id); delete op; return false; } if (insertBeginning) script.Insert(where++,op); else script.Push(op); } else if ( strcmp( node->GetValue(), "action" ) == 0 ) { ActionResponseOp *op = new ActionResponseOp; if (!op->Load(node)) { Error2("Could not load operation in script %d. Error in XML",id); delete op; return false; } if (insertBeginning) script.Insert(where++,op); else script.Push(op); } else if ( strcmp( node->GetValue(), "verifyquestcompleted") == 0) { VerifyQuestCompletedResponseOp *op = new VerifyQuestCompletedResponseOp; if (!op->Load(node)) { Error2("Could not load operation in script %d. Error in XML",id); delete op; return false; } if (insertBeginning) script.Insert(where++,op); else script.Push(op); } else if ( strcmp( node->GetValue(), "verifyquestassigned") == 0) { VerifyQuestAssignedResponseOp *op = new VerifyQuestAssignedResponseOp; if (!op->Load(node)) { Error2("Could not load operation in script %d. Error in XML",id); delete op; return false; } if (insertBeginning) script.Insert(where++,op); else script.Push(op); } else if ( strcmp( node->GetValue(), "verifyquestnotassigned") == 0) { VerifyQuestNotAssignedResponseOp *op = new VerifyQuestNotAssignedResponseOp; if (!op->Load(node)) { Error2("Could not load operation in script %d. Error in XML",id); delete op; return false; } if (insertBeginning) script.Insert(where++,op); else script.Push(op); } else if ( strcmp( node->GetValue(), "assign" ) == 0 ) { AssignQuestResponseOp *op = new AssignQuestResponseOp; if (!op->Load(node)) { Error2("Could not load operation in script %d. Error in XML",id); delete op; return false; } if (insertBeginning) script.Insert(where++,op); else script.Push(op); if (op->GetTimeoutMsg()) { CheckQuestTimeoutOp *op2 = new CheckQuestTimeoutOp(op); script.Insert(0,op2); where++; } AssignQuestSelectOp *op3 = new AssignQuestSelectOp(op); script.Insert(0,op3); where++; } else if ( strcmp( node->GetValue(), "complete" ) == 0 ) { CompleteQuestResponseOp *op = new CompleteQuestResponseOp; if (!op->Load(node)) { Error2("Could not load operation in script %d. Error in XML",id); delete op; return false; } if (insertBeginning) script.Insert(where++,op); else script.Push(op); } else if ( strcmp( node->GetValue(), "give" ) == 0 ) { GiveItemResponseOp *op = new GiveItemResponseOp; if (!op->Load(node)) { Error2("Could not load operation in script %d. Error in XML",id); delete op; return false; } if (insertBeginning) script.Insert(where++,op); else script.Push(op); } else if ( strcmp( node->GetValue(), "run" ) == 0 ) { RunScriptResponseOp *op = new RunScriptResponseOp; if (!op->Load(node)) { Error2("Could not load operation in script %d. Error in XML",id); delete op; return false; } if (insertBeginning) script.Insert(where++,op); else script.Push(op); } else if ( strcmp( node->GetValue(), "train" ) == 0 ) { TrainResponseOp *op = new TrainResponseOp; if (!op->Load(node)) { Error2("Could not load operation in script %d. Error in XML",id); delete op; return false; } if (insertBeginning) script.Insert(where++,op); else script.Push(op); } else if ( strcmp( node->GetValue(), "guild_award" ) == 0 ) { GuildAwardResponseOp *op = new GuildAwardResponseOp; if (!op->Load(node)) { Error2("Could not load operation in script %d. Error in XML",id); delete op; return false; } if (insertBeginning) script.Insert(where++,op); else script.Push(op); } else if ( strcmp( node->GetValue(), "offer" ) == 0 ) { OfferRewardResponseOp *op = new OfferRewardResponseOp; if (!op->Load(node)) { Error2("Could not load operation in script %d. Error in XML",id); delete op; return false; } if (insertBeginning) script.Insert(where++,op); else script.Push(op); } else if ( strcmp( node->GetValue(), "money" ) == 0 ) { MoneyResponseOp *op = new MoneyResponseOp; if (!op->Load(node)) { Error2("Could not load operation in script %d. Error in XML",id); delete op; return false; } if (insertBeginning) script.Insert(where++,op); else script.Push(op); } else { Error2("undefined operation specified in response script %d.",id); return false; } } return true; } bool NpcResponse::HasPublicResponse() { for (size_t i=0; i(script[i]); if (op && op->saypublic) return true; } return false; } bool NpcResponse::ExecuteScript(Client *client, gemNPC* target) { active_quest = -1; // not used by default for (size_t i=0; iRun(target,client,this)) { csString resp = script[i]->GetResponseScript(); Error3("Error running script in %s operation for client %s.", resp.GetData(),client->GetName() ); return false; } } return true; } csString NpcResponse::GetResponseScript() { csString respScript = ""; for (size_t i=0; i",script[i]->GetResponseScript().GetData()); respScript.Append(op); } respScript.Append(""); return respScript; } bool NpcResponse::ParsePrerequisiteScript(const char *xmlstr,bool insertBeginning) { if (!xmlstr || strcmp(xmlstr,"")==0) { return false; } psQuestPrereqOp * op; LoadPrerequisiteXML(op,xmlstr); if (op) AddPrerequisite(op,insertBeginning); return true; } bool NpcResponse::AddPrerequisite(psQuestPrereqOp * op, bool insertBeginning) { // Make sure that the first op is an AND list if there are an // prerequisite from before. if (prerequisite) { // Check if first op is an and list. psQuestPrereqOpAnd * list = dynamic_cast(prerequisite); if (list == NULL) { // If not insert an and list. list = new psQuestPrereqOpAnd(); list->Push(prerequisite); prerequisite = list; } if (insertBeginning) list->Insert(0,op); else list->Push(op); } else { // No prerequisite from before so just set this. prerequisite = op; } return true; } bool NpcResponse::CheckPrerequisite(psCharacter * character) { if (prerequisite) return prerequisite->Check(character); return true; // No prerequisite so its ok to do this quest } //////////////////////////////// /////////// Training /////////// //////////////////////////////// /** Checks if training is possible (enough money to pay etc.) * If not, tells the user why */ bool CheckTraining(gemNPC *who, Client *target, psSkillInfo* skill) { if (!who->GetCharacterData()->IsTrainer()) { CPrintf(CON_ERROR, "%s isn't a trainer, but have train in dialog!!",who->GetCharacterData()->GetCharName()); return false; } psCharacter * character = target->GetCharacterData(); CPrintf(CON_DEBUG, " PP available: %d\n", character->GetProgressionPoints() ); // Test for progression points if (character->GetProgressionPoints() <= 0) { csString str; csString downcase = skill->name.Downcase(); str.Format("You don't have any progression points to be trained in %s, Sorry",downcase.GetData()); who->Say(str,target); return false; } // Test for money if (skill->price > character->Money()) { csString str; csString downcase = skill->name.Downcase(); str.Format("Sorry, but I see that you don't have enough money to be trained in %s",downcase.GetData()); who->Say(str,target); return false; } if ( !character->CanTrain( skill->id ) ) { csString str; csString downcase = skill->name.Downcase(); str.Format("You can't train %s higher yet",downcase.GetData()); who->Say(str,target); return false; } return true; } /** This class asks user to confirm that he really wants to pay for training */ class TrainingConfirm : public PendingQuestion { public: TrainingConfirm(const csString & question, gemNPC *who, Client *target, psSkillInfo* skill) : PendingQuestion(target->GetClientNum(), question, psQuestionMessage::generalConfirm) { this->who = who; this->target = target; this->skill = skill; } virtual void HandleAnswer(const csString & answer) { if (answer != "yes") return; // We better check again, if everything is still ok if (!CheckTraining(who, target, skill)) return; psCharacter * character = target->GetCharacterData(); character->UseProgressionPoints(1); character->SetMoney(character->Money()-skill->price); character->Train(skill->id,1); csString downcase = skill->name.Downcase(); psserver->SendSystemInfo(target->GetClientNum(), "You've received some %s training", downcase.GetData()); } protected: gemNPC *who; Client *target; psSkillInfo* skill; }; //////////////////////////////////////////////////////////////////// // Responses bool SayResponseOp::Load(iDocumentNode *node) { return true; } csString SayResponseOp::GetResponseScript() { psString resp; resp = GetName(); return resp; } bool SayResponseOp::Run(gemNPC *who, Client *target,NpcResponse *owner) { psString response = owner->GetResponse(); who->GetNPCDialogPtr()->SubstituteKeywords(target,response); who->Say(response,target,saypublic); return true; } bool ActionResponseOp::Load(iDocumentNode *node) { anim = node->GetAttributeValue("anim"); return true; } csString ActionResponseOp::GetResponseScript() { psString resp; resp = GetName(); resp.AppendFmt(" anim=\"%s\"",anim.GetData()); return resp; } bool ActionResponseOp::Run(gemNPC *who, Client *target,NpcResponse *owner) { who->SetAction(anim); return true; } bool VerifyQuestCompletedResponseOp::Load(iDocumentNode *node) { quest = CacheManager::GetSingleton().GetQuestByName( node->GetAttributeValue("quest") ); error_msg = node->GetAttributeValue("error_msg"); if (error_msg=="(null)") error_msg=""; if (!quest) { Error2("Quest %s was not found in VerifyQuestCompleted script op! You must have at least one!",node->GetAttributeValue("quest") ); return false; } return true; } csString VerifyQuestCompletedResponseOp::GetResponseScript() { psString resp = GetName(); resp.AppendFmt(" quest=\"%s\"",quest->GetName()); if (error_msg != "") { resp.AppendFmt(" error_msg=\"%s\"",error_msg.GetDataSafe()); } return resp; } bool VerifyQuestCompletedResponseOp::Run(gemNPC *who, Client *target,NpcResponse *owner) { bool avail = target->GetCharacterData()->CheckQuestCompleted(quest); if (!avail) { who->GetNPCDialogPtr()->SubstituteKeywords(target,error_msg); who->Say(error_msg,target); return false; } return true; // and don't say anything } VerifyQuestAssignedResponseOp::VerifyQuestAssignedResponseOp(int quest_id) { name="verifyquestassigned"; quest = CacheManager::GetSingleton().GetQuestByID(quest_id); if (!quest) { Error2("Quest %d was not found in VerifyQuestAssigned script op!",quest_id); } } bool VerifyQuestAssignedResponseOp::Load(iDocumentNode *node) { quest = CacheManager::GetSingleton().GetQuestByName( node->GetAttributeValue("quest") ); error_msg = node->GetAttributeValue("error_msg"); if (error_msg=="(null)") error_msg=""; if (!quest && node->GetAttributeValue("quest") ) { Error2("Quest <%s> not found!",node->GetAttributeValue("quest")); } else if (!quest) { Error1("Quest name must be specified in VerifyQuestAssigned script op!"); return false; } return true; } csString VerifyQuestAssignedResponseOp::GetResponseScript() { psString resp = GetName(); resp.AppendFmt(" quest=\"%s\"",quest->GetName()); if (error_msg != "") { resp.AppendFmt(" error_msg=\"%s\"",error_msg.GetDataSafe()); } return resp; } bool VerifyQuestAssignedResponseOp::Run(gemNPC *who, Client *target,NpcResponse *owner) { bool avail = target->GetCharacterData()->CheckQuestAssigned(quest); if (!avail) { if (error_msg.IsEmpty() || error_msg.Length() == 0) { error_msg = "I don't know what you are talking about."; // TODO: use the standard error response for that NPC } who->GetNPCDialogPtr()->SubstituteKeywords(target,error_msg); who->Say(error_msg,target); return false; } return true; // and don't say anything } VerifyQuestNotAssignedResponseOp::VerifyQuestNotAssignedResponseOp(int quest_id) { name="verifyquestnotassigned"; quest = CacheManager::GetSingleton().GetQuestByID(quest_id); if (!quest) { Error2("Quest %d was not found in VerifyQuestNotAssigned script op!",quest_id); } } bool VerifyQuestNotAssignedResponseOp::Load(iDocumentNode *node) { quest = CacheManager::GetSingleton().GetQuestByName( node->GetAttributeValue("quest") ); error_msg = node->GetAttributeValue("error_msg"); if (error_msg=="(null)") error_msg=""; if (!quest && node->GetAttributeValue("quest") ) { Error2("Quest <%s> not found!",node->GetAttributeValue("quest")); } else if (!quest) { Error1("Quest name must be specified in VerifyQuestNotAssigned script op!"); return false; } return true; } csString VerifyQuestNotAssignedResponseOp::GetResponseScript() { psString resp = GetName(); resp.AppendFmt(" quest=\"%s\"",quest->GetName()); if (error_msg != "") { resp.AppendFmt(" error_msg=\"%s\"",error_msg.GetDataSafe()); } return resp; } bool VerifyQuestNotAssignedResponseOp::Run(gemNPC *who, Client *target,NpcResponse *owner) { bool avail = target->GetCharacterData()->CheckQuestAssigned(quest); if (avail) { if (error_msg.IsEmpty() || error_msg.Length() == 0) { error_msg = "I don't know what you are talking about."; // TODO: use the standard error response for that NPC } who->GetNPCDialogPtr()->SubstituteKeywords(target,error_msg); who->Say(error_msg,target); return false; } return true; // and don't say anything } bool AssignQuestResponseOp::Load(iDocumentNode *node) { quest[0] = CacheManager::GetSingleton().GetQuestByName( node->GetAttributeValue("q1") ); quest[1] = CacheManager::GetSingleton().GetQuestByName( node->GetAttributeValue("q2") ); quest[2] = CacheManager::GetSingleton().GetQuestByName( node->GetAttributeValue("q3") ); quest[3] = CacheManager::GetSingleton().GetQuestByName( node->GetAttributeValue("q4") ); quest[4] = CacheManager::GetSingleton().GetQuestByName( node->GetAttributeValue("q5") ); if (!quest[0]) { Error2("Quest %s was not found in Assign Quest script op! You must have at least one!",node->GetAttributeValue("q1") ); return false; } if (node->GetAttributeValue("timeout_msg")) { timeout_msg = node->GetAttributeValue("timeout_msg"); if (timeout_msg=="(null)") timeout_msg=""; } if (quest[4]) num_quests = 5; else if (quest[3]) num_quests = 4; else if (quest[2]) num_quests = 3; else if (quest[1]) num_quests = 2; else if (quest[0]) num_quests = 1; return true; } csString AssignQuestResponseOp::GetResponseScript() { psString resp = GetName(); for (int n = 0; n < 5; n++) { if (quest[n]) { resp.AppendFmt(" q%d=\"%s\"",n+1,quest[n]->GetName()); } } if (timeout_msg != "") { resp.AppendFmt(" timeout_msg=\"%s\"",timeout_msg.GetDataSafe()); } return resp; } bool AssignQuestResponseOp::Run(gemNPC *who, Client *target,NpcResponse *owner) { if (owner->GetActiveQuest() == -1) { owner->SetActiveQuest(GetMaxQuests()); Debug4(LOG_QUESTS, target->GetClientNum(),"Selected quest %d out of %d for %s",owner->GetActiveQuest()+1, GetMaxQuests(),target->GetCharacterData()->GetCharName()); } if (target->GetCharacterData()->CheckQuestAssigned(quest[owner->GetActiveQuest()])) { Debug3(LOG_QUESTS, target->GetClientNum(),"Quest(%d) is already assigned for %s",owner->GetActiveQuest()+1, target->GetCharacterData()->GetCharName()); return false; } psserver->questmanager->Assign(quest[owner->GetActiveQuest()],target,who); return true; } bool AssignQuestSelectOp::Run(gemNPC *who, Client *target,NpcResponse *owner) { if (owner->GetActiveQuest() == -1) { owner->SetActiveQuest(quest_op->GetMaxQuests()); Debug4(LOG_QUESTS, target->GetClientNum(), "Selected quest %d out of %d for %s",owner->GetActiveQuest()+1, quest_op->GetMaxQuests(),target->GetCharacterData()->GetCharName()); } if (target->GetCharacterData()->CheckQuestAssigned(quest_op->GetQuest(owner->GetActiveQuest()))) { Debug3(LOG_QUESTS, target->GetClientNum(), "Quest(%d) is already assignd for %s",quest_op->GetQuest(owner->GetActiveQuest())->GetID(), target->GetCharacterData()->GetCharName()); return false; } return true; } csString AssignQuestSelectOp::GetResponseScript() { psString resp = GetName(); resp.AppendFmt(" max_quest=\"%d\"", quest_op->GetMaxQuests()); return resp; } bool CheckQuestTimeoutOp::Run(gemNPC *who, Client *target,NpcResponse *owner) { if (owner->GetActiveQuest() == -1) { owner->SetActiveQuest(quest_op->GetMaxQuests()); Debug4(LOG_QUESTS, target->GetClientNum(), "Selected quest %d out of %d for %s",owner->GetActiveQuest()+1, quest_op->GetMaxQuests(),target->GetCharacterData()->GetCharName()); } bool avail = target->GetCharacterData()->CheckQuestAvailable(quest_op->GetQuest(owner->GetActiveQuest()), who->GetPlayerID() ); if (!avail) { psString timeOutMsg = quest_op->GetTimeoutMsg(); who->GetNPCDialogPtr()->SubstituteKeywords(target,timeOutMsg); who->Say(timeOutMsg,target); return false; } return true; } csString CheckQuestTimeoutOp::GetResponseScript() { psString resp = GetName(); return resp; } bool CompleteQuestResponseOp::Load(iDocumentNode *node) { quest = CacheManager::GetSingleton().GetQuestByName( node->GetAttributeValue("quest_id") ); if (!quest) { Error2("Quest '%s' was not found in Complete Quest script op!",node->GetAttributeValue("quest_id") ); return false; } error_msg = node->GetAttributeValue("error_msg"); if (error_msg=="(null)") error_msg=""; return true; } csString CompleteQuestResponseOp::GetResponseScript() { psString resp = GetName(); resp.AppendFmt(" quest_id=\"%s\"", quest->GetName()); if (error_msg != "") { resp.AppendFmt(" error_msg=\"%s\"",error_msg.GetDataSafe()); } return resp; } bool CompleteQuestResponseOp::Run(gemNPC *who, Client *target,NpcResponse *owner) { if (!psserver->questmanager->Complete(quest,target)) { who->GetNPCDialogPtr()->SubstituteKeywords(target,error_msg); who->Say(error_msg,target); return false; } else return true; } bool GiveItemResponseOp::Load(iDocumentNode *node) { itemstat = CacheManager::GetSingleton().GetBasicItemStatsByID( node->GetAttributeValueAsInt("item") ); if (!itemstat) itemstat = CacheManager::GetSingleton().GetBasicItemStatsByName(node->GetAttributeValue("item") ); if (!itemstat) { Error2("ItemStat '%s' was not found in GiveItem script op!",node->GetAttributeValue("item") ); return false; } // Check for count attribute if (node->GetAttribute("count")) { count = node->GetAttributeValueAsInt("count"); if (count < 1) { Error1("Try to give negative or zero count in GiveItem script op!"); count = 1; } if (count > 1 && !itemstat->GetIsStackable()) { Error2("ItemStat '%s' isn't stackable in GiveItem script op!",node->GetAttributeValue("item") ); return false; } } return true; } csString GiveItemResponseOp::GetResponseScript() { psString resp = GetName(); resp.AppendFmt(" item_id=\"%s\"",itemstat->GetName()); if (count != 1) { resp.AppendFmt(" count=\"%d\"",count); } return resp; } bool GiveItemResponseOp::Run(gemNPC *who, Client *target,NpcResponse *owner) { psCharacter *looter = target->GetActor()->GetCharacterData(); if (!looter) return false; psItem *item = itemstat->InstantiateBasicItem(false); // Not a transient item if (!item) { Error3("Couldn't give item %d to player %s!\n",itemstat,target->GetName()); return false; } item->SetStackCount(count); csString ItemName = item->GetQuantityName(); if ( !looter->MoveToInventory(item) ) { Debug5(LOG_ANY,target->GetClientNum(),"GiveItemResponseOp::Run() Could not give item of stat %u (%s)" " to character %u [%s])", itemstat->GetUID(),itemstat->GetName(), looter->GetCharacterID(),looter->GetCharName()); CacheManager::GetSingleton().RemoveInstance(item); return false; } if (item) { item->SetLoaded(); // Item is fully created item->Save(); // First save } psSystemMessage given(target->GetClientNum(),MSG_INFO,"%s has received %s!", target->GetName(), ItemName.GetData() ); target->GetActor()->SendGroupMessage(given.msg); return true; } bool RunScriptResponseOp::Load(iDocumentNode *node) { scriptname = node->GetAttributeValue("scr"); if (!scriptname.Length()) { Error1("Progression script name was not specified in Run script op!"); return false; } p0 = node->GetAttributeValueAsFloat("param0"); p1 = node->GetAttributeValueAsFloat("param1"); p2 = node->GetAttributeValueAsFloat("param2"); return true; } csString RunScriptResponseOp::GetResponseScript() { psString resp = GetName(); resp.AppendFmt(" scr=\"%s\"",scriptname.GetData()); if (p0 != 0) resp.AppendFmt(" param0=\"%f\"",p0); if (p1 != 0) resp.AppendFmt(" param1=\"%f\"",p1); if (p2 != 0) resp.AppendFmt(" param2=\"%f\"",p2); return resp; } bool RunScriptResponseOp::Run(gemNPC *who, Client *target,NpcResponse *owner) { ProgressionEvent *event; if ((scriptname.GetDataSafe())[0] == '<') { event = psserver->GetProgressionManager()->CreateEvent(who->GetName(),scriptname); if (!event) { Error2("Progression script '%s' could not be created in the Progression Manager!",scriptname.GetData()); return true; } } else { event = psserver->GetProgressionManager()->FindEvent(scriptname); if (!event) { Error2("Progression script '%s' was not found in the Progression Manager!",scriptname.GetData()); return true; } } MathScriptVar *var; var = event->FindOrCreateVariable("Param0"); if (var) var->SetValue(p0); var = event->FindOrCreateVariable("Param1"); if (var) var->SetValue(p1); var = event->FindOrCreateVariable("Param2"); if (var) var->SetValue(p2); event->Run(target->GetActor(),who,psserver->GetProgressionManager() ); return true; } bool TrainResponseOp::Load(iDocumentNode *node) { if (node->GetAttributeValue("skill")) skill = CacheManager::GetSingleton().GetSkillByName(node->GetAttributeValue("skill")); else skill = NULL; if(!skill) { CPrintf(CON_ERROR, "Couldn't find skill '%s'",node->GetAttributeValue("skill")? node->GetAttributeValue("skill"):"Not Specified"); return false; } return true; } csString TrainResponseOp::GetResponseScript() { psString resp = GetName(); return resp; } bool TrainResponseOp::Run(gemNPC *who, Client *target,NpcResponse *owner) { if (CheckTraining(who, target, skill)) { csString question; question.Format("Do you really want to train %s ? You will be charged %d trias.", skill->name.GetData(), skill->price.GetTotal()); psserver->questionmanager->SendQuestion( new TrainingConfirm(question, who, target, skill)); } return true; } bool GuildAwardResponseOp::Load(iDocumentNode *node) { karma = node->GetAttributeValueAsInt("karma"); return true; } csString GuildAwardResponseOp::GetResponseScript() { psString resp = GetName(); return resp; } bool GuildAwardResponseOp::Run(gemNPC *who, Client *target,NpcResponse *owner) { psGuildInfo * guild = CacheManager::GetSingleton().FindGuild(target->GetGuildID()); if (!guild) { CPrintf(CON_ERROR, "Couldn't find guild (%d). Guild karma points not added\n",target->GetGuildID()); return false; } guild->karma_points += karma; db->Command("UPDATE guilds SET karma_points = '%d' WHERE id = '%d'",guild->karma_points,guild->id); // TODO: Notify player and guild of what happened here. return true; } bool OfferRewardResponseOp::Load(iDocumentNode *node) { // // // // csRef iter = node->GetNodes(); // for each item in the offer list while (iter->HasNext()) { csRef node = iter->Next(); psItemStats* itemstat; // get item uint32 itemID = (uint32)node->GetAttributeValueAsInt("id"); if (itemID) itemstat = CacheManager::GetSingleton().GetBasicItemStatsByID(itemID); else itemstat = CacheManager::GetSingleton().GetBasicItemStatsByName(node->GetAttributeValue("name")); // make sure that the item exists if (!itemstat) { Error3("ItemStat #%u/%s was not found in OfferReward script op!",itemID,node->GetAttributeValue("name")); return false; } // add this item to the list offer.Push(itemstat); } return true; } csString OfferRewardResponseOp::GetResponseScript() { psString resp = GetName(); for (size_t n = 0; n < offer.Length();n++) { resp.AppendFmt(">GetName()); } resp.AppendFmt(">questmanager->OfferRewardsToPlayer(target, offer); return true; } bool MoneyResponseOp::Load(iDocumentNode *node) { if (node->GetAttributeValue("value")) money = psMoney(node->GetAttributeValue("value")); else money = psMoney(); if(money.GetTotal()==0) { CPrintf(CON_ERROR, "Couldn't load money '%s' or money = 0",node->GetAttributeValue("value")); return false; } return true; } csString MoneyResponseOp::GetResponseScript() { psString resp; resp = GetName(); resp.AppendFmt(" value=\"%s\"",money.ToString().GetData()); return resp; } bool MoneyResponseOp::Run(gemNPC *who, Client *target,NpcResponse *owner) { psCharacter * character = target->GetCharacterData(); character->SetMoney(character->Money()+money); psserver->SendSystemInfo(target->GetClientNum(), "You've received %s",money.ToUserString().GetData()); return true; }