MAN NL/NG AI enhancements

  • This modification provides two enhanced, AI-only versions of the standard M+R MAN NL/NG buses; the goal being, as usual, to render the AI a tiny bit more realistic / intelligent. A secondary goal is to discover just how complex AI scripts can get without bringing about major performance loss.

    To use this modification:

    • Download its files from here.
    • Extract the downloaded compressed archive's Vehicles and Scripts directories into your root OMSI 2 directory (no standard content will be modified). If updating from a previous release, or otherwise re-installing, always overwrite any files remaining from previous installations.
    • Declare <OMSI>\Vehicles\MAN_NL_NG\MAN_EN92_aipp.bus and/or <OMSI>\Vehicles\MAN_NL_NG\MAN_GN92_aipp_main.bus in your map's ailists.cfg.

    Disclaimers:

    • Use at own risk.
    • Expect a (non-severe) performance degradation.
    • The current release is an alpha (and will probably remain one for a long time), so expect bugs and poorly-written code.
    • I can neither guarantee support provision for, nor further development of this modification.
  • This is a great list of things to improve realism of the AI traffic. I never knew this could be done although it is a shame that probably a view people will see everything of these features when driving their own bus. But I like the fact for instance that the AI bus will now only open the front door instead of all doors. By the way, a dedicated busdriver should always use indicators but in real life he or she also "forgets" it sometimes. That is so not professional.

    Die Signatur ist derzeit unterwegs mit einem unbekannten Bus mit einem unbekannten Ziel.

  • Thanks! It would indeed be nice if more people were in general willing to explore scripting beyond what was originally implemented by M+R, as the possibilities are--almost--limitless. I do agree that it's to some extent a lost cause in the particular instance though, due to--as you argued yourself--the average user being unlikely to ever notice, or, in the first place, particularly care about, such details. Regarding usage of indicators--and while the extent of driver forgetfulness varies by country, training and legislation, of course--and, generally, the human factor, it's precisely such instances of "real-world imperfection" that I personally enjoy seeing recreated in a simulator. :)

  • 1.0.2-alpha is out—and the bug hunt continues... :)


    In this post I would like to expand a bit on the per-map extensibility feature of the lighting subsystem mentioned in the changelog.


    Background
    Generally speaking, one of my ultimate goals for a stable 1.x version—should the project ever reach that stage—is for as many aspects of the scripts as possible to be customizable. In many cases customization can easily be attained via constants, or, if map-specific behavior is desired, via so-called ".hof-defined constants", thanks to SchulterSack's brilliant idea. Nevertheless, there are cases where constants are inadequate for expressing complex logic. There are different approaches to this problem, the most common of which is to simply create duplicate .bus files, wherein some of the scripts vary per map. The approach I personally favor (and which I also employed in my UCHill project) is to provide "pluggability points" in the scripts, allowing third party scripts to extend or override the formers' default behavior, without the logic of my scripts per se having to be directly modified, thus potentially breaking backwards compatibility. That is not to imply that doing so has no drawbacks—primarily, the inability to add code at runtime, necessitating in-advance declaration of all dependencies in the .bus file. Still, I consider my approach favorable over the alternative of directly altering the distribution's scripts and/or duplicating .bus files; and this is of course only my biased, non-authoritative opinion.


    Starting with 1.0.2-alpha, lights_aipp.osc provides a few such "pluggability points". Note that, since the project is still in alpha, none of what I am about to describe is to be considered "stable" or otherwise immutable in nature; this is all nothing more than a mere proof of concept. The most interesting among these points resides in the is_sufficiently_dark(_interior) macros. These macros first evaluate, as their names suggest, whether current conditions allow for usage of headlights or cabin lights, respectively. This is referred to as the "default implementation". Having acquired that information, they then delegate to an intermediate macro, is_sufficiently_dark__map_delegate (located in lights_aipp_map_delegate.osc), which in turn gives a chance to some third-party extension script to override the aforementioned default. The beauty of it lies in the fact that the intermediate delegate need not know beforehand where it will delegate the decision making to; it may, for instance, read a .hof-defined constant to determine its delegatee. Thus, what is essentially achieved, is the ability of a single .bus to exhibit variable behavior—in terms of logic being executed, not just different preference / setting values being loaded—depending on the map on which it is loaded.


    Confused? For the remainder of this post, let us have a look at a couple of practical examples.


    A simple example
    Assume the vehicle is to be used on a map, the legislation of the country of which mandates unconditional usage of headlights. Here is how you would do it:

    • In the map's .hof, declare a pseudo-constant as follows:
    • Create a new script, as well as an accompanying variable declarations file, and declare both in the .bus file.
    • In the varlist, declare the variable ai_sufficiently_dark__999 referenced subsequently.
    • Have the intermediate delegate (lights_aipp_map_delegate.osc) call into your custom implementation—which we will write in a moment—as follows:
    • In your newly-created script, implement the macro called above:

    Discussion: We first declared a bus stop in the .hof to identify the particular map with. Note that its value merely serves as an identifier—it can really be anything, as long as it is unique, in the sense that it has not been reserved by another map. Its name, however, has to be "aipp_map_id", because the utility macro map_init in map_aipp.osc queries the .hof file for the constant's presence for us, storing its value in the regular variable map_id for convenient retrieval.


    Moving forward, based on the constant's value being equal to 999 (indicating that the vehicle has indeed been loaded on the particular map), we have the intermediate script call our extension. Again, how exactly the extension macro is named is irrelevant, as long as the likelihood of a name clash occurring remains low.


    Next, in our extension macro, the extension-specific ai_sufficiently_dark__999 variable is set to 1. This variable represents the contract between the intermediate script and the extension. The values that can legally be assigned to the variable are -1 (force lights off), 0 (don't care / abstain / use default implementation), 1 (force lights on).


    Lastly, upon the extension macro's return, the intermediate delegate assigns the extension-specific variable's value to the variable actually read by lights_aipp.osc, ai_sufficiently_dark__map. The final decision made in lights_aipp.osc then depends on the evaluation of the expression

    Code
    1. (L.L.ai_sufficiently_dark__map) 1 = l5 || (L.L.ai_sufficiently_dark__map) -1 = ! &&

    , where the fifth register holds the default implementation's evaluation's outcome (1 = dark enough by default, 0 = otherwise).


    Closing this section, you might argue that all this complexity is unreasonable, when one could achieve the same result, were e.g. a repaint setvar to be introduced for that purpose. And you would be absolutely right, of course. The example merely aimed to demonstrate how the process of writing the simplest possible extension looks like, not whether all the hassle actually makes sense for satisfying such a primitive requirement. And since we're there, let's now discuss...


    A "real-world" example
    In the city of Ahlheim (map "Ahlheim & Laurenzbach"), there is a tunnel section, roughly spanning between the stops "Rudolphst." and "Kronprinzenstr.", served by lines 19 and 21. We would like buses to turn on lights (both headlights and passenger cabin ones) whilst in the particular section of their journey, as shown here.

    • Repeat steps 1 and 2 as in the first example.
    • In the newly-created varlist, declare the variables ai_sufficiently_dark__ah_lz, and ai_sufficiently_dark__ah_lz_internal.
    • Modify the intermediate delegate as follows:
    • At long last, add the actual extension logic into your newly-created script file:

    Discussion: The is_sufficiently_dark(_interior)__ah_lz macros should be self-explanatory at this point, with the only real difference from the previous example being the introduction of the "internal" extension-specific variable, the purpose of which is to track state between invocations.

    Now let's cut to the chase, the lights_frame_pre__ah_lz macro. If you think it is more complicated than it should be, you are not alone—I am myself having a hard time deciphering it as I am writing this, even though I only wrote it recently. Do remember however that in OMSI certain tasks, such as, in our case, tracking the location of the bus, are hard, if not impossible to carry out without extensive use of heuristics, i.e., simply put, by guessing. So let's break down the macro to see if we can understand what it actually does (see inline commentary).


    Phew! That was awfully lots of code for just 2 stops. And if the macro were to cover all the tunnels / underpasses on the particular map, it would have to further grow significantly. Plus, not all tunnels are conveniently situated and in close proximity to stops at both their ends (e.g., the long tunnel before the start of the Autobahn in Laurenzbach). One could of course always introduce additional stops specifically for the purpose of signaling lighting-related events to the script, but I am fairly confident that few besides myself would be willing to go that far simply in order to attain a few "milligrams" of realism.


    "So is this all in vain?", you will surely wonder. Well, yes and no. The particular use case is admittedly poor, I give you that. But, as stated initially, this isn't about a single use case, but the approach it demonstrates towards authoring more flexible scripts in general; the latter I certainly do consider useful, and perhaps future OMSI scripters will as well.

  • 1.0.3-alpha is available.


    I recorded a ride on Grundorf to showcase a handful of the visible subset among the changes referred to thus far. The quality is shitty—my skills and above all my hardware are severely lacking as usual—but I hope the following points are evident:

    • Usage of passenger windows and the sun blind.
    • Adjustment of the driver's window in "break mode", as the weather is hot and the engine is not running (otherwise the vehicle is assumed to be air-conditioned).
    • More dynamic front door operation—in this case both wings are opened at the termini, and the second door pair gets overridden, as opposed to, typically, only the first front door wing being used throughout the remainder of the trip.
    • Stop workflow improvements, e.g., the ordering sequence of actions such as setting and releasing the stop brake and hand brake, etc. being partially random, as is the case with their intervals.
    • More varied indicator usage, including a "forgetfulness" factor (e.g. the AI forgets to indicate upon its arrival at Gaussdorf).
    • Triggering of further cockpit animations and sounds.