1

I'm messing around with game item implementations, and to avoid hard-coding the items, I'd like to load their information and implementations from external files.

The basic format would be something like this:

-- Items.json
[
    {
        "name": "Rusty Key",
        "shortDesc": "A rusty key.",
        "longDesc": "A slightly rusty key. You don't know what door it opens.",
        "code": "RustyKey.cs"
    }
]

OR embed the Use() code directly into the JSON file:

-- Items.json
[
    {
        "name": "Rusty Key"
        ...
        "use": "return 0;"
    }
]

The JSON file would be loaded at runtime and dynamically add the items as well as their implementations.

This could also perhaps morph into a simple scripting language allowing players to create their own items, but in its current form I would not release it to the public due to the potential for abuse.

So my question is this: how can I load/parse the attached script file OR embedded code and attach it to the dynamically-created object?

Abluescarab
  • 537
  • 8
  • 25
  • You can compile code on runetime and execute it (http://stackoverflow.com/questions/826398/is-it-possible-to-dynamically-compile-and-execute-c-sharp-code-fragments), but if the code you want to execute is not so complicated you should use some kind of pseudo-code as text and parse it, creating you own scripting language. – Pau C Sep 17 '16 at 07:13
  • How do you plan to use `Items.json`? What should the "code" contain? Classes and/or functions or just a piece of code? – Orkhan Alikhanov Sep 17 '16 at 07:18
  • @OrkhanAlikhanov Items.json would be a file included in either the installation directory or the AppData folder. The items in Items.json would be dynamically loaded at runtime and added to a List. – Abluescarab Sep 17 '16 at 07:21
  • @Abluescarab You misunderstood me. Could you provide `RustyKey.cs` as sample? – Orkhan Alikhanov Sep 17 '16 at 07:26
  • @OrkhanAlikhanov Oh, sorry. I don't have an implementation file at this moment, but it would probably be something like this (formatting doesn't work): public bool Use(Door door) { door.Unlock(); return true; } – Abluescarab Sep 17 '16 at 07:27
  • In `Door.Open();`. `Door` should be instance of an object. How do you plan to provide that instance. I think you should first define your logic before implementing it – Orkhan Alikhanov Sep 17 '16 at 07:34
  • @OrkhanAlikhanov I edited my comment after posting it. I provide the Door instance as a parameter to the Use() function. – Abluescarab Sep 17 '16 at 07:35

2 Answers2

0

Create MyLibrary.dll and add following code:

namespace MyTypesNamespace
{

    public abstract class BaseItem
    {
        public abstract bool Use(object onMyObject);
    }

    public class Door
    {
        public bool IsLocked { get; set; }

        public bool Open()
        {
            if (IsLocked)
            {
                System.Console.WriteLine("Cannot open door. It is locked!");
                return false;
            }

            //Some code
            System.Console.WriteLine("Door is opened!");
            return true;
        }
    }
}

In your main project, add reference to MyLibrary.dll and the following method:

private static List<BaseItem> loadItems(string fromCode)
{
    CodeDomProvider codeProvider = new CSharpCodeProvider();

    // add compiler parameters
    CompilerParameters compilerParams = new CompilerParameters();
    compilerParams.CompilerOptions = "/target:library /optimize";
    compilerParams.GenerateExecutable = false;
    compilerParams.GenerateInMemory = true;
    compilerParams.IncludeDebugInformation = false;
    compilerParams.ReferencedAssemblies.Add("mscorlib.dll");
    compilerParams.ReferencedAssemblies.Add("System.dll");
    compilerParams.ReferencedAssemblies.Add("MyLibrary.dll");

    // compile the code
    CompilerResults results = codeProvider.CompileAssemblyFromSource(compilerParams, fromCode);
    var items = new List<BaseItem>();
    foreach (var itemType in results.CompiledAssembly.DefinedTypes)
    {
        ConstructorInfo ctor = itemType.GetConstructor(Type.EmptyTypes);
        object instance = ctor.Invoke(null);
        items.Add(instance as BaseItem);
    }
    return items;
}

Usage:

private static void Main()
{
    string code =  loadCode();
    List<BaseItem> items = loadItems(code);

    BaseItem rustyKey = items[0];
    BaseItem unlockAnyDoor = items[1];

    Door myDoor = new Door { IsLocked = true };
    rustyKey.Use(myDoor);
    unlockAnyDoor.Use(myDoor);
    rustyKey.Use(myDoor);


    Console.ReadLine();
}

private static string loadCode()
{
    return @"
        using MyTypesNamespace;
        public class RustyKey : BaseItem
        {
            public override bool Use(object onMyObject)
            {
                var door = onMyObject as Door;

                return door.Open();
            }
        }

        public class UnlockAnyDoor : BaseItem
        {
            public override bool Use(object onMyObject)
            {
                var door = onMyObject as Door;
                door.IsLocked = false;
                System.Console.WriteLine(""Door is unlocked!"");
                return true;
            }
        }
";
}

Output: (See the output online)

Cannot open door. It is locked!
Door is unlocked!
Door is opened!

Edit:

You can simplify future code supply (which will be read from file) by eliminating code duplication. Let's generateItemClass method which will take className and body of Use() method:

private static string generateItemClass(string className, string useBody)
{
    return $@"
        public class {className} : BaseItem
        {{
            public override bool Use(object onMyObject)
            {{
                {useBody}
            }}
        }}";
}

Don't forget to add using MyTypesNamespace; for all classes.

private static string loadCode()
{
    string namespaces = @"using MyTypesNamespace;";

    string rustyKeyClass = generateItemClass("RustyKey",
        @"var door = onMyObject as Door;
        return door.Open();");

    string unlockAnyDoorClass = generateItemClass("UnlockAnyDoor",
        @"var door = onMyObject as Door;
        door.IsLocked = false;
        System.Console.WriteLine(""Door is unlocked!"");
        return true;");

    return namespaces + rustyKeyClass + unlockAnyDoorClass;
}
Community
  • 1
  • 1
Orkhan Alikhanov
  • 9,122
  • 3
  • 39
  • 60
  • Thanks! Is it possible to compile just one method? Say that the string is loadCode() is a simple statement, like "return 0;". Is it possible to compile this and use it as the code for a preexisting method? – Abluescarab Sep 17 '16 at 23:23
  • @Abluescarab yep it is possible through hardcoding it once in the main program . I will update the anawer – Orkhan Alikhanov Sep 18 '16 at 00:13
  • @Abluescarab I think it's done. Play with the code until it fulfills your requirements, read `className` and `useBody` from your json file. – Orkhan Alikhanov Sep 18 '16 at 04:40
  • Apologies for getting back to you so late. I've been busy with college. I'm going to give this a go as soon as I have some free time! – Abluescarab Sep 26 '16 at 09:16
0

Putting raw code into JSON means having to pick up that code, probably parse it, glue it into some code shell, compile it and check for errors. That's a lot of machinery. (Another answer pretty provides details to do this).

There's also the problem that if somebody can write arbitrary code there, they will, and they will write something that breaks your application in arbitrarily strange ways, either creating a security leak or if nothing else some difficult debugging problems.

I think you'd be better off in most cases simply signalling what you want in the JSON, e.g.,

-- Items.json
[
    {
        "name": "Rusty Key"
        ...
        "result": "0"
    }
]

Now the program logic reads the JSON file, stores <"RustyKey","0"> into a hashed lookup collection, and then when a request for RustyKey comes up, looks up the result value in the collection.

Granted, you cannot do as many complicated things as with include code, but you'll get into a lot less trouble.

Motto: "eval is evil".

Ira Baxter
  • 93,541
  • 22
  • 172
  • 341
  • Apologies for replying so late. I'm aware of the evils of eval, having used it before and noticing the ill effects. :) I was hoping to sort of evolve this question into perhaps a custom scripting language, but your idea doesn't sound half-bad. Return the ID of an action and provide some extra details in the caller. – Abluescarab Sep 26 '16 at 09:08