// This _function should prevent a mob from spamming // spellups on itself if it is already affected by the spell. // in the Spellup() _function, checks for Affect.AFFECT_WHATEVER // still need to be done. - Xangis static bool CheckSpellup( CharData ch, string name, int percent ) { if (ch == null) return false; if (String.IsNullOrEmpty(name)) return false; Spell spell = Spell.SpellList[name]; if (spell == null) { return false; } // Keep mobs from casting spells they are affected by if( ch.HasAffect( Affect.AffectType.spell, spell ) ) return false; if( ( ch.HasSpell( name ) ) && ( MUDMath.NumberPercent() < percent ) ) { if (spell.ValidTargets != TargetType.singleCharacterDefensive && spell.ValidTargets != TargetType.self) Log.Error( "CheckSpellup: Mob casting spell {0} which is neither TargetType.self nor TargetType.defensive.", spell ); SocketConnection.Act( "$n&n starts casting...", ch, null, null, SocketConnection.MessageTarget.room ); ch.SetAffectBit( Affect.AFFECT_CASTING ); CastData caster = new CastData(); caster.Who = ch; caster.Eventdata = Event.CreateEvent(Event.EventType.spell_cast, spell.CastingTime, ch, ch, spell); Database.CastList.Add( caster ); return true; } return false; }
/* * If a spell casting mob is hating someone, try to summon them. * * Xangis - Need to add code to also gate to the person if they can't be summoned */ static void SummonIfHating( CharData ch ) { string name = String.Empty; string buf = String.Empty; if( ch.Fighting || ch.Fearing || ch.Hating.Count == 0 || ch.InRoom.HasFlag( RoomTemplate.ROOM_SAFE ) ) return; /* If summoner is busy hunting someone aleady, don't summon. */ if( ch.Hunting ) return; CharData victim = ch.GetRandomHateTarget( false ); // Pretty stupid to summon someone who's in the same room. if( !victim || ch.InRoom == victim.InRoom ) return; if( ( ch.HasSpell( "relocate" ) )) { if( !victim.IsNPC() ) buf += "relocate 0." + name; else buf += "relocate " + name; } else if( ch.HasSpell( "summon" ) ) { if( !victim.IsNPC() ) buf += "summon 0." + name; else buf += "summon " + name; } else if ((ch.Level * 4 - 3) >= Spell.SpellList["spirit jump"].SpellCircle[(int)ch.CharacterClass.ClassNumber]) { if( !victim.IsNPC() ) buf += "'spirit jump' 0." + name; else buf += "'spirit jump' " + name; } CommandType.Interpret(ch, "cast " + buf); return; }
/// <summary> /// Check for use of Direct targeted spells (TargetType.singleCharacterOffensive) /// </summary> /// <param name="ch"></param> /// <param name="victim"></param> /// <param name="spell"></param> /// <param name="percent"></param> /// <returns></returns> public static bool CheckOffensive( CharData ch, CharData victim, Spell spell, int percent ) { if (ch == null) return false; string buf = String.Format("CheckOffensive: spell ({0})'{1}'", spell, spell.Name); Log.Trace( buf ); if( spell == null ) return false; if( ch.HasSpell(spell) && ( MUDMath.NumberPercent() < percent ) ) { if (spell.ValidTargets != TargetType.singleCharacterOffensive && spell.ValidTargets != TargetType.none && spell.ValidTargets != TargetType.singleCharacterRanged) Log.Error( "Check_spellup: Mob casting spell {0} which is neither _targetType offensive nor ignore.a", spell ); SocketConnection.Act( "$n&n starts casting...", ch, null, null, SocketConnection.MessageTarget.room ); ch.SetAffectBit( Affect.AFFECT_CASTING ); CastData caster = new CastData(); caster.Who = ch; caster.Eventdata = Event.CreateEvent(Event.EventType.spell_cast, spell.CastingTime, ch, victim, spell); Database.CastList.Add( caster ); return true; } return false; }
/// <summary> /// This is called by Command.Memorize and Commandpray. This was originally /// attached to each function, but it's silly to have two large blocks /// of the same code /// </summary> /// <param name="ch"></param> /// <param name="argument"></param> /// <param name="pray"></param> public static void Memorize( CharData ch, string argument, bool pray ) { string text; Dictionary<String, Int32> memmed = new Dictionary<String, Int32>(); int[] circle = new int[ Limits.MAX_CIRCLE ]; int[] circfree = new int[ Limits.MAX_CIRCLE ]; // with an argument they want to start memorizing a new spell. if( !String.IsNullOrEmpty(argument) ) { // Must be in the proper position if( ch.CurrentPosition != Position.resting ) { if( pray ) ch.SendText( "You can only pray for spells while resting.\r\n" ); else ch.SendText( "You can memorize spells only when resting.\r\n" ); return; } // Find the spell they want Spell spell = StringLookup.SpellLookup( argument ); if( spell == null ) { ch.SendText( "Never heard of that spell...\r\n" ); return; } // Check to see that they can memorize another spell // Immortals have no limits if( !ch.IsImmortal() ) { if( ( (PC)ch ).Hunger <= 0 || ( (PC)ch ).Thirst <= 0 ) { ch.SendText( "You can't seem to concentrate on anything but your appetite.\r\n" ); } if( !ch.HasSpell( argument ) ) { ch.SendText( "That spell is beyond you.\r\n" ); return; } if (!pray && ((PC)ch).SpellAptitude[spell.Name] < 1) { ch.SendText( "You have not yet learned that spell. Find a place to scribe it.\r\n" ); return; } int lvltotal = 0; foreach( MemorizeData mem in ((PC)ch).Memorized ) { if (mem.Circle == spell.SpellCircle[(int)ch.CharacterClass.ClassNumber]) lvltotal += 1; } int numMemmable = 0; if (ch.CharacterClass.MemType == CharClass.MemorizationType.Lesser) { numMemmable = LesserMemchart[(ch.Level - 1), (spell.SpellCircle[(int)ch.CharacterClass.ClassNumber] - 1)]; } else { numMemmable = Memchart[(ch.Level - 1), (spell.SpellCircle[(int)ch.CharacterClass.ClassNumber] - 1)]; } if (lvltotal >= numMemmable ) { if( pray ) ch.SendText( "You can pray for no more spells of that level.\r\n" ); else ch.SendText( "You can memorize no more spells of that circle.\r\n" ); return; } } // If we know what they want and they can have it, let's create it. MemorizeData memm = CreateMemorizeData( ch, spell ); if( !memm ) { Log.Error( "Unable to create memorization (spell {0})", spell ); return; } // If they're not already memorizing, they are now. ch.SetActionBit(PC.PLAYER_MEMORIZING ); if( pray ) text = String.Format( "You start praying for {0} which will take about {1} seconds.\r\n", spell.Name, ( memm.Memtime / Event.TICK_PER_SECOND ) ); else text = String.Format( "You start memorizing {0} which will take about {1} seconds.\r\n", spell.Name, ( memm.Memtime / Event.TICK_PER_SECOND ) ); ch.SendText( text ); return; } bool found = false; // If they didn't give us an argument, that means that they must // want to either see their spell list or continue memorizing // Either way we show their spell list. // make sure they have some mem data first... int count; if( ( (PC)ch ).Memorized.Count > 0 ) { // Figure out what spells they have memorized foreach( MemorizeData mem in ((PC)ch).Memorized ) { if( mem.Memmed ) { if (memmed.ContainsKey(mem.Name)) { memmed[mem.Name] = memmed[mem.Name] + 1; } else { memmed[mem.Name] = 1; } } } // Show memorized spells if( pray ) ch.SendText( "You have prayed the following spells:\r\n" ); else ch.SendText( "You have memorized the following spells:\r\n" ); int circleIndex; for( circleIndex = 12; circleIndex > 0; circleIndex-- ) { foreach (KeyValuePair<String, Spell> kvp in Spell.SpellList) { if (kvp.Value.SpellCircle[(int)ch.CharacterClass.ClassNumber] != circleIndex) continue; if( memmed.ContainsKey(kvp.Key) && memmed[ kvp.Key ] > 0 && kvp.Value.Name != null ) { text = String.Format( "({0,2}{1} circle) {2} - {3}\r\n", kvp.Value.SpellCircle[(int)ch.CharacterClass.ClassNumber], MUDString.NumberSuffix(kvp.Value.SpellCircle[(int)ch.CharacterClass.ClassNumber]), memmed[ kvp.Key ], kvp.Value.Name ); ch.SendText( text ); } } } //end for(circle) // Figure out what spells they are working on if( pray ) ch.SendText( "You are praying for the following spells:\r\n" ); else ch.SendText( "You are memorizing the following spells:\r\n" ); int totalMem = 0; // TODO: Make sure we cycle through this in the right order. // [ might need to do _memorized.Reverse() ] foreach( MemorizeData mem in ((PC)ch).Memorized ) { if( mem.Name == null ) break; if( mem.Memmed ) continue; found = true; text = String.Format( " {0} seconds: ({1}{2}) {3}\r\n", ( ( totalMem + mem.Memtime ) / Event.TICK_PER_SECOND ), Spell.SpellList[mem.Name].SpellCircle[(int)ch.CharacterClass.ClassNumber], MUDString.NumberSuffix(Spell.SpellList[mem.Name].SpellCircle[(int)ch.CharacterClass.ClassNumber]), mem.Name ); ch.SendText( text ); totalMem += mem.Memtime; } } // // Tell them what they still have slots open for... for( count = 0; count < Limits.MAX_CIRCLE; ++count ) circle[ count ] = 0; foreach( MemorizeData mem in ((PC)ch).Memorized ) { circle[ ( mem.Circle - 1 ) ] += 1; } bool left = false; for( count = 0; count < Limits.MAX_CIRCLE; ++count ) { int numMemmable = 0; if (ch.CharacterClass.MemType == CharClass.MemorizationType.Lesser) { numMemmable = LesserMemchart[(ch.Level - 1), count]; } else { numMemmable = Memchart[(ch.Level - 1), count]; } circfree[ count ] = numMemmable - circle[ count ]; if( circfree[ count ] > 0 ) left = true; } if( !left ) { if( pray ) ch.SendText( "\r\nYou can pray for no more spells.\r\n" ); else ch.SendText( "\r\nYou can memorize no more spells.\r\n" ); } else { if( pray ) text = String.Format( "\r\nYou can pray for" ); else text = String.Format( "\r\nYou can memorize" ); for( count = 0; count < Limits.MAX_CIRCLE; ++count ) { if( circfree[ count ] > 0 ) { string buf2 = String.Format( " {0}x{1}{2}", circfree[ count ], ( count + 1 ), MUDString.NumberSuffix( count + 1 ) ); text += buf2; } } text += " level spells.\r\n"; ch.SendText( text ); } // If they aren't memming and they should be, start 'em up. if( found && !ch.HasActionBit(PC.PLAYER_MEMORIZING ) && ch.CurrentPosition == Position.resting ) { ch.SetActionBit(PC.PLAYER_MEMORIZING ); if( ( (PC)ch ).Hunger > 0 && ( (PC)ch ).Thirst > 0 ) { if( pray ) ch.SendText( "You continue your prayers.\r\n" ); else ch.SendText( "You continue your studies.\r\n" ); } } return; }
/// <summary> /// When a spell event terminates, we need something to happen. /// /// By this point we should have terminated the spell/song event data /// and should only need the info about the character and the spell /// and the argument(s). /// /// Passing of the correct function parameters should be handled by the /// event system. /// </summary> /// <param name="ch">The caster</param> /// <param name="spell">The spell number</param> /// <param name="target">The _targetType</param> public static void FinishSpell( CharData ch, Spell spell, Target target ) { Object obj; int chance = 0; bool found = false; string lbuf = String.Format("Magic.FinishSpell: {0} by {1}", spell.Name, ch.Name); Log.Trace( lbuf ); for( int i = Database.CastList.Count - 1; i >= 0; i--) { if (Database.CastList[i].Who && Database.CastList[i].Who == ch) { Database.CastList.RemoveAt( i ); } } // If they're not casting at the end of the song or spell // they certainly can't finish it. if( ch.IsAffected( Affect.AFFECT_CASTING ) ) ch.RemoveAffect( Affect.AFFECT_CASTING ); else { return; } if( !ch.CheckConcentration(spell) ) return; if( ( ch.IsAffected( Affect.AFFECT_MUTE ) || ch.HasInnate( Race.RACE_MUTE ) ) && !ch.IsClass( CharClass.Names.psionicist ) ) { ch.SendText( "Your lips move but no sound comes out.\r\n" ); return; } // Make sure the room is still castable. if( !ch.InRoom.CheckCastable( ch, false, false ) ) return; if( ch.InRoom.CheckStarshell( ch ) ) return; MemorizeData memorized = null; if (!ch.IsNPC() && !ch.IsImmortal() && !ch.IsClass(CharClass.Names.psionicist)) { foreach( MemorizeData mem in ((PC)ch).Memorized ) { if( !mem.Memmed ) continue; if( mem.Name == spell.Name ) { found = true; memorized = mem; break; } } if (!found && !ch.IsNPC() && !ch.IsClass(CharClass.Names.psionicist)) { ch.SendText( "You do not have that spell memorized!\r\n" ); if( spell.ValidTargets == TargetType.objectOrCharacter ) target = null; else if( spell.ValidTargets == TargetType.none ) target = null; return; } } if( ch.IsAffected( Affect.AFFECT_FEEBLEMIND ) ) { ch.SendText( "You are just too stupid to cast that spell!\r\n" ); SocketConnection.Act( "$n&n screws up $s face in concentration.", ch, null, null, SocketConnection.MessageTarget.room ); SocketConnection.Act( "$n&n tries really, really hard to complete a spell, but fails.", ch, null, null, SocketConnection.MessageTarget.room ); return; } // Locate targets. CharData victim = null; switch( spell.ValidTargets ) { default: Log.Trace( "FinishSpell: bad TargetType for spell {1}.", spell ); return; case TargetType.objectOrCharacter: if( ch.IsAffected( Affect.AFFECT_BLIND ) ) { ch.SendText( "You cannot see to cast that spell.\r\n" ); return; } break; case TargetType.none: break; case TargetType.trap: ch.SendText( "You cannot cast a trap!\r\n" ); return; case TargetType.singleCharacterOffensive: victim = (CharData)target; if( ch.IsAffected( Affect.AFFECT_BLIND ) ) { //allow casting if in combat and no _targetType specified if( !( ch.Fighting && victim == ch.Fighting ) ) { ch.SendText( "You cannot see to cast that spell.\r\n" ); return; } } if( !victim ) { ch.SendText( "They aren't here.\r\n" ); return; } if( !victim.InRoom || victim.InRoom != ch.InRoom ) { ch.SendText( "They are not here.\r\n" ); return; } if( Combat.IsSafe( ch, victim ) ) return; // Command.is_safe could wipe out victim, as it calls procs if a boss // check and see that victim is still valid if( !victim ) return; Crime.CheckAttemptedMurder( ch, victim ); /* Check for globes. This will stop any spells of type TargetType.singleCharacterOffensive * but area effect spells with type TargetType.none will get through, since we * don't know whether they will be offensive or not. The only thing we can * really do is add this same thing in the Command.SpellDamage function to prevent * those from getting through. However, we must treat cases of things like * an area effect sleep spell as a special case within the SpellWhatever * function in Spells.cs. However, by the nature of the spell, anything * that is not either offensive and not direct damage, it should get through * just so that these spells have some weaknesses for the strategic to get * around. */ /* * TODO: Find out why this globe code was commented out and either uncomment or delete. if( CharData.IsAffected( victim, Affect.AFFECT_MAJOR_GLOBE ) && Spell.Table[spell].spell_circle[ch.cclass] <= 6 ) { Descriptor._actFlags( "&+RThe globe around $N&n's body bears the brunt of your assault!&n", ch, null, victim, Descriptor.MessageTarget.character ); Descriptor._actFlags( "&+RYour globe deflects $n&+R's attack!&n", ch, null, victim, Descriptor.MessageTarget.victim ); Descriptor._actFlags( "&+R$N&+R's globe deflects $n&+R's attack!&n", ch, null, victim, Descriptor.MessageTarget.room ); return; } if( CharData.IsAffected( victim, Affect.AFFECT_GREATER_SPIRIT_WARD ) && Spell.Table[spell].spell_circle[ch.cclass] <= 5 ) { Descriptor._actFlags( "&+WThe aura around $N&n's body bears the brunt of your assault!&n", ch, null, victim, Descriptor.MessageTarget.character ); Descriptor._actFlags( "&+WYour globe absorbs $n&+W's attack!&n", ch, null, victim, Descriptor.MessageTarget.victim ); Descriptor._actFlags( "&+W$N&+W's aura absorbs $n&+W's attack!&n", ch, null, victim, Descriptor.MessageTarget.room ); return; } if( CharData.IsAffected( victim, Affect.AFFECT_MINOR_GLOBE ) && Spell.Table[spell].spell_circle[ch.cclass] <= 4 ) { Descriptor._actFlags( "&+RThe globe around $N&n's body bears the brunt of your assault!&n", ch, null, victim, Descriptor.MessageTarget.character ); Descriptor._actFlags( "&+RYour globe deflects $n&+R's attack!&n", ch, null, victim, Descriptor.MessageTarget.victim ); Descriptor._actFlags( "&+R$N&+R's globe deflects $n&+R's attack!&n", ch, null, victim, Descriptor.MessageTarget.room ); return; } if( CharData.IsAffected( victim, Affect.AFFECT_SPIRIT_WARD ) && Spell.Table[spell].spell_circle[ch.cclass] <= 3 ) { Descriptor._actFlags( "&+WThe aura around $N&n's body bears the brunt of your assault!&n", ch, null, victim, Descriptor.MessageTarget.character ); Descriptor._actFlags( "&+WYour globe absorbs $n&+W's attack!&n", ch, null, victim, Descriptor.MessageTarget.victim ); Descriptor._actFlags( "&+W$N&+W's aura absorbs $n&+W's attack!&n", ch, null, victim, Descriptor.MessageTarget.room ); return; } */ break; case TargetType.singleCharacterWorld: victim = (CharData)target; if( ch.IsAffected( Affect.AFFECT_BLIND ) && victim != ch ) { ch.SendText( "You cannot see to cast that spell.\r\n" ); return; } break; case TargetType.singleCharacterDefensive: victim = (CharData)target; if( ch.IsAffected( Affect.AFFECT_BLIND ) && victim != ch ) { ch.SendText( "You cannot see to cast that spell.\r\n" ); return; } if( !victim || victim.InRoom != ch.InRoom ) { ch.SendText( "They aren't here.\r\n" ); return; } break; case TargetType.self: break; case TargetType.objectInInventory: obj = (Object)target; if( ch.IsAffected( Affect.AFFECT_BLIND ) ) { ch.SendText( "You cannot see to cast that spell.\r\n" ); return; } if( !obj || obj.CarriedBy != ch ) { ch.SendText( "You are not carrying that.\r\n" ); return; } break; case TargetType.objectInRoom: obj = (Object)target; if( ch.IsAffected( Affect.AFFECT_BLIND ) ) { ch.SendText( "You cannot see to cast that spell.\r\n" ); return; } if( !obj || ( obj.CarriedBy != ch && obj.InRoom != ch.InRoom ) ) { ch.SendText( "You do not see that here.\r\n" ); return; } break; case TargetType.objectCorpse: break; case TargetType.singleCharacterRanged: victim = (CharData)target; if( ch.IsAffected( Affect.AFFECT_BLIND ) ) { ch.SendText( "You cannot see to cast that spell.\r\n" ); return; } if( !victim || victim.FlightLevel != ch.FlightLevel || !CharData.CanSee( ch, victim ) ) { ch.SendText( "Your prey has disappeared.\r\n" ); return; } //check that _targetType is still within the spell range if( ch.InRoom == victim.InRoom ) { break; } bool targetInRange = false; int dir; for( dir = 0; dir < Limits.MAX_DIRECTION; dir++ ) { if( !ch.InRoom.ExitData[ dir ] || ch.InRoom.ExitData[ dir ].HasFlag( Exit.ExitFlag.secret ) || ch.InRoom.ExitData[ dir ].HasFlag( Exit.ExitFlag.closed ) || ch.InRoom.ExitData[ dir ].HasFlag( Exit.ExitFlag.blocked ) || ch.InRoom.ExitData[dir].HasFlag(Exit.ExitFlag.walled)) continue; if( ch.InRoom.ExitData[ dir ].TargetRoom == victim.InRoom ) { targetInRange = true; break; } // for fireball we check two rooms away if( ch.InRoom.ExitData[ dir ].TargetRoom && ch.InRoom.ExitData[ dir ].TargetRoom.ExitData[ dir ] && ch.InRoom.ExitData[ dir ].TargetRoom.ExitData[ dir ].TargetRoom == victim.InRoom ) { targetInRange = true; break; } } if( !targetInRange ) { ch.SendText( "They are no longer in range!\r\n" ); return; } break; } // No wait state - we already made them wait. ch.PracticeSpell( spell ); if (ch.IsNPC()) { chance = 85; } else if (ch.HasSpell(spell.Name)) { chance = ((PC)ch).SpellAptitude[spell.Name]; } if( !ch.IsImmortal() && ( MUDMath.NumberPercent() > chance ) ) { ch.SendText( "You lost your concentration.\r\n" ); SocketConnection.Act( "&+r$n&n&+r stops chanting abruptly.&n", ch, null, null, SocketConnection.MessageTarget.room ); } else { // TODO: Figure out whether this should be re-enabled. //if( song ) //{ // ch.SendText( "You complete a verse of the song...\r\n" ); // ch.GainExperience( 1 ); // SaySong( ch, spell ); //} //else { if (!ch.IsClass(CharClass.Names.psionicist)) { ch.SendText( "You complete your spell...\r\n" ); if( MUDString.StringsNotEqual( spell.Name, "ventriloquate" ) ) SaySpell( ch, spell ); } ch.GainExperience( 1 ); } if( !ch.IsNPC() ) { string buf = String.Format( "Spell ({0}) being cast by {1}", spell.Name, ch.Name ); Log.Trace( buf ); } int level = Macros.Range(1, ch.Level, Limits.LEVEL_HERO); spell.Invoke(ch, level, target); if( memorized && !ch.IsNPC() && !ch.IsImmortal() ) { memorized.Memmed = false; memorized.Memtime = memorized.FullMemtime; } } if( ( spell.ValidTargets == TargetType.singleCharacterOffensive || spell.ValidTargets == TargetType.singleCharacterRanged ) && victim && victim.Master != ch && victim != ch && victim.IsAwake() ) { if( ch.InRoom == victim.InRoom ) { if( !victim.Fighting && CharData.CanSee( victim, ch ) ) victim.AttackCharacter( ch ); } else { // Range spell presumably, since different rooms Combat.StartGrudge( victim, ch, true ); foreach( CharData roomChar in ch.InRoom.People ) { if( roomChar == victim ) continue; if( roomChar.FlightLevel != ch.FlightLevel ) continue; //protectors will be aggro'd if (roomChar.HasActionBit(MobTemplate.ACT_PROTECTOR) && (roomChar.GetRace() == victim.GetRace())) { Combat.StartGrudge( roomChar, ch, true ); } // all aggro mobs will hunt down caster if (roomChar.HasActionBit(MobTemplate.ACT_AGGRESSIVE)) { Combat.StartGrudge(roomChar, ch, true); } } } } return; }
/// <summary> /// Handles all spellcasting, whether it be willing, singing, or casting /// If they got here as a bard, they're using the SING command word, /// if they got here as a psionicist, they're using the WILL command word, /// and if they got here as anything else, they're using CAST. /// /// These are just cheesy details handled by CommandType.cs... we don't care. /// What we do care about is that we *know* it's safe to base all our /// messages/decisions on the character's class. /// /// This function is also *mob-safe*, meaning that mobs can cast spells /// too. However, this is not the preferred method (as far as I can tell) /// /// Shaman totems are checked for in this function. /// </summary> /// <param name="ch"></param> /// <param name="argument"></param> public static void Cast( CharData ch, string argument ) { // No casting while berzerked... Nor singing! Hah! if (ch.IsAffected(Affect.AFFECT_BERZERK)) { ch.SendText( "Not while you're in a &+RBl&n&+ro&+Ro&n&+rd&+L Rage&n!\r\n" ); return; } // No casting while thirsty... Nor singing! Hah! if (ch.IsAffected(Affect.AFFECT_THIRST) && ( ch.IsNPC() || ( (PC)ch ).Thirst < 10 ) ) { ch.SendText( "&+BNo&+Ct w&+chi&+ble &+cyo&+Bu'&+cre &+Bso p&+carc&+Bhed&n!\r\n" ); return; } String[] pieces = argument.Split( new Char[] {'\''}, StringSplitOptions.RemoveEmptyEntries); if (pieces.Length < 1) { ch.SendText("Spell names must always be in single quotes, such as: cast 'magic missile' troll.\r\n"); return; } if (pieces.Length > 1) { pieces[1] = pieces[1].Trim(); } Spell spell; if (((spell = StringLookup.SpellLookup(pieces[0])) == null) || ((!ch.HasSpell(pieces[0])) && !ch.IsImmortal())) { ch.SendText( "You can't do that.\r\n" ); return; } if( !CheckTotem( ch, spell )) return; if( !ch.CheckConcentration( spell ) ) return; if (!ch.CheckMemorized(spell)) return; if( ( !ch.CanSpeak() || ch.HasInnate(Race.RACE_MUTE)) && !ch.IsClass(CharClass.Names.psionicist)) { ch.SendText( "Your lips move but no sound comes out.\r\n" ); return; } if( !ch.InRoom.CheckCastable( ch, ch.IsClass( CharClass.Names.bard ), true) ) return; if( ch.InRoom.CheckStarshell( ch ) ) return; int manaUsed = 0; // TODO: Rather than hard-code psionicist as a mana class, let that be set in the class files. if (ch.IsClass(CharClass.Names.psionicist)) { manaUsed = Macros.ManaCost(ch, spell); } else if (ch.IsClass(CharClass.Names.bard)) { manaUsed = spell.MinimumMana; } // Locate targets. if( ch.IsNPC() ) { ImmortalChat.SendImmortalChat( null, ImmortalChat.IMMTALK_SPAM, 0, "Magic.Cast: Attempting to find _targetType for " + ch.ShortDescription + "&n." ); } if (pieces.Length > 1) { ProcessSpellTargets(ch, spell, pieces[1]); } else { ProcessSpellTargets(ch, spell, null); } }