Sunday, April 23, 2017

Create a Rust Plugin Using Oxide


You guys want to mod some Rust? Yea me too! In this tutorial I will show you how to use the Oxide API to create a basic Rust plugin and upload it to your rust server. I will not be showing you how to setup a server but maybe with some requests I'll do that in another post. I wrote this plugin in C# using Visual Studio 2015. Oxide does support other language options like lua.

Some things you will need:
  • Visual Studio 2015 - The community edition is free. Technically you don't NEED it...a text editor will get you by but seriously recommend using an IDE instead.
  • An FTP Client - I'll be using FileZilla.
  • RustAdmin - Not required but it's nice way to get feedback from the server if you have log messages or errors.
At a high level, all we need to do is write a plugin, which is simply a .cs file, and upload it to the server. If you're a text editor user all you really need to concern yourself with is uploading the code file you create. VS2015 users might want to pull the git repo repository for Oxide which you can find at: https://github.com/OxideMod/Oxide so you can take advantage of other peoples hard work to give you intellisenseand code suggestions. Thanks Oxide team!

After pulling/downloading the code from git and opening the visual studio solution - navigate to the Rust plugins folder in the Solution Explorer. Here you will right click the Plugins folder and Add > New Item. Select the C# class option and give your file a name.



I'm calling my plugin "Lottery" (filename LotteryPlugin.cs). After a certain amount of time players will be prompted to play the lottery. They will guess a number between 1 and 10. If they guess correctly they will win a Supply Signal . If they guess incorrectly they will not be able to guess again until the next lottery and be awarded a Rock. The lottery will also have a length/duration, if no one guesses correctly in that timeframe the lottery will end and players will wait until the next lottery to play again.

Let's write some code!

First, our class needs to inherit from RustPlugin and be in the Oxide.Plugins namespace. Be sure to include the Info and Description attributes for your plugin. These attributes include the plugin name, author name, plugin version, and description.

using Oxide.Core.Libraries.Covalence;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("Lottery""Adam Massingale"0.1)]
    [Description("Plugin that gives a player signal flare when they guess the right number")]
    public class LotteryPlugin : RustPlugin
    {
        ...
    }
}

Next, there are some things worth noting. Oxide supports timers, I opted to use my own because they weren't quite what I needed. Next there are two server hooks and one attribute I use that are worth noting, Init(), OnTick(), and ChatCommand respectively.

Init is a hook that is called when the plugin is initialized. I'm using it for some initialization.

void Init()
{
    nextLotteryTime = Time.time + lotteryRate;
    guessedPlayerIds = new List<ulong>();
}

OnTick is simply an update function called every tick. I'm using it to run my timer logic.

void OnTick()
{            
    if(!isLotteryRunning && nextLotteryTime < Time.time)
    {
        StartLottery();
    }

    if(isLotteryRunning && lotteryEndTime < Time.time)
    {
        LotteryExpired();
    }
}

ChatCommand is the attribute I'm using on my function to callback when the client types /lottery in the chat window. As you can see this also passes args which are parameters of the command.


[ChatCommand("lottery")]
void LotteryCommand(BasePlayer player, string command, string[] args)
{
    ...
}


You'll also see in the code I'm using a few more API calls I'll quickly explain these.

PrintToChat sends a server chat message to the server.

PrintToChat("Lottery Time! Guess a number between 1 and 10 (example: /lottery 10");

Player.ChatMessage sends a chat message to that instance of the player it's called on.

player.ChatMessage("Invalid guess. Try something like /lottery 10");

ItemManager.CreateByItemID passes in an item definition which I'm resolving with ItemManager.FindItemDefinition which passes in a string id for the item (i.e. supply.signal).

Item item = ItemManager.CreateByItemID(ItemManager.FindItemDefinition(gift).itemid);


That's about it for the important parts of the code. The rest of the code is a bit of simple logic for the lottery. Here is the script in it's entirety.

using Oxide.Core.Libraries.Covalence;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("Lottery""Adam Massingale"0.1)]
    [Description("Plugin that gives a player signal flare when they guess the right number")]
    public class LotteryPlugin : RustPlugin
    {
        const float lotteryRate = 25f//in seconds
        const float lotteryLength = 25f;
        float lotteryEndTime;
        float nextLotteryTime;
        int winningNumber = -1;
        bool isLotteryRunning;
        List<ulong> guessedPlayerIds; //list of playerids that have guessed

        //Called when a plugin is being initialized
        void Init()
        {
            nextLotteryTime = Time.time + lotteryRate;
            guessedPlayerIds = new List<ulong>();
        }

        void OnTick()
        {            
            if(!isLotteryRunning && nextLotteryTime < Time.time)
            {
                StartLottery();
            }

            if(isLotteryRunning && lotteryEndTime < Time.time)
            {
                LotteryExpired();
            }
        }

        void LotteryExpired()
        {
            PrintToChat("The lottery has expired. There were no winners. Better luck next time.");
            isLotteryRunning = false;
            nextLotteryTime = Time.time + lotteryRate;
            guessedPlayerIds.Clear();
        }

        void StartLottery()
        {
            isLotteryRunning = true;
            lotteryEndTime = Time.time + lotteryLength;
            winningNumber = UnityEngine.Random.Range(111);
            PrintToChat("Lottery Time! Guess a number between 1 and 10 (example: /lottery 10");
        }

        [ChatCommand("lottery")]
        void LotteryCommand(BasePlayer player, string command, string[] args)
        {
            if (!isLotteryRunning)
            {
                float delta = nextLotteryTime - Time.time;
                player.ChatMessage("The lottery has not started yet! Next lottery in " + delta + " seconds.");
                return;
            }

            if(guessedPlayerIds.Contains(player.userID))
            {
                float delta = nextLotteryTime - Time.time;
                player.ChatMessage("You've already played! Try again at the next lottery in " + delta + " seconds.");
                return;
            }

            if(args.Length != 1)
            {
                player.ChatMessage("Invalid guess. Try something like /lottery 10");
                return;
            }

            int guessedNumber;
            bool isNumeric = int.TryParse(args[0], out guessedNumber);
            
            if(!isNumeric)
            {
                player.ChatMessage("Your guess isn't a number! Try something like /lottery 10");
                return;
            }

            //WINNER
            if(isLotteryRunning && guessedNumber == winningNumber)
            {
                PrintToChat(player.displayName + " has won the lottery!");
                GivePlayerGift(player"supply.signal");
                isLotteryRunning = false;
                nextLotteryTime = Time.time + lotteryRate;
                guessedPlayerIds.Clear();
            }
            //LOSER
            else
            {             
                player.ChatMessage("You didn't win this time...Keep Playing!");
                GivePlayerGift(player"rock");                
                guessedPlayerIds.Add(player.userID);
            }
        }

        void GivePlayerGift(BasePlayer player, string gift)
        {
            Item item = ItemManager.CreateByItemID(ItemManager.FindItemDefinition(gift).itemid);
            player.GiveItem(item);
        }
    }
}

Now that we have our plugin written we just need to upload it to our server and we're done! You'll need the FTP information to use FileZilla to upload it to the server. If you use a decent hosting service this info should be provided to you. After you Quickconnect with these credentials via FileZilla you should see your servers file structure under the Remote site pane. Navigate to server>oxide>plugins. This is where you an drag and drop your plugin in the pane below. See the image below for clarification.


As you can see I'm trying out several other plugins that can be found on Oxides site. After you've uploaded your plugin. It will immediately be available in game. This plugin does not require a server restart...but there are likely some that do.


A quick piece of advice on debugging issues. RustAdmin is a tool that I use to identify issues in uploading my plugins. So if you're catching null reference exceptions etc it's great to get some insight on to what's going on.

Thanks for reading! Please post any questions in the comments and/or share if you liked this post!


Friday, March 31, 2017

Programming Weapon Sway

Weapon sway in games can easily be taken for granted by players. However, when it's not implemented the game looks plain and undone with a stationary weapon in the first person view. So that begs the question, how do first person shooters implement that cool weapon sway?! The truth is it's quite simple and this post will provide a brief code snippet on how to do it. Keep in mind that there are 1000 ways to skin a cat and the technique I'm presenting is one of those ways..but it is fairly popular.

Let's talk about Lissajous curves. There are plenty of websites that go into great detail about all the history, equations, and cool stuff you can do with Lissajous curves. I'll explain it simply and in terms of weapon sway. Think in your mind of your favorite FPS. As you walk the weapon typically sways horizontally and vertically in nice harmonic pattern. This complex motion in the x and y dimension is a Lissajous pattern.

Now, to program sway via Lissajous curves you can simply combine sin and/or cosine waves. There are different forms of the equations that use sin and/or cosine waves but for our example we will use 2 sin waves. Basically the goal is to oscillate vertically and horizontally harmoniously. From there you can customize your waves to get the effect you want.

This code will get you that traditional figure eight sway (this is a Unity C# snippet). Enjoy!

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SimpleWeaponSway : MonoBehaviour 
{

    [SerializeField]
    Transform weapon;

    [Range(01f)]
    [SerializeField]
    float verticalSwayAmount = 0.5f;

    [Range(01f)]
    [SerializeField]
    float horiztonalSwayAmount = 1f;

    [Range(015f)]
    [SerializeField]
    float swaySpeed = 4f;

    // Update is called once per frame
    void Update () 
    {
        float x = 0, y = 0;
        y += verticalSwayAmount * Mathf.Sin((swaySpeed * 2) * Time.time);
        x += horiztonalSwayAmount * Mathf.Sin(swaySpeed * Time.time);        
        weapon.position = new Vector3(xyweapon.position.z);
    }
}

Monday, March 13, 2017

WebAPI Using Entity Framework Core and Postgres in Visual Studio Code

It seems like Visual Studio Code is really starting to get some traction so I finally decided to bite the bullet and see what all the buzz is about. I set out a goal of putting together a webapi that would incorperate Entity Framework Core and Postgres. This seemed like this was a good route because there was some confusing/lacking information I came across when trying to put this project together. So hopefully in this blog post I can give you folks a nice, clean, and easy to understand guide to putting this stuff together.

I do recommend that the audience for this has some knowledge Entity Framework, database drivers, etc. I'm not going into great detail in this post about that stuff - it's more for just project setup. Regardless this will get you generating a code first databases! Enjoy.

Here goes...


My Environment
I've ran through this process on a Windows 7 desktop and a Mac bootcamping Windows 10

Software requirements






.Net Core SDK 1.1.1
Download the .exe download and use the default install options
https://www.microsoft.com/net/download/core





Visual Studio Code 1.10.2 
Downloaded the installer for windows and used all the default install options.
https://code.visualstudio.com/Download


Visual Studio Code Extensions


Navigate to the extension tab in VSCode, or use the shortcut  Ctrl+Shift+X, and install:  C#, C# Extensions, C# XML Documentation Comments, and Code Runner



PostgreSql 9.6.2
Download and run the installer, default settings are fine, you will be prompted to enter your superuser password.

Creating the Project

First create a folder in a spot on your machine where you want your project to live. I'm going to put mine on my desktop.
Example Path: C:\Users\Adam\Desktop\MyProject


Open Windows PowerShell and navigate to the folder you created

Execute the command: dotnet new

Then execute the command: dotnet new webapi

Finally execute:  code .
This will open your project in VSCode

Adding NuGet Packages

Open your terminal (Ctrl+`)
Execute the following commands
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package NpgSql.EntityFrameworkCore.PostgreSQL
dotnet add package NpgSql.EntityFrameworkCore.PostgreSQL.Design
dotnet add package Microsoft.EntityFrameworkCore.Tools.DotNet



You will need to add the <ItemGroup> for tools and add the reference to the Entity Framework Tools to your csproj file. The file should reflect the image above


Create Your Database




Open PgAdmin 4
In the Create-Server Dialog enter a server name in the General tab, I chose "MyProject". In the Connection tab you can enter localhost for the hostname and type a password of your choice. For this example my password will be password. You can leave the other fields as their default values.


Create DbContext and Model



In VSCode create a file  called dbContext.cs. File contents:

using Microsoft.EntityFrameworkCore;
using MyProject.Models;

namespace MyProject
{
    public class MyProjectDbContext : DbContext
    {
        public DbSet<Person> People { getset; }
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseNpgsql("Host=localhost;Port=5432;Username=postgres;Password=password;Database=MyProject;");
        }
    }
}

In VSCode create a Models Folder in the root of your project. Inside of the Models folder create a Person.cs. File contents:

namespace MyProject.Models
{
    public class Person
    {
        public int PersonId { getset; }
        public string Name { getset; }
    }
}


Adding Migration and Updating Database


It's finally time to add your migration and update your database. We are going to call our migration "initial" but you can call it whatever you like.
In the VSCode terminal execute the command: dotnet ef migrations add initial
This should have generated some files under a new folder in your project called Migrations



Lastly to update the database execute the command: dotnet ef database update
Your database should now be updated in PgAdmin. You may have to refresh the database to see the changes.


That's it, thank you very much for reading. Please comment if you run into any issues I might be able to help with!