Results 1 to 30 of 30

Thread: Server/Session Activity Statistics for CloD

  1. #1
    Novice Pilot Doctor_Ramble's Avatar
    Join Date
    Nov 2025
    Location
    Sydney, Australia
    Posts
    38
    Post Thanks / Like
    Total Downloaded
    227.87 MB

    Server/Session Activity Statistics for CloD

    * SAS v0.2-0 Update and Windows SAS GUI announcement

    Hi all,

    For the past month I have been creating an external programme for CloD that grabs the log.txt file and returns the important information in it.
    This includes vehicle destruction information, bail-out information, landing/crash information and player side-switching information.

    I originally started making this programme because, when playing on Linux, some text fails to display. This includes the singleplayer after-action statistics page which tells you how many aircraft you have destroyed.
    Instead of spending a couple of minutes to ask what font was used for these pages, I spent nearly 2 months making this programme.
    This is not a full replacement for that statistics page, but it does display some information that the statistics page does not.
    Since most of the player base of CloD runs Windows and also might enjoy this programme, I made sure to make this programme Windows-compatible.

    Please keep in mind that I only do programming as a hobby so the code may not be up to professional standards. But I didn’t get AI to help me at all, so that’s something.


    One feature of this programme is the ability to choose what you see, for example, you can edit the config file so that you only see vehicle destruction information.

    Another is being able to colour the text or it's background, this makes it easier to differentiate what line is what. This may not work in all terminals which is why it is disabled by default.
    If you use Linux and encounter errors, please try the GNOME Terminal. The Windows 10 command prompt does not support colour, but from memory the Windows 11 one does.

    This programme also creates player profiles. If it detects a player username it adds it to the player list and if, for example, that player destroys an aircraft, it will add 1 to the player’s Score (how many vehicles they have destroyed). After the information in the log has been summarised, all the player profiles are displayed showing their statistics. This can be handy on a multiplayer session with your friends as it is an easy way to see who shot down the most aircraft when the mission is over!

    Some points worth noting:
    * This is a terminal-based programme, but since it only displays text there would be no advantage to having a GUI.
    * You have to finish and exit a mission before CloD updates the log file and you may also have to close the game as well.

    You can find this programme on GitHub.
    To download it click the green "Code" button and then click "Download ZIP".
    You will have to compile it yourself, but don't worry as it is not difficult at all! On the GitHub page I have easy-to-follow instructions on how to compile the programme and how to install a C++ compiler if you don’t already have one.
    I also provide instructions on how to create a script file that you can place on your desktop that opens the terminal and runs the programme with a a single double-click. More information on this programme is also provided there.

    This programme is not yet at v1.0. This is because there are probably still bugs in it and log messages that need adding. According to my Steam statistics, I have played this game for 219 hours, and I started testing at 199 hours. This means that I have played CloD for about 20 hours in total to test this programme, and during that time I have fixed at least 10 bugs and added 3 more lines for the programme to process.


    If you encounter any bugs or display errors, then please check if you are running the latest version of the programme. If you already are then please contact me.
    Include your OS information, your log file (or at least the line/s that is/are having problems) and a description of what is wrong. You can either post it on this thread or via additional contact information on the GitHub page.
    Enabling “Debug_Mode” in your config file displays additional information including what values the programme has grabbed from the config file, this may help with figuring out what is wrong.

    Reporting bugs is appreciated as there is only so much testing I can do myself, especially since I have to test on both Linux and Windows! I will make sure to respond to you ASAP, but please be patient if I do not.

    Hope you find this programme useful and happy flying!
    ~ Ramble


    Linux Mint 22.2 in the GNOME Terminal (Background Colours):

    2025-11-18_10-08.png

    Windows 10 in the Command Prompt (No Colours):

    2025-11-01_13-15.png


    P.S. Before I forget, does anyone know what font is used for the Statistics Page in CloD?


    Edit: Here is a guide I have made for running CloD with Opentrack (and the Neuralnet tracker) on Linux.
    Attached Images Attached Images
    Last edited by Doctor_Ramble; Feb-05-2026 at 03:53.

  2. #2
    Team Fusion
    Join Date
    Oct 2018
    Location
    Toronto, Ontario
    Posts
    1,125
    Post Thanks / Like
    Total Downloaded
    4.02 GB

    Re: Server/Session Activity Statistics for CloD

    I have been looking to create a persistent RAF Form 414 style Flying Pilot's Log Book to accompany the BoB single player campaign I am building. I have been using Microsoft Copilot to write a C# script as I have no programming skills and no wish to take learning that on. I have had some modest success at creating a prototype.

    What you are doing is really interesting to me. I'll be taking a look. Knowing what you know already, do you think accomplishing the above would be possible with your code?

    ATAG_Lenny
    Desktop - Alienware Area 51, Intel Core Ultra 9 285K, 64Gb DDR5 6400MT/s, Nvidia Geforce RTX 5090 32Gb (latest drivers), Dell 3221QS (4K@60Hz), Win 11 (latest update), C Drive Steam default, Pimax Crystal Super Ultrawide, TM Warthog throttle, TM Viper panel, TM T-Rudder, Moza AB9 FFB with TM F15 stick.
    Desktop - Alienware R10, AMD Ryzen 9 5950X, 64Gb DDR4-3400Mhz OC 3734Mhz, Dell RTX 3090 24Gb (512.15), Dell 3221QS (4K @ 60Hz), Win 11 (22000.567), C Drive Steam default, Pimax Crystal Light, TM Warthog Throttle/Stick, TM T-Rudder, TM MFDx2, Razer Tartarus Pro
    Laptop - Dell G7 7590, Intel i7-8750H, 64Gb DDR-2666Mhz, GPU0 Intel UHD 630 1Gb GPU1 Dell RTX 2060 6Gb (512.15), BenQ EX3203R (3K @ 60Hz), Win 11 (22000.567), C drive Steam default, TM T16000M, TM TWCS

  3. #3
    Novice Pilot Doctor_Ramble's Avatar
    Join Date
    Nov 2025
    Location
    Sydney, Australia
    Posts
    38
    Post Thanks / Like
    Total Downloaded
    227.87 MB

    Re: Server/Session Activity Statistics for CloD

    Quote Originally Posted by ATAG_Lenny View Post
    I have been looking to create a persistent RAF Form 414 style Flying Pilot's Log Book to accompany the BoB single player campaign I am building. I have been using Microsoft Copilot to write a C# script as I have no programming skills and no wish to take learning that on. I have had some modest success at creating a prototype.

    What you are doing is really interesting to me. I'll be taking a look. Knowing what you know already, do you think accomplishing the above would be possible with your code?

    ATAG_Lenny
    That sounds like an interesting project you are working on!

    I gather that you are creating an external programme for CloD that acts like a log book and stores what kills you scored, when you landed, bailed out, etc. Is this correct?

    If so, I was actually planning on implementing this code into a "career mode" programme of sorts.
    I already have a basic plan laid out and it may help you modify my code to your needs.

    This is a bit of a long read so I will just summarise that, yes, it might be possible to implement my code into your programme.

    ----------------------------------------------
    * You'd need to generate a mission or, in your case, run it along with a campaign.

    * The player would play the mission.
    It is very important that the user quits the game immediately after they finish the mission because my programme gets the statistics for the whole game session.
    My programme does however get the information regarding a new mission loading (includes the path and name of the mission) and when a battle starts/ends.
    You can use that information to modify the programme to only collect statistics from certain missions.

    * The programme would need to identify kill info, bail out info, landing info and crash info. This is where you might start running into problems and it is due to the limitations of a programme that is not directly integrated into CloD.

    ==============================================
    1. Unlike some games where kill info is determined by the player/entity that finished off or damaged the destroyed player/entity last, CloD gives the credit to all contributing entities.
    This means that if you are shoulder-shooting and you accidentally hit the friendly in front of you, even just a tiny bit, if that friendly dies you and any other aircraft that hit it are credited with the kill.
    From memory, when making the destruction info part of my programme, CloD did save the contributors in a certain order (who shot it first to who shot it last I think), so you may be able to look into that.

    You obviously wouldn't want a friendly kill to contribute to your score so one way to fix this would be to create a list of aircraft/vehicles for the Red team and the Blue team.
    If the programme detects a kill it will check what side the destroyed entity is on (e.g. a Spitfire) and then it will remove any contributors that are on the same side as a Spitfire (e.g. Bofors, British Tankers, Hurricanes, Su-26s, etc).
    This leaves you with contributors that are only on the opposing side. If there are no contributors left then that means it was purely friendly-fire that ended the entity.
    This process alone would require adding a substantial amount of code to the programme and that is why I have not yet implemented it.
    I will note that you can check if there are more than one contributors by checking for the character "," in the string. If that is not present there is only one.

    2. With bail out information you will run into another problem. Unlike Great Battles, CloD does not check if pilot bails out into enemy territory.
    When I was thinking about making a career mode programme I ran into this problem. I concluded that you'd either have to count all landed bail outs as a death or, more leniently, pretend it never happened and no matter where they land somehow they return.

    3. When someone lands it generally means that their sortie is over. You'd be able to tell how long their sortie was by getting the time when the battle began, and the time when they landed.
    You could alternatively use the BEGIN LOG and END LOG markers, but the time/date stamps on those are in two different formats so you'd have to convert them beforehand.
    For some reason BEGIN uses the [yyyy-mm-dd hh:mm:ss] format; whereas END uses the [dd/mm/yyyy hh:mm:ss] format.
    I was going to make my programme report how long the session lasted for at the end, but after seeing this I decided to leave that for after I was sure that the main part of the programme worked.

    4. Landing information and crash information is pretty easy to determine.
    On my programme's GitHub page you can see all of the "messages" I have added into the programme. Under the section for Landing information I have found that CloD reports landings as:
    -> username returned to base.
    -> username is safe on the ground.
    -> username crash landed.
    or
    -> username landed.

    And for crashes:
    -> entity collided with terrain.
    or
    -> entity crashed.

    If it is part of landing information (including crash landings; those are just good landings) it usually means that the pilot survives.
    But if it is part of crash information, it tends to mean that you have crashed into the side of a chimney and somehow travelled to another dimension as there is no trace of any wreckage; we will presume that the pilot is dead.

    We can assume that "returned to base" and "landed" means that you are at an airfield, but for the others it is similar to the problem with bail out information.
    ==============================================

    * If you have figured all of that out, then next is to save the information.
    The programme will read the log file as normal.
    An example log file would be along the lines of:

    ++++++++++++++++++++++++++++++++++++++++++++++
    * BEGIN log session [2025-11-19 09:50:51]
    * [09:51:16] Battle starting [09:51:16] Server: Battle begins!
    * Pilot of a Spitfire IIa (JX o A) hit the silk.
    * Spitfire IIa (JX o A) (AI) was destroyed by AI in a Bf 109 E-4/N ( 4+ ).
    # This means that the pilot bailed out but the aircraft was destroyed. The pilot survives. The 109 gets the kill.
    * Doctor_Ramble's Spitfire IIa (JX o B), Ju-88 A-4 ( - ) (AI), AI in a Hurricane I Rotol (FC o A) destroyed Bf 109 E-4/N ( 4+ ).
    # Since there are more than one contributors (the programme checks for this by seeing if there is a "," in the string), we get the first one first.
    # Doctor_Ramble gets a shared kill added to their log.
    # The Ju-88 is on the same team as the Bf 109 so they get a strike and removed from the contributors list (for the log book).
    # The Hurricane (FC o A) gets a shared kill added to their log.
    # Because we do not see the 109 pilot bail out beforehand it is safe to presume their death.
    * Doctor_Ramble of a Spitfire IIa (JX o B) hit the silk.
    * AI in a Spitfire IIa (JX o B) was destroyed by Bf 109 E-4/N ( 5+ ) (AI).
    # We see that I safely bail out and then our aircraft is destroyed, we survive though.
    # I did not add "AI in a..." by mistake. After you bail out an AI with no brain keeps your aircraft in safe hands for you.
    # This means that we either have to recognise Doctor_Ramble belongs to the Spitfire JX o B beforehand or we can just ignore it and the 109 still gets the kill.
    * Doctor_Ramble's parachute failed to deploy.
    # Oops! Looks like we were out of luck there. We now have to recognise that Doctor_Ramble is dead. Game over for us, the campaign is over.
    * [09:53:17] Server: The battle ends.
    * [19/11/2025 09:53:22] END log session
    ++++++++++++++++++++++++++++++++++++++++++++++

    The programme should create a new text file (maybe with the same name as the mission we just played?) and store this information in it.
    It then creates another text file (maybe campaign-name_mission-num-of-campaign) and that is the next entry in our pilot's log book.
    For this one the programme enters in something like this:

    * At [09:51:16] Doctor_Ramble took off.
    # We have to assume that we take off when the battle begins because CloD does not report a player taking off.
    * At [hh:mm:ss] Doctor_Ramble shared a kill with [FC o A] and shot down a Bf 109.
    # We then get a shared kill added to our profile.
    * At [hh:mm:ss] Doctor_Ramble hit the silk.
    * At [hh:mm:ss] Doctor_Ramble's aircraft was destroyed by an enemy 109.
    # We survive but our aircraft is destroyed. Optionally add a tally to "lost aircraft" so that the ground crews can figure out who gets the worst aircraft next time.
    * At [hh:mm:ss] Doctor_Ramble was killed in action. Parachute failed to deploy.
    # Unfortunately our parachute didn't deploy! That is the end of the career/campaign for us.
    # You can optionally let the player choose to ignore this death or choose to accept defeat.
    ----------------------------------------------

    An idea would be to let the programme know how many aircraft are in our squadron (and their codes), randomly assign them names and let them have their own little profiles and be replaced by another name if they die (plus reset their statistics).

    Your programme is written in C# so you'd either have to get it re-written entirely in C++, or convert my code over to C#. You can also try calling a modified version of my programme with your C# code (probably the easiest option if it works).
    I do not know C# (my only experience with it is mission scripting in CloD), but if you need any assistance at all I'd be more than happy to try and help.
    If you can't understand what a piece of my code does then feel free to ask me.

    I would implement my code into your programme in a way that is easy to update if necessary.
    I have no doubt that someone will find a bug of some sort, so having it easy to update would be good.
    But I have played CloD for over 20 hours since starting testing so for the most part the current code should work without error.


    I hope this information helps and I can't wait to try out your campaign, it sounds like it will be a lot of fun, good luck!

    ~ Ramble

    Please note that my programme is published under the MIT license. A LICENSE file is included with my programme.

    Edit:
    A much better way to check for pilot deaths would be to utilise the crew member death messages that CloD reports.
    An example is:
    * "[14:10:57] Server: Doctor_Ramble Bf 110 C-7 ( - ) Pilot is dead."

    This specifically tells us that the pilot of my Bf 110 is dead, meaning that there is no reason to do the more complex task of checking if we did not bail out before our aircraft was destroyed.

    I have not (yet) implemented this as a feature in SAS because I did not deem it too necessary to inform the user if individual crew members of an aircraft have died.

    You can implement this yourself, but due to the limitations of the log file I have to agree with Rostic.
    SAS is capable of being used as a "log book" of sorts (after being modified), but for your purpose I think you'd be better off trying to use the mission script method instead.

    SAS was only made as a quick way to see what happened in the previous CloD session as a semi-replacement for the statistics page on Linux, though it is also useful for servers.
    Last edited by Doctor_Ramble; Nov-19-2025 at 17:13.

  4. Likes FTC_Rostic liked this post
  5. #4
    Veteran Combat pilot FTC_Rostic's Avatar
    Join Date
    Nov 2023
    Posts
    345
    Post Thanks / Like
    Total Downloaded
    103.70 MB

    Re: Server/Session Activity Statistics for CloD

    Quote Originally Posted by ATAG_Lenny View Post
    I have been looking to create a persistent RAF Form 414 style Flying Pilot's Log Book to accompany the BoB single player campaign I am building. I have been using Microsoft Copilot to write a C# script as I have no programming skills and no wish to take learning that on. I have had some modest success at creating a prototype.

    What you are doing is really interesting to me. I'll be taking a look. Knowing what you know already, do you think accomplishing the above would be possible with your code?

    ATAG_Lenny
    In a few words:
    That is a bad idea!!!

    More detailed explanation:
    You can't rely on the log file. Those usually made for storing debugging info and their syntax can be changed at any point. Also log file behavior depends on settings in "conf.ini" that can be changed by user any time:
    LOG=1
    LOGTIME=1
    LOGFILE=log.txt
    LOGKEEP=0

    So, what ever program you make, it may broke any moment.

    The only good thing about utilizing log file
    Log file contain messages from server. Right now that is the only way to get any info about multiplayer session on client side. But see disadvantage I mentioned above and many other disadvantages mentioned by Doctor_Ramble.

    The right way for Single Player
    Utilize mission script is the only way! (as starting point)
    First you should decide what is your goal:
    a) Create code that should work with every single player missions?
    b) To make logbook for specific campaigns only?

    Both can be implemented at same time.

    For goal (a) only general data required. Number of missions flown, for which faction, number of air kills, ground kill, weight of bombs dropped, every kills by object name, etc. Something for "single player / Statistics" page. At early stage of development you can create mission script that collect that information in to any sort of file saved somewhere in "Documents\1C SoftClub\il-2 sturmovik cliffs of dover". After that create "add-in" that will add "Statistics" page as entry point to any number of pages with custom UI layout for all sort of data you would like to show. Sky is the limit. At some point, when you decide that your code work pretty well pass it to one of programmers to integrate that in to engine (should not be impossible for TFS member ).

    Under spoiler not working idea, I published in first place, but I was wrong.. see right way below.
    Spoiler: 

    I WAS WRONG, IGNORE: Goal (b) is way easier. I did not tested, but I think it is possible to generate text string that will be displayed in built-in "Debriefing" page. Basically do the same. Create mission script that collect all necessary information in to any sort of file saved somewhere in "Documents\1C SoftClub\il-2 sturmovik cliffs of dover\mission\campaign\custom\campaign_Whatever". Display AAR in "Debriefing" page. To be able to see results after game restart (or mission end) is to create "add-in" that will add "Statistics" page as entry point to any number of pages with custom UI layout for all sort of data you would like to show. Sky is the limit...


    Goal (b) is more complicated. You have to create completely your own Add-In that will provide all necessary UI pages for campaign selection, campaign missions start, etc. Prety much same thing what "CloD DCE" does, but as static campaign approach. On good side you can add any pages with any information you like to display including images. Sky is the limit... No external programs. No need to pass it to programmers to implement in game engine. Just ask to add it to build as add-in.

    As mission script architecture I advise to create dedicated class (for example "StatisticsCollector") with same methods and arguments as "Mission" class. So you separate statistics script code from mission logic if any required by mission. Inside that class I recommend to collect data as fast as possible in original game format and pass it though another class "pipeline" which have only temporarily sore data in memory. Create third class that in separate thread will parse data from "Pipeline" and store it in to file on a disk without utilizing CPU time of main game logic cycle.

    Good luck!

    P.S.: Many examples of mission scripts can be found in that archive: https://drive.google.com/file/d/1Ei6...usp=drive_link inside folder "Full Mission Builder And DedicServer\Downloads".
    Last edited by FTC_Rostic; Nov-19-2025 at 07:16.
    PC spec: Intel Core i7 8700K 3.7Ghz, DDR4 32Gb 2666Mhz, Asus Prime Z370-A, ADATA XPG SX8200 240Gb (PCIe Gen3x4), RTX 2060 6Gb
    Monitor: DELL P2717H (1920x1080:60Hz)
    Joystick: Android smartphone MonectPC app (virtual joystick driver)
    Hadtracker: Track IR 4 / No VR

    Enjoy multiplayer historical campaigns with Flying Tin Cans.
    Join us: https://flyingtincans.com —(•)— Discord —(•)— YouTube


  6. Likes Doctor_Ramble liked this post
  7. #5
    Novice Pilot Doctor_Ramble's Avatar
    Join Date
    Nov 2025
    Location
    Sydney, Australia
    Posts
    38
    Post Thanks / Like
    Total Downloaded
    227.87 MB

    Re: Server/Session Activity Statistics for CloD

    Quote Originally Posted by FTC_Rostic View Post
    In a few words:
    That is a bad idea!!!

    More detailed explanation:
    You can't rely on the log file. Those usually made for storing debugging info and their syntax can be changed at any point. Also log file behavior depends on settings in "conf.ini" that can be changed by user any time:
    LOG=1
    LOGTIME=1
    LOGFILE=log.txt
    LOGKEEP=0

    So, what ever program you make, it may broke any moment.

    The only good thing about utilizing log file
    Log file contain messages from server. Right now that is the only way to get any info about multiplayer session on client side. But see disadvantage I mentioned above and many other disadvantages mentioned by Doctor_Ramble.

    The right way for Single Player
    Utilize mission script is the only way! (as starting point)
    First you should decide what is your goal:
    a) Create code that should work with every single player missions?
    b) To make logbook for specific campaigns only?

    Both can be implemented at same time.

    For goal (a) only general data required. Number of missions flown, for which faction, number of air kills, ground kill, weight of bombs dropped, every kills by object name, etc. Something for "single player / Statistics" page. At early stage of development you can create mission script that collect that information in to any sort of file saved somewhere in "Documents\1C SoftClub\il-2 sturmovik cliffs of dover". After that create "add-in" that will add "Statistics" page as entry point to any number of pages with custom UI layout for all sort of data you would like to show. Sky is the limit. At some point, when you decide that your code work pretty well pass it to one of programmers to integrate that in to engine (should not be impossible for TFS member ).

    Under spoiler not working idea, I published in first place, but I was wrong.. see right way below.
    Spoiler: 

    I WAS WRONG, IGNORE: Goal (b) is way easier. I did not tested, but I think it is possible to generate text string that will be displayed in built-in "Debriefing" page. Basically do the same. Create mission script that collect all necessary information in to any sort of file saved somewhere in "Documents\1C SoftClub\il-2 sturmovik cliffs of dover\mission\campaign\custom\campaign_Whatever". Display AAR in "Debriefing" page. To be able to see results after game restart (or mission end) is to create "add-in" that will add "Statistics" page as entry point to any number of pages with custom UI layout for all sort of data you would like to show. Sky is the limit...


    Goal (b) is more complicated. You have to create completely your own Add-In that will provide all necessary UI pages for campaign selection, campaign missions start, etc. Prety much same thing what "CloD DCE" does, but as static campaign approach. On good side you can add any pages with any information you like to display including images. Sky is the limit... No external programs. No need to pass it to programmers to implement in game engine. Just ask to add it to build as add-in.

    As mission script architecture I advise to create dedicated class (for example "StatisticsCollector") with same methods and arguments as "Mission" class. So you separate statistics script code from mission logic if any required by mission. Inside that class I recommend to collect data as fast as possible in original game format and pass it though another class "pipeline" which have only temporarily sore data in memory. Create third class that in separate thread will parse data from "Pipeline" and store it in to file on a disk without utilizing CPU time of main game logic cycle.

    Good luck!

    P.S.: Many examples of mission scripts can be found in that archive: https://drive.google.com/file/d/1Ei6...usp=drive_link inside folder "Full Mission Builder And DedicServer\Downloads".
    Very well said Rostic, this looks like a much better way to handle this. I might actually look into this myself when I get around to making a singleplayer campaign.

    ~ Ramble
    Computer Specifications:
    CPU: Intel Core i9-13900K; RAM: 64GB DDR5 @ 5200MT/s; GPU: nVidia GeForce RTX 4070 v1.0 w/ 12GB VRAM;
    OS 1: Linux Mint 22.3 (Cinnamon - X11); nVidia Driver: 590.48.01;
    OS 2: Windows 10 22H2; nVidia Driver: 566.36 Studio;
    Display: 1920*1080 @ 60 Hz

  8. #6
    Team Fusion
    Join Date
    Oct 2018
    Location
    Toronto, Ontario
    Posts
    1,125
    Post Thanks / Like
    Total Downloaded
    4.02 GB

    Re: Server/Session Activity Statistics for CloD

    Below is the C# script that Copilot wrote for me. A couple of comments:

    1 - The file location is hard coded but I know can be soft-coded to fit the users file structure
    2 - There appears to be a lot of info that is uncovered in campaign missions but not in single player. The initial most important one was whether the pilot crashed or landed. So what we did is define the location of all airfields on the Channel Map (x,y,z). Then the logic determines whether the players landing stopped position was within 1000 metres of the airfield and within 5 metres vertical. If yes, then presumption the player landed. If > 1000m or 5m vertical then assume crashed on land unless altitude <5m in which case presume crashed at sea. It could also be enhanced to view Tobruk map as well. What I also like about this logic, is that it would allow the player to land a wounded warbird at any nearby airfield and still get the mission outcome as safely landed.
    3 - outcome along with some other info bits we were able to glean is written to a text file
    4 - information from text file is also written to a CSV file and hyperlinked back to the text file. This is the Form 414 log. I think the hyperlink code needs one fix but if you run it you will see what needs to be edited in the CSV.
    5 - Next step would be to see what else I we could retrieve and format to look like a Form 414.

    I stopped here because this was just a proof of concept. The concept being could a persistent log be created, and did I actually need any programming skills to do it. The answers to me were yes and no. Perfect.

    All that needs to be done is copy the script to a text file (Notepad++ the best) and the file saved as a ".cs" file with the same main name as the ".mis". Will work for any single mission near as I can tell. More work to be done, but not right now for me. If people don't like the code, well, I have no pride of authorship since I didn't write it, and Microsoft Copilot has no pride, it's just code too.

    Lenny

    PS the Grins below are colons. I don't know how to get rid of the grins.

    // Mission Debrief with Channel Airfields
    using System;
    using System.IO;
    using System.Collections.Generic;
    using maddox.game;
    using maddox.game.world;

    public struct Point2d
    {
    public double x;
    public double y;
    public Point2d(double x, double y) { this.x = x; this.y = y; }
    }

    public struct Point3d
    {
    public double x;
    public double y;
    public double z;
    public Point3d(double x, double y, double z) { this.x = x; this.y = y; this.z = z; }
    }

    public class Mission : AMission
    {
    private DateTime missionStartTime = DateTime.MinValue;
    private bool debriefWritten = false;
    private string folder = @"C:\Users\paull\OneDrive\Documents\1C SoftClub\il-2 sturmovik cliffs of dover\missions\Battle of Britain\_PilotLog";
    private string csvFile = "mission_log.csv";

    // Source dictionary (full Channel airfields list)
    private Dictionary<string, Point3d> channelAirfields = new Dictionary<string, Point3d>
    {
    { "Abbeville", new Point3d(284007, 124840, 61) },
    { "Achiet-Grevillers", new Point3d(350150, 121130, 127) },
    { "Amiens-Allonville", new Point3d(321977, 101320, 89) },
    { "Amiens-Glisy", new Point3d(322075, 95307, 59) },
    { "Andover", new Point3d(46142, 238991, 90) },
    { "Arras", new Point3d(351330, 145343, 98) },
    { "Arras-Saint Liger", new Point3d(355623, 128799, 109) },
    { "Audembert", new Point3d(272013, 205320, 42) },
    { "Barly", new Point3d(314851, 130755, 122) },
    { "Beaumont Le Roger", new Point3d(208122, 10022, 139) },
    { "Beauvais Nivillers", new Point3d(307577, 50024, 120) },
    { "Beauvais Tille", new Point3d(303216, 48623, 99) },
    { "Bembridge", new Point3d(76324, 180178, 13) },
    { "Berk", new Point3d(265712, 156097, 1) },
    { "Bernay-Saint Martin", new Point3d(191769, 10254, 161) },
    { "Biggin Hill", new Point3d(155403, 253686, 179) },
    { "Boisjean Ecuires", new Point3d(278725, 157067, 57) },
    { "Boscombe Down", new Point3d(31456, 233239, 127) },
    { "Boulogne Alprech", new Point3d(264387, 186323, 69) },
    { "Brias", new Point3d(321117, 153653, 150) },
    { "Brombos", new Point3d(285450, 70129, 191) },
    { "Brooklands", new Point3d(120333, 255377, 20) },
    { "Caen Carpiquet", new Point3d(117911, 18146, 61) },
    { "Caffiers", new Point3d(281868, 203450, 112) },
    { "Calais-Marck", new Point3d(291178, 216286, 2) },
    { "Campagne Les Guines", new Point3d(287169, 202045, 75) },
    { "Canterbury", new Point3d(234568, 248429, 51) },
    { "Carquebut", new Point3d(52601, 39782, 20) },
    { "Colembert", new Point3d(284920, 193001, 198) },
    { "Coquelles", new Point3d(281202, 212948, 13) },
    { "Cramont-Yvrench", new Point3d(298439, 127466, 121) },
    { "Crecy", new Point3d(304944, 38421, 141) },
    { "Creil", new Point3d(331579, 26699, 101) },
    { "Crepon", new Point3d(111526, 33307, 59) },
    { "Croydon", new Point3d(144258, 256424, 101) },
    { "Deauville-Saint Gatien", new Point3d(162575, 38819, 140) },
    { "Desvres", new Point3d(285042, 182696, 200) },
    { "Dieppe-Saint Aubin", new Point3d(229199, 96278, 101) },
    { "Eastchurch", new Point3d(212285, 262830, 7) },
    { "Estree", new Point3d(280257, 164215, 80) },
    { "Farnborough", new Point3d(100060, 247376, 77) },
    { "Feltham", new Point3d(125306, 265580, 17) },
    { "Ford", new Point3d(112848, 195780, 1) },
    { "Gatwick", new Point3d(141685, 234076, 60) },
    { "Gosport", new Point3d(72435, 194011, 1) },
    { "Grandvilliers", new Point3d(291085, 73515, 179) },
    { "Gravesend", new Point3d(180346, 264835, 63) },
    { "Guines", new Point3d(284085, 205076, 46) },
    { "Hamble", new Point3d(61264, 201022, 20) },
    { "Harwell", new Point3d(61265, 280192, 120) },
    { "Haute-Fontaine", new Point3d(293360, 64646, 180) },
    { "Hawkinge", new Point3d(234930, 232459, 158) },
    { "Heathrow", new Point3d(121346, 269212, 23) },
    { "Hendon", new Point3d(135473, 283727, 50) },
    { "Hermelinghen", new Point3d(285740, 199982, 161) },
    { "Heston", new Point3d(125379, 271489, 30) },
    { "Hornchurch", new Point3d(166766, 277772, 10) },
    { "Hydrequent", new Point3d(276190, 200785, 78) },
    { "Kenley", new Point3d(146689, 251072, 174) },
    { "Larkhill", new Point3d(22426, 238675, 114) },
    { "Le Havre-Octeville", new Point3d(157374, 57869, 96) },
    { "Le Touquet", new Point3d(267738, 166723, 1) },
    { "Lee on Solent", new Point3d(69155, 195373, 10) },
    { "Ligescourt", new Point3d(285109, 139909, 70) },
    { "Littlestone", new Point3d(221950, 221332, 2) },
    { "Lympne", new Point3d(225174, 228785, 100) },
    { "Manston", new Point3d(248248, 258983, 44) },
    { "Marquise West", new Point3d(272460, 200326, 24) },
    { "Merville-Calonne", new Point3d(340749, 178426, 9) },
    { "Monchy-Breton", new Point3d(325708, 152724, 150) },
    { "Montdidier", new Point3d(335376, 72793, 108) },
    { "Netheravon", new Point3d(29865, 242135, 119) },
    { "North Weald", new Point3d(162440, 297791, 80) },
    { "Northolt", new Point3d(123413, 278194, 37) },
    { "Odiham(Oldham)", new Point3d(86982, 242034, 112) },
    { "Old Sarum", new Point3d(28764, 226253, 79) },
    { "Oye-Plage", new Point3d(296378, 217322, 2) },
    { "Persan Beaumont", new Point3d(317533, 17080, 42) },
    { "Peuplingues", new Point3d(277055, 209352, 101) },
    { "Pihen", new Point3d(278920, 207297, 96) },
    { "Plumetot", new Point3d(124247, 28435, 40) },
    { "Poix Nord", new Point3d(292756, 88726, 171) },
    { "Pontoise-Cormeilles", new Point3d(296969, 9149, 50) },
    { "Portsmouth", new Point3d(80117, 196482, 1) },
    { "Querqueville", new Point3d(27835, 72604, 1) },
    { "Ramsgate", new Point3d(251184, 260927, 47) },
    { "Reading", new Point3d(90946, 266311, 46) },
    { "Redhill", new Point3d(143424, 241162, 80) },
    { "Rely- Norrent Fontes", new Point3d(321400, 172372, 94) },
    { "Rochester", new Point3d(188229, 257808, 130) },
    { "Rochford", new Point3d(200959, 282782, 10) },
    { "Rosieres en Santerre", new Point3d(346178, 87489, 82) },
    { "Rouen Boos", new Point3d(236496, 41402, 140) },
    { "Roye-Amy", new Point3d(353042, 70057, 83) },
    { "Ryde", new Point3d(74252, 183309, 52) },
    { "Saint Inglevert", new Point3d(275941, 207456, 129) },
    { "Salisbury", new Point3d(27946, 230224, 131) },
    { "Samer", new Point3d(278083, 182132, 61) },
    { "Sandown", new Point3d(71245, 177136, 21) },
    { "Sempy", new Point3d(287389, 163213, 120) },
    { "Shoreham", new Point3d(133698, 198565, 1) },
    { "Southampton", new Point3d(58765, 209960, 9) },
    { "St Omer-Arques", new Point3d(317659, 190565, 29) },
    { "St Omer-Clairmarais", new Point3d(317864, 195075, 9) },
    { "St Omer-Wizernes", new Point3d(311564, 190353, 78) },
    { "Tangmere", new Point3d(104593, 199293, 12) },
    { "Theville", new Point3d(43818, 70627, 135) },
    { "Thorney Island", new Point3d(89632, 196176, 1) },
    { "Tramecourt", new Point3d(306422, 160671, 126) },
    { "Upavon", new Point3d(28778, 247273, 147) },
    { "Wailly-Beaucamp", new Point3d(275846, 156103, 51) },
    { "Watchfield", new Point3d(37948, 284823, 100) },
    { "West Malling (Maidstone)", new Point3d(181260, 248833, 84) },
    { "Westhampnett", new Point3d(101418, 199773, 19) },
    { "White Waltham", new Point3d(98892, 272114, 36) },
    { "Wilmington", new Point3d(167682, 198386, 22) },
    { "Wissant", new Point3d(269735, 207212, 21) },
    { "Worthydown", new Point3d(60583, 228112, 100) },
    { "Yatesbury", new Point3d(18618, 264227, 170) },
    { "Yvrench", new Point3d(295249, 130393, 110) },
    { "Zutkerque", new Point3d(299432, 203047, 36) },
    };

    // Working dictionary used in proximity checks
    private Dictionary<string, Point3d> airfields = new Dictionary<string, Point3d>();

    // --- methods follow after the dictionary ---

    public override void OnBattleStarted()
    {
    if (GamePlay == null) return;

    missionStartTime = DateTime.Now;
    GamePlay.gpHUDLogCenter("Mission started.");
    Directory.CreateDirectory(folder);

    airfields.Clear();
    foreach (var kvp in channelAirfields)
    airfields[kvp.Key] = kvp.Value;
    }

    public override void OnTickGame()
    {
    if (GamePlay == null) return;
    if (debriefWritten) return;

    Player player = GamePlay.gpPlayer();
    if (player == null || player.Place() == null) return;

    AiAircraft aircraft = player.Place() as AiAircraft;
    if (aircraft == null || !aircraft.IsAlive()) return;

    Point2d pos2d = new Point2d(aircraft.Pos().x, aircraft.Pos().y);
    double altitude = aircraft.Pos().z;

    bool nearAirfield = false;

    foreach (var entry in airfields)
    {
    string name = entry.Key;
    Point3d fieldPos = entry.Value;
    double distance = getDistance(pos2d, new Point2d(fieldPos.x, fieldPos.y));

    if (distance < 1000.0)
    {
    if (altitude < fieldPos.z + 5.0)
    {
    GamePlay.gpHUDLogCenter("Landing detected near " + name + ". Writing debrief...");
    writeDebrief(player, aircraft, "Landed at " + name);
    debriefWritten = true;
    return;
    }
    nearAirfield = true;
    }
    }

    if (!nearAirfield && altitude < 5.0)
    {
    GamePlay.gpHUDLogCenter("Crash detected at sea level. Writing debrief...");
    writeDebrief(player, aircraft, "Crashed into water");
    debriefWritten = true;
    }
    }

    private double getDistance(Point2d a, Point2d b)
    {
    double dx = a.x - b.x;
    double dy = a.y - b.y;
    return Math.Sqrt(dx * dx + dy * dy);
    }

    private void writeDebrief(Player player, AiAircraft aircraft, string status)
    {
    if (GamePlay == null) return;
    if (missionStartTime == DateTime.MinValue)
    {
    GamePlay.gpHUDLogCenter("Debrief error: missionStartTime not initialized.");
    return;
    }

    try
    {
    string pilotName = player?.Name() ?? "Unknown";
    string aircraftType = aircraft?.Type()?.ToString() ?? "Unknown";
    string callSign = aircraft?.CallSign() ?? "Unknown";
    string faction = (player?.Army() == 1) ? "Red" : (player?.Army() == 2) ? "Blue" : "Unknown";

    string missionName = Path.GetFileNameWithoutExtension(System.Reflection .Assembly.GetExecutingAssembly().Location);
    string missionTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
    TimeSpan duration = DateTime.Now - missionStartTime;
    string startTimeFormatted = missionStartTime.ToString("HH:mm:ss");
    string durationFormatted = String.Format("{02}:{12}:{22}", duration.Hours, duration.Minutes, duration.Seconds);

    string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
    string remarksFilename = "remarks_" + timestamp + ".txt";
    string remarksLink = "=HYPERLINK("" + remarksFilename + "", "View")"; // ✅ properly closed

    // TXT Debrief
    string debrief = String.Format(
    "Mission Complete\nTime: {0}\nStart Time: {1}\nDuration: {2}\nPilot: {3}\nFaction: {4}\nAircraft: {5}\nCallsign: {6}\nMission: {7}\nStatus: {8}\n\n",
    missionTime, startTimeFormatted, durationFormatted, pilotName, faction, aircraftType, callSign, missionName, status);

    File.WriteAllText(Path.Combine(folder, "debrief_" + timestamp + ".txt"), debrief);

    // CSV Logging
    string csvPath = Path.Combine(folder, csvFile);
    bool writeHeader = !File.Exists(csvPath) || new FileInfo(csvPath).Length == 0;

    if (writeHeader)
    {
    string header = "Date,Aircraft Type,Callsign,Duty,Pilot,Faction,Mission Name,Departure Time,Duration,Remarks,Result";
    File.AppendAllText(csvPath, header + Environment.NewLine);
    }

    string csvLine = String.Format("{0},{1},{2},{3},{4},{5},{6},{7},{8} ,{9},{10}",
    missionTime.Substring(0, 10), aircraftType, callSign, "Pilot", pilotName, faction,
    missionName, startTimeFormatted, durationFormatted, remarksLink, status);

    File.AppendAllText(csvPath, csvLine + Environment.NewLine);

    // Remarks Template
    string remarksPath = Path.Combine(folder, remarksFilename);
    string remarksTemplate = String.Format(
    "Pilot: {0}\nAircraft: {1} (Callsign {2})\nMission: {3}\nDate: {4}\nStart Time: {5}\nDuration: {6}\nStatus: {7}\n\n" +
    "--- Historical Context ---\n" +
    "Historical Date: \n" +
    "Historical Time: \n" +
    "Squadron/Staffel: \n" +
    "Aircraft Type: \n" +
    "Mission Type: \n\n" +
    "--- Remarks ---\n" +
    "Weather:\nEnemy Contact:\nDamage Sustained:\nLanding Notes:\nPersonal Impressions:\n",
    pilotName, aircraftType, callSign, missionName,
    missionTime.Substring(0, 10), startTimeFormatted, durationFormatted, status);

    File.WriteAllText(remarksPath, remarksTemplate);
    }
    catch (Exception ex)
    {
    GamePlay.gpHUDLogCenter("writeDebrief() failed: " + ex.Message);
    }
    }
    } // end of Mission class
    Desktop - Alienware Area 51, Intel Core Ultra 9 285K, 64Gb DDR5 6400MT/s, Nvidia Geforce RTX 5090 32Gb (latest drivers), Dell 3221QS (4K@60Hz), Win 11 (latest update), C Drive Steam default, Pimax Crystal Super Ultrawide, TM Warthog throttle, TM Viper panel, TM T-Rudder, Moza AB9 FFB with TM F15 stick.
    Desktop - Alienware R10, AMD Ryzen 9 5950X, 64Gb DDR4-3400Mhz OC 3734Mhz, Dell RTX 3090 24Gb (512.15), Dell 3221QS (4K @ 60Hz), Win 11 (22000.567), C Drive Steam default, Pimax Crystal Light, TM Warthog Throttle/Stick, TM T-Rudder, TM MFDx2, Razer Tartarus Pro
    Laptop - Dell G7 7590, Intel i7-8750H, 64Gb DDR-2666Mhz, GPU0 Intel UHD 630 1Gb GPU1 Dell RTX 2060 6Gb (512.15), BenQ EX3203R (3K @ 60Hz), Win 11 (22000.567), C drive Steam default, TM T16000M, TM TWCS

  9. Likes Doctor_Ramble, Dauntless liked this post
  10. #7
    Novice Pilot Doctor_Ramble's Avatar
    Join Date
    Nov 2025
    Location
    Sydney, Australia
    Posts
    38
    Post Thanks / Like
    Total Downloaded
    227.87 MB

    Re: Server/Session Activity Statistics for CloD

    Quote Originally Posted by ATAG_Lenny View Post
    Below is the C# script that Copilot wrote for me. A couple of comments:

    1 - The file location is hard coded but I know can be soft-coded to fit the users file structure
    2 - There appears to be a lot of info that is uncovered in campaign missions but not in single player. The initial most important one was whether the pilot crashed or landed. So what we did is define the location of all airfields on the Channel Map (x,y,z). Then the logic determines whether the players landing stopped position was within 1000 metres of the airfield and within 5 metres vertical. If yes, then presumption the player landed. If > 1000m or 5m vertical then assume crashed on land unless altitude <5m in which case presume crashed at sea. It could also be enhanced to view Tobruk map as well. What I also like about this logic, is that it would allow the player to land a wounded warbird at any nearby airfield and still get the mission outcome as safely landed.
    3 - outcome along with some other info bits we were able to glean is written to a text file
    4 - information from text file is also written to a CSV file and hyperlinked back to the text file. This is the Form 414 log. I think the hyperlink code needs one fix but if you run it you will see what needs to be edited in the CSV.
    5 - Next step would be to see what else I we could retrieve and format to look like a Form 414.

    I stopped here because this was just a proof of concept. The concept being could a persistent log be created, and did I actually need any programming skills to do it. The answers to me were yes and no. Perfect.

    All that needs to be done is copy the script to a text file (Notepad++ the best) and the file saved as a ".cs" file with the same main name as the ".mis". Will work for any single mission near as I can tell. More work to be done, but not right now for me. If people don't like the code, well, I have no pride of authorship since I didn't write it, and Microsoft Copilot has no pride, it's just code too.

    Lenny

    PS the Grins below are colons. I don't know how to get rid of the grins.

    // Mission Debrief with Channel Airfields
    using System;
    using System.IO;
    using System.Collections.Generic;
    using maddox.game;
    using maddox.game.world;

    [...]

    } // end of Mission class
    I initially ran into some compilation errors on lines 235, 236, 237, 238 and 248.
    Since I do not know C# I had to run the code through MS Copilot to get it working, and after some prompting, lines 235-248 were replaced with working code.
    I then created a mission where the player starts over Hawkinge. When I am a few metres above the runway, the text "Landing detected near Hawkinge. Writing debrief..." appears. And my status in the debrief text file is "Landed at Hawkinge".
    The CSV file is a nice addition and looks like it will make a very nice log book.

    I'd say that this is a great proof of concept and has the potential to be a pretty useful mission script.

    ~ Ramble

    PS. You can disable the grins by checking "Disable smilies in text" under "Additional Options >> Miscellaneous Options" when creating/editing a post.
    Last edited by Doctor_Ramble; Nov-29-2025 at 15:36.
    Computer Specifications:
    CPU: Intel Core i9-13900K; RAM: 64GB DDR5 @ 5200MT/s; GPU: nVidia GeForce RTX 4070 v1.0 w/ 12GB VRAM;
    OS 1: Linux Mint 22.3 (Cinnamon - X11); nVidia Driver: 590.48.01;
    OS 2: Windows 10 22H2; nVidia Driver: 566.36 Studio;
    Display: 1920*1080 @ 60 Hz

  11. #8
    Veteran Combat pilot FTC_Rostic's Avatar
    Join Date
    Nov 2023
    Posts
    345
    Post Thanks / Like
    Total Downloaded
    103.70 MB

    Re: Server/Session Activity Statistics for CloD

    There is no need to store dictionary of airfields. List of airfields can be requested. I recommend to familiarize yourself with API available. Later I'll give you more details. But most solutions can be found in archive I published earlier.
    PC spec: Intel Core i7 8700K 3.7Ghz, DDR4 32Gb 2666Mhz, Asus Prime Z370-A, ADATA XPG SX8200 240Gb (PCIe Gen3x4), RTX 2060 6Gb
    Monitor: DELL P2717H (1920x1080:60Hz)
    Joystick: Android smartphone MonectPC app (virtual joystick driver)
    Hadtracker: Track IR 4 / No VR

    Enjoy multiplayer historical campaigns with Flying Tin Cans.
    Join us: https://flyingtincans.com —(•)— Discord —(•)— YouTube


  12. Likes ATAG_Lenny, DD_FT liked this post
  13. #9
    Veteran Combat pilot FTC_Rostic's Avatar
    Join Date
    Nov 2023
    Posts
    345
    Post Thanks / Like
    Total Downloaded
    103.70 MB

    Re: Server/Session Activity Statistics for CloD

    Well, this is empty mission script template and Microsoft Visual Studio community edition project BLITZmission_template.zip
    I use it to check mission script for syntax errors. Also automatic drop down list with functions and classes available in namespaces are very helpful to get idea what I can do. Most of that can be found in "References" "Object Browser". For example on the screenshot you can see function name that can give you list of airfields available on the map.
    e01.JPG

    I'm sorry, I can't provide too much of technical support or guidance, due to I do not have enough time for that. Also my knowledge is very limited to what Cliffs of Dover can offer (like 0.01% or less).

    But I get all info from same sources available to anyone!
    First of all TFS resource pages: https://www.team-fusion-simulations.de/
    There is great example mission with fantastic script: SampleMission-v1.0.10.7z

    Use my collection of files: IL-2 Cliffs of Dover - random documents.zip the most useful info about mission scripting is located in "Full Mission Builder And DedicServer\Downloads". Quite interesting collection is in file "Full Mission Builder And DedicServer\Downloads\SCRIPTS_allSamples2.zip" but explore other files for way more examples. There are collections of dynamic campaigns, missions, etc.

    Most of those example where found here, on ATAG forum, old 1C forum and old Sukhoi forum (probably you'll need to use google translate ).

    Very good example of Cliffs of Dover Add-In is silkyskyj CloDDce which is based on IL2DCE

    Also I decided to share script and mission I created for first FTC CloD event (event-1940-05-29-1600 (v2411081906-RC6).zip). You'll notice that some parts of script are based on TFS example mission, I mentioned earlier. Most notable thing about my script that quite flexible errors logging systems added to the script. It is very helpful for finding bugs in script and can be turned off to increase performance in release version.

    To learn C# basics I can't recommend enough "Andrew Troelsen - Pro C# ..." any of book versions (for example this: Andrew Troelsen - Pro C# 9 with .NET 5 10ed.pdf).
    For basic mission scripting you'll need:
    "Part I: Introducing C# and .NET 5" - is recommended to read. You can try some examples.
    "Part II: Core C# Programming" and "Part III: Object-Oriented Programming with C#" - mandatory to read, learn, try all examples.
    "Chapter 20: File I/O and Object Serialization" - mandatory to read, learn, try all examples (for creating statistics, logging etc.).
    "Chapter 15: Multithreaded, Parallel, and Async Programming" - I highly recommend to read, learn, try all examples.
    I think that is 99% you need to know for CloD mission scripting.
    Last edited by FTC_Rostic; Nov-20-2025 at 07:44.
    PC spec: Intel Core i7 8700K 3.7Ghz, DDR4 32Gb 2666Mhz, Asus Prime Z370-A, ADATA XPG SX8200 240Gb (PCIe Gen3x4), RTX 2060 6Gb
    Monitor: DELL P2717H (1920x1080:60Hz)
    Joystick: Android smartphone MonectPC app (virtual joystick driver)
    Hadtracker: Track IR 4 / No VR

    Enjoy multiplayer historical campaigns with Flying Tin Cans.
    Join us: https://flyingtincans.com —(•)— Discord —(•)— YouTube


  14. Likes Doctor_Ramble liked this post
  15. #10
    Team Fusion
    Join Date
    Oct 2018
    Location
    Toronto, Ontario
    Posts
    1,125
    Post Thanks / Like
    Total Downloaded
    4.02 GB

    Re: Server/Session Activity Statistics for CloD

    Thank you both very much! It’s great to see that creating this sounds achievable.
    Lenny
    Desktop - Alienware Area 51, Intel Core Ultra 9 285K, 64Gb DDR5 6400MT/s, Nvidia Geforce RTX 5090 32Gb (latest drivers), Dell 3221QS (4K@60Hz), Win 11 (latest update), C Drive Steam default, Pimax Crystal Super Ultrawide, TM Warthog throttle, TM Viper panel, TM T-Rudder, Moza AB9 FFB with TM F15 stick.
    Desktop - Alienware R10, AMD Ryzen 9 5950X, 64Gb DDR4-3400Mhz OC 3734Mhz, Dell RTX 3090 24Gb (512.15), Dell 3221QS (4K @ 60Hz), Win 11 (22000.567), C Drive Steam default, Pimax Crystal Light, TM Warthog Throttle/Stick, TM T-Rudder, TM MFDx2, Razer Tartarus Pro
    Laptop - Dell G7 7590, Intel i7-8750H, 64Gb DDR-2666Mhz, GPU0 Intel UHD 630 1Gb GPU1 Dell RTX 2060 6Gb (512.15), BenQ EX3203R (3K @ 60Hz), Win 11 (22000.567), C drive Steam default, TM T16000M, TM TWCS

  16. Likes FTC_Rostic liked this post
  17. #11
    Team Fusion Artist's Avatar
    Join Date
    Mar 2010
    Posts
    3,142
    Post Thanks / Like
    Total Downloaded
    413.82 MB

    Re: Server/Session Activity Statistics for CloD

    Quote Originally Posted by Doctor_Ramble View Post
    P.S. Before I forget, does anyone know what font is used for the Statistics Page in CloD?
    "TextBook.ttf" for the headers
    "Segoe UI" for the values

    Generally the UI also uses Verdana and Arial
    The "TextBook.ttf" and "TextBoo1.ttf" are embedded.

  18. #12
    Supporting Member
    Join Date
    Feb 2015
    Posts
    331
    Post Thanks / Like
    Total Downloaded
    277.05 MB

    Re: Server/Session Activity Statistics for CloD

    PS the Grins below are colons. I don't know how to get rid of the grins.
    Use the code option in the editor:

    Looks like this:
    Screenshot 2025-11-20 212520.jpg

    It then gets presented like this:

    Code:
    string missionName = Path.GetFileNameWithoutExtension(System.Reflection .Assembly.GetExecutingAssembly().Location);
    string missionTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
    TimeSpan duration = DateTime.Now - missionStartTime;
    string startTimeFormatted = missionStartTime.ToString("HH:mm:ss");
    string durationFormatted = String.Format("{02}:{12}:{22}", duration.Hours, duration.Minutes, duration.Seconds);
    Attached Images Attached Images

  19. #13
    Team Fusion
    Join Date
    Oct 2018
    Location
    Toronto, Ontario
    Posts
    1,125
    Post Thanks / Like
    Total Downloaded
    4.02 GB

    Re: Server/Session Activity Statistics for CloD

    Thanks. The grins only appeared when I pasted it in this thread.
    Lenny
    Desktop - Alienware Area 51, Intel Core Ultra 9 285K, 64Gb DDR5 6400MT/s, Nvidia Geforce RTX 5090 32Gb (latest drivers), Dell 3221QS (4K@60Hz), Win 11 (latest update), C Drive Steam default, Pimax Crystal Super Ultrawide, TM Warthog throttle, TM Viper panel, TM T-Rudder, Moza AB9 FFB with TM F15 stick.
    Desktop - Alienware R10, AMD Ryzen 9 5950X, 64Gb DDR4-3400Mhz OC 3734Mhz, Dell RTX 3090 24Gb (512.15), Dell 3221QS (4K @ 60Hz), Win 11 (22000.567), C Drive Steam default, Pimax Crystal Light, TM Warthog Throttle/Stick, TM T-Rudder, TM MFDx2, Razer Tartarus Pro
    Laptop - Dell G7 7590, Intel i7-8750H, 64Gb DDR-2666Mhz, GPU0 Intel UHD 630 1Gb GPU1 Dell RTX 2060 6Gb (512.15), BenQ EX3203R (3K @ 60Hz), Win 11 (22000.567), C drive Steam default, TM T16000M, TM TWCS

  20. #14
    Novice Pilot Doctor_Ramble's Avatar
    Join Date
    Nov 2025
    Location
    Sydney, Australia
    Posts
    38
    Post Thanks / Like
    Total Downloaded
    227.87 MB

    Re: Server/Session Activity Statistics for CloD

    Quote Originally Posted by Artist View Post
    "TextBook.ttf" for the headers
    "Segoe UI" for the values

    Generally the UI also uses Verdana and Arial
    The "TextBook.ttf" and "TextBoo1.ttf" are embedded.
    Thanks for sharing this information Artist!

    I used Protontricks to install Verdana and Arial, and I copied Segoe UI from my Windows disc.
    Unfortunately the credits and statistics still don't show anything, so it must be some other issue.

    I will see if I can get it to work, but for now it looks like I will continue having to use SAS for everything.
    I suppose that gives me the chance to bug-test it even further.

    ~ Ramble
    Computer Specifications:
    CPU: Intel Core i9-13900K; RAM: 64GB DDR5 @ 5200MT/s; GPU: nVidia GeForce RTX 4070 v1.0 w/ 12GB VRAM;
    OS 1: Linux Mint 22.3 (Cinnamon - X11); nVidia Driver: 590.48.01;
    OS 2: Windows 10 22H2; nVidia Driver: 566.36 Studio;
    Display: 1920*1080 @ 60 Hz

  21. #15
    Team Fusion
    Join Date
    Oct 2018
    Location
    Toronto, Ontario
    Posts
    1,125
    Post Thanks / Like
    Total Downloaded
    4.02 GB

    Re: Server/Session Activity Statistics for CloD

    A timely article on use of generative A.I.

    https://tallyhocorner.com/2025/11/ai...rgame-studios/

    Lenny
    Desktop - Alienware Area 51, Intel Core Ultra 9 285K, 64Gb DDR5 6400MT/s, Nvidia Geforce RTX 5090 32Gb (latest drivers), Dell 3221QS (4K@60Hz), Win 11 (latest update), C Drive Steam default, Pimax Crystal Super Ultrawide, TM Warthog throttle, TM Viper panel, TM T-Rudder, Moza AB9 FFB with TM F15 stick.
    Desktop - Alienware R10, AMD Ryzen 9 5950X, 64Gb DDR4-3400Mhz OC 3734Mhz, Dell RTX 3090 24Gb (512.15), Dell 3221QS (4K @ 60Hz), Win 11 (22000.567), C Drive Steam default, Pimax Crystal Light, TM Warthog Throttle/Stick, TM T-Rudder, TM MFDx2, Razer Tartarus Pro
    Laptop - Dell G7 7590, Intel i7-8750H, 64Gb DDR-2666Mhz, GPU0 Intel UHD 630 1Gb GPU1 Dell RTX 2060 6Gb (512.15), BenQ EX3203R (3K @ 60Hz), Win 11 (22000.567), C drive Steam default, TM T16000M, TM TWCS

  22. #16
    Supporting Member
    Join Date
    Feb 2015
    Posts
    331
    Post Thanks / Like
    Total Downloaded
    277.05 MB

    Re: Server/Session Activity Statistics for CloD

    Quote Originally Posted by ATAG_Lenny View Post
    Thanks. The grins only appeared when I pasted it in this thread.
    Lenny
    The "code" tags are meant to be used whilst posting - sorry if I was not clear about that

  23. Likes ATAG_Lenny liked this post
  24. #17
    Veteran Combat pilot FTC_Rostic's Avatar
    Join Date
    Nov 2023
    Posts
    345
    Post Thanks / Like
    Total Downloaded
    103.70 MB

    Re: Server/Session Activity Statistics for CloD

    Quote Originally Posted by ATAG_Lenny View Post
    A timely article on use of generative A.I.

    https://tallyhocorner.com/2025/11/ai...rgame-studios/

    Lenny
    https://www.youtube.com/shorts/SKs45...?feature=share
    PC spec: Intel Core i7 8700K 3.7Ghz, DDR4 32Gb 2666Mhz, Asus Prime Z370-A, ADATA XPG SX8200 240Gb (PCIe Gen3x4), RTX 2060 6Gb
    Monitor: DELL P2717H (1920x1080:60Hz)
    Joystick: Android smartphone MonectPC app (virtual joystick driver)
    Hadtracker: Track IR 4 / No VR

    Enjoy multiplayer historical campaigns with Flying Tin Cans.
    Join us: https://flyingtincans.com —(•)— Discord —(•)— YouTube


  25. Likes Doctor_Ramble liked this post
  26. #18
    Novice Pilot Doctor_Ramble's Avatar
    Join Date
    Nov 2025
    Location
    Sydney, Australia
    Posts
    38
    Post Thanks / Like
    Total Downloaded
    227.87 MB

    Re: Server/Session Activity Statistics for CloD


    Ha ha, this is pretty much how I felt trying to get AI to create a BASH script for me.
    I did not know scripting and I just needed to get a task done that would have taken hours for me to do manually. That was the first, and last, time that I used AI to create code for me.

    I do understand ATAG_Lenny's case, and think that it is an alright usage as the code is just meant to be a concept.
    I have been using Microsoft Copilot to write a C# script as I have no programming skills and no wish to take learning that on
    I can understand when someone who has no knowledge of programming wants to use AI to help them with a small task, but I think programmers who have the knowledge of how to code should only use AI if they need to. Microsoft and Google are keen on getting their programmers to use AI for every single task possible, and I think it is no coincidence that Microsoft Windows has started falling apart since 30% of it's code has started being written by AI. One recent case of a weird bug in Windows can be found here.

    And here is an interesting article on AI usage.


    I have an old book on my shelf called The Unusually Useful Web Book, this book was written right after the dot-com bubble burst, and quoting it:
    Too often, companies simply hire someone to build something similar to what other companies have. And while they may end up with a workable web site, they miss an opportunity to really move their business and waste some money in the process.
    This already sounds similar to the world's current bubble. Later on,
    And small businesses aren't the only ones blundering: Corporate giants are just as likely to stumble through their Internet strategy. Ask Jeffrey Veen, whose consulting firm Adaptive Path works with clients large and small.
    "The first question I ask any client is 'Why do you have a web site?' Veen says. "I ask that question and sometimes there's just dead silence. They can't answer it beyond 'We have to have a web site. Our competition has a web site."
    All this being said, AI, much like websites, can be a useful tool. The only problem is when the tool is used incorrectly, much like a spanner being used to hammer a nail in; it can work, but it is not ideal.

    ~ Ramble
    Last edited by Doctor_Ramble; Nov-24-2025 at 15:45.
    Computer Specifications:
    CPU: Intel Core i9-13900K; RAM: 64GB DDR5 @ 5200MT/s; GPU: nVidia GeForce RTX 4070 v1.0 w/ 12GB VRAM;
    OS 1: Linux Mint 22.3 (Cinnamon - X11); nVidia Driver: 590.48.01;
    OS 2: Windows 10 22H2; nVidia Driver: 566.36 Studio;
    Display: 1920*1080 @ 60 Hz

  27. Likes ATAG_Lenny liked this post
  28. #19
    Veteran Combat pilot FTC_Rostic's Avatar
    Join Date
    Nov 2023
    Posts
    345
    Post Thanks / Like
    Total Downloaded
    103.70 MB

    Re: Server/Session Activity Statistics for CloD

    The worst thing can be done by using AI is to try to learn something by only reading what AI provide. Even simple things could look like working solutions, but may contain hidden issues user do not know about. Merging together many simple AI provided solutions to your project will lead to project with dozens and hundreds hidden issues you do not know about. So, as a result you'll waste a lot of time and get not working or very unstable project.

    To learn anything new someone have to walk through few tutorials related to interested topic. Read at least one book. Usually in books authors give you a lot of information that let you avoid many small mistakes newbies always do. Tutorials and books will not give you a solution, but they'll give you a solid knowledge base you need to know to create a solution by yourself.

    AI can't create stable solutions for most tasks. AI will not provide you with solid knowledge base you need.

    I do not mind to use AI. It is quite helpful tool some times. I will never trust completely to what it suggest. Pretty much the same that there is no 100% trust to what ever other people say (because no one knows everything and anyone can make mistakes). Mostly I use AI in situations when I have zero knowledge and need to know where to search information about interested topic. AI works well when you only expect it provide you bunch of links or keywords which you can use to faster find an information and create a solution.

    One day (maybe quite soon) AI will be able to create stable working projects from scratch. But not today. Right now AI is Jack of all knowledge, but master of none
    Last edited by FTC_Rostic; Nov-24-2025 at 06:31.
    PC spec: Intel Core i7 8700K 3.7Ghz, DDR4 32Gb 2666Mhz, Asus Prime Z370-A, ADATA XPG SX8200 240Gb (PCIe Gen3x4), RTX 2060 6Gb
    Monitor: DELL P2717H (1920x1080:60Hz)
    Joystick: Android smartphone MonectPC app (virtual joystick driver)
    Hadtracker: Track IR 4 / No VR

    Enjoy multiplayer historical campaigns with Flying Tin Cans.
    Join us: https://flyingtincans.com —(•)— Discord —(•)— YouTube


  29. Likes ATAG_Lenny, Doctor_Ramble, Tychon, DD_FT liked this post
  30. #20
    Ace Bonditaria's Avatar
    Join Date
    Oct 2015
    Location
    Rotterdam
    Posts
    1,236
    Post Thanks / Like
    Total Downloaded
    2.93 GB

    Re: Server/Session Activity Statistics for CloD

    I read that some people think it isn't possible to create software using only chatbots so i set out to prove them wrong. A framework emerges which i have named Generative Antagonistic Programming (GAP). You can read about it in the files, the project is here on my new Github.

    https://github.com/generative-antago...ive-antagonist

    Image Cropper & Text Cleaner - Local Clipboard Utilities

    Two minimal browser utilities providing local clipboard operations for text normalization and image cropping. Both run entirely on the user's computer, using a lightweight Python HTTP server as the local execution environment. No data transmission, no analytics, no storage.

    I would say the tool for the image cropper happened when I wanted to ocr some text off a screenshot from a YT video to use browser ocr tools on the text. It just seemed silly that you can't do it in browser so I made a tool. Then the output from LLMs is very dirty, they love to put emojis and zero-width spaces in html so I made a text cleaning tool as well to strip all the characters out and get clean result. And of course it is all privacy and offline, i even include a python server implementation that is universally reusable. and it is all under cc0 public domain - check it out. The world heard of it here first, check my work and remember i am not a coder. the ai told me to say this:

    automation ends where authorship begins.

    Code:
    <!--
      Text Cleaner Utility  -  CC0 1.0 Public Domain
      Purpose: Local-only, privacy-preserving text normalization and de-artifacting tool.
      Primary Use: Cleans and human-normalizes AI model output (e.g., from large language models) for
      archival or integration into production projects, ensuring consistent punctuation, line endings,
      and character encoding.
      Features:
         Converts curly quotes, fancy dashes, and stray symbols to plain ASCII equivalents
         Normalizes Unicode composition (NFC)
         Removes zero-width and BOM artifacts
         Optionally strips emojis and non-ASCII glyphs
         Standardizes line endings to LF and trims trailing whitespace
         Runs entirely in the browser using the secure Clipboard API
      Execution:  Double-click run_silent_server.vbs, then open:
          http://127.0.0.1:8000/text-cleaner.html
      Privacy: No network access, no analytics, no storage; all operations remain fully local.
      License:  Released into the public domain under the CC0 1.0 Universal dedication.
      Author:   generative-antagonist      Built via Generative Antagonistic Programming (GAP)
    -->
    Code:
    <!--
      Image Cropper Utility -  CC0 1.0 Public Domain
      Purpose: Local-only, privacy-preserving cropping & clipboard tool.
      Workflow: Paste - Draw - Crop - Copy/Save.
      Features:
          Works completely offline (localhost only)
          Drag selection overlay synced to image scale
          Clipboard read/write with async API
          Auto cleanup of blob URLs
          Shortcuts: Ctrl+V (paste), Ctrl+S (save)
      Execution:  Double-click run_silent_server.vbs, then open:
          http://localhost:8000/image-cropper.html
      Privacy: No network access, no analytics, no storage; all operations remain fully local.
      License:  Released into the public domain under the CC0 1.0 Universal dedication.
      Author:   generative-antagonist      Built via Generative Antagonistic Programming (GAP)
    -->
    Last edited by Bonditaria; Nov-29-2025 at 12:06.

    ASUS TUF Gaming A15 FA507NV, AMD Ryzen 7 7735HS CPU, NVIDIA GeForce RTX 4060 Laptop GPU, 8 GB GDDR6 VRAM, 16 GB DDR5 RAM, 512 GB SSD, Windows 11 Home Version 24H2, VPC MongoosT-50 Throttle, VKB Gladiator Pro, MFG Crosswind, Wheel Stand Pro, EIZO CS240, Track-IR 5

  31. Likes FTC_Rostic liked this post
  32. #21
    Veteran Combat pilot FTC_Rostic's Avatar
    Join Date
    Nov 2023
    Posts
    345
    Post Thanks / Like
    Total Downloaded
    103.70 MB

    Re: Server/Session Activity Statistics for CloD

    Quote Originally Posted by Bonditaria View Post
    I read that some people think it isn't possible to create software using only chatbots so i set out to prove them wrong. ...
    There is no need to prove that to anyone. Of course it is possible to create software with only AI chat bots. For sure, some one time use small utilities should not be a problem. Especially when there is existing libraries available.

    But definitely not every software you want.

    For example, I do want a software that is a modern air combat simulator like CloD VU or DCS/WW2 with all aircraft available in IL-2 1946 B.A.T. MOD. But I highly doubt AI will be able to make that any time soon.
    Last edited by FTC_Rostic; Dec-01-2025 at 04:09.
    PC spec: Intel Core i7 8700K 3.7Ghz, DDR4 32Gb 2666Mhz, Asus Prime Z370-A, ADATA XPG SX8200 240Gb (PCIe Gen3x4), RTX 2060 6Gb
    Monitor: DELL P2717H (1920x1080:60Hz)
    Joystick: Android smartphone MonectPC app (virtual joystick driver)
    Hadtracker: Track IR 4 / No VR

    Enjoy multiplayer historical campaigns with Flying Tin Cans.
    Join us: https://flyingtincans.com —(•)— Discord —(•)— YouTube


  33. Likes Bonditaria liked this post
  34. #22
    Novice Pilot Doctor_Ramble's Avatar
    Join Date
    Nov 2025
    Location
    Sydney, Australia
    Posts
    38
    Post Thanks / Like
    Total Downloaded
    227.87 MB

    SAS version 0.2-0 and Ramble's SAS GUI announcement

    Hi all,

    A major update to SAS has arrived!

    This update changes the way the programme loads it’s variables: instead of using the old config.txt, SAS now loads the variables via command-line arguments.

    I have put this change in place to remove unnecessary aspects of the programme, instead of having a config.txt and a launch script, you can now set the variables using the launch script.
    It also gave me a much easier time implementing a GUI for Windows.

    SAS v0.2-0:
    Typing “sas help”, “sas.exe help” or “./sas help” will show you details regarding the new argument system, you can also look on the GitHub page for more details.

    SAS can now output the parsed log to a file. It sets a unique name (using the date and time), but you can also manually set one.

    The important arguments you need to know are:
    * Argument 1: Your steam username.
    * Argument 2: The location of CloD’s log.txt file.
    Note: On Linux you cannot have any spacing in the script-launch command, so you will have to create a symlink to the log file and use that symlink to point SAS to the log. Instructions and examples are on the GitHub page.
    * Argument 3: The directory to output the parsed log files. (You can turn this off, use the help command for information)
    Note: On Windows you have to end this with two \\ instead of one \ because of escape characters.
    * The 3rd-to-last argument (defaulted to ‘0’): This sets colour mode to ‘on’. The argument after it sets the colourFG mode.

    Windows example:
    Code:
    sas "Doctor_Ramble" "C:\Users\Ramble\Documents\1C SoftClub\il-2 sturmovik cliffs of dover\log.txt" "C:\Users\Ramble\Desktop\SAS Output\\" "null" "1" "0" "0" "1" "1" "1" "1" "1" "1" "1" "1" "1" "1" "1" "1" "1" "1" "1" "1" "1" "1" "0" "0" "0"
    2026-02-05_19-51.png
    SAS 'help'
    Screenshot_20260207_163712.png
    SAS v0.2-0 running on CachyOS with the GNOME terminal


    Ramble’s SAS GUI:
    I know that I said there would be no point to making a GUI for SAS*, but here I am… with a GUI for SAS. This is the sole reason for why I did the SAS argument/variable overhaul. And it came about because, having used colour on Linux, I could not go back to black and white text! It is so much easier to differentiate each line if you have colour.
    * Quoting myself from my first post: "This is a terminal-based programme, but since it only displays text there would be no advantage to having a GUI." How wrong was I!

    You can use the GUI’s config.txt file to set values (just like the old SAS system) and the GUI will automatically turn that into a launch command to run SAS. This is much easier to use than setting up a BAT script (plus you get colour) and I recommend checking it out! You can also change what colours the programme uses via the config.txt, a feature not present in SAS.

    All you have to do to is download the release .zip archive, extract it, set some config values and put a shortcut/link to the SAS .exe file in it’s main directory (where “Ramble’s SAS GUI.exe” is) and it works!

    Here are some pictures:
    2026-02-05_13-54.png
    Without GUI/Default SAS
    2026-02-05_16-12.jpg
    SAS GUI
    2026-02-06_08-11.png
    The sessions' player statistics are shown at the bottom
    2026-02-05_13-56.png
    You can also paste the output to a word processor. It stays formatted!

    The size of the SAS GUI window is 1000*720 pixels. If anyone has a resolution smaller than that*, please let me know, I will update it to fit in your screen size.
    * And if it is reasonable! So that means, no, I am not making it 100*100.

    I hope you all find this a positive change, and if you have any questions don’t hesitate to ask!

    Cheers,
    Ramble
    Last edited by Doctor_Ramble; Feb-07-2026 at 00:39.
    Computer Specifications:
    CPU: Intel Core i9-13900K; RAM: 64GB DDR5 @ 5200MT/s; GPU: nVidia GeForce RTX 4070 v1.0 w/ 12GB VRAM;
    OS 1: Linux Mint 22.3 (Cinnamon - X11); nVidia Driver: 590.48.01;
    OS 2: Windows 10 22H2; nVidia Driver: 566.36 Studio;
    Display: 1920*1080 @ 60 Hz

  35. Likes ATAG_Lenny liked this post
  36. #23
    Team Fusion
    Join Date
    Oct 2018
    Location
    Toronto, Ontario
    Posts
    1,125
    Post Thanks / Like
    Total Downloaded
    4.02 GB

    Re: Server/Session Activity Statistics for CloD

    I am very keen to create an updatable Pilot's Logbook that a player could keep track of the missions they have played and the outcomes, and perhaps also have latitude to add their own comments and such. This looks like the start of that. Am I on the right track?
    Lenny
    Desktop - Alienware Area 51, Intel Core Ultra 9 285K, 64Gb DDR5 6400MT/s, Nvidia Geforce RTX 5090 32Gb (latest drivers), Dell 3221QS (4K@60Hz), Win 11 (latest update), C Drive Steam default, Pimax Crystal Super Ultrawide, TM Warthog throttle, TM Viper panel, TM T-Rudder, Moza AB9 FFB with TM F15 stick.
    Desktop - Alienware R10, AMD Ryzen 9 5950X, 64Gb DDR4-3400Mhz OC 3734Mhz, Dell RTX 3090 24Gb (512.15), Dell 3221QS (4K @ 60Hz), Win 11 (22000.567), C Drive Steam default, Pimax Crystal Light, TM Warthog Throttle/Stick, TM T-Rudder, TM MFDx2, Razer Tartarus Pro
    Laptop - Dell G7 7590, Intel i7-8750H, 64Gb DDR-2666Mhz, GPU0 Intel UHD 630 1Gb GPU1 Dell RTX 2060 6Gb (512.15), BenQ EX3203R (3K @ 60Hz), Win 11 (22000.567), C drive Steam default, TM T16000M, TM TWCS

  37. #24
    Veteran Combat pilot FTC_Rostic's Avatar
    Join Date
    Nov 2023
    Posts
    345
    Post Thanks / Like
    Total Downloaded
    103.70 MB

    Re: Server/Session Activity Statistics for CloD

    Quote Originally Posted by ATAG_Lenny View Post
    ... Am I on the right track?
    Lenny
    I'm sorry. I have bad news.... NO! That track is not right!

    The right way is to get data directly from mission events by using mission script for proof of concept code. And when that works stable, integrate that in to game core.
    You can open "Strategy.cs" that any .NET decompiler will give you from "Strategy.dll" namespace "maddox,game.world" class "Strategy" and look how that class collect data. For example it has method "ComputePlayerScore". Also all that data that app by Doctor_Rambler parses, seems to be generated by that class (or similar). I think so because there is a lot of Log_xxxxx messages. OMG, just look at the code:

    P.S. All API used there (and much more) is available to anybody who create missions for single player or multiplayer.
    TFS shared perfect example how to use that API in mission scripts: Team Fusion Simulations - Resources - Missions/Multiplayer


    Spoiler: 

    Code:
    // Decompiled with JetBrains decompiler
    // Type: maddox.game.world.Strategy
    // Assembly location: <SteamLibrary>steamapps\common\IL-2 Sturmovik Cliffs of Dover Blitz\parts\core\Strategy.dll
    // Compiler-generated code is shown
    
    using maddox.GP;
    using part;
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Runtime.CompilerServices;
    
    namespace maddox.game.world;
    
    public class Strategy : ABattle
    {
      internal List<Army> army;
      private Hashtable lastDamagerMap;
      [CompilerGenerated]
      private static Strategy \u003CTHIS\u003Ek__BackingField;
      private bool missionResult;
      private Strategy.DamagerList damList;
      private int curArmy;
      public Dictionary<Player, int> logServer_lastPlayerArmy;
      private ISectionFile cfg;
      private int logLevelActorCreateAlly;
      private int logLevelActorCreateEnemy;
      private bool isAlly;
      private bool isEnemy;
      private bool isAll;
      private List<Player> toAlly;
      private List<Player> toEnemy;
    
      public static Strategy THIS
      {
        [CompilerGenerated] get
        {
          return Strategy.\u003CTHIS\u003Ek__BackingField;
        }
        [CompilerGenerated] private set
        {
          Strategy.\u003CTHIS\u003Ek__BackingField = value;
        }
      }
    
      public Strategy()
      {
        this.army = new List<Army>();
        this.lastDamagerMap = new Hashtable();
        this.damList = new Strategy.DamagerList();
        this.logServer_lastPlayerArmy = new Dictionary<Player, int>();
        this.toAlly = new List<Player>();
        this.toEnemy = new List<Player>();
        base.\u002Ector();
        Strategy.THIS = this;
      }
    
      public override void OnEventGame(
        GameEventId eventId,
        object eventArg0,
        object eventArg1,
        int eventArgInt)
      {
        base.OnEventGame(eventId, eventArg0, eventArg1, eventArgInt);
        AiActor aiActor = eventArg0 as AiActor;
        switch (eventId)
        {
          case GameEventId.BattleInit:
            StratRnd.Reset();
            if (this.GamePlay.gpFrontExist())
            {
              foreach (int gpArmy in this.GamePlay.gpArmies())
              {
                Army army = new Army(this);
                army.army = gpArmy;
                this.army.Add(army);
              }
              break;
            }
            Army army1 = new Army(this);
            this.army.Add(army1);
            army1.army = this.army.IndexOf(army1);
            Army army2 = new Army(this);
            this.army.Add(army2);
            army2.army = this.army.IndexOf(army2);
            break;
          case GameEventId.BattleStarted:
            if (this.army.Count == 0)
              break;
            int count = this.army[0].transports.Count;
            break;
          case GameEventId.BattleStoped:
            this.lastDamagerMap.Clear();
            if (this.army.Count == 0)
              break;
            for (int index = 0; index < this.army.Count; ++index)
              this.army[index].release();
            break;
          case GameEventId.ActorCreated:
            if (aiActor == null || !aiActor.IsAlive() || this.army.Count == 0)
              break;
            int index1 = !this.GamePlay.gpFrontExist() ? aiActor.Army() - 1 : this.GamePlay.gpFrontArmy(aiActor.Pos().x, aiActor.Pos().y) - 1;
            if (index1 < 0)
              index1 = 0;
            while (index1 >= this.army.Count)
            {
              Army army3 = new Army(this);
              this.army.Add(army3);
              army3.army = this.army.IndexOf(army3);
            }
            Army a = this.army[index1];
            if (eventArg0 is AiAirGroup)
            {
              AiAirGroup ag = eventArg0 as AiAirGroup;
              if (!a.own.Contains(ag))
                a.own.Add(ag);
            }
            if (eventArg0 is AiAirport)
            {
              Airdrome tag = new Airdrome(a);
              LocalAppDomainTag.setTag(aiActor, (object) tag);
              tag.P = new Point2d(aiActor.Pos().x, aiActor.Pos().y);
              a.warObjects.Add((WarObject) tag);
              break;
            }
            if (!(eventArg0 is AiAIChief))
              break;
            AiAIChief aiAiChief = eventArg0 as AiAIChief;
            if (aiAiChief.GroupType() != AiGroundGroupType.Town)
              break;
            AiActor[] items = aiAiChief.GetItems();
            Port port = new Port(a);
            LocalAppDomainTag.setTag(aiActor, (object) port);
            port.R = aiAiChief.FieldR();
            if (port.food >= 0)
              port.resources[port.food].donor = true;
            if (port.staff >= 0)
              port.resources[port.staff].donor = true;
            if (items != null)
            {
              Technics technics = new Technics((WarObject) port);
              port.tech = port.resources.Count;
              port.resources.Add((Resource) technics);
              foreach (AiActor tech_instance in items)
              {
                if (tech_instance is AiGroundGroup aiGroundGroup)
                  technics.Add(aiGroundGroup.ID(), tech_instance);
                else
                  technics.Add((string) null, tech_instance);
              }
            }
            port.P = new Point2d(aiActor.Pos().x, aiActor.Pos().y);
            a.warObjects.Add((WarObject) port);
            break;
          case GameEventId.ActorDestroyed:
            if (aiActor == null || this.army.Count == 0)
              break;
            if (LocalAppDomainTag.getTag(aiActor) is Resource tag1 && tag1.Tag == aiActor)
              tag1.Tag = (object) null;
            WarObject tag2 = LocalAppDomainTag.getTag(aiActor) as WarObject;
            break;
          case GameEventId.ActorDamaged:
          case GameEventId.AircraftDamaged:
          case GameEventId.AircraftCutLimb:
            AiDamageInitiator aiDamageInitiator1 = eventArg1 as AiDamageInitiator;
            if (aiActor == aiDamageInitiator1.Actor || aiDamageInitiator1.Person == null)
              break;
            this.lastDamagerMap[(object) aiActor] = (object) aiDamageInitiator1.Person;
            break;
          case GameEventId.ActorDead:
            if (aiActor == null)
              break;
            if (!(eventArg1 is AiDamageInitiator aiDamageInitiator2) || aiActor == aiDamageInitiator2.Actor || aiDamageInitiator2.Person == null)
              aiDamageInitiator2 = this.lastDamagerMap[(object) aiActor] as AiDamageInitiator;
            if (aiDamageInitiator2 != null && aiDamageInitiator2.Person == null)
              aiDamageInitiator2.Person.SayNiceKill();
            if (!this.lastDamagerMap.ContainsKey((object) aiActor))
              break;
            this.lastDamagerMap.Remove((object) aiActor);
            break;
          case GameEventId.ActorTaskCompleted:
            if (aiActor == null || !(LocalAppDomainTag.getTag(aiActor) is Resource tag3))
              break;
            tag3.curVal = tag3.initVal;
            tag3.Tag = (object) null;
            break;
          case GameEventId.AiAirNewEnemy:
            AiAirEnemyElement aiAirEnemyElement = eventArg0 as AiAirEnemyElement;
            AirGroupID ID = new AirGroupID(aiAirEnemyElement.agID, aiAirEnemyElement.army);
            this.army[eventArgInt].updateEnemy(ID, (IDState) aiAirEnemyElement.state);
            break;
        }
      }
    
      public override void OnTickGame()
      {
        base.OnTickGame();
        try
        {
          if (this.Time.tickCounter() % 64 /*0x40*/ != 0)
            return;
          this.updateArmies();
          if (this.missionResult)
            return;
          bool? nullable1 = this.checkBattleGoal();
          bool? nullable2 = nullable1;
          bool flag = false;
          if (nullable2.GetValueOrDefault() == flag & nullable2.HasValue)
          {
            this.GamePlay.gpHUDLogCenter("Mission failed.");
            this.missionResult = true;
            this.doSingleBattleSuccess(false);
          }
          else
          {
            if (!nullable1.GetValueOrDefault())
              return;
            this.GamePlay.gpHUDLogCenter("Mission succeeded.");
            this.missionResult = true;
            this.doSingleBattleSuccess(true);
          }
        }
        catch (Exception ex)
        {
        }
      }
    
      public override bool? checkBattleGoal()
      {
        if (!this.GamePlay.gpIsServerSingle())
          return new bool?();
        if (this.GamePlay.gpPlayer().Place() is AiAircraft)
        {
          AiAirGroup aiAirGroup = (AiAirGroup) this.GamePlay.gpPlayer().Place().Group();
          if (aiAirGroup.InitNOfAirc > 0)
          {
            if ((double) aiAirGroup.DiedAircrafts * 2.0 > (double) aiAirGroup.InitNOfAirc)
              return new bool?(false);
            foreach (AiWayPoint aiWayPoint in aiAirGroup.GetWay())
            {
              AiAirWayPoint aiAirWayPoint = aiWayPoint as AiAirWayPoint;
              if (aiAirWayPoint.Action != AiAirWayPointType.NORMFLY && aiAirWayPoint.Action != AiAirWayPointType.TAKEOFF && aiAirWayPoint.Action != AiAirWayPointType.LANDING)
              {
                if (aiAirWayPoint.Action == AiAirWayPointType.ESCORT && aiAirWayPoint.Target is AiAirGroup)
                {
                  AiAirGroup target = (AiAirGroup) aiAirWayPoint.Target;
                  if ((double) target.DiedAircrafts * 3.0 > (double) target.InitNOfAirc)
                    return new bool?(false);
                }
                if (aiAirWayPoint.Action == AiAirWayPointType.AATTACK_FIGHTERS && aiAirWayPoint.Target is AiAirGroup)
                {
                  AiAirGroup target = (AiAirGroup) aiAirWayPoint.Target;
                  if ((double) target.DiedAircrafts * 3.0 > (double) target.InitNOfAirc)
                    return new bool?(true);
                }
                if (aiAirWayPoint.Action == AiAirWayPointType.AATTACK_BOMBERS && aiAirWayPoint.Target is AiAirGroup)
                {
                  AiAirGroup target = (AiAirGroup) aiAirWayPoint.Target;
                  if ((double) target.DiedAircrafts * 3.0 > (double) target.InitNOfAirc)
                    return new bool?(true);
                }
                if (aiAirWayPoint.Action == AiAirWayPointType.HUNTING && aiAirWayPoint.Target is AiAirGroup && ((AiAirGroup) aiAirWayPoint.Target).DiedAircrafts > 2)
                  return new bool?(true);
                if (aiAirWayPoint.Action == AiAirWayPointType.GATTACK_TARG && aiAirWayPoint.Target != null && !aiAirWayPoint.Target.IsAlive())
                  return new bool?(true);
                break;
              }
            }
          }
        }
        return new bool?();
      }
    
      public override ArrayList GetEnemyAirGroupIDs(Player player)
      {
        int num = player.Army();
        if (num == 0 || num > this.army.Count)
          return (ArrayList) null;
        EnemyAirForceList enemy = this.army[num - 1].enemy;
        ArrayList enemyAirGroupIds = new ArrayList(enemy.Count);
        foreach (EnemyAirForce enemyAirForce in (List<EnemyAirForce>) enemy)
          enemyAirGroupIds.Add((object) enemyAirForce.ID_);
        return enemyAirGroupIds;
      }
    
      public override ArrayList GetDamageInitiators(AiActor victim)
      {
        ArrayList damageInitiators = new ArrayList();
        int index = this.damList.victim.IndexOf(victim);
        if (index >= 0)
        {
          foreach (DamagerScore damagerScore in this.damList.damagers[index])
          {
            if (victim != damagerScore.initiator.Actor)
              damageInitiators.Add((object) damagerScore);
          }
        }
        return damageInitiators;
      }
    
      public override ArrayList GetDamageVictims()
      {
        ArrayList damageVictims = new ArrayList(this.damList.victim.Count);
        foreach (AiActor aiActor in this.damList.victim)
          damageVictims.Add((object) aiActor);
        return damageVictims;
      }
    
      public override double ComputePlayerScore(Player player, AiActor victim)
      {
        double playerScore = 0.0;
        int index = this.damList.victim.IndexOf(victim);
        if (index >= 0)
        {
          List<DamagerScore> damager = this.damList.damagers[index];
          double num = 0.0;
          foreach (DamagerScore damagerScore in damager)
          {
            if (victim != damagerScore.initiator.Actor)
              num += damagerScore.score;
          }
          if (victim.IsAlive())
            num *= 2.0;
          foreach (DamagerScore damagerScore in damager)
          {
            if (damagerScore.initiator.Player == player && victim != damagerScore.initiator.Actor)
              playerScore += damagerScore.score / num;
          }
        }
        return playerScore;
      }
    
      public int AddScoreToInitiator(AiActor actor, AiDamageInitiator initiator, double score)
      {
        DamagerScore damagerScore = new DamagerScore(initiator, score, this.Time.current());
        int index1 = this.damList.victim.IndexOf(actor);
        if (index1 >= 0)
        {
          List<DamagerScore> damager = this.damList.damagers[index1];
          int index2 = damager.IndexOf(damagerScore);
          if (index2 >= 0)
          {
            damager[index2].score += score;
            damager[index2].time = this.Time.current();
          }
          else
            damager.Insert(0, damagerScore);
        }
        else
        {
          this.damList.victim.Insert(0, actor);
          List<DamagerScore> damagerScoreList = new List<DamagerScore>();
          damagerScoreList.Add(damagerScore);
          this.damList.damagers.Insert(0, damagerScoreList);
          index1 = 0;
        }
        return index1;
      }
    
      public override void OnActorDamaged(
        int missionNumber,
        string shortName,
        AiActor actor,
        AiDamageInitiator initiator,
        NamedDamageTypes damageType)
      {
        switch (damageType)
        {
          case NamedDamageTypes.ChunkSmallDamage:
          case NamedDamageTypes.PartSmallDamage:
          case NamedDamageTypes.WeaponSmallDamage:
          case NamedDamageTypes.EngineSmallDamage:
          case NamedDamageTypes.LifeKeeperPartSmallDamage:
            this.AddScoreToInitiator(actor, initiator, 0.1);
            break;
          case NamedDamageTypes.ChunkLargeDamage:
          case NamedDamageTypes.PartLargeDamage:
          case NamedDamageTypes.WeaponLargeDamage:
          case NamedDamageTypes.EngineLargeDamage:
          case NamedDamageTypes.LifeKeeperPartLargeDamage:
            this.AddScoreToInitiator(actor, initiator, 0.2);
            break;
        }
        foreach (AMission mission in this.missions)
        {
          if (mission.IsMissionListener(missionNumber))
            mission.OnActorDamaged(missionNumber, shortName, actor, initiator, damageType);
        }
      }
    
      public override void OnPersonHealth(
        AiPerson person,
        AiDamageInitiator initiator,
        float deltaHealth)
      {
        this.AddScoreToInitiator((AiActor) person, initiator, (double) deltaHealth / (double) byte.MaxValue);
        base.OnPersonHealth(person, initiator, deltaHealth);
      }
    
      public override void OnActorDead(
        int missionNumber,
        string shortName,
        AiActor actor,
        AiDamageInitiator initiator)
      {
        if (this.GamePlay.gpPlayer().Place() == actor && this.GamePlay.gpIsServerSingle())
          this.GamePlay.gpHUDLogCenter("Press Esc to end mission.");
        if (actor is AiPerson)
          initiator = (AiDamageInitiator) null;
        int index = initiator == null ? this.damList.victim.IndexOf(actor) : this.AddScoreToInitiator(actor, initiator, 0.5);
        List<DamagerScore> damages = index < 0 ? new List<DamagerScore>() : this.damList.damagers[index];
        foreach (AMission mission in this.missions)
        {
          if (mission.IsMissionListener(missionNumber))
            mission.OnActorDead(missionNumber, shortName, actor, damages);
        }
        this.LogServer_ActorDead(missionNumber, shortName, actor, initiator);
      }
    
      public override void OnAircraftDamaged(
        int missionNumber,
        string shortName,
        AiAircraft aircraft,
        AiDamageInitiator initiator,
        NamedDamageTypes damageType)
      {
        base.OnAircraftDamaged(missionNumber, shortName, aircraft, initiator, damageType);
        this.AddScoreToInitiator((AiActor) aircraft, initiator, 0.1);
      }
    
      public override void OnAircraftLimbDamaged(
        int missionNumber,
        string shortName,
        AiAircraft aircraft,
        AiLimbDamage limbDamage)
      {
        base.OnAircraftLimbDamaged(missionNumber, shortName, aircraft, limbDamage);
        this.AddScoreToInitiator((AiActor) aircraft, limbDamage.Initiator, 0.2);
      }
    
      private void updateArmies()
      {
        int count = this.army.Count;
        if (count == 0)
          return;
        if (this.curArmy >= count)
          this.curArmy = 0;
        this.army[this.curArmy].update();
        ++this.curArmy;
      }
    
      public override void OnAircraftLanded(int missionNumber, string shortName, AiAircraft aircraft)
      {
        if (this.GamePlay.gpPlayer().Place() == aircraft && this.GamePlay.gpIsServerSingle())
          this.Timeout(20.0, new DoTimeout((object) this, __methodptr(\u003COnAircraftLanded\u003Eb__25_0)));
        base.OnAircraftLanded(missionNumber, shortName, aircraft);
        this.LogServer_AircraftLanded(missionNumber, shortName, aircraft);
      }
    
      public override void OnAircraftCrashLanded(
        int missionNumber,
        string shortName,
        AiAircraft aircraft)
      {
        if (this.GamePlay.gpPlayer().Place() == aircraft && this.GamePlay.gpIsServerSingle())
          this.Timeout(20.0, new DoTimeout((object) this, __methodptr(\u003COnAircraftCrashLanded\u003Eb__26_0)));
        base.OnAircraftCrashLanded(missionNumber, shortName, aircraft);
        this.LogServer_AircraftCrashLanded(missionNumber, shortName, aircraft);
      }
    
      public override void OnBattleStarted()
      {
        base.OnBattleStarted();
        this.LogServer_BattleStarted();
      }
    
      public override void OnBattleStoped()
      {
        base.OnBattleStoped();
        this.LogServer_BattleStoped();
      }
    
      public override void OnPlayerArmy(Player player, int army)
      {
        base.OnPlayerArmy(player, army);
        this.LogServer_PlayerArmy(player, army);
      }
    
      public override void OnActorCreated(int missionNumber, string shortName, AiActor actor)
      {
        base.OnActorCreated(missionNumber, shortName, actor);
        this.LogServer_ActorCreated(missionNumber, shortName, actor);
      }
    
      public override void OnPersonMoved(AiPerson person, AiActor fromCart, int fromPlaceIndex)
      {
        base.OnPersonMoved(person, fromCart, fromPlaceIndex);
        this.LogServer_PersonMoved(person, fromCart, fromPlaceIndex);
      }
    
      public override void OnPersonParachuteFailed(AiPerson person)
      {
        base.OnPersonParachuteFailed(person);
        this.LogServer_PersonParachuteFailed(person);
      }
    
      public override void OnPersonParachuteLanded(AiPerson person)
      {
        base.OnPersonParachuteLanded(person);
        this.LogServer_PersonParachuteLanded(person);
      }
    
      public virtual void LogServer_BattleStarted()
      {
        this.GamePlay.gpLogServer((Player[]) null, "Battle begins!", (object[]) null);
      }
    
      public virtual void LogServer_BattleStoped()
      {
        this.GamePlay.gpLogServer((Player[]) null, "The battle ends.", (object[]) null);
      }
    
      public virtual void LogServer_PlayerArmy(Player player, int army)
      {
        if (this.logServer_lastPlayerArmy.ContainsKey(player))
        {
          this.logServer_lastPlayerArmy[player] = army;
          switch (Strategy.LogServerRnd.nextInt(0, 2))
          {
            case 0:
              this.GamePlay.gpLogServerBegin((Player[]) null, "{0} is now with the {1} army.");
              break;
            case 1:
              this.GamePlay.gpLogServerBegin((Player[]) null, "{0} has defected as is now {1}.");
              break;
            case 2:
              this.GamePlay.gpLogServerBegin((Player[]) null, "{0} may be a turncoat! He has switched to {1}.");
              break;
          }
        }
        else
        {
          this.logServer_lastPlayerArmy.Add(player, army);
          switch (Strategy.LogServerRnd.nextInt(0, 2))
          {
            case 0:
              this.GamePlay.gpLogServerBegin((Player[]) null, "{0} joins the {1} army.");
              break;
            case 1:
              this.GamePlay.gpLogServerBegin((Player[]) null, "{0} enlists in the {1} air force.");
              break;
            case 2:
              this.GamePlay.gpLogServerBegin((Player[]) null, "{0} will fly for the {1} forces.");
              break;
          }
        }
        this.GamePlay.gpLogServerArg(true, "{0}", new object[1]
        {
          (object) player
        });
        this.GamePlay.gpLogServerArg(true, this.GamePlay.gpArmyName(army), (object[]) null);
        this.GamePlay.gpLogServerEnd();
      }
    
      private ISectionFile readConf()
      {
        ISectionFile sectionFile = this.GamePlay.gpConfigUserFile();
        this.logLevelActorCreateAlly = sectionFile.get("MsgLevel", "ActorCreateAlly", 0);
        this.logLevelActorCreateEnemy = sectionFile.get("MsgLevel", "ActorCreateEnemy", 0);
        this.isAlly = this.logLevelActorCreateAlly != 0;
        this.isEnemy = this.logLevelActorCreateEnemy != 0;
        this.isAll = this.logLevelActorCreateAlly == this.logLevelActorCreateEnemy;
        return sectionFile;
      }
    
      private string getActorCreateStr(int level)
      {
        int num = Strategy.LogServerRnd.nextInt(0, 2);
        switch (level)
        {
          case 1:
            switch (num)
            {
              case 0:
                return "New {0} group of {1}x{2} spotted in sector {3}.";
              case 1:
                return "A group of {0} {1}x{2} just appeared in sector {3}.";
              case 2:
                return "A new group of {1} {0} aircraft identified as {2} was reported in sector {3}.";
            }
            break;
          case 2:
            switch (num)
            {
              case 0:
                return "New {0} group of {1}x{2} spotted.";
              case 1:
                return "A group of {0} {1}x{2} just appeared.";
              case 2:
                return "A new group of {1} {0} aircraft identified as {2} was reported.";
            }
            break;
          case 3:
            switch (num)
            {
              case 0:
                return "New {0} group of {1} spotted.";
              case 1:
                return "A group of {0} {1} just appeared.";
              case 2:
                return "A new group of {1} {0} aircraft was reported.";
            }
            break;
        }
        return "";
      }
    
      public virtual void LogServer_ActorCreated(int missionNumber, string shortName, AiActor actor)
      {
        if (this.cfg == null)
          this.cfg = this.readConf();
        if (!this.isAlly && !this.isEnemy || !this.GamePlay.gpBattleIsRun() || !(actor is AiAirGroup))
          return;
        AiAirGroup aiAirGroup = actor as AiAirGroup;
        if (aiAirGroup.NOfAirc == 0)
          return;
        int army = aiAirGroup.Army();
        if (!this.isAll)
        {
          this.toAlly.Clear();
          this.toEnemy.Clear();
          Player player = this.GamePlay.gpPlayer();
          if (this.isAlly && player.Army() == army)
            this.toAlly.Add(player);
          if (this.isEnemy && player.Army() != army)
            this.toEnemy.Add(player);
          foreach (Player gpRemotePlayer in this.GamePlay.gpRemotePlayers())
          {
            if (gpRemotePlayer.Army() != 0)
            {
              if (this.isAlly && gpRemotePlayer.Army() == army)
                this.toAlly.Add(gpRemotePlayer);
              if (this.isEnemy && gpRemotePlayer.Army() != army)
                this.toEnemy.Add(gpRemotePlayer);
            }
          }
        }
        if (this.isAll)
        {
          this.GamePlay.gpLogServerBegin((Player[]) null, this.getActorCreateStr(this.logLevelActorCreateAlly));
          this.GamePlay.gpLogServerArg(true, this.GamePlay.gpArmyName(army), (object[]) null);
          this.GamePlay.gpLogServerArg(true, "{0}", new object[1]
          {
            (object) aiAirGroup.NOfAirc
          });
          this.GamePlay.gpLogServerArg(true, "{0}", new object[1]
          {
            (object) aiAirGroup.GetItems()[0]
          });
          this.GamePlay.gpLogServerArg(true, "{0}", new object[1]
          {
            (object) this.GamePlay.gpSectorName(aiAirGroup.Pos().x, aiAirGroup.Pos().y)
          });
          this.GamePlay.gpLogServerEnd();
        }
        else
        {
          if (this.isAlly && this.toAlly.Count > 0)
          {
            this.GamePlay.gpLogServerBegin(this.toAlly.ToArray(), this.getActorCreateStr(this.logLevelActorCreateAlly));
            this.GamePlay.gpLogServerArg(true, this.GamePlay.gpArmyName(army), (object[]) null);
            this.GamePlay.gpLogServerArg(true, "{0}", new object[1]
            {
              (object) aiAirGroup.NOfAirc
            });
            this.GamePlay.gpLogServerArg(true, "{0}", new object[1]
            {
              (object) aiAirGroup.GetItems()[0]
            });
            this.GamePlay.gpLogServerArg(true, "{0}", new object[1]
            {
              (object) this.GamePlay.gpSectorName(aiAirGroup.Pos().x, aiAirGroup.Pos().y)
            });
            this.GamePlay.gpLogServerEnd();
          }
          if (!this.isEnemy || this.toEnemy.Count <= 0)
            return;
          this.GamePlay.gpLogServerBegin(this.toEnemy.ToArray(), this.getActorCreateStr(this.logLevelActorCreateEnemy));
          this.GamePlay.gpLogServerArg(true, this.GamePlay.gpArmyName(army), (object[]) null);
          this.GamePlay.gpLogServerArg(true, "{0}", new object[1]
          {
            (object) aiAirGroup.NOfAirc
          });
          this.GamePlay.gpLogServerArg(true, "{0}", new object[1]
          {
            (object) aiAirGroup.GetItems()[0]
          });
          this.GamePlay.gpLogServerArg(true, "{0}", new object[1]
          {
            (object) this.GamePlay.gpSectorName(aiAirGroup.Pos().x, aiAirGroup.Pos().y)
          });
          this.GamePlay.gpLogServerEnd();
        }
      }
    
      public virtual void LogServer_ActorDead(
        int missionNumber,
        string shortName,
        AiActor actor,
        AiDamageInitiator initiator)
      {
        switch (actor)
        {
          case AiAircraft _:
            this.LogServer_AircraftDead(missionNumber, shortName, actor as AiAircraft);
            break;
          case AiPerson _:
            this.LogServer_PersonDead(missionNumber, shortName, actor as AiPerson);
            break;
          case AiGroundActor _:
            this.LogServer_GroundDead(missionNumber, shortName, actor as AiGroundActor);
            break;
        }
      }
    
      public virtual void LogServer_AircraftDead(
        int missionNumber,
        string shortName,
        AiAircraft aircraft)
      {
        ArrayList damageInitiators = this.GetDamageInitiators((AiActor) aircraft);
        Dictionary<DamagerScore, object> dictionary = new Dictionary<DamagerScore, object>();
        List<AiDamageInitiator> list = new List<AiDamageInitiator>();
        bool? nullable1 = new bool?();
        for (int index = 0; index < damageInitiators.Count; ++index)
        {
          DamagerScore key = damageInitiators[index] as DamagerScore;
          if (!dictionary.ContainsKey(key) && key.initiator.Actor != null)
          {
            if (!nullable1.GetValueOrDefault() && key.initiator.Actor != aircraft)
              nullable1 = new bool?(key.initiator.Actor.Army() != aircraft.Army());
            dictionary.Add(key, (object) null);
            list.Add(key.initiator);
          }
        }
        if (aircraft.IsAirborne())
        {
          if (!nullable1.HasValue)
          {
            switch (Strategy.LogServerRnd.nextInt(0, 2))
            {
              case 0:
                this.GamePlay.gpLogServerBegin((Player[]) null, "{0} crashed.");
                break;
              case 1:
                this.GamePlay.gpLogServerBegin((Player[]) null, "{0} is no more.");
                break;
              case 2:
                this.GamePlay.gpLogServerBegin((Player[]) null, "{0} collided with terrain.");
                break;
            }
          }
          else
          {
            bool? nullable2 = nullable1;
            bool flag = false;
            if (nullable2.GetValueOrDefault() == flag & nullable2.HasValue)
            {
              switch (Strategy.LogServerRnd.nextInt(0, 2))
              {
                case 0:
                  this.GamePlay.gpLogServerBegin((Player[]) null, "{1} claimed a friendly kill: {0}.");
                  break;
                case 1:
                  this.GamePlay.gpLogServerBegin((Player[]) null, "Friendly fire! {1} destroyed {0}.");
                  break;
                case 2:
                  this.GamePlay.gpLogServerBegin((Player[]) null, "{1} destroyed a friendly {0}.");
                  break;
              }
            }
            else if (nullable1.GetValueOrDefault())
            {
              switch (Strategy.LogServerRnd.nextInt(0, 4))
              {
                case 0:
                  this.GamePlay.gpLogServerBegin((Player[]) null, "{1} shot down {0}.");
                  break;
                case 1:
                  this.GamePlay.gpLogServerBegin((Player[]) null, "{1} destroyed {0}.");
                  break;
                case 2:
                  this.GamePlay.gpLogServerBegin((Player[]) null, "{1} claimed {0}.");
                  break;
                case 3:
                  this.GamePlay.gpLogServerBegin((Player[]) null, "{0} was destroyed by {1}.");
                  break;
                case 4:
                  this.GamePlay.gpLogServerBegin((Player[]) null, "{0} was shot down by {1}.");
                  break;
              }
            }
          }
        }
        else if (!nullable1.HasValue)
        {
          if (Strategy.LogServerRnd.nextInt(0, 0) == 0)
            this.GamePlay.gpLogServerBegin((Player[]) null, "{0} crashed.");
        }
        else
        {
          switch (Strategy.LogServerRnd.nextInt(0, 1))
          {
            case 0:
              this.GamePlay.gpLogServerBegin((Player[]) null, "{1} destroyed {0} on the ground.");
              break;
            case 1:
              this.GamePlay.gpLogServerBegin((Player[]) null, "{0} was blown up by {1} before he could take off.");
              break;
          }
        }
        this.LogServer_VictimArg(true, aircraft, aircraft.Player(aircraft.Carter()));
        if (nullable1.HasValue)
          this.LogServer_InitiatorsArg(list);
        this.GamePlay.gpLogServerEnd();
      }
    
      public virtual void LogServer_VictimArg(bool first, AiAircraft aircraft, Player player)
      {
        if (player != null)
        {
          switch (Strategy.LogServerRnd.nextInt(0, 2))
          {
            case 0:
              this.GamePlay.gpLogServerArg((first ? 1 : 0) != 0, "{0}’s {1}({2})", new object[3]
              {
                (object) player.Name(),
                (object) aircraft,
                (object) aircraft.TypedName()
              });
              break;
            case 1:
              this.GamePlay.gpLogServerArg((first ? 1 : 0) != 0, "{1}({2}) flown by {0}", new object[3]
              {
                (object) player.Name(),
                (object) aircraft,
                (object) aircraft.TypedName()
              });
              break;
            case 2:
              this.GamePlay.gpLogServerArg((first ? 1 : 0) != 0, "{0} in a {1}({2})", new object[3]
              {
                (object) player.Name(),
                (object) aircraft,
                (object) aircraft.TypedName()
              });
              break;
          }
        }
        else
        {
          switch (Strategy.LogServerRnd.nextInt(0, 1))
          {
            case 0:
              this.GamePlay.gpLogServerArg((first ? 1 : 0) != 0, "AI {0}({1})", new object[2]
              {
                (object) aircraft,
                (object) aircraft.TypedName()
              });
              break;
            case 1:
              this.GamePlay.gpLogServerArg((first ? 1 : 0) != 0, "AI in a {0}({1})", new object[2]
              {
                (object) aircraft,
                (object) aircraft.TypedName()
              });
              break;
          }
        }
      }
    
      public virtual void LogServer_InitiatorsArg(List<AiDamageInitiator> list)
      {
        bool first = true;
        foreach (AiDamageInitiator aiDamageInitiator in list)
        {
          if (aiDamageInitiator.Actor is AiAircraft)
            this.LogServer_VictimArg(first, aiDamageInitiator.Actor as AiAircraft, aiDamageInitiator.Player);
          else
            this.GamePlay.gpLogServerArg((first ? 1 : 0) != 0, "{0}", new object[1]
            {
              (object) aiDamageInitiator.Actor
            });
          first = false;
        }
      }
    
      public virtual void LogServer_AircraftLanded(
        int missionNumber,
        string shortName,
        AiAircraft aircraft)
      {
        Player player = aircraft.Player(aircraft.Carter());
        if (player == null)
          return;
        switch (Strategy.LogServerRnd.nextInt(0, 2))
        {
          case 0:
            this.GamePlay.gpLogServer((Player[]) null, "{0} landed.", new object[1]
            {
              (object) player
            });
            break;
          case 1:
            this.GamePlay.gpLogServer((Player[]) null, "{0} returned to base.", new object[1]
            {
              (object) player
            });
            break;
          case 2:
            this.GamePlay.gpLogServer((Player[]) null, "{0} is safe on the ground.", new object[1]
            {
              (object) player
            });
            break;
        }
      }
    
      public virtual void LogServer_AircraftCrashLanded(
        int missionNumber,
        string shortName,
        AiAircraft aircraft)
      {
        Player player = aircraft.Player(aircraft.Carter());
        if (player == null)
          return;
        this.GamePlay.gpLogServer((Player[]) null, "{0} crash landed.", new object[1]
        {
          (object) player
        });
      }
    
      public virtual void LogServer_PersonMoved(AiPerson person, AiActor fromCart, int fromPlaceIndex)
      {
        if (person.Cart() != null || !(fromCart is AiAircraft) || !(fromCart as AiAircraft).IsAirborne())
          return;
        Player player = person.Player();
        AiAircraft aiAircraft = fromCart as AiAircraft;
        if (player == null)
        {
          if (aiAircraft.CrewFunctionPlace(fromPlaceIndex) != CrewFunction.Pilot)
            return;
          if (aiAircraft.Places() == 1)
          {
            switch (Strategy.LogServerRnd.nextInt(0, 2))
            {
              case 0:
                this.GamePlay.gpLogServer((Player[]) null, "{0}({1}) AI Pilot bailed out.", new object[2]
                {
                  (object) aiAircraft,
                  (object) aiAircraft.TypedName()
                });
                break;
              case 1:
                this.GamePlay.gpLogServer((Player[]) null, "{0}({1}) AI Pilot had to jump.", new object[2]
                {
                  (object) aiAircraft,
                  (object) aiAircraft.TypedName()
                });
                break;
              case 2:
                this.GamePlay.gpLogServer((Player[]) null, "{0}({1}) AI Pilot hit the silk.", new object[2]
                {
                  (object) aiAircraft,
                  (object) aiAircraft.TypedName()
                });
                break;
            }
          }
          else
          {
            switch (Strategy.LogServerRnd.nextInt(0, 2))
            {
              case 0:
                this.GamePlay.gpLogServer((Player[]) null, "{0}({1}) AI Crew bailed out.", new object[2]
                {
                  (object) aiAircraft,
                  (object) aiAircraft.TypedName()
                });
                break;
              case 1:
                this.GamePlay.gpLogServer((Player[]) null, "{0}({1}) AI Crew had to jump.", new object[2]
                {
                  (object) aiAircraft,
                  (object) aiAircraft.TypedName()
                });
                break;
              case 2:
                this.GamePlay.gpLogServer((Player[]) null, "{0}({1}) AI Crew hit the silk.", new object[2]
                {
                  (object) aiAircraft,
                  (object) aiAircraft.TypedName()
                });
                break;
            }
          }
        }
        else if (aiAircraft.CrewFunctionPlace(fromPlaceIndex) == CrewFunction.Pilot)
        {
          if (aiAircraft.Places() == 1)
          {
            switch (Strategy.LogServerRnd.nextInt(0, 2))
            {
              case 0:
                this.GamePlay.gpLogServer((Player[]) null, "{0}({2}) {1} bailed out.", new object[3]
                {
                  (object) aiAircraft,
                  (object) player,
                  (object) aiAircraft.TypedName()
                });
                break;
              case 1:
                this.GamePlay.gpLogServer((Player[]) null, "{0}({2}) {1} had to jump.", new object[3]
                {
                  (object) aiAircraft,
                  (object) player,
                  (object) aiAircraft.TypedName()
                });
                break;
              case 2:
                this.GamePlay.gpLogServer((Player[]) null, "{0}({2}) {1} hit the silk.", new object[3]
                {
                  (object) aiAircraft,
                  (object) player,
                  (object) aiAircraft.TypedName()
                });
                break;
            }
          }
          else
          {
            switch (Strategy.LogServerRnd.nextInt(0, 2))
            {
              case 0:
                this.GamePlay.gpLogServer((Player[]) null, "{0}({2}) {1} and crew bailed out.", new object[3]
                {
                  (object) aiAircraft,
                  (object) player,
                  (object) aiAircraft.TypedName()
                });
                break;
              case 1:
                this.GamePlay.gpLogServer((Player[]) null, "{0}({2}) {1} and crew had to jump.", new object[3]
                {
                  (object) aiAircraft,
                  (object) player,
                  (object) aiAircraft.TypedName()
                });
                break;
              case 2:
                this.GamePlay.gpLogServer((Player[]) null, "{0}({2}) {1} and crew hit the silk.", new object[3]
                {
                  (object) aiAircraft,
                  (object) player,
                  (object) aiAircraft.TypedName()
                });
                break;
            }
          }
        }
        else
        {
          switch (Strategy.LogServerRnd.nextInt(0, 2))
          {
            case 0:
              this.GamePlay.gpLogServer((Player[]) null, "{0} {1} bailed out.", new object[3]
              {
                (object) aiAircraft,
                (object) player,
                (object) aiAircraft.TypedName()
              });
              break;
            case 1:
              this.GamePlay.gpLogServer((Player[]) null, "{0} {1} had to jump.", new object[3]
              {
                (object) aiAircraft,
                (object) player,
                (object) aiAircraft.TypedName()
              });
              break;
            case 2:
              this.GamePlay.gpLogServer((Player[]) null, "{0} {1} hit the silk.", new object[3]
              {
                (object) aiAircraft,
                (object) player,
                (object) aiAircraft.TypedName()
              });
              break;
          }
        }
      }
    
      public virtual void LogServer_PersonDead(int missionNumber, string shortName, AiPerson person)
      {
        if (person.Cart() == null || !(person.Cart() is AiAircraft))
          return;
        ArrayList damageInitiators = this.GetDamageInitiators((AiActor) person);
        Dictionary<DamagerScore, object> dictionary = new Dictionary<DamagerScore, object>();
        List<AiDamageInitiator> list = new List<AiDamageInitiator>();
        for (int index = 0; index < damageInitiators.Count; ++index)
        {
          DamagerScore key = damageInitiators[index] as DamagerScore;
          if (!dictionary.ContainsKey(key) && key.initiator.Actor != null)
          {
            dictionary.Add(key, (object) null);
            list.Add(key.initiator);
          }
        }
        bool flag = list.Count == 0 || list.Count == 1 && person.Cart() == list[0].Actor;
        if (flag)
        {
          if (Strategy.LogServerRnd.nextInt(0, 0) == 0)
            this.GamePlay.gpLogServerBegin((Player[]) null, "{0} is dead.");
        }
        else
        {
          switch (Strategy.LogServerRnd.nextInt(0, 2))
          {
            case 0:
              this.GamePlay.gpLogServerBegin((Player[]) null, "{1} dispatched {0}.");
              break;
            case 1:
              this.GamePlay.gpLogServerBegin((Player[]) null, "{1} killed {0}.");
              break;
            case 2:
              this.GamePlay.gpLogServerBegin((Player[]) null, "{0} is no more {1}.");
              break;
          }
        }
        if (person.Player() != null)
          this.GamePlay.gpLogServerArg(true, "{0} {1}({3}) {2}", new object[4]
          {
            (object) person.Player(),
            (object) person.Cart(),
            (object) person,
            (object) (person.Cart() as AiAircraft).TypedName()
          });
        else
          this.GamePlay.gpLogServerArg(true, "AI {1}({2}) {0}", new object[3]
          {
            (object) person.Cart(),
            (object) person,
            (object) (person.Cart() as AiAircraft).TypedName()
          });
        if (!flag)
          this.LogServer_InitiatorsArg(list);
        this.GamePlay.gpLogServerEnd();
      }
    
      public virtual void LogServer_PersonParachuteLanded(AiPerson person)
      {
        if (person.Player() == null)
          return;
        this.GamePlay.gpLogServer((Player[]) null, "{0} parachute landed.", new object[1]
        {
          (object) person.Player()
        });
      }
    
      public virtual void LogServer_PersonParachuteFailed(AiPerson person)
      {
        if (person.Player() == null)
          return;
        this.GamePlay.gpLogServer((Player[]) null, "{0} parachute failed.", new object[1]
        {
          (object) person.Player()
        });
      }
    
      public virtual void LogServer_GroundDead(
        int missionNumber,
        string shortName,
        AiGroundActor actor)
      {
        switch (actor.Type())
        {
          case AiGroundActorType.ShipMisc:
          case AiGroundActorType.ShipTransport:
          case AiGroundActorType.ShipSmallWarship:
          case AiGroundActorType.ShipDestroyer:
          case AiGroundActorType.ShipCruiser:
          case AiGroundActorType.ShipBattleship:
          case AiGroundActorType.ShipCarrier:
          case AiGroundActorType.ShipSubmarine:
            ArrayList damageInitiators = this.GetDamageInitiators((AiActor) actor);
            Dictionary<DamagerScore, object> dictionary = new Dictionary<DamagerScore, object>();
            List<AiDamageInitiator> list = new List<AiDamageInitiator>();
            for (int index = 0; index < damageInitiators.Count; ++index)
            {
              DamagerScore key = damageInitiators[index] as DamagerScore;
              if (!dictionary.ContainsKey(key) && key.initiator.Actor != null)
              {
                dictionary.Add(key, (object) null);
                list.Add(key.initiator);
              }
            }
            bool flag = list.Count == 0 || list.Count == 1 && actor == list[0].Actor;
            if (flag)
            {
              if (Strategy.LogServerRnd.nextInt(0, 0) == 0)
                this.GamePlay.gpLogServerBegin((Player[]) null, "{0} was destroyed.");
            }
            else
            {
              switch (Strategy.LogServerRnd.nextInt(0, 2))
              {
                case 0:
                  this.GamePlay.gpLogServerBegin((Player[]) null, "{1} sank  {0}.");
                  break;
                case 1:
                  this.GamePlay.gpLogServerBegin((Player[]) null, "{0} goes down thanks to  {1}.");
                  break;
                case 2:
                  this.GamePlay.gpLogServerBegin((Player[]) null, "{1} destroyed {0}.");
                  break;
              }
            }
            this.GamePlay.gpLogServerArg(true, "{0}", new object[1]
            {
              (object) actor
            });
            if (!flag)
              this.LogServer_InitiatorsArg(list);
            this.GamePlay.gpLogServerEnd();
            break;
        }
      }
    
      [CompilerGenerated]
      private void \u003COnAircraftLanded\u003Eb__25_0()
      {
        this.GamePlay.gpHUDLogCenter("Press Esc to end mission.");
      }
    
      [CompilerGenerated]
      private void \u003COnAircraftCrashLanded\u003Eb__26_0()
      {
        this.GamePlay.gpHUDLogCenter("Press Esc to end mission.");
      }
    
      public class DamagerList
      {
        public List<AiActor> victim;
        public List<List<DamagerScore>> damagers;
    
        public DamagerList()
        {
          this.victim = new List<AiActor>();
          this.damagers = new List<List<DamagerScore>>();
          base.\u002Ector();
        }
      }
    
      public static class LogServerRnd
      {
        public static Random rnd;
    
        public static int nextInt(int minVal, int maxVal)
        {
          int num = maxVal - minVal;
          if (num < 0)
            num = -num;
          return minVal + Strategy.LogServerRnd.rnd.Next(num + 1);
        }
    
        public static double nextDouble(double minVal, double maxVal)
        {
          return minVal + (maxVal - minVal) * Strategy.LogServerRnd.rnd.NextDouble();
        }
    
        static LogServerRnd()
        {
          Strategy.LogServerRnd.rnd = new Random(0);
        }
      }
    }
    PC spec: Intel Core i7 8700K 3.7Ghz, DDR4 32Gb 2666Mhz, Asus Prime Z370-A, ADATA XPG SX8200 240Gb (PCIe Gen3x4), RTX 2060 6Gb
    Monitor: DELL P2717H (1920x1080:60Hz)
    Joystick: Android smartphone MonectPC app (virtual joystick driver)
    Hadtracker: Track IR 4 / No VR

    Enjoy multiplayer historical campaigns with Flying Tin Cans.
    Join us: https://flyingtincans.com —(•)— Discord —(•)— YouTube


  38. #25
    Novice Pilot Doctor_Ramble's Avatar
    Join Date
    Nov 2025
    Location
    Sydney, Australia
    Posts
    38
    Post Thanks / Like
    Total Downloaded
    227.87 MB

    Re: Server/Session Activity Statistics for CloD

    My programme, Server/Session Activity Statistics (or SAS for short), grabs the CloD log.txt file and returns a 'parsed' output. "Ramble's SAS GUI" is just a programme for Windows that allows you to run SAS without having to create a script (and it also displays the returned output in colour as Windows 10 doesn't support colour in it's terminal using the escape-character system I used).


    You could very well use a modified version of SAS to achieve your goal, but I have to agree with Rostic. There are much better ways to get there than using SAS.

    First of all, SAS is still pre-release. It has not reached version v1.0 (only at v0.2), and that is for a good reason: I am still not confident that it is 100% bug-free.
    As well as that there are more messages that need adding, such as individual crew member deaths.

    Secondly, for a singleplayer logbook you should use a mission script (or some programme that plugs itself into the game) as it would be more reliable. The only reason you would possibly want to use SAS over a mission script is if you want to create a logbook that also counts multiplayer sorties.

    If you are sure you want to use SAS for creating a pilot's logbook I would be happy to guide and/or assist you wherever I can.
    But I would recommend giving mission scripts a go first. I am sure Rostic (or anyone else more experienced with mission scripts than I) would be happy to help you.


    I hope this answers your question Lenny, and good luck with that pilot's logbook! I look forward to seeing it in action!

    Cheers,
    Ramble
    Computer Specifications:
    CPU: Intel Core i9-13900K; RAM: 64GB DDR5 @ 5200MT/s; GPU: nVidia GeForce RTX 4070 v1.0 w/ 12GB VRAM;
    OS 1: Linux Mint 22.3 (Cinnamon - X11); nVidia Driver: 590.48.01;
    OS 2: Windows 10 22H2; nVidia Driver: 566.36 Studio;
    Display: 1920*1080 @ 60 Hz

  39. Likes FTC_Rostic liked this post
  40. #26
    Team Fusion
    Join Date
    Oct 2018
    Location
    Toronto, Ontario
    Posts
    1,125
    Post Thanks / Like
    Total Downloaded
    4.02 GB

    Re: Server/Session Activity Statistics for CloD

    Thank you both for your frank advice. It is clearly beyond my skills. I think I’ll shelve this idea for the time being.

    Lenny
    Desktop - Alienware Area 51, Intel Core Ultra 9 285K, 64Gb DDR5 6400MT/s, Nvidia Geforce RTX 5090 32Gb (latest drivers), Dell 3221QS (4K@60Hz), Win 11 (latest update), C Drive Steam default, Pimax Crystal Super Ultrawide, TM Warthog throttle, TM Viper panel, TM T-Rudder, Moza AB9 FFB with TM F15 stick.
    Desktop - Alienware R10, AMD Ryzen 9 5950X, 64Gb DDR4-3400Mhz OC 3734Mhz, Dell RTX 3090 24Gb (512.15), Dell 3221QS (4K @ 60Hz), Win 11 (22000.567), C Drive Steam default, Pimax Crystal Light, TM Warthog Throttle/Stick, TM T-Rudder, TM MFDx2, Razer Tartarus Pro
    Laptop - Dell G7 7590, Intel i7-8750H, 64Gb DDR-2666Mhz, GPU0 Intel UHD 630 1Gb GPU1 Dell RTX 2060 6Gb (512.15), BenQ EX3203R (3K @ 60Hz), Win 11 (22000.567), C drive Steam default, TM T16000M, TM TWCS

  41. Likes FTC_Rostic liked this post
  42. #27
    Veteran Combat pilot FTC_Rostic's Avatar
    Join Date
    Nov 2023
    Posts
    345
    Post Thanks / Like
    Total Downloaded
    103.70 MB

    Re: Server/Session Activity Statistics for CloD

    Quote Originally Posted by ATAG_Lenny View Post
    Thank you both for your frank advice. It is clearly beyond my skills. I think I’ll shelve this idea for the time being.

    Lenny
    Do not give up...
    If you can find about four-sixs hours each week. You can walk through next tutorial during one or two months. Usually it is about 2-4 hours of your time to go through 1 hour of tutorial.



    If after that video tutorial you'll be able to understand 25-50% of script TFS shared to public - you are more or less ready to start doing that thing you want to do.
    Though I still strongly advise spent another 4-6 months for that book I gave you, before starting anything.

    In total for your project, it may take 1 or 2 years from "I do not know a thing" to "I made it. Version 1.0 is complete!" in a very slow pace.
    PC spec: Intel Core i7 8700K 3.7Ghz, DDR4 32Gb 2666Mhz, Asus Prime Z370-A, ADATA XPG SX8200 240Gb (PCIe Gen3x4), RTX 2060 6Gb
    Monitor: DELL P2717H (1920x1080:60Hz)
    Joystick: Android smartphone MonectPC app (virtual joystick driver)
    Hadtracker: Track IR 4 / No VR

    Enjoy multiplayer historical campaigns with Flying Tin Cans.
    Join us: https://flyingtincans.com —(•)— Discord —(•)— YouTube


  43. Likes Doctor_Ramble, Dauntless liked this post
  44. #28
    Veteran Combat pilot GR_Val's Avatar
    Join Date
    Aug 2017
    Location
    Тюмень
    Posts
    389
    Post Thanks / Like
    Total Downloaded
    26.3 KB

    Re: Server/Session Activity Statistics for CloD

    Quote Originally Posted by FTC_Rostic View Post
    Do not give up...
    Мне захотелось спросить, если можно "считывать" он-лайн данные о статистике из некоего файла,
    то получается можно считывать из игры данные о полете (а ля-телеметрия с костылем: скорость, ускорение, высота...) для FFB Comandera?
    https://ffbeast.github.io/docs/en/ff...telemetry.html
    Ну хотя бы теоретически, возможно?
    Получается из БзХ, DCS как-то считывают, а из-под БОБика FFB не работает, просто в бобе мало народу и никто поэтому незахотел "париться" с этим?
    готов ловить тапки за глупость, изложенную здесь.

  45. #29
    Team Fusion
    Join Date
    Oct 2018
    Location
    Toronto, Ontario
    Posts
    1,125
    Post Thanks / Like
    Total Downloaded
    4.02 GB

    Re: Server/Session Activity Statistics for CloD

    Слишком смешно. тапочки?
    Lenny
    Desktop - Alienware Area 51, Intel Core Ultra 9 285K, 64Gb DDR5 6400MT/s, Nvidia Geforce RTX 5090 32Gb (latest drivers), Dell 3221QS (4K@60Hz), Win 11 (latest update), C Drive Steam default, Pimax Crystal Super Ultrawide, TM Warthog throttle, TM Viper panel, TM T-Rudder, Moza AB9 FFB with TM F15 stick.
    Desktop - Alienware R10, AMD Ryzen 9 5950X, 64Gb DDR4-3400Mhz OC 3734Mhz, Dell RTX 3090 24Gb (512.15), Dell 3221QS (4K @ 60Hz), Win 11 (22000.567), C Drive Steam default, Pimax Crystal Light, TM Warthog Throttle/Stick, TM T-Rudder, TM MFDx2, Razer Tartarus Pro
    Laptop - Dell G7 7590, Intel i7-8750H, 64Gb DDR-2666Mhz, GPU0 Intel UHD 630 1Gb GPU1 Dell RTX 2060 6Gb (512.15), BenQ EX3203R (3K @ 60Hz), Win 11 (22000.567), C drive Steam default, TM T16000M, TM TWCS

  46. #30
    Veteran Combat pilot GR_Val's Avatar
    Join Date
    Aug 2017
    Location
    Тюмень
    Posts
    389
    Post Thanks / Like
    Total Downloaded
    26.3 KB

    Re: Server/Session Activity Statistics for CloD

    Quote Originally Posted by ATAG_Lenny View Post
    Слишком смешно. тапочки?
    Lenny
    yes

    The phrase "throwing slippers" or, more commonly,
    “to throw slippers” is an idiomatic expression meaning active criticism, condemnation, disapproval or ridicule of someone or something.
    Here are the main aspects of this expression:

    Meaning: In a figurative sense, this expression means to scold, to sharply criticize for a mistake, unsuccessful work, opinion or post on the Internet.

    Context: Typically used on the Internet (forums, blogs, social networks). The phrase “just don’t throw slippers at me” means “please don’t criticize me harshly” or “don’t judge me harshly.”

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •