Hey there,

I am trying to get a dishing stump to work as a tool for defAlchemy using this post as an example, but having a few hangups and any help would be appreciated.

- The dishing stump should either not wear out at all or last for several hundred uses.
- Currently it allows the Alchemy gump when standing next to it, however when clicking OK to craft the desired item it still wants the dishing stump in the backpack.

Dishing Stump:
using System;
using Server.Network;
using Server.Engines.Craft;

namespace Server.Items
{

   [FlipableAttribute( 0x1865, 0x1866 )]
   public class DishingStump : Item
   {
      public override CraftSystem CraftSystem {get {return DefAlchemy.CraftSystem;} }
      public override void OnDoubleClick(Mobile from)
      {
         if ((from.InRange(this, 2) && from.CanSee(this)) || IsChildOf(from.Backpack) || Parent == from)
         {

            CraftSystem system = CraftSystem;

            int num = system.CanCraft(from, this, null);

            {
               CraftContext context = system.GetContext(from);

               from.SendGump(new CraftGump(from, system, this, null));
            }
         }
         else
         {
            from.SendMessage("This must be in your pack or 1 tile away for you to use it.");
         }
      }

      [Constructable]
      public DishingStump() : base( 0x1865 )
      {
         Name = "Dishing Stump";
         Weight = 10.0;
      }

      public DishingStump( 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();
      }
   }
}
 
change

Code:
if ((from.InRange(this, 2) && from.CanSee(this)) || IsChildOf(from.Backpack) || Parent == from)

to

Code:
if ((from.InRange(this, 2) && from.CanSee(this)) || Parent == from)
 
change

Code:
if ((from.InRange(this, 2) && from.CanSee(this)) || IsChildOf(from.Backpack) || Parent == from)

to

Code:
if ((from.InRange(this, 2) && from.CanSee(this)) || Parent == from)

I've made the suggested change, however the gump still presents with error.

1595088391587.png
 
Crafting checks for the tool in other scripts too. DefAlchemy.cs also checks for the tool to be in your pack, and that's the same part where the tool's uses are checked to see if they're worn out -- and yours doesn't need that.

Code:
        public override int CanCraft(Mobile from, ITool tool, Type itemType)
        {
            int num = 0;

            if (tool == null || tool.Deleted || tool.UsesRemaining <= 0)
                return 1044038; // You have worn out your tool!
            else if (!tool.CheckAccessible(from, ref num))
                return num; // The tool must be on your person to use.

            return 0;
        }

You could try a quick addition to that section to bypass that check for your dishing stump. I have no idea if this will work but...

Code:
        public override int CanCraft(Mobile from, ITool tool, Type itemType)
        {
        if (tool is DishingStump)
        return 0;

            int num = 0;

            if (tool == null || tool.Deleted || tool.UsesRemaining <= 0)
                return 1044038; // You have worn out your tool!
            else if (!tool.CheckAccessible(from, ref num))
                return num; // The tool must be on your person to use.

            return 0;
        }

just might do what you're looking for! It should let your dishing stump be used without checks (since you check for distance within the tool's script, that's ok) and it won't have any uses to wear out -- also good since your dishing stump is an item and not a BaseTool.
 
So the suggested edit worked in that I am able to craft now from the menu, but a few things to note:
  • I was unable to use the public overrides necessary to open the crafting menu with a public class of item, I had to change it to basetool, otherwise it fails to compile with
    'Server.Items.DishingStump.CraftSystem': no suitable method found to override
  • Range check does not occur in continued use of the gump, so I can effectively start the gump from a locked down dishing stump and then hit a moongate and continue crafting.
  • Tried wrapping the SendGump after first craft call in another range check, but that didn't work, so thinking I have to do an override for one of the crafting gump scripts.
So current edits as stand:
DefAlchemy.cs:
        public override int CanCraft( Mobile from, BaseTool tool, Type itemType )
        {
            if( tool is DishingStump)
                return 0; // Ignore uses remaining and on character requirement (needed for Custom/DishingStump.cs)
            else if( tool == null || tool.Deleted || tool.UsesRemaining < 0 )
                return 1044038; // You have worn out your tool!
            else if ( !BaseTool.CheckAccessible( tool, from ) )
                return 1044263; // The tool must be on your person to use.
            return 0;
        }
and

DishingStump.cs:
using System;
using Server.Network;
using Server.Engines.Craft;

namespace Server.Items
{

   [FlipableAttribute( 0x1865, 0x1866 )]
   public class DishingStump : BaseTool
   {
      public override CraftSystem CraftSystem{ get{ return DefAlchemy.CraftSystem; } }
      public override void OnDoubleClick(Mobile from)
      {
         if ((from.InRange(this, 1) && from.CanSee(this)) || Parent == from)
         {

            CraftSystem system = CraftSystem;

            int num = system.CanCraft(from, this, null);

            {
               CraftContext context = system.GetContext(from);

               from.SendGump(new CraftGump(from, system, this, null));
            }
         }
         else
         {
            from.SendMessage("This must be in your pack or 1 tile away for you to use it.");
         }
      }

      [Constructable]
      public DishingStump() : base( 0x1865 )
      {
         Name = "Dishing Stump";
         Weight = 10.0;
      }

      public DishingStump( 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();
      }
   }
}
 
There are two solutions to the issue. One is really involved, doing something in DefAlchemy like is done in DefBlacksmithy to check for a nearby anvil and forge. I was going to post the CheckAnvilAndForge part here and then a simpler fix dawned on me...

Do it like the in-game store does and make the player CantWalk=true when they use the DishingStump! Then set it to false when the gump is closed.

Put the from.cantwalk = true in the OnDoubleClick override of the dishing stump script. Then edit CraftGump.cs like this:

Code:
        public override void OnResponse(NetState sender, RelayInfo info)
        {
            if (info.ButtonID <= 0)
        {
        m_From.CantWalk = false;
                return; // Canceled
        }

It'll set it to false any time anyone exits any crafting menu but you won't care since it'll already be false. On the rare chance that someone disconnects or crashes while crafting and frozen, they can just open the DishingStump crafting menu and exit again.
Post automatically merged:

Let me change that slightly to this:

Code:
        public override void OnResponse(NetState sender, RelayInfo info)
        {
            if (info.ButtonID <= 0)
        {
        if (m_CraftSystem is DefAlchemy)
        m_From.CantWalk = false;

                return; // Canceled
        }

It now checks to make sure they're exiting an alchemy crafting menu, just in case someone had the bright idea of opening a different crafting tool to get free. I may be over-thinking that part since I think think every crafting menu closes all others, but it never hurts to be a little cautious :D
 
Last edited:
So taking a look at DefBlacksmithy.cs, it occurred to me with this section, though I don't know how everything interacts just yet, that I might be able to work in some bug/exploit control.


DefBlacksmithy.cs Lines 149-165:
        public override int CanCraft( Mobile from, BaseTool tool, Type itemType )
        {
            if ( tool == null || tool.Deleted || tool.UsesRemaining < 0 )
                return 1044038; // You have worn out your tool!
            else if ( !BaseTool.CheckTool( tool, from ) )
                return 1048146; // If you have a tool equipped, you must use that tool.
            else if ( !BaseTool.CheckAccessible( tool, from ) )
                return 1044263; // The tool must be on your person to use.

            bool anvil, forge;
            CheckAnvilAndForge( from, 2, out anvil, out forge );

            if ( anvil && forge )
                return 0;

            return 1044267; // You must be near an anvil and a forge to smith items.
        }

I'm thinking if I did something similar to this, but I'd prefer to override in the DishingStump script, so that it works without having to modify core files. I need to brush up on my scripting for this. I can do other languages decently enough but never spent the time I need on this.
 
If you don't go with my simple cantwalk approach you'll have a lot bigger project on your hands.

First you'll have to do a check like this for Anvil / Forge with one looking for your dishingstump.

Code:
        public static void CheckAnvilAndForge(Mobile from, int range, out bool anvil, out bool forge)
        {
            anvil = false;
            forge = false;

            Map map = from.Map;

            if (map == null)
            {
                return;
            }

            IPooledEnumerable eable = map.GetItemsInRange(from.Location, range);

            foreach (Item item in eable)
            {
                Type type = item.GetType();

                bool isAnvil = (type.IsDefined(typeofAnvil, false) || item.ItemID == 4015 || item.ItemID == 4016 ||
                                item.ItemID == 0x2DD5 || item.ItemID == 0x2DD6);
                bool isForge = (type.IsDefined(typeofForge, false) || item.ItemID == 4017 ||
                                (item.ItemID >= 6522 && item.ItemID <= 6569) || item.ItemID == 0x2DD8);

                if (!isAnvil && !isForge)
                {
                    continue;
                }

                if ((from.Z + 16) < item.Z || (item.Z + 16) < from.Z || !from.InLOS(item))
                {
                    continue;
                }

                anvil = anvil || isAnvil;
                forge = forge || isForge;

                if (anvil && forge)
                {
                    break;
                }
            }

            eable.Free();

            for (int x = -range; (!anvil || !forge) && x <= range; ++x)
            {
                for (int y = -range; (!anvil || !forge) && y <= range; ++y)
                {
                    var tiles = map.Tiles.GetStaticTiles(from.X + x, from.Y + y, true);

                    for (int i = 0; (!anvil || !forge) && i < tiles.Length; ++i)
                    {
                        int id = tiles[i].ID;

                        bool isAnvil = (id == 4015 || id == 4016 || id == 0x2DD5 || id == 0x2DD6);
                        bool isForge = (id == 4017 || (id >= 6522 && id <= 6569) || id == 0x2DD8);

                        if (!isAnvil && !isForge)
                        {
                            continue;
                        }

                        if ((from.Z + 16) < tiles[i].Z || (tiles[i].Z + 16) < from.Z ||
                            !from.InLOS(new Point3D(from.X + x, from.Y + y, tiles[i].Z + (tiles[i].Height / 2) + 1)))
                        {
                            continue;
                        }

                        anvil = anvil || isAnvil;
                        forge = forge || isForge;
                    }
                }
            }
        }

Don't forget both ItemIDs for the stump. That's a bigger PITA than I'm going to attempt to code. As you can see from the existing code it's a real endeavor to find out if you're standing next to a specific item, taking into account z values and LOS, and even access levels.

I think that's why they coded the UO Store the same way -- it's easiest to freeze you while the interface is open to keep you from opening the gump and then running somewhere else.

I've already posted the exact cantwalk code needed for CraftGump.cs. Edit your dishingstump to this and you'll be ready to go.

Code:
      public override void OnDoubleClick(Mobile from)
      {
         if ((from.InRange(this, 2) && from.CanSee(this)) || IsChildOf(from.Backpack) || Parent == from)
         {

            CraftSystem system = CraftSystem;

            int num = system.CanCraft(from, this, null);

            {
               CraftContext context = system.GetContext(from);

               from.CantWalk = true;  // This is the added line, freezing player while crafting gump is open
               from.SendGump(new CraftGump(from, system, this, null));
            }
<snip>
 
I'll try it out, are there any scenarios you're aware of where a player would be legitimately be stuck in CantWalk upon login? I may add a check state so the users are not required to reopen the stump. As the stumps themselves break currently (haven't been able to drop the uses remaining), someone could arguably get flagged CantWalk, disconnect, then someone uses the remaining uses of dishing stump without replacing it and the logging in player has no nearby tool to unflag CantWalk.
 
Instead of using cantwalk, use an onmovement check, so if the player moves, the gump disposes!

This is perfect, sorry, it is something I should know but I'm rusty in practice and mixed learning from all the previous emulators.

I tried grabbing the Public Moongate as an example, but it fails to close the gump. Either it's a bad example to use or there is something derp I am doing. Going to keep poking it with a stick XD.

DishingStump.cs:
using System;
using Server.Network;
using Server.Gumps;
using Server.Mobiles;
using Server.Engines.Craft;

namespace Server.Items
{

   [FlipableAttribute( 0x1865, 0x1866 )]
   public class DishingStump : BaseTool
   {
      public override CraftSystem CraftSystem{ get{ return DefAlchemy.CraftSystem; } }
      public override void OnDoubleClick(Mobile from)
      {
         if ((from.InRange(this, 1) && from.CanSee(this)) || Parent == from)
         {

            CraftSystem system = CraftSystem;

            int num = system.CanCraft(from, this, null);

            {
               CraftContext context = system.GetContext(from);

               from.SendGump(new CraftGump(from, system, this, null));
            }
         }
         else
         {
            from.SendMessage("This must be in your pack or 1 tile away for you to use it.");
         }
      }
      public override void OnMovement( Mobile m, Point3D oldLocation )
      {
         if ( m is PlayerMobile )
         {
            if ( !Utility.InRange( m.Location, this.Location, 1 ) && Utility.InRange( oldLocation, this.Location, 1 ) )
               m.CloseGump( typeof( CraftGump ) );
         }
      }

      [Constructable]
      public DishingStump() : base( 0x1865 )
      {
         Name = "Dishing Stump";
         Weight = 10.0;
      }

      public DishingStump( 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();
      }
   }
}
 
True -- there is a similar onmovement check in playermobile.cs that checks for a rez gump. Since we're using the usual alchemy gump it would close it if they walked while using the mortar & pestle in their pack, but no big deal there.

Or, for a quick fix on the cantwalk, add this edit to /misc/LoginStats.cs

Code:
            Mobile m = args.Mobile;

        if (m.CantWalk = true)  // Just in case they are stuck with cantwalk
        m.CantWalk = false;

            m.SendMessage("Welcome, {0}! There {1} currently {2} user{3} online, with {4} item{5} and {6} mobile{7} in the world.",
                args.Mobile.Name,
<snip>

That would release anyone who lost connection while crafting with the dishingstump. Just a relog would fix it. I can't think of any scenario where this could be exploited. Most other scripts and even GM jailing tools use the variable "frozen" which wouldn't be affected by this code.

Want to make sure the dishingstump never wears out? Edit craftitem.cs to add an if check for that tool.

Code:
        if ( !(tool is DishingStump) ) // bypasses the negative increment if using the DishingStump
                tool.UsesRemaining--;

Every other tool will wear out as before.

It might not be the prettiest code but it's understandable and functional -- my two main requirements!
 
at the top of your dishing stump make these fields

Code:
PlayerMobile PM { get; set; }
Point3D PMLocation { get; set; }

then in the OnDoubleClick method

Code:
PM = from as PlayerMobile;
PMLocation = PM.Location;

in the OnMovement

Code:
if (PM != null)
{
     if (m == PM && PM.location != PMLocation)
               PM.CloseGump(typeof(CraftGump));
}

Not tested but it would be the way I would go!

As for the CantWalk, if you go that route, use the events for logout and crash to fix the value instead of placing code in the distro files, hope that helps either way!

Code:
            EventSink.Logout += EventSink_Logout;
            EventSink.Crashed += EventSink_Crashed;
 
Last edited by a moderator:
Code:
        if ( !(tool is DishingStump) ) // bypasses the negative increment if using the DishingStump
                tool.UsesRemaining--;

This worked great for the uses remaining, I'm going to hide the display for this but worked perfect for the need, thanks!

Just going to poke the gump closure with a harder stick but gotta put little ones to bed, thanks for the quick replies :)
 
Back