├── eGlide ├── eGlide_Total.pas ├── eGlide_Daily_results.pas └── eGlide_Daily_results_DHT.pas ├── total_script ├── Readme.md └── Solve_ties_by_daily_ranks.pas ├── .gitignore ├── README.md └── igc_annex_a ├── AnnexA_AlternativeScoring.pas ├── AnnexA_scoring.pas └── AnnexA_scoring_2023.pas /eGlide/eGlide_Total.pas: -------------------------------------------------------------------------------- 1 | Program eGlide_Total_results; 2 | var i, j, minIdx : integer; 3 | min, PilotTotal, WinnersTotal : double; 4 | begin 5 | 6 | // Find the lowest score 7 | min := -1000000; 8 | minIdx := -1; 9 | for i := 0 to GetArrayLength(Pilots)-1 do 10 | begin 11 | if Pilots[i].Total > min Then 12 | begin 13 | min := Pilots[i].Total; 14 | minIdx := i; 15 | end; 16 | end; 17 | 18 | for i := 0 to GetArrayLength(Pilots)-1 do 19 | begin 20 | Pilots[i].Total := Pilots[i].Total - min; 21 | // ShowMessage(Pilots[i].CompID + ' ' + IntToStr(Round(Pilots[i].Total))); 22 | end; 23 | 24 | // Pilots[minIdx].Total := WinnersTotal; 25 | end. 26 | -------------------------------------------------------------------------------- /total_script/Readme.md: -------------------------------------------------------------------------------- 1 | ## How to use Total script (optional) 2 | 3 | The script for Total results is optional. It is blank by default. In the absence of a Total script, results are simply added together to create a total score for each competitor. 4 | 5 | If that doesn't solve your problem, you can use the Total script to calculate Total results in a different way. Procedure is the same way as for daily scripts except that they are located in Edit > Competition Properties > Total Script. 6 | 7 | ### Available variables for Total script 8 | 9 | Pilots = record 10 | 11 | Total : Double; total points (default value is sum of DayPoints, not 0) 12 | 13 | TotalString : String; if this is not empty, total points will be shown as the string defined in this value 14 | 15 | DayPts : array of Double; Points from each day as calculated in daily scripts 16 | 17 | DayPtsString : array of String; Same functionality as TotalString 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/delphi,macos,windows,linux 3 | # Edit at https://www.gitignore.io/?templates=delphi,macos,windows,linux 4 | 5 | ### Delphi ### 6 | # Uncomment these types if you want even more clean repository. But be careful. 7 | # It can make harm to an existing project source. Read explanations below. 8 | # 9 | # Resource files are binaries containing manifest, project icon and version info. 10 | # They can not be viewed as text or compared by diff-tools. Consider replacing them with .rc files. 11 | #*.res 12 | # Type library file (binary). In old Delphi versions it should be stored. 13 | # Since Delphi 2009 it is produced from .ridl file and can safely be ignored. 14 | #*.tlb 15 | # Diagram Portfolio file. Used by the diagram editor up to Delphi 7. 16 | # Uncomment this if you are not using diagrams or use newer Delphi version. 17 | #*.ddp 18 | # Visual LiveBindings file. Added in Delphi XE2. 19 | # Uncomment this if you are not using LiveBindings Designer. 20 | #*.vlb 21 | # Deployment Manager configuration file for your project. Added in Delphi XE2. 22 | # Uncomment this if it is not mobile development and you do not use remote debug feature. 23 | #*.deployproj 24 | # C++ object files produced when C/C++ Output file generation is configured. 25 | # Uncomment this if you are not using external objects (zlib library for example). 26 | #*.obj 27 | 28 | # Delphi compiler-generated binaries (safe to delete) 29 | *.exe 30 | *.dll 31 | *.bpl 32 | *.bpi 33 | *.dcp 34 | *.so 35 | *.apk 36 | *.drc 37 | *.map 38 | *.dres 39 | *.rsm 40 | *.tds 41 | *.dcu 42 | *.lib 43 | *.a 44 | *.o 45 | *.ocx 46 | 47 | # Delphi autogenerated files (duplicated info) 48 | *.cfg 49 | *.hpp 50 | *Resource.rc 51 | 52 | # Delphi local files (user-specific info) 53 | *.local 54 | *.identcache 55 | *.projdata 56 | *.tvsconfig 57 | *.dsk 58 | 59 | # Delphi history and backups 60 | __history/ 61 | __recovery/ 62 | *.~* 63 | 64 | # Castalia statistics file (since XE7 Castalia is distributed with Delphi) 65 | *.stat 66 | 67 | # Boss dependency manager vendor folder https://github.com/HashLoad/boss 68 | modules/ 69 | 70 | ### Linux ### 71 | *~ 72 | 73 | # temporary files which can be created if a process still has a handle open of a deleted file 74 | .fuse_hidden* 75 | 76 | # KDE directory preferences 77 | .directory 78 | 79 | # Linux trash folder which might appear on any partition or disk 80 | .Trash-* 81 | 82 | # .nfs files are created when an open file is removed but is still being accessed 83 | .nfs* 84 | 85 | ### macOS ### 86 | # General 87 | .DS_Store 88 | .AppleDouble 89 | .LSOverride 90 | 91 | # Icon must end with two \r 92 | Icon 93 | 94 | # Thumbnails 95 | ._* 96 | 97 | # Files that might appear in the root of a volume 98 | .DocumentRevisions-V100 99 | .fseventsd 100 | .Spotlight-V100 101 | .TemporaryItems 102 | .Trashes 103 | .VolumeIcon.icns 104 | .com.apple.timemachine.donotpresent 105 | 106 | # Directories potentially created on remote AFP share 107 | .AppleDB 108 | .AppleDesktop 109 | Network Trash Folder 110 | Temporary Items 111 | .apdisk 112 | 113 | ### Windows ### 114 | # Windows thumbnail cache files 115 | Thumbs.db 116 | ehthumbs.db 117 | ehthumbs_vista.db 118 | 119 | # Dump file 120 | *.stackdump 121 | 122 | # Folder config file 123 | [Dd]esktop.ini 124 | 125 | # Recycle Bin used on file shares 126 | $RECYCLE.BIN/ 127 | 128 | # Windows Installer files 129 | *.cab 130 | *.msi 131 | *.msix 132 | *.msm 133 | *.msp 134 | 135 | # Windows shortcuts 136 | *.lnk 137 | 138 | # End of https://www.gitignore.io/api/delphi,macos,windows,linux 139 | -------------------------------------------------------------------------------- /total_script/Solve_ties_by_daily_ranks.pas: -------------------------------------------------------------------------------- 1 | Program Solve_ties_by_daily_ranks; 2 | 3 | // + Version 1.20 by Wojciech Scigala, 14.10.2014 4 | // Script solves tied scores according to number of daily ranks 5 | // (no. of 1st places, no. of 2nd places and so on) 6 | // Designed to operate with IGC 1000-point scoring system 7 | 8 | // known bug: HC is not checked when determining Daily or Total ranks 9 | // (variable Pilots[i].isHC not available in SeeYou) 10 | 11 | 12 | // Parameter ONLYTOPRANKS limits solving ties only to given number 13 | // of top ranked pilots. If set to 0, all ties are solved. 14 | 15 | 16 | const 17 | ONLYTOPRANKS = 3; 18 | SMALLINC = 0.1; 19 | 20 | var 21 | i,j,x : Integer; 22 | Days : Integer; 23 | Ties : array of Integer; 24 | PilotsDone : array of boolean; 25 | DayRankCache : array of array of SmallInt; 26 | 27 | 28 | function DayRank (Pilot, Day : integer) : integer; 29 | var j, r : integer; 30 | 31 | begin 32 | r := DayRankCache[Pilot][Day]; 33 | if r = -1 then 34 | begin 35 | r := 1; 36 | for j := low(Pilots) to high(Pilots) do 37 | begin 38 | if Pilots[Pilot].DayPts[Day] < Pilots[j].DayPts[Day] then 39 | r := r + 1; 40 | end; 41 | DayRankCache[Pilot][Day] := r; 42 | end 43 | 44 | result := r; 45 | end; 46 | 47 | 48 | Function PilotTotalRank(Pilot : integer) : integer; 49 | var i, r : integer; 50 | 51 | begin 52 | r := 1; 53 | for i:=low(Pilots) to high(Pilots) do 54 | begin 55 | if (Pilots[i].Total > Pilots[Pilot].Total) then 56 | r := r + 1; 57 | end; 58 | 59 | result := r; 60 | end; 61 | 62 | 63 | function CountDayRanks (Pilot, Rank : integer) : integer; 64 | 65 | var d,r : integer; 66 | 67 | begin 68 | r := 0; 69 | for d := 0 to Days-1 do 70 | begin 71 | if Rank = DayRank(Pilot,d) then 72 | r := r + 1; 73 | end; 74 | 75 | result := r; 76 | end; 77 | 78 | 79 | function CompareTwoPilots (p1,p2 : integer) : integer; 80 | 81 | var 82 | dr : integer; 83 | p1_dr, p2_dr : integer; 84 | 85 | begin 86 | result := 0; // unsolvable 87 | 88 | for dr := 1 to high(Pilots)+1 do 89 | begin 90 | // Count Day Ranks 91 | p1_dr := CountDayRanks(p1,dr); 92 | p2_dr := CountDayRanks(p2,dr); 93 | 94 | if p1_dr > p2_dr then 95 | begin 96 | result := 1; 97 | break; 98 | end; 99 | 100 | if p1_dr < p2_dr then 101 | begin 102 | result := 2; 103 | break; 104 | end; 105 | 106 | end; 107 | end; 108 | 109 | 110 | procedure SolveTies (TiedPilots : array of integer); 111 | var 112 | i,j : integer; 113 | p1,p2 : integer; 114 | CompResult : integer; 115 | 116 | begin 117 | for i:=low(TiedPilots) to high(TiedPilots)-1 do 118 | begin 119 | PilotsDone[i] := true; 120 | p1 := TiedPilots[i]; 121 | for j:=i+1 to high(TiedPilots) do 122 | begin 123 | p2 := TiedPilots[j]; 124 | CompResult := CompareTwoPilots(p1,p2); 125 | if CompResult = 1 then 126 | Pilots[p1].Total := Pilots[p1].Total + SMALLINC; 127 | 128 | if CompResult = 2 then 129 | Pilots[p2].Total := Pilots[p2].Total + SMALLINC; 130 | 131 | end; 132 | end; 133 | end; 134 | 135 | 136 | 137 | begin 138 | 139 | // initial checks 140 | if GetArrayLength(Pilots) <= 1 then 141 | exit; 142 | 143 | Days := GetArrayLength(Pilots[0].DayPts); 144 | if Days <= 1 then 145 | exit; 146 | 147 | // set up PilotsDone array 148 | setarraylength(PilotsDone, GetArrayLength(Pilots)); 149 | for i:= low(PilotsDone) to high(PilotsDone) do 150 | PilotsDone[i] := false; 151 | 152 | //set up DayRankCache 153 | setarraylength(DayRankCache, GetArrayLength(Pilots)); 154 | for i:=low(DayRankCache) to high(DayRankCache) do 155 | begin 156 | setarraylength(DayRankCache[i], Days); 157 | for j:=0 to Days-1 do 158 | DayRankCache[i][j] := -1; 159 | end; 160 | 161 | // Store actual points as strings for presentation 162 | for i := low(Pilots) to high(Pilots) do 163 | begin 164 | Pilots[i].TotalString := FloatToStr(Pilots[i].Total); 165 | end; 166 | 167 | // set PilotsDone := true for pilots with rank > ONLYTOPRANKS 168 | if (ONLYTOPRANKS > 0) then 169 | for i := low(Pilots) to high(Pilots) do 170 | begin 171 | if (PilotTotalRank(i) > ONLYTOPRANKS) then 172 | PilotsDone[i] := true; 173 | end; 174 | 175 | // Find and solve tied scores 176 | for i := low(Pilots) to high(Pilots)-1 do 177 | begin 178 | if (Pilots[i].Total <> 0) and (PilotsDone[i] = false) then 179 | begin 180 | x := 0; 181 | setarraylength(Ties, 0); 182 | for j := low(Pilots) to high(Pilots) do 183 | if (Pilots[i].Total = Pilots[j].Total) then 184 | begin 185 | x := x + 1; 186 | setarraylength(Ties, x); 187 | Ties[x-1] := j; 188 | end; 189 | 190 | if GetArrayLength(Ties) >= 2 then 191 | SolveTies(Ties); 192 | 193 | end; 194 | end; 195 | end. 196 | -------------------------------------------------------------------------------- /eGlide/eGlide_Daily_results.pas: -------------------------------------------------------------------------------- 1 | Program eGlide_Elapsed_time_scoring; 2 | 3 | const 4 | UseHandicaps = 2; // set to: 0 to disable handicapping, 1 to use handicaps, 2 is auto (handicaps only for club and multi-seat) 5 | PowerTreshold = 20; // In Watts [W]. If Current*Voltage is less than that, it won't count towards consumed energy. 6 | RefVoltage = 110; // Fallback if nothing else is known about voltage used when engine is running 7 | RefCurrent = 200; // Fallback if nothing is known about current consumption 8 | FreeAllowance = 2000; // Watt-hours. No penalty if less power was consumed 9 | EnginePenaltyPerSec = 1000/15/60; // Penalty in seconds per Watt-hour consumed over Free Allowance. 1000 Wh of energy allows you to cruise for 15 minutes. 10 | Fa = 1.2; // Amount of time penalty for next finisher / outlander 11 | 12 | var 13 | Dm, D1, 14 | Dt, n1, n2, n3, n4, N, D0, Vo, T0, Tm, Hmin, 15 | Pm, Pdm, Pvm, Pn, F, Fcr, Day: Double; 16 | 17 | D, H, Dh, M, T, Dc, Pd, V, Vh, Pv, S : double; 18 | 19 | PmaxDistance, PmaxTime, PilotEnergyConsumption, CurrentPower, PilotEngineTime, EnginePenalty : double; 20 | 21 | i,j, minIdx : integer; 22 | str : String; 23 | Interval, NumIntervals, GateIntervalPos, NumIntervalsPos, PilotStartInterval, PilotStartTime, PilotPEVStartTime, StartTimeBuffer : Integer; 24 | AAT : boolean; 25 | Auto_Hcaps_on : boolean; 26 | 27 | 28 | begin 29 | // initial checks 30 | if GetArrayLength(Pilots) <= 1 then 31 | exit; 32 | 33 | Hmin := 100000; // Lowest Handicap of all competitors in the class 34 | T0 := 10000000; 35 | Tm := 0; // slowest finisher time 36 | for i:=0 to GetArrayLength(Pilots)-1 do 37 | begin 38 | Pilots[i].start := Task.NoStartBeforeTime; 39 | if Pilots[i].finish > 0 Then 40 | begin 41 | Pilots[i].speed := Pilots[i].dis / (Pilots[i].finish-Pilots[i].start); 42 | end; 43 | If not Pilots[i].isHC Then 44 | begin 45 | If Pilots[i].Hcap < Hmin Then Hmin := Pilots[i].Hcap; // Lowest Handicap of all competitors in the class 46 | end; 47 | end; 48 | If Hmin=0 Then begin 49 | Info1 := ''; 50 | Info2 := 'Error: Lowest handicap is zero!'; 51 | Exit; 52 | end; 53 | 54 | for i:=0 to GetArrayLength(Pilots)-1 do 55 | begin 56 | If not Pilots[i].isHC Then 57 | begin 58 | // Find the lowest task time 59 | T := (Pilots[i].finish-Pilots[i].start) * Pilots[i].Hcap/Hmin; 60 | If (T < T0) and (Pilots[i].finish > 0) Then 61 | begin 62 | T0 := T; 63 | minIdx := i; 64 | end; 65 | 66 | // Find the slowest finisher 67 | if T > Tm Then 68 | begin 69 | Tm := T; 70 | end; 71 | end; 72 | end; 73 | 74 | // Energy Consumption by pilot on task 75 | for i:=0 to GetArrayLength(Pilots)-1 do 76 | begin 77 | Pilots[i].Warning := ''; 78 | PilotEnergyConsumption := 0; 79 | PilotEngineTime := 0; 80 | 81 | for j := 0 to GetArrayLength(Pilots[i].Fixes)-1 do 82 | begin 83 | if (Pilots[i].Fixes[j].Tsec > Pilots[i].start) and (Pilots[i].Fixes[j].Tsec < Pilots[i].finish) Then 84 | begin 85 | // If pilot has Cur and Vol 86 | if Pilots[i].HasCur then 87 | begin 88 | if not Pilots[i].HasVol Then 89 | Pilots[i].Fixes[j].Vol := RefVoltage; 90 | if (Pilots[i].Fixes[j].Cur > 0) and (Pilots[i].Fixes[j].Vol > 0) then 91 | begin 92 | CurrentPower := Pilots[i].Fixes[j].Cur * Pilots[i].Fixes[j].Vol; 93 | If CurrentPower > PowerTreshold then 94 | begin 95 | PilotEngineTime := PilotEngineTime + Pilots[i].Fixes[j+1].Tsec - Pilots[i].Fixes[j].Tsec; 96 | // Pilots[i].Warning := Pilots[i].Warning + IntToStr(Round(Pilots[i].Fixes[j].Cur))+ ' * ' + IntToStr(Round(Pilots[i].Fixes[j].Vol)) + ' * ' + IntToStr(Pilots[i].Fixes[j+1].Tsec - Pilots[i].Fixes[j].Tsec) + #10; 97 | PilotEnergyConsumption := PilotEnergyConsumption + CurrentPower * (Pilots[i].Fixes[j+1].Tsec - Pilots[i].Fixes[j].Tsec) / 3600; 98 | Pilots[i].td1 := PilotEnergyConsumption; 99 | end; 100 | end; 101 | end 102 | else 103 | begin 104 | If Pilots[i].Fixes[j].EngineOn Then 105 | begin 106 | CurrentPower := RefCurrent * RefVoltage; 107 | PilotEngineTime := PilotEngineTime + Pilots[i].Fixes[j+1].Tsec - Pilots[i].Fixes[j].Tsec; 108 | // Pilots[i].Warning := Pilots[i].Warning + IntToStr(Round(Pilots[i].Fixes[j].Cur))+ ' * ' + IntToStr(Round(Pilots[i].Fixes[j].Vol)) + ' * ' + IntToStr(Pilots[i].Fixes[j+1].Tsec - Pilots[i].Fixes[j].Tsec) + #10; 109 | PilotEnergyConsumption := PilotEnergyConsumption + CurrentPower * (Pilots[i].Fixes[j+1].Tsec - Pilots[i].Fixes[j].Tsec) / 3600; 110 | Pilots[i].td1 := PilotEnergyConsumption; 111 | end; 112 | end; 113 | end; 114 | end; 115 | 116 | // // Debug output 117 | // if Pilots[i].HasCur Then 118 | // Pilots[i].Warning := Pilots[i].Warning + 'HasCur = 1'+#10 119 | // else 120 | // Pilots[i].Warning := Pilots[i].Warning + 'HasCur = 0'+#10; 121 | // if Pilots[i].HasVol Then 122 | // Pilots[i].Warning := Pilots[i].Warning + 'HasVol = 1'+#10 123 | // else 124 | // Pilots[i].Warning := Pilots[i].Warning + 'HasVol = 0'+#10; 125 | // if Pilots[i].HasEnl Then 126 | // Pilots[i].Warning := Pilots[i].Warning + 'HasEnl = 1'+#10 127 | // else 128 | // Pilots[i].Warning := Pilots[i].Warning + 'HasEnl = 0'+#10; 129 | // if Pilots[i].HasMop Then 130 | // Pilots[i].Warning := Pilots[i].Warning + 'HasMop = 1'+#10 131 | // else 132 | // Pilots[i].Warning := Pilots[i].Warning + 'HasMop = 0'+#10; 133 | Pilots[i].Warning := Pilots[i].Warning + 'EngineTime = ' + IntToStr(Round(PilotEngineTime)) + ' s' + #10; 134 | Pilots[i].Warning := Pilots[i].Warning + 'PowerConsumption = ' + IntToStr(Round(PilotEnergyConsumption)) + ' Wh' +#10; 135 | if PilotEnergyConsumption > FreeAllowance then 136 | Pilots[i].Warning := Pilots[i].Warning 137 | + 'Engine Penalty = ' + IntToStr(Round(PilotEnergyConsumption-FreeAllowance)) + ' Wh = ' 138 | + FormatFloat('0.00',((PilotEnergyConsumption - FreeAllowance) * EnginePenaltyPerSec / 60)) + ' minutes' 139 | +#10; 140 | end; 141 | 142 | 143 | // ELAPSED TIME SCORING 144 | for i:=0 to GetArrayLength(Pilots)-1 do 145 | begin 146 | if Pilots[i].finish > 0 then 147 | begin 148 | Pilots[i].Points := -1.0*((Pilots[i].finish - Pilots[i].start)*Pilots[i].Hcap/Hmin - T0)/60; 149 | end 150 | else 151 | begin 152 | // Outlanders get 1.2 x the slowest finisher 153 | Pilots[i].Points := (-1.0*Tm*Fa + T0)/60; 154 | end; 155 | 156 | // Engine penalty 157 | PilotEnergyConsumption := Pilots[i].td1; 158 | if PilotEnergyConsumption > FreeAllowance then 159 | begin 160 | EnginePenalty := (PilotEnergyConsumption - FreeAllowance) * EnginePenaltyPerSec / 60; // Penalty in minutes 161 | Pilots[i].Points := Pilots[i].Points - EnginePenalty; 162 | end; 163 | 164 | //Worst score a pilot can get is 1.2 times the last finisher's time. 165 | if Pilots[i].Points < (-1.0*Tm*Fa+T0)/60 Then 166 | Pilots[i].Points := (-1.0*Tm*Fa+T0)/60; 167 | 168 | Pilots[i].Points := Round((Pilots[i].Points- Pilots[i].Penalty/60)*100)/100; // Expected penalty is in seconds 169 | end; 170 | 171 | // Data which is presented in the score-sheets 172 | for i:=0 to GetArrayLength(Pilots)-1 do 173 | begin 174 | Pilots[i].sstart:=Pilots[i].start; 175 | Pilots[i].sfinish:=Pilots[i].finish; 176 | Pilots[i].sdis:=Pilots[i].dis; 177 | Pilots[i].sspeed:=Pilots[i].speed; 178 | end; 179 | // Pilots[minIdx].Points := Round(T0/60*100)/100; 180 | 181 | // Info fields, also presented on the Score Sheets 182 | Info1 := 'Elapsed time race'; 183 | Info1 := Info1 + ', results in minutes behind leader, handicapped'; 184 | 185 | end. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![SeeYou Competition Scoring Scripts](https://www.naviter.com/scoring_scripts_github.png) 2 | 3 | ### Table of contents 4 | 5 | * [Writing your own scripts](#writing-your-own-scripts) 6 | * [Available variables for daily points scripts](#available-variables-for-daily-points-scripts) 7 | * [Available variables for total results script](#available-variables-for-total-results-script) 8 | * [Contributing Guidelines](#contributing-guidelines) 9 | 10 | 11 | # Writing your own scripts 12 | 13 | **Scripts for daily results** 14 | 15 | SeeYou will calculate the day performance items like Marking Distance, speed, start and finish times etc. It is the responsibility of the script to determine how many POINTS are awarded for the achieved performance. 16 | 17 | SeeYou Competition scripts are implemented using the Innerfuse [Pascal Scripts](http://www.carlo-kok.com). They are very basic Pascal routines with some exceptions. 18 | 19 | You can write scripts in Notepad or any other Text editor of your choice. You can assign scripts to each class of competition separately through Edit > Competition Properties > Scripts. 20 | 21 | ![img](https://d33v4339jhl8k0.cloudfront.net/docs/assets/5a1c4392042863319924c769/images/5cf8efcc2c7d3a3837133075/file-TKhMl28z6o.png) 22 | 23 | It is important to keep the general structure of the script: 24 | 25 | ```pascal 26 | Program Scoring_Script_Name; 27 | 28 | begin 29 | 30 | // Your script here 31 | 32 | end. 33 | ``` 34 | 35 | There are many variables available to the scoring script. See "Available variables for daily points script". 36 | 37 | **Scripts for total rsults** 38 | 39 | You can also write a script for Total results. Procedure is the same way as for daily scripts except that they are located in Edit > Competition Properties > Total Script. See "Available variables for total points script". 40 | 41 | **How does it work?** 42 | 43 | A TPilots record is provided by SeeYou to the Scoring script. TPilots record and a couple other fields determine all the information required to calculate the scoring for a given contest day. This is the definition of the TPilot record: 44 | 45 | ## Available variables for daily points scripts 46 | 47 | All variable values are **double** if not indicated otherwise 48 | All times are **seconds since midnight** if not indicated otherwise 49 | 50 | ### TPilots 51 | Type: Record 52 | Information about pilot's performance. 53 | 54 | Typical use: Handicap := Pilots[i].Hcap; where i is a running integer in a loop. 55 | 56 | | Variable | Description | Unit | Remarks | 57 | | ------------------------------- | :----------------------------------------------------------- | ---- | --------------------------------------------- | 58 | | sstart | start time displayed in results sets | s | | 59 | | sfinish | finish time displayed in results sets | s | negative values - no finish | 60 | | sdis | distance shown in results | m | negative values will be shown in parenthesis | 61 | | sspeed | speed shown in results | m/s | negative values will be shown in parenthesis | 62 | | points | points shown in results | | | 63 | | pointString | a string representation of points for custom output | | string | 64 | | Hcap | handicap factor as declared in pilot setup | | | 65 | | penalty | penalty points defined in "Day performance" dialog | | | 66 | | start | start time of task | s | -1 if no start | 67 | | finish | finish time of task | s | -1 if no finish | 68 | | dis | flown distance | m | | 69 | | speed | speed of finished taks | m/s | -1 if no finish, takes into account task time | 70 | | tstart | start time of task with time | s | -1 if no start | 71 | | tfinish | finish time of task with time | s | | 72 | | tdis | flown distance in task time | m | | 73 | | tspeed | flown distance divided by task time | m/s | | 74 | | takeoff | takeoff time | s | -1 if no takeoff | 75 | | landing | landing time | s | -1 if no landing | 76 | | phototime | outlanding time | s | -1 if no outlanding | 77 | | isHc | set to TRUE if not competing is used | | bool | 78 | | FinishAlt | altitude of task finish | m | | 79 | | DisToGoal | distance between Task landing point and flight landing point | m | | 80 | | Tag | string value as defined in Day performace dialog | | string | 81 | | Leg, LegT | array of TLeg records | | array | 82 | | Warning | used to set up a user warning | | string | 83 | | CompID | Competition ID of the glider | | string | 84 | | PilotTag | string value as defined in Pilot edit dialog | | string | 85 | | user_str1, user_str2, user_str3 | user strings, use for anything | | string | 86 | | td1, td2, td3 | temprary variables, use for anything | | | 87 | | Markers | array of TMarker (see definition for TMarker below) | | array | 88 | | PotStarts | Array of all valid crossings of the start line. In seconds since midnight | s | array of integers | 89 | 90 | ### TFix 91 | 92 | Type: record 93 | 94 | Information about each recorded position in the IGC file. This data is only available if the switch in Edit > Contest properties > Options > Expose fixes in scripts is enabled. 95 | 96 | Typical use: QNH_Altitiude := Pilots[i].Fix[j].AltQNH; where i and j are running integeres in loops. 97 | 98 | | Entry | Description | Unit | Remarks | 99 | | ------ | --------------- | ---- | ------- | 100 | | Tsec | time of fix | s | integer | 101 | | AltQnh | Altitude above mean sea level | m | double | 102 | | AltQne | Altitude above standard pressure level | m | double | 103 | | Gsp | Ground speed | m/s | double | 104 | | DoT | Distance on task| m | double | 105 | | Cur | Current current consumption | A | double; Requires LXNAV FES Bridge data stored in IGC file | 106 | | Vol | Current battery voltage | V | double; Requires LXNAV FES Bridge data stored in IGC file | 107 | | Enl | Current value stored in ENL field | | double | 108 | | Mop | Current value stored in MOP field | | double | 109 | | EngineOn | Wheter SeeYou determined that engine was running or not | | boolean | 110 | 111 | ### TLeg 112 | 113 | Type: record 114 | 115 | Task leg information for each Pilot's performance. 116 | 117 | Typical use: Leg_distance := Pilots[i].Leg[j].d; where i and j are running integers in loops. 118 | 119 | | Entry | Description | Unit | Remarks | 120 | | ------ | --------------- | ---- | ------- | 121 | | start | leg start time | s | | 122 | | finish | leg finish time | s | | 123 | | d | leg distance | m | | 124 | | crs | leg course | rad | | 125 | 126 | ### TMarkers 127 | 128 | Type: record 129 | 130 | Records of Pilot Event Marker (PEM) events - created when pilot depressed "Event Marker" button. 131 | 132 | Typical use: PEV_time := Pilots[i].Markers[j].Tsec; where i and j are running integers in loops. 133 | 134 | | Entry | Description | Unit | Remarks | 135 | | ------------ | ------------------------------------------------ | ---- | ------- | 136 | | Tsec | time of the PEM | s | integer | 137 | | Msg | optional message stored with the PEM in IGC file | | string | 138 | | info1, info2 | information shown in the results | | string | 139 | | DatTag | value as defined in "Day properties" dialog | | string | 140 | | ShowMessage | string for debugging purposes | | string | 141 | 142 | ### Task 143 | 144 | Type: record 145 | 146 | Basic information about task 147 | 148 | Typical use: Task_Distance := Task.TotalDis; 149 | 150 | | Entry | Description | Unit | Remarks | 151 | | ----------------- | ------------------------------- | ---- | ------- | 152 | | TotalDis | task distance | m | | 153 | | TaskTime | task time | s | integer | 154 | | NoStartBeforeTime | start time | s | integer | 155 | | Point | array of TTaskPoints | | array | 156 | | ClassID | enum of existing glider classes | | string | 157 | 158 | ClassID enum: 159 | 160 | - world 161 | - club 162 | - standard 163 | - 13_5_meter 164 | - 15_meter 165 | - 18_meter 166 | - double_seater 167 | - open 168 | - hang_glider_flexible 169 | - hang_glider_rigid 170 | - paraglider 171 | - unknown 172 | 173 | - ClassName: string; 174 | Optional nice name for the given class - as entered at Soaring Spot. 175 | 176 | ### TTaskPoint 177 | 178 | Type: record 179 | 180 | Basic information about taskpoint which defines the task. Note that in Assigned Area Tasks these are not the optimal points which pilot achieved, but the points which define the task. 181 | 182 | Typical use: Distance_to_next_TP_center := Task.TaskPoint[i].d; where i is a running integer in a loop. 183 | 184 | | Entry | Description | Unit | Remarks | 185 | | ------------- | ------------------------------------- | ---- | ------- | 186 | | lon | longitude | | | 187 | | lat | lattitude | | | 188 | | d | distance to next point | m | | 189 | | crs | course to next point | rad | | 190 | | td1, td2, td3 | optional, used as temporary variables | | | 191 | 192 | ## Available variables for total results script 193 | 194 | ### Pilots 195 | 196 | Type: record 197 | 198 | This record is used only in scripts which calculate total results. These are separate scripts from the scripts which compute results for each competition day. 199 | 200 | If Total script is empty, the daily points for each pilot are simply added together to create the pilot's total score. If you write a total script, then you can do advanced calculations for the pilot's total like dropping the worst day, or even calculating FTV in paragliding competitions. Let loose your creativity. 201 | 202 | Typical use: Pilots[i].Total := Pilots[i].Total + Pilots[i].DayPts; where i is a running integer in a loop. 203 | 204 | | Entry | Description | Unit | Remarks | 205 | | ------------ | --------------------------------------------------- | ---- | ---------------------------------------- | 206 | | Total | total points | | default value is sum of DayPoints, not 0 | 207 | | TotalString | format of the total points when shown as string | | string | 208 | | DayPts | Points from each day as calculated in daily scripts | | array of doubles | 209 | | DayPtsString | format of the day points when shown as string | | string | 210 | 211 | # Contributing guidelines 212 | 213 | You wrote a great script, found a bug and corrected it? Great! To incorportate changes to existing scripts or adding your own scripts, please fork the repo, make your changes in a new branch and create a pull request. Pull requests from forks are described in [pull request from fork](https://help.github.com/en/articles/creating-a-pull-request-from-a-fork) article. 214 | -------------------------------------------------------------------------------- /igc_annex_a/AnnexA_AlternativeScoring.pas: -------------------------------------------------------------------------------- 1 | Program IGC_Annex_A_Alternative_scoring_2022; 2 | // Collaborate on writing scripts at Github: 3 | // https://github.com/naviter/seeyou_competition_scripts/ 4 | // Version 1.00 5 | // . First implementation of Alternate scoring according to Annex A version February 7, 2022 6 | // . Definition of median: https://en.wikipedia.org/wiki/Median 7 | 8 | const UseHandicaps = 2; // set to: 0 to disable handicapping, 1 to use handicaps, 2 is auto (handicaps only for club and multi-seat) 9 | PevStartTimeBuffer = 30; // PEV which is less than PevStartTimeBuffer seconds later than last PEV will be ignored and not counted 10 | 11 | var 12 | Dm, D1, 13 | Dt, n1, n2, n3, n4, N, D0, V0, T0, Hmin, 14 | Pm, Pdm, Pvm, Pn, F, Fcr, Day, Sp, Sp0, Spm, SumSp, SpMedian, DevaluateDay: Double; 15 | 16 | D, H, Dh, M, T, Dc, Pd, V, Vh, Pv, S : double; 17 | 18 | PmaxDistance, PmaxTime : double; 19 | 20 | i,j : integer; 21 | PevWaitTime,PEVStartWindow,AllUserWrng, PilotStartInterval, PilotStartTime, PilotPEVStartTime,StartTimeBuffer,MaxStartSpeed,nSp0 : Integer; 22 | AAT : boolean; 23 | Auto_Hcaps_on : boolean; 24 | 25 | // Starttime calculation and PEV Warnings 26 | PilotStartSpeed, PilotStartSpeedSum, PilotStartSpeedFixes : double; 27 | ActMarker : TMarker; 28 | PevWarning : String; 29 | Ignore_PEV,PEVStartNotValid : boolean; 30 | Pevcount, LastPev : Integer; 31 | 32 | Function MinValue( a,b,c : double ) : double; 33 | var m : double; 34 | begin 35 | m := a; 36 | if b < m Then m := b; 37 | if c < m Then m := c; 38 | 39 | MinValue := m; 40 | end; 41 | 42 | Function GetTimeString (time: integer) : string; // converts integer time in seconds to "hh:mm:ss" string 43 | var 44 | h,min,sec: Integer; 45 | sth,stmin,stsec:String; 46 | begin 47 | h:=Trunc(time/3600); 48 | min:=Trunc((time-h*3600)/60); 49 | sec:=time-h*3600-min*60; 50 | sth:=IntToStr(h); 51 | if Length(sth)=1 Then sth:='0'+sth; 52 | stmin:=IntToStr(min); 53 | if Length(stmin)=1 Then stmin:='0'+stmin; 54 | stsec:=IntToStr(sec); 55 | if Length(stsec)=1 Then stsec:='0'+stsec; 56 | GetTimeString :=sth+':'+stmin+':'+stsec; 57 | end; 58 | 59 | Function ReadDayTagParameter ( name : string; default : double ) : double; 60 | var 61 | sp, tp : Integer; 62 | sub : string; 63 | begin 64 | sp := Pos(UpperCase(name) + '=',UpperCase(DayTag)); 65 | 66 | if (sp > 0) then 67 | begin 68 | sub:= Copy(DayTag,sp + Length(name) + 1,Length(DayTag)); 69 | 70 | tp := Pos(' ',sub); 71 | if (tp > 0) then 72 | sub:= Copy(sub,0,tp-1); 73 | 74 | tp := Pos(',',sub); 75 | if (tp > 0) then 76 | sub := Copy (sub,0,tp-1) + '.' + Copy (sub,tp+1,Length(sub)); 77 | ReadDayTagParameter := StrToFloat(sub); 78 | end 79 | else 80 | ReadDayTagParameter := default; // string not found 81 | end; 82 | 83 | 84 | begin 85 | 86 | // initial checks 87 | if GetArrayLength(Pilots) <= 1 then 88 | exit; 89 | 90 | if (UseHandicaps < 0) OR (UseHandicaps > 2) then 91 | begin 92 | Info1 := ''; 93 | Info2 := 'ERROR: constant UseHandicaps is set wrong'; 94 | exit; 95 | end; 96 | 97 | if Task.TaskTime = 0 then 98 | AAT := false 99 | else 100 | AAT := true; 101 | 102 | if (AAT = true) AND (Task.TaskTime < 1800) then 103 | begin 104 | Info1 := ''; 105 | Info2 := 'ERROR: Incorrect Task Time'; 106 | exit; 107 | end; 108 | 109 | 110 | // Minimum Distance to validate the Day, depending on the class [meters] 111 | Dm := 100000; 112 | if Task.ClassID = 'club' Then Dm := 100000; 113 | if Task.ClassID = '13_5_meter' Then Dm := 100000; 114 | if Task.ClassID = 'standard' Then Dm := 120000; 115 | if Task.ClassID = '15_meter' Then Dm := 120000; 116 | if Task.ClassID = 'double_seater' Then Dm := 120000; 117 | if Task.ClassID = '18_meter' Then Dm := 140000; 118 | if Task.ClassID = 'open' Then Dm := 140000; 119 | 120 | // Minimum distance for 1000 points, depending on the class [meters] 121 | D1 := 250000; 122 | if Task.ClassID = 'club' Then D1 := 250000; 123 | if Task.ClassID = '13_5_meter' Then D1 := 250000; 124 | if Task.ClassID = 'standard' Then D1 := 300000; 125 | if Task.ClassID = '15_meter' Then D1 := 300000; 126 | if Task.ClassID = 'double_seater' Then D1 := 300000; 127 | if Task.ClassID = '18_meter' Then D1 := 350000; 128 | if Task.ClassID = 'open' Then D1 := 350000; 129 | 130 | // Handicaps for club and 20m multi-seat and unknown (formerly 'mixed') class 131 | Auto_Hcaps_on := false; 132 | if Task.ClassID = 'club' Then Auto_Hcaps_on := true; 133 | if Task.ClassID = 'double_seater' Then Auto_Hcaps_on := true; 134 | if Task.ClassID = 'unknown' Then Auto_Hcaps_on := true; 135 | 136 | // PEV Start PROCEDURE 137 | // Read PEV Gate Parameters from DayTag. Return zero PEVWaitTime or PEVStartWindow are unparsable or missing 138 | 139 | StartTimeBuffer:=30; // Start time buffer zone. if one starts 30 seconds too early he is scored by his actual start time 140 | PEVWaitTime := Trunc(ReadDayTagParameter('PEVWAITTIME',0)) * 60; // WaitTime in seconds 141 | PEVStartWindow := Trunc(ReadDayTagParameter('PEVSTARTWINDOW',0))* 60; // StartWindow open in seconds 142 | MaxStartSpeed := Trunc(ReadDayTagParameter('MAXSTSPD',0)); // Startspeed interpolation done if MaxStartSpeed (in km/h) >0 143 | AllUserWrng := Trunc(ReadDayTagParameter('ALLUSERWRNG',1)); // Output of All UserWarnings with PEVs: ON=1(for debugging and testing) OFF=0 144 | 145 | // if DayTag variables PEVWaitTime and PEVStartWindow are set (>0) then PEV Marker Start Warnings are shown 146 | if (PEVWaitTime > 0) and (PEVStartWindow> 0) then // Only display number of intervals if it is not zero 147 | begin 148 | Info3 :='PEVWaitTime: '+IntToStr(PevWaitTime div 60)+'min, PEVStartWindow: '+IntToStr(PevStartWindow div 60)+'min, '; 149 | end 150 | else 151 | begin 152 | Info3:='PEVStarts: OFF, '; 153 | PEVWaitTime:=0; 154 | PEVStartWindow:=0; 155 | end; 156 | 157 | // Calculation of basic parameters 158 | N := 0; // Number of pilots having had a competition launch 159 | n1 := 0; // Number of pilots with Marking distance greater than Dm - normally 100km 160 | n2 := 0; // Number of competitors who have achieved at least 2/3 of best speed for the day V0 161 | n3 := 0; // Number of finishers, regardless of speed 162 | Hmin := 100000; // Lowest Handicap of all competitors in the class 163 | 164 | for i:=0 to GetArrayLength(Pilots)-1 do 165 | begin 166 | if UseHandicaps = 0 Then Pilots[i].Hcap := 1; 167 | if (UseHandicaps = 2) and (Auto_Hcaps_on = false) Then Pilots[i].Hcap := 1; 168 | 169 | if not Pilots[i].isHC Then 170 | begin 171 | if Pilots[i].Hcap < Hmin Then Hmin := Pilots[i].Hcap; // Lowest Handicap of all competitors in the class 172 | end; 173 | end; 174 | 175 | // Annex A version 2022 has removed the capability of Hmin in the results. Simply removing Hmin doesn't work for comps where Handicaps are given as 108, 125 etc. Hence this addition. 176 | if Hmin >= 500 then Hmin = 1000; // Not sure if there are any comps that uses Annex A rules with Handicaps over 10000? 177 | if (Hmin >= 50) and (Hmin < 500) then Hmin := 100; // For comps that use Handicaps typically between 70 and 130 178 | if (Hmin >= 5) and (Hmin < 50) then Hmin := 10; // Just in case 179 | if (Hmin >= 0.5) and (Hmin < 5) then Hmin := 1; // Typical IGC Annex A comps with handicaps around 1.000 180 | if (Hmin >= 0) and (Hmin < 0.5) then Hmin := Hmin; // Just in case 181 | 182 | for i:=0 to GetArrayLength(Pilots)-1 do 183 | begin 184 | if not Pilots[i].isHC Then 185 | begin 186 | if Pilots[i].dis*Hmin/Pilots[i].Hcap >= Dm Then n1 := n1+1; // Competitors who have achieved at least Dm 187 | if Pilots[i].finish > 0 Then n3 := n3+1; // Competitors who completed the task, regardless of speed 188 | if Pilots[i].takeoff >= 0 Then N := N+1; // Number of competitors in the class having had a competition launch that Day 189 | end; 190 | end; 191 | if N=0 Then begin 192 | Info1 := ''; 193 | Info2 := 'Warning: Number of competition pilots launched is zero'; 194 | Exit; 195 | end; 196 | 197 | D0 := 0; 198 | T0 := 0; 199 | V0 := 0; 200 | for i:=0 to GetArrayLength(Pilots)-1 do 201 | begin 202 | if not Pilots[i].isHC Then 203 | begin 204 | // Find the highest Corrected distance 205 | if Pilots[i].dis*Hmin/Pilots[i].Hcap > D0 Then D0 := Pilots[i].dis*Hmin/Pilots[i].Hcap; 206 | 207 | // Find the highest finisher's speed of the day 208 | // and corresponding Task Time 209 | if Pilots[i].speed*Hmin/Pilots[i].Hcap = V0 Then // in case of a tie, lowest Task Time applies 210 | begin 211 | if (Pilots[i].finish-Pilots[i].start) < T0 Then 212 | begin 213 | V0 := Pilots[i].speed*Hmin/Pilots[i].Hcap; 214 | T0 := Pilots[i].finish-Pilots[i].start; 215 | end; 216 | end 217 | else 218 | begin 219 | if Pilots[i].speed*Hmin/Pilots[i].Hcap > V0 Then 220 | begin 221 | V0 := Pilots[i].speed*Hmin/Pilots[i].Hcap; 222 | T0 := Pilots[i].finish-Pilots[i].start; 223 | if (AAT = true) and (T0 < Task.TaskTime) Then // if marking time is shorter than Task time, Task time must be used for computations 224 | T0 := Task.TaskTime; 225 | end; 226 | end; 227 | end; 228 | end; 229 | 230 | if D0=0 Then begin 231 | Info1 := ''; 232 | Info2 := 'Warning: Longest handicapped distance is zero'; 233 | Exit; 234 | end; 235 | 236 | // Maximum available points for the Day 237 | PmaxDistance := 1250 * (D0/D1) - 250; 238 | PmaxTime := (400*T0/3600.0)-200; 239 | if T0 <= 0 Then PmaxTime := 1000; 240 | Pm := MinValue( PmaxDistance, PmaxTime, 1000.0 ); 241 | 242 | // Day Factor 243 | F := Pm/1000; 244 | if F>1 Then F := 1; 245 | 246 | // Completion Ratio Factor 247 | Fcr := 1; 248 | if n1 > 0 then 249 | Fcr := 1.2*(n3/n1)+0.6; 250 | if Fcr>1 Then Fcr := 1; 251 | 252 | 253 | // Competitor's provisional score 254 | Sp0 := 0; // Highest provisional score of the day 255 | 256 | for i:=0 to GetArrayLength(Pilots)-1 do 257 | begin 258 | // For any finisher 259 | if Pilots[i].finish > 0 Then 260 | begin 261 | Pv := 1000 * (Pilots[i].speed*Hmin/Pilots[i].Hcap / V0); 262 | Pd := 750 * (Pilots[i].dis*Hmin/Pilots[i].Hcap/D0); 263 | end 264 | else //For any non-finisher 265 | begin 266 | Pv := 0; 267 | Pd := 750 * (Pilots[i].dis*Hmin/Pilots[i].Hcap/D0); 268 | end; 269 | 270 | // Points for each pilot 271 | if Pv > Pd Then 272 | begin 273 | Pilots[i].Points := Round( F*Fcr*Pv - Pilots[i].Penalty ); 274 | end 275 | else // Pd is more than Pv 276 | begin 277 | Pilots[i].Points := Round( F*Fcr*Pd - Pilots[i].Penalty ); 278 | end; 279 | 280 | // Sp0 - maximum provisional score for the day 281 | if Pilots[i].Points > Sp0 Then 282 | begin 283 | Sp0 := Pilots[i].Points; 284 | end; 285 | end; 286 | 287 | // Find median provisional score 288 | // This is not median, it is mean because this script doesn't have access to Sort algorithm. A sort function is needed to fix this 289 | SumSp := 0; 290 | nSp0 := 0; // Number of competitors with provisional score more than zero 291 | for i:=0 to GetArrayLength(Pilots)-1 do 292 | begin 293 | if Pilots[i].Points > 0 Then 294 | begin 295 | SumSp := SumSp+Pilots[i].Points; 296 | nSp0 := nSp0+1 297 | end; 298 | end; 299 | if (SumSp > 0) and (nSp0 > 0) Then 300 | SpMedian := SumSp/nSp0; 301 | if (Sp0-SpMedian) > 200 Then 302 | DevaluateDay := MinValue( 1,1,200/(Sp0-SpMedian) ) 303 | else 304 | DevaluateDay := 1; 305 | 306 | // Assign score to each pilot 307 | for i:=0 to GetArrayLength(Pilots)-1 do 308 | begin 309 | Pilots[i].Points := DevaluateDay * Pilots[i].Points; 310 | end; 311 | 312 | // Data which is presented in the score-sheets 313 | for i:=0 to GetArrayLength(Pilots)-1 do 314 | begin 315 | Pilots[i].sstart:=Pilots[i].start; 316 | Pilots[i].sfinish:=Pilots[i].finish; 317 | Pilots[i].sdis:=Pilots[i].dis; 318 | Pilots[i].sspeed:=Pilots[i].speed; 319 | end; 320 | 321 | // Info fields, also presented on the Score Sheets 322 | if AAT = true Then 323 | Info1 := 'Assigned Area Task, ' 324 | else 325 | Info1 := 'Racing Task, '; 326 | 327 | Info1 := Info1 + 'Maximum Points: '+IntToStr(Round(Pm)); 328 | Info1 := Info1 + ', F = '+FormatFloat('0.000',F); 329 | Info1 := Info1 + ', Fcr = '+FormatFloat('0.000',Fcr); 330 | Info1 := Info1 + ', DevaluateDay = '+FormatFloat('0.000',DevaluateDay); 331 | 332 | if (n1/N) <= 0.25 then 333 | Info1 := 'Day not valid - rule 8.2.1b'; 334 | 335 | Info2 := 'Dm = ' + IntToStr(Round(Dm/1000.0)) + 'km'; 336 | Info2 := Info2 + ', D1 = ' + IntToStr(Round(D1/1000.0)) + 'km'; 337 | if (UseHandicaps = 0) or ((UseHandicaps = 2) and (Auto_Hcaps_on = false)) Then 338 | Info2 := Info2 + ', no handicaps' 339 | else 340 | Info2 := Info2 + ', handicapping enabled'; 341 | 342 | // for debugging: 343 | Info3 := Info3 +' N: ' + IntToStr(Round(N)); 344 | Info3 := Info3 + ', n1: ' + IntToStr(Round(n1)); 345 | Info3 := Info3 + ', n3: ' + IntToStr(Round(n3)); 346 | Info3 := Info3 + ', Do: ' + FormatFloat('0.00',D0/1000.0) + 'km'; 347 | Info3 := Info3 + ', Vo: ' + FormatFloat('0.00',V0*3.6) + 'km/h'; 348 | 349 | // Give out PEV as Warnings 350 | // PevStartTimeBuffer is set to 30 351 | 352 | for i:=0 to GetArrayLength(Pilots)-1 do 353 | begin 354 | Pilots[i].Warning := ''; 355 | if (Pilots[i].start > 0) Then 356 | begin 357 | if (PEVWaitTime>0) and (PEVStartWindow>0) then 358 | begin 359 | PevWarning:=''; 360 | PevCount:=0; LastPev:=0; 361 | Ignore_PEV:=false; 362 | 363 | for j:=0 to GetArrayLength(Pilots[i].Markers)-1 do 364 | begin 365 | Ignore_Pev:= ((Pilots[i].Markers[j].Tsec-LastPev<=PevStartTimeBuffer) and (Lastpev>0)) or (Pevcount=3); // TODO: Also ignore PEV if PEV time is greater than start time. 366 | if Ignore_Pev Then 367 | begin 368 | if (ALLUserWrng>=1)Then PevWarning := PevWarning + ' (PEV ignored='+ GetTimestring(Pilots[i].Markers[j].Tsec) +'!), ' 369 | end 370 | else 371 | begin 372 | PevCount:=PevCount+1; 373 | LastPev:= Pilots[i].Markers[j].Tsec; 374 | if (AllUserWrng>=1) Then PevWarning := PevWarning + 'PEV'+IntTostr(Pevcount)+'='+ GetTimestring(Pilots[i].Markers[j].Tsec)+', '; 375 | end; 376 | end; 377 | 378 | if PEVCount>0 Then 379 | begin 380 | PevStartNotValid:=(Trunc(Pilots[i].Start)<(LastPEV+PEVWaitTime)) or (Trunc(Pilots[i].Start)>(LastPEV+PEVWaitTime+PEVStartWindow)); 381 | if PevStartNotValid Then 382 | PEVWarning:=PevWarning+' Start='+GetTimestring(Trunc(Pilots[i].Start))+' PEVGate not open!'+', ' 383 | else 384 | if (Pilots[i].start>=Task.NoStartBeforeTime) and (AllUserWrng>=1) Then 385 | PEVWarning:=PevWarning+' Start='+GetTimestring(Trunc(Pilots[i].Start))+' OK'+', '; 386 | Pilots[i].Warning:= PevWarning; 387 | end 388 | else 389 | PEVWarning:='PEV not found!'+', '; 390 | 391 | Pilots[i].Warning:= PevWarning; 392 | end; 393 | if Pilots[i].start 0 to if Pilots[i].takeoff >= 0. It is theoretically possible that one takes off at 00:00:00 UTC 58 | // . Changed if Pilots[i].start > 0 to if Pilots[i].start >= 0. It is theoretically possible that one starts at 00:00:00 UTC 59 | // Version 3.10 60 | // . removed line because it doesn't exist in Annex A 2006: 61 | // if Pilots[i].dis*Hmin/Pilots[i].Hcap < (2.0/3.0*D0) Then Pd := Pdm*Pilots[i].dis*Hmin/Pilots[i].Hcap/(2.0/3.0*D0); 62 | // Version 3.20 63 | // . added warnings when Exit 64 | 65 | const UseHandicaps = 2; // set to: 0 to disable handicapping, 1 to use handicaps, 2 is auto (handicaps only for club and multi-seat) 66 | PevStartTimeBuffer = 30; // PEV which is less than PevStartTimeBuffer seconds later than last PEV will be ignored and not counted 67 | 68 | var 69 | Dm, D1, 70 | Dt, n1, n2, n3, n4, N, D0, V0, T0, Hmin, 71 | Pm, Pdm, Pvm, Pn, F, Fcr, Day: Double; 72 | 73 | D, H, Dh, M, T, Dc, Pd, V, Vh, Pv, S : double; 74 | 75 | PmaxDistance, PmaxTime : double; 76 | 77 | i,j : integer; 78 | PevWaitTime,PEVStartWindow,AllUserWrng, PilotStartInterval, PilotStartTime, PilotPEVStartTime,StartTimeBuffer,MaxStartSpeed : Integer; 79 | AAT : boolean; 80 | Auto_Hcaps_on : boolean; 81 | 82 | // Starttime calculation and PEV Warnings 83 | PilotStartSpeed, PilotStartSpeedSum, PilotStartSpeedFixes : double; 84 | ActMarker : TMarker; 85 | PevWarning : String; 86 | Ignore_PEV,PEVStartNotValid : boolean; 87 | Pevcount, LastPev : Integer; 88 | 89 | //Prestart Altitude 90 | PreStartAltLimit, NbrFixes, MinPreStartAltTime : Integer; 91 | MinPreStartAlt : Double; 92 | PreStartInfo : string; 93 | PreStartLimitOK : boolean; 94 | 95 | //below altitude on task 96 | BelowAltFound : boolean; 97 | FixDuration, LaunchAboveAltFix, LastFixTime, FGAboveAltFix, LowPointTsec : Integer; 98 | MinimumAlt, LowPoint : Double; 99 | 100 | Function MinValue( a,b,c : double ) : double; 101 | var m : double; 102 | begin 103 | m := a; 104 | if b < m Then m := b; 105 | if c < m Then m := c; 106 | 107 | MinValue := m; 108 | end; 109 | 110 | Function GetTimeString (time: integer) : string; // converts integer time in seconds to "hh:mm:ss" string 111 | var 112 | h,min,sec: Integer; 113 | sth,stmin,stsec:String; 114 | begin 115 | h:=Trunc(time/3600); 116 | min:=Trunc((time-h*3600)/60); 117 | sec:=time-h*3600-min*60; 118 | sth:=IntToStr(h); 119 | if Length(sth)=1 Then sth:='0'+sth; 120 | stmin:=IntToStr(min); 121 | if Length(stmin)=1 Then stmin:='0'+stmin; 122 | stsec:=IntToStr(sec); 123 | if Length(stsec)=1 Then stsec:='0'+stsec; 124 | GetTimeString :=sth+':'+stmin+':'+stsec; 125 | end; 126 | 127 | Function ReadDayTagParameter ( name : string; default : double ) : double; 128 | var 129 | sp, tp : Integer; 130 | sub : string; 131 | begin 132 | sp := Pos(UpperCase(name) + '=',UpperCase(DayTag)); 133 | 134 | if (sp > 0) then 135 | begin 136 | sub:= Copy(DayTag,sp + Length(name) + 1,Length(DayTag)); 137 | 138 | tp := Pos(' ',sub); 139 | if (tp > 0) then 140 | sub:= Copy(sub,0,tp-1); 141 | 142 | tp := Pos(',',sub); 143 | if (tp > 0) then 144 | sub := Copy (sub,0,tp-1) + '.' + Copy (sub,tp+1,Length(sub)); 145 | ReadDayTagParameter := StrToFloat(sub); 146 | end 147 | else 148 | ReadDayTagParameter := default; // string not found 149 | end; 150 | 151 | // Main Code 152 | begin 153 | // initial checks 154 | if GetArrayLength(Pilots) <= 1 then 155 | exit; 156 | 157 | if (UseHandicaps < 0) OR (UseHandicaps > 2) then 158 | begin 159 | Info1 := ''; 160 | Info2 := 'ERROR: constant UseHandicaps is set wrong'; 161 | exit; 162 | end; 163 | 164 | if Task.TaskTime = 0 then 165 | AAT := false 166 | else 167 | AAT := true; 168 | 169 | if (AAT = true) AND (Task.TaskTime < 1800) then 170 | begin 171 | Info1 := ''; 172 | Info2 := 'ERROR: Incorrect Task Time'; 173 | exit; 174 | end; 175 | 176 | 177 | // Minimum Distance to validate the Day, depending on the class [meters] 178 | Dm := 100000; 179 | if Task.ClassID = 'club' Then Dm := 100000; 180 | if Task.ClassID = '13_5_meter' Then Dm := 100000; 181 | if Task.ClassID = 'standard' Then Dm := 120000; 182 | if Task.ClassID = '15_meter' Then Dm := 120000; 183 | if Task.ClassID = 'double_seater' Then Dm := 120000; 184 | if Task.ClassID = '18_meter' Then Dm := 140000; 185 | if Task.ClassID = 'open' Then Dm := 140000; 186 | 187 | // Minimum distance for 1000 points, depending on the class [meters] 188 | D1 := 250000; 189 | if Task.ClassID = 'club' Then D1 := 250000; 190 | if Task.ClassID = '13_5_meter' Then D1 := 250000; 191 | if Task.ClassID = 'standard' Then D1 := 300000; 192 | if Task.ClassID = '15_meter' Then D1 := 300000; 193 | if Task.ClassID = 'double_seater' Then D1 := 300000; 194 | if Task.ClassID = '18_meter' Then D1 := 350000; 195 | if Task.ClassID = 'open' Then D1 := 350000; 196 | 197 | // Handicaps for club and 20m multi-seat and unknown (formerly 'mixed') class 198 | Auto_Hcaps_on := false; 199 | if Task.ClassID = 'club' Then Auto_Hcaps_on := true; 200 | if Task.ClassID = 'double_seater' Then Auto_Hcaps_on := true; 201 | if Task.ClassID = 'unknown' Then Auto_Hcaps_on := true; 202 | 203 | // PEV Start PROCEDURE 204 | // Read PEV Gate Parameters from DayTag. Return zero PEVWaitTime or PEVStartWindow are unparsable or missing 205 | 206 | StartTimeBuffer:=30; // Start time buffer zone. if one starts 30 seconds too early he is scored by his actual start time 207 | PEVWaitTime := Trunc(ReadDayTagParameter('PEVWAITTIME',0)) * 60; // WaitTime in seconds 208 | PEVStartWindow := Trunc(ReadDayTagParameter('PEVSTARTWINDOW',0))* 60; // StartWindow open in seconds 209 | MaxStartSpeed := Trunc(ReadDayTagParameter('MAXSTSPD',0)); // Startspeed interpolation done if MaxStartSpeed (in km/h) >0 210 | AllUserWrng := Trunc(ReadDayTagParameter('ALLUSERWRNG',1)); // Output of All UserWarnings with PEVs: ON=1(for debugging and testing) OFF=0 211 | 212 | // if DayTag variables PEVWaitTime and PEVStartWindow are set (>0) then PEV Marker Start Warnings are shown 213 | if (PEVWaitTime > 0) and (PEVStartWindow> 0) then // Only display number of intervals if it is not zero 214 | begin 215 | Info3 :='PEVWaitTime: '+IntToStr(PevWaitTime div 60)+'min, PEVStartWindow: '+IntToStr(PevStartWindow div 60)+'min, '; 216 | end 217 | else 218 | begin 219 | Info3:='PEVStarts: OFF, '; 220 | PEVWaitTime:=0; 221 | PEVStartWindow:=0; 222 | end; 223 | 224 | // Prestart Altitude 225 | PreStartAltLimit := Trunc(ReadDayTagParameter('PRESTARTALT',0)); // Prestart altitude in m >0 226 | if PreStartAltLimit > 0 then 227 | begin 228 | Info3 := Info3 + 'PreStart Alt = '+IntToStr(PreStartAltLimit)+'m, '; 229 | end; 230 | 231 | 232 | // altitude less than minimum altitude 233 | MinimumAlt := Trunc(ReadDayTagParameter('MINIMUMALT',0)); 234 | if MinimumAlt > 0 then 235 | begin 236 | Info3 := Info3 + 'Minimum Alt = '+FloatToStr(MinimumAlt)+'m, '; 237 | end; 238 | 239 | 240 | // Calculation of basic parameters 241 | N := 0; // Number of pilots having had a competition launch 242 | n1 := 0; // Number of pilots with Marking distance greater than Dm - normally 100km 243 | n4 := 0; // Number of competitors who achieve a Handicapped Distance (Dh) of at least Dm/2 244 | Hmin := 100000; // Lowest Handicap of all competitors in the class 245 | 246 | for i:=0 to GetArrayLength(Pilots)-1 do 247 | begin 248 | if UseHandicaps = 0 Then Pilots[i].Hcap := 1; 249 | if (UseHandicaps = 2) and (Auto_Hcaps_on = false) Then Pilots[i].Hcap := 1; 250 | 251 | if not Pilots[i].isHC Then 252 | begin 253 | if Pilots[i].Hcap < Hmin Then Hmin := Pilots[i].Hcap; // Lowest Handicap of all competitors in the class 254 | end; 255 | end; 256 | 257 | // Annex A version 2022 has removed the capability of Hmin in the results. Simply removing Hmin doesn't work for comps where Handicaps are given as 108, 125 etc. Hence this addition. 258 | if Hmin >= 500 then Hmin = 1000; // Not sure if there are any comps that uses Annex A rules with Handicaps over 10000? 259 | if (Hmin >= 50) and (Hmin < 500) then Hmin := 100; // For comps that use Handicaps typically between 70 and 130 260 | if (Hmin >= 5) and (Hmin < 50) then Hmin := 10; // Just in case 261 | if (Hmin >= 0.5) and (Hmin < 5) then Hmin := 1; // Typical IGC Annex A comps with handicaps around 1.000 262 | if (Hmin >= 0) and (Hmin < 0.5) then Hmin := Hmin; // Just in case 263 | 264 | for i:=0 to GetArrayLength(Pilots)-1 do 265 | begin 266 | if not Pilots[i].isHC Then 267 | begin 268 | if (Pilots[i].Hcap = 0) and (UseHandicaps <> 0) then 269 | begin 270 | info1 := 'Warning: Glider ' + Pilots[i].compID + ' ihas a hadicap 0, fix handicaps or set in script UseHandicaps=0'; 271 | exit; 272 | end; 273 | if Pilots[i].dis*Hmin/Pilots[i].Hcap >= Dm Then n1 := n1+1; // Competitors who have achieved at least Dm 274 | if Pilots[i].dis*Hmin/Pilots[i].Hcap >= ( Dm / 2.0) Then n4 := n4+1; // Number of competitors who achieve a Handicapped Distance (Dh) of at least Dm/2 275 | if Pilots[i].takeoff >= 0 Then N := N+1; // Number of competitors in the class having had a competition launch that Day 276 | end; 277 | end; 278 | if N=0 Then begin 279 | Info1 := ''; 280 | Info2 := 'Warning: Number of competition pilots launched is zero'; 281 | Exit; 282 | end; 283 | 284 | D0 := 0; 285 | T0 := 0; 286 | V0 := 0; 287 | for i:=0 to GetArrayLength(Pilots)-1 do 288 | begin 289 | if not Pilots[i].isHC Then 290 | begin 291 | // Find the highest Corrected distance 292 | if Pilots[i].dis*Hmin/Pilots[i].Hcap > D0 Then D0 := Pilots[i].dis*Hmin/Pilots[i].Hcap; 293 | 294 | // Find the highest finisher's speed of the day 295 | // and corresponding Task Time 296 | if Pilots[i].speed*Hmin/Pilots[i].Hcap = V0 Then // in case of a tie, lowest Task Time applies 297 | begin 298 | if (Pilots[i].finish-Pilots[i].start) < T0 Then 299 | begin 300 | V0 := Pilots[i].speed*Hmin/Pilots[i].Hcap; 301 | T0 := Pilots[i].finish-Pilots[i].start; 302 | end; 303 | end 304 | else 305 | begin 306 | if Pilots[i].speed*Hmin/Pilots[i].Hcap > V0 Then 307 | begin 308 | V0 := Pilots[i].speed*Hmin/Pilots[i].Hcap; 309 | T0 := Pilots[i].finish-Pilots[i].start; 310 | if (AAT = true) and (T0 < Task.TaskTime) Then // if marking time is shorter than Task time, Task time must be used for computations 311 | T0 := Task.TaskTime; 312 | end; 313 | end; 314 | end; 315 | end; 316 | 317 | if D0=0 Then begin 318 | Info1 := ''; 319 | Info2 := 'Warning: Longest handicapped distance is zero'; 320 | Exit; 321 | end; 322 | 323 | // Maximum available points for the Day 324 | PmaxDistance := 1250 * (D0/D1) - 250; 325 | PmaxTime := (400*T0/3600.0)-200; 326 | if T0 <= 0 Then PmaxTime := 1000; 327 | Pm := MinValue( PmaxDistance, PmaxTime, 1000.0 ); 328 | 329 | // Day Factor 330 | F := 1.25* n1/N; 331 | if F>1 Then F := 1; 332 | 333 | // Number of competitors who have achieved at least 2/3 of best speed for the day V0 334 | n2 := 0; 335 | // Number of finishers, regardless of speed 336 | n3 := 0; 337 | 338 | for i:=0 to GetArrayLength(Pilots)-1 do 339 | begin 340 | if not Pilots[i].isHC Then 341 | begin 342 | // n3 to count only finishers... 343 | if Pilots[i].finish > 0 Then n3 := n3+1; 344 | if Pilots[i].speed*Hmin/Pilots[i].Hcap > (2.0/3.0*V0) Then 345 | begin 346 | n2 := n2+1; 347 | end; 348 | end; 349 | end; 350 | 351 | // Completion Ratio Factor 352 | Fcr := 1; 353 | if n1 > 0 then 354 | Fcr := 1.2*(n2/n1)+0.6; 355 | if Fcr>1 Then Fcr := 1; 356 | 357 | Pvm := 2.0/3.0 * (n2/N) * Pm; // maximum available Speed Points for the Day 358 | Pdm := Pm-Pvm; // maximum available Distance Points for the Day 359 | 360 | for i:=0 to GetArrayLength(Pilots)-1 do 361 | begin 362 | // For any finisher 363 | if Pilots[i].finish > 0 Then 364 | begin 365 | Pv := Pvm * (Pilots[i].speed*Hmin/Pilots[i].Hcap - 2.0/3.0*V0)/(1.0/3.0*V0); 366 | if Pilots[i].speed*Hmin/Pilots[i].Hcap < (2.0/3.0*V0) Then Pv := 0; 367 | Pd := Pdm; 368 | end 369 | else 370 | //For any non-finisher 371 | begin 372 | Pv := 0; 373 | Pd := Pdm * (Pilots[i].dis*Hmin/Pilots[i].Hcap/D0); 374 | end; 375 | 376 | // Pilot's score 377 | Pilots[i].Points := Round( F*Fcr*(Pd+Pv) - Pilots[i].Penalty ); 378 | end; 379 | 380 | // Data which is presented in the score-sheets 381 | for i:=0 to GetArrayLength(Pilots)-1 do 382 | begin 383 | Pilots[i].sstart:=Pilots[i].start; 384 | Pilots[i].sfinish:=Pilots[i].finish; 385 | Pilots[i].sdis:=Pilots[i].dis; 386 | Pilots[i].sspeed:=Pilots[i].speed; 387 | end; 388 | 389 | // Info fields, also presented on the Score Sheets 390 | if AAT = true Then 391 | Info1 := 'Assigned Area Task, ' 392 | else 393 | Info1 := 'Racing Task, '; 394 | 395 | Info1 := Info1 + 'Maximum Points: '+IntToStr(Round(Pm)); 396 | Info1 := Info1 + ', F = '+FormatFloat('0.000',F); 397 | Info1 := Info1 + ', Fcr = '+FormatFloat('0.000',Fcr); 398 | Info1 := Info1 + ', Max speed pts: '+IntToStr(Round(Pvm)); 399 | 400 | if (n1/N) <= 0.25 then 401 | Info1 := 'Day not valid - rule 8.2.1b'; 402 | 403 | Info2 := 'Dm = ' + IntToStr(Round(Dm/1000.0)) + 'km'; 404 | Info2 := Info2 + ', D1 = ' + IntToStr(Round(D1/1000.0)) + 'km'; 405 | if (UseHandicaps = 0) or ((UseHandicaps = 2) and (Auto_Hcaps_on = false)) Then 406 | Info2 := Info2 + ', no handicaps' 407 | else 408 | Info2 := Info2 + ', handicapping enabled'; 409 | 410 | // for debugging: 411 | Info3 := Info3 +' N: ' + IntToStr(Round(N)); 412 | Info3 := Info3 + ', n1: ' + IntToStr(Round(n1)); 413 | Info3 := Info3 + ', n2: ' + IntToStr(Round(n2)); 414 | Info3 := Info3 + ', Do: ' + FormatFloat('0.00',D0/1000.0) + 'km'; 415 | Info3 := Info3 + ', Vo: ' + FormatFloat('0.00',V0*3.6) + 'km/h'; 416 | 417 | // Give out PEV as Warnings 418 | // PevStartTimeBuffer is set to 30 419 | 420 | for i:=0 to GetArrayLength(Pilots)-1 do 421 | begin 422 | Pilots[i].Warning := ''; 423 | if (Pilots[i].start > 0) Then 424 | begin 425 | if (PEVWaitTime>0) and (PEVStartWindow>0) then 426 | begin 427 | PevWarning:=''; 428 | PevCount:=0; LastPev:=0; 429 | Ignore_PEV:=false; 430 | 431 | for j:=0 to GetArrayLength(Pilots[i].Markers)-1 do 432 | begin 433 | Ignore_Pev:= ((Pilots[i].Markers[j].Tsec-LastPev<=PevStartTimeBuffer) and (Lastpev>0)) or (Pevcount=3) or (Pilots[i].Markers[j].Tsec > Pilots[i].Start); 434 | if Ignore_Pev Then 435 | begin 436 | if (ALLUserWrng>=1)Then PevWarning := PevWarning + ' (PEV ignored='+ GetTimestring(Pilots[i].Markers[j].Tsec) +'!), ' 437 | end 438 | else 439 | begin 440 | PevCount:=PevCount+1; 441 | LastPev:= Pilots[i].Markers[j].Tsec; 442 | if (AllUserWrng>=1) Then PevWarning := PevWarning + 'PEV'+IntTostr(Pevcount)+'='+ GetTimestring(Pilots[i].Markers[j].Tsec)+', '; 443 | end; 444 | end; 445 | 446 | if PEVCount>0 Then 447 | begin 448 | PevStartNotValid:=(Trunc(Pilots[i].Start)<(LastPEV+PEVWaitTime)) or (Trunc(Pilots[i].Start)>(LastPEV+PEVWaitTime+PEVStartWindow)); 449 | if PevStartNotValid Then 450 | PEVWarning:=PevWarning+' Start='+GetTimestring(Trunc(Pilots[i].Start))+' PEVGate not open!'+', ' 451 | else 452 | if (Pilots[i].start>=Task.NoStartBeforeTime) and (AllUserWrng>=1) Then 453 | PEVWarning:=PevWarning+' Start='+GetTimestring(Trunc(Pilots[i].Start))+' OK'+', '; 454 | Pilots[i].Warning:= PevWarning; 455 | end 456 | else 457 | PEVWarning:='PEV not found!'+', '; 458 | 459 | Pilots[i].Warning:= PevWarning; 460 | end; 461 | if Pilots[i].start 0 to if Pilots[i].takeoff >= 0. It is theoretically possible that one takes off at 00:00:00 UTC 58 | // . Changed if Pilots[i].start > 0 to if Pilots[i].start >= 0. It is theoretically possible that one starts at 00:00:00 UTC 59 | // Version 3.10 60 | // . removed line because it doesn't exist in Annex A 2006: 61 | // if Pilots[i].dis*Hmin/Pilots[i].Hcap < (2.0/3.0*D0) Then Pd := Pdm*Pilots[i].dis*Hmin/Pilots[i].Hcap/(2.0/3.0*D0); 62 | // Version 3.20 63 | // . added warnings when Exit 64 | 65 | const UseHandicaps = 2; // set to: 0 to disable handicapping, 1 to use handicaps, 2 is auto (handicaps only for club and multi-seat) 66 | PevStartTimeBuffer = 30; // PEV which is less than PevStartTimeBuffer seconds later than last PEV will be ignored and not counted 67 | 68 | var 69 | Dm, D1, 70 | Dt, n1, n2, n3, n4, N, D0, V0, T0, Hmin, 71 | Pm, Pdm, Pvm, Pn, F, Fcr, Day: Double; 72 | 73 | D, H, Dh, M, T, Dc, Pd, V, Vh, Pv, S : double; 74 | 75 | PmaxDistance, PmaxTime : double; 76 | 77 | i,j : integer; 78 | PevWaitTime,PEVStartWindow,AllUserWrng, PilotStartInterval, PilotStartTime, PilotPEVStartTime,StartTimeBuffer,MaxStartSpeed : Integer; 79 | AAT : boolean; 80 | Auto_Hcaps_on : boolean; 81 | 82 | // Starttime calculation and PEV Warnings 83 | PilotStartSpeed, PilotStartSpeedSum, PilotStartSpeedFixes : double; 84 | ActMarker : TMarker; 85 | PevWarning : String; 86 | Ignore_PEV,PEVStartNotValid : boolean; 87 | Pevcount, LastPev : Integer; 88 | 89 | //Prestart Altitude 90 | PreStartAltLimit, NbrFixes, MinPreStartAltTime : Integer; 91 | MinPreStartAlt : Double; 92 | PreStartInfo : string; 93 | PreStartLimitOK : boolean; 94 | 95 | //below altitude on task 96 | BelowAltFound : boolean; 97 | FixDuration, LaunchAboveAltFix, LastFixTime, FGAboveAltFix, LowPointTsec : Integer; 98 | MinimumAlt, LowPoint : Double; 99 | 100 | Function MinValue( a,b,c : double ) : double; 101 | var m : double; 102 | begin 103 | m := a; 104 | if b < m Then m := b; 105 | if c < m Then m := c; 106 | 107 | MinValue := m; 108 | end; 109 | 110 | Function GetTimeString (time: integer) : string; // converts integer time in seconds to "hh:mm:ss" string 111 | var 112 | h,min,sec: Integer; 113 | sth,stmin,stsec:String; 114 | begin 115 | h:=Trunc(time/3600); 116 | min:=Trunc((time-h*3600)/60); 117 | sec:=time-h*3600-min*60; 118 | sth:=IntToStr(h); 119 | if Length(sth)=1 Then sth:='0'+sth; 120 | stmin:=IntToStr(min); 121 | if Length(stmin)=1 Then stmin:='0'+stmin; 122 | stsec:=IntToStr(sec); 123 | if Length(stsec)=1 Then stsec:='0'+stsec; 124 | GetTimeString :=sth+':'+stmin+':'+stsec; 125 | end; 126 | 127 | Function ReadDayTagParameter ( name : string; default : double ) : double; 128 | var 129 | sp, tp : Integer; 130 | sub : string; 131 | begin 132 | sp := Pos(UpperCase(name) + '=',UpperCase(DayTag)); 133 | 134 | if (sp > 0) then 135 | begin 136 | sub:= Copy(DayTag,sp + Length(name) + 1,Length(DayTag)); 137 | 138 | tp := Pos(' ',sub); 139 | if (tp > 0) then 140 | sub:= Copy(sub,0,tp-1); 141 | 142 | tp := Pos(',',sub); 143 | if (tp > 0) then 144 | sub := Copy (sub,0,tp-1) + '.' + Copy (sub,tp+1,Length(sub)); 145 | ReadDayTagParameter := StrToFloat(sub); 146 | end 147 | else 148 | ReadDayTagParameter := default; // string not found 149 | end; 150 | 151 | // Main Code 152 | begin 153 | // initial checks 154 | if GetArrayLength(Pilots) <= 1 then 155 | exit; 156 | 157 | if (UseHandicaps < 0) OR (UseHandicaps > 2) then 158 | begin 159 | Info1 := ''; 160 | Info2 := 'ERROR: constant UseHandicaps is set wrong'; 161 | exit; 162 | end; 163 | 164 | if Task.TaskTime = 0 then 165 | AAT := false 166 | else 167 | AAT := true; 168 | 169 | if (AAT = true) AND (Task.TaskTime < 1800) then 170 | begin 171 | Info1 := ''; 172 | Info2 := 'ERROR: Incorrect Task Time'; 173 | exit; 174 | end; 175 | 176 | 177 | // Minimum Distance to validate the Day, depending on the class [meters] 178 | Dm := 100000; 179 | if Task.ClassID = 'club' Then Dm := 100000; 180 | if Task.ClassID = '13_5_meter' Then Dm := 100000; 181 | if Task.ClassID = 'standard' Then Dm := 120000; 182 | if Task.ClassID = '15_meter' Then Dm := 120000; 183 | if Task.ClassID = 'double_seater' Then Dm := 120000; 184 | if Task.ClassID = '18_meter' Then Dm := 140000; 185 | if Task.ClassID = 'open' Then Dm := 140000; 186 | 187 | // Minimum distance for 1000 points, depending on the class [meters] 188 | D1 := 250000; 189 | if Task.ClassID = 'club' Then D1 := 250000; 190 | if Task.ClassID = '13_5_meter' Then D1 := 250000; 191 | if Task.ClassID = 'standard' Then D1 := 300000; 192 | if Task.ClassID = '15_meter' Then D1 := 300000; 193 | if Task.ClassID = 'double_seater' Then D1 := 300000; 194 | if Task.ClassID = '18_meter' Then D1 := 350000; 195 | if Task.ClassID = 'open' Then D1 := 350000; 196 | 197 | // Handicaps for club and 20m multi-seat and unknown (formerly 'mixed') class 198 | Auto_Hcaps_on := false; 199 | if Task.ClassID = 'club' Then Auto_Hcaps_on := true; 200 | if Task.ClassID = 'double_seater' Then Auto_Hcaps_on := true; 201 | if Task.ClassID = 'unknown' Then Auto_Hcaps_on := true; 202 | 203 | // PEV Start PROCEDURE 204 | // Read PEV Gate Parameters from DayTag. Return zero PEVWaitTime or PEVStartWindow are unparsable or missing 205 | 206 | StartTimeBuffer:=30; // Start time buffer zone. if one starts 30 seconds too early he is scored by his actual start time 207 | PEVWaitTime := Trunc(ReadDayTagParameter('PEVWAITTIME',0)) * 60; // WaitTime in seconds 208 | PEVStartWindow := Trunc(ReadDayTagParameter('PEVSTARTWINDOW',0))* 60; // StartWindow open in seconds 209 | MaxStartSpeed := Trunc(ReadDayTagParameter('MAXSTSPD',0)); // Startspeed interpolation done if MaxStartSpeed (in km/h) >0 210 | AllUserWrng := Trunc(ReadDayTagParameter('ALLUSERWRNG',1)); // Output of All UserWarnings with PEVs: ON=1(for debugging and testing) OFF=0 211 | 212 | // if DayTag variables PEVWaitTime and PEVStartWindow are set (>0) then PEV Marker Start Warnings are shown 213 | if (PEVWaitTime > 0) and (PEVStartWindow> 0) then // Only display number of intervals if it is not zero 214 | begin 215 | Info3 :='PEVWaitTime: '+IntToStr(PevWaitTime div 60)+'min, PEVStartWindow: '+IntToStr(PevStartWindow div 60)+'min, '; 216 | end 217 | else 218 | begin 219 | Info3:='PEVStarts: OFF, '; 220 | PEVWaitTime:=0; 221 | PEVStartWindow:=0; 222 | end; 223 | 224 | // Prestart Altitude 225 | PreStartAltLimit := Trunc(ReadDayTagParameter('PRESTARTALT',0)); // Prestart altitude in m >0 226 | if PreStartAltLimit > 0 then 227 | begin 228 | Info3 := Info3 + 'PreStart Alt = '+IntToStr(PreStartAltLimit)+'m, '; 229 | end; 230 | 231 | 232 | // altitude less than minimum altitude 233 | MinimumAlt := Trunc(ReadDayTagParameter('MINIMUMALT',0)); 234 | if MinimumAlt > 0 then 235 | begin 236 | Info3 := Info3 + 'Minimum Alt = '+FloatToStr(MinimumAlt)+'m, '; 237 | end; 238 | 239 | 240 | // Calculation of basic parameters 241 | N := 0; // Number of pilots having had a competition launch 242 | n1 := 0; // Number of pilots with Marking distance greater than Dm - normally 100km 243 | n4 := 0; // Number of competitors who achieve a Handicapped Distance (Dh) of at least Dm/2 244 | Hmin := 100000; // Lowest Handicap of all competitors in the class 245 | 246 | for i:=0 to GetArrayLength(Pilots)-1 do 247 | begin 248 | if UseHandicaps = 0 Then Pilots[i].Hcap := 1; 249 | if (UseHandicaps = 2) and (Auto_Hcaps_on = false) Then Pilots[i].Hcap := 1; 250 | 251 | if not Pilots[i].isHC Then 252 | begin 253 | if Pilots[i].Hcap < Hmin Then Hmin := Pilots[i].Hcap; // Lowest Handicap of all competitors in the class 254 | end; 255 | end; 256 | 257 | // Annex A version 2022 has removed the capability of Hmin in the results. Simply removing Hmin doesn't work for comps where Handicaps are given as 108, 125 etc. Hence this addition. 258 | if Hmin >= 500 then Hmin = 1000; // Not sure if there are any comps that uses Annex A rules with Handicaps over 10000? 259 | if (Hmin >= 50) and (Hmin < 500) then Hmin := 100; // For comps that use Handicaps typically between 70 and 130 260 | if (Hmin >= 5) and (Hmin < 50) then Hmin := 10; // Just in case 261 | if (Hmin >= 0.5) and (Hmin < 5) then Hmin := 1; // Typical IGC Annex A comps with handicaps around 1.000 262 | if (Hmin >= 0) and (Hmin < 0.5) then Hmin := Hmin; // Just in case 263 | 264 | for i:=0 to GetArrayLength(Pilots)-1 do 265 | begin 266 | if not Pilots[i].isHC Then 267 | begin 268 | if (Pilots[i].Hcap = 0) and (UseHandicaps <> 0) then 269 | begin 270 | info1 := 'Warning: Glider ' + Pilots[i].compID + ' ihas a hadicap 0, fix handicaps or set in script UseHandicaps=0'; 271 | exit; 272 | end; 273 | if Pilots[i].dis*Hmin/Pilots[i].Hcap >= Dm Then n1 := n1+1; // Competitors who have achieved at least Dm 274 | if Pilots[i].dis*Hmin/Pilots[i].Hcap >= ( Dm / 2.0) Then n4 := n4+1; // Number of competitors who achieve a Handicapped Distance (Dh) of at least Dm/2 275 | if Pilots[i].takeoff >= 0 Then N := N+1; // Number of competitors in the class having had a competition launch that Day 276 | end; 277 | end; 278 | if N=0 Then begin 279 | Info1 := ''; 280 | Info2 := 'Warning: Number of competition pilots launched is zero'; 281 | Exit; 282 | end; 283 | 284 | D0 := 0; 285 | T0 := 0; 286 | V0 := 0; 287 | for i:=0 to GetArrayLength(Pilots)-1 do 288 | begin 289 | if not Pilots[i].isHC Then 290 | begin 291 | // Find the highest Corrected distance 292 | if Pilots[i].dis*Hmin/Pilots[i].Hcap > D0 Then D0 := Pilots[i].dis*Hmin/Pilots[i].Hcap; 293 | 294 | // Find the highest finisher's speed of the day 295 | // and corresponding Task Time 296 | if Pilots[i].speed*Hmin/Pilots[i].Hcap = V0 Then // in case of a tie, lowest Task Time applies 297 | begin 298 | if (Pilots[i].finish-Pilots[i].start) < T0 Then 299 | begin 300 | V0 := Pilots[i].speed*Hmin/Pilots[i].Hcap; 301 | T0 := Pilots[i].finish-Pilots[i].start; 302 | end; 303 | end 304 | else 305 | begin 306 | if Pilots[i].speed*Hmin/Pilots[i].Hcap > V0 Then 307 | begin 308 | V0 := Pilots[i].speed*Hmin/Pilots[i].Hcap; 309 | T0 := Pilots[i].finish-Pilots[i].start; 310 | if (AAT = true) and (T0 < Task.TaskTime) Then // if marking time is shorter than Task time, Task time must be used for computations 311 | T0 := Task.TaskTime; 312 | end; 313 | end; 314 | end; 315 | end; 316 | 317 | if D0=0 Then begin 318 | Info1 := ''; 319 | Info2 := 'Warning: Longest handicapped distance is zero'; 320 | Exit; 321 | end; 322 | 323 | // Maximum available points for the Day 324 | PmaxDistance := 1250 * (D0/D1) - 250; 325 | PmaxTime := (400*T0/3600.0)-200; 326 | if T0 <= 0 Then PmaxTime := 1000; 327 | Pm := MinValue( PmaxDistance, PmaxTime, 1000.0 ); 328 | 329 | // Day Factor 330 | F := 1.25* n1/N; 331 | if F>1 Then F := 1; 332 | 333 | // Number of competitors who have achieved at least 2/3 of best speed for the day V0 334 | n2 := 0; 335 | // Number of finishers, regardless of speed 336 | n3 := 0; 337 | 338 | for i:=0 to GetArrayLength(Pilots)-1 do 339 | begin 340 | if not Pilots[i].isHC Then 341 | begin 342 | // n3 to count only finishers... 343 | if Pilots[i].finish > 0 Then n3 := n3+1; 344 | if Pilots[i].speed*Hmin/Pilots[i].Hcap > (2.0/3.0*V0) Then 345 | begin 346 | n2 := n2+1; 347 | end; 348 | end; 349 | end; 350 | 351 | // Completion Ratio Factor 352 | Fcr := 1; 353 | if n1 > 0 then 354 | Fcr := 1.2*(n2/n1)+0.6; 355 | if Fcr>1 Then Fcr := 1; 356 | 357 | Pvm := 2.0/3.0 * (n2/N) * Pm; // maximum available Speed Points for the Day 358 | Pdm := Pm-Pvm; // maximum available Distance Points for the Day 359 | 360 | for i:=0 to GetArrayLength(Pilots)-1 do 361 | begin 362 | // For any finisher 363 | if Pilots[i].finish > 0 Then 364 | begin 365 | Pv := Pvm * (Pilots[i].speed*Hmin/Pilots[i].Hcap - 2.0/3.0*V0)/(1.0/3.0*V0); 366 | if Pilots[i].speed*Hmin/Pilots[i].Hcap < (2.0/3.0*V0) Then Pv := 0; 367 | Pd := Pdm; 368 | end 369 | else 370 | //For any non-finisher 371 | begin 372 | Pv := 0; 373 | Pd := Pdm * (Pilots[i].dis*Hmin/Pilots[i].Hcap/D0); 374 | end; 375 | 376 | // Pilot's score 377 | Pilots[i].Points := Round( F*Fcr*(Pd+Pv) - Pilots[i].Penalty ); 378 | end; 379 | 380 | // Data which is presented in the score-sheets 381 | for i:=0 to GetArrayLength(Pilots)-1 do 382 | begin 383 | Pilots[i].sstart:=Pilots[i].start; 384 | Pilots[i].sfinish:=Pilots[i].finish; 385 | Pilots[i].sdis:=Pilots[i].dis; 386 | Pilots[i].sspeed:=Pilots[i].speed; 387 | end; 388 | 389 | // Info fields, also presented on the Score Sheets 390 | if AAT = true Then 391 | Info1 := 'Assigned Area Task, ' 392 | else 393 | Info1 := 'Racing Task, '; 394 | 395 | Info1 := Info1 + 'Maximum Points: '+IntToStr(Round(Pm)); 396 | Info1 := Info1 + ', F = '+FormatFloat('0.000',F); 397 | Info1 := Info1 + ', Fcr = '+FormatFloat('0.000',Fcr); 398 | Info1 := Info1 + ', Max speed pts: '+IntToStr(Round(Pvm)); 399 | 400 | if (n1/N) <= 0.25 then 401 | Info1 := 'Day not valid - rule 8.2.1b'; 402 | 403 | Info2 := 'Dm = ' + IntToStr(Round(Dm/1000.0)) + 'km'; 404 | Info2 := Info2 + ', D1 = ' + IntToStr(Round(D1/1000.0)) + 'km'; 405 | if (UseHandicaps = 0) or ((UseHandicaps = 2) and (Auto_Hcaps_on = false)) Then 406 | Info2 := Info2 + ', no handicaps' 407 | else 408 | Info2 := Info2 + ', handicapping enabled'; 409 | 410 | // for debugging: 411 | Info3 := Info3 +' N: ' + IntToStr(Round(N)); 412 | Info3 := Info3 + ', n1: ' + IntToStr(Round(n1)); 413 | Info3 := Info3 + ', n2: ' + IntToStr(Round(n2)); 414 | Info3 := Info3 + ', Do: ' + FormatFloat('0.00',D0/1000.0) + 'km'; 415 | Info3 := Info3 + ', Vo: ' + FormatFloat('0.00',V0*3.6) + 'km/h'; 416 | 417 | // Give out PEV as Warnings 418 | // PevStartTimeBuffer is set to 30 419 | 420 | for i:=0 to GetArrayLength(Pilots)-1 do 421 | begin 422 | Pilots[i].Warning := ''; 423 | if (Pilots[i].start > 0) Then 424 | begin 425 | if (PEVWaitTime>0) and (PEVStartWindow>0) then 426 | begin 427 | PevWarning:=''; 428 | PevCount:=0; LastPev:=0; 429 | Ignore_PEV:=false; 430 | 431 | for j:=0 to GetArrayLength(Pilots[i].Markers)-1 do 432 | begin 433 | Ignore_Pev:= ((Pilots[i].Markers[j].Tsec-LastPev<=PevStartTimeBuffer) and (Lastpev>0)) or (Pevcount=3) or (Pilots[i].Markers[j].Tsec > Pilots[i].Start); 434 | if Ignore_Pev Then 435 | begin 436 | if (ALLUserWrng>=1)Then PevWarning := PevWarning + ' (PEV ignored='+ GetTimestring(Pilots[i].Markers[j].Tsec) +'!), ' 437 | end 438 | else 439 | begin 440 | PevCount:=PevCount+1; 441 | LastPev:= Pilots[i].Markers[j].Tsec; 442 | if (AllUserWrng>=1) Then PevWarning := PevWarning + 'PEV'+IntTostr(Pevcount)+'='+ GetTimestring(Pilots[i].Markers[j].Tsec)+', '; 443 | end; 444 | end; 445 | 446 | if PEVCount>0 Then 447 | begin 448 | PevStartNotValid:=(Trunc(Pilots[i].Start)<(LastPEV+PEVWaitTime)) or (Trunc(Pilots[i].Start)>(LastPEV+PEVWaitTime+PEVStartWindow)); 449 | if PevStartNotValid Then 450 | PEVWarning:=PevWarning+' Start='+GetTimestring(Trunc(Pilots[i].Start))+' PEVGate not open!'+', ' 451 | else 452 | if (Pilots[i].start>=Task.NoStartBeforeTime) and (AllUserWrng>=1) Then 453 | PEVWarning:=PevWarning+' Start='+GetTimestring(Trunc(Pilots[i].Start))+' OK'+', '; 454 | Pilots[i].Warning:= PevWarning; 455 | end 456 | else 457 | PEVWarning:='PEV not found!'+', '; 458 | 459 | Pilots[i].Warning:= PevWarning; 460 | end; 461 | if Pilots[i].start