ATVO by Appgineering
Download
Read-only

Championship standings example

39 posts 27,961 views Started 31 Oct 2018, 18:51
Showing 1–39 of 39 posts
Nick Thissen Appgineering
Original poster
· edited

Starting from version 1.15.8, ATVO provides some support for displaying championship points and standings.

The following is supported:

  • The ChampionshipResult property on IEntity includes four properties:

    Position: current champ position (before end of this race).

  • LivePosition: live champ position (updated according to position in current race).

  • Points: current champ points (before end of this race).

  • LivePoints: live champ points (updated according to position in current race).

These properties will all be 0 by default, you must set them to the desired value using a script.
New Data Bindings for each of these properties:
championshipposition, championshippoints, livechampionshipposition, livechampionshippoints
New Data Orders for Championship Position and Live Championship Position, allowing you to sort/order drivers by their championship standings, once set via your script.

The reason for the distinction between "current" and "live" points/positions is so you can show both the current standings (e.g. before the race) and live standings, or potentially a delta ("how many positions is a driver gaining in the championship if he stays in this position").

Setting the championship standings
To use the championship properties you must set them via a script. Your script must be a converter script that takes an object as the value input. Depending on which object you must dig down to the Entity and there you can find the ChampionshipResult property.

Binding to "entity_object":

IEntity entity = (IEntity) value;
IChampionshipResult champ = entity.ChampionshipResult;

Binding to "entitysessionresult_object":

IEntitySessionResult result = (IEntitySessionResult) value;
IChampionshipResult champ = result.Entity.ChampionshipResult;

Once you have the IChampionshipResult object, you can set its properties to the desired value:

champ.Position = 3;
champ.LivePosition = 4;
champ.Points = 192;
champ.LivePoints = 185;

Where do I get the amount of points I want to award?
To show current and live standings you will need at least a source of:

  1. Current champ standings (as they were before this race).
  2. Amount of points to award for each position

You can store both in a Spreadsheet object and read it via your script. Please let us know if you need assistance with this part.

Jaakko P.
Reply #1

We have a points system in our RX series where you get 10 points for winning qualification, 8 points for winning your heat and 50 points for winning the final (feature) heat. Do you plan on supporting this sort of a points system?

Nick Thissen Appgineering
Reply #2

Setting the championship points and standings is fully manual and you have to do it via a script. We do not provide any automated way to award championship points (because it would quickly become way too complicated with each league having their own rules, like your example).

So yes we support this as long as you can figure out how to use a script to set the points.

Nicolas C.
Reply #3

Hi,

some question:

  • in VS Code, what using we need to use to have IChampionshipResult, for auto completion ? I try in new theme with new script generation on VS Code
  • when we have more driver in spreadsheet championship standing before race that we have on server, how can add they only on championship result ?

Thanks for your work

Nick Thissen Appgineering
Reply #4

I need to upload a new version of the nuget package for that, sorry. I'll try to do it soon.

For the second question I don't know what you mean can you elaborate?

Nicolas C.
Reply #5

Thx

For second question, the IChampionshipResult his part of IEntity. But we have one IEntity by car on server that we watch. So if i want to have full championship classification with driver not in this race ?

Nicolas C.
Reply #6

Nick ?
you need more explain ? Because i don't know how to do if i thinks how to work.

i have 3 driver in championship (A, B , C)
i have a race with driver A and B.
another race with driver A and C.
another race with all driver...

for the second race how can i do to display all driver ( A B and C ) because the B driver is not in second race ?

Thx

Nick Thissen Appgineering
Reply #7

I have to think about this more. It is not an easy problem.

To show drivers that are not in the server, the only solution is to use a Spreadsheet and bind to that. If you bind to any of the sim data sets, you'll only get drivers in the session, and I don't see a good way to hack around that.

If you bind to a Spreadsheet however you have some more "hacking room". You can in theory even create or modify the Spreadsheet in memory. I think you were doing this before as well but never figured it out properly. This is very tricky to get right because of how our data binding works. I will have to try some things out.

Nick Thissen Appgineering
Reply #8

Perhaps you can make it work easier using the Custom Data Set items. A Custom Data Set is an object in your theme in which you can simply store some raw values (a list of data). Then you can use this as a dataset in a Widget.

You could use a script to dynamically fill the data in a Custom Data Set.

The downside is that a Custom Data Set has only one item per row (one value), there's no way to store for example driver name AND points separately. You can get around that with multiple Custom Data Sets, but then you'd have to create multiple Widgets to show this information. For example one Widget (ticker) that shows the driver names, then another Widget (ticker) for the points, maybe another for car numbers, etc.

I had some downtime at work, here is a quick and dirty example I came up with. Not sure if it compiles but it should be close. You'll need four Custom Data Set objects, a spreadsheet for the standings and a spreadsheet for the point system, see the script.

using System;
using System.Data;
using ATVO.ThemesSDK;
using ATVO.ThemesSDK.Data.Entity;
using ATVO.ThemeEditor.ThemeModels;
using ATVO.ThemeEditor.Scripting.DotNET;
using System.Collections.Generic;

namespace Scripts
{
    public class ChampPointScript : IScript
    {
        private Dictionary<int, int> _pointSystem = new Dictionary<int, int>();
        private Dictionary<int, ChampDriver> _standings = new Dictionary<int, ChampDriver>();

        public object Execute(ThemeContentItem item, object value, string parameter, ISimulation sim)
        {
            // Make sure point system and standings are loaded
            LoadPointSystem(item.Theme);
            LoadStandings(item.Theme);

            // Update the points
            UpdateSessionStandings(sim);

            // Sort by points and assign position
            // This updates positions for drivers in the session AND drivers not in the session
            UpdateChampPositions();

            // Also update the champ results for drivers in the session
            // in case you are showing these somewhere else (in another widget)
            UpdateSessionChampResults();

            // Finally update our Custom Data Sets so the widgets are showing the new results
            UpdateDataSets(item.Theme);
        }

        private void UpdateSessionStandings(ISimulation sim)
        {
            // Loop through entity results in the session and update the standings
            foreach (var result in sim.Session.Current.Results)
            {
                ChampDriver driver;

                // Check if there is a driver in the session who was NOT in the standings csv
                if (!_standings.ContainsKey(result.Entity.Id))
                {
                    // This driver isn't in the standings yet, let's add him
                    driver = new ChampDriver();
                    driver.CustomerId = result.Entity.Id;
                    driver.Name = result.Entity.Name;
                    driver.CarNumber = result.Entity.Car.Number;
                    driver.ChampPoints = 0;
                    _standings.Add(driver.CustomerId, driver);
                }
                else 
                {
                    // Otherwise, grab him from memory
                    driver = _standings[result.Entity.Id];
                }
                
                // Link him to the session entity
                driver.Entity = result.Entity;

                // Add the champ points obtained during this race
                driver.LiveChampPoints = driver.ChampPoints + _pointSystem[result.Position]; // Or result.LivePosition to update during a lap
            }
        }

        private void UpdateChampPositions()
        {
            // Sort the standings by live champ points and assign positions in order
            var sorted = _standings.Values.OrderByDescending(d => d.LiveChampPoints);

            int position = 1;
            foreach (var driver in sorted)
            {
                driver.LiveChampPosition = position;
                position += 1;
            }
        }

        private void UpdateSessionChampResults()
        {
            foreach (var driver in _standings.Values)
            {
                // If this driver is in the session, he has his Entity linked up
                // If not, Entity is null
                if (driver.Entity != null)
                {
                    // update the ChampionshipResult object
                    var champ = driver.Entity.ChampionshipResult;

                    champ.Position = driver.ChampPosition; // previous pos
                    champ.LivePosition = driver.LiveChampPosition; // updated pos
                    champ.Points = driver.ChampPoints; // previous points
                    champ.LivePoints = driver.LiveChampPoints; // updated points
                }
            }
        }

        private void UpdateDataSets(ITheme theme)
        {
            // Assuming you have four Custom Data Set items:
            var cds_pos = theme.CustomDataSets.Find("cds_pos"); // for champ position
            var cds_name = theme.CustomDataSets.Find("cds_name"); // for driver name
            var cds_carnum = theme.CustomDataSets.Find("cds_carnum"); // for car num
            var cds_points = theme.CustomDataSets.Find("cds_points"); // for champ points

            // Clear their data
            cds_pos.Data.Clear();
            cds_name.Data.Clear();
            cds_carnum.Data.Clear();
            cds_points.Data.Clear();

            // Add the new data in order
            foreach (var driver in _standings.Values.OrderBy(d => d.LiveChampPosition))
            {
                cds_pos.Data.Add(new CustomDataSetItem(driver.LiveChampPosition));
                cds_name.Data.Add(new CustomDataSetItem(driver.Name));
                cds_carnum.Data.Add(new CustomDataSetItem(driver.CarNumber));
                cds_points.Data.Add(new CustomDataSetItem(driver.LiveChampPoints));
            }
        }

        private void LoadPointSystem(ITheme theme)
        {
            if (_pointSystem.Count == 0)
            {
                // Read point system from an embedded spreadsheet
                // Two columns: position, points
                var sheet = theme.Spreadsheets.Find("pointsystem.csv");

                foreach (var row in sheet.Table.Data.Rows.OfType<DataRow>())
                {
                    var position = row[0];
                    var points = row[1];
                    _pointSystem.Add(position, points);
                }
            }
        }

        private void LoadStandings(ITheme theme)
        {
            if (_standings.Count == 0)
            {
                // Read current standings from embedded csv
                // Columns: customer_id, position, carnumber, drivername, points
                var sheet = theme.Spreadsheets.Find("standings.csv");

                foreach (var row in sheet.Table.Data.Rows.OfType<DataRow>())
                {
                    var custid = row[0];
                    var position = row[1];
                    var carnum = row[2];
                    var name = row[3];
                    var points = row[4];
                    
                    var driver = new ChampDriver();
                    driver.CustomerId = custid;
                    driver.ChampPosition = position;
                    driver.CarNumber = carnum;
                    driver.Name = name;
                    driver.ChampPoints = points;

                    _standings.Add(custid, driver);
                }
            }
        }

        public class ChampDriver
        {
            public IEntity Entity {get;set;}
            public int CustomerId {get;set;}
            public string CarNumber {get;set;}
            public string Name {get;set;}

            public int ChampPosition {get;set;}
            public int ChampPoints {get;set;}
            public int LiveChampPosition {get;set;}
            public int LiveChampPoints {get;set;}
        }
    }
}

This assumes a simple point system with just a relation between current race position and number of points obtained. In your RX example it will be more complicated, but maybe you can figure it out. You'd need to grab the results for each heat and check how many points to obtain.

Note I have no idea about performance for this code. It is constantly updating the data in the Custom Data Sets, if those are bound to a ticker or something it will cause a lot of UI updates. It may work fine, or it may cause lag, no idea, but best not to call this script too often. You could call it every few seconds using a Timer or something.

Nicolas C.
Reply #9

Ok thx Nick.
I read this evening and i come back to you.

Yeah it is a hard question, sorry for that.
Before, i use only spreadsheet and it work fine, but with a build (not sure the number) i have one function of order the spreadsheet before displaying not work anymore.

I can use your script. i have more question :

  • how can i declare an object in script ?
  • can i do a class of "IFRN_DRIVER" with lot of information, use a LIST of this class in script, and after run, calculation, order, write a spreadsheet for display ?

Maybe a suggestion, in script, to have possibility to create custom class

I read all of that and i search the better solution

Thxs for your help and your work

Nick Thissen Appgineering
Reply #10

What you're suggesting seems to be exactly what I'm doing. See the end of the script, I declare a custom class ChampDriver with some properties to hold this information in a list (actually a Dictionary, which is like a hash table). This should work just fine, but like I said I did not test it.

Writing to an actual spreadsheet binding is problematic since a few builds ago yes. I had to change something to avoid a bug and in that process I seem to have disabled you from easily changing the spreadsheet. But I don't think I can reverse it without introducing back the bug.

Instead of writing the standings to a spreadsheet I proposed here to write it to Custom Data Set objects. That should work very well, the only issue is you'll need one Widget for each type of data (position, car number, driver name, etc). So instead of one parent Widget ticker with different SubWidgets/Labels for each data type, you need a separate Widget with a different Dataset. A bit more annoying to work with, but it should work in the end...

Leandro V.
Reply #11

Hi Nick!
I try to test your code, but ATVO shows an error saying that IChampionshipResult does not exists inside the Entity Interface.
But, My ATVO Theme Editor has the Dataset.
How can I update the metadata to solve this problem?

Thanks!

Nick Thissen Appgineering
Reply #12

Can you post your code? IChampionshipResult is the type of the object, but the property on the IEntity interface is called ChampionshipResult (without the I), maybe that's the issue?

Leandro V.
Reply #13
· edited

Hi Nick!
I'm a new ATVO user and I'm learning how to use scripts.
I used your code to test this feature:

using System;
using System.Data;
using ATVO.ThemesSDK;
using ATVO.ThemesSDK.Data.Entity;
using ATVO.ThemeEditor.ThemeModels;
using ATVO.ThemeEditor.Scripting.DotNET;
using System.Collections.Generic;

namespace Scripts
{
 public class LiveChampionshipPoints : IScript
 {
 private Dictionary<int, int> _pointSystem = new Dictionary<int, int>();
        private Dictionary<int, DriverContender> _standings = new Dictionary<int, DriverContender>();

 public object Execute(ThemeContentItem item, object value, string parameter, ISimulation sim)
 {
            // Make sure point system and standings are loaded
            LoadPointSystem(item.Theme);
            LoadStandings(item.Theme);

            // Update the points
            UpdateSessionStandings(sim);

            // Sort by points and assign position
            // This updates positions for drivers in the session AND drivers not in the session
            UpdateChampPositions();

            // Also update the champ results for drivers in the session
            // in case you are showing these somewhere else (in another widget)
            UpdateSessionChampResults();

            // Finally update our Custom Data Sets so the widgets are showing the new results
            UpdateDataSets(item.Theme);
            
 return value.ToString();
 }

        private void UpdateSessionStandings(ISimulation sim)
        {
            // Loop through entity results in the session and update the standings
            foreach (var result in sim.Session.Current.Results)
            {
                DriverContender driver;

                // Check if there is a driver in the session who was NOT in the standings csv
                if (!_standings.ContainsKey(result.Entity.Id))
                {
                    // This driver isn't in the standings yet, let's add him
                    driver = new DriverContender();
                    driver.CustomerId = result.Entity.Id;
                    driver.Name = result.Entity.Name;
                    driver.CarNumber = result.Entity.Car.Number;
                    driver.ChampPoints = 0;
                    _standings.Add(driver.CustomerId, driver);
                }
                else 
                {
                    // Otherwise, grab him from memory
                    driver = _standings[result.Entity.Id];
                }
                
                // Link him to the session entity
                driver.Entity = result.Entity;

                // Add the champ points obtained during this race
                driver.LiveChampPoints = driver.ChampPoints + _pointSystem[result.Position]; // Or result.LivePosition to update during a lap
            }
        }

        private void UpdateChampPositions()
        {
            // Sort the standings by live champ points and assign positions in order
            var sorted = _standings.Values.OrderByDescending(d => d.LiveChampPoints);

            int position = 1;
            foreach (var driver in sorted)
            {
                driver.LiveChampPosition = position;
                position += 1;
            }
        }

        private void UpdateSessionChampResults()
        {
            foreach (var driver in _standings.Values)
            {
                // If this driver is in the session, he has his Entity linked up
                // If not, Entity is null
                if (driver.Entity != null)
                {
                    // update the ChampionshipResult object
                    var champ = driver.Entity.ChampionshipResult;

                    champ.Position = driver.ChampPosition; // previous pos
                    champ.LivePosition = driver.LiveChampPosition; // updated pos
                    champ.Points = driver.ChampPoints; // previous points
                    champ.LivePoints = driver.LiveChampPoints; // updated points
                }
            }
        }

        private void UpdateDataSets(Theme theme)
        {
            // Assuming you have four Custom Data Set items:
            var cds_pos = theme.CustomDataSets.Find("cds_pos"); // for champ position
            var cds_name = theme.CustomDataSets.Find("cds_name"); // for driver name
            var cds_carnum = theme.CustomDataSets.Find("cds_carnum"); // for car num
            var cds_points = theme.CustomDataSets.Find("cds_points"); // for champ points

            // Clear their data
            cds_pos.Data.Clear();
            cds_name.Data.Clear();
            cds_carnum.Data.Clear();
            cds_points.Data.Clear();

            // Add the new data in order
            foreach (var driver in _standings.Values.OrderBy(d => d.LiveChampPosition))
            {
                cds_pos.Data.Add(new CustomDataSetItem(driver.LiveChampPosition));
                cds_name.Data.Add(new CustomDataSetItem(driver.Name));
                cds_carnum.Data.Add(new CustomDataSetItem(driver.CarNumber));
                cds_points.Data.Add(new CustomDataSetItem(driver.LiveChampPoints));
            }
        }

 private void LoadPointSystem(Theme theme)
        {
            if (_pointSystem.Count == 0)
            {
                // Read point system from an embedded spreadsheet
                // Two columns: position, points
                var sheet = theme.Spreadsheets.Find("points_system.csv");

                foreach (var row in sheet.Table.Data.Rows.OfType<DataRow>())
                {
                    var position = row[0];
                    var points = row[1];
                    _pointSystem.Add(position, points);
                }
            }
        }

        private void LoadStandings(Theme theme)
        {
            if (_standings.Count == 0)
            {
                // Read current standings from embedded csv
                // Columns: customer_id, position, carnumber, lastname, firstname, points
                var sheet = theme.Spreadsheets.Find("/csv/standings.csv");

                foreach (var row in sheet.Table.Data.Rows.OfType<DataRow>())
                {
                    var custid = row[0];
                    var position = row[1];
                    var carnum = row[2];
                    var name = String.Concat(row[4], row[3]);
                    var points = row[5];
                    
                    var driver = new DriverContender();
                    driver.CustomerId = custid;
                    driver.ChampPosition = position;
                    driver.CarNumber = carnum;
                    driver.Name = name;
                    driver.ChampPoints = points;

                    _standings.Add(custid, driver);
                }
            }
        }
 }

 public class DriverContender
 {
 public IEntity Entity {get;set;}
        public int CustomerId {get;set;}
        public string CarNumber {get;set;}
        public string Name {get;set;}
        public int ChampPosition {get;set;}
        public int ChampPoints {get;set;}
        public int LiveChampPosition {get;set;}
 public int LiveChampPoints {get;set;}
 }
}

So, here are the errors in ATVO Theme Editor:

LiveChampionshipPoints.cs (Line 74) 'Dictionary<int, DriverContender>.ValueCollection' does not contain a definition for 'OrderByDescending' and no extension method 'OrderByDescending' accepting a first argument of type 'Dictionary<int, DriverContender>.ValueCollection' could be found (are you missing a using directive or an assembly reference?)
LiveChampionshipPoints.cs (Line 77) foreach statement cannot operate on variables of type '?' because '?' does not contain a public definition for 'GetEnumerator'
LiveChampionshipPoints.cs (Line 118) 'Dictionary<int, DriverContender>.ValueCollection' does not contain a definition for 'OrderBy' and no extension method 'OrderBy' accepting a first argument of type 'Dictionary<int, DriverContender>.ValueCollection' could be found (are you missing a using directive or an assembly reference?)
LiveChampionshipPoints.cs (Line 135) 'DataRowCollection' does not contain a definition for 'OfType' and no extension method 'OfType' accepting a first argument of type 'DataRowCollection' could be found (are you missing a using directive or an assembly reference?)
LiveChampionshipPoints.cs (Line 152) 'DataRowCollection' does not contain a definition for 'OfType' and no extension method 'OfType' accepting a first argument of type 'DataRowCollection' could be found (are you missing a using directive or an assembly reference?)

And here are the errors when I'm using Visual Studio Code:

'Dictionary <int, DriverContender> .ValueCollection' does not contain a definition for 'OrderByDescending' and could not find any 'OrderByDescending' extension method that accepts a first argument of type 'Dictionary <int, DriverContender> .ValueCollection' (you are forgetting to use a directive or assembly reference?) (CS1061)

'IEntity' does not contain a definition for "ChampionshipResult" and could not find any "ChampionshipResult" extension method that accepts a first argument of type 'IEntity' (are you forgetting to use a directive or an assembly reference?) ( CS1061)

'Dictionary <int, DriverContender> .ValueCollection' does not contain a definition for 'OrderBy' and could not find any 'OrderBy' extension method that accepts a first argument of type 'Dictionary <int, DriverContender> .ValueCollection' (you are forgetting to use a directive or assembly reference?) (CS1061)

'DataRowCollection' does not contain a definition for "OfType" and could not find any "OfType" extension method that accepts a first argument of type 'DataRowCollection' (are you forgetting to use a directive or an assembly reference?) ( CS1061)

‘DataRowCollection’ não contém uma definição para "OfType" e não foi possível encontrar nenhum método de extensão "OfType" que aceite um primeiro argumento do tipo ‘DataRowCollection’ (você está se esquecendo de usar uma diretiva ou uma referência de assembly?) (CS1061)

Sorry for this long post here.
If you can help me with this problem, I'll appreciate.

Thanks!


Nick Thissen Appgineering
Reply #14

For the error about IEntity not having ChampionshipResult: this is likely just because I did not yet include that new property in the Nuget package. You can ignore that error in VS Code, it should still compile in ATVO Editor with no issues.

The other errors are likely missing 'using' statements. In VS Code, it should give you a popup that suggests a fix. Can you try to do that and does the error go away?

Leandro V.
Reply #15

Hi Nick!
It worked! Thank you so much!
And please, update the Nuget Package! hehe

Regards,
Leandro

Joe W.
Reply #16

hello Nick.....I would love to see if there is a script for Nascar style points and telling who is in the chase and all that on the ticker?....I would pay for somone to help me set this up or give help on how to set it up.....

Thanks
Joe

Daniel J.
Reply #17

Hi! Im currently trying to get this on my themes, but since am a code muggle, Ive struggle to get it done, is not that am a millionaire but, I can spare a couple of bucks to someone better than me can help me with this, and helíng me a little bit with the implementation in my themes, just a simple graphic to show the first 10 drivers in the live championship on a small graphic during the race. Thanks ATVO creator for the amazing software by the way :)

Emmanuel S.
Reply #18

Leandro V. wrote:
Hi Nick!
It worked! Thank you so much!
And please, update the Nuget Package! hehe

Regards,
Leandro

Hi Leandro, you seems to find the solution to do it works nice. Is it possible for you to show your final script ?

Emmanuel S.
Reply #19

Nick, Just on the top of your script exemple you say that:
I had some downtime at work, here is a quick and dirty example I came up with. Not sure if it compiles but it should be close. You'll need four Custom Data Set objects, a spreadsheet for the standings and a spreadsheet for the point system, see the script.
I am not sure how to select a custom data set in data of a widget.

I make some test and I can make appears with your script (with some corrections) actual point and updated point from liveposition. I use a ticker widget and I use Standing for Data Set. Bur I think you say something different!

Nick Thissen Appgineering
Reply #20

The standings dataset will always only show drivers that are currently in your session. But for a championship results graphic, there may be people in the championship who are not currently in the session (e.g. they missed this race). You probably will want to show them, hence you need a spreadsheet as the source of drivers, and you need custom datasets as the data source for a ticker widget.

A custom dataset is just an empty list of data which you can fill manually or via a script. You can put anything you want in there just like my script is putting data for championship drivers.

More recently I added better support for custom datasets by using a script as a dataset. That is probably the better solution to use now, but it requires a different script.

Emmanuel S.
Reply #21

If I understand, for the script show here I just have to add custom dataset with this name (dataset can be empty)
var cds_pos = theme.CustomDataSets.Find("cds_pos"); // for champ position
var cds_name = theme.CustomDataSets.Find("cds_name"); // for driver name
var cds_carnum = theme.CustomDataSets.Find("cds_carnum"); // for car num
var cds_points = theme.CustomDataSets.Find("cds_points"); // for champ points
I test without this custom dataset with 2 csv and the thing I don't have is the driver in the csv that are not in session.

Nick Thissen Appgineering
Reply #22

It is assumed that you have widgets that have these custom datasets as their data set.

But to be honest: this code was a very rough example and should be done much better. Unfortunately I don't have much time today but I will see if I can add a better example soon.

Emmanuel S.
Reply #23

No problem, I do work this code, I do step by step, thanks.

Emmanuel S.
Reply #24

Make some test but can't do appears anything with custom data set.

I can show Drivers in session with their old points (come from csv) and new points added. But I use Standings data set.

Emmanuel S.
Reply #25

OK, I understand, I don't really use the script, just 2 CSV with good format and dataset "STANDINGS" and dataorder "live championship position". I make test with all and think the script do something, but not in this case .... sorry

Emmanuel S.
Reply #26

Test again and now something work, because I don't create in my first test the all 4 custom data as need in script:
cds_pos / cds_name /cds_carnum / cds_points

// Check if there is a driver in the session who was NOT in the standings csv
                if (!_standings.ContainsKey(result.Entity.Id))
                {
                    // This driver isn't in the standings yet, let's add him
                    driver = new DriverContender();
                    driver.CustomerId = result.Entity.Id;
                    driver.Name = result.Entity.Name;
                    driver.CarNumber = result.Entity.Car.Number;
                    driver.ChampPoints = 0;
                    _standings.Add(driver.CustomerId, driver);
                }
                else 
                {
                    // Otherwise, grab him from memory
                    driver = _standings[result.Entity.Id];
                }

In this code what happen to driver in CSV but not in session ? In my case they don't come with other

Emmanuel S.
Reply #27
private void UpdateSessionChampResults()
        {
            foreach (var driver in _standings.Values)
            {
                // If this driver is in the session, he has his Entity linked up
                // If not, Entity is null
                if (driver.Entity != null)
                {
                    // update the ChampionshipResult object
                    var champ = driver.Entity.ChampionshipResult;

                    champ.Position = driver.ChampPosition; // previous pos
                    champ.LivePosition = driver.LiveChampPosition; // updated pos
                    champ.Points = driver.ChampPoints; // previous points
                    champ.LivePoints = driver.LiveChampPoints; // updated points
                }
            }
        }

Or is it in this part that driver in CSV are deleted ?

Nick Thissen Appgineering
Reply #28

Sorry I have a hard time figuring out what you mean.

The code simply does these steps:

  1. LoadPointSystem: Load point structure from spreadsheet (how many points to award per position)
  2. LoadStandings: Load current standings from spreadsheet (e.g. which driver has how many points)
  3. UpdateSessionStandings: go through the drivers in the standings, check their current position and award them the points for this race.
  4. UpdateChampPositions: assign championship (live) position based on their points
  5. UpdateSessionChampResults: go through the drivers in the session and also assign the champ data to the Entity.ChampionshipResult data. This can be used in regular databinding, there should be bindings for each of these.
  6. UpdateDataSets: for each driver in the standings, add his championship data (pos, name, carnumber and points as an example) to a bunch of Custom Dataset items. This allows you to show a set of standings that include drivers that are not in your session, but this is a very "hacky" way to do it.

The logic is very simple: everyone that is in the session will be awarded points. Everyone that is not in the session, but is listed in your standings csv, will also be listed, but you cannot show them in any regular data sets because they are not in the session.

I feel that this is such a recurring issue that I will investigate if I can add this functionality to ATVO directly. It will probably not support all usecases especially with complicated point systems or bonus points etc, but I should manage some simple functionality.

Emmanuel S.
Reply #29

Thanks for this answer, if I understand with this code we supposed to see in a specific widget use custom data could show together pilot in session and pilot in csv.

As you know the code in exemple has some errors, so I try to make correction, now it is ok (no errors visible) but impossible to get together driver in and out in my widget.

So I put my correction and if you have time or if someone could test it it will be nice to say me what is wrong

using System;
using System.Data;
using ATVO.ThemesSDK;
using ATVO.ThemesSDK.Data.Entity;
using ATVO.ThemeEditor.ThemeModels;
using ATVO.ThemeEditor.Scripting.DotNET;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Scripts
{
 public class championnat : IScript
 {
 private Dictionary<int, int> _pointSystem = new Dictionary<int, int>();
        private Dictionary<int, DriverContender> _standings = new Dictionary<int, DriverContender>();

 public object Execute(ThemeContentItem item, object value, string parameter, ISimulation sim)
 {
 if (value == null)
				{
			    // do something appropriate, in this case probably just return nothing
			    return null;
				}	
            // Make sure point system and standings are loaded
            LoadPointSystem(item.Theme);
            LoadStandings(item.Theme);

            // Update the points
            UpdateSessionStandings(sim);

            // Sort by points and assign position
            // This updates positions for drivers in the session AND drivers not in the session
            UpdateChampPositions();

            // Also update the champ results for drivers in the session
            // in case you are showing these somewhere else (in another widget)
            UpdateSessionChampResults();

            // Finally update our Custom Data Sets so the widgets are showing the new results
            UpdateDataSets(item.Theme);
            
 return value.ToString();
 }

        private void UpdateSessionStandings(ISimulation sim)
        {
            // Loop through entity results in the session and update the standings
            foreach (var result in sim.Session.Current.Results)
            {
                DriverContender driver;

                // Check if there is a driver in the session who was NOT in the standings csv
                if (!_standings.ContainsKey(result.Entity.Id))
                {
                    // This driver isn't in the standings yet, let's add him
                    driver = new DriverContender();
                    driver.CustomerId = result.Entity.Id;
                    driver.ChampPosition = (1000-driver.ChampPoints);
                    driver.Name = result.Entity.Name;
                    driver.CarNumber = result.Entity.Car.Number;
                    driver.ChampPoints = 0;
                   // driver.Exist = "1";
                    _standings.Add(driver.CustomerId, driver);
                }
                else 
                {
                    // Otherwise, grab him from memory
                    
                    driver = _standings[result.Entity.Id];
                }
                
                // Link him to the session entity
                driver.Entity = result.Entity;

                // Add the champ points obtained during this race
                driver.LiveChampPoints = driver.ChampPoints + _pointSystem[result.Position]; // Or result.LivePosition to update during a lap
            }
        }

        private void UpdateChampPositions()
        {
            // Sort the standings by live champ points and assign positions in order
            var sorted = _standings.Values.OrderByDescending(d => d.LiveChampPoints);

            int position = 1;
            foreach (var driver in sorted)
            {
                driver.LiveChampPosition = position;
                position += 1;
            }
        }

        private void UpdateSessionChampResults()
        {
            foreach (var driver in _standings.Values)
            {
                // If this driver is in the session, he has his Entity linked up
                // If not, Entity is null
                if (driver.Entity != null)
                {
                    // update the ChampionshipResult object
                    var champ = driver.Entity.ChampionshipResult;

                    champ.Position = driver.ChampPosition; // previous pos
                    champ.LivePosition = driver.LiveChampPosition; // updated pos
                    champ.Points = driver.ChampPoints; // previous points
                    champ.LivePoints = driver.LiveChampPoints; // updated points
                }

            }
        }

        private void UpdateDataSets(Theme theme)
        {
            // Assuming you have four Custom Data Set items:
            var cds_pos = theme.CustomDataSets.Find("cds_pos"); // for champ position
            var cds_name = theme.CustomDataSets.Find("cds_name"); // for driver name
            var cds_carnum = theme.CustomDataSets.Find("cds_carnum"); // for car num
            var cds_points = theme.CustomDataSets.Find("cds_points"); // for champ points

            // Clear their data
            cds_pos.Data.Clear();
            cds_name.Data.Clear();
            cds_carnum.Data.Clear();
            cds_points.Data.Clear();

            // Add the new data in order
            foreach (var driver in _standings.Values.OrderBy(d => d.LiveChampPosition))
            {
           		//var LiveChampPosition2 = LiveChampPosition.ToString();
                cds_pos.Data.Add(new CustomDataSetItem(driver.LiveChampPosition.ToString()));
                cds_name.Data.Add(new CustomDataSetItem(driver.Name));
                cds_carnum.Data.Add(new CustomDataSetItem(driver.CarNumber));
                cds_points.Data.Add(new CustomDataSetItem(driver.LiveChampPoints.ToString()));
            }
        }

 private void LoadPointSystem(Theme theme)
        {
            if (_pointSystem.Count == 0)
            {
                // Read point system from an embedded spreadsheet
                // Two columns: position, points
                var sheet = theme.Spreadsheets.Find("points_system.csv");

                foreach (var row in sheet.Table.Data.Rows.OfType<DataRow>())
                {
                    var position = Convert.ToInt32(row[0]);
                    var points = Convert.ToInt32(row[1]);
                    _pointSystem.Add(position,points);
                }
            }
        }

        private void LoadStandings(Theme theme)
        {
            if (_standings.Count == 0)
            {
                // Read current standings from embedded csv
                // Columns: customer_id, position, carnumber, lastname, firstname, points
                var sheet = theme.Spreadsheets.Find("standings.csv");

                foreach (var row in sheet.Table.Data.Rows.OfType<DataRow>())
                {
                    var custid = Convert.ToInt32(row[0]);
                    var position = Convert.ToInt32(row[1]);
                    var carnum = Convert.ToString(row[2]);
                    var name = String.Concat(row[4], row[3]);
                    var points = Convert.ToInt32(row[5]);
                 //   var exist = Convert.ToString(row[6]);
                    
                    var driver = new DriverContender();
                    driver.CustomerId = custid;
                    driver.ChampPosition = position;
                    driver.CarNumber = carnum;
                    driver.Name = name;
                    driver.ChampPoints = points;
                  //  driver.Exist = exist;

                    _standings.Add(custid,driver);
                }
            }
        }
 }

 public class DriverContender
 {
 public IEntity Entity {get;set;}
        public int CustomerId {get;set;}
        public string CarNumber {get;set;}
        public string Name {get;set;}
        public int ChampPosition {get;set;}
        public int ChampPoints {get;set;}
        public int LiveChampPosition {get;set;}
    //  public string Exist {get;set;}
 public int LiveChampPoints {get;set;}
 }
}
Emmanuel S.
Reply #30

Impossible to edit my post
Don't look my post I do tests again. Some new things.

First the system don't work in multiclass session (i do Watch in iracing and search active race, so I don't focus on that before)

IT IS WORKING in single class race, I can saw pilot from session and pilot from CSV. New Pilot from session are correctly showing with good points, pilots in Session and in CSV are showing with good points (old & new added) and pilot only in CSV are showing with 0 point

I have to search a little bit more.

Emmanuel S.
Reply #32

Some more investigations. It is ok with MULTI too, just with TEAM session you have to change ID to Team ID in the CSV.

But always the same problem : driver not in session have 0 point even if they get points in CSV.

Emmanuel S.
Reply #33

Nick Thissen wrote:
It is assumed that you have widgets that have these custom datasets as their data set.

But to be honest: this code was a very rough example and should be done much better. Unfortunately I don't have much time today but I will see if I can add a better example soon.

Hi, Nick,
I've been very interrested if you can show me a better exemple with a better code ;)

Kyle W.
Reply #34

Hello Nick,

New user here. I am looking to add a live points tracker to my broadcast but the format is a little complicated as we use the current NASCAR playoffs format. Does this update/feature or any others that you have allow for tracking elimination style playoff rounds?

For example, we have 16 playoff drivers, 3 rounds. 6 drivers are eliminated after round 1, 6 more after round 2, leaving 4 drivers for the final playoff race. Our drivers are scored by position in the race plus with 1 bonus point for winning the pole, leading a lap, and finishing with 0 incident points. Is there any possible way to code an overlay for something like this? Basically I'm just looking to show a live points update in the corner of the broadcast that displays all drivers above the cutline and the 6 drivers currently below the cut line.
Thanks!

Josh L.
Reply #35

Bumping this because we need a simpler explanation on what to do. Emmanuel's script works, but everything else isn't clear and concise. Does anyone have it working in their theme that they can share? Or share the proper steps to get it to work for both live points and pre-race points?

Josh L.
Reply #36

Alright bumping this again, somehow I got it to half work but the live data only updates when the script is executed, so I need to set a constant timer to keep executing the script. I also need help on getting it to work with Heats and Qualifying to give points that way. Any help is appreciated!

Josh L.
Reply #37

Josh L. wrote:
Alright bumping this again, somehow I got it to half work but the live data only updates when the script is executed, so I need to set a constant timer to keep executing the script. I also need help on getting it to work with Heats and Qualifying to give points that way. Any help is appreciated!

Amendment: It updates every time past the start finish while also needing another script execute to work. This can become a problem for longer tracks where a lot of passing is happening and the points just aren't updating quick enough.

Billy D.
Reply #38

I would really appreciate a step by step on how to get this working. TIA.

Archive · Read-only

New replies have moved to Discord.