Line 10: |
Line 10: |
| | | |
| ===Net types=== | | ===Net types=== |
− | Stardew Valley 1.3 uses a new set of C# types to synchronise data between players. For example, a crop's <tt>bool dead</tt> field is now <tt>NetBool dead</tt>. The game will intermittently get the value of all reachable net fields, and send them to the other players. A ''net type'' means a network-synchronised type (like <tt>NetBool</tt>), and a ''net field'' is a field with such a type (like <tt>Crop.dead</tt>). | + | Stardew Valley 1.3 uses net types (like <tt>NetBool</tt> and <tt>NetInt</tt>) to handle multiplayer sync. These types can implicitly convert to their equivalent normal values (like <code>bool x = new NetBool()</code>), but their conversion rules are unintuitive and error-prone. For example, <code>item?.category == null && item?.category != null</code> can both be true at once, and <code>building.indoors != null</code> will be true for a null value in some cases. With the mod build config package installed, rebuild the project and look for warnings in the Error List pane like this: |
| + | <blockquote>'''{{expression}}' is a {{net type}} field; consider using the {{property name}} property instead. See https://smapi.io/buildmsg/smapi002 for details.''</blockquote> |
| + | <blockquote>''This implicitly converts '{{expression}}' from {{net type}} to {{other type}}, but {{net type}} has unintuitive implicit conversion rules. Consider comparing against the actual value instead to avoid bugs. See https://smapi.io/buildmsg/smapi001 for details.''</blockquote> |
| | | |
− | Usage notes:
| + | Suggested fix: |
| <ul> | | <ul> |
− | <li>Many net fields have a standard property to access them. Use those when possible to simplify your code and avoid net field gotchas: | + | <li>Some net fields have an equivalent non-net property, like <tt>monster.Health</tt> (<tt>int</tt>) instead of <tt>monster.health</tt> (<tt>NetBool</tt>). The mod build package will add a [https://smapi.io/buildmsg/smapi002 SMAPI002 warning] which says which property to use instead.</li> |
| + | <li>For a reference type (i.e. one that can contain <tt>null</tt>), you can use the <tt>.Value</tt> property (or <tt>.FieldDict</tt> for a <tt>NetDictionary</tt>): |
| <source lang="c#"> | | <source lang="c#"> |
− | Game1.player.name // NetString
| + | if (building.indoors.Value == null) |
− | Game1.player.Name // string
| |
| </source> | | </source> |
− | </li>
| + | |
− | <li>You can implicitly cast net types to their standard equivalent:
| + | Or convert the value before comparison: |
| <source lang="c#"> | | <source lang="c#"> |
− | NetBool a = crop.dead;
| + | GameLocation indoors = building.indoors; |
− | bool b = crop.dead;
| + | if(indoors == null) |
− | | + | // ... |
− | if (a == b) // true | |
| </source></li> | | </source></li> |
− | <li>You may need to access the value explicitly in some cases. Here's how: | + | <li>For a value type (i.e. one that can't contain <tt>null</tt>), check if the object is null (if applicable) and compare with <tt>.Value</tt>: |
− | {| class="wikitable"
| |
− | |-
| |
− | ! net type
| |
− | ! syntax
| |
− | |-
| |
− | | <tt>NetDictionary</tt>
| |
− | | <tt>netField.FieldDict</tt>
| |
− | |-
| |
− | | ''most net types''
| |
− | | <tt>netField.Value</tt>
| |
− | |}</li>
| |
− | <li>'''Do not''' reassign net fields. (Many are readonly to prevent that, but not all.) Instead, set the new value:
| |
| <source lang="c#"> | | <source lang="c#"> |
− | // either of these will work
| + | if (item != null && item.category.Value == 0) |
− | crop.dead.Value = true;
| |
− | crop.dead.Set(true);
| |
| </source></li> | | </source></li> |
− | <li>Quirk: when using [https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-conditional-operators null-conditional operators] with [https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/value-types value-type] net fields (like <tt>NetBool</tt> and <tt>NetInt</tt>), '''missing values will be equal to both null and <tt>default(T)</tt>''':
| |
− | <source lang="c#">
| |
− | Item item = null;
| |
− | if (item?.category == null && item?.category == 0) // both true
| |
− | </source>
| |
− |
| |
− | When comparing to the default value, check for null first to avoid that issue:
| |
− | <source lang="c#">
| |
− | Item item = null;
| |
− | if (item != null && item.category == 0)
| |
− | </source>
| |
− |
| |
− | </li>
| |
| </ul> | | </ul> |
| | | |