CSharp - Beginner's Guide

From Stardew Modding Wiki
Jump to navigation Jump to search

Last edited by Abagaianye on 2025-09-13 06:24:08

Introduction

This is an introduction to programming in C# - The language Stardew Valley is written in, and in which its code mods are made.
It contains the basics of C#, and some examples specific to Stardew Valley that you can copy paste and learn from.


C# is one of the easiest programming languages to learn, and since modding is easier than programming from scratch, this will be a good place to start.
You do not need to understand math to program. It is mostly memorizing lines of code. (and Googling + copy pasting)
Most of what's in this guide can be learned from any other C# programming tutorial, however this one is specialized for Stardew Valley.


Requirements

  • SMAPI Developer Edition
  • Visual Studio Community
  • A PC that can run Visual Studio Community. Anything made in the last 10 years will probably do.


C# Basics

Variables

Variables store data such as numbers, text, true/false, and more. The most common variable types are:

  • int - a number without a decimal.
  • float - a number with a decimal.
  • string - text.
  • bool - true/false.

Creating variables:

int lives = 3;
float speedMultiplier = 1.25f;
string sentence = "Hello there!";
bool alive = true;

You can change the value of existing variables.

alive = false;
sentence += " How are you?";
speedMultiplier = lives * 0.25f + 1;
lives--;

If not sure what variable type a value is, just call it var. This only works inside methods, not inside classes.

var someNumber = 5;					//This will turn into an int.
var someAnotherNumber = 102.25f;    //This will turn into a float.
var someText = "Some text.";        //This will turn into a string.

Methods

Methods contain all the actual code. You can call methods from other methods to execute code.

Example:

void Start()	//Imagine this method gets called on game startup.
{
	SomeMethod();
	MethodWithArgument(5);
	
	int someInt = 100;
	MethodWithArgument(someInt); //Variables can be used interchangeably with constant values like the 5 above.
	
	MethodWithManyArguments(10, 4, 0.5f, "Hello", true);
}


void SomeMethod()
{   			
	//You can write comments in code with 2 forward slashes
	//You can call methods from other methods. Try not to create an endless loop or the program will crash.
	MethodWithArgument(10);
}

void MethodWithArgument(int someArgumentNumber)
{
	//Methods can take arguments, which are variables you can use in the method.
}

void MethodWithManyArguments(int someNumber,int someOtherNumber, float yetAnotherNumber, string someString, bool someBool)
{
	//You can use any number of arguments.
}


Conditions

A variable's value can be checked with conditions.

Examples:

void TakeDamage(int damage)
{
	hitpoints -= damage;	//	-= is short for 	hitpoints = hitpoints - damage;
	if(hitpoints <= 0)
	{
		Die();
	}
}


void TryToBuySomething(int cost)
{
	if (money >= cost)
	{   
		BuyItem()
		money -= cost;
	}
	else
	{
		PlayErrorSound();
	}
}

Classes

Classes store variables and methods. All methods and variables must be inside a class.

Two important use cases:

  • Manager classes such as SceneManager or AudioManager, of which you only have 1, which have methods that can be called to change the scene, play an audio clip, or change the music.
  • Instance classes such as NPC or Object, of which there are many, and can be created and deleted as needed. Every time you catch a fish, a new Object class with the fish's properties gets created for example.


Manager class example.

public static class AudioManager
{
	public static void ChangeMusic(string musicID)
	{
		//Do music changing
	}
	public static void PlayAudioClip(string audioClipID)
	{
		//Play audio clip
	}
}

Note how this class and its methods have the modifiers "public" (can be called from any class instead of just the same class, opposite of the default "private") and "static" (only one of each, an instance of the class can not be created.)

To change the music all you have to do is call from any script:

void EnterCoolCastle()
{
	AudioManager.ChangeMusic("castleMusicID");
}


Instance/blueprint class example.

public class Enemy
{
	float speed;
	int lives;
	int damage;
	
	float x;
	float y;

	//This is a constructor. It is like a method that is used to create a new class instance. Constructors are used to assign the class' starting variable values.
	public Enemy(float startX, float startY, int _damage, float _speed)	
	{
		x = startX;
		y = startY;
					
		damage = _damage;
		speed = _speed;
		lives = 3;
	}
	
	void FollowPlayer()
	{
		//Following player code goes here
	}
}

An instance of Enemy could be created from for example a method in EnemyManager

void Create2Enemies()
{
	Enemy fastEnemy = new Enemy(250,500,25,5f);			//The "new" keyword calls a constructor, creating a new instance of a class.
	Enemy strongEnemy = new Enemy(300,500,50,2.5f);     //Use different values to create a different type of enemy.
}

Notice how "Enemy" is now also a variable type. fastEnemy and strongEnemy are variables of type "Enemy"

A Stardew modding beginner will likely only use manager classes, assuming you even need more than just the ModEntry class.

Stardew Valley

Creating the mod

The actual part of creating the mod + files + setup has a guide on the main wiki. Use that one for now.

https://stardewvalleywiki.com/Modding:Modder_Guide/Get_Started#Create_a_basic_mod

What & When

When creating a mod consider 2 questions:

  • What do you want to happen?
  • When do you want it to happen?


"What" could be:

  • Send a letter
  • Get a cool sword
  • Open a menu
  • Restore some HP
  • Kill all enemies in the room
  • Start an event
  • Get an achievement


"When" could be:

  • Every single frame
  • Pressing a button
  • The day starts
  • Warping to a new area
  • Interacting with a map tile
  • Using an item
  • An event command is called


The possibilities for both "What" and "When" are endless. Combine these two and you get endless squared. Your skill and creativity are the limit. For example:

  • Start an event when interacting with a tile.
  • Kill all enemies in the room when using an item.
  • Get an achievement when entering an area and have a specific item in your inventory.

Important "Whens"

Events

Most of Stardew modding's important "When"s are events.

An event (code events, not Stardew Valley events) is a variable that is a list of methods.

You can add methods to an event, and when the event is called, all those methods are called.

You can create your own events, but let's focus on some events that come with SMAPI, as they are far more useful.

SMAPI mods start in the Entry method in the ModEntry class.

The method has a "IModHelper" argument.

You can use IModHelper to access events.

Type helper.Events. and you'll get an autocomplete window showing several event categories.

Let's check out the player warp event.

Type in the Entry method:

 helper.Events.Player.Warped +=

Then press tab twice to automatically create a method that gets called when the player warps.

This is one example of "When".

Whenever the player warps, some code is executed.

Let's add a simple console log whenever the player warps.

 
    public override void Entry(IModHelper helper)
    {
		helper.Events.Player.Warped += Player_Warped;
	}
	
	private void Player_Warped(object? sender, WarpedEventArgs e)
    {
		Monitor.Log("PLAYER HAS WARPED TO: " + e.NewLocation.DisplayName);
		//The "e" variable has some cool data in it like the old location and new location.
		//Both these locations have a DisplayName variable (string) that you can log.
    }


Another example would be when pressing a button.

    public override void Entry(IModHelper helper)
    {
		helper.Events.Input.ButtonPressed += Input_ButtonPressed;
	}
	
     private void Input_ButtonPressed(object? sender, ButtonPressedEventArgs e)
     {
         if (e.Button == SButton.P)
         {
             Game1.playSound("toyPiano");
         }
     }

Now whenever the P button is pressed, you hear a sound effect.


Some other important events to consider:

  • helper.Events.Input. input such as mouse movement, mouse pressed, keyboard pressed, keyboard released, etc.
  • helper.Events.GameLoop. things such as a game tick, every second, game saved, game loaded, day start, day end, etc.
  • helper.Events.Display. for drawing things to the screen such as UI elements, and moving parts on maps like rotating clock hands, etc.

Experiment with all the different events.


Tile actions

Tile actions can be things such as doors, shops, or just dialogue boxes.

Type:

GameLocation.RegisterTileAction("SomeTileAction", SomeTileAction);

The second SomeTileAction will show an error. Select it, press alt + enter, and "Generate Method". Let's just play a sound effect.

    public override void Entry(IModHelper helper)
    {
		GameLocation.RegisterTileAction("SomeTileAction", SomeTileAction);
	}

    private bool SomeTileAction(GameLocation location, string[] arg2, Farmer farmer, Point point)
    {
		Game1.PlaySound("toyPiano");
		return true;
		
		//Notice how this method is of type "bool" instead of the usual "void"
		//Methods can have types just like variables, and this one is of type "bool"
		//This means it wants something returned, in this case a true/false
		
		//For tile actions specifically, that would be whether the tile action happened succesfully.
		//true: Do not interact with item you're holding. (Such as eating food or teleporting with a warp totem)	 
		//false: Interact with item you're holding.
    }

Now whenever you interact with a tile on a map with this tile action, you'll hear a sound effect.

If you only want to do something when you're holding a specific item, like a diamond:

    private bool SomeTileAction(GameLocation location, string[] arg2, Farmer farmer, Point point)
    {
		//Always null check player.ActiveObject, because if you're not holding an item and try to access its variables, you get an error. 
		if(Game1.player.ActiveObject != null && Game1.player.ActiveObject.ItemId == "72")
		{
		
			Game1.activeClickableMenu = new DialogueBox("Thank you for your diamond.");
			Game1.player.reduceActiveItemByOne();
			return true; 
			//Because of the return keyword, the rest of the code in the method does not get called.
		}
		Game1.activeClickableMenu = new DialogueBox("Bring me a diamond.");
		return true;
    }

Event Commands

This one is actually Stardew Valley events :) I already wrote a beginner friendly guide on this wiki for that here:

CSharp - Adding Custom Event Commands







Important "Whats"

What should code do when it is triggered by a "When"?

You don't need to understand all the code for now. Just copy paste what you need, and familiarize yourself with the syntax.


Some examples shown here:

  • Adding item to inventory
  • Warping
  • Adding a buff
  • Creating a new console command

//todo: this segment is too specific atm and needs to be important, generic, simple stuff only.

//like the player class, Game1 class, starting an event

//save specific things for a part 2.


	void AddCoolFishToInventory()
	{
		//You'll be using Game1.player a lot. This is the player's class, and has some useful methods like the one below used to add items to the inventory.
		Game1.player.addItemByMenuIfNecessary(new Object("699", 1));	//699 is Tiger Fish
	}
	
 	void AddCoolRandomFishToInventory()
	{
		//An array of item IDs
		string[] possibleFish = new string[]
		{
            "698",	//Sturgeon
            "699",	//Tiger Trout
            "700",	//Bullfish
            "701",	//Tilapia
        };
		//Get a random fish from the array. 
		string randomItemId = possibleFish[Random.Shared.Next(possibleFish.Length)];
		
		Game1.player.addItemByMenuIfNecessary(new Object(randomItemId, 1));
	}


 	void WarpToForest()
	{
		Game1.warpFarmer("Forest", 90, 16);
	}





	void GiveCoolBuff()
	{
		//This gives a buff with 2 speed, 2 attack, for 5 minutes. It has a 50% Chance to double the speed and attack values.
		//This is a custom buff because it has an ID of -1, which does not exist in Data/Buffs.
		
		BuffEffects effects = new BuffEffects();	//Create a new instance of the BuffEffects class
        effects.Speed.Value = 2;                    //Speed and Attack are also classes inside the BuffEffects class, which have a Value variable for its number.
        effects.Attack.Value = 2;
		
		if (Random.Shared.Next(2) == 0)
		{
			//This adds a 50% chance to multiply the Speed and Attack by 2.
			//Random.Shared.Next(2) returns either 0 or 1, which is a total of 2 random numbers.
			effects.Speed.Value *= 2;
			effects.Attack.Value *= 2;
		}
		
        int duration = 1000 * 60 * 5; 				//This is 5 minutes. 1000 Milliseconds = 1 second * 60 = 1 minute * 5 = 5 minutes
        Game1.player.applyBuff(new Buff("-1", "Cool source", "Cool display source", duration, null, 0, effects, false, "Cool display name", "Cool description"));
		
		
		//If you want you can instead give a buff defined in Data/Buffs, like oil of garlic with its default values, type:
		//Game1.player.applyBuff(Buff.avoidMonsters);
		//Buff.avoidMonsters is a constant string that is the oil of garlic's buff ID.
		
		
		//Or a buff that your mod adds to Data/Buffs via Content Patcher
		//Game1.player.applyBuff("YourModBuffId");

		//The applyBuff method has 2 "overloads"
		//That means there's 2 argument orders you can call it with.
		//1: string ID for the buff
		//2: Buff class
	}





	
Adding console commands

	void ModEntry(IModHelper helper)
	{
	    helper.ConsoleCommands.Add("someCommand", "this is a debug command.", (a, b) => 
		{ 
			Monitor.Log("The console command was called.");
		});
	}




C# Basics 2

Continued so as not to overwhelm you at the start.

Vectors & Points

Vectors are a variable type, nearly indentical to a class, used for positions, directions, and velocity. But mostly position.

It is like a class that stores coordinates.

  • "Vector2" stores x, y.
  • "Vector3" stores x, y, z.
  • "Point" stores x, y. Points use ints for x and y instead of floats like vectors.

Vectors are for precise positions. (Player/NPC position)

Points are for tile grids. (Tile position)

To create a new Vector/Point just type:

Void SomeMethod()
{
    //Remember contructors from the classes chapter.
    Vector2 position = new Vector2(25.24f,98f);
    Point doorTile = new Point(24,32);
}


Arrays & Lists

An array holds a bunch of variables of the same type.

A list is a better array that you should almost always use instead, because it has .Add and .Remove() methods.

You can not add or remove from arrays.

	void SomeMethod()
	{
		//This is an array of type int
	    int[] bunchOfNumbers = new int[]
        {
            1,2,3,4,5,6,7,8
        };
		
		//This is a List of type int
		List<int> someList = new List<int>()
		{
			1,2,3,4,5,6,7,8
		};
		someList.Add(9);
		bool addTen = true;
		if(addTen)
			someList.Add(10);
	}

Arrays and Lists have a starting index of 0, meaning that the 0th index of the array is the first item, the 1st index the second item, the 2nd the third item, etc.

You can access the values of an array or list like so:

	void SomeMethod()
	{
	    int[] bunchOfNumbers = new int[]
        {
            1,2,3,4,5,6,7,8
        };
		List<int> someList = new List<int>()
		{
			1,2,3,4,5,6,7,8
		};
		
		int firstNumber = bunchOfNumbers[0];
		int secondNumber = bunchOfNumbers[1];
		int lastNumber = bunchOfNumbers[^1];
		
		int firstListNumber = someList[0];
		int secondListNumber = someList[1];
		int lastListNumber = someList[^1];
		
		//You can change the values of arrays and lists.
		
		someList[0] = 1000;
		someArray[0] = 1000;
		
		
		//Do not access array values out bounds, or you get an error.
		//This will cause an error:
		
		int thisCausesAnError = someList[1000];
		
		//You may not know in advance if the code you want to execute will be in bounds of the array.
		//So check using a condition.
		
		if (someArray.Length >= 4)
		{
		    //Now we know we can access someArray[3]
		}
	}


Dictionaries

Dictionaries are key:value pairs.

They have a key input instead of an index, and a value output.

If you've used dialogue files, you've used dictionaries.

Most dictionaries use a string as key, and some class or another string as value.

Like how items have item IDs as keys, and item data as the value.

Furniture data uses an item ID as key, and a string as value, which contains the furniture item's data separated by slashes (which is then read by code and turned into a class)

	Dictionary<string,string> dialogue = new Dictionary<string,string>();
	void AddDialogues()
	{
		dialogue["spring_1"] = "Hello! Happy spring first!";
		
		string springOneDialogue = dialogue["spring_1"];
	}

When accessing a dictionary the key you're looking for might not exist.

If you access a dictionary with a key that does not exist you get an error.

Check if the key exists first.

	void DoSomething()
	{
		string keyToCheck = "someKey";
		if(someDictionary.ContainsKey(keyToCheck))
		{
			//The key exists!
			string someValue = someDictionary[keyToCheck];
		}
		else
		{
			//The key does not exist.
		}
	}

	void DoSomething()
	{
		string keyToCheck = "someKey";
		if(someDictionary.TryGetValue(keyToCheck, out var value))
		{
			string someValue = value;
		}
		
		string someOtherKeyToCheck = "someOtherKey";
		//You can also invert this condition with a ! to log errors. 
		if(!someDictionary.TryGetValue(someOtherKeyToCheck, out var value))
		{
			Monitor.Log("Key not found in dictionary: "+ someOtherKeyToCheck);
			return;
		}
		string someValue2 = value;
	}

Loops

Loops in code allow you to execute the same lines of code multiple times. The loops are:

  • for
  • foreach
  • while

"for" loops will loop a set number of times. You can set a constant value like 100, or a dynamic value like array.Length.

	//This loop will execute 100 times instantly.
	//Each loop the "i" variable changes value by one, so the loop is different each time.
    for (int i = 0; i < 100; i++)
    {
		Monitor.Log("Hello!");
		Monitor.Log(i.ToString());	//This will print 0 to 99 (A total 100 numbers)
    }
	//This loop will execute once for every entry of the array.
	for (int i = 0; i < someArray.Length; i++)
    {
		Monitor.Log("Hello!");
		Monitor.Log(someArray[i].ToString());
    }

You can create a for-loop by typing:

for

And then pressing tab twice to create the for loop snippet.


"foreach" loops are used in conjuntion with Arrays, Lists, and Dictionaries.

	//This loop will execute once for every entry in the array.
	foreach (var item in someArray)
    {
		Monitor.Log(item.name);
    }
	//This loop will execute once for every entry in the list.
	foreach (var item in someList)
    {
		Monitor.Log(item.name);
    }
	//This loop will execute once for every entry in the dictionary.
	foreach (var pair in someDictionary)
    {   
		Monitor.Log(pair.Key);
		Monitor.Log(pair.Value.name);
    }

"while" loops stay on for as long as a condition is met.

These are risky because it's possible to create an infinite loop with a minor oversight, crashing the game.

	int i = 0;
	while (i < 100)
	{
		i++;
		Monitor.Log(i.ToString());
	}

These keywords can be used in any type of loop:

  • continue: Go to the next loop immediately.
  • break: Break loop, and continue after loop.
  • return: Break method, and continue where the method was called. (This is not loop-specific but still useful to keep in mind)

Methods with return types

Most methods are of type void, which means that they return nothing.

Some methods are instead of a variable type, such as int, bool, string, or a class.

This means these methods must return a variable that can be read and stored.

	void SomeMethod()
	{
		bool someBool = IsThisStringLongerThan10Characters("Some cool string");
		string someString = CombineThese3StringsWithSpaces("String a", "String b", "String c");
		float someNumber = MultiplyThese2NumbersAndThenAdd10(5,25);
		SomeClass someClassInstance = GiveMeAClassWithCoolRandomValues();
	}

	bool IsThisStringLongerThan10Characters(string str)
	{
		if(str.Length > 10)
			return true;
		return false;
	}

	string CombineThese3StringsWithSpaces(string a, string b, string c)
	{
		return a + " " + b + " " + c;
	}

	float MultiplyThese2NumbersAndThenAdd10(float a, float b)
	{
		Monitor.Log("We're doing the multiply method now.);
		return a * b + 10;
	}

	SomeClass GiveMeAClassWithCoolRandomValues()
	{
		SomeClass newClass = new SomeClass();
		newClass.x = Random.Shared.Next(255);
		newClass.y = Random.Shared.Next(255);
		newClass.z = Random.Shared.Next(255);
		return newClass;
	}


Switches

Executes code depending on a variable's value.

It's like a very fancy and more readable if, if else, if else, if else..

	void SomeMethod()
	{
		int a = Random.Shared.Next(5);
		switch(a)
		{
			case 0:
				Monitor.Log("Zero was randomly chosen.");
				break;
			case 1:
				Monitor.Log("One was randomly chosen.");
				break;
			case 2:
				Monitor.Log("Two was randomly chosen.");
				break;
			default:
				Monitor.Log("Another number was randomly chosen.");
				break;
		}
	}

In Stardew's source code, switches are often used with an item's ID to check what to do when it is used.

Example of the teleport totems in Stardew's code below (edited slightly for readability)

	private void totemWarpForReal()
	{
		switch (theItem.QualifiedItemId)
		{
			case "(O)689":
				Game1.warpFarmer("Mountain", 31, 20);
				break;
			case "(O)690":
				Game1.warpFarmer("Beach", 20, 4);
				break;
			case "(O)261":
				Game1.warpFarmer("Desert", 35, 43);
				break;
			case "(O)886":
				Game1.warpFarmer("IslandSouth", 11, 11);
				break;
		}
	}


Further Learning

No guide can cover everything.

Here's where to continue:


Youtube

A great place to learn.

Search Youtube for a C# beginner's guide and see which parts of this guide you've remembered.

Sadly there's very little of Stardew-specific C# guides on there.


Decompile Stardew Valley

This is the best way to learn code specific to Stardew Valley.

You can even call some of the methods you find in here, and copy paste some of that code.

There's a decompilation guide on the man wiki.

https://stardewvalleywiki.com/Modding:Modder_Guide/Get_Started#How_do_I_decompile_the_game_code.3F

CTRL + Shift + F allows you to search the entire decompiled code. Try searching an item ID to see how it's implemented. Examples:

  • (O)261 //Desert warp totem to learn about warping
  • (O)74 //Prismatic shard to learn about the Galaxy Sword event
  • (F)1466 //Television to learn about television code

Note that decompiled code isn't flawless, there will be some red errors, and the syntax changes a bit. Despite that it is still the best place to learn.


Other mods

Some mods have public source code. A great way to learn specific things. You can also decompile mods. Follow the same steps as decompiling Stardew's code.


Other people

Having a friend who cares, teach you specific things is the best way to learn specific things. Maybe you can be that friend in the future. If friendless you'll have to settle for online communities.


End

This guide is a work in progress but covers the fundamentals.

Todo: add links

Planned topics:

When & What part 2

Loading files from content patcher

Improvements to What part 1, important vanilla classes and variables to touch.

Improve formatting and newlining/spacing.

Remove filler words.