1. What I'm talking about: Oxide API for Rust

    So I have been trying to understand how the data tables work from the code example. I have copied the code to a clean C# file, and it runs as it should. But there's one problem I have come across about the command /Test.
    Code:
    [ChatCommand("Test")]
            private void Test(BasePlayer player, string command, string[] args)
            {
                var info = new PlayerInfo(player);
                if (storedData.Players.Contains(info))
                    PrintToChat(player, "Your data has already been added to the file.");
                else
                {
                    PrintToChat(player, "Saving your data to the file.");
                    storedData.Players.Add(info);
                    Interface.GetMod().DataFileSystem.WriteObject("MyDataFile", storedData);
                }
            }
    When I do the command /Test for the first time, everything goes as it should. Message about saving the player data and the player info gets stored in the json file.

    But when I do the command a second time to see if the if statement works and that it checks that the data is already existing, it somehow doesn't work. It doesn't read the json file. So instead it duplicates the information and it becomes this mess:
    Code:
    {
      "Players": [
        {
          "UserId": "76561198023852873",
          "Name": "Masteroliw"
        },
        {
          "UserId": "76561198023852873",
          "Name": "Masteroliw"
        },
        {
          "UserId": "76561198023852873",
          "Name": "Masteroliw"
        }
      ]
    }
    I have tested to restart the server, and then the command, but that doesn't work. And tried to reload the plugin.

    So I ask, has something changed and that it's just out of date information I just have copied and tried to run.

    Or is it just working fine for everyone else and there's something else I have to do to get it all to work?
     
  2. Code:
    [ChatCommand("Test")]
            private void Test(BasePlayer player, string command, string[] args)
            {
                if (storedData.Players.Contains(player.UserId))
                    PrintToChat(player, "Your data has already been added to the file.");
                else
                {
                    PrintToChat(player, "Saving your data to the file.");
            var info = new PlayerInfo(player);
                    storedData.Players.Add(info);
                    Interface.GetMod().DataFileSystem.WriteObject("MyDataFile", storedData);
                }
            }
    
     
  3. Calytic

    Calytic Community Admin Community Mod

    You are confusing binary and JSON serialization. JSON is an intermediate format, binary is the in-memory representation of an object. JSON serialization is not like binary serialization in that the object instances are stateless and may not be re-used. Essentially, saving the data destroys the object, and when it is reloaded it is a totally different object (with the same data). This is why the Contains check is not functioning as intended.

    You can use an in-memory working copy of your objects and only save/load the data on restart.

    Or you can key your stored JSON data using a key-value pair/dictionary

    Or you can do both. In any case, you will no longer have duplicate data in your file.
     
  4. Heya Calytic, It'd be real awesome if you could show what you mean with a bit of example code.

    The API documentation lists one example snippet that appears broken af, and when any potential plugin developer asks about it, the stock answer is "Don't do that, that is bad", without any generally useful clarification.

    I've gathered there are better ways to do this sort of thing (read the stored data at startup, modify it in memory, and write it at shutdown, for instance), but without some pre-existing solid grounding in oxide plugin development or the theory of JSON instantiation, these sort of broad suggestions don't find fertile ground with me (and it would seem, many others).

    Throw us a bone, yo, hopefully one that hasn't been previously boiled for soup ;)

    Thanks in advance!
     
  5. This simply doesn't work.
     
  6. Calytic

    Calytic Community Admin Community Mod

    Code:
    [ChatCommand("Test")]
            private void Test(BasePlayer player, string command, string[] args)
            {
                var info = new PlayerInfo(player);
                if (storedData.Players.Contains(info))
                    PrintToChat(player, "Your data has already been added to the file.");
                else
                {
                    PrintToChat(player, "Saving your data to the file.");
                    storedData.Players.Add(info);
                    Interface.GetMod().DataFileSystem.WriteObject("MyDataFile", storedData);
                }
            }
    The Test method, in this case, follows the Chain of Responsibility pattern commonly used in logging. A clearer way to express this pattern would be to add a DateTime property to PlayerInfo. Therein denoting a stateless list of singular events as opposed to a stateful list of all in-memory players.

    What's confusing you is that a log file would generally not be reloaded on restart. I will agree that the example above is achieving 2 separate things and doing neither particularly well. But then again, it is just an API guide, not a primer on conventional programming practices.

    There is somewhere in the range of 500 plugins in the Oxide plugin repository, many of which demonstrate data storage in a variety of patterns using a variety of data formats. 3 of my public plugins reliably store player data by a keyed list, you may consult:

    NoEscape for Rust | Oxide
    AntiOfflineRaid for Rust | Oxide
    Bank for Rust | Oxide
     
  7. Thanks for the reply Calytic!

    Also, I didn't mean to be throwing shade on you regarding the quality of the example code in the API doc; I'm sure we'd both agree that an example that *doesn't* illustrate best coding practices is probably not the most desireable of examples.

    That being said, thanks for the more penetrating illustration, and the encouragement to review the technique in the plugins you've written; I have done this with a few of the plugins, and have gotten some traction with some things, though not with this particular problem.

    However, I think that the real problem with this example code per the OP's original question, is that the code given *does not work*. This is complicated by the fact that it appears to provide a useful recipe for a commonly required solution, that of recognizing the presence or absence of a member in the hashset (in spite of the example actually being about something else entirely).

    I'll have a look at your code in the morning, I'm just clearing out for the day.

    Thanks again :)
     
  8. Ok Caylitic

    I spent a few days taking a good look at your plugins and man you got a lot going on there, proud of you :)

    Not exactly a great place to send a newb to parse a simple solution though. I singled out the AntiOfflineRaid plugin, and focussed entirely on that one for this purpose.

    I did manage to sort out the relevant bits and come up with the following piece of code, which is actually foundational for a simple greeter type plugin that I'm writing (not because the world needs another one, but because I desire to know how to do this sort of thing). It does only slightly more than the example code referenced in OP's initial post, had the example code actually worked:

    Code:
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Reflection;
    using Rust;
    using Facepunch;
    using Oxide.Core;namespace Oxide.Plugins
    {
        [Info("MC", "James Stallings", 0.1)]
        [Description("Master of Ceremonies")]    public class MC : RustPlugin
        {
            public ulong userid;
            public string username;        public Dictionary<ulong, string> playerData = new Dictionary<ulong, string>();        public BasePlayer player
            {
                get
                {
                    return BasePlayer.FindByID(userid);
                }
            }        void ReadPlayerData()
            {
                playerData = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<ulong, string>>("MC");
            }
     
         
            void WritePlayerData()
            {
                 Interface.Oxide.DataFileSystem.WriteObject("MC", playerData);
            }     
            void OnServerInitialized()
            {
                ReadPlayerData();
            }     
            void OnServerSave()
            {
                WritePlayerData();
            }     
            void OnServerShutdown()
            {
                playerData.Add(player.userID,player.displayName);
                WritePlayerData();
            }        void OnPlayerInit(BasePlayer player)
            {
                if (playerData.ContainsKey(player.userID))
                    PrintToChat(player, "Welcome Back {0}",player.displayName);
                else
                {
                     playerData.Add(player.userID,player.displayName);
                     PrintToChat("Everyone welcome {0} to the local food chain!",player.displayName);
                }
          }
    }
    Sorry about any formatting concerns; web text entry is not my friend.

    For the sake of OP I will mention that the execution context of a rust plugin is largely an event driven beast, and so patterns reflect a lot of callback and event hooking construction.

    In this instance, all file I/O is contingent on timed events (server state-saves), startup/shutdown events, and user joins. All data is kept in memory, and only persisted when it is logical to do so.

    a few of the using directives may not be strictly required for this snippet of code, but are for my continued work on this little project, and I cbt to eliminate them in this post.

    One thing about this code that is stumping me, is that the chats are sent well before the user login process is completed, and so they aren't actually seen by the user except and unless the user open up the chat on his client, where they will be visible in the history. Do I need to hook a differnet event than 'OnPlayerInit()'?

    Comments/Questions are welcome and encouraged.

    NOTE: JamesStallings is [CHRONIC] Skeeter. Dunno why it did that in the reply, perhaps because I'm on a different PC.
     
    Last edited by a moderator: Jan 5, 2017