Skip down to the Specific Datastorage Formats if you want the useful stuff. It's mostly just what I'm thinking so lots of speculation and development of reasoning (or me making lots of assumptions).
- Entrypoint is
StardewValley.Program.cs:39
- Game1 is a godclass holding the gamestate and most used Constants
- Content is loaded using
Microsoft.XNA.Framework.Content
The Dictionaries below are used to index most objects and are accessible under StardewValley.Game1
.
public static IDictionary<int, string> objectInformation;
public static IDictionary<int, string> bigCraftablesInformation;
public static IDictionary<string, string> NPCGiftTastes;
- XNB is a binary format used to pack assets. Apparently decompressable with this.[[Link is dead]]
- Stardew Valley makes use of [Microsoft XNA's Content Pipeline](https://docs.microsoft.com/en-us/previous-versions/windows/xna/bb195587(v%3dxnagamestudio.41) for loading game assets.
Microsoft.XNA.Framework.Content.ContentManager
is wrapped byStardewValley.LocalizedContentManager
to provide localization support on Assets. (-es/-fr/-ru/etc. suffixed to string queries) when referring to XNBs. - XNB file contains xml/yaml, and I will be referring it to the decompressed YAML for the time being. (This may be inaccurate but is assumed hereforth. (Based on what I've read from xnbNode xml is stored in xnb but when you extract it is simplified to yaml, this may be the authors choice but it's probably likely the xnb stores compressed xml which roughly equates to yaml)))*
- Each class which loads from the content pipeline implements their own method for reading the string stored in the xml/yaml. It seems to be the constructor in most cases.
- The Content pipeline makes use of a string path format relative to the game's Content directory. So something such as
Strings\someAsset
will load<Stardew Valley Path>\Strings\someAsset.xnb
. When stored in files there is an escaped backslash used, so it will be presented asStrings\\someAsset
rather thanStrings\someAsset
.
Content references are both used in YAML and in raw code. Examples of code usage follow:
Game1.content.LoadString("Strings\\StringsFromCSFiles:Farmer.cs.1954");
Game1.content.LoadString("Strings\\StringsFromCSFiles:MoneyMadeScreen.cs.3854", (object) this.total)
Dictionary<string, string> dictionary = Game1.content.Load<Dictionary<string, string>>("Data\\animationDescriptions");
- StardewValley.NPC.cs:2558
Strings\\someAsset
will look for <stardew valley path>\Content\Strings\someAsset.xnb
.
More specific elements may be referenced such as:
Strings\\someAsset:someElement
in this case the yaml structure accessed would be something along the lines of
someAsset.yaml
in Strings\someAsset.xnb
xnbdata:
...
...
content: #!Dictionary<String,String>
someElement: "foo" #!String
...
Worth noting that string dictionaries are not the only form of storage in xnbs, and it may vary. Most of the game data however is stored in this format. And later processed manually by the class Constructor.
A third kind of reference is seen in:
Strings\\StringsFromCSFiles:Farmer.cs.1954
Is not different from before, the actual keys reflect the same format.
ie.
BarnDweller.cs.386: "{0} is sleeping." #!String
BarnDweller.cs.387: "{0} is looking happy today!" #!String
BarnDweller.cs.388: "{0} seems to be in a bad mood..." #!String
BluePrint.cs.1: "Use to see information about your animals." #!String
Buff.cs.453: "Goblin's Curse" #!String
Buff.cs.454: "-3 Speed" #!String
Buff.cs.455: "-3 Defense" #!String
YAML stored inside the content XNBs are usually found as thus:
snippet from animationDescriptions.yaml
xnbData:
target: "w"
compressed: true
hiDef: true
readerData:
-
type: "Microsoft.Xna.Framework.Content.DictionaryReader`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"
version: 0
-
type: "Microsoft.Xna.Framework.Content.StringReader"
version: 0
numSharedResources: 0
content: #!Dictionary<String,String>
abigail_videogames: "22 21 20/20/20 21 22/Strings\\animationDescriptions:abigail_videogames" #!String
abigail_sit_down: "23/23/23" #!String
From this we can see a few things:
target:w
implies the xnb is intended to be run on windows. Guessing this, will need to check with an xnb from linux/mac, but will predict it will change tol
andm
respectively- The readers required to load the
content
tag are specified under thereaderData
tag. - Types are prefixed with
#!
(ie.#!String
) which doesn't seem to be part of the yaml markup so it's likely internal. - The Content tag has it's type specified as
Dictionary<String, String>
- Each element in the content tag consists of a key-value pair.
- The type of the value is also specified as
String
, this is likely to help a reader find which elements should be parsed by theStringReader
specified earlier. Looking closer at the content tag:
content: #!Dictionary<String,String>
abigail_videogames: "22 21 20/20/20 21 22/Strings\\animationDescriptions:abigail_videogames" #!String
Key: abigail_videogames
Value: 22 21 20/20/20 21 22/Strings\\animationDescriptions:abigail_videogames
Value holds type specific data for the class. Looking at value:
/
separatorStrings\\animationDescriptions:abigail_videogames
is an asset reference to a string value that will be displayed during the animation.abigail_videogames: "Urghh... I still can't beat the first level of 'Journey of the Prairie King'!$u"
- This is supported by
abigail_flute: "0/16 16 17 17 18 18 19 19/0/silent"
which is specified as being 'silent'. At this time I'm still working on figuring out how these animations are loaded, and what each of the fields actually refer to. It's likely an internal XNA format, but I may be wrong.
- This is supported by
Animations stored in Data\animationDescriptions.xnb
.
Likely cutscene/special animations based on content.
Parsing code located in StardewValley.NPC.loadEndOfRouteBehaviour(string name):2553
:
Dictionary<string, string> dictionary = Game1.content.Load<Dictionary<string, string>>("Data\\animationDescriptions");
if (!dictionary.ContainsKey(name))
return;
string[] strArray = dictionary[name].Split('/');
this.routeEndIntro = Utility.parseStringToIntArray(strArray[0], ' ');
this.routeEndAnimation = Utility.parseStringToIntArray(strArray[1], ' ');
this.routeEndOutro = Utility.parseStringToIntArray(strArray[2], ' ');
if (strArray.Length <= 3)
return;
this.nextEndOfRouteMessage = strArray[3];
Sample data:
abigail_videogames: "22 21 20/20/20 21 22/Strings\\animationDescriptions:abigail_videogames"
Suggested values:
this.routeEndIntro/this.routeEndAnimation/this.routeEndOutro/this.nextEndOfRouteMessage
(The last field is optional)
or by types:
int[]/int[]/int[]/ContentReference as string
(The last field is optional)
Declaration of variables:
private int[] routeEndIntro; //intro anim/start frame?
private int[] routeEndAnimation; //animation?
private int[] routeEndOutro; //outro animation?
[XmlIgnore]
public string nextEndOfRouteMessage;
What the variable names mean or do isn't something I've looked into heavily yet but their declaration is on the NPC, and based on code I've read in AnimatedSprite and FarmerSprite, I'd wager it's to do with animation transitions into and out of the animation itself.
Crafting Recipies are loaded along with Cooking Recipies in StardewValley.CraftingRecipe.InitShared()
public static void InitShared()
{
CraftingRecipe.craftingRecipes = Game1.content.Load<Dictionary<string, string>>("Data//CraftingRecipes");
CraftingRecipe.cookingRecipes = Game1.content.Load<Dictionary<string, string>>("Data//CookingRecipes");
}
Data\CookingRecipe.xnb elements:
Fried Egg: "-5 1/10 10/194/default"
Omelet: "-5 1 -6 1/1 10/195/l 10"
Salad: "20 1 22 1 419 1/25 5/196/f Emily 3"
Cheese Cauli.: "190 1 424 1/5 5/197/f Pam 3"
Data\CraftingRecipe.xnb elements:
Wood Fence: "388 2/Field/322/false/l 0" #!String
Stone Fence: "390 2/Field/323/false/Farming 2" #!String
Iron Fence: "335 1/Field/324 10/false/Farming 4" #!String
Hardwood Fence: "709 1/Field/298/false/Farming 6" #!String
Gate: "388 10/Home/325/false/l 0" #!String
Key is once again an identifier as well as recipe name, with elements containing spaces and other characters without issue (no :
can be used, or \n
) to keep with the format. Reserved characters may also be found in the yaml reference.
public CraftingRecipe(string name, bool isCookingRecipe)
{
this.isCookingRecipe = isCookingRecipe;
this.name = name;
string str1 = !isCookingRecipe || !CraftingRecipe.cookingRecipes.ContainsKey(name) ? (CraftingRecipe.craftingRecipes.ContainsKey(name) ? CraftingRecipe.craftingRecipes[name] : (string) null) : CraftingRecipe.cookingRecipes[name];//determine if its cooking or crafting recipe, null if it's neither
if (str1 == null)//this seems to be a construction which forces a crafting recipe selection if null, probably just a specific
{
this.name = "Torch";
name = "Torch";
str1 = CraftingRecipe.craftingRecipes[name];
}
string[] strArray1 = str1.Split('/');//split on /
string[] strArray2 = strArray1[0].Split(' ');//first segment
//strange that they did not use Utility.stringToInt32Array()
int index1 = 0;
while (index1 < strArray2.Length)
{
this.recipeList.Add(Convert.ToInt32(strArray2[index1]), Convert.ToInt32(strArray2[index1 + 1]));
index1 += 2;//ah this is why, the format requires pairs be read, not singles
}
string[] strArray3 = strArray1[2].Split(' ');//split the third segment
int index2 = 0;
while (index2 < strArray3.Length)
{
this.itemToProduce.Add(Convert.ToInt32(strArray3[index2]));
this.numberProducedPerCraft = strArray3.Length > 1 ? Convert.ToInt32(strArray3[index2 + 1]) : 1;
index2 += 2;//do the same as before with pairs
}
this.bigCraftable = !isCookingRecipe && Convert.ToBoolean(strArray1[3]);
try
{
string str2;
if (!this.bigCraftable)
str2 = Game1.objectInformation[this.itemToProduce[0]].Split('/')[5];
else
str2 = Game1.bigCraftablesInformation[this.itemToProduce[0]].Split('/')[4];
this.description = str2;
}
catch (Exception ex)
{
this.description = "";
}
this.timesCrafted = Game1.player.craftingRecipes.ContainsKey(name) ? Game1.player.craftingRecipes[name] : 0;
if (name.Equals("Crab Pot") && Game1.player.professions.Contains(7))
{
this.recipeList = new Dictionary<int, int>();
this.recipeList.Add(388, 25);
this.recipeList.Add(334, 2);
}
if (LocalizedContentManager.CurrentLanguageCode != LocalizedContentManager.LanguageCode.en)
this.DisplayName = strArray1[strArray1.Length - 1];//for non english
else
this.DisplayName = name; //the key value in the xnb
}
Worth noting: because of this.numberProducedPerCraft = strArray3.Length > 1 ? Convert.ToInt32(strArray3[index2 + 1]) : 1;
The last element in an ingredient list does not have to have an amount, and it will default to 1.
Format for crafting/cooking we can see from just this class is:
name: "recipeList/???/itemsToProduce/bigCraftable/???"
Types:
string: "<space separated integer pairs>/string/<space separated integer pairs>/string
But since we can guess that the last unknown ???, has something to do with requirements based on information from the wiki - we'll start looking in Skills and Quests for the ContentReference Data\\CraftingRecipe
or Data\\CookingRecipe
.
Working on this at the moment, though Quests doesn't seem to have direct link.
content: #!Dictionary<Int32,String>
0: "Weeds/0/-1/Basic/Weeds/A bunch of obnoxious weeds." #!String
2: "Stone/0/-300/Basic/Stone/A useful material when broken with the Pickaxe." #!String
4: "Stone/0/-300/Basic/Stone/A useful material when chopped with the axe." #!String
Key: int ObjectID
Value:
"Stone/0/-300/Basic/Stone/A useful material when broken with the Pickaxe."
In StardewValley.CraftingRecipe.CraftingRecipe(string name, bool isCookingRecipe)
We can see
string str2;
if (!this.bigCraftable)
str2 = Game1.objectInformation[this.itemToProduce[0]].Split('/')[5];
else
str2 = Game1.bigCraftablesInformation[this.itemToProduce[0]].Split('/')[4];
this.description = str2;
The 6th element is used as a description.
Which co-incides in our case with A useful material when broken with the Pickaxe.
which fits the idea of a description.
In StardewValley.Crop.harvest(int xTile, int yTile, HoeDirt soil, JunimoHarvester junimoHarvester = null)
We can see
float num7 = (float) (16.0 * Math.Log(0.018 * (double) Convert.ToInt32(Game1.objectInformation[(int) ((NetFieldBase<int, NetInt>) this.indexOfHarvest)].Split('/')[1]) + 1.0, Math.E));
if (junimoHarvester == null)
Game1.player.gainExperience(0, (int) Math.Round((double) num7));
Perhaps suggesting that the 2nd element is a level or experience modifier. Though it's unclear.
To further confuse matters, In StardewValley.Debris(int debrisType, int numberOfChunks, Vector2 debrisOrigin, Vector2 playerPosition, float velocityMultiplyer)
It seems that the 4th element is checked for two different kinds of information, specifically a String literal and an integer, which doesn't help figure out what the field is intended to be. Though I'd offer a category/type classification as a suggestion.
int num1;
if (Game1.objectInformation.ContainsKey(debrisType))
num1 = Game1.objectInformation[debrisType].Split('/')[3].Contains("-4") ? 1 : 0;
else
num1 = 0;
floppingFish.Value = num1 != 0;
int num2;
if (Game1.objectInformation.ContainsKey(debrisType))
num2 = Game1.objectInformation[debrisType].Split('/')[3].Contains("Fish") ? 1 : 0;
else
num2 = 0;
However,
string str;
if (objectIndex <= 0)
str = "Crafting";
else
str = Game1.objectInformation[objectIndex].Split('/')[3].Split(' ')[0];
This snippet from StardewValley.Debris.Debris(int objectIndex, Vector2 debrisOrigin, Vector2 playerPosition
, mentions "Crafting" as a default type, and suggests that all indexes <= 0 are "Crafting" objects.
So I'd be happy to say the 4th elements first part is indicative of what 'category' the object falls under.
There also seems to be references which suggest the 5th element may hold description/name information in certain cases, evidenced by:
if (descriptionElement1.param[index1] is StardewValley.Object)
{
string str;
Game1.objectInformation.TryGetValue((int) ((NetFieldBase<int, NetInt>) (descriptionElement1.param[index1] as StardewValley.Object).parentSheetIndex), out str);
descriptionElement1.param[index1] = (object) str.Split('/')[4];
}
In StardewValley.Quests.DescriptionElement.loadDescriptionElement()
.
In StardewValley.Farmer.doneEating()
else if (this.IsLocalPlayer)
{
string[] strArray1 = Game1.objectInformation[itemToEat.ParentSheetIndex].Split('/');
if (Convert.ToInt32(strArray1[2]) > 0)
{
string[] strArray2;
if (strArray1.Length <= 7)
strArray2 = new string[12]
{
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0"
};
else
strArray2 = strArray1[7].Split(' ');
string[] strArray3 = strArray2;
if (strArray1.Length > 6 && strArray1[6].Equals("drink"))
{
if (!Game1.buffsDisplay.tryToAddDrinkBuff(new Buff(Convert.ToInt32(strArray3[0]), Convert.ToInt32(strArray3[1]), Convert.ToInt32(strArray3[2]), Convert.ToInt32(strArray3[3]), Convert.ToInt32(strArray3[4]), Convert.ToInt32(strArray3[5]), Convert.ToInt32(strArray3[6]), Convert.ToInt32(strArray3[7]), Convert.ToInt32(strArray3[8]), Convert.ToInt32(strArray3[9]), Convert.ToInt32(strArray3[10]), strArray3.Length > 10 ? Convert.ToInt32(strArray3[10]) : 0, strArray1.Length > 8 ? Convert.ToInt32(strArray1[8]) : -1, strArray1[0], strArray1[4])))
;
}
else if (Convert.ToInt32(strArray1[2]) > 0)
Game1.buffsDisplay.tryToAddFoodBuff(new Buff(Convert.ToInt32(strArray3[0]), Convert.ToInt32(strArray3[1]), Convert.ToInt32(strArray3[2]), Convert.ToInt32(strArray3[3]), Convert.ToInt32(strArray3[4]), Convert.ToInt32(strArray3[5]), Convert.ToInt32(strArray3[6]), Convert.ToInt32(strArray3[7]), Convert.ToInt32(strArray3[8]), Convert.ToInt32(strArray3[9]), Convert.ToInt32(strArray3[10]), strArray3.Length > 11 ? Convert.ToInt32(strArray3[11]) : 0, strArray1.Length > 8 ? Convert.ToInt32(strArray1[8]) : -1, strArray1[0], strArray1[4]), Math.Min(120000, (int) ((double) Convert.ToInt32(strArray1[2]) / 20.0 * 30000.0)));
}
Whole stack of things are mentioned, especially past the 6th element, namely with regards to buffs and if the item eaten is infact food or drink (stored in the 7th element). Will need to take a longer look at the binary-if statement mess there to figure out what it's expecting. More drink stuff is seen later in the same file:
this.itemToEat = (Item) o;
this.mostRecentlyGrabbedItem = (Item) o;
string[] strArray = Game1.objectInformation[o.ParentSheetIndex].Split('/');
this.forceCanMove();
this.completelyStopAnimatingOrDoingAction();
if (strArray.Length > 6 && strArray[6].Equals("drink"))
{
if (this.IsLocalPlayer && Game1.buffsDisplay.hasBuff(7) && !overrideFullness)
{
Game1.addHUDMessage(new HUDMessage(Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.2898"), Color.OrangeRed, 3500f));
return;
}
this.drinkAnimationEvent.Fire(o.getOne() as Object);
}
else if (Convert.ToInt32(strArray[2]) != -300)
{
if (Game1.buffsDisplay.hasBuff(6) && !overrideFullness)
{
Game1.addHUDMessage(new HUDMessage(Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.2899"), Color.OrangeRed, 3500f));
return;
}
this.eatAnimationEvent.Fire(o.getOne() as Object);
}
StardewValley.Tools.FishingRod.draw(SpriteBatch b)
gets the caught fish's name using an object lookup
string text = Game1.objectInformation[this.whichFish].Split('/')[4];
Therefore suggesting Fish that can be caught will have a name in the 5th element. (This complements evidence found earlier)
StardewValley.GameLocation.digUpArtifactSpot(int xLocation, int yLocation, Farmer who)
makes use of the 4th element to determine if an object is archeological. If it is, Then the 7th element is a set of integer pairs, likely an objectID and amount.
foreach (KeyValuePair<int, string> keyValuePair in (IEnumerable<KeyValuePair<int, string>>) Game1.objectInformation)
{
string[] strArray1 = keyValuePair.Value.Split('/');
if (strArray1[3].Contains("Arch"))
{
string[] strArray2 = strArray1[6].Split(' ');
int index = 0;
while (index < strArray2.Length)
{
if (strArray2[index].Equals((string) ((NetFieldBase<string, NetString>) this.name)) && random.NextDouble() < Convert.ToDouble(strArray2[index + 1], (IFormatProvider) CultureInfo.InvariantCulture))
{
objectIndex = keyValuePair.Key;
break;
}
index += 2;
}
}
if (objectIndex != -1)
break;
}
Additionally later in the same class, it mentions:
if(Game1.objectInformation[index2].Split('/')[3].Contains("Arch") || index2 == 102)
item index 102 is seemingly an exception to the "Arch" rule.
The recurring value of -300 on items may be because they are marked as inedible, as evidenced by StardewValley.Game1.parseDebufInput(string debufInput)
case "makeInedible":
if (Game1.player.ActiveObject != null)
{
Game1.player.ActiveObject.edibility.Value = -300;
break;
}
This is supported by StardewValley.Object.Object(Vector2 tileLocation, int parentSheetIndex, string Givenname...)
Game1.objectInformation.TryGetValue(parentSheetIndex, out str);
try
{
if (str != null)
{
string[] strArray1 = str.Split('/');
this.name = strArray1[0];
this.price.Value = Convert.ToInt32(strArray1[1]);
this.edibility.Value = Convert.ToInt32(strArray1[2]);
string[] strArray2 = strArray1[3].Split(' ');
this.type.Value = strArray2[0];
if (strArray2.Length > 1)
this.Category = Convert.ToInt32(strArray2[1]);
}
}
which lists specifically that:
- Element 1 is the name
- Element 2 is the price
- Element 3 is the edibility
- Element 4's first part (split on space) is the ObjectType
- Element 4's second part (split on space) is the Category (optional)
- Element 5 is a DisplayedName
- Element 6 is a Description
- The subsequent Elements are variable based on the type.
Applying what we've figured out so far to:
"Stone/0/-300/Basic/Stone/A useful material when broken with the Pickaxe."
Each field represents:
Name/Price/Edibility/ObjectType/DisplayedName/Description
Or optionally:
Name/Price/Edibility/ObjectType Category/DisplayedName/Description
Dictionary<int, string> dictionary = Game1.temporaryContent.Load<Dictionary<int, string>>("Data\\Quests");
if (dictionary != null && dictionary.ContainsKey((int) ((NetFieldBase<int, NetInt>) this.id)))
{
string[] strArray = dictionary[(int) ((NetFieldBase<int, NetInt>) this.id)].Split('/');
if (strArray[3].Length > 1)
this._currentObjective = strArray[3];
}
this.reloadObjective();
if (this._currentObjective == null)
this._currentObjective = "";
return this._currentObjective;
public static Quest getQuestFromId(int id)
{
Dictionary<int, string> dictionary = Game1.temporaryContent.Load<Dictionary<int, string>>("Data\\Quests");
if (dictionary == null || !dictionary.ContainsKey(id))
return (Quest) null;
string[] strArray1 = dictionary[id].Split('/');
string str1 = strArray1[0];
Quest quest = (Quest) null;
string[] strArray2 = strArray1[4].Split(' ');
switch (str1)
{
case "Basic":
quest = new Quest();
quest.questType.Value = 1;
break;
case "Building":
quest = new Quest();
quest.questType.Value = 8;
quest.completionString.Value = strArray2[0];
break;
case "Crafting":
quest = (Quest) new CraftingQuest(Convert.ToInt32(strArray2[0]), strArray2[1].ToLower().Equals("true"));
quest.questType.Value = 2;
break;
case "ItemDelivery":
quest = (Quest) new ItemDeliveryQuest();
(quest as ItemDeliveryQuest).target.Value = strArray2[0];
(quest as ItemDeliveryQuest).item.Value = Convert.ToInt32(strArray2[1]);
(quest as ItemDeliveryQuest).targetMessage = strArray1[9];
if (strArray2.Length > 2)
(quest as ItemDeliveryQuest).number.Value = Convert.ToInt32(strArray2[2]);
quest.questType.Value = 3;
break;
case "ItemHarvest":
quest = (Quest) new ItemHarvestQuest(Convert.ToInt32(strArray2[0]), strArray2.Length > 1 ? Convert.ToInt32(strArray2[1]) : 1);
break;
case "Location":
quest = (Quest) new GoSomewhereQuest(strArray2[0]);
quest.questType.Value = 6;
break;
case "LostItem":
quest = (Quest) new LostItemQuest(strArray2[0], strArray2[2], Convert.ToInt32(strArray2[1]), Convert.ToInt32(strArray2[3]), Convert.ToInt32(strArray2[4]));
break;
case "Monster":
quest = (Quest) new SlayMonsterQuest();
(quest as SlayMonsterQuest).loadQuestInfo();
(quest as SlayMonsterQuest).monster.Value.Name = strArray2[0].Replace('_', ' ');
(quest as SlayMonsterQuest).monsterName.Value = (quest as SlayMonsterQuest).monster.Value.Name;
(quest as SlayMonsterQuest).numberToKill.Value = Convert.ToInt32(strArray2[1]);
if (strArray2.Length > 2)
(quest as SlayMonsterQuest).target.Value = strArray2[2];
else
(quest as SlayMonsterQuest).target.Value = "null";
quest.questType.Value = 4;
break;
case "Social":
quest = (Quest) new SocializeQuest();
(quest as SocializeQuest).loadQuestInfo();
break;
}
quest.id.Value = id;
quest.questTitle = strArray1[1];
quest.questDescription = strArray1[2];
if (strArray1[3].Length > 1)
quest.currentObjective = strArray1[3];
string str2 = strArray1[5];
char[] chArray = new char[1]{ ' ' };
foreach (string str3 in str2.Split(chArray))
{
if (str3.StartsWith("h"))
{
if (Game1.IsMasterGame)
str3 = str3.Substring(1);
else
continue;
}
quest.nextQuests.Add(Convert.ToInt32(str3));
}
quest.showNew.Value = true;
quest.moneyReward.Value = Convert.ToInt32(strArray1[6]);
quest.rewardDescription.Value = strArray1[6].Equals("-1") ? (string) null : strArray1[7];
if (strArray1.Length > 8)
quest.canBeCancelled.Value = strArray1[8].Equals("true");
return quest;
}
protected override string translateName(string name)
{
switch (name)
{
case "Bear":
return Game1.content.LoadString("Strings\\NPCNames:Bear");
case "Bouncer":
return Game1.content.LoadString("Strings\\NPCNames:Bouncer");
case "Gil":
return Game1.content.LoadString("Strings\\NPCNames:Gil");
case "Governor":
return Game1.content.LoadString("Strings\\NPCNames:Governor");
case "Grandpa":
return Game1.content.LoadString("Strings\\NPCNames:Grandpa");
case "Gunther":
return Game1.content.LoadString("Strings\\NPCNames:Gunther");
case "Henchman":
return Game1.content.LoadString("Strings\\NPCNames:Henchman");
case "Kel":
return Game1.content.LoadString("Strings\\NPCNames:Kel");
case "Marlon":
return Game1.content.LoadString("Strings\\NPCNames:Marlon");
case "Mister Qi":
return Game1.content.LoadString("Strings\\NPCNames:MisterQi");
case "Morris":
return Game1.content.LoadString("Strings\\NPCNames:Morris");
case "Old Mariner":
return Game1.content.LoadString("Strings\\NPCNames:OldMariner");
case "Welwick":
return Game1.content.LoadString("Strings\\NPCNames:Welwick");
default:
Dictionary<string, string> dictionary = Game1.content.Load<Dictionary<string, string>>("Data\\NPCDispositions");
if (!dictionary.ContainsKey(name))
return name;
string[] strArray = dictionary[name].Split('/');
return strArray[strArray.Length - 1];
}
}
public Dictionary<string, string> Dialogue
{
get
{
if (this is Monster)
return (Dictionary<string, string>) null;
if (this.dialogue == null)
{
try
{
IEnumerable<KeyValuePair<string, string>> source = Game1.content.Load<Dictionary<string, string>>("Characters\\Dialogue\\" + this.Name).Select<KeyValuePair<string, string>, KeyValuePair<string, string>>((Func<KeyValuePair<string, string>, KeyValuePair<string, string>>) (pair =>
{
string key = pair.Key;
string str1 = pair.Value;
if (str1.Contains("¦"))
str1 = !Game1.player.IsMale ? str1.Substring(str1.IndexOf("¦") + 1) : str1.Substring(0, str1.IndexOf("¦"));
string str2 = str1;
return new KeyValuePair<string, string>(key, str2);
}));
Func<KeyValuePair<string, string>, string> func = (Func<KeyValuePair<string, string>, string>) (p => p.Key);
Func<KeyValuePair<string, string>, string> keySelector;
this.dialogue = source.ToDictionary<KeyValuePair<string, string>, string, string>(keySelector, (Func<KeyValuePair<string, string>, string>) (p => p.Value));
}
catch (ContentLoadException ex)
{
this.dialogue = new Dictionary<string, string>();
}
}
return this.dialogue;
}
}
string[] strArray = this.Dialogue[(string) ((NetFieldBase<string, NetString>) l.name) + "_Entry"].Split('/');
Under 10 minute update:
if (Game1.random.NextDouble() < 0.1 && this.Dialogue != null && this.Dialogue.ContainsKey((string) ((NetFieldBase<string, NetString>) l.name) + "_Ambient"))
{
string[] strArray = this.Dialogue[(string) ((NetFieldBase<string, NetString>) l.name) + "_Ambient"].Split('/');
int preTimer = Game1.random.Next(4) * 1000;
this.showTextAboveHead(strArray[Game1.random.Next(strArray.Length)], -1, 2, 3000, preTimer);
}
Dictionary<string, string> dictionary2 = Game1.content.Load<Dictionary<string, string>>("Data\\NPCGiftTastes");
StardewValley.NPC.loadCurrentDialogue() : 1896
See StardewValley.NPC.parseMasterSchedule(string rawData) : 2991
Game1.content.Load<Dictionary<string, string>>("Characters\\schedules\\" + this.Name)[currentSeason].Split('/')
public NPC(AnimatedSprite sprite, Vector2 position, string defaultMap, int facingDir, string name, Dictionary<int, int[]> schedule, Texture2D portrait, bool eventActor)
: base(sprite, position, 2, name)
{
this.portrait = portrait;
this.faceDirection(facingDir);
this.defaultPosition.Value = position;
this.defaultMap.Value = defaultMap;
this.currentLocation = Game1.getLocationFromName(defaultMap);
this.defaultFacingDirection = facingDir;
if (!eventActor)
this.lastCrossroad = new Microsoft.Xna.Framework.Rectangle((int) position.X, (int) position.Y + 64, 64, 64);
try
{
Dictionary<string, string> source = Game1.content.Load<Dictionary<string, string>>("Data\\NPCDispositions");
if (!source.ContainsKey(name))
return;
string[] strArray = source[name].Split('/');
string str1 = strArray[0];
if (!(str1 == nameof (teen)))
{
if (str1 == nameof (child))
this.Age = 2;
}
else
this.Age = 1;
string str2 = strArray[1];
if (!(str2 == nameof (rude)))
{
if (str2 == nameof (polite))
this.Manners = 1;
}
else
this.Manners = 2;
string str3 = strArray[2];
if (!(str3 == nameof (shy)))
{
if (str3 == nameof (outgoing))
this.SocialAnxiety = 0;
}
else
this.SocialAnxiety = 1;
string str4 = strArray[3];
if (!(str4 == nameof (positive)))
{
if (str4 == nameof (negative))
this.Optimism = 1;
}
else
this.Optimism = 0;
string str5 = strArray[4];
if (!(str5 == nameof (female)))
{
if (str5 == nameof (undefined))
this.Gender = 2;
}
else
this.Gender = 1;
string str6 = strArray[5];
if (!(str6 == nameof (datable)))
{
if (str6 == "not-datable")
this.datable.Value = false;
}
else
this.datable.Value = true;
this.loveInterest = strArray[6];
string str7 = strArray[7];
if (!(str7 == "Desert"))
{
if (!(str7 == "Other"))
{
if (str7 == "Town")
this.homeRegion = 2;
}
else
this.homeRegion = 0;
}
else
this.homeRegion = 1;
if (strArray.Length > 8)
{
this.Birthday_Season = strArray[8].Split(' ')[0];
this.Birthday_Day = Convert.ToInt32(strArray[8].Split(' ')[1]);
}
for (int index = 0; index < source.Count; ++index)
{
if (source.ElementAt<KeyValuePair<string, string>>(index).Key.Equals(name))
{
this.id = index;
break;
}
}
this.displayName = strArray[11];
}
catch (Exception ex)
{
}
}