RunUO Community

This is a sample guest message. Register a free account today to become a member! Once signed in, you'll be able to participate on this site by adding your own topics and posts, as well as connect with other members through your own private inbox!

Master Looter 3.01.00 can use deeds multiple times

daat99

Moderator
Staff member
Ouch, that means it doesn't save the type.
We'll have to use a Dictionary<string,BaseStorage> instead of List<BaseStorage> for StorageList in MasterStorage and add a "type name" property to BaseStorage that is set by default to the GetType when the item is being created (in the constructor).

Then we need to access the storage list based on that new type name instead.

An alternative which has much better performance and lower readability is to add an int property to BaseStorage which has unique ID in each storage deed.
Then use a dictionary with int key instead of string key in MasterStorage similar to the previous solution.

Let me know if you got confused.
 

Hammerhand

Knight
Between just having woken up & being self taught with a long way yet to go... I got the 1st line ok. After that.. not so much. I figure its probably easier to do than it sounds, but right now I'm lost. :(
 

daat99

Moderator
Staff member
Let's start by saving the type in the BaseStorage:
Add the following property:
Code:
private type storageType;
public Type StorageType { get { return storageType; } set { storageType = value; } }

You need to initialize it in the BaseStorage constructor like this:
Code:
StorageType = this.GetType();
//Console.WriteLine("storage type is: {0}", StorageType.ToString()); //use this line to make sure it does save the correct type when you add a new BaseStorage from a new BaseStorageDeed

Once you have the type added you need to serialize it and deserialize it.
Don't forget to increase the version number and serialize/deserialize the type as a string (I'm sure you can find plenty of examples on how to serialize a Type as a string).

Post the code once you have these 2 ready and I'll assist with the rest as well.
 

Hammerhand

Knight
Maybe I'm way off base here, but BaseStorage.cs doesnt seem to even HAVE a Deserialize.. could that be part of the problem? Or is this it?
Code:
        internal BaseStorage(GenericReader reader)
        {
            int version = reader.ReadInt();
 
            name = reader.ReadString();
 
            int count = reader.ReadInt();
            StoredTypes = new List<Type>(count);
            while (count-- > 0)
            {
                try
                {
                    Type type = Type.GetType(reader.ReadString());
                    if (type != null && !StoredTypes.Contains(type))
                        StoredTypes.Add(type);
                }
                catch { }
            }
        }
Code:
/*
created by:
    /\            888                  888    .d8888b.  .d8888b. 
____/_ \____      888                  888    d88P  Y88b d88P  Y88b
\  ___\ \  /      888                  888    888    888 888    888
\/ /  \/ /    .d88888  8888b.  8888b.  888888 Y88b. d888 Y88b. d888
/ /\__/_/\  d88" 888    "88b    "88b 888    "Y888P888  "Y888P888
/__\ \_____\  888  888 .d888888 .d888888 888          888        888
    \  /      Y88b 888 888  888 888  888 Y88b.  Y88b  d88P Y88b  d88P
    \/        "Y88888 "Y888888 "Y888888  "Y888  "Y8888P"  "Y8888P" 
*/
using System;
using System.Collections.Generic;
using Server;
using Server.Items;
 
namespace daat99
{
    public class BaseStorage
    {
        private static Type[] defaultStoredTypes = new Type[0] { };
        public virtual Type[] DefaultStoredTypes { get { return BaseStorage.defaultStoredTypes; } }
        private List<Type> storedTypes;
        public List<Type> StoredTypes
        {
            get { return storedTypes; }
            private set { storedTypes = value; }
        }
 
        protected static BaseStorage singleton;
        public static BaseStorage Storage { get{ if ( singleton == null ) singleton = new BaseStorage(); return singleton; } }
        public virtual BaseStorage GetStorage() { return BaseStorage.Storage; }
        private string name;
        public virtual string Name { get { return name; } }
        protected BaseStorage()
        {
            storedTypes = new List<Type>(DefaultStoredTypes);
        }
 
        //deserialize constructor
        internal BaseStorage(GenericReader reader)
        {
            int version = reader.ReadInt();
 
            name = reader.ReadString();
 
            int count = reader.ReadInt();
            StoredTypes = new List<Type>(count);
            while (count-- > 0)
            {
                try
                {
                    Type type = Type.GetType(reader.ReadString());
                    if (type != null && !StoredTypes.Contains(type))
                        StoredTypes.Add(type);
                }
                catch { }
            }
        }
        public virtual bool IsItemStorable(Item itemToCheck)
        {
            if (!CanStoreItemLootType(itemToCheck))
                return false;
            return IsTypeStorable(itemToCheck.GetType());
        }
        public virtual bool IsTypeStorable(Type typeToCheck)
        {
            return IsTypeStorable(typeToCheck, true);
        }
 
        public virtual bool IsTypeStorable(Type typeToCheck, bool canBeEqual)
        {
            foreach (Type type in StoredTypes)
            {
                if ((type.IsInterface && type.IsAssignableFrom(typeToCheck)) ||
                    ((canBeEqual && typeToCheck == type) || typeToCheck.IsSubclassOf(type)))
                    return true;
            }
            return false;
        }
 
        public virtual void RemoveStorableType(Type typeToRemove)
        {
            if (StoredTypes.Contains(typeToRemove))
                StoredTypes.Remove(typeToRemove);
        }
 
        public virtual void AddStorableType(Type typeToAdd)
        {
            if (IsTypeStorable(typeToAdd, false))
                return;
            if (StoredTypes.Contains(typeToAdd))
                return;
            StoredTypes.Add(typeToAdd);
        }
 
        public virtual void ResetTypesList()
        {
            StoredTypes = new List<Type>(DefaultStoredTypes);
        }
 
        public void Serialize(GenericWriter writer)
        {
            writer.Write(0); //version
 
            writer.Write(Name);
 
            writer.Write(StoredTypes.Count);
            foreach (Type type in StoredTypes)
                writer.Write(type.FullName);
        }
 
        public virtual bool CanStoreItemLootType( Item item )
        {
            if (item.Insured || item.LootType == LootType.Blessed || item.LootType == LootType.Newbied)
                return false;
            return true;
        }
       
        public virtual Dictionary<Type, int> GetStorableTypesFromItem(Item item)
        {
            Dictionary<Type, int> types = new Dictionary<Type, int>();
            if (!IsItemStorable(item))
                return types;
            IUsesRemaining iUsesRemainingItem = item as IUsesRemaining;
            if (iUsesRemainingItem != null)
            {
                types.Add(item.GetType(), iUsesRemainingItem.UsesRemaining);
            }
            else if (item.Stackable)
            {
                types.Add(item.GetType(), item.Amount);
            }
            else
                types.Add(item.GetType(), 1);
            return types;
        }
    }
}
 

Dian

Sorceror
You would assume there is some reason for not having a deserialize method, being Daat created the script, huh?
 

daat99

Moderator
Staff member
A "deserialize" method is a method that can take a stream of data that was serialized from an object and recreate the original object from it.
In the BaseStorage case I used a deserialize constructor (a method that creates an object from a serialized data stream "reader").

This means that you are correct, the constructor you referred to is the deserializing method in the BaseStorage case.

This however reveals the real issue with the system.

Lets go back to StorageDeed.cs.

In there you will find:
Code:
        public override void Serialize(GenericWriter writer)
        {
            base.Serialize(writer);
 
            writer.Write((int)0);
            Storage.Serialize(writer);
        }
 
        public override void Deserialize(GenericReader reader)
        {
            base.Deserialize(reader);
            int version = reader.ReadInt();
            Storage = new BaseStorage(reader);
        }
As you can see above when I deserialize the storage I create a new "BaseStorage" regardless of the real storage class.

This is where the real fix should be implemented instead of the work-around I suggested before.

We need to change the serialization to serialize the Storage type name first and than use Activator.CreateInstance to create an instance of the storage using the "reader" as an argument for the constructor.

The serialization should look like this:
Code:
        public override void Serialize(GenericWriter writer)
        {
            base.Serialize(writer);
 
            writer.Write((int)1); //note version is 1 now
            writer.Write(this.GetType().FullName); //serialize the type of the real storage
            Storage.Serialize(writer);
        }
The deserialization should look something like this (not tested):
Code:
        public override void Deserialize(GenericReader reader)
        {
            base.Deserialize(reader);
            int version = reader.ReadInt();
            if ( version >= 1 )
            {
                string typeName = reader.ReadString(); //read the storage type name
                if ( !string.IsNullOrEmpty(typeName) ) //make sure we have a name
                {
                    Type storageType = ScriptCompiler.FindTypeByFullName(typeName); //get the storage type
                    if ( storageType != null ) //make sure we have a type
                    {
                        Storage = Activator.CreateInstance( storageType, new object[]{ reader } ); //create a storage from the type we saved with the data stream we have
                    }
                }
            }
            else
            {
                Storage = new BaseStorage(reader); //old behavior so we can load a save file from old server
            }
            if ( Storage == null )
            {
                //something went wrong and we can't load the storage deed correctly, change the name so the GM/Admin can replace it with a new one.
                this.Name="This deed is broken, please return it to a GM for replacement!!!";
            }
        }

When you get the above code to compile please test it with both old and new storage deeds.
See if it can work correctly with new storage deeds and make sure it can't recover old storage deeds (because the information we need wasn't saved).

On a side note:
There is a way to actually recover the old deeds.
It involves in using the type of the storagedeed to determine what is the type of the storage itself.
I chose not to do it because I don't have the time.
If you want to tackle this than you can wait for the server to load completely and than execute a command (that you'll have to write) which:
1. loops through all the StorageDeed's in the world.
2. If the Storage is BaseStorage than replace the Storage with a new Storage that matches the StorageDeed real type.

For example:
Code:
//you already know how to get all the items in the world from specific type
foreach ( storageDeed in allStorageDeedsInTheWorld )
{
    switch ( storageDeed.GetType() )
    {
        case typeof(BardStorageDeed):
            storageDeed.Storage = BardStorage.Storage;
            break;
        case typeof(ToolStorageDeed):
            storageDeed.Storage = ToolStorage.Storage;
            break;
        //and so forth
    }
}
 

Hammerhand

Knight
Compile errors are as follows.
Code:
 + Customs/MasterStorage/Storage/StorageDeed.cs:
    CS0246: Line 88: The type or namespace name 'Type' could not be found (are y
ou missing a using directive or an assembly reference?)
    CS0103: Line 91: The name 'Activator' does not exist in the current context
Lines 88 through 91
Code:
Line 88 >>             Type storageType = ScriptCompiler.FindTypeByFullName(typeName); //get the storage type
                    if (storageType != null) //make sure we have a type
                    {
       Line 91 >>           Storage = Activator.CreateInstance(storageType, new object[] { reader }); //create a storage from the type we saved with the data stream we have
 

Hammerhand

Knight
Adding
using.System;
to the using statements cleared those up, but gave me this..
Code:
    CS0266: Line 94: Cannot implicitly convert type 'object' to 'daat99.BaseStor
age'. An explicit conversion exists (are you missing a cast?)
Line 94
Code:
Storage = Activator.CreateInstance(storageType, new object[] { reader }); //create a storage from the type we saved with the data stream we have
 

daat99

Moderator
Staff member
Add the phrase "as BaseStorage" just before the semi-colon in that line.
What this does is tell the compiler that you expects a "BaseStorage" type and to try and cast whatever the CreateInstance method returns to BaseStorage.
Using the "as" keyword also means that if the returned object is NOT BaseStorage and can't be casted into a BaseStorage than it will return null instead (as in: instead of crashing).

It'll be a good practice to make sure the Storage is not null after that line and if it is than some kind of error correction should be made.
Something like telling players to contact the admin or some other advanced recovery method should work nicely.
 

Hammerhand

Knight
Ok, added the as BaseStorage before the semicolon & it compiled. Started with a new MasterStorage pack & added 2 ToolStorage deeds to my backpack. Added 1 to MasterStorage & it went in fine. Left the 2nd in my pack, saved & rebooted. 1st thing it did was delete the ToolStoragedeed in my backpack. Once I rebooted after that & added a new tool storage deed, it also added into the MasterStorage with no trouble. This is the ConsoleWriteline from that..
Code:
====================
attempting to add daat99.ToolStorage, exist already? False
before Storage: daat99.ToolStorage, exist: True.
after Storage: daat99.ToolStorage, exist: True.
after Storage: daat99.ToolStorage, exist: True.
====================
At least it knows they are both Tool Storage now. :D
 

daat99

Moderator
Staff member
Grrrr... I need to debug this...

Can you send me the latest OWLTR code you have please?
 

Hammerhand

Knight
Thought I had found part of the issue.... not sure though.
Original OWLTR 3.01.00 ToolStorageDeed line 46
Code:
public ToolStorageDeed() : base(new ToolStorage()) { }
My "fix" attempt which does compile, but seems to change nothing unfortunately..
Code:
public ToolStorageDeed() : base(ToolStorage.Storage) { }
I was comparing the ToolStorage to others & found that one line different in ToolStorage
(new ToolStorage()) instead of (ToolStorage.Storage)
Thought it might have some affect at least, but I didnt see any. :(
 

daat99

Moderator
Staff member
The storage parameters are the same for all the players so using ToolStorage.Storage will make sure we have a single instance of the "ToolStorage" class for all the ToolStorageDeed's while using the "new ToolStorage" will create a new ToolStorage instance for every ToolStorageDeed.
That is a performance related change and it will only effect the save file size and the time it takes to save/load the storage when we save/restart the server.

That should be added to the "things to fix for next release" list.

As for the code, I need the latest code of the (entire) OWLTR for pure RunUO server without any additions like FSATS or nerun.
I don't have my copy anymore and bitbucket isn't updated with the latest bugs we fixed already :(
 

Hammerhand

Knight
Ok, I'll add in all the fixes I have (the ones I remember at least with notations for them), zip it up & put it on bitbucket for you.
 

Hammerhand

Knight
*grumble* BitBucket doesnt seem to want to let me upload the rar'd file.. Says "Choose file to upload", I do that, the button grays out & shows a little circle thingy next to it like its working, but does nothing but sit there.
 

Hammerhand

Knight
It did the same thing with the file zipped. I got it on there by creating a new "issue", but its weird that I couldnt upload it to one area, but could to another.
 

aarony14

Traveler
Hi guys, I know you're busy but is there anything I can help out with? Maybe with some testing? I have an empty shard at my disposal.
 
Top