Hey Ian!
You're almost there. I think your confusion is arising from not fully understanding boolean operators and their truth tables. It's super easy!
Boolean logic has two states. True and False. A lot of things end up becoming boolean functions. Something like from.InRange(this, 1) is True when you're 1 tile away, and False when you're not. For simplicity, I'll be using 1 to signify True and 0 to signify False.
AND (&&)
The AND operator is only True when BOTH inputs are present. The truth table below shows how it works.
---------------
| X1 | X2 | Y |
|-------------|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
---------------
OR (||)
The OR operator is only True when EITHER inputs are present. The truth table below shows how it works.
---------------
| X1 | X2 | Y |
|-------------|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
---------------
NOT (!)
The NOT operator only has a single input. It will give its opposite as output. You place the (!) BEFORE your input in the code. The truth table below shows how it works.
----------
| X1 | Y |
|--------|
| 0 | 1 |
| 1 | 0 |
----------
XOR (^)
The XOR operator is only True when ONE of EITHER inputs are present. The truth table below shows how it works.
---------------
| X1 | X2 | Y |
|-------------|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
---------------
Remember, you can use these operators like basic maths with 1's and 0's
For example, when you want something to work out of either when its in your pack or when its 1 tile away. It helps to write it out in a sentence.
This code will execute
IF the player is 1 tile away from the object AND the player can see it OR it is in the player's backpack OR it is in a container located somewhere in the player's inventory
So now we can reduce it to
if((from.InRange(this, 2) && from.CanSee(this)) || IsChildOf(from.Backpack) || Parent == from)
See how that makes sense now
I also just noticed a second misunderstanding that you're having - and that's with object inheritance.
Imagine I have a generic car called BaseCar. In the BaseCar i have a function called OpenDoor. Here it is below.
public virtual void OpenDoors(){
foreach (Door door in this.Doors) {
door.SwingOut();
}
}
Very simple and we have no problem with it. Now sometimes we don't want a BaseCar. Sometimes we want something like a BaseCar but ONLY SLIGHTLY different. For example, there's now a new model of car that's always Red. It would be tiresome to copy over the ENTIRE BaseCar database only to change the colour of it. Also what happens if I wanted to change the way doors opened, for example. I would then need to change it in two scripts rather than one.
This is where inheritance comes in. At the top of our RedCar script, we can write
public class RedCar : BaseCar
This means that a red car is an identical copy of BaseCar in every way unless otherwise stated in this file. Let's turn the car red!
[Constructable]
public RedCar()
: base()
{
Hue = Red;
}
So now, when a RedCar is constructed it calls base() (which in this case is BaseCar) with no arguments, and then it continues with turning the hue red. Keep in mind that sometimes you'll want to send arguments to your base model (most times in ServUO this is the ItemID). Check what arguments your parent model needs
NOW, here's where it starts to get interesting. We want to make a DeLorean. A very basic one. Basically we just want the doors to open upwards, rather than to swing out horizontally. But how do we do that? We've already established how doors open in BaseCar - does that mean we now have to write a whole new car script and maintain it? NOT WITH OBJECT INHERITANCE YOU DON'T!
Ok, so we get our DeLorean
public class DeLorean: BaseCar
So now our DeLorean inherits everything from BaseCar, but how do we change what BaseCar does with the OpenDoors function? If we take a look at OpenDoors, we'll see that there's a few keywords in there. Let's go over them!
public : This means that this function is able to be called from any other script that has access to the car object. So any object can say something like car.OpenDoors(); and the doors will open. The opposite of this is
private. When private is used, then only functions WITHIN your object can call that function. This is to protect certain functionality in your class and not allow, say a tree object to be able to do anything to your gas level.
void : this is what gets returned. So void means nothing needs to be returned from the function. You can put any type of variable here.
int will return an integer.
string will give you a string.
bool will give you a boolean value. You get the gist.
static : this one isn't in the basecar script, but its worth noting. When there's a static keyword, it means that the function can be called WITHOUT reference to an object. So usually you may have a BaseCar object called car which you can use to car.OpenDoors(). But you may have a static function that is for telling you something general about BaseCar objects (like who invented cars), in which case you'd call BaseCar.Inventor() and it would return a string of "Karl Benz". You don't need to have a car in order to get that information!
virtual : Now here's where it gets awesome. When you declare something as virtual, it means that the functionality can be
overridden by a child class. So say we have OpenDoors in the BaseCar model, and we wanted to change the way it works in a DeLorean which is also a BaseCar - we put this in the DeLorean file.
public override void OpenDoors(){
foreach (Door door in this.Doors) {
door.RiseUp();
}
}
You can also override overrides. So if there's a model that's based on a DeLorean that you want to change the OpenDoors function of, you can just insert an override function there.
You can also call the parent's function (that you have overriden) by calling base.OpenDoors(). Whenever you see base, you're dealing with the object that you inherited from (or the one that THAT one inherited from or the one that THAT one inherited from etc. etc.)
So coming back to your question, we can now try to understand why things worked or didn't work for you.
What you did first was edit BaseTool. This would have theoretically worked, but you would be changing the functionality of ALL tools on the server while you only wanted to change SmithingPress which inherits from BaseTool.
Second thing you did was create an override function in SmithingPress (on the right track!) and added checks for being in range (good!) BUT THEN you call the base.OnDoubleClick(from) function. So what's happening here is you're passing the range check, but THEN you're going to the BaseTool's double click function which checks whether or not the basetool is in your bag before working. So in this case, you would have to have the smithing press both simultaneously less than 1 tile away from you and in your LoS while also being in your bag.
Next thing you did was copy the OnDoubleClick function from BaseTool and put it in your SmithingPress script. This is on the right track! BUT within all that, you left a call to base.OnDoubleClick() (noooo!) So it'd pass the range check, fail the base.ondoubleclick check which would give you an error message, but then it would go on and continue with the rest of the script in SmithingPress. This works, but its confusing to players to get an error message and still have the thing work!
So what you want to do is check for distance OR if its in your pack (like we did earilier in this post) and then have all that functionality EXCLUSIVELY in your SmithingTool script.
Namely, just copy/paste this.
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);
if (num > 0 && (num != 1044267 || !Core.SE)) // Blacksmithing shows the gump regardless of proximity of an anvil and forge after SE
{
from.SendLocalizedMessage(num);
}
else
{
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.");
}
}
Sorry for the wall of text, but now you can understand what's going on