/* * progressionmanager.cpp * * 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 #include #include #include #include "globals.h" #include "clients.h" #include "usermanager.h" #include "rpgrules/factions.h" #include "util/eventmanager.h" #include "progressionmanager.h" #include "entitymanager.h" #include "util/log.h" #include "util/serverconsole.h" #include "util/mathscript.h" #include "psserver.h" #include "cachemanager.h" #include "events.h" #include "bulkobjects/pscharacterloader.h" #include "bulkobjects/pscharacter.h" #include "spellmanager.h" #include "combatmanager.h" #include "weathermanager.h" #include "playergroup.h" #include "gem.h" #include "util/psxmlparser.h" #include "bulkobjects/psitem.h" #include "bulkobjects/pstrainerinfo.h" #include "bulkobjects/psraceinfo.h" #include "npcmanager.h" #include "net/messages.h" #include "net/npcmessages.h" #include "util/skillcache.h" #include "psbehave/psworld.h" /*-------------------------------------------------------------*/ class ScriptOp; class psScriptGameEvent : public psGEMEvent { public: psScriptGameEvent(csTicks offsetticks, ProgressionManager *mgr, ProgressionEvent * script, gemActor *actor, gemObject *target, bool persistent); virtual void DeleteObjectCallback(iDeleteNotificationObject * object); void Trigger(); protected: ProgressionManager *mgr; ProgressionEvent * script; bool persistent; csWeakRef actor; csWeakRef target; bool disconnected; }; /*-------------------------------------------------------------*/ class ProgressionOperation { protected: MathScript *value_script; MathScriptVar *valuevar,*targetvar,*actorvar; csString script_text,delay_text,result_var_name; ProgressionEvent *my_script; MathScript *delay_script; MathScriptVar *delayvar,*delaytargetvar,*delayactorvar; float result; bool LoadValue(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script); float GetValue(gemActor * actor, gemObject *target, ProgressionManager *mgr); bool LoadDelay(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script); float GetDelay(gemActor * actor, gemObject *target, ProgressionManager *mgr); public: csString * eventName; ProgressionOperation() { my_script=NULL; valuevar=targetvar=actorvar=NULL; value_script=delay_script=NULL; result = 0.0F;} virtual ~ProgressionOperation() {if (value_script) delete value_script; if (delay_script) delete delay_script; } virtual bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse)=0; virtual bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script)=0; virtual csString ToString()=0; MathScript *GetMathScript() { return value_script; } virtual void LoadVariables(csArray & variables); virtual float GetResult() { return result; }; }; void ProgressionOperation::LoadVariables(csArray & variables) { MathScriptVar *var; size_t i; for (i=0; iGetOrCreateVar(variables[i]->name); var->SetValue(variables[i]->GetValue() ); } if (delay_script) { var = delay_script->GetOrCreateVar(variables[i]->name); var->SetValue(variables[i]->GetValue() ); } } } bool ProgressionOperation::LoadValue(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *prg_script) { // check if value is valid if (node->GetAttributeValue("value")==NULL || node->GetAttributeValue("value")=="") { Error1("Script MUST contain an attribute called \"Value\""); return false; } csString script("Value = "); script.Append(node->GetAttributeValue("value")); script_text = node->GetAttributeValue("value"); // save for persisting later value_script = new MathScript(prg_script->name.GetData(),script); valuevar = value_script->GetVar("Value"); // always required and supplied targetvar = value_script->GetOrCreateVar("Target"); actorvar = value_script->GetOrCreateVar("Actor"); my_script = prg_script; const char *varname = node->GetAttributeValue("save"); if (varname) { if (!prg_script->FindVariable(varname)) { MathScriptVar *pv = new MathScriptVar; pv->name = varname; pv->SetValue(0); prg_script->AddVariable(pv); } result_var_name=varname; } return true; }; bool ProgressionOperation::LoadDelay(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *prg_script) { // check if value is valid if (node->GetAttributeValue("delay") != NULL) delay_text = node->GetAttributeValue("delay"); else delay_text = "0"; csString script("Delay = "); script.Append(delay_text); delay_script = new MathScript(prg_script->name.GetData(),script); delayvar = delay_script->GetVar("Delay"); // always required and supplied delaytargetvar = delay_script->GetOrCreateVar("Target"); delayactorvar = delay_script->GetOrCreateVar("Actor"); return true; }; float ProgressionOperation::GetValue(gemActor * actor, gemObject *target, ProgressionManager *mgr) { if (!value_script) { Error2("Invalid value script in Progression Event '%s'.",this->eventName->GetData() ); return 0.0; } targetvar->SetObject(target ? target->GetCharacterData() : NULL); actorvar->SetObject(actor ? actor->GetCharacterData() : NULL ); value_script->Execute(); if (result_var_name.Length()) { MathScriptVar *pv = my_script->FindVariable(result_var_name); pv->SetValue(valuevar->GetValue()); } return valuevar->GetValue(); } float ProgressionOperation::GetDelay(gemActor * actor, gemObject *target, ProgressionManager *mgr) { if (!delay_script) { Error2("Invalid delay script in Progression Event '%s'.",this->eventName->GetData() ); return 0.0; } delaytargetvar->SetObject(target ? target->GetCharacterData() : NULL); delayactorvar->SetObject(actor ? actor->GetCharacterData() : NULL); delay_script->Execute(); return delayvar->GetValue(); } /*-------------------------------------------------------------*/ /** Used to create a change in character traits. * This is used to change a character's appearance live in game. * It takes the trait ID number and sends a broadcast out to all * the players in range about the change. * * A Trait op is simply * * On the Run it checks to make sure the selected trait is allowed * for that race and rejects with an error message if it is not. */ class TraitChangeOp : public ProgressionOperation { protected: int traitID; public: TraitChangeOp() : ProgressionOperation() {} virtual ~TraitChangeOp() {} bool Load( iDocumentNode* node, ProgressionManager* mgr, ProgressionEvent* script ) { traitID = node->GetAttributeValueAsInt( "value" ); return LoadValue( node, mgr, script ); } virtual csString ToString() { csString xml; xml.Format("", traitID ); return xml; } bool Run( gemActor * actor, gemObject* target, ProgressionManager *mgr, bool inverse ) { // Remove this when adding support for the inverse operation if (inverse) return true; // Cannot be run if there is no actor if (!actor) { Error2("Error: ProgressionEvent(%s) TraitChangeOp needs an actor\n",eventName->GetData()); return true; } int clientID = actor->GetClientID(); psCharacter *data = actor->GetCharacterData(); psRaceInfo* raceInfo = data->GetRaceInfo(); psTrait* trait = CacheManager::GetSingleton().GetTraitByID( traitID ); // Validate that the selected trait can be applied to the player's race. if ( trait->raceID != raceInfo->uid ) { psserver->SendSystemInfo(clientID,"Doesn't work on you, try a better brand."); return false; } data->SetTraitForLocation( trait->location, trait ); // Send out updated information to all clients on prox. list csString str( "" ); str.Append(trait->ToXML() ); str.Append(""); psTraitChangeMessage message( (uint32_t)clientID, (uint32_t)actor->GetEntity()->GetID(), str ); message.Multicast( actor->GetMulticastClients(), 0, PROX_LIST_ANY_RANGE ); return true; } }; /** * Adjust the experience of the target. */ class ExperienceOp : public ProgressionOperation { protected: csString type; public: ExperienceOp() : ProgressionOperation() { }; virtual ~ExperienceOp() {}; bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script) { type = node->GetAttributeValue("type"); return LoadValue(node,mgr,script); } virtual csString ToString() { csString xml; xml.Format("",type.GetData(),script_text.GetData() ); return xml; } float AllocateKillDamage(gemObject *target, ProgressionManager *mgr,int exp) { // Convert to gemActor gemActor* targetAct = target->GetActorPtr(); if(!targetAct) return 0.0f; csArray attackers; unsigned int timeOfDeath = csGetTicks(); // TODO: Should be recorded on death // in the targetAct. // Last timestamp, used for breaking the loop when > 10 secs had gone unsigned int lastTimestamp = 0; float totalDamage = 0; // The denominator for the percentages // First build list of attackers and determine how far to go back and what total dmg was. int i; for (i=(int)targetAct->GetDamageHistoryCount(); i>0; i--) { DamageHistory* history = targetAct->GetDamageHistory(i-1); // 10 secs passed if(lastTimestamp - history->timestamp > 15000 && lastTimestamp != 0) { Debug1(LOG_COMBAT, 0,"15 secs passed between hits, breaking loop\n"); break; } lastTimestamp = history->timestamp; // Special check for DoT adjustments if the target died before the DoT expired if(history->damageRate != 0) { csTicks duration = -(int)(history->damage/history->damageRate); if (duration > timeOfDeath - history->timestamp) // Since damageRate should always be negative: history->damage = -(history->damageRate * (timeOfDeath - history->timestamp)); } totalDamage += history->damage; bool found = false; if (!history->attacker_ref.IsValid()) continue; // This attacker has disconnected since he did this damage. // Have we already added that player? for(size_t x=0;x < attackers.Length();x++) { if(attackers[x] == history->attacker_ref) { found = true; break; } } // New player, add to list if(!found) { attackers.Push(dynamic_cast((gemObject *) history->attacker_ref)); // This is ok because it is ONLY used in this function } } int lastHistory = i; for(size_t i=0;i < attackers.Length();i++) { gemActor* attacker = attackers[i]; if(!attacker) continue; // should not happen with new safe ref system. float dmgMade = 0; float mod = 0; for (int x = (int)targetAct->GetDamageHistoryCount();x > lastHistory; x--) { DamageHistory* history = targetAct->GetDamageHistory(x-1); if(history->attacker_ref == attacker) { dmgMade += history->damage; } } if (!totalDamage) { Error2("%s was found to have zero totalDamage in damagehistory!",targetAct->GetName() ); continue; } // Use the latest HP (needs to be redesigned when NPCs can cast heal spells on eachoter) mod = dmgMade / totalDamage; // Get a 0.something value or 1 if we did all dmg if (mod > 1.0) mod = 1.0; int final = int(exp * mod); psserver->SendSystemInfo(attacker->GetClientID(),"You gained %d experience points.",final); if (int pp = attacker->GetCharacterData()->AddExperiencePoints(final)) { psserver->SendSystemInfo(attacker->GetClientID(),"You gained %d progression points.",pp); } } targetAct->ClearDamageHistory(); return 0.0f; } bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse) { // Remove this when adding support for the inverse operation if (inverse) return true; int exp = (int)GetValue(actor,target,mgr); // allocate_dmg used to be in the if statement, but it should rightfully be the default // since we almost always want it and it is almost never in the data :)-- KWF if (type == "allocate_last") { if ( actor ) actor->GetCharacterData()->AddExperiencePoints(exp); } else { result = AllocateKillDamage(target,mgr,exp); } return true; } }; /*-------------------------------------------------------------*/ /** * Adjust the faction of the actor relative to the target. */ class FactionOp : public ProgressionOperation { protected: Faction *faction; // True if its the actors stat that should be used. bool aimIsActor; public: FactionOp() : ProgressionOperation() { }; virtual ~FactionOp() {}; bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script) { if (node->GetAttributeValue("aim")) { aimIsActor = !strcasecmp(node->GetAttributeValue("aim"),"actor"); } else aimIsActor = true; // Default faction = mgr->FindFaction(node->GetAttributeValue("name") ); if (!faction) { Error2("Error: FactionOp faction(%s) not found\n",node->GetAttributeValue("name")); return false; } return LoadValue(node,mgr,script); } virtual csString ToString() { psString xml; xml.Format("name); xml.AppendFmt("name=\"%s\" value=\"%s\" />", escpxml.GetData(), script_text.GetData()); return xml; } bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse) { // Remove this when adding support for the inverse operation if (inverse) return true; if (!aimIsActor && !target) { Error2("Error: ProgressionEvent(%s) FactionOp need a target\n",eventName->GetData()); return true; } if (aimIsActor && !actor) { Error2("Error: ProgressionEvent(%s) FactionOp need an actor\n",eventName->GetData()); return true; } psCharacter * character; if (aimIsActor) character = actor->GetCharacterData(); else character = target->GetCharacterData(); if (!character) { Error2("Error: ProgressionEvent(%s) FactionOp aim %s isn't a character\n",eventName->GetData()); return true; } int delta = (int)GetValue(actor,target,mgr); character->GetActor()->GetFactions()->UpdateFactionStanding(faction->id,delta); if (delta > 0) { psserver->SendSystemInfo(character->GetActor()->GetClientID(),"Your faction with %s has improved.",faction->name.GetData()); } else { psserver->SendSystemInfo(character->GetActor()->GetClientID(),"Your faction with %s has worsened.",faction->name.GetData()); } return true; } }; /*-------------------------------------------------------------*/ /** * Adjust the specified stat of the target. */ class StatsOp : public ProgressionOperation { public: typedef enum {HP,MANA,PSTAMINA,MSTAMINA,STR,AGI,END,INT,WIL,CHA,CON,STA,MSTA,ATTACK,DEFENSE,HPRATE} Stat_t; typedef enum {adjust_set, adjust_add, adjust_mul, adjust_pct} adjust_t; static const char * statToString[]; static const PSITEMSTATS_STAT statToAttrib[]; StatsOp(Stat_t state):ProgressionOperation() {stat=state;}; virtual ~StatsOp() {}; bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script) { adjust = adjust_add; if (node->GetAttributeValue("adjust")) { csString adjustStr = node->GetAttributeValue("adjust"); if (adjustStr == "set") adjust = adjust_set; else if (adjustStr == "mul") adjust = adjust_mul; else if (adjustStr == "pct") adjust = adjust_pct; } if (node->GetAttributeValue("aim")) aimIsActor = !strcasecmp(node->GetAttributeValue("aim"),"actor"); else aimIsActor = true; // Default base = (node->GetAttributeValue("base") && !strcmp(node->GetAttributeValue("base"), "yes")); undoMsg = node->GetAttributeValue("undomsg"); return LoadValue(node,mgr,script) && LoadDelay(node,mgr,script); } virtual csString ToString() { psString xml; psString adjustStr, delayStr; xml.Format("<%s ",statToString[stat]); if (!aimIsActor) xml.AppendFmt("aim=\"target\" "); switch (adjust) { case adjust_set: adjustStr = "set"; break; case adjust_add: adjustStr = "add"; break; case adjust_mul: adjustStr = "mul"; break; case adjust_pct: adjustStr = "pct"; break; } xml.AppendFmt("adjust=\"%s\" ", adjustStr.GetData()); if (base) xml.AppendFmt("base=\"yes\" "); xml.AppendFmt("delay=\"%s\" ", delay_text.GetData()); if (undoMsg.GetData() != NULL) xml.AppendFmt("undomsg=\"%s\" ", undoMsg.GetData()); xml.AppendFmt("value=\"%s\" />",script_text.GetData() ); return xml; } float GetCurrentValue(psCharacter * targetChar, bool findBase = false) { switch (stat) { case HP: if (base) return targetChar->GetHitPointsMax(); else return targetChar->GetHP(); break; case MANA: if (base) return targetChar->GetManaMax(); else return targetChar->GetMana(); break; case PSTAMINA: if (base) return targetChar->GetStaminaMax(true); else return targetChar->GetStamina(true); break; case MSTAMINA: if (base) return targetChar->GetStaminaMax(false); else return targetChar->GetStamina(false); break; case STR: case AGI: case END: case INT: case WIL: case CHA: case CON: case STA: if (base || findBase) return targetChar->GetAttributes()->GetStat(statToAttrib[stat], false); else return targetChar->GetAttributes()->GetStat(statToAttrib[stat], true); break; case ATTACK: return targetChar->GetAttackValueModifier(); case DEFENSE: return targetChar->GetDefenseValueModifier(); case HPRATE: return targetChar->AdjustHitPointsRate(0.0); default: return 0.0; } return 0.0; } float CalcNewValue(float oldValue, float adjustValue, bool inverse, float baseValue) { switch (adjust) { case adjust_set: return adjustValue; case adjust_add: if (inverse) return oldValue - adjustValue; else return oldValue + adjustValue; case adjust_mul: if (inverse) return oldValue / adjustValue; else return oldValue * adjustValue; case adjust_pct: { float pct = baseValue * adjustValue/100.0f; if ( inverse ) return oldValue-pct; else return oldValue+pct; } } return 0.0; } void SetValue(gemActor * actor, psCharacter * targetChar, float oldValue, float newValue, int duration) { switch (stat) { case HP: if (base) targetChar->SetHitPointsMax(newValue); else { if (newValue >= oldValue) targetChar->SetHitPoints(newValue); else targetChar->GetActor()->DoDamage(actor, targetChar->GetHP()-newValue); } break; case MANA: if (base) targetChar->SetManaMax(newValue); else targetChar->SetMana(newValue); break; case PSTAMINA: if (base) targetChar->SetStaminaMax(newValue,true); else targetChar->SetStamina(newValue,true); break; case MSTAMINA: if (base) targetChar->SetStaminaMax(newValue,false); else targetChar->SetStamina(newValue,false); break; case STR: case AGI: case END: case INT: case WIL: case CHA: case CON: case STA: if (base) targetChar->GetAttributes()->SetStat(statToAttrib[stat],(unsigned)newValue); else targetChar->GetAttributes()->BuffStat(statToAttrib[stat],unsigned(newValue-oldValue)); break; case ATTACK: targetChar->AdjustAttackValueModifier(newValue/oldValue); break; case DEFENSE: targetChar->AdjustDefenseValueModifier(newValue/oldValue); break; case HPRATE: targetChar->GetActor()->DoDamage(actor, 0.0, newValue-oldValue, duration); break; default: break; } // This function recalculates the target HP, Mana and Stamina targetChar->RecalculateStats(); } csString CreateUndoScript(psCharacter * targetChar, float oldValue, float finalValue) { psString script; psString baseStr, statName, adjustStr, aimStr; float value; if (base) baseStr = "yes"; else baseStr = "no"; script = ""; switch (stat) { case HP: statName = "hp"; break; case MANA: statName = "mana"; break; case PSTAMINA: statName = "pstamina"; break; case MSTAMINA: statName = "mstamina"; break; case STR: case AGI: case END: case INT: case WIL: case CHA: case CON: case STA: statName = statToString[stat]; break; case ATTACK: statName = "attack"; break; case DEFENSE: statName = "defense"; break; case HPRATE: statName = "hpRate"; break; default: break; } if (adjust == adjust_mul) { adjustStr = "mul"; value = oldValue / finalValue; } else { adjustStr = "add"; value = oldValue - finalValue; } script.AppendFmt("<%s adjust=\"%s\" aim=\"target\" base=\"%s\" value=\"%f\">", statName.GetData(), adjustStr.GetData(), baseStr.GetData(), value); script += ""; if (undoMsg.Length() > 0) script.AppendFmt("", undoMsg.GetData()); script += ""; // This function recalculates the target HP, Mana and Stamina targetChar->RecalculateStats(); return script; } bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse) { psCharacter * targetChar; gemActor * object; // return 0.0f; // Disabled to stop crashes when target is an invalid pointer object = dynamic_cast (aimIsActor ? actor : target); if (!object) { Error2("Error: ProgressionEvent(%s) StatsOp need a target\n",eventName->GetData()); return true; } targetChar = object->GetCharacterData(); if (!targetChar) { Error3("Error: ProgressionEvent(%s) StatsOp aim %s isn't a character\n",eventName->GetData(),object->GetName() ); return true; } Client* client = NULL; if (actor) client = psserver->GetConnections()->Find( actor->GetClientID() ); if (client != NULL) object->SendTargetStatDR( client ); int delay = (int)GetDelay(actor,target,mgr); float oldValue = GetCurrentValue(targetChar); float adjustValue = GetValue(actor,target,mgr); float baseValue = GetCurrentValue( targetChar, true ); float newValue = CalcNewValue(oldValue, adjustValue, inverse, baseValue); SetValue(actor, targetChar, oldValue, newValue, delay); if (delay != 0) { float finalValue = GetCurrentValue(targetChar); csString undoScript = CreateUndoScript(targetChar, oldValue, finalValue); mgr->QueueScript(undoScript.GetData(), delay, actor, object); } return true; } protected: adjust_t adjust; // True if it is the base value that should be used. bool base; // True if its the actors stat that should be used. bool aimIsActor; csString undoMsg; Stat_t stat; }; const char *StatsOp::statToString[] = {"hp","mana","pstamina","mstamina","str","agi","end","int","wil","cha","con","sta","msta", "attack", "defense", "hpRate"}; const PSITEMSTATS_STAT StatsOp::statToAttrib[] = {PSITEMSTATS_STAT_NONE, // hp PSITEMSTATS_STAT_NONE, // mana PSITEMSTATS_STAT_NONE, //stamina PSITEMSTATS_STAT_NONE, //stamina PSITEMSTATS_STAT_STRENGTH, PSITEMSTATS_STAT_AGILITY, PSITEMSTATS_STAT_ENDURANCE, PSITEMSTATS_STAT_INTELLIGENCE, PSITEMSTATS_STAT_WILL, PSITEMSTATS_STAT_CHARISMA, PSITEMSTATS_STAT_CONSTITUTION, PSITEMSTATS_STAT_STAMINA, PSITEMSTATS_STAT_NONE, // attack modifier PSITEMSTATS_STAT_NONE, // defense modifier PSITEMSTATS_STAT_NONE // hp rate }; /*-------------------------------------------------------------*/ /** * Adjust the specified skill level of the target. */ class SkillOp : public ProgressionOperation { public: SkillOp() : ProgressionOperation() { }; virtual ~SkillOp() {}; bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script) { if (node->GetAttributeValue("aim")) { aimIsActor = !strcasecmp(node->GetAttributeValue("aim"),"actor"); } else aimIsActor = true; // Default if (!node->GetAttributeValue("name")) { Error1("Error: No name for skill\n"); return false; } csString name = node->GetAttributeValue("name"); skill = CacheManager::GetSingleton().ConvertSkillString(name); bufferValue = (node->GetAttributeValue("buffer") && !strcmp(node->GetAttributeValue("buffer"),"yes")); setValue = (node->GetAttributeValue("adjust") && !strcmp(node->GetAttributeValue("adjust"),"set")); return LoadValue(node,mgr,script) && LoadDelay(node,mgr,script); } bool CreateUndoScript( float adjustValue, psString& undoScript ) { undoScript = ""; psSkillInfo* info = CacheManager::GetSingleton().GetSkillByID(skill); if ( info ) { csString escname = EscpXML( info->name ); undoScript.AppendFmt("", -adjustValue ); } else { return false; } undoScript.Append(""); return true; } virtual csString ToString() { psString xml; psSkillInfo * info = CacheManager::GetSingleton().GetSkillByID(skill); if (info) { csString escpxml = EscpXML(info->name); xml.Format("",script_text.GetData() ); } else { xml.Format (""); } return xml; } bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse) { // Remove this when adding support for the inverse operation if (inverse) return true; if (!aimIsActor && !target) { Error2("Error: ProgressionEvent(%s) SkillOp need a target\n",eventName->GetData()); return true; } if (aimIsActor && !actor) { Error2("Error: ProgressionEvent(%s) SkillOp need an actor\n",eventName->GetData()); return true; } psCharacter * character; if (aimIsActor) character = actor->GetCharacterData(); else character = target->GetCharacterData(); if (!character) { Error2("Error: ProgressionEvent(%s) SkillOp target %s isn't a character\n",eventName->GetData()); return true; } int adjustValue = (int)GetValue(actor,target,mgr); gemActor * object = dynamic_cast (aimIsActor ? actor : target); if ( !bufferValue ) { result = (unsigned int)(adjustValue + (int)(setValue?0:character->GetSkills()->GetSkillRank(skill, false))); character->GetSkills()->SetSkillRank(skill,(int)result); } else { character->GetSkills()->BuffSkillRank( skill, adjustValue ); result = adjustValue; } int delay = (int)GetDelay(actor, target, mgr); if ( delay != 0 ) { psString undoScript; CreateUndoScript( adjustValue, undoScript ); mgr->QueueScript( undoScript.GetData(), delay, actor, object ); } return true; } protected: // True if the return from the expression shall be used to // set the skill value. Otherwise it will just adjust it. bool setValue; // True if its the actors is the aim that should be used. bool aimIsActor; // True if the skill should be buffed bool bufferValue; PSSKILL skill; }; /*-------------------------------------------------------------*/ /** * Send a message to the target OR the actor. */ class MsgOp : public ProgressionOperation { public: MsgOp() : ProgressionOperation() { }; virtual ~MsgOp() {}; bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script) { my_script = script; if (node->GetAttributeValue("aim")) { aimIsActor = !strcasecmp(node->GetAttributeValue("aim"),"actor"); } else aimIsActor = true; // Default text = node->GetAttributeValue("text"); return true; } virtual csString ToString() { psString xml; xml.Format("",escpxml.GetData()); return xml; } bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse) { // Remove this when adding support for the inverse operation if (inverse) return true; if (!aimIsActor && !target) { Error2("Error: ProgressionEvent(%s) MsgOp need a target\n",eventName->GetData()); return true; } if (aimIsActor && !actor) { Error2("Error: ProgressionEvent(%s) MsgOp need an actor\n",eventName->GetData()); return true; } int clientID; if (aimIsActor) clientID = actor->GetClientID(); else clientID = target->GetClientID(); if (!clientID) { // Spell on NPC instead of PC--not really an error in the script. // CPrintf(CON_ERROR, "Error: ProgressionEvent(%s) MsgOp aim isn't connected to a client.", // eventName->GetData()); return true; } psString sendtext(text); SubstituteVars(actor, target, sendtext); psserver->SendSystemInfo(clientID,sendtext); return true; } protected: void SubstituteVars(gemActor * actor, gemObject *target, psString& str) { int where = (int) str.FindFirst('$'); while (where != -1) { psString word; str.GetWord(where+1,word,false); MathScriptVar *pv = my_script->FindVariable(word); if (pv) { csString buff; buff.Format("%1.0f",pv->GetValue() ); str.ReplaceSubString(word,buff); str.DeleteAt(where); } else { if (word.CompareNoCase( "target" ) == true ) { str.ReplaceSubString( word, target->GetName() ); str.DeleteAt( where ); } else if (word.CompareNoCase( "actor" ) == true ) { str.ReplaceSubString( word, actor->GetName() ); str.DeleteAt( where ); } } where = (int) str.FindFirst('$',where+1); } } csString text; // True if its the actors is the aim that should be used. bool aimIsActor; }; /*-------------------------------------------------------------*/ /** * Block a category of spells from being recast again for a * specified time delay. */ class BlockOp : public ProgressionOperation { public: typedef enum {BLOCK_ADD, BLOCK_REMOVE} Operation_t; BlockOp() : ProgressionOperation() { }; virtual ~BlockOp() {}; static const char * operationToString[]; bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script) { my_script = script; if ( node->GetAttribute("operation") ) { csString operationStr = node->GetAttributeValue("operation"); if ( operationStr.CompareNoCase(operationToString[BLOCK_ADD]) ) { operation = BLOCK_ADD; } else { operation = BLOCK_REMOVE; } } else { operation = BLOCK_ADD; } category = node->GetAttributeValue("category"); return LoadDelay(node,mgr,script);; } virtual csString ToString() { psString xml; csString escpxml = EscpXML(operationToString[operation]); xml.Format( " " ); return xml; } bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse) { // Remove this when adding support for the inverse operation if (inverse) return true; gemActor *targetActor; if ( !target ) { Error2("Error: ProgressionEvent(%s) BlockOp needs a target\n",eventName->GetData()); return 0.0f; } targetActor = target->GetActorPtr(); switch ( operation ) { case BLOCK_ADD: if ( ( targetActor->AddSpellCategory( category ) != -1 ) ) { int delay = (int)GetDelay(actor,target,mgr); if (delay != 0) { psString undoscript = CreateUndoScript(); mgr->QueueScript(undoscript.GetData(), delay, actor, target); } } else { return false; } break; case BLOCK_REMOVE: targetActor->RemoveSpellCategory( category ); break; } return true; } protected: psString CreateUndoScript( void ) { psString script; script = ""; script.Append(" "); script.Append(""); return script; } Operation_t operation; csString category; }; const char *BlockOp::operationToString[] = {"add","remove"}; /*-------------------------------------------------------------*/ /** * Attach scripts reacting on gemActor events to a gemActor */ class AttachScriptOp : public ProgressionOperation { public: AttachScriptOp() : ProgressionOperation() { }; virtual ~AttachScriptOp() {}; bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script) { my_script = script; if (node->GetAttributeValue("aim")) aimIsActor = !strcasecmp(node->GetAttributeValue("aim"),"actor"); else aimIsActor = true; // Default scriptName = node->GetAttributeValue("scriptName"); event = node->GetAttributeValue("event"); undoMsg = node->GetAttributeValue("undomsg"); return LoadDelay(node,mgr,script); } virtual csString ToString() { psString xml; xml.Format(" (actor); else object = dynamic_cast (target); if (!object) { Error2("Error: ProgressionEvent(%s) DetachScriptOp need a target\n",eventName->GetData()); return true; } if (event == "attack") scriptID = object->AttachAttackScript(scriptName); else scriptID = object->AttachDamageScript(scriptName); int delay = (int)GetDelay(actor,target,mgr); if (delay != 0) { psString undoScript; undoScript.AppendFmt(" ", event.GetData(), scriptID); if (undoMsg.Length() > 0) undoScript.AppendFmt("", undoMsg.GetData()); undoScript += ""; mgr->QueueScript(undoScript.GetData(), delay, actor, object); } return true; } protected: bool aimIsActor; csString event; csString scriptName; csString undoMsg; }; /*-------------------------------------------------------------*/ /** * Detach scripts reacting on gemActor events from a gemActor */ class DetachScriptOp : public ProgressionOperation { public: DetachScriptOp() : ProgressionOperation() { }; virtual ~DetachScriptOp() {}; bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script) { my_script = script; if (node->GetAttributeValue("aim")) aimIsActor = !strcasecmp(node->GetAttributeValue("aim"),"actor"); else aimIsActor = true; // Default scriptID = node->GetAttributeValueAsInt("scriptID"); event = node->GetAttributeValue("event"); return true; } virtual csString ToString() { psString xml; xml.Format(" (actor); else object = dynamic_cast (target); if (!object) { Error2("Error: ProgressionEvent(%s) DetachScriptOp need a target\n",eventName->GetData()); return 0.0; } if (event == "attack") object->DetachAttackScript(scriptID); else object->DetachDamageScript(scriptID); return true; } protected: bool aimIsActor; csString event; int scriptID; }; /*-------------------------------------------------------------*/ /** * Determine if 'target' is magical item and tell 'actor' the outcome */ class IdentifyMagicOp : public ProgressionOperation { public: IdentifyMagicOp() : ProgressionOperation() { }; virtual ~IdentifyMagicOp() {}; bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script) { return true; } virtual csString ToString() { psString xml; xml.Format("GetData()); return true; } clientnum = actor->GetClientID(); if (clientnum == 0) { Error2("Error: ProgressionEvent(%s) IdentifyMagicOp needs a client\n",eventName->GetData()); return true; } item = dynamic_cast (target); if (!item) { psserver->SendSystemError(clientnum,"You must have an item selected"); return true; } psItemStats * stats = item->GetItem()->GetBaseStats(); // this is really bad.. should have specific flag? if (!stats->GetIsConsumable() && (stats->GetProgressionEventEquip().Length()>0 || stats->GetProgressionEventUnEquip().Length()>0)) psserver->SendSystemInfo(clientnum,"You found magical properties in this item !"); else psserver->SendSystemInfo(clientnum,"This is an ordinary item without any magical powers."); return true; } }; /*-------------------------------------------------------------*/ /** * Queue another named script to run after a specifed delay. */ class ScriptOp : public ProgressionOperation { public: ScriptOp() : ProgressionOperation() { }; virtual ~ScriptOp(){}; bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *prg_script) { delay = node->GetAttributeValueAsInt("delay"); if (node->GetAttributeValue("persistent")) { persistent = !strcmp(node->GetAttributeValue("persistent"),"yes"); } else persistent = false; script.name = *eventName; script.LoadScript(node,mgr); return true; } virtual csString ToString() { csString xml; csString script_str = script.ToString(false); xml.Format("", delay,(persistent?"persistent=\"yes\" ":""), script_str.GetData()); return xml; } bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse) { // Remove this when adding support for the inverse operation if (inverse) return true; mgr->QueueEvent( new psScriptGameEvent(delay,mgr,&script,actor,target,persistent) ); return true; } protected: int delay; bool persistent; ProgressionEvent script; }; /*-------------------------------------------------------------*/ /* Applies a script to everything of a type in an area. */ /* Syntax: */ /* (default yes) */ /* script */ /* */ class AreaOp : public ProgressionOperation { public: AreaOp() : ProgressionOperation() { }; virtual ~AreaOp(){}; enum target_type { ENTITY, ITEM, ACTOR, GROUP, HOSTILE, FRIENDLY }; bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *prg_script) { type = node->GetAttributeValue("type"); range = node->GetAttributeValueAsInt("range"); anglerange = node->GetAttributeValueAsInt("anglerange"); delaybetween = node->GetAttributeValueAsInt("delaybetween"); includetarget = node->GetAttributeValueAsBool("includetarget",true); if (type.IsEmpty()) { Error2("ProgressionEvent(%s) AreaOp must specify an entity type\n",eventName->GetData() ); return true; } else if (type == "entity") typecode = ENTITY; else if (type == "item") typecode = ITEM; else if (type == "actor") typecode = ACTOR; else if (type == "group") typecode = GROUP; else if (type == "hostile") typecode = HOSTILE; else if (type == "friendly") typecode = FRIENDLY; else { Error3("Invalid type in ProgressionEvent(%s) AreaOp: %s\n",eventName->GetData(), type.GetData() ); return true; } if (range < 1 || range > 100) { Error2("Range in ProgressionEvent(%s) AreaOp must be at least 1 and less than 100\n",eventName->GetData() ); return true; } if (anglerange < 0 || anglerange >= 360) { Error2("Angle range in ProgressionEvent(%s) AreaOp must be between 0 and 360\n",eventName->GetData() ); return true; } if (delaybetween > 5000) { Error2("Delay between applies in ProgressionEvent(%s) AreaOp must be between 0 and 5000ms\n",eventName->GetData() ); return true; } script.name = *eventName; script.LoadScript(node,mgr); return true; } virtual csString ToString() { csString xml; csString script_str = script.ToString(false); xml.Format(""); xml.Append(script_str); xml.Append(""); return xml; } bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse) { if (inverse) // No inverse return true; if (!target) { // If we don't have a target, use the actor target = actor; if (!target) { Error2("Error: ProgressionEvent(%s) AreaOp needs a target or actor\n",eventName->GetData()); return true; } } Client* client; if (anglerange || typecode > ACTOR) /// anglerange, GROUP, HOSTILE, and FRIENDLY need info about the actor { if (!actor) { Error2("Error: ProgressionEvent(%s) AreaOp needs an actor\n",eventName->GetData()); return true; } if (typecode > GROUP) /// HOSTILE and FRIENDLY need info about the actor's client { client = actor->GetClient(); if (!client) { Error2("Error: ProgressionEvent(%s) AreaOp actor needs a client\n",eventName->GetData()); return true; } } } csVector3 actor_pos; /// Actor's position csVector3 target_pos; /// Target's position iSector* actor_sector; /// Actor's sector iSector* target_sector; /// Target's sector #define NORMALIZE_BIG_ANGLE(a) { if (a > TWO_PI) a -= TWO_PI; } #define NORMALIZE_NEG_ANGLE(a) { if (a < 0.0f) a += TWO_PI; } float max_angle, min_angle; if (anglerange) { float actor_angle; actor->GetPosition(actor_pos,actor_angle,actor_sector); // angle is actually in the opposite direction the character is facing... actor_angle += PI; NORMALIZE_BIG_ANGLE(actor_angle); /// We get 1/2 of the given range to either side of the actor's facing angle. float half_range = float(anglerange) * ((PI/180.0f) * 0.5f); // In radians max_angle = actor_angle + half_range; min_angle = actor_angle - half_range; NORMALIZE_BIG_ANGLE(max_angle); NORMALIZE_NEG_ANGLE(min_angle); } if (anglerange && target == actor) { // Just copy the values if we already have them target_pos = actor_pos; target_sector = actor_sector; } else { target->GetPosition(target_pos,target_sector); } GEMSupervisor* gem = GEMSupervisor::GetSingletonPtr(); psWorld* world = EntityManager::GetSingleton().GetWorld(); csRef nearlist = gem->pl->FindNearbyEntities(target_sector,target_pos,range); size_t count = nearlist->GetCount(); for (size_t i=0, n=0; iGetObjectFromEntityList(nearlist,i); if (!includetarget && nearobj == target) continue; switch (typecode) { case ENTITY: break; // Everything case ITEM: if ( nearobj->GetItem() ) break; else continue; case ACTOR: if ( nearobj->GetPlayerID() ) break; else continue; case GROUP: if ( actor->IsGroupedWith(nearobj->GetActorPtr()) ) break; else continue; case HOSTILE: if ( client->IsAllowedToAttack(nearobj,false) ) break; else continue; case FRIENDLY: if ( !client->IsAllowedToAttack(nearobj,false) ) break; else continue; } if (anglerange && nearobj != target) { nearobj->GetPosition(target_pos,target_sector); world->WarpSpace(target_sector,actor_sector,target_pos); float dx = target_pos.x - actor_pos.x; float dz = target_pos.z - actor_pos.z; float angle = atan2f(dx,dz); NORMALIZE_NEG_ANGLE(angle); if (max_angle > min_angle) { if (angle > max_angle || angle < min_angle) continue; } else // Wedge includes 0 { if (angle > max_angle && angle < min_angle) continue; } } // Queue an event for each intended object mgr->QueueEvent( new psScriptGameEvent(delaybetween*n++, mgr, &script, actor, nearobj, true) ); } return true; } protected: int range; /// Range of area application, in meters int anglerange; /// Wedge of area to apply to, in degrees csString type; /// Type of entity to apply to target_type typecode; /// Code for type, from enum csTicks delaybetween; /// Delay between each apply, in miliseconds bool includetarget; /// Apply to target, or just area around? ProgressionEvent script; /// Script to apply to each }; /*-------------------------------------------------------------*/ class ItemOp : public ProgressionOperation { public: ItemOp() : ProgressionOperation() { }; virtual ~ItemOp() {}; bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script) { if (node->GetAttributeValue("aim")) { aimIsActor = !strcasecmp(node->GetAttributeValue("aim"),"actor"); } else aimIsActor = true; // Default name = node->GetAttributeValue("name"); location = node->GetAttributeValue("location"); stackCount = node->GetAttributeValueAsInt("count"); if (location != "" && location != "inventory" && location != "wallet" && location != "ground" ) { Error3("Error:ProgressionEvent(%s) ItemOp Location %s not legal\n",eventName->GetData(), location.GetData()); } return true; } virtual csString ToString() { psString xml; csString escpxml = EscpXML(name); xml.Format(""); return xml; } bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse) { // Remove this when adding support for the inverse operation if (inverse) return true; if (!aimIsActor && !target) { Error2("Error: ProgressionEvent(%s) ItemOp need a target\n",eventName->GetData()); return true; } if (aimIsActor && !actor) { Error2("Error: ProgressionEvent(%s) ItemOp need an actor\n", eventName->GetData()); return true; } psCharacter * character; if (aimIsActor) character = actor->GetCharacterData(); else character = target->GetCharacterData(); if (!character) { Error3("Error: ProgressionEvent(%s) ItemOp No character data was found for target %s.\n", eventName->GetData(),(aimIsActor?actor->GetName():target->GetName())); return true; } //This is for a player that is given some money if ( location == "wallet" ) { psMoney money; if ( name == "trias" ) money.SetTrias( stackCount ); if ( name == "hexas" ) money.SetHexas( stackCount ); if ( name == "octas" ) money.SetOctas( stackCount ); if ( name == "circles" ) money.SetCircles( stackCount ); psMoney charMoney = character->Money(); charMoney = charMoney + money; character->SetMoney( charMoney ); return true; } else if (location == "inventory") { psItem * iteminstance = CreateItem(false); if (!iteminstance) { return true; } if ( character->Inventory().PutInBulk(iteminstance, false, false) != 0) { Error3("Error: ProgressionEvent(%s) ItemOp The item %s did not fit into the character's inventory.\n", eventName->GetData(),iteminstance->GetName()); CacheManager::GetSingleton().RemoveInstance(iteminstance); // So put it on the ground iteminstance = CreateItem(true); psSectorInfo *sectorinfo; float loc_x,loc_y,loc_z,loc_yrot; character->GetLocationInWorld(sectorinfo,loc_x,loc_y,loc_z,loc_yrot); iteminstance->SetLocationInWorld(sectorinfo,loc_x,loc_y,loc_z,loc_yrot); if (!EntityManager::GetSingleton().CreateItem(iteminstance,true)) { delete iteminstance; } iteminstance->Save(); return true; } } else if (location == "ground") { psItem * iteminstance = CreateItem(true); if (!iteminstance) { return true; } psSectorInfo *sectorinfo; float loc_x,loc_y,loc_z,loc_yrot; character->GetLocationInWorld(sectorinfo,loc_x,loc_y,loc_z,loc_yrot); iteminstance->SetLocationInWorld(sectorinfo,loc_x,loc_y,loc_z,loc_yrot); if (!EntityManager::GetSingleton().CreateItem(iteminstance,true)) { delete iteminstance; } iteminstance->Save(); } return true; } psItem *CreateItem(bool transient) { // Get the ItemStats based on the name provided. psItemStats *itemstats=CacheManager::GetSingleton().GetBasicItemStatsByName(name.GetData()); if (!itemstats) { Error3("Error: ProgressionEvent(%s) ItemOp No Basic Item Template with name %s was found.\n", eventName->GetData(),name.GetData()); return NULL; } psItem *iteminstance = itemstats->InstantiateBasicItem(transient); if (iteminstance==NULL) { Error2("Error: ProgressionEvent(%s) ItemOp Could not instanciate item based on basic properties.\n", eventName->GetData()); return NULL; } if (stackCount != 0) { if (!iteminstance->GetIsStackable()) { Error2("Error: ProgressionEvent(%s) ItemOp Item isn't statckable.\n",eventName->GetData()); } else { iteminstance->SetStackCount(stackCount); } } iteminstance->SetLoaded(); // Item is fully created iteminstance->Save(); // First save return iteminstance; } protected: csString name; csString location; int stackCount; // True if its the actors is the aim that should be used. bool aimIsActor; }; /*-------------------------------------------------------------*/ class PurifyOp : public ProgressionOperation { public: PurifyOp() : ProgressionOperation() { }; virtual ~PurifyOp() {}; bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script) { glyphUID = node->GetAttributeValueAsInt("glyph"); return true; } virtual csString ToString() { csString xml ; xml.Format("",glyphUID); return xml; } bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse) { // Remove this when adding support for the inverse operation if (inverse) return true; if (!actor) { Error2("Error: ProgressionEvent(%s) PurifyOp need an actor\n",eventName->GetData()); return true; } psCharacter *character = actor->GetCharacterData(); if (!character) { Error2("Error: ProgressionEvent(%s) PurifyOp need a character\n",eventName->GetData()); return true; } psserver->GetSpellManager()->EndPurifying(character,glyphUID); return true; } protected: uint32 glyphUID; }; /*-------------------------------------------------------------*/ /* Syntax: */ /* */ /* (set mesh to "reset" to reset) */ class MorphOp : public ProgressionOperation { public: MorphOp() : ProgressionOperation() { }; virtual ~MorphOp() {}; bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script) { mesh = node->GetAttributeValue("mesh"); duration = node->GetAttributeValueAsInt("duration"); return true; } virtual csString ToString() { csString xml; xml.Format("", mesh.GetData(), duration ); return xml; } bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse) { if (!actor) { Error2("Error: ProgressionEvent(%s) MorphOp need an actor\n",eventName->GetData()); return true; } if (inverse || mesh == "reset") actor->ResetMesh(); else actor->SetMesh(mesh); if (duration != 0) { // Queue undo script mgr->QueueScript("",duration*1000,actor,actor); } return true; } protected: csString mesh; /// Mesh to morph into int duration; /// Duration of effect in seconds }; /*-------------------------------------------------------------*/ /* Syntax: */ /* (default true) */ class AttributeOp : public ProgressionOperation { public: AttributeOp() : ProgressionOperation() { }; virtual ~AttributeOp() {}; bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script) { attrib = node->GetAttributeValue("attrib"); duration = node->GetAttributeValueAsInt("duration"); value = node->GetAttributeValueAsBool("value",true); if (attrib != "invincible" && attrib != "invisible" && attrib != "nofalldamage" && attrib != "nevertired") { Error3("Invalid attribute for ProgressionEvent(%s) AttributeOp: %s\n", eventName->GetData(), attrib.GetData() ); return true; } if (duration != 0) { // Create undo script undo.Format("", attrib.GetData(), (!value)?"true":"false" ); } return true; } virtual csString ToString() { csString xml; xml.Format("", attrib.GetData(), (value)?"true":"false", duration ); return xml; } bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse) { if (!actor) { Error2("Error: ProgressionEvent(%s) AttributeOp need an actor\n",eventName->GetData()); return true; } bool setvalue = false; if (attrib == "invincible") { setvalue = (actor->GetInvincibility() != value); if (setvalue) actor->SetInvincibility(value); } else if (attrib == "invisible") { setvalue = (actor->GetVisibility() != !value); if (setvalue) actor->SetVisibility(!value); } else if (attrib == "nofalldamage") { setvalue = (actor->safefall != value); if (setvalue) actor->safefall = value; } else if (attrib == "nevertired") { setvalue = (actor->nevertired != value); if (setvalue) actor->nevertired = value; } if (duration != 0 && setvalue) { // Queue undo script mgr->QueueScript(undo,duration*1000,actor,actor); } return true; } protected: csString undo; /// Undo script csString attrib; /// Attribute we're setting bool value; /// Value we're setting to (true=on, false=off) int duration; /// Duration of effect in seconds }; /*-------------------------------------------------------------*/ /* Syntax: */ /* (true by default) */ class WeatherOp : public ProgressionOperation { public: WeatherOp() : ProgressionOperation() { }; virtual ~WeatherOp() {}; bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script) { type = node->GetAttributeValue("type"); duration = node->GetAttributeValueAsInt("duration"); density = node->GetAttributeValueAsInt("density"); fade = node->GetAttributeValueAsInt("fade"); r = node->GetAttributeValueAsInt("r"); g = node->GetAttributeValueAsInt("g"); b = node->GetAttributeValueAsInt("b"); enable = node->GetAttributeValueAsBool("enable",true); // Convert seconds to miliseconds for WeatherManager duration *= 1000; fade *= 1000; csString sector = node->GetAttributeValue("sector"); if (sector.IsEmpty()) { Error2("Error: ProgressionEvent(%s) WeatherOp needs a sector name or \"this\"\n",eventName->GetData() ); return true; } else if (sector == "this") { useCurLoc = true; sectorinfo = NULL; } else { useCurLoc = false; sectorinfo = CacheManager::GetSingleton().GetSectorInfoByName(sector); if (!sectorinfo) { Error3("Invalid sector for ProgressionEvent(%s) WeatherOp: %s\n",eventName->GetData(), sector.GetData() ); return true; } } if (!enable && (duration || density || fade || r || g || b)) { Error2("ProgressionEvent(%s) WeatherOp may only have \"type\" and \"sector\" with enable=\"false\"\n",eventName->GetData() ); return true; } if (type == "fog") wtype = psWeatherMessage::FOG; else if (type == "rain") wtype = psWeatherMessage::RAIN; else if (type == "snow") wtype = psWeatherMessage::SNOW; else if (type == "lightning") wtype = psWeatherMessage::LIGHTNING; else if (type == "auto") wtype = 0; else wtype = -1; // Check parameters switch (wtype) { case psWeatherMessage::FOG: { break; } case psWeatherMessage::RAIN: { if (density < 0 || density > WEATHER_MAX_RAIN_DROPS) { Error3("ProgressionEvent(%s) WeatherOp: Rain drop density must be between 0 and %d\n", eventName->GetData(), WEATHER_MAX_RAIN_DROPS ); return true; } else break; } case psWeatherMessage::SNOW: { if (density < 0 || density > WEATHER_MAX_SNOW_FALKES) { Error3("ProgressionEvent(%s) WeatherOp: Snow flake density must be between 0 and %d\n", eventName->GetData(), WEATHER_MAX_SNOW_FALKES ); return true; } else break; } case psWeatherMessage::LIGHTNING: { if (sectorinfo && sectorinfo->lightning_max_gap == 0) { Error3("Lightning undefined for sector in ProgressionEvent(%s) WeatherOp: %s\n", eventName->GetData(), sector.GetData() ); return true; } else break; } case 0: // "auto" { if (duration || density || fade || r || g || b) { Error2("ProgressionEvent(%s) WeatherOp may only have \"sector\" and \"enable\" with type=\"auto\"\n", eventName->GetData() ); return true; } else break; } default: { Error3("Invalid type for ProgressionEvent(%s) WeatherOp: %s\n", eventName->GetData(), type.GetData() ); return true; } } return true; } virtual csString ToString() { csString xml; xml.Format("name.GetData() ); if (duration) xml.AppendFmt("duration=\"%d\" ", duration ); if (density) xml.AppendFmt("density=\"%d\" ", density ); if (fade) xml.AppendFmt("fade=\"%d\" ", fade ); if (r) xml.AppendFmt("r=\"%d\" ", r ); if (g) xml.AppendFmt("g=\"%d\" ", g ); if (b) xml.AppendFmt("b=\"%d\" ", b ); if (!enable) xml.Append("enable=\"false\" "); xml.Append("/>"); return xml; } bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse) { if (inverse) enable = !enable; if (useCurLoc) { if (!target) { // If we don't have a target, use the actor target = actor; if (!target) { Error2("Error: ProgressionEvent(%s) WeatherOp need a target or actor for sector=\"this\"\n", eventName->GetData() ); return true; } } iSector* sector = target->GetSector(); if (sector == NULL) { Error2("Error: ProgressionEvent(%s) WeatherOp target is in an invalid sector!\n", eventName->GetData() ); return true; } const char* sectorname = sector->QueryObject()->GetName(); sectorinfo = CacheManager::GetSingleton().GetSectorInfoByName(sectorname); if (!sectorinfo) { Error3("Missing SectorInfo in ProgressionEvent(%s) WeatherOp: %s\n", eventName->GetData(), sectorname ); return true; } if (wtype == psWeatherMessage::LIGHTNING && sectorinfo->lightning_max_gap == 0) { Error3("Lightning undefined for sector in ProgressionEvent(%s) WeatherOp: %s\n", eventName->GetData(), sectorname ); return true; } } if (wtype) { if (enable) // Queue the weather event psserver->GetWeatherManager()->QueueNextEvent(0,wtype,density,duration,fade,sectorinfo->name,sectorinfo,0,r,g,b); else psserver->GetWeatherManager()->QueueNextEvent(0,wtype,0,0,fade,sectorinfo->name,sectorinfo); } else // "auto" { if (enable) { sectorinfo->rain_enabled = true; psserver->GetWeatherManager()->StartWeather(sectorinfo); } else { sectorinfo->rain_enabled = false; // Prevents new auto-weather, but does not need to stop anything in-progress } } return true; } protected: psSectorInfo* sectorinfo; /// Sector to work in bool useCurLoc; /// Use target or actor's current sector csString type; /// Type of weather int wtype; /// Type of weather code int duration; /// Duration in seconds int density; /// Density of fog/precipitation int fade; /// Fade in/out time in seconds int r, g, b; /// Color for fog bool enable; /// Are we turning on or off? }; /*-------------------------------------------------------------*/ /* Syntax: */ /* */ class CreatePetOp : public ProgressionOperation { public: virtual bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse) { // Remove this when adding support for the inverse operation if (inverse) return true; if (!actor) { Error2("Error: ProgressionEvent(%s) CreatePetOp needs an actor\n",eventName->GetData()); return true; } if ( !actor->GetClientID() || !actor->GetClient() ) { Error2("Error: ProgressionEvent(%s) CreatePetOp needs a valid client\n",eventName->GetData()); return true; } int familiarid; value = (int)GetValue( actor, target, mgr); switch ( master_ids.Length() ) { case 0: break; case 1: familiarid = master_ids[ 0 ]; break; default: familiarid = (int) ( value / ( 100 / master_ids.Length() ) ); if ( familiarid < 0 ) familiarid = 0; if ( familiarid > (int)master_ids.Length() ) familiarid =(int) master_ids.Length() - 1; familiarid = master_ids[ familiarid ]; break; } gemNPC *Familiar = EntityManager::GetSingleton().CreatePet( actor->GetClient(), familiarid ); if ( Familiar == NULL ) { Error2("Failed to create pet %d \n",familiarid); return false; } return true; } virtual bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script) { psString masterids( node->GetAttributeValue("master_ids") ); psString token; int pos = 0; /* Establish string and get the first token: */ masterids.GetWord( pos, token, psString::NO_PUNCT ); while( token.Length() != 0 ) { pos += (int)token.Length() + 1; master_ids.Push( atoi( token ) ); masterids.GetWord( pos, token, psString::NO_PUNCT ); } controlled = node->GetAttributeValueAsBool("controlled"); return LoadValue(node,mgr,script); } virtual csString ToString() { return ""; } protected: csArray master_ids; bool controlled; int value; }; /*-------------------------------------------------------------*/ /*-------------------------------------------------------------*/ /* Syntax: */ /* */ class CreateFamiliarOp : public ProgressionOperation { public: virtual bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse) { if (!actor) { Error2( "Error: ProgressionEvent(%s) CreateFamiliar needs an actor\n",eventName->GetData()); return true; } if ( !actor->GetClientID()) { Error3( "Error: ProgressionEvent(%s) CreateFamiliar needs a valid client for actor '%s'.\n",eventName->GetData(),actor->GetName() ); return true; } if ( actor->GetCharacterData()->GetFamiliarID() == 0 ) { gemNPC *Familiar = EntityManager::GetSingleton().CreateFamiliar( actor ); if ( Familiar == NULL ) { Error2( "Failed to create familiar for %s.\n", actor->GetName() ); return false; } return true; } else { psserver->SendSystemInfo( actor->GetClientID(), "You already have a familiar, please take care of it."); return false; } } virtual bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script) { return true; } virtual csString ToString() { return ""; } }; /*-------------------------------------------------------------*/ /*-------------------------------------------------------------*/ /* Syntax: */ /* */ class AnimalAffinityOp : public ProgressionOperation { public: typedef enum { adjust_add, adjust_set } adjust_t; virtual bool Run( gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse ) { /// Pointer to the Crystal Space iDocumentSystem. //csRef xml = csPtr(new csTinyDocumentSystem); if ( !actor ) { Error2( "Error: ProgressionEvent(%s) AnimalAffinityOp needs an actor\n", eventName->GetData() ); return true; } Debug2(LOG_CHARACTER, actor->GetClientID(),"AnimalAffinityOp: %s \n", this->ToString().GetData() ); //if ( !actor->GetClient() ) //{ // CPrintf( CON_ERROR, "Error: ProgressionEvent(%s) AnimalAffinityOp needs a valid client\n", eventName->GetData() ); // return true; //} psCharacter *chardata = actor->GetCharacterData(); csString animalAffinity = chardata->GetAnimalAffinity(); if ( animalAffinity.Length() == 0 ) { animalAffinity.Append(""); } // // // ... // // Parse the string into an XML document. csRef xml = csPtr(new csTinyDocumentSystem); CS_ASSERT(xml != NULL); csRef xmlDoc = xml->CreateDocument(); const char* error = xmlDoc->Parse( animalAffinity ); csRef node; bool found = false; if ( !error ) { // Find existing node csRef iter = xmlDoc->GetRoot()->GetNodes(); while ( iter->HasNext() ) { node = iter->Next(); csString operationStr = node->GetAttributeValue( "name" ); if ( operationStr.CompareNoCase( name ) ) { found = true; Debug3( LOG_PETS, actor->GetClientID(),"AnimalAffinityOp: %s : %s \n", name.GetData(), operationStr.GetData() ); break; } } } // Add new node if one doesn't exist if ( !found ) { csString attrNode = mgr->GetAffinityCategories().Get( name.Downcase() , "" ).GetData(); if ( attrNode.Length() != 0 ) { node = xmlDoc->GetRoot()->CreateNodeBefore( CS_NODE_ELEMENT ); node->SetValue( "category" ); node->SetAttribute( "name" , name ); node->SetAttribute( "attribute", attrNode ); node->SetAttribute( "value" , "0" ); } else { Error2( "Error: ProgressionEvent(%s) AnimalAffinityOp needs a valid category\n", eventName->GetData() ); return false; } } // Modify Value if ( node ) { float oldValue = node->GetAttributeValueAsFloat( "value" ); float adjustValue = GetValue( actor, target, mgr); float newValue = CalcNewValue(oldValue, adjustValue, inverse, oldValue); Debug4( LOG_PETS, actor->GetClientID(),"AnimalAffinityOp: %f : %f : %f\n", oldValue, adjustValue, newValue ); node->SetAttributeAsFloat( "value", newValue ); } // Save changes back csRef str; str.AttachNew( new scfString() ); xmlDoc->Write( str ); chardata->SetAnimialAffinity( str->GetData() ); Debug2( LOG_PETS, actor->GetClientID(),"AnimalAffinityOp: %s \n", str->GetData() ); return true; } float CalcNewValue(float oldValue, float adjustValue, bool inverse, float baseValue) { switch ( attribute ) { case adjust_set: return adjustValue; case adjust_add: if ( inverse ) return oldValue - adjustValue; else return oldValue + adjustValue; } return 0.0; } virtual bool Load( iDocumentNode *node, ProgressionManager *mgr, ProgressionEvent *script ) { attribute = adjust_add; if (node->GetAttributeValue("attribute")) { csString adjustStr = node->GetAttributeValue("attribute"); if (adjustStr == "set") attribute = adjust_set; } name = node->GetAttributeValue("name"); return LoadValue( node, mgr, script ); Debug2( LOG_PETS, 0,"AnimalAffinityOp: %s \n", this->ToString().GetData() ); } virtual csString ToString() { psString attrStr, xml; switch (attribute) { case adjust_set: attrStr = "set"; break; case adjust_add: attrStr = "adjust"; break; } xml.Format( "", name.GetData(), attrStr.GetData(), script_text.GetData() ); return xml; } protected: adjust_t attribute; csString name; }; /*-------------------------------------------------------------*/ class ShowDetailsOp : public ProgressionOperation { public: csString * eventName; virtual bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse) { // Remove this when adding support for the inverse operation if (inverse) return true; gemActor * tgtAsActor; gemItem * tgtAsItem; Client * client; if (!actor) { Error2( "Error: ProgressionEvent(%s) ShowDetailsOp needs an actor\n", eventName->GetData()); return true; } if (actor->GetCharacterData() == NULL) return true; client = psserver->GetConnections()->FindPlayer(actor->GetCharacterData()->GetCharacterID()); if (client == NULL) return true; tgtAsActor = dynamic_cast(target); if (tgtAsActor != NULL) { psserver->usermanager->SendCharacterDescription(client, tgtAsActor->GetCharacterData(), true, false, "ShowDetailsOp"); return true; } tgtAsItem = dynamic_cast(target); if (tgtAsItem != NULL) { //TOBEDONE return true; } return true; } virtual bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script) { return true; } virtual csString ToString() { return ""; } }; /*-------------------------------------------------------------*/ /* Syntax: */ /* */ class MovementOp : public ProgressionOperation { public: MovementOp() : ProgressionOperation() { }; virtual ~MovementOp() {}; bool Load(iDocumentNode *node, ProgressionManager *mgr,ProgressionEvent *script) { type = node->GetAttributeValue("type"); if (type == "reset") { typecode = psMoveModMsg::NONE; // Remove all mods duration = 0; return true; // All we need } else if (type == "add") typecode = psMoveModMsg::ADDITION; // Add factor to moves else if (type == "mod") typecode = psMoveModMsg::MULTIPLIER; // Multiply factor against moves else if (type == "const") typecode = psMoveModMsg::CONSTANT; // Add factor until disabled else if (type == "push") typecode = psMoveModMsg::PUSH; // Execute move once else { Error3( "Invalid type for ProgressionEvent(%s) MovementOp: %s\n", eventName->GetData(), type.GetData() ); return true; } duration = node->GetAttributeValueAsInt("duration"); if (typecode == psMoveModMsg::PUSH && duration != 0) { Error2("Duration cannot be used for type=\"push\" in ProgressionEvent(%s) MovementOp\n", eventName->GetData()); return true; } moveMod.x = node->GetAttributeValueAsFloat("x"); // x-axis motion moveMod.y = node->GetAttributeValueAsFloat("y"); // y-axis motion moveMod.z = node->GetAttributeValueAsFloat("z"); // z-axis motion YrotMod = node->GetAttributeValueAsFloat("yrot"); // y-axis rotation // Make defaults for multiplication 1.0f instead of 0.0f if (typecode == psMoveModMsg::MULTIPLIER) { size_t i; for (i=0; i<3; i++) if (fabsf(moveMod[i]) < SMALL_EPSILON) moveMod[i] = 1.0f; if (fabsf(YrotMod) < SMALL_EPSILON) YrotMod = 1.0f; } return true; } virtual csString ToString() { csString xml; xml.Format(""); return xml; } bool Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse) { if (!actor) { Error2("Error: ProgressionEvent(%s) MovementOp needs an actor\n",eventName->GetData()); return true; } Client* client = actor->GetClient(); if (!client) { Error2( "Error: ProgressionEvent(%s) MovementOp needs a client\n",eventName->GetData()); return true; } // Send modifier to client psMoveModMsg msg(client->GetClientNum(), typecode, moveMod, YrotMod); msg.SendMessage(); if (duration != 0) { // Queue undo script mgr->QueueScript("",duration*1000,actor,actor); } return true; } protected: csString type; /// Type of modifier psMoveModMsg::ModType typecode; /// Type code from msg enum int duration; /// Duration of effect in seconds csVector3 moveMod; /// Movement modifier float YrotMod; /// Rotation modifier }; /*-------------------------------------------------------------*/ /*-------------------------------------------------------------*/ psScriptGameEvent::psScriptGameEvent(csTicks offsetticks, ProgressionManager *mgr, ProgressionEvent * script, gemActor *actor, gemObject *target, bool persistent) : psGEMEvent(0,offsetticks,target,"psScriptGameEvent"),persistent(persistent) { this->mgr = mgr; this->script = script; this->actor = actor; this->target = target; } /** * Called when the target object disconnect. It will than store * the progression script in the DB to be executed when character * reconnect. There are no way to remove events from the event queue * so by setting script to NULL, the event is than marked for * no execution when it is triggered. */ void psScriptGameEvent::DeleteObjectCallback(iDeleteNotificationObject * object) { psGEMEvent::DeleteObjectCallback(object); // This removes from callback list. csString status; status.Format("Debug: Deleting script %pp for object %pp", this, object); psserver->GetLogCSV()->Write(CSV_STATUS, status); csString xml; // Store script to character if persistent if (persistent) { csTicks remindingTime = triggerticks - csGetTicks(); csString script_str = script->ToString(false); xml.Format("", remindingTime,script_str.GetData()); // Store the progression script psCharacter * character = (target.IsValid()) ? target->GetCharacterData() : NULL; if (character) { character->AppendProgressionScript(xml); } else { Error2("Can't store progression script, because target %s has no character.", (target.IsValid())? target->GetName():"Actor already removed)"); } } // There are now way to remove a queued game event so we mark it so // it dosn't execute the script later. script = NULL; } void psScriptGameEvent::Trigger() { // This event have been terminated by a disconnecting target. if (script == NULL) return; if (target.IsValid()) { gemActor* act = (actor.IsValid()) ? dynamic_cast((gemObject *) actor) : NULL ; script->Run( act, target, mgr ); } } /*-------------------------------------------------------------*/ ProgressionManager::ProgressionManager(ClientConnectionSet *ccs) { clients = ccs; psserver->GetEventManager()->Subscribe(this,MSGTYPE_GUISKILL,REQUIRE_READY_CLIENT); psserver->GetEventManager()->Subscribe(this,MSGTYPE_DEATH_EVENT,NO_VALIDATION); psserver->GetEventManager()->Subscribe(this,MSGTYPE_ZPOINT_EVENT,NO_VALIDATION); Initialize(); // Load from db. } ProgressionManager::~ProgressionManager() { psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_GUISKILL); psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_DEATH_EVENT); psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_ZPOINT_EVENT); } void ProgressionManager::QueueScript(const char *scriptText, int delay, gemActor * actor, gemObject *target) { csRef doc = ParseString(scriptText); if (doc == NULL) { Error2("Failed to parse script %s", scriptText); return; } ProgressionEvent * script = new ProgressionEvent(); if (!script->LoadScript(doc,this)) { Error2("Failed to load script %s", scriptText); return; } psScriptGameEvent * event = new psScriptGameEvent(delay,this,script,actor,target,true); psserver->GetEventManager()->Push(event); } void ProgressionManager::Initialize() { Result result_factions(db->Select("SELECT * from factions")); unsigned int x = 0; if ( result_factions.IsValid() ) { for ( x = 0; x < result_factions.Count(); x++ ) { Faction *f = new Faction; f->id = atoi( result_factions[x]["id"] ); f->name = result_factions[x]["faction_name"]; f->weight = atof( result_factions[x]["faction_weight"] ); // Stored two different ways factions.Insert(f,TREE_OWNS_DATA); factions_by_id.Put(f->id,f); } } Result result_events(db->Select("SELECT * from progression_events")); if ( result_events.IsValid() ) { csRef xml = csPtr(new csTinyDocumentSystem); for ( x = 0; x < result_events.Count(); x++ ) { ProgressionEvent *ev = new ProgressionEvent; ev->name = result_events[x]["name"]; csRef doc = xml->CreateDocument(); const char* error = doc->Parse( result_events[x]["event_script"] ); if ( error ) { Error2("Could not parse the event named %s. Reason: ", ev->name.GetData() ); Error3("%s\n%s",error,result_events[x]["event_script"]); delete ev; continue; } if (ev->LoadScript(doc,this)) { events.Insert(ev,TREE_OWNS_DATA); } else { Error2("Coudn't load script %s\n",ev->name.GetData()); delete ev; } } } Result result_affinitycategories(db->Select("SELECT * from char_create_affinity")); if ( result_affinitycategories.IsValid() ) { for ( x = 0; x < result_affinitycategories.Count(); x++ ) { affinitycategories.Put( csString( result_affinitycategories[x]["category"]).Downcase() , csString( result_affinitycategories[x]["attribute"]).Downcase() ); } } } void ProgressionManager::HandleMessage(MsgEntry *me,Client *client) { switch( me->GetType()) { case MSGTYPE_GUISKILL: { psGUISkillMessage msg(me); if (msg.valid) HandleSkill(client, msg); else { Debug2(LOG_NET,me->clientnum,"Received unparsable psGUISkillMessage from client %u.\n",me->clientnum); } break; } case MSGTYPE_DEATH_EVENT: { HandleDeathEvent(me); break; } case MSGTYPE_ZPOINT_EVENT: { psZPointsGainedEvent evt(me); Client* client = clients->Find( evt.actor->GetClientID() ); if (!client) { Error1("Got unknown client!"); return; } csString string; string.Format("You've gained some practice points in %s. ", evt.skillName.GetData() ); if ( evt.rankUp ) { string.Append("You've also Ranked up!"); } psserver->SendSystemInfo(evt.actor->GetClientID(), string); SendSkillList( client, false ); break; } } } void ProgressionManager::HandleDeathEvent(MsgEntry *me) { Debug1(LOG_COMBAT, me->clientnum,"Progression Manager handling Death Event\n"); psDeathEvent evt(me); // Only do progression if dead guy is an NPC and not a pet if (evt.deadActor->GetClientID()==0 && !evt.deadActor->GetCharacterData()->IsPet()) { csString progEvent = FindEvent( "kill" )->ToString(true); // Convert the exp to a char csString buffer; buffer.Format("%lu",(unsigned long) evt.deadActor->GetCharacterData()->GetKillExperience()); ChangeScript( progEvent, 0, (const char*)buffer ); ProcessScript(progEvent,evt.killer,evt.deadActor); } } void ProgressionManager::HandleSkill(Client * client, psGUISkillMessage& msg) { // CPrintf(CON_DEBUG, "ProgressionManager::HandleSkill(%d,%s)\n",msg.command, (const char*)msg.commandData); switch ( msg.command ) { case psGUISkillMessage::REQUEST: { // Clear the current skill cache psCharacter * character = client->GetCharacterData(); if (character) character->GetSkillCache()->clear(); SendSkillList(client,false); break; } case psGUISkillMessage::SKILL_SELECTED: { csRef xml = csPtr(new csTinyDocumentSystem); CS_ASSERT( xml ); csRef invList = xml->CreateDocument(); const char* error = invList->Parse( msg.commandData); if ( error ) { Error2("Error in XML: %s", error ); return; } csRef root = invList->GetRoot(); if(!root) { Error1("No XML root"); return; } csRef topNode = root->GetNode("S"); if(!topNode) { Error1("No tag"); return; } csString skillName = topNode->GetAttributeValue("NAME"); psSkillInfo * info = CacheManager::GetSingleton().GetSkillByName(skillName); csString buff; if (info) { buff.Format("", EscpXML(skillName).GetData(), EscpXML(info->description).GetData(), info->category); } else { buff.Format("", EscpXML(skillName).GetData(), info->category); } psCharacter* chr = client->GetCharacterData(); psGUISkillMessage newmsg(client->GetClientNum(), psGUISkillMessage::DESCRIPTION, buff, NULL, int(chr->GetAttributes()->GetStat(PSITEMSTATS_STAT_STRENGTH)), int(chr->GetAttributes()->GetStat(PSITEMSTATS_STAT_ENDURANCE)), int(chr->GetAttributes()->GetStat(PSITEMSTATS_STAT_AGILITY)), int(chr->GetAttributes()->GetStat(PSITEMSTATS_STAT_INTELLIGENCE)), int(chr->GetAttributes()->GetStat(PSITEMSTATS_STAT_WILL)), int(chr->GetAttributes()->GetStat(PSITEMSTATS_STAT_CHARISMA)), int(chr->GetHP()), int(chr->GetMana()), int(chr->GetStamina(true)), int(chr->GetStamina(false)), int(chr->GetHitPointsMax()), int(chr->GetManaMax()), int(chr->GetStaminaMax(true)), int(chr->GetStaminaMax(false)), float(chr->duel_points), true, PSSKILL_NONE, -1, false); if (newmsg.valid) SendMessage(newmsg.msg); else { Bug2("Could not create valid psGUISkillMessage for client %u.\n",client->GetClientNum()); } break; } case psGUISkillMessage::BUY_SKILL: { Debug1(LOG_SKILLXP, client->GetClientNum(),"---------------Buying Skill-------------\n"); csRef xml = csPtr(new csTinyDocumentSystem); CS_ASSERT( xml ); csRef invList = xml->CreateDocument(); const char* error = invList->Parse( msg.commandData); if ( error ) { Error2("Error in XML: %s", error ); return; } csRef root = invList->GetRoot(); if(!root) { Error1("No XML root"); return; } csRef topNode = root->GetNode("B"); if(!topNode) { Error1("No tag"); return; } csString skillName = topNode->GetAttributeValue("NAME"); psSkillInfo * info = CacheManager::GetSingleton().GetSkillByName(skillName); Debug2(LOG_SKILLXP, client->GetClientNum()," Looking for: %s\n", (const char*)skillName); if (!info) { Error2("No skill with name %s found!",skillName.GetData()); Error2("Full Data Sent from Client was: %s\n", msg.commandData.GetData() ); return; } psCharacter * character = client->GetCharacterData(); if (character->GetTrainer() == NULL) { psserver->SendSystemInfo(client->GetClientNum(), "Can't buy skills when not training!"); return; } gemActor* actorTrainer = character->GetTrainer()->GetActor(); if ( actorTrainer ) { if ( character->GetActor()->RangeTo(actorTrainer, false) > RANGE_TO_SELECT ) { psserver->SendSystemInfo(client->GetClientNum(), "Need to get a bit closer to understand the training."); return; } } Debug2(LOG_SKILLXP, client->GetClientNum()," PP available: %d\n", character->GetProgressionPoints() ); // Test for progression points if (character->GetProgressionPoints() <= 0) { psserver->SendSystemInfo(client->GetClientNum(), "You don't have any progression points!"); return; } // Test for money if (info->price > character->Money()) { psserver->SendSystemInfo(client->GetClientNum(), "You don't have the money to buy this skill!"); return; } if ( !character->CanTrain( info->id ) ) { psserver->SendSystemInfo(client->GetClientNum(), "You cannot train this skill any higher yet!"); return; } int current = character->GetSkills()->GetSkillRank((PSSKILL)info->id); float faction = actorTrainer->GetRelativeFaction(character->GetActor()); if ( !character->GetTrainer()->GetTrainerInfo()->TrainingInSkill((PSSKILL)info->id, current, faction)) { psserver->SendSystemInfo(client->GetClientNum(), "You cannot train this skill currently."); return; } character->UseProgressionPoints(1); character->SetMoney(character->Money()-info->price); character->Train(info->id,1); SendSkillList(client,true,info->id); psserver->SendSystemInfo(client->GetClientNum(), "You've received some %s training", skillName.GetData()); break; } case psGUISkillMessage::QUIT: { client->GetCharacterData()->SetTrainer(NULL); client->GetCharacterData()->GetSkillCache()->clear(); break; } } } void ProgressionManager::SendSkillList(Client * client, bool forceOpen, PSSKILL focus, bool isTraining ) { psCharacter * character = client->GetCharacterData(); psCharacter * trainer = character->GetTrainer(); psTrainerInfo * trainerInfo = NULL; float faction = 0.0; int selectedSkillCat = -1; //This is used for storing the category of the selected skill int selectedSkillNameId = -1; // Name ID value of the selected skill // Get the current skill cache psSkillCache *skills = character->GetSkillCache(); skills->setProgressionPoints(character->GetProgressionPoints()); if (trainer) { trainerInfo = trainer->GetTrainerInfo(); faction = trainer->GetActor()->GetRelativeFaction(character->GetActor()); } for (int skillID = 0; skillID < (int)PSSKILL_COUNT; skillID++) { psSkillInfo * info = CacheManager::GetSingleton().GetSkillByID(skillID); if (!info) { Error2("Can't find skill %d",skillID); return; } Skill * charSkill = character->GetSkills()->GetSkill( (PSSKILL)skillID ); if (charSkill == NULL) { Error3("Can't find skill %d in character %i",skillID, character->characterid); return; } // If we are training, send skills that the trainer is providing education in only if ( !trainerInfo || trainerInfo->TrainingInSkill((PSSKILL)skillID, character->GetSkills()->GetSkillRank((PSSKILL)skillID), faction) ) { bool stat = info->id == PSSKILL_AGI || info->id == PSSKILL_CHA || info->id == PSSKILL_END || info->id == PSSKILL_INT || info->id == PSSKILL_WILL || info->id == PSSKILL_STR; /* Get the ID value for the skill name string and find the skill in the cache. If it can't be found, skip this skill. */ unsigned int skillNameId = CacheManager::GetSingleton().FindCommonStringID(info->name); if (skillNameId == 0) { Error2("Can't find skill name \"%s\" in common strings", info->name.GetData()); continue; } psSkillCacheItem *item = skills->getItemBySkillId(skillID); if (info->id == focus) { selectedSkillNameId = (int)skillNameId; selectedSkillCat=info->category; } int actualStat = 0; if (info->category == 0) { if(info->name=="Strength") { actualStat=character->GetAttributes()->GetStat(PSITEMSTATS_STAT_STRENGTH); } else if(info->name== "Endurance") { actualStat=character->GetAttributes()->GetStat(PSITEMSTATS_STAT_ENDURANCE); } else if(info->name== "Agility") { actualStat=character->GetAttributes()->GetStat(PSITEMSTATS_STAT_AGILITY); } else if(info->name== "Intelligence") { actualStat=character->GetAttributes()->GetStat(PSITEMSTATS_STAT_INTELLIGENCE); } else if(info->name== "Will") { actualStat=character->GetAttributes()->GetStat(PSITEMSTATS_STAT_WILL); } else { actualStat=character->GetAttributes()->GetStat(PSITEMSTATS_STAT_CHARISMA); } } if (item) { item->update(charSkill->rank, actualStat, charSkill->y, charSkill->yCost, charSkill->z, charSkill->zCost); } else { item = new psSkillCacheItem(skillID, skillNameId, charSkill->rank, actualStat, charSkill->y, charSkill->yCost, charSkill->z, charSkill->zCost, info->category, stat); skills->addItem(skillID, item); } } else if (trainerInfo) { // We are training, but this skill is not available for training psSkillCacheItem *item = skills->getItemBySkillId(skillID); if (item) item->setRemoved(true); } } bool training= false; if (isTraining) training= true; psGUISkillMessage newmsg(client->GetClientNum(), psGUISkillMessage::SKILL_LIST, "", skills, (unsigned int)character->GetAttributes()->GetStat(PSITEMSTATS_STAT_STRENGTH), (unsigned int)character->GetAttributes()->GetStat(PSITEMSTATS_STAT_ENDURANCE), (unsigned int)character->GetAttributes()->GetStat(PSITEMSTATS_STAT_AGILITY), (unsigned int)character->GetAttributes()->GetStat(PSITEMSTATS_STAT_INTELLIGENCE), (unsigned int)character->GetAttributes()->GetStat(PSITEMSTATS_STAT_WILL), (unsigned int)character->GetAttributes()->GetStat(PSITEMSTATS_STAT_CHARISMA), (unsigned int)character->GetHP(), (unsigned int)character->GetMana(), (unsigned int)character->GetStamina(true), (unsigned int)character->GetStamina(false), (unsigned int)character->GetHitPointsMax(), (unsigned int)character->GetManaMax(), (unsigned int)character->GetStaminaMax(true), (unsigned int)character->GetStaminaMax(false), character->duel_points, forceOpen, selectedSkillNameId, selectedSkillCat, training //If we are training the client must know it ); Debug2(LOG_SKILLXP, client->GetClientNum(),"Sending psGUISkillMessage w/ stats to %d, Valid: ",int(client->GetClientNum())); if (newmsg.valid) { Debug1(LOG_SKILLXP, client->GetClientNum(),"Yes\n"); SendMessage(newmsg.msg); } else { Debug1(LOG_SKILLXP, client->GetClientNum(),"No\n"); Bug2("Could not create valid psGUISkillMessage for client %u.\n",client->GetClientNum()); } } void ProgressionManager::StartTraining(Client * client, psCharacter * trainer) { client->GetCharacterData()->SetTrainer(trainer); SendSkillList(client, true, PSSKILL_NONE, true); } float ProgressionManager::ProcessEvent(const char *event, gemActor * actor, gemObject *target) { ProgressionEvent * ev = FindEvent(event); if (ev) { return ProcessEvent(ev,actor,target); } csString actorName = "N/A"; csString targetName = "N/A"; if ( actor ) actorName = actor->GetName(); if ( target ) targetName = target->GetName(); Error4( "Error: Can't find progression event: %s from actor [%s] on target [%s]\n", event, actorName.GetData(), targetName.GetData()); return 0.0; } float ProgressionManager::ProcessEvent(ProgressionEvent * ev, gemActor * actor, gemObject *target, bool inverse) { Debug5(LOG_SPELLS, actor?actor->GetClientID():0,"Process %s event %s on %s with target %s.\n", inverse ? "inverse" : "", ev->name.GetData(),(actor?actor->GetName():"(null)"), (target?target->GetName():"(null)")); return ev->Run(actor,target,this,inverse); } ProgressionEvent * ProgressionManager::CreateEvent(const char *name, const char *script) { ProgressionEvent *ev = new ProgressionEvent; // Create a Uniq event name!! int count = 0; do { count ++; int uniq_number = (int)(9999*psserver->GetRandom()); ev->name.Format("%s%04d",name,uniq_number++); } while (FindEvent(ev->name) != NULL && count < 100); if (count >= 100) { Error2("Uniq number not found. Failed to execute script: %s",script); return NULL; } csRef xml = csPtr(new csTinyDocumentSystem); csRef doc = xml->CreateDocument(); const char* error = doc->Parse( script ); if ( error ) { Error3("%s\n%s",error, script ); delete ev; return NULL; } if (ev->LoadScript(doc,this)) { events.Insert(ev,TREE_OWNS_DATA); } else { Error2("Faild to load %s", script ); delete ev; return NULL; } return ev; } float ProgressionManager::ProcessScript(const char *script, gemActor * actor, gemObject *target) { ProgressionEvent * ev = CreateEvent((actor?actor->GetName():"Unknown"),script); if (ev) return ProcessEvent(ev->name,actor,target); return 0.0; } bool ProgressionManager::AddScript(const char *name, const char *script) { ProgressionEvent *ev = new ProgressionEvent; csRef xml = csPtr(new csTinyDocumentSystem); csRef doc = xml->CreateDocument(); const char* error = doc->Parse( script ); if ( error ) { Error3("Adding script error: %s\n%s",error, script ); delete ev; return false; } ev->name = name; if (ev->LoadScript(doc,this)) events.Insert(ev,TREE_OWNS_DATA); csString escText; db->Escape( escText, script ); unsigned long res = db->Command("INSERT INTO progression_events " "(name,event_script) " "VALUES (\"%s\",\"%s\")",name, escText.GetData()); if(res!=1) return false; else return true; } ProgressionEvent *ProgressionManager::FindEvent(char const *name) { if ( name == NULL ) return NULL; ProgressionEvent ev; ev.name = name; return events.Find(&ev); } Faction *ProgressionManager::FindFaction(const char *name) { Faction fac; fac.name = name; return factions.Find(&fac); } Faction *ProgressionManager::FindFaction(int id) { return factions_by_id.Get(id,0); } void ProgressionManager::QueueEvent(psGameEvent *event) { psserver->GetEventManager()->Push(event); } void ProgressionManager::SendMessage(MsgEntry *me) { psserver->GetEventManager()->SendMessage(me); } void ProgressionManager::Broadcast(MsgEntry *me) { psserver->GetEventManager()->Broadcast(me, NetBase::BC_EVERYONE); } void ProgressionManager::ChangeScript( csString& script, int param, const char* text ) { psString scriptString( script ); csString buff; buff.Format("$%d", param ); scriptString.ReplaceAllSubString( buff, text ); script.Replace( scriptString.GetData() ); } /*-------------------------------------------------------------*/ ProgressionEvent::~ProgressionEvent() { while (sequence.Length()) { delete sequence.Pop(); } while (variables.Length()) { delete variables.Pop(); } } bool ProgressionEvent::LoadScript(iDocument *doc,ProgressionManager *mgr) { csRef root = doc->GetRoot(); if(!root) { Error1("No XML root in progression script"); return false; } csRef topNode = root->GetNode("evt"); if (topNode) return LoadScript(topNode,mgr); else { Error1("Could not find tag in progression script!"); return false; } } bool ProgressionEvent::LoadScript(iDocumentNode * topNode, ProgressionManager *mgr) { csRef iter = topNode->GetNodes(); while ( iter->HasNext() ) { csRef node = iter->Next(); if ( node->GetType() != CS_NODE_ELEMENT ) continue; ProgressionOperation * op = NULL; // This is a widget so read it's factory to create it. if ( strcmp( node->GetValue(), "agi" ) == 0 ) { op = new StatsOp(StatsOp::AGI); } else if ( strcmp( node->GetValue(), "cha" ) == 0 ) { op = new StatsOp(StatsOp::CHA); } else if ( strcmp( node->GetValue(), "end" ) == 0 ) { op = new StatsOp(StatsOp::END); } else if ( strcmp( node->GetValue(), "exp" ) == 0 ) { op = new ExperienceOp; } else if ( strcmp( node->GetValue(), "faction" ) == 0 ) { op = new FactionOp; } else if ( strcmp( node->GetValue(), "pstamina" ) == 0 ) { op = new StatsOp(StatsOp::PSTAMINA); } else if ( strcmp( node->GetValue(), "mstamina" ) == 0 ) { op = new StatsOp(StatsOp::MSTAMINA); } else if ( strcmp( node->GetValue(), "hp" ) == 0 ) { op = new StatsOp(StatsOp::HP); } else if ( strcmp( node->GetValue(), "int" ) == 0 ) { op = new StatsOp(StatsOp::INT); } else if ( strcmp( node->GetValue(), "item" ) == 0 ) { op = new ItemOp(); } else if ( strcmp( node->GetValue(), "mana" ) == 0 ) { op = new StatsOp(StatsOp::MANA); } else if ( strcmp( node->GetValue(), "msg" ) == 0 ) { op = new MsgOp(); } else if ( strcmp( node->GetValue(), "block" ) == 0 ) { op = new BlockOp(); } else if ( strcmp( node->GetValue(), "purify" ) == 0 ) { op = new PurifyOp(); } else if ( strcmp( node->GetValue(), "script" ) == 0 ) { op = new ScriptOp(); } else if ( strcmp( node->GetValue(), "area" ) == 0 ) { op = new AreaOp(); } else if ( strcmp( node->GetValue(), "str" ) == 0 ) { op = new StatsOp(StatsOp::STR); } else if ( strcmp( node->GetValue(), "wil" ) == 0 ) { op = new StatsOp(StatsOp::WIL); } else if ( strcmp( node->GetValue(), "con" ) == 0 ) { op = new StatsOp(StatsOp::CON); } else if ( strcmp( node->GetValue(), "sta" ) == 0 ) { op = new StatsOp(StatsOp::STA); } else if ( strcmp( node->GetValue(), "attack" ) == 0 ) { op = new StatsOp(StatsOp::ATTACK); } else if ( strcmp( node->GetValue(), "defense" ) == 0 ) { op = new StatsOp(StatsOp::DEFENSE); } else if ( strcmp( node->GetValue(), "hpRate" ) == 0 ) { op = new StatsOp(StatsOp::HPRATE); } else if ( strcmp( node->GetValue(), "skill" ) == 0 ) { op = new SkillOp(); } else if ( strcmp( node->GetValue(), "showdetails" ) == 0 ) { op = new ShowDetailsOp(); } else if ( strcmp( node->GetValue(), "attachscript" ) == 0 ) { op = new AttachScriptOp(); } else if ( strcmp( node->GetValue(), "detachscript" ) == 0 ) { op = new DetachScriptOp(); } else if ( strcmp( node->GetValue(), "identifymagic" ) == 0 ) { op = new IdentifyMagicOp(); } else if ( strcmp( node->GetValue(), "createnpc" ) == 0 ) { op = new CreatePetOp(); } else if ( strcmp( node->GetValue(), "createpet" ) == 0 ) { op = new CreatePetOp(); } else if ( strcmp( node->GetValue(), "createfamiliar" ) == 0 ) { op = new CreateFamiliarOp(); } else if ( strcmp( node->GetValue(), "move" ) == 0 ) { op = new MovementOp(); } else if ( strcmp( node->GetValue(), "trait" ) == 0 ) { op = new TraitChangeOp(); } else if ( strcmp( node->GetValue(), "animalaffinity" ) == 0 ) { op = new AnimalAffinityOp(); } else if ( strcmp( node->GetValue(), "morph" ) == 0 ) { op = new MorphOp(); } else if ( strcmp( node->GetValue(), "set" ) == 0 ) { op = new AttributeOp(); } else if ( strcmp( node->GetValue(), "weather" ) == 0 ) { op = new WeatherOp(); } else { // op = ExpressionOp::Factory(node,mgr); Error3("Unknown script operation %s in script %s.",node->GetValue(), name.GetData() ); return false; } op->eventName = &name; if (op->Load(node,mgr,this)) sequence.Push(op); else { delete op; return false; } } return true; } csString ProgressionEvent::ToString(bool topLevel) const { csString xml; if (topLevel) xml.Append(""); csArray::ConstIterator seq = sequence.GetIterator(); while (seq.HasNext()) { ProgressionOperation * po = seq.Next(); xml.Append(po->ToString()); } if (topLevel) xml.Append(""); return xml; } void ProgressionEvent::CopyVariables(MathScript *from) { csHash::GlobalIterator iter = from->variables.GetIterator(); while (iter.HasNext()) { MathScriptVar *ptr = iter.Next(); MathScriptVar *mine = FindVariable(ptr->name); if (!mine) { mine = new MathScriptVar; variables.Push(mine); } mine->Copy(ptr); } } void ProgressionEvent::LoadVariables(MathScript *script) { if (!script) return; size_t i; for (i=0; iGetOrCreateVar(variables[i]->name); var->SetValue(variables[i]->GetValue() ); } } MathScriptVar *ProgressionEvent::FindOrCreateVariable(const char *name) { MathScriptVar *found = FindVariable(name); if (found) return found; MathScriptVar *pv = new MathScriptVar; pv->name = name; pv->SetValue(0); AddVariable(pv); return pv; } void ProgressionEvent::AddVariable(MathScriptVar *pv) { MathScriptVar *found = FindVariable(pv->name); if (!found) variables.Push(pv); else found->Copy(pv); } void ProgressionEvent::SetValue( const char* name, double val ) { for( size_t z = 0; z < sequence.Length(); z++ ) { MathScript* script = sequence[z]->GetMathScript(); if ( script ) { MathScriptVar* var = script->GetVar( name ); if ( var ) { var->SetValue( val ); } } } } MathScriptVar *ProgressionEvent::FindVariable(const char *name) { size_t i; for (i=0; iname == name) return variables[i]; } return NULL; } float ProgressionEvent::Run(gemActor * actor, gemObject *target, ProgressionManager *mgr, bool inverse) { float result = 0.0f; csString dump_str = Dump(); Notify5(LOG_SCRIPT,"%s run this script %son %s:\n%s", (actor?actor->GetName():""), (inverse?"inverse ":""),(target?target->GetName():""), dump_str.GetData()); csArray::Iterator seq = sequence.GetIterator(); while (seq.HasNext()) { ProgressionOperation * po = seq.Next(); po->LoadVariables(variables); if ( po->Run(actor,target,mgr,inverse) == false ) break; result += po->GetResult(); } return result; } csString ProgressionEvent::Dump() const { csString str; str.Append("Name : "); str.Append(name.GetData()); str.Append("\nScript:\n"); str.Append(ToString(true)); str.Append("\nVariables:\n"); for (size_t i=0; i < variables.Length(); i++) { str.Append(variables[i]->Dump()); str.Append("\n"); } return str; }