Can a var = 2 things - c#

I've been working with Unity for a while now and been scripting with C# a bit.
I'm working on a script that checks if selected target is a NPC or a Craft(station).
As the base of the script is calling upon many other scripts and variables, I don't want to duplicate all of the scripts in order to make the other kind of NPC which is , in my case, the Craft(station).
Let me explain in detail with examples now:
void UpdateNpcTrading(Player player) {
// only if visible
if (!npcTradingPanel.activeSelf) return;
// npc trading
if (player.target != null && player.target is Craft &&
Vector3.Distance(player.transform.position, player.target.transform.position) <= player.talkRange) {
var npc = (Craft)player.target; // Here is the var I need to edit
// items for sale
for (int i = 0; i < npcTradingContent.childCount; ++i) {
var entry = npcTradingContent.GetChild(i).GetChild(0);
// get the item
if (i < npc.saleItems.Length) {
var item = npc.saleItems[i];
There is more to it ( feel free to ask for it if needed )
So the line : var npc = (Craft)player.target; identifies npc as Craft because player.Target = Craft.
But I'd like to edit it so It checks if the player.target = Craft then, Set var npc = Craft
but if player.target = Npc , Set var npc = Npc instead.
Not sure if I make myself clear or not. Please feel free to ask for more details and I'll do my best.
Any help is most appreciated, Mike
EDIT :
Ok so I've scripted it like this. Still when I run in game it doesnt looks like it knows what to do.. ( But compilers doesnt show syntax error )
// npc trading
if (player.target != null && player.target is Craft &&
Vector3.Distance(player.transform.position, player.target.transform.position) <= player.talkRange) {
var npc = (Craft)player.target;
}else if (player.target != null && player.target is Npc &&
Vector3.Distance(player.transform.position, player.target.transform.position) <= player.talkRange) {
var npc = (Npc)player.target;
// items for sale
for (int i = 0; i < npcTradingContent.childCount; ++i) {
var entry = npcTradingContent.GetChild(i).GetChild(0);
// get the item
if (i < npc.saleItems.Length) {
var item = npc.saleItems[i];
Where am I wrong

It looks like you are looking for the is operator.
if (player.target is Craft)
{
var npc = (Craft)player.target;
// do other 'Craft' logic here
}
else if (player.target is Npc)
{
var npc = (Npc)player.target;
// do other 'Npc' logic here
}
I'm assuming that player.target can either be an Npc or a Craft? If this is the case then they both inherit from whatever base type player.target is. You can check if it is either a Craft or Npc with the is operator before you cast.

Related

C# Mapping a list to numbered variables

Yes, I'm well aware Not to do this, but I have no choice. I'd agree that it's an XYZ issue, but since I can't update the service I have to use, it's out of my hands. I need some help to save some time, maybe learn something handy in the process.
I'm looking to map a list of models (items in this example) to what is essentially numbered variables of a service I'm posting to, in the example, that's the fields a part of new 'newUser'.
Additionally, there may not be always be X amount items in the list (On the right in the example), and yet I have a finite amount (say 10) of numbered variables from 'newUser' to map to (On the left in the example). So I'll have to perform a bunch of checks to avoid indexing a null value as well.
Current example:
if (items.Count >= 1 && !string.IsNullOrWhiteSpace(items[0].id))
{
newUser.itemId1 = items[0].id;
newUser.itemName1 = items[0].name;
newUser.itemDate1 = items[0].date;
newUser.itemBlah1 = items[0].blah;
}
else
{
// This isn't necessary, but this effectively what will happen
newUser.itemId1 = string.Empty;
newUser.itemName1 = string.Empty;
newUser.itemDate1 = string.Empty;
newUser.itemBlah1 = string.Empty;
}
if (items.Count >= 2 && !string.IsNullOrWhiteSpace(items[1].id))
{
newUser.itemId2 = items[1].id;
newUser.itemName2 = items[1].name;
newUser.itemDate2 = items[1].date;
newUser.itemBlah2 = items[1].blah;
}
// Removed the else to clean it up, but you get the idea.
// And so on, repeated many more times..
I looked into an example using Dictionary, but I'm unsure of how to map that to the model without just manually mapping all the variables.
PS: To all who come across this question, if you're implementing numbered variables in your API, please don't- it's wildly unnecessary and time consuming.
As an alternative to fiddling with the JSON, you could get down and dirty and use Reflection.
Given the following test data:
const int maxItemsToSend = 3;
class ItemToSend {
public string
itemId1, itemName1,
itemId2, itemName2,
itemId3, itemName3;
}
ItemToSend newUser = new();
record Item(string id, string name);
Item[] items = { new("1", "A"), new("2", "B") };
Using the rules you set forth in the question, we can loop through the projected fields as so:
// If `itemid1`,`itemId2`, etc are fields:
var fields = typeof(ItemToSend).GetFields();
// If they're properties, replace GetFields() with
// .GetProperties(BindingFlags.Instance | BindingFlags.Public);
for(var i = 1; i <= maxItemsToSend; i++){
// bounds check
var item = (items.Count() >= i && !string.IsNullOrWhiteSpace(items[i-1].id))
? items[i-1] : null;
// Use Reflection to find and set the fields
fields.FirstOrDefault(f => f.Name.Equals($"itemId{i}"))
?.SetValue(newUser, item?.id ?? string.Empty);
fields.FirstOrDefault(f => f.Name.Equals($"itemName{i}"))
?.SetValue(newUser, item?.name ?? string.Empty);
}
It's not pretty, but it works. Here's a fiddle.

Best way to compare variables between instances of the same class?

This is a game based on certain elemental combos, I'm trying to find a way to have two elements compare to each other in order to reduce hard coding. I do have a working solution provided at the end, but I'm trying to learn any ways that would be more simple, straight forward, and readable.
//Wizard is a class
Wizard player1;
Wizard player2;
player1.health = 3;
player2.health = 3;
//Elements is an enum that allows fire/water/air to be selected
player1.elementSelected = Elements.fire;
player2.elementSelected = Elements.water;
Wizard[] bothPlayers = { player1, player2 };
I want to search for the active element in bothPlayers so I can effect the Player's health. I know this doesn't work, but I was wondering if I could do something like:
if (bothPlayers.Contains(Wizard.elementSelected.Elements.fire) && bothPlayers.Contains(Wizard.elementSelected.Elements.water))
Alternatively I was thinking of just setting it to a new array but that wont let me call back to effect player health unless I set them up to a new variable like:
Elements[] bothSelectedElements = { player1.elementSelected, player2.elementSelected };
if (bothSelectedElements.Contains(Elements.fire) && bothSelectedElements.Contains(Elements.water))
{
Wizard playerWithFire; // = player who selected fire. Can't set without hardcoding
Wizard playerWithWater; // = player who selected water. Can't set without hardcoding
playerWithFire.health--;
playerWithWater.waterStrength++;
}
//CURRENT WORKING SOLUTION
//set each Wizard container to null at the start of each check
Wizard fire = null;
Wizard water = null;
Wizard air = null;
//add check to make sure same elements aren't selected. Then assign the players to the containers
foreach (Wizard player in bothPlayers)
{
if (player.elementSelected == Elements.fire)
{
fire = player;
}
if (player.elementSelected == Elements.water)
{
water = player;
}
if (player.elementSelected == Elements.air)
{
air = player;
}
}
//then do the actual check
if (fire != null && water != null)
{
fire.health--;
water.waterStrength++;
}
//repeat with other if statement comparisons
This is a fiddly problem, but thankfully you are working with a language that has all the tools you need to hide the complexity away.
var elementWizards = wizards.GroupBy(w => w.elementSelected).ToDictionary(g => g.Key);
var elements = elementWizards.Keys.ToHashSet(); // gives us access to SetEquals
if (elements.SetEquals(new[] { Elements.Fire, Elements.Water }))
{
foreach (var wizard in elementWizards[Elements.Fire]) wizard.health--;
foreach (var wizard in elementWizards[Elements.Water]) wizard.waterStrength++;
}
else if (elements.SetEquals(new[] { Elements.Earth, Elements.Fire }))
{
// more effects...
}
Note that SetEquals doesn't care about the order of items, so you don't need to worry about handling water/fire instead of fire/water.
Footnote: in the real world I would define some static HashSet<Element> objects and call if(foo.SetEquals(elementWizards.Keys)), but I kept things simple for this answer.
Solution with Linq:
Check if there are water and fire wizard (with Linq)
Foreach to search and add health etc...
if (bothPlayers.Count(wizard => wizard.elementSelected == Elements.fire) > 0 &&
bothPlayers.Count(wizard => wizard.elementSelected == Elements.water) > 0)
{
bothPlayers.ForEach(wizard =>
{
var _ = wizard.elementSelected == Elements.water ? wizard.waterStrength++ :
wizard.elementSelected == Elements.fire ? wizard.health-- : 1;
});
}
you can change the wizards you are searching for and the properties that depends on it
There are multiple ways of finding item in a collection. For example :
// using Array.Find method
Wizard fire = Array.Find(bothPlayers, player => player.elementSelected == Elements.fire);
// or using System.Linq FirstOrDefault extension
Wizard water = bothPlayers.FirstOrDefault(player => player.elementSelected == Elements.water);
if (fire != null && water != null)
{
fire.health--;
water.waterStrength++;
}
For more than two players, a Lookup can be used to separate the players into groups :
var lookup = bothPlayers.ToLookup(player => player.elementSelected);
if (lookup.Contains(Elements.fire) && lookup.Contains(Elements.water))
{
foreach (Wizard fire in lookup[Elements.fire] ) fire.health--;
foreach (Wizard water in lookup[Elements.water]) water.waterStrength++;
}

Sorting Gameobjects and set their SiblingIndex in Hierarchy

i have a friendsystem and want to sort it by the Users Worldrank (via PlayFab).
I want the best at the Top and the worst at the bottom.
I tried it with the following Script
private void sortfriendlist()
{
GameObject[] Friendcolumns = GameObject.FindGameObjectsWithTag("Friendcolumn");
for (int i = 0; i < Friendcolumns.Length; i++)
{
//If ranked
if (Friendcolumns[i].name.Substring(0, 1) != "F")
{
//Set Sibling (Rank)
int rank = int.Parse(Friendcolumns[i].name.Substring(0, 1));
Friendcolumns[i].transform.SetSiblingIndex(rank);
}
//If unranked
else
{
Friendswithoutrank[i] = Friendcolumns[i];
}
}
Debug.Log(Friendswithoutrank.Length + " Friends without Rank");
for(int i = 0; i < Friendswithoutrank.Length; i++)
{
Debug.Log(Friendswithoutrank[i].name + " has no Rank");
Friendcolumns[i].transform.SetAsLastSibling();
}
}
But that doesnt work as you can see here
First Im getting all Friends and store them in an Array. After that Im checking if the Player even has a Rank by checking if there is an "F" at the beginning of the GameObjects Name (Friends without a Rank have no Ranknumber in front of the Gameobject Name so the Name start with an "F"). Than Im getting the Rank of the Friend and set the SiblingIndex to the Rank. So normally it should be sorted now as i think. But that idea doesn´t seem to work.
If you have any idea how to fix it or make it better please let me know!
Sorting can actually be done much easier using Linq (also see this thread):
using System.Linq;
using UnityEngine;
private void sortfirendlist()
{
var Friendcolumns = GameObject.FindGameObjectsWithTag("Friendcolumn");
// Use linq to get the Objects with and without Rank (not starting / starting with "F")
// This works a bit similar to sql code
// -> if the "Where" returns true -> object stays in the list
// using string.StartsWidth which is a
// better choice for what you are doing with Substring(0,1)== ...
var withoutRank = Friendcolumns.Where(obj => obj.name.StartsWith("F"));
var withRank = Friendcolumns.Where(obj => !obj.name.StartsWith("F"));
// Sort the ranked by name (again using Linq)
var rankedSorted = withRank.OrderBy(go => go.name);
// Now we have our ordered arrays -> we just have to apply that to the scene
// set sibling index for the ranked
foreach (var t in rankedSorted)
{
// I simply send them one by one to the bottom
// => when we are finished they are all sorted at the bottom
t.transform.SetAsLastSibling();
}
// Do the same for the unranked to also send them to the bottom
Debug.Log(withoutRank.Length + " Friends without Rank");
foreach (var t in withoutRank)
{
Debug.Log(t.name + " has no rank");
t.transform.SetAsLastSibling();
}
// Now the object we sent to the bottom first
// (first element in withRank) should be on top and everything sorted below it
}

Get the next element in Linked List with a condition

I'm using a Linked List for turn management in my game. I have Players which I iterate through them, however when one player completes the game he needs to be skipped over, which is where I'm failing at.
How can I do this? here is what I have right now:
public Player GetNextPlayer() {
var current = linkedPlayerList.Find(currentPlayer);
Player nextPlayer = current.Next == null ? linkedPlayerList.First.Value : current.Next.Value;
SetCurrentPlayer(nextPlayer);
return nextPlayer;
}
I tried the following but it doesn't work.
Player nextPlayer = current.Next == null
? linkedPlayerList.First(f => !f.RoundCompleted)
: current.Next.List.Where(w=>!w.RoundCompleted).ElementAt(linkedPlayerList.ToList().IndexOf(currentPlayer));
I would use a loop to check for your condition. Comments in code to explain.
LinkedList<Player> linkedPlayerList = ......;
Player currentPlayer = .......;
public Player GetNextPlayer()
{
// Find the current node
var curNode = linkedPlayerList.Find(currentPlayer);
// Point to the next
LinkedListNode<Player> nextNode = curNode.Next;
// Check if at the end of the list
nextNode = nextNode == null ? linkedPlayerList.First : nextNode;
// Loop stops when we find the condition true or we reach the starting point
while (curNode != nextNode)
{
// Exit if found....
if (!nextNode.Value.RoundCompleted)
break;
// Manage the next node to check for....
nextNode = nextNode?.Next == null ? linkedPlayerList.First : nextNode.Next;
}
SetCurrentPlayer(nextNode.Value);
return nextNode.Value;
}
You can do something like this (add an an extra null check on next):
Player nextPlayer = current.Next.List.FirstOrDefault (x => !.RoundCompleted);

.Add(null) would break my list<string>. I would like to add in a condition to replcae null with a string

I ran into quite an interesting issue. In conclusion, I learned that empty and null are two different things.
I am currently creating a constructore that takes stores two List<string>s.
public ValueList(List<string> firstList, List<string> secondList) {
for (int i = 0; i < firstList.Count; i++) {
// Checks for nulls.
if(firstList[i] == null){
_firstList.Add("null");
}else if (secondList[i] == null){
_secondList.Add("null");
}else{
_firstList.Add(firstList[i].ToString());
_secondList.Add(secondList[i].ToString());
}
}
}
There are times where this constructor would read Excel files, and in any blank square, the other script I use would parse in a null value. The null value would break my script entirely, and not .Add() anything.
So I tried to put in my null-check conditions. If that particular index is a null, then we add a "null" string.
However, the null continues to break my script.
I was wondering if there is a more elegant and fool-proof way to combat this issue? I think I have also tried .Equals(null) but that did not solve the issue at all.
The proper logic would be
for (int i = 0; i < firstList.Count; i++) {
// Checks for nulls.
if(firstList[i] == null){
_firstList.Add("null");
}
else {
_firstList.Add(firstList[i].ToString());
}
if (secondList[i] == null){
_secondList.Add("null");
}
else{
_secondList.Add(secondList[i].ToString());
}
}
But you could just to a Linq projection:
_firstList = firstList.Select(s => s ?? "null").ToList();
_secondList = secondList.Select(s => s ?? "null").ToList();
EDIT
Since you do want to store nulls you just need to not call ToString:
for (int i = 0; i < firstList.Count; i++) {
_firstList.Add(firstList[i]);
_secondList.Add(secondList[i]);
}

Categories

Resources