Full Version : Scripting an item that affects an objective
xmlspawner >>Scripting Support >>Scripting an item that affects an objective


<< Prev | Next >>

Ednyved- 01-20-2008
Hi, all.

I'm wondering if it is possible (and if I could get an example of how it might be done) to affect a questholder objective though a script. I'm designing a quest in which players are asked to study rare or endangered creatures. I have a pen which, when double clicked, begins the studying/notetaking process.

When a player succeeds in the note-taking, I'd like the objective set to completed. The pen is fully functional minus this one step. In fact, here is the script, as it might make it easier to see what I intend. It's essentially a modified bandage script:

CODE

using System;
using System.Collections.Generic;
using Server;
using Server.Gumps;
using Server.Items;
using Server.Mobiles;
using Server.Network;
using Server.Targeting;
using Server.Engines.XmlSpawner2;

namespace Server.Items
{
public class NaturePen : Item
{
 public static int Range = ( 5 );

 public override double DefaultWeight
 {
  get { return 0.1; }
 }

 [Constructable]
       public NaturePen(int amount) : base(0x0FBF)
 {
  Stackable = false;
  Amount = amount;
           Name = "Irwin's Pen";
           Hue = 2006;
 }

 public NaturePen( Serial serial ) : base( serial )
 {
 }

 public override void Serialize( GenericWriter writer )
 {
  base.Serialize( writer );

  writer.Write( (int) 0 ); // version
 }

 public override void Deserialize( GenericReader reader )
 {
  base.Deserialize( reader );

  int version = reader.ReadInt();
 }

 public override void OnDoubleClick( Mobile from )
 {
  if ( from.InRange( GetWorldLocation(), Range ) )
  {
               from.SendMessage ("Which creature do you wish to observe?");

   from.Target = new InternalTarget( this );
  }
  else
  {
               from.SendMessage("You must be closer to the creature in order to properly observe it.");
  }
 }

 private class InternalTarget : Target
 {
  private NaturePen m_NaturePen;

           public InternalTarget(NaturePen pen) : base(NaturePen.Range, false, TargetFlags.None)
  {
   m_NaturePen = pen;
  }

  protected override void OnTarget( Mobile from, object targeted )
  {
   if ( m_NaturePen.Deleted )
    return;

   if ( targeted is Mobile )
               {
                   if (from.InRange(m_NaturePen.GetWorldLocation(), NaturePen.Range))
                   {    
                       if (NaturePenContext.BeginNotes(from, (Mobile)targeted) != null)
                       {
                           if ((Mobile)targeted is PlayerMobile)
                           {
                               from.SendMessage("That person, although fascinating, is not a member of an endangered species.");
                           }
                       }
                   }
                   else
                   {
                       from.SendMessage("You must be closer to the creature in order to properly observe it.");
                   }
               }
   else
   {
                   from.SendMessage("You ponder it for a moment, but decide not to waste time taking any notes.");
   }
  }
 }
}

public class NaturePenContext
{
 private Mobile m_Notetaker;
 private Mobile m_Targetcreature;
 private int m_Slips;
 private Timer m_Timer;

 public Mobile Notetaker{ get{ return m_Notetaker; } }
 public Mobile Targetcreature{ get{ return m_Targetcreature; } }
 public int Slips{ get{ return m_Slips; } set{ m_Slips = value; } }
 public Timer Timer{ get{ return m_Timer; } }

 public void Slip()
 {
           m_Notetaker.SendMessage("Your note-taking has been disturbed!");
  ++m_Slips;
 }

 public NaturePenContext( Mobile notetaker, Mobile targetcreature, TimeSpan delay )
 {
  m_Notetaker = notetaker;
  m_Targetcreature = targetcreature;

  m_Timer = new InternalTimer( this, delay );
  m_Timer.Start();
 }

 public void StopNotes()
 {
  m_Table.Remove( m_Notetaker );

  if ( m_Timer != null )
   m_Timer.Stop();

  m_Timer = null;
 }

 private static Dictionary<Mobile, NaturePenContext> m_Table = new Dictionary<Mobile, NaturePenContext>();

 public static NaturePenContext GetContext( Mobile notetaker )
 {
  NaturePenContext bc = null;
  m_Table.TryGetValue( notetaker, out bc );
  return bc;
 }

 public static SkillName GetPrimarySkill( Mobile m )
 {
   return SkillName.Inscribe;
 }

 public static SkillName GetSecondarySkill( Mobile m )
 {
   return SkillName.AnimalLore;
 }

 public void EndNotes()
 {
  StopNotes();

  int notetakerNumber = -1, targetcreatureNumber = -1;
  bool playSound = true;
  bool checkSkills = false;

  SkillName primarySkill = GetPrimarySkill( m_Targetcreature );
  SkillName secondarySkill = GetSecondarySkill( m_Targetcreature );

  BaseCreature petTargetcreature = m_Targetcreature as BaseCreature;

  if ( !m_Notetaker.Alive )
  {
   notetakerNumber = 500962; // You were unable to finish your work before you died.
   targetcreatureNumber = -1;
   playSound = false;
  }
  else if ( !m_Notetaker.InRange( m_Targetcreature, NaturePen.Range ) )
  {
               m_Notetaker.SendMessage("You did not stay close enough to properly observe your target!");
   targetcreatureNumber = -1;
   playSound = false;
  }
           else if (!m_Targetcreature.Alive)
           {
               m_Notetaker.SendMessage("You must observe and record the behaviors of a living creature!");
           }
           else //if (creature from list one... repeat for the 4 other lists)
           {
               targetcreatureNumber = -1;

               double inscription = m_Notetaker.Skills[primarySkill].Value;
               double anatomy = m_Notetaker.Skills[secondarySkill].Value;
               double chance = ((inscription) / 100.0) - (m_Slips * 0.02);

               if (chance > Utility.RandomDouble())
               {
                   m_Notetaker.SendMessage("You successfully complete your observation of the creature!");
                   //TODO: Set a specific objective on a questholder to complete
               }
               else
               {
                   switch (Utility.Random(4))
                   {
                       case 0: m_Notetaker.SendMessage("Your notes are barely comprehensible. Try again, and this time aim for clarity and precision!");
                       playSound = false;
                       break;

                       case 1: m_Notetaker.SendMessage("Your drawing fails to capture the essence of the creature. Try again, and this time cast aside the constructs of your brain and draw what you see!");
                       playSound = false;
                       break;

                       case 2: m_Notetaker.SendMessage("Your writing lacks the detail necessary to truly capture the essence of the creature in words. Try again!");
                       playSound = false;
                       break;

                       case 3: m_Notetaker.SendMessage("You look over your notes and decide that further observation is necessary.");
                       playSound = false;
                       break;
                   }
               }
           }

  if ( notetakerNumber != -1 )
   m_Notetaker.SendLocalizedMessage( notetakerNumber );

  if ( targetcreatureNumber != -1 )
   m_Targetcreature.SendLocalizedMessage( targetcreatureNumber );

  if ( playSound )
               m_Targetcreature.PlaySound(0x249); // should be the writing noise

  if ( checkSkills )
  {
   m_Notetaker.CheckSkill( secondarySkill, 0.0, 120.0 );
   m_Notetaker.CheckSkill( primarySkill, 0.0, 120.0 );
  }
 }

 private class InternalTimer : Timer
 {
  private NaturePenContext m_Context;

  public InternalTimer( NaturePenContext context, TimeSpan delay ) : base( delay )
  {
   m_Context = context;
   Priority = TimerPriority.FiftyMS;
  }

  protected override void OnTick()
  {
   m_Context.EndNotes();
  }
 }

 public static NaturePenContext BeginNotes( Mobile notetaker, Mobile targetcreature )
 {
  bool isDeadPet = ( targetcreature is BaseCreature && ((BaseCreature)targetcreature).IsDeadPet );

  if ( targetcreature is Golem )
  {
               notetaker.SendMessage("You get the feeling that the tinkers of the world already have fairly extensive Golem documentation."); // NaturePens cannot be used on that.
  }
  else if ( targetcreature is BaseCreature && ((BaseCreature)targetcreature).IsAnimatedDead )
  {
               notetaker.SendMessage("You wonder if the necromancers would appreciate your efforts and think the better of it.");
  }
           else
  {
   //bool onSelf = ( notetaker == targetcreature );
   int dex = notetaker.Dex;

   double seconds;
   double resDelay = ( targetcreature.Alive ? 0.0 : 5.0 );

     if ( dex >= 190 )

                           seconds = 10.0 + resDelay;
     else if ( dex >= 100 )

                           seconds = 15.0 + resDelay;
     else

                           seconds = 20.0 + resDelay;


   NaturePenContext context = GetContext( notetaker );

   if ( context != null )
    context.StopNotes();

   context = new NaturePenContext( notetaker, targetcreature, TimeSpan.FromSeconds( seconds ) );

   m_Table[notetaker] = context;

   //if ( !onSelf )
                   //replace this with a flirty message
    //targetcreature.SendLocalizedMessage( 1008078, false, notetaker.Name ); //  : Attempting to heal you.

               notetaker.SendMessage("You begin the record of your observations.");
   return context;
  }

  return null;
 }
}
}


I would certainly appreciate any help you might be able to give. I have also considered having the successful observation set an attachment on the player which would trigger a spawner near the xmlquestnpc to set the objective to complete (and then delete the attachment for repeatablilty), but I'd prefer a more direct approach if possible.

Thank you very much!

ArteGordon- 01-22-2008
If you know which objective number you want to set, then it is easy. You can find the xmlquestholder that your player is carrying with a call like this

CODE
XmlQuestHolder quest = (XmlQuestHolder ) BaseXmlSpawner.SearchMobileForItem(m_Notetaker, questname, "XmlQuestHolder" , false);


and then set the objective to completed with something like

CODE
if(quest != null)
   quest.Completed1 = true;


If it was the second objective then you would want to set Completed2, etc.
You would need to substitute the name of your quest for the 'questname' argument.

You will probably have to add this at the top of the script as well

CODE
using Server.Engines.XmlSpawner2;


I assume that you would want to add that code in your EndNotes() method where you have the TODO indicated.

Your question gave me an idea for adding support for setting custom objectives by name instead of just by number. I'll think about that for an upcoming release.

Ednyved- 01-23-2008
Thank you very much, Arte. I'll give it a go.

Ednyved- 01-28-2008
It worked very well; thanks again. I see what you mean now about objectives by name! That'd be a great feature, for sure, and would prevent some exploiting that the current script leaves possible.

ArteGordon- 01-29-2008
yeah, the idea would be that could specify a custom objective like

MYOBJECTIVE

in your quest objective string, and then in your scripts you could use calls like

SetCustomObjectiveStatus(quest, "MYOBJECTIVE", true)

instead of

quest.Completed1 = true;

and

GetCustomObjectiveStatus(quest, "MYOBJECTIVE")

to check the value of the associated Completed property.

and perhaps spawner keywords like

SETOBJECTIVESTATUS,MYOBJECTIVE/true

to set the status

and

GETOBJECTIVESTATUS,MYOBJECTIVE

to get the status