/* * spellmanager.cpp by Anders Reggestad * * Copyright (C) 2001-2002 Atomic Blue (info@planeshift.it, http://www.atomicblue.org) * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation (version 2 of the License) * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ #include #include #include #include #include #include "globals.h" #include "spellmanager.h" #include "clients.h" #include "playergroup.h" #include "gem.h" #include "net/msghandler.h" #include "net/messages.h" #include "util/eventmanager.h" #include "util/psxmlparser.h" #include "bulkobjects/pscharacterloader.h" #include "bulkobjects/psspell.h" #include "bulkobjects/psglyph.h" #include "psserver.h" #include "psserverchar.h" #include "cachemanager.h" #include "progressionmanager.h" psSpellManager::psSpellManager(ClientConnectionSet *ccs, iObjectRegistry * object_reg) { clients = ccs; this->object_reg = object_reg; randomgen = psserver->rng; psserver->GetEventManager()->Subscribe(this,MSGTYPE_GLPYH_REQUEST,REQUIRE_READY_CLIENT|REQUIRE_ALIVE); psserver->GetEventManager()->Subscribe(this,MSGTYPE_GLYPH_ASSEMBLE,REQUIRE_READY_CLIENT|REQUIRE_ALIVE); psserver->GetEventManager()->Subscribe(this,MSGTYPE_SPELL_CAST,REQUIRE_READY_CLIENT|REQUIRE_ALIVE); psserver->GetEventManager()->Subscribe(this,MSGTYPE_PURIFY_GLYPH,REQUIRE_READY_CLIENT|REQUIRE_ALIVE); psserver->GetEventManager()->Subscribe(this,MSGTYPE_SAVE_SPELL,REQUIRE_READY_CLIENT|REQUIRE_ALIVE); psserver->GetEventManager()->Subscribe(this,MSGTYPE_SPELL_BOOK,REQUIRE_READY_CLIENT|REQUIRE_ALIVE); psserver->GetEventManager()->Subscribe(this,MSGTYPE_SPELL_CANCEL,REQUIRE_READY_CLIENT|REQUIRE_ALIVE); } psSpellManager::~psSpellManager() { if (psserver->GetEventManager()) { psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_GLPYH_REQUEST); psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_GLYPH_ASSEMBLE); psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_SPELL_CAST); psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_PURIFY_GLYPH); psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_SAVE_SPELL); psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_SPELL_BOOK); psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_SPELL_CANCEL); } } void psSpellManager::HandleMessage(MsgEntry *me,Client *client) { switch( me->GetType()) { case MSGTYPE_SPELL_CAST: { psSpellCastMessage mesg(me); Cast(client,mesg.spell,mesg.kFactor); break; } case MSGTYPE_PURIFY_GLYPH: { psPurifyGlyphMessage mesg(me); StartPurifying(client, mesg.glyph); break; } case MSGTYPE_SAVE_SPELL: { psSaveSpellMessage mesg(me); SaveSpell(client, mesg.spell); break; } case MSGTYPE_SPELL_BOOK: { SendSpellBook( client ); break; } case MSGTYPE_GLPYH_REQUEST: { SendGlyphs( client ); break; } case MSGTYPE_GLYPH_ASSEMBLE: { HandleAssembler( client, me ); break; } case MSGTYPE_SPELL_CANCEL: { CancelSpellCasting(client->GetActor()); break; } } } //debug: void DumpAssembler(Client * client, glyphList_t & assembler) { psString msg; msg=""; for (size_t i=0; i < assembler.Length(); i++) msg.AppendFmt("S%u ",assembler[i]->GetUID()); psserver->SendSystemInfo(client->GetClientNum(), msg.GetData()); } void psSpellManager::HandleAssembler( Client* client, MsgEntry* me ) { psGlyphAssembleMessage mesg; mesg.FromClient( me ); glyphList_t assembler; psSpell * spell; psItemStats * stats; assembler.SetLength(GLYPH_ASSEMBLER_SLOTS); // set to maximum length so the glyphs will fit in size_t numGlyphs = 0; for (size_t slotNum=0; slotNum < GLYPH_ASSEMBLER_SLOTS; slotNum++) { int statID = mesg.glyphs[slotNum]; stats = CacheManager::GetSingleton().GetBasicItemStatsByID(statID); if (stats != NULL) { numGlyphs++; assembler[numGlyphs-1] = stats; } } assembler.SetLength(numGlyphs); // set assembler to actual length //DumpAssembler(client, assembler); if ( ! client->GetCharacterData()->HasGlyphs(assembler)) { Error2("Client %i tried to research spell with glyphs he actually doesn't have", client->GetClientNum()); SendGlyphs(client); return; } csString name(" "); csString image(" "); csString description(" "); if ( (spell = FindSpell(client, assembler)) ) { description = spell->GetDescription(); name = spell->GetName(); image = spell->GetImage(); } if ( spell ) { int skill = client->GetCharacterData()->GetSkills()->GetSkillRank( spell->GetWay()->skill ); int maxRealm = 1 + skill/20; if ( maxRealm >= spell->GetRealm() ) { psGlyphAssembleMessage newmsg(client->GetClientNum(), name, image, description); newmsg.SendMessage(); return; } } // clear the description, if this is not valid glyph sequence for our player: psGlyphAssembleMessage newmsg(client->GetClientNum(), name, image, description); newmsg.SendMessage(); } void psSpellManager::SaveSpell(Client * client, csString spellName) { psSpell * spell = client->GetCharacterData()->GetSpellByName(spellName); if (spell) { psserver->SendSystemInfo(client->GetClientNum(), "You know the %s spell already!",spellName.GetData()); return; } spell = CacheManager::GetSingleton().GetSpellByName(spellName); if (!spell) { psserver->SendSystemInfo(client->GetClientNum(), "%s isn't a defined spell!",spellName.GetData()); return; } client->GetCharacterData()->AddSpell(spell); psServer::CharacterLoader.SaveCharacterData(client->GetCharacterData(),client->GetActor()); SendSpellBook(client); psserver->SendSystemInfo(client->GetClientNum(), "%s added to your spell book!",spellName.GetData()); } void psSpellManager::Cast(Client * client, csString spellName, float kFactor) { psSpell * spell = CacheManager::GetSingleton().GetSpellByName(spellName); if (!spell) { psserver->SendSystemInfo(client->GetClientNum(), "%s is a unknown spell for you!",spellName.GetData()); return; } client->GetCharacterData()->SetKFactor(kFactor); csString effectName; csVector3 offset; PS_ID anchorID; PS_ID targetID; unsigned int castingDuration; csString castingText; psSpellCastGameEvent *event = spell->Cast(this, client, effectName, offset, anchorID, targetID, castingDuration, &castingText); if ( event ) { event->FireEvent(); //targetID = event->target->GetEntity()->GetID(); if (effectName && effectName != "") { psEffectMessage newmsg(0, effectName, offset, anchorID, targetID, castingDuration, 0); if (newmsg.valid) newmsg.Multicast(event->caster->GetActor()->GetMulticastClients(),0,PROX_LIST_ANY_RANGE); else { Bug1("Could not create valid psEffectMessage for broadcast.\n"); } } } // Inform player if (castingText != "") { psserver->SendSystemInfo(client->GetClientNum(),castingText.GetData()); } } void psSpellManager::CancelSpellCasting(gemActor * caster) { caster->GetCharacterData()->InterruptSpellCasting(); } void psSpellManager::SendSpellBook(Client * client) { psSpellBookMessage mesg( client->GetClientNum() ); csArray spells = client->GetCharacterData()->GetSpellList(); for ( size_t i = 0; i < spells.Length(); i++ ) { csString glyph0(""); csString glyph1(""); csString glyph2(""); csString glyph3(""); csString name( spells[i]->GetName() ); csString description( spells[i]->GetDescription() ); csString way(spells[i]->GetWay()->name); int realm = spells[i]->GetRealm(); csArray glyphs = spells[i]->GetGlyphList(); if ( glyphs.Length() > 0 ) glyph0 = glyphs[0]->GetImageName(); if ( glyphs.Length() > 1 ) glyph1 = glyphs[1]->GetImageName(); if ( glyphs.Length() > 2 ) glyph1 = glyphs[2]->GetImageName(); if ( glyphs.Length() > 3 ) glyph1 = glyphs[3]->GetImageName(); mesg.AddSpell(name,description,way,realm, glyph0,glyph1,glyph2,glyph3 ); } mesg.Construct(); mesg.SendMessage(); } void psSpellManager::SendGlyphs( Client * client) { psCharacter * character = client->GetCharacterData(); csArray slots; size_t slotNum; int wayNum; character->CreateGlyphList(false, slots); psRequestGlyphsMessage outMessage( client->GetClientNum() ); for (slotNum=0; slotNum < slots.Length(); slotNum++) { csString way; PSITEMSTATS_SLOTLIST validSlots; int statID; validSlots = slots[slotNum].glyphType->GetValidSlots(); statID = slots[slotNum].glyphType->GetUID(); if (validSlots & PSITEMSTATS_SLOT_CRYSTAL) wayNum = 0; if (validSlots & PSITEMSTATS_SLOT_BLUE) wayNum = 1; if (validSlots & PSITEMSTATS_SLOT_AZURE) wayNum = 2; if (validSlots & PSITEMSTATS_SLOT_BROWN) wayNum = 3; if (validSlots & PSITEMSTATS_SLOT_RED) wayNum = 4; if (validSlots & PSITEMSTATS_SLOT_DARK) wayNum = 5; outMessage.AddGlyph( slots[slotNum].glyphType->GetName(), slots[slotNum].glyphType->GetImageName(), slots[slotNum].count, slots[slotNum].purifyStatus, wayNum, statID ); } outMessage.Construct(); outMessage.SendMessage(); } psGlyph * FindUnpurifiedGlyph(psCharacter * character, unsigned int statID) { int bulkNum; for (bulkNum=0; bulkNum < PSCHARACTER_BULK_COUNT; bulkNum++) { psItem * item = character->Inventory().GetBulkItem(bulkNum); //We need to search the glyphs in the containers in the inventory. if (item && item->GetIsContainer() && !item->GetIsContainerEmpty()) { psContainerIterator* it = new psContainerIterator(item); while (it->HasNext()) { psGlyph* glyphInContainer = dynamic_cast (it->Next()); //If one of the item of the content is a glyph, add to the slot list if (glyphInContainer && glyphInContainer->GetBaseStats()->GetUID()==statID && glyphInContainer->GetPurifyStatus()==0) { delete it; return glyphInContainer; } } delete it; } else { psGlyph * glyph = dynamic_cast (character->Inventory().GetBulkItem(bulkNum)); if (glyph != NULL) { if (glyph->GetBaseStats()->GetUID()==statID && glyph->GetPurifyStatus()==0) { return glyph; } } } } return NULL; } void psSpellManager::StartPurifying(Client * client, int statID) { psCharacter* character = client->GetCharacterData(); psGlyph* glyph = FindUnpurifiedGlyph(character, statID); if (glyph == NULL) return; if (glyph->GetStackCount() > 1) { if ( !character->Inventory().CanFit(glyph) || !character->Inventory().HasEmptyBulkSlots() ) { psserver->SendSystemError(client->GetClientNum(), "Your inventory is full!" ); SendGlyphs(client); return; } glyph = dynamic_cast (glyph->SplitStack(1)); if (glyph == NULL) return; if ( character->Inventory().PutInBulk( (psItem*&)glyph, false, false, ANY_EMPTY_BULK_SLOT) != 0 ) { Error2("Failed to move purifying glyph to bulk slot for %s", client->GetName() ); CacheManager::GetSingleton().RemoveInstance( (psItem*&)glyph ); return; } glyph->Save(); } glyph->PurifyingStarted(); // If the glyph has no ID, we must save it now because we need to have an ID for the purification script glyph->ForceSaveIfNew(); // Use the progression manager to purify so that the // purify event will continue when player reconnect if // not finished before disconnect. psserver->SendSystemInfo(client->GetClientNum(), "You start to purify %s", glyph->GetName() ); csString event; event.Format("",20000,glyph->GetUID()); psserver->GetProgressionManager()->ProcessScript(event.GetData(),client->GetActor(),client->GetActor()); SendGlyphs(client); psserver->GetCharManager()->SendInventory(client->GetClientNum()); } void psSpellManager::EndPurifying(psCharacter * character, uint32 glyphUID) { Client * client = clients->FindPlayer(character->GetCharacterID()); if (!client) { Error1("No purifyer!"); return; } for (int bulkNum=0; bulkNum < PSCHARACTER_BULK_COUNT; bulkNum++) { psItem * item = character->Inventory().GetBulkItem(bulkNum); if (item) { if (item->GetUID()==glyphUID) { psGlyph * glyph = dynamic_cast (item); if (glyph != NULL) { glyph->PurifyingFinished(); glyph->Save(); psserver->SendSystemInfo(client->GetClientNum(), "The glyph %s is now purified", glyph->GetName()); SendGlyphs(client); psserver->GetCharManager()->SendInventory(client->GetClientNum()); return; } } else { //We need to search the glyphs in the containers in the inventory. if (item->GetIsContainer() && !item->GetIsContainerEmpty()) { psContainerIterator* it = new psContainerIterator(item); while (it->HasNext()) { psGlyph* glyphInContainer = dynamic_cast (it->Next()); //If one of the item of the content is a glyph, add to the slot list if (glyphInContainer && glyphInContainer->GetUID() == glyphUID) { glyphInContainer->PurifyingFinished(); glyphInContainer->Save(); psserver->SendSystemInfo(client->GetClientNum(), "The glyph %s is now purified", glyphInContainer->GetName()); SendGlyphs(client); psserver->GetCharManager()->SendInventory(client->GetClientNum()); delete it; return; } } delete it; } } } } } /** * Verify if the combination in the assembler slots * form a spell. * @return Return the spell created by the assembler glyphs. */ psSpell* psSpellManager::FindSpell(Client * client, const glyphList_t & assembler) { CacheManager::SpellIterator loop = CacheManager::GetSingleton().GetSpellIterator(); psSpell *p; while (loop.HasNext()) { p = loop.Next(); if (p->MatchGlyphs(assembler)) { // Combination of glyphs correct return p; } } return NULL; } psSpell* psSpellManager::FindSpell(csString& name) { return CacheManager::GetSingleton().GetSpellByName(name); } psSpell* psSpellManager::FindSpell(int spellID) { return CacheManager::GetSingleton().GetSpellByID(spellID); } /** * This is the meat and potatoes of the spell engine here. */ void psSpellManager::HandleSpellCastEvent(psSpellCastGameEvent *event) { csString responseEffectName; csVector3 offset; PS_ID anchorID; PS_ID targetID; csString affectText; // Start the effect if spell is successfully cast gemActor * caster = event->caster->GetActor(); gemObject * target = event->target; const psSpell * spell = event->spell; // Check for spell failure float chanceOfSuccess = spell->ChanceOfSuccess( caster->GetCharacterData()->GetKFactor(), caster->GetCharacterData()->GetSkills()->GetSkillRank( spell->GetSkill() ), caster->GetCharacterData()->GetSkills()->GetSkillRank( spell->GetRelatedStat() ) ); Notify4(LOG_SPELLS, "%s Casting %s with a chance of success = %.2f\n",caster->GetName(), spell->GetName().GetData(), chanceOfSuccess); if ( psserver->GetRandom() * 100.0 > chanceOfSuccess ) { // Spell casting failed now we are in Peace mode again caster->SetMode( PSCHARACTER_MODE_PEACE ); affectText.Format( "You failed to cast the spell %s" , spell->GetName().GetData() ); } else { // Spell casting succeeded, find out what targets are affected. if ( spell->AffectTargets( this, event, responseEffectName, offset, anchorID, targetID, &affectText ) ) { // Only gain practice if the spell was effective caster->GetCharacterData()->GetSkills()->AddSkillPractice( spell->GetSkill(), 1 ); } // Spell Casting complete, we are now in Peace mode again. event->caster->GetActor()->SetMode( PSCHARACTER_MODE_PEACE ); event->caster->GetCharacterData()->SetSpellCasting( NULL ); // If there is some sort of visual/particle/audio effect for the target then fire it out. if (responseEffectName && responseEffectName != "") { psEffectMessage newmsg( 0, responseEffectName, offset, anchorID, targetID, 0, 0 ); if ( newmsg.valid ) newmsg.Multicast( event->target->GetMulticastClients(), 0, PROX_LIST_ANY_RANGE ); else { Bug1( "Could not create valid psEffectMessage for broadcast.\n" ); } } } // Inform player if ( affectText != "" ) { psserver->SendSystemInfo( event->caster->GetActor()->GetClientID(), affectText.GetData() ); } } void psSpellManager::HandleSpellAffectEvent( psSpellAffectGameEvent *event ) { // Since we just came in from an event, make sure target is still alive. if ( event->target->IsAlive() ) { event->spell->PerformResult( event->caster->GetActor(), event->target, event->min_range, event->max_range, event->saved, event->powerLevel); } // We may have just killed the target, lets check to make sure it's still alive. if ( event->target->IsAlive() ) { // If this is a DoT spell then set up the next event. if ( event->interval != 0 ) { float duration, interval; if ( event->duration > event->interval ) { interval = event->interval; duration = event->duration - event->interval; } else { interval = event->duration; duration = 0.0f; } if ( interval + duration > 0 ) { psSpellAffectGameEvent *e = new psSpellAffectGameEvent( psserver->GetSpellManager(), event->spell, event->caster, event->target, event->interval, event->min_range, event->max_range, event->saved, event->powerLevel, event->interval, event->duration); e->FireEvent(); } } } } /*-------------------------------------------------------------*/ psSpellCastGameEvent::psSpellCastGameEvent(psSpellManager *mgr, const psSpell * spell, Client *caster, gemObject *target, csTicks castingDuration, float min_range, float max_range, float powerLevel, float interval, float duration ) : psGameEvent(0,castingDuration,"psSpellCastGameEvent") { spellmanager = mgr; this->spell = spell; this->caster = caster; this->target = target; valid = true; this->min_range = min_range; this->max_range = max_range; this->powerLevel= powerLevel; this->interval = interval; this->duration = duration; target->RegisterCallback( this ); caster->GetActor()->RegisterCallback( this ); caster->GetCharacterData()->SetSpellCasting(this); } psSpellCastGameEvent::~psSpellCastGameEvent() { if ( target ) target->UnregisterCallback(this); if ( caster ) { caster->GetCharacterData()->SetSpellCasting(NULL); caster->GetActor()->UnregisterCallback(this); } } void psSpellCastGameEvent::DeleteObjectCallback(iDeleteNotificationObject * object) { if ( target ) target->UnregisterCallback(this); if ( caster ) caster->GetActor()->UnregisterCallback(this); Interrupt(); target = NULL; caster = NULL; } void psSpellCastGameEvent::Interrupt() { // Check if this event have been stoped before if (!IsValid()) return; psserver->SendSystemInfo(caster->GetClientNum(),"Your spell (%s) has been interrupted!",spell->GetName().GetData()); caster->GetActor()->SetMode( PSCHARACTER_MODE_PEACE ); caster->GetCharacterData()->SetSpellCasting(NULL); // Stop event from beeing executet when trigged. SetValid(false); } void psSpellCastGameEvent::Trigger() { spellmanager->HandleSpellCastEvent(this); } /*-------------------------------------------------------------*/ psSpellAffectGameEvent::psSpellAffectGameEvent(psSpellManager *mgr, const psSpell *spell, Client *caster, gemObject *target, csTicks progression_delay, float min_range, float max_range, bool saved, float powerLevel, float interval, float duration ) : psGameEvent(0, progression_delay,"psSpellAffectGameEvent") { spellmanager = mgr; this->spell = spell; this->caster = caster; this->target = target; this->min_range = min_range; this->max_range = max_range; this->saved = saved; this->powerLevel= powerLevel; this->interval = interval; this->duration = duration; target->RegisterCallback( this ); caster->GetActor()->RegisterCallback( this ); } psSpellAffectGameEvent::~psSpellAffectGameEvent() { if ( target ) target->UnregisterCallback(this); if ( caster ) caster->GetActor()->UnregisterCallback(this); } void psSpellAffectGameEvent::DeleteObjectCallback(iDeleteNotificationObject * object) { SetValid(false); // Prevent the Trigger from being called. if ( target ) target->UnregisterCallback(this); if ( caster ) caster->GetActor()->UnregisterCallback(this); target = NULL; caster = NULL; } void psSpellAffectGameEvent::Trigger() { spellmanager->HandleSpellAffectEvent(this); }