Fandom

Surfpup's tConfig Mod Wiki

Generating Ore During Online Play w/Items

124pages on
this wiki
Add New Page
Comments7 Share

by Taien

Difficulty: Difficulty-4
Clock: 15-20 minutes


IntroductionEdit

This tutorial assumes you already have knowledge of how to create a custom ore and how to create usable items. If you don't, I recommend you visit the Tutorials Page and read up on those subjects first.

Note: For the purposes of this tutorial we'll refer to the spawner item as Spawner and the custom ore/tile as Platinum.

Files you'll need ahead of time:

  • A custom tile (Tile/Platinum.ini and Tile/Platinum.png)
  • A custom ore item for the tile (Item/Platinum Ore.ini and Item/Platinum Ore.png)
  • A custom spawner item (Spawner.png)

Files we'll be creating (in order of creation):

  • Item/Spawner.ini
  • Global/GameMode.cs
  • Global/OreHandler.cs
  • Global/World.cs
  • Item/Spawner.cs

Let's Start!Edit

Step 1: Create an Item/TileEdit

  • Create an ini and a png for the spawner item in Item/Spawner.ini (or whatever). I recommend making it a consumable item since you don't want people spawning infinite amounts of ore! Here's an example .ini file for a spawning item:
[Stats]
width=20
height=20
type=-1
useAnimation=20
useTime=20
toolTip='Brings platinum to the world'
maxStack=1
scale=1.0
rare=4
value=50000
consumable=true
useStyle=4
  • You should also have a tile already set up at this point that you want to use for this. I'll call mine 'Platinum'. Here's an example .ini file for the tile:
[Stats]
id=-1
Width=1
Height=1
pick=0.25
axe=0
hammer=0
DropName=Platinum Ore
minPick=100
Lighted=True
hitSound=21
hitSoundList=21
MergeDirt=False
Cut=False
Alch=False
Shine=120
Shine2=True
Stone=False
WaterDeath=False
LavaDeath=False
Table=False
BlockLight=True
NoSunLight=True
Dungeon=False
SolidTop=False
Solid=True
NoAttach=False
NoFail=False
FrameImportant=False
  • As you can see, I've set this ore to have a minimum pick requirement of 100 power (which I believe is Hellstone). It also has a pick modifier of 0.25, which means with a Hellstone pick it will take four hits to break. You don't need to have Lighted=true, but I always like to spruce up my ores with particle effects/light, and the AddLight method is a good place for that. Also you may notice I have hitSoundList=21, even though it really should be 1 (I believe). This is because there seems to currently be a bug in tConfig in multiplayer play that takes the soundList id as the sound id. Offline, hitSoundList=1 will work just fine.
  • You'll also need a Platinum Ore.ini and Platinum Ore.png. Once again, see the Custom Ore tutorial if you don't know how to create these.
  • Before we code the .cs file for the spawning item, we need to set up some NetMessage codes in our mod. Now, since we want this mod to have separate code for online and offline play, we have to create a few custom classes for the mod to use to transfer information back and forth.

Step 2: Set up the basic client mod classEdit

  • First, we need to set up an "interface" (although not the tConfig definition of one - the CODING definition), which is a class that contains methods with no bodies which C# refers to as 'virtual' methods. Think of this class as a template for which methods have to be handled by the server. While it seems like it does nothing, you'll understand later why we need this.
  • We're going to call our interface "GameMode", since that's what Surfpup calls it, and why change a good thing? :p It goes in Global/GameMode.cs.
  • Code it as follows:
public class GameMode
{
    public virtual void Initialize()
    {
        //
    }
    public virtual void NetReceive(int msg, BinaryReader reader)
    {
        //
    }
    public virtual void NetSend(int msg, BinaryWriter writer)
    {
        //
    }
}
  • This essentially tells the program that any class which is child to (inherits from) this class will require these methods, or it will fail to compile.

Step 3: Create the 'thinking' classEdit

  • Next we need to set up the class which will be doing the brunt of the work. This class will inherit from the GameMode class, which will help to structure it. We'll call this one Global/OreHandler.cs.
public class OreHandler : GameMode
{
  • The colon tells the compiler that we'll be inheriting from (childed to) a 'superclass', which is GameMode.
  • Next we should create some constants which represent the different message codes we'll be using, and some variables to store any needed information, which in this case is only the modIndex.
public class OreHandler : GameMode
{
    const int USE_SPAWNER_ITEM = 1; //Client sends this to server
    const int SPAWNER_SUCCESS = 2; //Server sends this as a response
    public int modIndex;
  • In programming common practice, you always name constants in all caps, as a reminder that they can't be changed.
  • We're going to need a method to set up the variables in this class. We'll call this method 'Initialize', because we're elite coders who use proper terminology.
    public override void Initialize(int modIndex)
    {
        this.modIndex = modIndex;
    }
  • Notice the override tag in the method declaration. This tells the compiler that we're using this method to replace the empty one in the superclass GameMode.cs. Also, you would change "TaienMod" to whatever you're calling this mod, or the name of your mod if you're just adding this code to one that already exists. :)
  • Now we'll set up a method that the server will use to create the ore in the world. This code is directly copied from the "How to Make a Custom Ore" tutorial:
   public static void AddPlatinum()
   {
       int LowX = 0;
       int HighX = Main.maxTilesX;
       int LowY = (int)Main.rockLayer;
       int HighY = Main.maxTilesY - 200;
       int X = Main.rand.Next(LowX, HighX); //don't touch
       int Y = Main.rand.Next(LowY, HighY); //don't touch
       int OreMinimumSpread = 2; //feel free to tweak
       int OreMaximumSpread = 6; //feel free to tweak
       int OreMinimumFrequency = 5; //feel free to tweak
       int OreMaximumFrequency = 9; //feel free to tweak
       double OreSpread = (double)WorldGen.genRand.Next(OreMinimumSpread, OreMaximumSpread + 1); //don't touch
       int OreFrequency = WorldGen.genRand.Next(OreMinimumFrequency, OreMaximumFrequency + 1); //don't touch
       WorldGen.OreRunner(X, Y, OreSpread, OreFrequency, Config.tileDefs.ID["Platinum"]);
   }
  • You can mess with the value of LowX, HighX, LowY, HighY, OreMinimumSpread, OreMaximumSpread, OreMinimumFrequency, and OreMaximumFrequency to your heart's content, but don't set them too insanely high or you'll have some laggy action going on when you use the item.
  • Next comes the fun part. We'll now create the NetSend and NetReceive methods for the server, which will handle the online-i-ness of this bit of codework:
   public override void NetSend(int msg, BinaryWriter writer)
   {
       if (msg == USE_SPAWNER_ITEM)
       { 
           writer.Write((byte)Main.myPlayer);
       }
   }
   public override void NetReceive(int msg, BinaryReader reader)
   {
       if (msg == USE_SPAWNER_ITEM)
       {
           //this message will only be received by the server
           int playerID = (int)reader.ReadByte();
           for (int i = 0; i < 60; i++) AddPlatinum();
           //now we'll send one back to the clients to indicate this has happened
           NetMessage.SendModData(modIndex, SPAWNER_SUCCESS);
           NetMessage.SendData(25, -1, -1, Main.player[playerID].name + " has used a Platinum Spawner!", 255, 175f, 75f, 255f, 0);
       }
       else if (msg == SPAWNER_SUCCESS)
       {
           //this message will only be received by the clients, since it is only SENT by the server
           //this message isn't really necessary, but we'll use it to create a sound effect in all clients when the item is used
           Player p = Main.player[Main.myPlayer];
           Main.PlaySound(6,(int)p.position.X,(int)p.position.Y,6);
           //magic mirror sound is perfect for this
       }
   }
  • Now for a little explanation. As you can see in the NetSend method, we're appending the playerID of the person who caused the message to be sent (the person using the item). This will allow us to reference them in any code which uses this message. In the NetReceive method, we are first checking if the message being sent is one of ours (and not a regular old server packet). If it's a USE_SPAWNER_ITEM, which will only be received by the server, we'll handle what will happen serverside when this message is received (in this case, running the ore spawning routine 60 times to generate 60 clumps in the world, and then sending a message back to the clients to indicate that this has happened). Sending a message back to the clients isn't really necessary for this exercise, but it's good to know how to do it in case you want to create some special effects. If the message being received is a SPAWNER_SUCCESS, which only the clients will get, the mod will play a sound (the magic mirror sound, in this case). As you can see once again, I used the same number in the soundlistid and the soundid due to the multiplayer glitch.
  • OK! This class is now complete, only a little more work to do connecting things up. Here's the completed class, for reference:
public class OreHandler : GameMode
{
    const int USE_SPAWNER_ITEM = 1; //Client sends this to server
    const int SPAWNER_SUCCESS = 2;
    public int modIndex;
    public override void Initialize()
    {
        modIndex = Config.mods.IndexOf("TaienMod");
    }
    public static void AddPlatinum()
    {
        int LowX = 0;
        int HighX = Main.maxTilesX;
        int LowY = (int)Main.rockLayer;  //where the brown background and the sky background meets
        int HighY = Main.maxTilesY - 200;     //where hell and the grey background meets
        int X = Main.rand.Next(LowX, HighX); //don't touch
        int Y = Main.rand.Next(LowY, HighY); //don't touch
        int OreMinimumSpread = 2; //feel free to tweak
        int OreMaximumSpread = 6; //feel free to tweak
        int OreMinimumFrequency = 5; //feel free to tweak
        int OreMaximumFrequency = 9; //feel free to tweak
        double OreSpread = (double)WorldGen.genRand.Next(OreMinimumSpread, OreMaximumSpread + 1); //don't touch
        int OreFrequency = WorldGen.genRand.Next(OreMinimumFrequency, OreMaximumFrequency + 1); //don't touch
        WorldGen.OreRunner(X, Y, OreSpread, OreFrequency, Config.tileDefs.ID["Uranium"]);
    }
    public override void NetSend(int msg, BinaryWriter writer)
    {
        if (msg == USE_SPAWNER_ITEM)
        { 
            writer.Write((byte)Main.myPlayer);
        }
    }
    public override void NetReceive(int msg, BinaryReader reader)
    {
        if (msg == USE_SPAWNER_ITEM)
        {
            //this message will only be received by the server
            int playerID = (int)reader.ReadByte();
            for (int i = 0; i < 60; i++) AddPlatinum();
            //now we'll send one back to the clients to indicate this has happened
            NetMessage.SendModData(modIndex, SPAWNER_SUCCESS);
            NetMessage.SendData(25, -1, -1, Main.player[playerID].name + " has used a Platinum Spawner!", 255, 175f, 75f, 255f, 0);
        }
        else if (msg == SPAWNER_SUCCESS)
        {
            //this message will only be received by the clients, since it is only SENT by the server
            //this message isn't really necessary, but we'll use it to create a sound effect in all clients when the item is used
            Player p = Main.player[Main.myPlayer];
            Main.PlaySound(6,(int)p.position.X,(int)p.position.Y,6);
            //magic mirror sound is perfect for this
        }
    }   
}

Step 4: Create the modified World classEdit

  • Next, we'll set up a custom world class, located in Global/World.cs.
  • In this class we'll basically handle the initialization of the subparts we've just programmed, as well as pass along messages to the mod. Here's the code:
#INCLUDE "GameMode.cs"
#INCLUDE "OreHandler.cs"
public static GameMode gameMode;
public static int gameModeID;
public void Initialize() 
{
    if(Main.dedServ)
    {
        gameMode = new OreHandler();
        gameModeID=0;
        gameMode.Initialize();
    }
    else
    {
        gameMode = new GameMode();
    }
}
public void NetReceive(int msg, BinaryReader reader)
{
    gameMode.NetReceive(msg, reader);
}
public void NetSend(int msg, BinaryWriter writer)
{
    gameMode.NetSend(msg, writer);
}
  • The lines with #INCLUDE tell the compiler to include the files listed in the compile, even though they're not normally part of the class. Without these, the program will fail to compile because it won't be able to reference the needed classes.
  • As you can see, the Initialize() method has a check to see whether the code is being run by the server or not. If it is, it uses the child class of GameMode, OreHandler, which contains all the message information and data. If not, it uses the simpler version, GameMode, which basically passes along all calls to it as Terraria normally would without modification.
  • Also, we pass along the NetReceive and NetSend messages here. If we were using the old netcode (before the advent of SendModData), we would also check if the message code is 100 here (which denotes a custom message).
  • Almost done!

Step 5: Add the NetMessage call to the Spawner item's UseItem methodEdit

  • And now we've reached the end of the road. The final step is to allow the item to send the proper NetMessage to the server, but only if they are a client. The following goes in Item/Spawner.cs (or whatever you called the spawner):
const int USE_SPAWNER_ITEM = 1;
public void UseItem(Player p,int pid)
{
    if(Main.netMode==1) //if the sender is a client
    {
        int modIndex = Config.mods.IndexOf("TaienMod");
        //send message to start the ore routine
        NetMessage.SendModData(modIndex, USE_SPAWNER_ITEM, -1, -1, (byte)pid);
        //you don't have to declare a constant to put into the message here, you could also just use 1
    }
}
  • Note that you convert the player ID (pid) to byte as you pass it to the SendModData method. Any parameters(and as far as I know, you can send as many as you want) you send to this method must be converted to bytes, and you must remember the order in which they are passed. When you read out these bits of data (in NetReceive, usually), you read them back in the same order, casting them back to their original types. In this way you can transfer any data you need to with the message, including player IDs, item IDs, npc IDs, coordinates, etc, etc, etc.
  • That's it! You should now be able to compile and have a fully-functional ore-spawning item. You can add whatever effects you like to the UseItem call, as well as put them in the same section we placed the sound effect before if you want all clients to experience them.

Thank You For ReadingEdit

And thanks to Surfpup for tConfig and for helping me find this information. Hopefully others will put it to good use, and won't have to search too hard to find it :D -Taien

Ad blocker interference detected!


Wikia is a free-to-use site that makes money from advertising. We have a modified experience for viewers using ad blockers

Wikia is not accessible if you’ve made further modifications. Remove the custom ad blocker rule(s) and the page will load as expected.