Page 1 of 8 123 ... LastLast
Results 1 to 10 of 76

Thread: Dedicated Server Configuration - Sample - Weather + MultiClass REALLY FIXED! -2/25/18

  1. #1
    WMD Member
    Join Date
    Oct 2011
    Posts
    208

    Dedicated Server Configuration - Sample - Weather + MultiClass REALLY FIXED! -2/25/18

    Hi All,

    Please read all the comments for the values to understand how they work.

    -----------> Link to modified files and sample configs <------------------- UPDATED 2/25/18

    Multi-Class has been fixed by SMS, no more need for my lib_rotate.lua file. If you get a message in the in console window about Ignoring multi class, its in error, looks like a print line was not comented out...

    You do not need to use the RemoveFlags/Flags anymore and you do not need to manually set the MULTI_CLASS flag anymore. If you Set a VehicleClassId and MultiClassSlots (1-3) attributes, it automatically sets the proper flags now for Multi-Class session. Which is fantastic, cleans up the config json file.
    UPDATED 2/21/18

    Weather values have been corrected with latest update, config files have been updated to reflect the changes. Sync to Race is of value 0 for WeatherProgression (increasing in multipliers like dateprogression)and DateProgression now uses equal values to in game (ie. 0/off, 1/realtime, 2, 5, 10, 15, 20 etc.

    See the below configs, feel free to steal em and use em in your servers, I also provide a code snippet for multi class dedicated servers

    server.cfg I highlighted the sections to change and update to make your server unique, bulk of the settngs are in the rotate file.
    Code:
    "//" : "You can use dummy entries like this to write comments into the config. 'rem' and '#' are also supported as comment entries."
    // But in recent version of the server, standard C++ like one-liner comments are supported as well.
    
    //////////////////////////
    // Basic server options //
    //////////////////////////
    
    // Logging level of the server. Messages of this severity and more important will be logged. Can be any of debug/info/warning/error.
    logLevel : "info"
    
    // Number of gameplay events stored on the server. Oldest ones will be discarded once the game logs more.
    eventsLogSize : 10000
    
    // The server's name, this will appear in server browser (when implemented) and will be also the default name of sessions hosted on the server.
    name : "CHANGEME SERVER NAME"
    
    // Authenticate users with Steam to check VAC ban when set to true.
    secure : true
    
    // Password required to create sessions on the server as well as to join the sessions, password set in Create options is ignored on DS.
    password : ""
    
    // Maximum size of sessions that can be created on this server.
    // Note that setting this above 16 will allow sessions unjoinable by 32-bit clients to be created on the server.
    // The game also currently does not support sessions larger than 32, even if the server can be configured for up to 64 players.
    maxPlayerCount : 64
    
    // IP address where the server's sockets should be bound. Leave empty for 'all interfaces'.
    bindIP : ""
    
    // ports used to communicate with Steam and game, they must all be accessible on the public IP of the server.
    steamPort : 8766
    hostPort : 27015
    queryPort : 27016
    
    // Delay between server ticks in milliseconds, when not hosting and when hosting a game, respectively.
    // Lower values will make the server more responsible and decrease latency by a bit at the expense of higher CPU usage.
    sleepWaiting : 50
    sleepActive : 5
    
    // Sports Play will use system sockets instead of Steam networking API. Recommended for offline LAN-only events.
    // Use cmdline switch -sportsplay serverIp:hostPort on client to host and join games on sportsPlay server.
    sportsPlay: false
    
    ///////////////////////////
    // Server access control //
    ///////////////////////////
    
    // NOTE: Usually you do not want to have these in your config, this is just a sample.
    // Especially the whitelist as used here will allow only one user with Steam ID onto this server 76561197994111033.
    // Use this as an example and modify the lists accordingly (most likely remove the whitelist entry completely),
    // or use sample server.cfg instead if you do not care about blacklisting/whitelisting.
    
    // Black list
    // Anyone in this list will not be allowed into the server.
    // You can use array or map/object. If the value is an object, the keys are ignored, but you can used the strings as comments with name or reason for being in the list.
    // If the value is an integer, it's interpreted as a Steam ID of the blacklisted user.
    // If it's a string, it's file from which the black list is loaded. The file can contain either an array or an object just like this one, and can reference more files.
    // Here we just load the list from file "blacklist.cfg"
    //blackList : [ "blacklist.cfg" ]
    
    // White list
    // If this is not empty only whitelisted members will be allowed into the server. In that case the black list is ignored.
    // Usually you don't want to have this set at all and use passwords instead, but if you prefer to control server access by whitelisting instead, keep this in and enter the right IDs.
    // You can use array or map/object. If the value is an object, the keys are ignored, but you can used the strings as comments with name or reason for being in the list.
    // If the value is an integer, it's interpreted as a Steam ID of the whitelisted user.
    // If it's a string, it's file from which the white list is loaded. The file can contain either an array or an object just like this one, and can reference more files.
    // Here we load the list from file "whitelist.cfg", and also allow user with Steam ID 76561197994111033.
    //whiteList : { "whitelist" : "whitelist.cfg", "Stouie" : 76561197994111033 }
    
    
    ///////////////////////////
    // HttpApi configuration //
    ///////////////////////////
    
    // This provides http-based API and basic web-based controls using a built-in http server.
    // HttpApi is disabled by default, and if you enable it with everything else left to default it will listen only locally on 127.0.0.1:9000
    
    // Master enable/disable toggle.
    enableHttpApi : true
    
    // Similar to logLevel above but used only for libwebsockets output.
    // Note that all logging still goes through the main filter, so you won't be able to use more verbose logging here than the main level.
    httpApiLogLevel : "warning"
    
    // Interface name or IP where to bind the local http server providing the API and web-based controls.
    // This is the textual name of the interface as assigned by your OS, or IP address of the interface.
    // The default value is "127.0.0.1", change it to an empty string to bind the listen socket to all available interfaces.
    httpApiInterface : "127.0.0.1"
    
    // Port where the local http server listens.
    httpApiPort : 9000
    
    // Map with extra HTTP headers to add to HTTP API responses.
    // The keys are the conditions for adding the headers, the values are the headers to add.
    // There are no conditions supported yet, so just use "*" for a generic wildard for now, that will match everything in the future too.
    httpApiExtraHeaders : {
        "*" : "Access-Control-Allow-Origin: *"
    }
    
    // Http API access level overrides.
    // Each HTTP API endpoint defines its default access level, usually one of "public", "private" or "admin".
    // This map can override these levels to anything else. The keys are wildcard endpoint paths, and the values are the
    // access levels to use. The first path that matches will be used, processing them in the order as written here.
    // The wildcard patterns are case-sensitive and can contain:
    // - '*': matches 0 or more characters
    // - '%': matches 0 or more characters except for forward slash
    // - '?': matches 1 character
    httpApiAccessLevels : {
        // The default is empty, using defaults as defined by the endpoints themselves.
    
        // But you could for example use this to change all access levels to public (not recommended!)
         "*" : "public"
    
        // Or this to hide the status from public
        // "" : "private"
        // "status" : "private"
    
        // And similar to hide the help and lists from public
        // "api/help" : "private"
        // "api/list*" : "private"
    
        // As you can see from the example above, the paths should be written with no initial or trailing slashes.
    }
    
    // Filtering rules for the access levels.
    // The default access levels are "public", "private" and "admin", but the httpAccessLevels above can define any additional levels.
    // This map then tells the server who has access to which level. It's a map from level names to filtering rules.
    //
    // Filtering rules are then specified as a list of structures, processed in the order as written in the config.
    // Each rule structure contains a type" and then type-specific fields. The supported types are:
    // - "accept": Accept this request, no additional checks.
    // - "reject": Reject this request, no additional checks.
    // - "reject-password": Reject this request and let the client know that a password is required, no additional checks.
    // - "ip-accept": Accept this request if it matches the "ip" mask in CIDR notation (for example, "192.168.1.0/24")
    // - "ip-reject": Reject this request if it matches the "ip" mask in CIDR notation (for example, "192.168.1.0/24")
    // - "user": Accept this request if it authenticates as given "user".
    // - "group" : Accept this request if it authenticates as given "group".
    //
    // User/group authentication is done using the standard HTTP basic access authentication (https://en.wikipedia.org/wiki/Basic_access_authentication).
    httpApiAccessFilters : {
    
        // Public rules. The default is to accept everything.
        "public" : [
            { "type" : "accept" }
        ],
    
        // Private rules. The default is to accept queries from localhost, queries authenticated as users in the "private" group
        // and to reject anything else.
        "private" : [
            { "type" : "ip-accept", "ip" : "127.0.0.1/32" },
            { "type" : "group", "group" : "private" },
            { "type" : "reject-password" }
        ],
    
        // Admin rules. The default is to accept queries from localhost, queries authenticated as users in the "admin" group
        // and to reject anything else.
        "admin" : [
            { "type" : "ip-accept", "ip" : "127.0.0.1/32" },
            { "type" : "group", "group" : "admin" },
            { "type" : "reject-password" }
        ],
    
    }
    
    // User list. Map from user names to passwords, in plain text.
    httpApiUsers : {
         "CHANGEMEUSER" : "CHANGEMEPASSWORD!",
         }
    
    // User groups. Map from group names to lists of users in said groups.
    httpApiGroups : {
         "private" : [ "CHANGEMEUSER" ],
         "admin" : [ "CHANGEMEUSER" ],
    }
    
    // Root directory where the static files for the web tool are located. Relative to current directory.
    staticWebFiles: "web_files"
    
    
    //////////////////////////
    // LuaApi configuration //
    //////////////////////////
    
    // Lua API allows the server to be extended by in-server scripting in Lua.
    // The server is running Lua version 5.3, currently with no sandboxing applied to the add-ons. All standard Lua library functions are available.
    
    // WARNING: The LuaApi is not final and the following breaking changes are planned soon:
    // - Rename Lua addon metadata files from *.txt to *.json
    // - Remove default config from the metadata and put it into separate file *_default_config.json
    // - Separate config and persistent data, so that data saved by addons will not pollute the config files, and the config files will retain all comments from the default files.
    
    // Master enable/disable toggle.
    enableLuaApi : true
    
    // Root directory from which the Lua addons are loaded. Relative to current directory if it's not absolute.
    luaAddonRoot: "lua"
    
    // Root directory where the addon configs will be stored if written out by addons. Default configs are defined in the addon base text files.
    luaConfigRoot: "lua_config"
    
    // Root directory where the addon output will be written, once supported. For now the io functions can write anywhere, but this will be limited to this directory in the future.
    luaOutputRoot: "lua_output"
    
    // Names of all Lua addons to load. The addons will be loaded in the specified order. Each addon can list other addons as its dependencies, which attempt to load those first.
    // The server will load addons from directory specified in "luaAddonRoot", loading of each addon will start by loading its config from ADDON_NAME/ADDON_NAME.txt
    luaApiAddons : [
    
        // Core server bootup scripts and helper functions. This will be always loaded first even if not specified here because it's an implicit dependency of all addons.
        "sms_base",
    
        // Automatic race setup rotation.
        "sms_rotate",
    
        // Sends greetings messages to joining members, optionally with race setup info, optionally also whenever returning back to lobby post-race.
        "sms_motd",
    
        // Tracks various stats on the server - server, session and player stats.
        "sms_stats",
    ]
    
    // Names of all lua libraries that are allowed to be used by any addons.
    luaAllowedLibraries : [
    
        "lib_rotate"
    ]
    
    
    ////////////////////////////////
    // Game setup control options //
    ////////////////////////////////
    
    // Set to true to make this server show up in the browser even if it's empty.
    allowEmptyJoin : true
    
    // Set to true to enable API that allows the server to control the game's setup. The host will not be able to control the setup if this is set.
    // This must be set to "true" for the following attributes to work: ServerControlsTrack, ServerControlsVehicleClass, ServerControlsVehicle
    controlGameSetup : true
    
    // Initial attribute values, see /api/list/attributes/session for the full list.
    // These attributes will be used when joining an empty server via the borwser (if allowEmptyJoin is true) and as the intial attributes for the set_attributes and set_next_attributes APIs (if controlGameSetup is true)
    // The defaults set these values:
    sessionAttributes : {
        // The host player can control track selection if set to 0. Set to 1 to disable track selection in the game.
        "ServerControlsTrack" : 1,
    
        // The host player can change the vehicle class by going through the garage if set to 0. Set to 1 to disallow players changing the class.
        // Flag FORCE_SAME_VEHICLE_CLASS (1024) should be also set for this to make sense, otherwise players are able to choose cars from any class.
        "ServerControlsVehicleClass" : 1,
    
        // Players can change their vehicle if set to 0. Set to 1 to disallow players changing the vehicle.
        // Flag FORCE_IDENTICAL_VEHICLES (2) should be also set for this to make sense.
        "ServerControlsVehicle" : 0,
    
        // Grid size up to 32, all reserved to players, so no AI.
        // Note that 32-bit clients will not be able to join the game if this is larger than 16.
        "GridSize" : 26,
        "MaxPlayers" : 26,
    
    }
    sms_rotate_config.json NOTE: This config uses a TIMED_RACE flag, remove it if you want LAPS UPDATED: 2/9/18
    SINGLE CLASS
    Code:
    // Config version.
    version : 7
    
    // Default configuration.
    config : {
    
    	// Is the current rotation index persistent? If true, the rotation will continue after server restart,
    	// If false, the rotation will always start from the first setup.
    	// You can always delete the sms_rotate_data.json file from lua_config to reset the persisted index.
    	"persist_index" : true,
    
    	// The default setup. This is a table with attributes and values. The following rules apply to the attributes:
    
    	// - The setup should never contain any of the "ServerControls" attributes, those are decided automatically.
    	// - The setup should never contain both VehicleModelId and VehicleClassId at the same time. Restrict either the class or specific vehicle.
    	
    	"default" : {
    		// Default Car Class
    		// -- If VehicleClassId is specified: The class to enforce. Automatically sets 1 to ServerControlsVehicleClass, sets FORCE_SAME_VEHICLE_CLASS to Flags
    		// -- Vehicle class and all enum/flags attributes can use values in string forms - so you can use either track id, or track name.
    		"VehicleClassId" : "Touring Car",
    	
    		// Default Car if VehicleClassId is not specificed for single Make Race
    		// -- If VehicleModelId is specified: The vehicle to enforce. Automatically sets 1 to ServerControlsVehicle, sets FORCE_IDENTICAL_VEHICLES to Flags
    		// -- Vehicle model, and all enum/flags attributes can use values in string forms - so you can use either track id, or track name.	
    		//"VehicleModelId" : "Opel Astra TCR",
    
    			// See /api/list/vehicles/ via HTTP (http://127.0.0.1:9000/api/list/vehicles/)
    
    		// Default Track for Session
    		// -- If TrackId is specified: The track to enforce. Automatically sets 1 to ServerControlsTrack
    		// -- Track, and all enum/flags attributes can use values in string forms - so you can use either track id, or track name.	
    		// -- WARNING! WARNING! WARNING! DO NOT UNCOMMENT THE TRACKID BELOW, BREAKS SERVER LAUNCH -- //
    		//"TrackId" : "DO NOT ENABLE ME",
    
    			// See /api/list/tracks/ via HTTP (http://127.0.0.1:9000/api/list/tracks/)
    
    		// Default Session settings for Realism and Penalties
    		// -- Remove TIMED_RACE to switch back to Laps based races --
    		// -- Flags in string form can contain multiple flags separated by comma, so for example "ABS_ALLOWED,SC_ALLOWED,TCS_ALLOWED"
    		// -- The Flags attribute should never contain FORCE_IDENTICAL_VEHICLES or FORCE_SAME_VEHICLE_CLASS flags, those are decided automatically.
    		"Flags" : "ALLOW_CUSTOM_VEHICLE_SETUP,AUTO_START_ENGINE,MECHANICAL_FAILURES,ONLINE_REPUTATION_ENABLED,WAIT_FOR_RACE_READY_INPUT,TIMED_RACE,GHOST_GRIEFERS,ANTI_GRIEFING_COLLISIONS",
    
    			// See /api/list/flags/session via HTTP (http://127.0.0.1:9000/api/list/flags/session)
    		//"OpponentDifficulty" : 120,
    		// Damage: "OFF","VISUAL_ONLY","PERFORMANCEIMPACTING","FULL" (Value in order of names shown: [0-3] 0 = OFF : 3 = FULL)
    		"DamageType" : "FULL",
    
    		// Tire Wear: "OFF","SLOW","STANDARD","X2" to "x7" (Value in order to names shown: [8-0] 8 = OFF : 0 = X7)
    		"TireWearType" : "STANDARD",
    
    		// Fuel Usage: "STANDARD","SLOW","OFF" (Value in order of names shown: [0-2] 0 = STANDARD : 2 = OFF)
    		"FuelUsageType" : "STANDARD",
    
    		// Penalty Setting: "NONE","FULL" (Value in order of names shown: [0-1] 0 = OFF : 1 = FULL)
    		"PenaltiesType" : "FULL",
    
    		// Penalty Time in Seconds
    		"AllowablePenaltyTime" : 5,
    
    		// Pit Lane exit crossing the white Line: 0 Disable 1 Enable 
    		"PitWhiteLinePenalty" : 1,
    
    		// Drive through penalties: 0 Disable 1 Enable
    		"DriveThroughPenalty" : 1,
    
    		// Camera view: "Any" All veiws "CockpitHelmet" Locked view to cockpit only
    		"AllowedViews" : "Any",
    
    		// Force Manual Pitstops: 0 Enable 1 Disable -- This might get fixed in future ded server updates
    		// -- Bugged values are reverse to what documentations states. NOTE: No visual change in In Game Lobby view
    		"ManualPitStops" : 0,
    
    		// Manual Rolling Starts: 0 Disable 1 Enable :: Used only when Rolling Starts is enabled
    		"ManualRollingStarts" : 0,
    
    		// Minimum Online Safety Rank: "U","F","E","D","C","B","A","S"
    		"MinimumOnlineRank" : "F",
    
    		// MinimuM Online Driver Rank: 100 - 5000 :: Use lower values to allow more players in, segragate by safety rank to get clean drivers
    		"MinimumOnlineStrength" : 1000,
    
    		// Default Practice Session Settings
    		// Practice Session Length in Time (Minutes) :: Leave uncommented and set to 0 if you don't want a practice session
    		"PracticeLength" : 0,
    
    		// Default Qualify Session Settings
    		// Qualify Session Length in Time (Minutes)
    		"QualifyLength" : 10,
    
    		// Default Race Session Settings
    		// Race Session Length in Time (Minutes) if TIMED_RACE Flag() is set, LAPS otherwise.
    		"RaceLength" : 10,
    		
    		// Default Race Start: 0 - Standing 1 - Rolling
    		"RaceRollingStart" : 0,
    
    		// Default Formation Lap: 0 - Disable 1 - Enable :: Only valid if "RaceRollingStart" is 1 (Rolling)
    		"RaceFormationLap" : 0,
    
    		// Default Mandatory Pitstop: 0 - Disable 1 - Enable
    		"RaceMandatoryPitStops" : 0,
    
    		// Default Practice Date and Time :: *DateHour is 24HR, no need for a leading 0 for early AM e.g. use 1 for 1AM, 8 for 8AM, 14 for 2PM
    		// "PracticeDateHour" : 6,
    
    		// Default Practice Time Progression Multiplier: 0 - OFF, 1 - Real Time, 2 - 2x, 5 - 5x, 10 - 10x, 15 - 15x, 20 - 20x, 25 - 25x, 30 - 30x, 40 - 40x, 50 - 50x, 60 - 60x
    		//"PracticeDateProgression" : 25,
    		
    		// Default Practice Weather Progression Multiplier: 0 - Sync to Race, 1 - Real Time, 2 - 2x, 5 - 5x, 10 - 10x, 15 - 15x, 20 - 20x, 25 - 25x, 30 - 30x, 40 - 40x, 50 - 50x, 60 - 60x
    		//"PracticeWeatherProgression" : 0,
    
    		// Default Qualify Date and Time :: *DateHour is 24HR, no need for a leading 0 for early AM e.g. use 1 for 1AM, 8 for 8AM, 14 for 2PM
    		"QualifyDateHour" : 8,
    
    		// Default Qualify Time Progression Multiplier: 0 - OFF, 1 - Real Time, 2 - 2x, 5 - 5x, 10 - 10x, 15 - 15x, 20 - 20x, 25 - 25x, 30 - 30x, 40 - 40x, 50 - 50x, 60 - 60x
    		"QualifyDateProgression" : 25,
    
    		// Default Qualify Weather Progression Multiplier: 0 - Sync to Race, 1 - Real Time, 2 - 2x, 5 - 5x, 10 - 10x, 15 - 15x, 20 - 20x, 25 - 25x, 30 - 30x, 40 - 40x, 50 - 50x, 60 - 60x
    		"QualifyWeatherProgression" : 0,
    
    		// Default Race Date and Time :: *DateHour is 24HR, no need for a leading 0 for early AM e.g. use 1 for 1AM, 8 for 8AM, 14 for 2PM
    		"RaceDateYear" : 2017,
    		"RaceDateMonth" : 3,
    		"RaceDateDay" : 27,
    		"RaceDateHour" : 14,
    
    		// Default Race Time Progression Multiplier: 0 - OFF, 1 - Real Time, 2 - 2x, 5 - 5x, 10 - 10x, 15 - 15x, 20 - 20x, 25 - 25x, 30 - 30x, 40 - 40x, 50 - 50x, 60 - 60x
    		"RaceDateProgression" : 25,
    
    		// Default Race Weather Progression Multiplier: 0 - Sync to Race, 1 - Real Time, 2 - 2x, 5 - 5x, 10 - 10x, 15 - 15x, 20 - 20x, 25 - 25x, 30 - 30x, 40 - 40x, 50 - 50x, 60 - 60x
    		"RaceWeatherProgression" : 0,
    
    		// Weather Types for easy changing, including value, can use name or value
    		// For snow/cold seasons the date has to match the weather if you want snow accumilation
    		// Snow in a July race is weird, you will have flakes falling but no snow accumilation
    
    			// "Clear"
    			// "LightCloud"
    			// "MediumClud"
    			// "HeavyCloud"
    			// "Overcast"
    			// "Foggy" 
    			// "HeavyFog"
    			// "Hazy"
    			// "Storm" 
    			// "ThunderStorm" 
    			// "Rain" 
    			// "LightRain" 
    			// "snow"
    			// "heavysnow"
    			// "blizzard"
    			// "HeavyFog"
    			// "HeavyFogWithRain"
    			// "random"
    
    			// See /api/list/enums/weather via HTTP (http://127.0.0.1:9000/api/list/enums/weather)
    		
    		// Default Practice Weather Slots: 0 - 4 Slots, 0 = "Real Weather" for ##CURRENT## date/time, NOT date/time set for the session
    		// Not clear if this is based on the track location or the location of the dedicated server. Need details from devs
    		// "PracticeWeatherSlots" : 4,
    		// "PracticeWeatherSlot1" : "Clear",
    		// "PracticeWeatherSlot2" : "Clear",
    		// "PracticeWeatherSlot3" : "Clear",
    		// "PracticeWeatherSlot4" : "Clear",
    
    		// Default Qualify Weather Slots: 0 - 4 Slots, 0 = "Real Weather" for ##CURRENT## date/time, NOT date/time set for the session
    		// Not clear if this is based on the track location or the location of the dedicated server. Need details from devs.
    		"QualifyWeatherSlots" : 4,
    		"QualifyWeatherSlot1" : "Clear",
    		"QualifyWeatherSlot2" : "Clear",
    		"QualifyWeatherSlot3" : "Clear",
    		"QualifyWeatherSlot4" : "Clear",
    
    		// Default Race Weather Slots: 0 - 4 Slots, 0 = "Real Weather" for ##CURRENT## date/time, NOT date/time set for the session
    		// Not clear if this is based on the track location or the location of the dedicated server. Need details from devs
    		"RaceWeatherSlots" : 4,
    		"RaceWeatherSlot1" : "Clear",
    		"RaceWeatherSlot2" : "Clear",
    		"RaceWeatherSlot3" : "Clear",
    		"RaceWeatherSlot4" : "Clear",
    	},
    
    	// The rotation. Array of setups to rotate. If empty, just the default setup will be used with no actual rotation happening.
    	//
    	// These setups are applied on top of the default setup, then applied to the game. Previous setup in the rotation is never used.
    	// So for example if you wanted to repeat the same track in multiple consecutive setups, different from the default track,
    	// each of those setups needs to explicitly include that track. Also remember that apart from the flags nothing can be "removed"
    	// from the default setup, so if the default setup contains a track, some track will always be enforced (either the default one,
    	// or the setup-specific one). And one last thing, these override setups can never include an attribute that's not specified in
    	// the default setup - it can just override some of the default attributes, not add new ones. The only exception are the
    	// track/vehicle/class attributes.
    	//
    	// Attributes in these setups can have a special attribute RemoveFlags set, which will remove the specified flags from the default
    	// setup instead of adding them to it (which is what the Flags attribute will do)
    
    	"rotation" : [
    
    		// WEC Track Rotation Reference 2017: Monza, Silverstone Int, Spa, Le Mans 24h, Nurburgring GP, COTA, Fuji, Shanghai, Bahrain
    		// WEC Dates in order MM/DD: 3/25, 4/16, 5/6, 6/17, 7/16, 9/3, 9/16, 10/15, 11/5, 11/18
    
    		// GTE Track Rotation Reference 2017: Same as GT3
    		// GTE Dates in order MM/DD: Same as GT3
    
    		// GT3 Blancpain Track Rotation 2017: Paul Ricard, Zolder, Monza, Brands Hatch GP, Silverstone Int, Paul Ricard, Misano, Spa, Spa24hr, Budapest, Nurburgring GP, Barcelona
    		// GT3 Dates in order MM/DD: 3/13-14, 4/6-8, 4/21-22, 5/5-6, 5/19-20, 6/1-2, 6/22-24, 7/3, 7/26-29, 8/31-9/2, 9/14-16, 9/29-30
    		
    		// GT4 Euro Series North Cup Track Rotation 2017: Misano, Red Bull Ring GP, Brands Hatch GP, Slovakia Ring, Zandvoort, Nurburgring GP
    		// GT4 Dates in order MM/DD:  3/31-4/2, 6/9-11, 5/6-7, 6/14-16, 8/19-20, 9/15-17
    
    		// -- You can override any of the defaults listed above by specifying them per track/rotation as desired --
    		// -- Use [ "RemoveFlags" : "FLAGS_TO_REMOVE" ] to remove a flag e.g. (TC_ENABLED) from a particular track/car combo in the rotation
    
    		// Race 1 Donington Park National
    		{
    			"TrackId" : "Donington Park National",
    			"VehicleClassId" : "Touring Car",
    
    			// "PracticeLength" : 10,
    			// "PracticeDateHour" : 6,
    
    			// "PracticeWeatherSlots" : 4,
    			// "PracticeWeatherSlot1" : "Clear",
    			// "PracticeWeatherSlot2" : "Overcast",
    			// "PracticeWeatherSlot3" : "Foggy",
    			// "PracticeWeatherSlot4" : "LightCloud",
    
    			"QualifyLength" : 10,
    			"QualifyDateHour" : 8,
    
    			"QualifyWeatherSlots" : 4,
    			"QualifyWeatherSlot1" : "Foggy",
    			"QualifyWeatherSlot2" : "Overcast",
    			"QualifyWeatherSlot3" : "LightCloud",
    			"QualifyWeatherSlot4" : "Clear",
    
    			//"ManualRollingStarts" : 0,
    			//"RaceRollingStart" : 0,
    			//"RaceFormationLap" : 0,
    			//"RaceMandatoryPitStops" : 0,
    
    			"RaceLength" : 10,
    
    			"RaceDateYear" : 2017,
    			"RaceDateMonth" : 3,
    			"RaceDateDay" : 27,
    			"RaceDateHour" : 14,
    
    			"RaceWeatherSlots" : 4,
    			"RaceWeatherSlot1" : "Clear",
    			"RaceWeatherSlot2" : "MediumCloud",
    			"RaceWeatherSlot3" : "HeavyCloud",
    			"RaceWeatherSlot4" : "Hazy",
    		},
    		// Race 2 Brands Hatch Indy
    		{
    			"TrackId" : "Brands Hatch Indy",
    			"VehicleClassId" : "Touring Car",
    
    			"PracticeLength" : 0,
    
    			"QualifyLength" : 10,
    
    			"QualifyDateHour" : 8,
    
    			"QualifyWeatherSlots" : 4,
    			"QualifyWeatherSlot1" : "Clear",
    			"QualifyWeatherSlot2" : "Overcast",
    			"QualifyWeatherSlot3" : "LightRain",
    			"QualifyWeatherSlot4" : "LightCloud",
    
    			"RaceLength" : 10,
    
    			"RaceDateYear" : 2017,
    			"RaceDateMonth" : 4,
    			"RaceDateDay" : 8,
    			"RaceDateHour" : 14,
    
    			"RaceWeatherSlots" : 4,
    			"RaceWeatherSlot1" : "Clear",
    			"RaceWeatherSlot2" : "Hazy",
    			"RaceWeatherSlot3" : "MediumCloud",
    			"RaceWeatherSlot4" : "HeavyCloud",
    		}
    	]
    }
    sms_rotate_config.json NOTE: This config uses a TIMED_RACE flag, remove it if you want LAPS UPDATED: 2/21/18
    MULTI-CLASS
    Code:
    // Config version.
    version : 7
    
    // Default configuration.
    config : {
    
    	// Is the current rotation index persistent? If true, the rotation will continue after server restart,
    	// If false, the rotation will always start from the first setup.
    	// You can always delete the sms_rotate_data.json file from lua_config to reset the persisted index.
    	"persist_index" : true,
    
    	// The default setup. This is a table with attributes and values. The following rules apply to the attributes:
    
    	// - The setup should never contain any of the "ServerControls" attributes, those are decided automatically.
    	// - The setup should never contain both VehicleModelId and VehicleClassId at the same time. Restrict either the class or specific vehicle.
    	
    	"default" : {
    		// Default Car Class
    		// -- If VehicleClassId is specified: The class to enforce. Automatically sets 1 to ServerControlsVehicleClass, sets FORCE_SAME_VEHICLE_CLASS to Flags
    		// -- Vehicle class and all enum/flags attributes can use values in string forms - so you can use either track id, or track name.
    		"VehicleClassId" : "LMP1", //-1289517523
    
    			// Popular Car Classes Name/Value for easy Switch
    			// See /api/list/vehicle_classes/ via HTTP (http://127.0.0.1:9000/api/list/vehicle_classes/)
    
    			// "GT1" = 1323122160
    			// "GT3" = -112887377
    			// "GT4" = 1553262379
    			// "GTE" = 1740243009
    			// "LMP1" = -1289517523
    			// "LMP2" = -564539194
    			// "LMP3" = 974854672
    			// "LMP900" = 1543160927
    			// "Touring Car" = 52697193
    			// "TC1" --Clio Cup-- = -1529501352
    			// "Group A" = -1270088329
    
    		// Default Multi Class settings
    		// -- The first class of car is set by VehicleClassId above, then you can set 3 additional classes here
    		// -- Don't forget to uncomment the RemoveFlags and Flags lines below
    		// ------------------------------------------------------- //
    		// -- MUST USE "value" for cars, can't use ENUM NAME :( -- //
    		// -- 			Fixed if using my sms_base.lua			-- //
    		// ------------------------------------------------------- //
    		"MultiClassSlots" : 3,
    		"MultiClassSlot1" : "GTE", //1740243009,
    		"MultiClassSlot2" : "GT3", //-112887377,
    		"MultiClassSlot3" : "GT4", //1553262379,
    	
    		// Default Car if VehicleClassId is not specificed for single Make Race
    		// -- If VehicleModelId is specified: The vehicle to enforce. Automatically sets 1 to ServerControlsVehicle, sets FORCE_IDENTICAL_VEHICLES to Flags
    		// -- Vehicle model, and all enum/flags attributes can use values in string forms - so you can use either track id, or track name.	
    		//"VehicleModelId" : "Opel Astra TCR",
    
    			// See /api/list/vehicles/ via HTTP (http://127.0.0.1:9000/api/list/vehicles/)
    
    		// Default Track for Session
    		// -- If TrackId is specified: The track to enforce. Automatically sets 1 to ServerControlsTrack
    		// -- Track, and all enum/flags attributes can use values in string forms - so you can use either track id, or track name.	
    		// -- WARNING! WARNING! WARNING! DO NOT UNCOMMENT THE TRACKID BELOW, BREAKS SERVER LAUNCH -- //
    		//"TrackId" : "DO NOT ENABLE ME",
    
    			// See /api/list/tracks/ via HTTP (http://127.0.0.1:9000/api/list/tracks/)
    
    		// Default Session settings for Realism and Penalties
    		// -- Remove TIMED_RACE to switch back to Laps based races --
    		// -- Flags in string form can contain multiple flags separated by comma, so for example "ABS_ALLOWED,SC_ALLOWED,TCS_ALLOWED"
    		// -- The Flags attribute should never contain FORCE_IDENTICAL_VEHICLES or FORCE_SAME_VEHICLE_CLASS flags, those are decided automatically.
    		"Flags" : "ALLOW_CUSTOM_VEHICLE_SETUP,AUTO_START_ENGINE,MECHANICAL_FAILURES,ONLINE_REPUTATION_ENABLED,WAIT_FOR_RACE_READY_INPUT,TIMED_RACE,GHOST_GRIEFERS,ANTI_GRIEFING_COLLISIONS",
    
    			// See /api/list/flags/session via HTTP (http://127.0.0.1:9000/api/list/flags/session)
    		//"OpponentDifficulty" : 120,
    		// Damage: "OFF","VISUAL_ONLY","PERFORMANCEIMPACTING","FULL" (Value in order of names shown: [0-3] 0 = OFF : 3 = FULL)
    		"DamageType" : "FULL",
    
    		// Tire Wear: "OFF","SLOW","STANDARD","X2" to "x7" (Value in order to names shown: [8-0] 8 = OFF : 0 = X7)
    		"TireWearType" : "STANDARD",
    
    		// Fuel Usage: "STANDARD","SLOW","OFF" (Value in order of names shown: [0-2] 0 = STANDARD : 2 = OFF)
    		"FuelUsageType" : "STANDARD",
    
    		// Penalty Setting: "NONE","FULL" (Value in order of names shown: [0-1] 0 = OFF : 1 = FULL)
    		"PenaltiesType" : "FULL",
    
    		// Penalty Time in Seconds
    		"AllowablePenaltyTime" : 5,
    
    		// Pit Lane exit crossing the white Line: 0 Disable 1 Enable 
    		"PitWhiteLinePenalty" : 1,
    
    		// Drive through penalties: 0 Disable 1 Enable
    		"DriveThroughPenalty" : 1,
    
    		// Camera view: "Any" All veiws "CockpitHelmet" Locked view to cockpit only
    		"AllowedViews" : "Any",
    
    		// Force Manual Pitstops: 0 Enable 1 Disable -- This might get fixed in future ded server updates
    		// -- Bugged values are reverse to what documentations states. NOTE: No visual change in In Game Lobby view
    		"ManualPitStops" : 0,
    
    		// Manual Rolling Starts: 0 Disable 1 Enable :: Used only when Rolling Starts is enabled
    		"ManualRollingStarts" : 0,
    
    		// Minimum Online Safety Rank: "U","F","E","D","C","B","A","S"
    		"MinimumOnlineRank" : "F",
    
    		// MinimuM Online Driver Rank: 100 - 5000 :: Use lower values to allow more players in, segragate by safety rank to get clean drivers
    		"MinimumOnlineStrength" : 1000,
    
    		// Default Practice Session Settings
    		// Practice Session Length in Time (Minutes) :: Leave uncommented and set to 0 if you don't want a practice session
    		"PracticeLength" : 0,
    
    		// Default Qualify Session Settings
    		// Qualify Session Length in Time (Minutes)
    		"QualifyLength" : 10,
    
    		// Default Race Session Settings
    		// Race Session Length in Time (Minutes) if TIMED_RACE Flag() is set, LAPS otherwise.
    		"RaceLength" : 10,
    		
    		// Default Race Start: 0 - Standing 1 - Rolling
    		"RaceRollingStart" : 0,
    
    		// Default Formation Lap: 0 - Disable 1 - Enable :: Only valid if "RaceRollingStart" is 1 (Rolling)
    		"RaceFormationLap" : 0,
    
    		// Default Mandatory Pitstop: 0 - Disable 1 - Enable
    		"RaceMandatoryPitStops" : 0,
    
    		// Default Practice Date and Time :: *DateHour is 24HR, no need for a leading 0 for early AM e.g. use 1 for 1AM, 8 for 8AM, 14 for 2PM
    		// "PracticeDateHour" : 6,
    
    		// Default Practice Time Progression Multiplier: 0 - OFF, 1 - Real Time, 2 - 2x, 5 - 5x, 10 - 10x, 15 - 15x, 20 - 20x, 25 - 25x, 30 - 30x, 40 - 40x, 50 - 50x, 60 - 60x
    		//"PracticeDateProgression" : 25,
    		
    		// Default Practice Weather Progression Multiplier: 0 - Sync to Race, 1 - Real Time, 2 - 2x, 5 - 5x, 10 - 10x, 15 - 15x, 20 - 20x, 25 - 25x, 30 - 30x, 40 - 40x, 50 - 50x, 60 - 60x
    		//"PracticeWeatherProgression" : 0,
    
    		// Default Qualify Date and Time :: *DateHour is 24HR, no need for a leading 0 for early AM e.g. use 1 for 1AM, 8 for 8AM, 14 for 2PM
    		"QualifyDateHour" : 8,
    
    		// Default Qualify Time Progression Multiplier: 0 - OFF, 1 - Real Time, 2 - 2x, 5 - 5x, 10 - 10x, 15 - 15x, 20 - 20x, 25 - 25x, 30 - 30x, 40 - 40x, 50 - 50x, 60 - 60x
    		"QualifyDateProgression" : 25,
    
    		// Default Qualify Weather Progression Multiplier: 0 - Sync to Race, 1 - Real Time, 2 - 2x, 5 - 5x, 10 - 10x, 15 - 15x, 20 - 20x, 25 - 25x, 30 - 30x, 40 - 40x, 50 - 50x, 60 - 60x
    		"QualifyWeatherProgression" : 0,
    
    		// Default Race Date and Time :: *DateHour is 24HR, no need for a leading 0 for early AM e.g. use 1 for 1AM, 8 for 8AM, 14 for 2PM
    		"RaceDateYear" : 2017,
    		"RaceDateMonth" : 3,
    		"RaceDateDay" : 27,
    		"RaceDateHour" : 14,
    
    		// Default Race Time Progression Multiplier: 0 - OFF, 1 - Real Time, 2 - 2x, 5 - 5x, 10 - 10x, 15 - 15x, 20 - 20x, 25 - 25x, 30 - 30x, 40 - 40x, 50 - 50x, 60 - 60x
    		"RaceDateProgression" : 25,
    
    		// Default Race Weather Progression Multiplier: 0 - Sync to Race, 1 - Real Time, 2 - 2x, 5 - 5x, 10 - 10x, 15 - 15x, 20 - 20x, 25 - 25x, 30 - 30x, 40 - 40x, 50 - 50x, 60 - 60x
    		"RaceWeatherProgression" : 0,
    
    		// Weather Types for easy changing, including value, can use name or value
    		// For snow/cold seasons the date has to match the weather if you want snow accumilation
    		// Snow in a July race is weird, you will have flakes falling but no snow accumilation
    
    			// "Clear"
    			// "LightCloud"
    			// "MediumClud"
    			// "HeavyCloud"
    			// "Overcast"
    			// "Foggy" 
    			// "HeavyFog"
    			// "Hazy"
    			// "Storm" 
    			// "ThunderStorm" 
    			// "Rain" 
    			// "LightRain" 
    			// "snow"
    			// "heavysnow"
    			// "blizzard"
    			// "HeavyFog"
    			// "HeavyFogWithRain"
    			// "random"
    
    			// See /api/list/enums/weather via HTTP (http://127.0.0.1:9000/api/list/enums/weather)
    		
    		// Default Practice Weather Slots: 0 - 4 Slots, 0 = "Real Weather" for ##CURRENT## date/time, NOT date/time set for the session
    		// Not clear if this is based on the track location or the location of the dedicated server. Need details from devs
    		// "PracticeWeatherSlots" : 4,
    		// "PracticeWeatherSlot1" : "Clear",
    		// "PracticeWeatherSlot2" : "Clear",
    		// "PracticeWeatherSlot3" : "Clear",
    		// "PracticeWeatherSlot4" : "Clear",
    
    		// Default Qualify Weather Slots: 0 - 4 Slots, 0 = "Real Weather" for ##CURRENT## date/time, NOT date/time set for the session
    		// Not clear if this is based on the track location or the location of the dedicated server. Need details from devs.
    		"QualifyWeatherSlots" : 4,
    		"QualifyWeatherSlot1" : "Clear",
    		"QualifyWeatherSlot2" : "Clear",
    		"QualifyWeatherSlot3" : "Clear",
    		"QualifyWeatherSlot4" : "Clear",
    
    		// Default Race Weather Slots: 0 - 4 Slots, 0 = "Real Weather" for ##CURRENT## date/time, NOT date/time set for the session
    		// Not clear if this is based on the track location or the location of the dedicated server. Need details from devs
    		"RaceWeatherSlots" : 4,
    		"RaceWeatherSlot1" : "Clear",
    		"RaceWeatherSlot2" : "Clear",
    		"RaceWeatherSlot3" : "Clear",
    		"RaceWeatherSlot4" : "Clear",
    	},
    
    	// The rotation. Array of setups to rotate. If empty, just the default setup will be used with no actual rotation happening.
    	//
    	// These setups are applied on top of the default setup, then applied to the game. Previous setup in the rotation is never used.
    	// So for example if you wanted to repeat the same track in multiple consecutive setups, different from the default track,
    	// each of those setups needs to explicitly include that track. Also remember that apart from the flags nothing can be "removed"
    	// from the default setup, so if the default setup contains a track, some track will always be enforced (either the default one,
    	// or the setup-specific one). And one last thing, these override setups can never include an attribute that's not specified in
    	// the default setup - it can just override some of the default attributes, not add new ones. The only exception are the
    	// track/vehicle/class attributes.
    	//
    	// Attributes in these setups can have a special attribute RemoveFlags set, which will remove the specified flags from the default
    	// setup instead of adding them to it (which is what the Flags attribute will do)
    
    	"rotation" : [
    
    		// WEC Track Rotation Reference 2017: Monza, Silverstone Int, Spa, Le Mans 24h, Nurburgring GP, COTA, Fuji, Shanghai, Bahrain
    		// WEC Dates in order MM/DD: 3/25, 4/16, 5/6, 6/17, 7/16, 9/3, 9/16, 10/15, 11/5, 11/18
    
    		// GTE Track Rotation Reference 2017: Same as GT3
    		// GTE Dates in order MM/DD: Same as GT3
    
    		// GT3 Blancpain Track Rotation 2017: Paul Ricard, Zolder, Monza, Brands Hatch GP, Silverstone Int, Paul Ricard, Misano, Spa, Spa24hr, Budapest, Nurburgring GP, Barcelona
    		// GT3 Dates in order MM/DD: 3/13-14, 4/6-8, 4/21-22, 5/5-6, 5/19-20, 6/1-2, 6/22-24, 7/3, 7/26-29, 8/31-9/2, 9/14-16, 9/29-30
    		
    		// GT4 Euro Series North Cup Track Rotation 2017: Misano, Red Bull Ring GP, Brands Hatch GP, Slovakia Ring, Zandvoort, Nurburgring GP
    		// GT4 Dates in order MM/DD:  3/31-4/2, 6/9-11, 5/6-7, 6/14-16, 8/19-20, 9/15-17
    
    		// -- You can override any of the defaults listed above by specifying them per track/rotation as desired --
    		// -- Use [ "RemoveFlags" : "FLAGS_TO_REMOVE" ] to remove a flag e.g. (TC_ENABLED) from a particular track/car combo in the rotation
    
    		// Race 1 Donington Park National
    		{
    			"TrackId" : "Donington Park National",
    			//"VehicleClassId" : "Touring Car",
    
    			// --------------- MULTI CLASS CONFIG ----------------- //
    			// ------ COPY/PASTE TO OTHER TRACKS IN ROTATION ------ //
    			// ------ COMMENT OUT THE ABOVE "VehicleClassId" ------ //
    			// ---------------------------------------------------- //
    
    			"VehicleClassId" : "LMP1",
    
    			// Popular Car Classes Name/Value for easy Switch
    			// See /api/list/vehicle_classes/ via HTTP (http://127.0.0.1:9000/api/list/vehicle_classes/)
    
    			// "GT1" = 1323122160
    			// "GT3" = -112887377
    			// "GT4" = 1553262379
    			// "GTE" = 1740243009
    			// "LMP1" = -1289517523
    			// "LMP2" = -564539194
    			// "LMP3" = 974854672
    			// "LMP900" = 1543160927
    			// "Touring Car" = 52697193
    			// "TC1" --Clio Cup-- = -1529501352
    			// "Group A" = -1270088329
    
    			// Default Multi Class settings
    			// -- The first class of car is set by VehicleClassId above, then you can set 3 additional classes here
    			// -- Don't forget to uncomment the RemoveFlags and Flags lines below
    			// ------------------------------------------------------- //
    			// -- MUST USE "value" for cars, can't use ENUM NAME :( -- //
    			// -- 			Fixed if using my sms_base.lua			-- //
    			// ------------------------------------------------------- //
    			"MultiClassSlots" : 3,
    			"MultiClassSlot1" : "GTE", //1740243009,
    			"MultiClassSlot2" : "GT3", //-112887377,
    			"MultiClassSlot3" : "GT4", //1553262379,	
    
                            //"Flags" : "ABS_ALLOWED",
    			//"RemoveFlags" : "TCS_ALLOWED",
    
    			// If enabled the the Formation lap and Rolling start are player controlled.
    			// "ManualRollingStarts" : 1,
    
    			// Rolling start options for Multi Class racing, set both to 1 to enable a Rolling start
    			// "RaceRollingStart" : 1,
    			// "RaceFormationLap" : 1,
    
    			// -------------- END MULTI CLASS CONFIG -------------- //
    
    			// "PracticeLength" : 10,
    			// "PracticeDateHour" : 6,
    
    			// "PracticeWeatherSlots" : 4,
    			// "PracticeWeatherSlot1" : "Clear",
    			// "PracticeWeatherSlot2" : "Overcast",
    			// "PracticeWeatherSlot3" : "Foggy",
    			// "PracticeWeatherSlot4" : "LightCloud",
    
    			"QualifyLength" : 10,
    			"QualifyDateHour" : 8,
    
    			"QualifyWeatherSlots" : 4,
    			"QualifyWeatherSlot1" : "Foggy",
    			"QualifyWeatherSlot2" : "Overcast",
    			"QualifyWeatherSlot3" : "LightCloud",
    			"QualifyWeatherSlot4" : "Clear",
    
    			//"ManualRollingStarts" : 0,
    			//"RaceRollingStart" : 0,
    			//"RaceFormationLap" : 0,
    			//"RaceMandatoryPitStops" : 0,
    
    			"RaceLength" : 10,
    
    			"RaceDateYear" : 2017,
    			"RaceDateMonth" : 3,
    			"RaceDateDay" : 27,
    			"RaceDateHour" : 14,
    
    			"RaceWeatherSlots" : 4,
    			"RaceWeatherSlot1" : "Clear",
    			"RaceWeatherSlot2" : "MediumCloud",
    			"RaceWeatherSlot3" : "HeavyCloud",
    			"RaceWeatherSlot4" : "Hazy",
    		},
    		// Race 2 Brands Hatch Indy
    		{
    			"TrackId" : "Brands Hatch Indy",
    			"VehicleClassId" : "LMP1",
    
    			// Popular Car Classes Name/Value for easy Switch
    			// See /api/list/vehicle_classes/ via HTTP (http://127.0.0.1:9000/api/list/vehicle_classes/)
    
    			// "GT1" = 1323122160
    			// "GT3" = -112887377
    			// "GT4" = 1553262379
    			// "GTE" = 1740243009
    			// "LMP1" = -1289517523
    			// "LMP2" = -564539194
    			// "LMP3" = 974854672
    			// "LMP900" = 1543160927
    			// "Touring Car" = 52697193
    			// "TC1" --Clio Cup-- = -1529501352
    			// "Group A" = -1270088329
    
    			// Default Multi Class settings
    			// -- The first class of car is set by VehicleClassId above, then you can set 3 additional classes here
    			// -- Don't forget to uncomment the RemoveFlags and Flags lines below
    			// ------------------------------------------------------- //
    			// -- MUST USE "value" for cars, can't use ENUM NAME :( -- //
    			// -- 			Fixed if using my sms_base.lua			-- //
    			// ------------------------------------------------------- //
    			"MultiClassSlots" : 3,
    			"MultiClassSlot1" : "GTE", //1740243009,
    			"MultiClassSlot2" : "GT3", //-112887377,
    			"MultiClassSlot3" : "GT4", //1553262379,	
    
                            //"Flags" : "ABS_ALLOWED",
    			//"RemoveFlags" : "TCS_ALLOWED",
    
    			// If enabled the the Formation lap and Rolling start are player controlled.
    			// "ManualRollingStarts" : 1,
    
    			// Rolling start options for Multi Class racing, set both to 1 to enable a Rolling start
    			// "RaceRollingStart" : 1,
    			// "RaceFormationLap" : 1,
    
    			"PracticeLength" : 0,
    
    			"QualifyLength" : 10,
    
    			"QualifyDateHour" : 8,
    
    			"QualifyWeatherSlots" : 4,
    			"QualifyWeatherSlot1" : "Clear",
    			"QualifyWeatherSlot2" : "Overcast",
    			"QualifyWeatherSlot3" : "LightRain",
    			"QualifyWeatherSlot4" : "LightCloud",
    
    			"RaceLength" : 10,
    
    			"RaceDateYear" : 2017,
    			"RaceDateMonth" : 4,
    			"RaceDateDay" : 8,
    			"RaceDateHour" : 14,
    
    			"RaceWeatherSlots" : 4,
    			"RaceWeatherSlot1" : "Clear",
    			"RaceWeatherSlot2" : "Hazy",
    			"RaceWeatherSlot3" : "MediumCloud",
    			"RaceWeatherSlot4" : "HeavyCloud",
    		}
          ]
    }
    sms_motd.lua MODIFIED to account for TIMED races and include Practice, Quali weather/time info, also a discord link. see below highlighted in red

    Also added timeprogression/weatherprogression info, and made it so it only shows info for sessions configured (Quali/Practice Length > 0), as well as show multi-class info for classes restricted

    FOR MULTI-CLASS INFO TO WORK, YOU NEED MY modified sms_base.lua FILE AS ITS USES TO ENUMARTE THE NAME, OTHERWISE ERRORS MIGHT OCCUR!
    UPDATED 2/25/18
    Code:
    --[[
    
    Simple "message of the day" addon.
    
    It sends a welcome message to all new joining members, and also can send lobby setup to new joining members
    and to everyone after returning back to lobby when a race finishes.
    
    --]]
    
    local addon_storage = ...
    local config = addon_storage.config
    
    local motd = config.motd
    if motd == "" then motd = nil end
    config.send_setup = config.send_setup or {}
    local send_setup = table.list_to_set( config.send_setup )
    local send_when_returning_to_lobby = config.send_when_returning_to_lobby or false
    
    
    -- Map from refid to send timer (GetServerUptimeMs)
    local scheduled_sends = {}
    
    
    -- Immediate send to given refid
    local function send_now( refid )
    
    	local attributes = session.attributes
    
    	-- Send the message
    	if motd then
    		SendChatToMember( refid, motd )
    	else
    		local privacy = attributes.Privacy
    		if privacy == 0 then privacy = ": PUBLIC"
    		elseif privacy == 1 then privacy = "friends only"
    		else privacy = "private"
    		end
    		local password = server.password_protected and " (password protected)" or ""
    		SendChatToMember( refid, "Welcome to '" .. server.name .. "' this server is " .. privacy .. password )
    	end
    
    	-- Send the controls setup setup
    	if send_setup.controls then
    		SendChatToMember( refid, "Server controls this game's setup" .. ( ( attributes.ServerControlsSetup ~= 0 ) and ": YES" or ": NO" ) )
    	end
    	SendChatToMember( refid, "")
    	if attributes.ServerControlsSetup == 0 then
    		return
    	end
    
    	if send_setup.controls then
    		if attributes.ServerControlsTrack ~= 0 then
    			local track_id = attributes.TrackId
    			local track = id_to_track[ track_id ]
    			local track_name = track and track.name or track_id
    			SendChatToMember( refid, "Server restricts the track to: " .. track_name )
    		end
    
    		if attributes.ServerControlsVehicle ~= 0 then
    			local vehicle_id = attributes.VehicleModelId
    			local vehicle = id_to_vehicle[ vehicle_id ]
    			local vehicle_name = vehicle and vehicle.name or vehicle_id
    			SendChatToMember( refid, "Server restricts the vehicle to: " .. vehicle_name )
    		elseif ( attributes.MultiClassSlots ) ~= 0 then
    			local multiclass = {}
    			local vehicle_class_id = attributes.VehicleClassId
    			local vehicle_class = id_to_vehicle_class[ vehicle_class_id ]
    			local vehicle_class_name = vehicle_class and vehicle_class.name or vehicle_class_id
    			table.insert( multiclass, vehicle_class_name)
    			for i = 1,attributes.MultiClassSlots do
    				local mvalue = attributes[ "MultiClassSlot" .. i ]
    				local mname = value_to_multiclass[ mvalue ] or tostring( mvalue )
    				table.insert( multiclass, mname )
    			end
    			SendChatToMember( refid, "Server restricts the vehicle classes to: -- " .. table.concat( multiclass, " -- " ) .. " -- ")			
    		elseif ( attributes.ServerControlsVehicleClass ) ~= 0 then
    			local vehicle_class_id = attributes.VehicleClassId
    			local vehicle_class = id_to_vehicle_class[ vehicle_class_id ]
    			local vehicle_class_name = vehicle_class and vehicle_class.name or vehicle_class_id
    			SendChatToMember( refid, "Server restricts the vehicle class to: " .. vehicle_class_name )
    		end
    		SendChatToMember( refid, "")
    	end
    
    	-- Send race format.
    	if send_setup.format then
    		local phases = {}
    		if attributes.PracticeLength ~= 0 then
    			table.insert( phases, "Practice: (" .. attributes.PracticeLength .. " Minutes)" )
    		end
    		if attributes.QualifyLength ~= 0 then
    			table.insert( phases, "Qualify: (" .. attributes.QualifyLength .. " Minutes)" )
    		end
    		if attributes.RaceLength ~= 0 then
    			if (attributes.Flags & SessionFlags.TIMED_RACE ) ~= 0 then
    				table.insert( phases, "Race: (" .. attributes.RaceLength .. " Minutes)" )
    			else
    				table.insert( phases, "Race: (" .. attributes.RaceLength .. " Laps)" )
    			end
    		end
    		-- Note: TODO: time-based race format
    		SendChatToMember( refid, "Race format: " .. table.concat( phases, ", " ) )
    		SendChatToMember( refid, "")		
    	end
    
    	-- Send race restrictions
    	if send_setup.restrictions then
    		local restricts = {}
    		--SendChatToMember( refid, "")
    		SendChatToMember( refid, "Restrictions:" )
    
    		if attributes.AllowedViews == AllowedView.NONE then table.insert( restricts, "Force interior view: NO" )
    		elseif attributes.AllowedViews == AllowedView.CockpitHelmet then table.insert( restricts, "Force interior view: YES" )
    		end
    		if ( attributes.Flags & SessionFlags.FORCE_MANUAL ) ~= 0 then table.insert( restricts, "Force manual gears: YES" ) else table.insert( restricts, "Force manual gears: NO" ) end
    		if ( attributes.Flags & SessionFlags.FORCE_REALISTIC_DRIVING_AIDS ) ~= 0 then table.insert( restricts, "Force realistic aids: YES" ) else table.insert( restricts, "Force realistic aids: NO" ) end
    		if ( attributes.Flags & SessionFlags.ALLOW_CUSTOM_VEHICLE_SETUP ) ~= 0 then table.insert( restricts, "Force default setup: NO" ) else table.insert( restricts, "Force default setup: YES" ) end
    		SendChatToMember( refid, "- " .. table.concat( restricts, ", " ) )
    
    		restricts = {}
    		if ( attributes.Flags & SessionFlags.ABS_ALLOWED ) ~= 0 then table.insert( restricts, "Allow ABS: YES" ) else table.insert( restricts, "Allow ABS: NO" ) end
    		if ( attributes.Flags & SessionFlags.SC_ALLOWED ) ~= 0 then table.insert( restricts, "Allow stability control: YES" ) else table.insert( restricts, "Allow stability control: NO" ) end
    		if ( attributes.Flags & SessionFlags.TCS_ALLOWED ) ~= 0 then table.insert( restricts, "Allow traction control: YES" ) else table.insert( restricts, "Allow traction control: NO" ) end
    		SendChatToMember( refid, "- " .. table.concat( restricts, ", " ) )
    
    		restricts = {}
    		if attributes.DamageType == Damage.OFF then table.insert( restricts, "Damage: OFF" )
    		elseif attributes.DamageType == Damage.VISUAL_ONLY then table.insert( restricts, "Damage: VISUAL" )
    		elseif attributes.DamageType == Damage.PERFORMANCEIMPACTING then table.insert( restricts, "Damage: PERFORMANCE" )
    		elseif attributes.DamageType == Damage.FULL then table.insert( restricts, "Damage: FULL" )
    		end
    		if ( attributes.Flags & SessionFlags.MECHANICAL_FAILURES ) ~= 0 then table.insert( restricts, "Mechanical failures: YES" ) else table.insert( restricts, "Mechanical failures: NO" ) end
    		if attributes.TireWearType == TireWear.OFF then table.insert( restricts, "Tire wear: OFF" )
    		elseif attributes.TireWearType == TireWear.SLOW then table.insert( restricts, "Tire wear: SLOW" )
    		elseif attributes.TireWearType == TireWear.STANDARD then table.insert( restricts, "Tire wear: STANDARD" )
    		else table.insert( restricts, "Tire wear: " .. value_to_tire_wear[ attributes.TireWearType ] )
    		end
    		if attributes.FuelUsageType == FuelUsage.OFF then table.insert( restricts, "Fuel usage: OFF" )
    		elseif attributes.FuelUsageType == FuelUsage.SLOW then table.insert( restricts, "Fuel usage: SLOW" )
    		elseif attributes.FuelUsageType == FuelUsage.STANDARD then table.insert( restricts, "Fuel usage: STANDARD" )
    		end
    		SendChatToMember( refid, "- " .. table.concat( restricts, ", " ) )
    
    		restricts = {}
    		if ( attributes.Flags & SessionFlags.AUTO_START_ENGINE ) ~= 0 then table.insert( restricts, "Auto start engine: YES" ) else table.insert( restricts, "Auto start engine: NO" ) end
    		if attributes.PenaltiesType == Penalties.NONE then table.insert( restricts, "Penalties: NONE" )
    		elseif attributes.PenaltiesType == Penalties.FULL then table.insert( restricts, "Penalties: FULL" )
    		end
    		SendChatToMember( refid, "- " .. table.concat( restricts, ", " ) )
    		SendChatToMember( refid, "")
    	end
    
    	-- Send Practice Weather
    	if send_setup.weather then
    		local weather = {}
    		if attributes.PracticeLength ~= 0 then
    			if attributes.PracticeWeatherSlots == 0 then
    				table.insert( weather, "Real Weather" )
    			else
    				for i = 1,attributes.PracticeWeatherSlots do
    					local wvalue = attributes[ "PracticeWeatherSlot" .. i ]
    					local wname = value_to_weather[ wvalue ] or tostring( wvalue )
    					table.insert( weather, wname )
    				end
    			end
    			SendChatToMember( refid, "Practice Weather: " .. table.concat( weather, " -> " ) )
    			if attributes.PracticeWeatherProgression == 0 then
    				SendChatToMember( refid, "Practice Weather Progression: Sync to Race" )
    			elseif attributes.PracticeWeatherProgression == 1 then
    				SendChatToMember( refid, "Practice Weather Progression: Real time" )
    			else
    				SendChatToMember( refid, "Practice Weather Progression: " .. attributes.PracticeWeatherProgression .. "x")
    			end
    			SendChatToMember( refid, "")
    		end
    	end
    
    	-- Send Qualify Weather
    	if send_setup.weather then
    		local weather = {}
    		if attributes.QualifyLength ~= 0 then
    			if attributes.QualifyWeatherSlots == 0 then
    				table.insert( weather, "Real Weather" )
    			else
    				for i = 1,attributes.QualifyWeatherSlots do
    					local wvalue = attributes[ "QualifyWeatherSlot" .. i ]
    					local wname = value_to_weather[ wvalue ] or tostring( wvalue )
    					table.insert( weather, wname )
    				end
    			end
    			SendChatToMember( refid, "Qualify Weather: " .. table.concat( weather, " -> " ) )
    			if attributes.QualifyWeatherProgression == 0 then
    				SendChatToMember( refid, "Qualify Weather Progression: Sync to Race" )
    			elseif attributes.QualifyWeatherProgression == 1 then
    				SendChatToMember( refid, "Qualify Weather Progression: Real time" )
    			else
    				SendChatToMember( refid, "Qualify Weather Progression: " .. attributes.QualifyWeatherProgression .. "x")
    			end
    			SendChatToMember( refid, "")
    		end	
    	end
    
    	-- Send Race weather
    	if send_setup.weather then
    		local weather = {}
    		if attributes.RaceWeatherSlots == 0 then
    			table.insert( weather, "Real Weather" )
    		else
    			for i = 1,attributes.RaceWeatherSlots do
    				local wvalue = attributes[ "RaceWeatherSlot" .. i ]
    				local wname = value_to_weather[ wvalue ] or tostring( wvalue )
    				table.insert( weather, wname )
    			end
    		end
    		SendChatToMember( refid, "Race Weather: " .. table.concat( weather, " -> " ) )
    		if attributes.RaceWeatherProgression == 0 then
    			SendChatToMember( refid, "Race Weather Progression: Sync to Race" )
    		elseif attributes.RaceWeatherProgression == 1 then
    			SendChatToMember( refid, "Race Weather Progression: Real time" )
    		else
    			SendChatToMember( refid, "Race Weather Progression: " .. attributes.RaceWeatherProgression .. "x")
    		end
    		SendChatToMember( refid, "")		
    	end
    
    	-- Send Race date/time
    	if send_setup.date then
    		if attributes.PracticeLength ~= 0 then			
    			SendChatToMember( refid, "Practice Starting time: " .. attributes.PracticeDateHour .. ":00" )
    			if attributes.PracticeDateProgression == 0 then
    				SendChatToMember( refid, "Practice Time Progression: OFF" )
    			elseif attributes.PracticeDateProgression == 1 then
    				SendChatToMember( refid, "Practice Time Progression: Real Time")	
    			else
    				SendChatToMember( refid, "Practice Time Progression: " .. attributes.PracticeDateProgression .. "x")
    			end
    			SendChatToMember( refid, "")		
    				-- not valid yet in lobby SendChatToMember( refid, "Current time: " .. attributes.CurrentYear .. "-" .. attributes.CurrentMonth .. "-" .. attributes.CurrentDay .. " " .. attributes.CurrentHour .. ":" .. attributes.CurrentMinute )
    		end
    	end
    
    	-- Send Race date/time
    	if send_setup.date then
    		if attributes.QualifyLength ~= 0 then			
    			SendChatToMember( refid, "Qualify Starting time: "  .. attributes.QualifyDateHour .. ":00" )
    			if attributes.QualifyDateProgression == 0 then
    				SendChatToMember( refid, "Qualify Time Progression: OFF" )
    			elseif attributes.QualifyDateProgression == 1 then
    				SendChatToMember( refid, "Qualify Time Progression: Real Time")	
    			else
    				SendChatToMember( refid, "Qualify Time Progression: " .. attributes.QualifyDateProgression .. "x")
    			end
    			SendChatToMember( refid, "")		
    			-- not valid yet in lobby SendChatToMember( refid, "Current time: " .. attributes.CurrentYear .. "-" .. attributes.CurrentMonth .. "-" .. attributes.CurrentDay .. " " .. attributes.CurrentHour .. ":" .. attributes.CurrentMinute )
    		end
    	end
    
    	-- Send Race date/time
    	if send_setup.date then
    		SendChatToMember( refid, "Race Starting time: " .. attributes.RaceDateYear .. "-" .. attributes.RaceDateMonth .. "-" .. attributes.RaceDateDay .. " " .. attributes.RaceDateHour .. ":00" )
    		if attributes.RaceDateProgression == 0 then
    			SendChatToMember( refid, "Race Time Progression: OFF" )
    		elseif attributes.RaceDateProgression == 1 then
    			SendChatToMember( refid, "Race Time Progression: Real Time")	
    		else
    			SendChatToMember( refid, "Race Time Progression: " .. attributes.RaceDateProgression .. "x")
    		end
    		SendChatToMember( refid, "")		
    		-- not valid yet in lobby SendChatToMember( refid, "Current time: " .. attributes.CurrentYear .. "-" .. attributes.CurrentMonth .. "-" .. attributes.CurrentDay .. " " .. attributes.CurrentHour .. ":" .. attributes.CurrentMinute )
    	end
    
    	SendChatToMember( refid, "Join discord for voice chat @ < INSERT DISCORD LINK HERE >(case sensative)" )
    	
    end
    
    -- The tick that processes all queued sends
    local function tick()
    	local now = GetServerUptimeMs()
    	for refid,time in pairs( scheduled_sends ) do
    		if now >= time then
    			send_now( refid )
    			scheduled_sends[ refid ] = nil
    		end
    	end
    end
    
    
    -- Request send to given refid, or all session members if refid is not specified.
    local function send_motd_to( refid )
    	local send_time = GetServerUptimeMs() + 2000
    	if refid then
    		scheduled_sends[ refid ] = send_time
    	else
    		for k,_ in pairs( session.members ) do
    			scheduled_sends[ k ] = send_time
    		end
    	end
    end
    
    
    -- Main addon callback
    local function addon_callback( callback, ... )
    
    	-- Regular tick
    	if callback == Callback.Tick then
    		tick()
    	end
    
    	-- Welcome new members.
    	if callback == Callback.MemberStateChanged then
    		local refid, _, new_state = ...
    		if new_state == "Connected" then
    			send_motd_to( refid )
    		end
    	end
    
    	-- Handle session state change back to lobby
    	if callback == Callback.EventLogged then
    		local event = ...
    		if ( event.type == "Session" ) and ( event.name == "StateChanged" ) then
    			if ( event.attributes.PreviousState ~= "None" ) and ( event.attributes.NewState == "Lobby" ) then
    				if send_when_returning_to_lobby then
    					send_motd_to()
    				end
    			end
    		end
    	end
    end
    
    
    -- Main
    RegisterCallback( addon_callback )
    EnableCallback( Callback.Tick )
    EnableCallback( Callback.MemberStateChanged )
    if send_when_returning_to_lobby then
    	EnableCallback( Callback.EventLogged )
    end
    
    
    -- EOF --
    sms_base.lua MODIFIED: Allows usage of CLASS Name for the value in MultiClassSlot1-3 instead of the INT value e.g "LMP1" instead of -1289517523 in the sms_rotate_config.json file
    Code:
    -- The server defines various lists: tracks, vehicles, vehicle_classes, as well as several enums and flags lists
    -- These lists are ordered arrays of structures. For easier access we process the lists at startup and build the
    -- following structures from them.
    
    -- Maps from either IDs or names to vehicles/vehicle classes/tracks.
    -- The values are the vehicle/class/track structures.
    -- So for example id_to_track[ 1300627020 ] and name_to_track[ "Brands Hatch Indy" ] both reference the same structure with info about the Brands Hatch Indy track.
    id_to_vehicle = {}
    name_to_vehicle = {}
    id_to_vehicle_class = {}
    name_to_vehicle_class = {}
    id_to_track = {}
    name_to_track = {}
    
    -- Attributes.
    -- Maps from attribute names to attribute descriptors.
    name_to_session_attribute = {}
    name_to_member_attribute = {}
    name_to_participant_attribute = {}
    
    -- Callbacks.
    -- Map from callback type names to values used when enabling/disabling the callbacks.
    -- So for example you would call "EnableCallback( Callback.Tick )" to enable the "Tick" callback.
    Callback = {}
    value_to_callback = {}
    
    -- Enums extracted from builtin lists.
    -- Maps from enum names to values, so for example Damage.OFF is 0, AllowedView.CockpitHelmet is 2.
    Damage = {}
    TireWear = {}
    FuelUsage = {}
    Penalties = {}
    GameMode = {}
    AllowedView = {}
    Weather = {}
    MultiClass = {}
    GridPositions = {}
    ManualPitStops = {}
    OnlineRep = {}
    
    -- Inverse enum maps from values to names.
    value_to_damage = {}
    value_to_tire_wear = {}
    value_to_fuel_usage = {}
    value_to_penalties = {}
    value_to_game_mode = {}
    value_to_allowed_view = {}
    value_to_weather = {}
    value_to_multiclass = {}
    value_to_grid_positions = {}
    value_to_pit_control = {}
    value_to_online_rep = {}
    
    -- Flags extracted from builtin lists.
    -- Maps from flag names to values, so for example SessionFlags.FORCE_IDENTICAL_VEHICLES is 2.
    SessionFlags = {}
    PlayerFlags = {}
    
    -- Inverse flags maps from values to names.
    value_to_session_flag = {}
    value_to_player_flag = {}
    
    
    -- Helper function: Get track name. Automatically returns "<unknown track %d>" if passed track id is not a valid tack id.
    function get_track_name_by_id( track_id )
    	local track = id_to_track[ track_id ]
    	if track then return track.name end
    	return string.format( "<unknown track %d>", track_id )
    end
    
    
    -- Helper function: Get vehicle name. Automatically returns "<unknown vehicle %d>" if passed vehicle id is not a valid vehicle id.
    function get_vehicle_name_by_id( vehicle_id )
    	local vehicle = id_to_vehicle[ vehicle_id ]
    	if vehicle then return vehicle.name end
    	return string.format( "<unknown vehicle %d>", vehicle_id )
    end
    
    
    -- Helper function: Simple table dumper
    -- Call dump( table ) to print the table contents to output. Useful for debugging scripts.
    function dump( table, indent )
    	indent = indent or ""
    	for k,v in pairs( table ) do
    		if type( v ) == "table" then
    			print( indent .. k .. ":" );
    			dump( v, indent .. "  " )
    		else
    			print( indent .. k .. ": " .. tostring( v ) )
    		end
    	end
    end
    
    
    -- Helper function: Similar to dump, but also prints key and scalar value types.
    function dump_typed( table, indent )
    	indent = indent or ""
    	for k,v in pairs( table ) do
    		if type( v ) == "table" then
    			print( indent .. type( k ) .. " " .. k .. ":" );
    			dump_typed( v, indent .. "  " )
    		else
    			print( indent .. type( k ) .. " " .. k .. ": " .. type( v ) .. " " .. tostring( v ) )
    		end
    	end
    end
    
    
    -- Helper function: Extend the "string" package with split method that returns array of string splits on given pattern.
    function string:split( pattern, results )
    	if not results then results = {} end
    	local offset = 1
    	local split_start, split_end = string.find( self, pattern, offset )
    	while split_start do
    		table.insert( results, string.sub( self, offset, split_start - 1 ) )
    		offset = split_end + 1
    		split_start, split_end = string.find( self, pattern, offset )
    	end
    	table.insert( results, string.sub( self, offset ) )
    	return results
    end
    
    
    -- Helper function: Extend the "table" package with shallow copy function. This also does not copy the meta-table.
    function table.shallow_copy( other_table )
    	if type( other_table ) ~= "table" then return other_table end
    	local new_table = {}
    	for k,v in pairs( other_table ) do
    		new_table[ k ] = v
    	end
    	return new_table
    end
    
    
    -- Helper function: Extend the "table" package with deep copy function. This also copies the meta-table.
    function table.deep_copy( other_table, seen )
    	if type( other_table ) ~= "table" then return other_table end
    	if seen and seen[ other_table ] then return seen[ other_table ] end
    	local s = seen or {}
    	local new_table = setmetatable( {}, getmetatable( other_table ) )
    	s[ other_table ] = new_table
    	for k,v in pairs( other_table ) do
    		new_table[ table.deep_copy( k, s ) ] = table.deep_copy( v, s )
    	end
    	return new_table
    end
    
    
    -- Helper function: Deep copy function that also converts keys in subtables matching given names from strings to integers.
    function table.deep_copy_normalized( other_table, intkey_table_names, this_table_name, seen )
    	if type( other_table ) ~= "table" then return other_table end
    	if seen and seen[ other_table ] then return seen[ other_table ] end
    	local s = seen or {}
    	local new_table = setmetatable( {}, getmetatable( other_table ) )
    	s[ other_table ] = new_table
    	local itn = intkey_table_names or {}
    	local ttn = this_table_name or ""
    	if itn[ ttn ] then
    		for k,v in pairs( other_table ) do
    			local kcopy = tonumber( table.deep_copy_normalized( k, itn, nil, s ) )
    			new_table[ kcopy ] = table.deep_copy_normalized( v, itn, nil, s )
    		end
    	else
    		for k,v in pairs( other_table ) do
    			local kcopy = table.deep_copy_normalized( k, itn, nil, s )
    			new_table[ kcopy ] = table.deep_copy_normalized( v, itn, kcopy, s )
    		end
    	end
    	return new_table
    end
    
    
    -- Helper function: Extend the "table" package with list to set conversion. List is a simple array of scalars, the set is map from those scalars to "true".
    function table.list_to_set( list )
    	local set = {}
    	for _,v in ipairs( list ) do
    		set[ v ] = true
    	end
    	return set
    end
    
    
    -- Add value to a table element, or create new element with the value.
    function table.add( table, key, delta )
    	if table[ key ] then
    		table[ key ] = table[ key ] + delta
    	else
    		table[ key ] = delta
    	end
    end
    
    
    -- Equivalent to table.add with negated delta
    function table.subtract( table, key, delta )
    	if table[ key ] then
    		table[ key ] = table[ key ] - delta
    	else
    		table[ key ] = -delta
    	end
    end
    
    
    local attribute_to_enum = {
    	DamageType = { Damage, value_to_damage },
    	TireWearType = { TireWear, value_to_tire_wear },
    	FuelUsageType = { FuelUsage, value_to_fuel_usage },
    	PenaltiesType = { Penalties, value_to_penalties },
    	AllowedViews = { AllowedView, value_to_allowed_view },
    	PracticeWeatherSlot1 = { Weather, value_to_weather },
    	PracticeWeatherSlot2 = { Weather, value_to_weather },
    	PracticeWeatherSlot3 = { Weather, value_to_weather },
    	PracticeWeatherSlot4 = { Weather, value_to_weather },
    	QualifyWeatherSlot1 = { Weather, value_to_weather },
    	QualifyWeatherSlot2 = { Weather, value_to_weather },
    	QualifyWeatherSlot3 = { Weather, value_to_weather },
    	QualifyWeatherSlot4 = { Weather, value_to_weather },
    	RaceWeatherSlot1 = { Weather, value_to_weather },
    	RaceWeatherSlot2 = { Weather, value_to_weather },
    	RaceWeatherSlot3 = { Weather, value_to_weather },
    	RaceWeatherSlot4 = { Weather, value_to_weather },
    	MultiClassSlot1 = { MultiClass, value_to_multiclass},
    	MultiClassSlot2 = { MultiClass, value_to_multiclass},
    	MultiClassSlot3 = { MultiClass, value_to_multiclass},
    	GridLayout = { GridPositions, value_to_grid_positions },
    	ManualPitStops = { ManualPitStops, value_to_pit_control },
    	MinimumOnlineRank = { OnlineRep, value_to_online_rep },
    }
    
    local attribute_to_flags = {
    	Flags = { SessionFlags, value_to_session_flag },
    }
    
    
    -- Helper function: Remap track name to id if possible.
    function normalize_track( value )
    	local t = type( value )
    	if ( t == "number" ) or ( t == "integer" ) then
    		return value
    	end
    	if ( t == "string" ) then
    		local track = name_to_track[ value ]
    		if track then
    			return track.id
    		end
    		error( "Unknown track name '" .. value .. "' passed to normalize_track" )
    	end
    	error( "Value of unexpected type '" .. t .. "' passed to normalize_track" )
    end
    
    
    -- Helper function: Remap vehicle name to id if possible.
    function normalize_vehicle( value )
    	local t = type( value )
    	if ( t == "number" ) or ( t == "integer" ) then
    		return value
    	end
    	if ( t == "string" ) then
    		local vehicle = name_to_vehicle[ value ]
    		if vehicle then
    			return vehicle.id
    		end
    		error( "Unknown vehicle name '" .. value .. "' passed to normalize_vehicle" )
    	end
    	error( "Value of unexpected type '" .. t .. "' passed to normalize_vehicle" )
    end
    
    
    -- Helper function: Remap vehicle class name to id if possible.
    function normalize_vehicle_class( value )
    	local t = type( value )
    	if ( t == "number" ) or ( t == "integer" ) then
    		return value
    	end
    	if ( t == "string" ) then
    		local vehicle_class = name_to_vehicle_class[ value ]
    		if vehicle_class then
    			return vehicle_class.id
    		end
    		error( "Unknown vehicle class '" .. value .. "' passed to normalize_vehicle_class" )
    	end
    	error( "Value of unexpected type '" .. t .. "' passed to normalize_vehicle_class" )
    end
    
    
    -- Helper function: Remap enum value from string to number if possible. The value can be either an integral value, or the enum name.
    function normalize_session_enum( enum, value )
    	local t = type( value )
    	if ( t == "number" ) or ( t == "integer" ) then
    		return value
    	end
    	if ( t == "string" ) then
    		local v = enum[ value ]
    		if v then
    			return v
    		end
    		error( "Unknown value '" .. value .. "' passed to normalize_session_enum" )
    	end
    	error( "Value of unexpected type '" .. t .. "' passed to normalize_session_enum" )
    end
    
    
    -- Helper function: Remap flag value from string to number if possible. The value can be either an integral value, the flag name, or comma-separated list of integral values and flag names.
    function normalize_session_flags( flags, value )
    	local t = type( value )
    	if ( t == "number" ) or ( t == "integer" ) then
    		return value
    	end
    	if ( t == "string" ) then
    		local f = 0
    		for _,flag in ipairs( value:split( "," ) ) do
    			local n = tonumber( flag, 10 )
    			if n then
    				f = f | n
    			else
    				local v = flags[ flag ]
    				if v then
    					f = f | v
    				else
    					error( "Unknown flag value '" .. flag .. "' passed to normalize_session_flags" )
    				end
    			end
    		end
    		return f
    	end
    	error( "Value of unexpected type '" .. t .. "' passed to normalize_session_flags" )
    end
    
    
    -- Help function: Normalize session key-value attribute pair. Returns the value, after applying normalization if the key is enum or flags.
    function normalize_session_attribute( key, value )
    	if key == "TrackId" then
    		return normalize_track( value )
    	elseif key == "VehicleModelId" then
    		return normalize_vehicle( value )
    	elseif key == "VehicleClassId" then
    		return normalize_vehicle_class( value )
    	end
    
    	local enum = attribute_to_enum[ key ]
    	if enum then
    		return normalize_session_enum( enum[ 1 ], value )
    	end
    
    	local flags = attribute_to_flags[ key ]
    	if flags then
    		return normalize_session_flags( flags[ 1 ], value )
    	end
    
    	return value
    end
    
    
    -- Helper function: Go over table with attribute-value pairs, and fixup all enum/flag values from strings to integers
    function normalize_session_attributes( attributes )
    	for k,v in pairs( attributes ) do
    		attributes[ k ] = normalize_session_attribute( k, v )
    	end
    	return attributes
    end
    
    
    -- Redefine builtins SetSessionAttributes, SetNextSessionAttributes, SetSessionAndNextAttributes
    -- to always automatically normalize enum/flags arguments.
    local builtinSetSessionAttributes = SetSessionAttributes
    local builtinSetNextSessionAttributes = SetNextSessionAttributes
    local builtinSetSessionAndNextAttributes = SetSessionAndNextAttributes
    
    function SetSessionAttributes( attributes )
    	return builtinSetSessionAttributes( normalize_session_attributes( attributes ) )
    end
    
    function SetNextSessionAttributes( attributes )
    	return builtinSetNextSessionAttributes( normalize_session_attributes( attributes ) )
    end
    
    function SetSessionAndNextAttributes( attributes )
    	return builtinSetSessionAndNextAttributes( normalize_session_attributes( attributes ) )
    end
    
    
    -- Helper function: Reverse of normalize_track, convert integral value to track name, if possible
    function stringify_track( value )
    	local t = type( value )
    	if ( t == "number" ) or ( t == "integer" ) then
    		local track = id_to_track[ value ]
    		if track then
    			return track.name
    		else
    			return value
    		end
    	end
    	if ( t == "string" ) then
    		return value
    	end
    	error( "Value of unexpected type '" .. t .. "' passed to stringify_track" )
    end
    
    
    -- Helper function: Reverse of normalize_vehicle, convert integral value to vehicle name, if possible
    function stringify_vehicle( value )
    	local t = type( value )
    	if ( t == "number" ) or ( t == "integer" ) then
    		local vehicle = id_to_vehicle[ value ]
    		if vehicle then
    			return vehicle.name
    		else
    			return value
    		end
    	end
    	if ( t == "string" ) then
    		return value
    	end
    	error( "Value of unexpected type '" .. t .. "' passed to stringify_vehicle" )
    end
    
    
    -- Helper function: Reverse of normalize_vehicle_class, convert integral value to vehicle class name, if possible
    function stringify_vehicle_class( value )
    	local t = type( value )
    	if ( t == "number" ) or ( t == "integer" ) then
    		local vehicle_class = id_to_vehicle_class[ value ]
    		if vehicle_class then
    			return vehicle_class.name
    		else
    			return value
    		end
    	end
    	if ( t == "string" ) then
    		return value
    	end
    	error( "Value of unexpected type '" .. t .. "' passed to stringify_vehicle_class" )
    end
    
    
    -- Helper function: Reverse of normalize_session_enum, convert integral value to enum name, if possible
    function stringify_session_enum( enum, value )
    	local t = type( value )
    	if ( t == "number" ) or ( t == "integer" ) then
    		local s = enum[ value ]
    		if s then
    			return s
    		else
    			return value
    		end
    	end
    	if ( t == "string" ) then
    		return value
    	end
    	error( "Value of unexpected type '" .. t .. "' passed to stringify_session_enum" )
    end
    
    
    -- Helper function: Reverse of normalize_session_flags, convert integral value to comma-separated stringified flags, if possible
    function stringify_session_flags( flags, value )
    	local t = type( value )
    	if ( t == "number" ) or ( t == "integer" ) then
    		local s = ""
    		for flag,name in pairs( flags ) do
    			if ( value & flag ) == flag then
    				value = value & ~flag
    				if s ~= "" then s = s .. "," end
    				s = s .. name
    			end
    		end
    		if value ~= 0 then
    			if s ~= "" then s = s .. "," end
    			s = s .. tostring( value )
    		end
    		return s
    	end
    	if ( t == "string" ) then
    		return value
    	end
    	error( "Value of unexpected type '" .. t .. "' passed to stringify_session_enum" )
    end
    
    
    -- Helper function: Reverse of normalize_session_attribute, convert values to strings if the key is enum or flags
    function stringify_session_attribute( key, value )
    	if key == "TrackId" then
    		return stringify_track( value )
    	elseif key == "VehicleModelId" then
    		return stringify_vehicle( value )
    	elseif key == "VehicleClassId" then
    		return stringify_vehicle_class( value )
    	end
    
    	local enum = attribute_to_enum[ key ]
    	if enum then
    		return stringify_session_enum( enum[ 2 ], value )
    	end
    
    	local flags = attribute_to_flags[ key ]
    	if flags then
    		return stringify_session_flags( flags[ 2 ], value )
    	end
    
    	return value
    end
    
    
    -- Helper function: Reverse of normalize_session_attributes, convert values of known enums/flags to strings where possible
    function stringify_session_attributes( attributes )
    	for k,v in pairs( attributes ) do
    		attributes[ k ] = stringify_session_attribute( k, v )
    	end
    	return attributes
    end
    
    
    ---------------------------------------------------------------------------------------------------------
    -- Private stuff begins, nothing to see down here if you care only about the public BASE functionality --
    ---------------------------------------------------------------------------------------------------------
    
    -- Create map from a list referencing specific list values.
    -- For example to create map of flag names to flag values.
    local function extract_list_values( target_map, source_list, key_name, value_name )
    	for _,v in ipairs( source_list ) do
    		target_map[ v[ key_name ] ] = v[ value_name ]
    	end
    end
    
    -- Create map from a list referencing the list elements.
    -- For example to create map of vehicle ids to vehicles.
    local function extract_list_references( target_map, source_list, key_name )
    	for _,v in ipairs( source_list ) do
    		target_map[ v[ key_name ] ] = v
    	end
    end
    
    -- Create various useful maps from the built-in lists.
    local function extract_lists()
    	extract_list_references( id_to_vehicle, lists.vehicles.list, "id" )
    	extract_list_references( name_to_vehicle, lists.vehicles.list, "name" )
    	extract_list_references( id_to_vehicle_class, lists.vehicle_classes.list, "id" )
    	extract_list_references( name_to_vehicle_class, lists.vehicle_classes.list, "name" )
    	extract_list_references( id_to_track, lists.tracks.list, "id" )
    	extract_list_references( name_to_track, lists.tracks.list, "name" )
    
    	extract_list_references( name_to_session_attribute, lists.attributes.session.list, "name" )
    	extract_list_references( name_to_member_attribute, lists.attributes.member.list, "name" )
    	extract_list_references( name_to_participant_attribute, lists.attributes.participant.list, "name" )
    
    	extract_list_values( Callback, lists.callbacks.list, "name", "value" )
    	extract_list_values( value_to_callback, lists.callbacks.list, "value", "name" )
    
    	extract_list_values( Damage, lists.enums.damage.list, "name", "value" )
    	extract_list_values( TireWear, lists.enums.tire_wear.list, "name", "value" )
    	extract_list_values( FuelUsage, lists.enums.fuel_usage.list, "name", "value" )
    	extract_list_values( Penalties, lists.enums.penalties.list, "name", "value" )
    	extract_list_values( GameMode, lists.enums.game_mode.list, "name", "value" )
    	extract_list_values( AllowedView, lists.enums.allowed_view.list, "name", "value" )
    	extract_list_values( Weather, lists.enums.weather.list, "name", "value" )
    	extract_list_values( MultiClass, lists.vehicle_classes.list, "name", "id" )
    	extract_list_values( GridPositions, lists.enums.grid_positions.list, "name", "value" )
    	extract_list_values( ManualPitStops, lists.enums.pit_control.list, "name", "value" )
    	extract_list_values( OnlineRep, lists.enums.online_rep.list, "name", "value" )
    
    	extract_list_values( value_to_damage, lists.enums.damage, "value", "name" )
    	extract_list_values( value_to_tire_wear, lists.enums.tire_wear.list, "value", "name" )
    	extract_list_values( value_to_fuel_usage, lists.enums.fuel_usage.list, "value", "name" )
    	extract_list_values( value_to_penalties, lists.enums.penalties.list, "value", "name" )
    	extract_list_values( value_to_game_mode, lists.enums.game_mode.list, "value", "name" )
    	extract_list_values( value_to_allowed_view, lists.enums.allowed_view.list, "value", "name" )
    	extract_list_values( value_to_weather, lists.enums.weather.list, "value", "name" )
    	extract_list_values( value_to_multiclass, lists.vehicle_classes.list, "id", "name" )
    	extract_list_values( value_to_grid_positions, lists.enums.grid_positions.list, "value", "name" )
    	extract_list_values( value_to_pit_control, lists.enums.pit_control.list, "value", "name" )
    	extract_list_values( value_to_online_rep, lists.enums.online_rep.list, "value", "name" )
    
    	extract_list_values( SessionFlags, lists.flags.session.list, "name", "value" )
    	extract_list_values( PlayerFlags, lists.flags.player.list, "name", "value" )
    
    	extract_list_values( value_to_session_flag, lists.flags.session.list, "value", "name" )
    	extract_list_values( value_to_player_flag, lists.flags.player.list, "value", "name" )
    end
    
    extract_lists()
    
    -- Temporary hacky tests
    
    --[[
    print( "SPLIT TESTS:" )
    dump( ( "foo,bar,baz" ):split( "," ) )
    dump( ( "foobar" ):split( "o" ) )
    dump( ( "foo,bar,baz" ):split( "," ) )
    dump( ( "foobar" ):split( "o" ) )
    --]]
    
    
    --[[
    dump( SessionFlags )
    dump( Weather )
    print( "" )
    print( "CLEAN TESTS:" )
    local attrs = { TrackId = 1234, Flags = "AI_ALLOWED,FORCE_IDENTICAL_VEHICLES", WeatherSlot1 = "Weather_Clear1", ServerControlsVehicleClass = 1, ServerControlsVehicle = 0, Practice1Length = 5 }
    dump( attrs )
    print( "" )
    print( "AFTER:" )
    normalize_session_attributes( attrs )
    dump( attrs )
    print( "" )
    print( "AND BACK:" )
    stringify_session_attributes( attrs )
    dump( attrs )
    --]]
    
    -- EOF --
    MULTI-CLASS lib_rotate.lua has been officially fixed by SMS, no more need for my file
    UPDATED 2/21/18
    Last edited by MortICi; 25-02-2018 at 06:12. Reason: Updated code and updated zip file.
    Gaming Rig: ASRock z77 Extreme4, Intel Core i5 3570K OC @ 4.6Ghz 1.23v, Samsung 840 PRO 250GB SSD, EVGA GTX 970 SSC ASX 2.0 4GB OC'd like a beast! +156 core, +406 mem +0mv, Corsair H100i, 16GB G.SKill Ripjaws X Series 10-10-10-30-2N DDR3, Lots-O-Fans
    Controller: Logitech G25 Steering Wheel
    Output: 55" LG 3D SmartTV
    Seat: Need to buy one or something....

  2. #2
    Kart Driver
    Join Date
    May 2015
    Posts
    21
    Platform
    PC

    Such a great work

    Thank you so much for your great work, the first time it seems the DS works
    The following user likes this Post: Mbondracing


  3. #3
    WMD Member
    Join Date
    May 2015
    Posts
    9
    Platform
    PC
    I believe it when I'am at my server and don't need an umbrella.
    Thanks for your work MortICi!

  4. #4
    Rookie
    Join Date
    May 2017
    Posts
    3
    Platform
    PC
    Has somebody got manual pit stops working on a dedicated server? Tried everything, but everytime I leave pits, the AI takes over control.

  5. #5
    WMD Member
    Join Date
    Oct 2011
    Posts
    208
    Quote Originally Posted by KampfSchneggy View Post
    Has somebody got manual pit stops working on a dedicated server? Tried everything, but everytime I leave pits, the AI takes over control.
    The setting in the LUA file is flipped, it states it should be 1, but actually setting it to 0 enables manual pit stops. At least that works for me, in the settings page for the server in the lobby it will show as disabled, but in session it should be manual.

    Code:
    // Force Manual Pitstops: 0 Enable 1 Disable -- This might get fixed in future ded server updates
    // -- Bugged values are reverse to what documentations states. NOTE: No visual change in In Game Lobby view
    "ManualPitStops" : 0,
    Gaming Rig: ASRock z77 Extreme4, Intel Core i5 3570K OC @ 4.6Ghz 1.23v, Samsung 840 PRO 250GB SSD, EVGA GTX 970 SSC ASX 2.0 4GB OC'd like a beast! +156 core, +406 mem +0mv, Corsair H100i, 16GB G.SKill Ripjaws X Series 10-10-10-30-2N DDR3, Lots-O-Fans
    Controller: Logitech G25 Steering Wheel
    Output: 55" LG 3D SmartTV
    Seat: Need to buy one or something....

  6. #6
    WMD Member
    Join Date
    May 2015
    Posts
    9
    Platform
    PC
    Still fighting. I keep getting kicked from my server after ~1 minute.
    When looking at my DS I see the following:

    [2018-02-14 18:52:14] WARNING: Auth session with connected user 76561197976448056 (Sundance) failed with error AuthTicketCanceled. Disconnecting the user
    [2018-02-14 18:52:14] INFO: Removing user 76561197976448056 (Sundance) at index 0, refId 29056, admin 1 - disconnected
    [2018-02-14 18:52:14] INFO: Abandoning timesync for user 76561197976448056 in timesync slot 0
    [2018-02-14 18:52:14] INFO: Everyone left the server, resetting to 'available for use'

    I searched the internet and found many other with the 'AuthTicketCanceled' problem but no real solutions.
    If someone has the same problem/solution it would be nice to hear from you. Also coukld somebody try to jump at my server (Sundance's Server ...) and drive a round or two? :-)

    Thanks in advance
    Last edited by hilavoku; 15-02-2018 at 11:36.

  7. #7
    WMD Member
    Join Date
    Oct 2011
    Posts
    208
    Quote Originally Posted by hilavoku View Post
    Still fighting. I keep getting kicked from my server after ~1 minute.
    When looking at my DS I see the following:

    [2018-02-14 18:52:14] WARNING: Auth session with connected user 76561197976448056 (Sundance) failed with error AuthTicketCanceled. Disconnecting the user
    [2018-02-14 18:52:14] INFO: Removing user 76561197976448056 (Sundance) at index 0, refId 29056, admin 1 - disconnected
    [2018-02-14 18:52:14] INFO: Abandoning timesync for user 76561197976448056 in timesync slot 0
    [2018-02-14 18:52:14] INFO: Everyone left the server, resetting to 'available for use'

    I searched the internet and found many other with the 'AuthTicketCanceled' problem but no real solutions.
    If someone has the same problem/solution it would be nice to hear from you. Also coukld somebody try to jump at my server (Sundance's Server ...) and drive a round or two? :-)

    Thanks in advance
    http://forum.projectcarsgame.com/sho...=1#post1474742

    See the above post.
    Gaming Rig: ASRock z77 Extreme4, Intel Core i5 3570K OC @ 4.6Ghz 1.23v, Samsung 840 PRO 250GB SSD, EVGA GTX 970 SSC ASX 2.0 4GB OC'd like a beast! +156 core, +406 mem +0mv, Corsair H100i, 16GB G.SKill Ripjaws X Series 10-10-10-30-2N DDR3, Lots-O-Fans
    Controller: Logitech G25 Steering Wheel
    Output: 55" LG 3D SmartTV
    Seat: Need to buy one or something....

  8. #8
    WMD Member
    Join Date
    May 2015
    Posts
    9
    Platform
    PC
    Thanks MortICi. I already saw the post and checked my settings. Will look at it over the weekend...

  9. #9
    WMD Member
    Join Date
    May 2015
    Posts
    9
    Platform
    PC
    I now took your files and only change the servername and the user and pwd for the http interface. Nothing else. I still got kicked.
    I opened all ports for the dedicatedservercmd.exe (TCP/UDP). No luck. Also disabled IPv6 protocol...
    Disabling the firewall also doesn't help.
    Last edited by hilavoku; 16-02-2018 at 16:25.

  10. #10
    Kart Driver
    Join Date
    Nov 2016
    Posts
    10
    Platform
    PC
    Hey MortICi,

    Thanks for your contributions! We took a look at the main issue in lib_rotate.lua for MultiClass, and notice that after last hotfix patch (released today) we have introduced a change that allows MultiClass to be set in the json config in a similar fashion it's done for SameClass and IdenticalVehicles. As the doc lines in new lib_rotate.lua say:

    The "setup" table is very similar to simple attributes table, with some extra rules:
    - If TrackId is set, sets 1 to ServerControlsTrack
    - If VehicleModelId is set, sets 1 to ServerControlsVehicle, sets FORCE_IDENTICAL_VEHICLES to Flags
    - If VehicleClassId is set, sets 1 to ServerControlsVehicleClass, sets FORCE_SAME_VEHICLE_CLASS to Flags
    - If MultiClassSlots and VehicleClassId are set, sets 1 to ServerControlsVehicleClass, sets FORCE_MULTI_VEHICLE_CLASS to Flags
    - These ServerControls* attributes and FORCE_* flags are in exclusive control of the library and
    can't be included in the setups directly.
    As you pointed out with your modifications in lib_rotate.lua, the issue could be originated if ServerControlsVehicleClass and the FORCE* flags were processed in the wrong order. This is no longer an issue as MultiClassSlots and VehicleClassId attributes are now the ones deciding the flags to be set.

    Cheers

Similar Threads

  1. Replies: 5
    Last Post: 31-08-2018, 14:35
  2. Dedicated Server configuration
    By Visceral_Syn in forum Multiplayer
    Replies: 328
    Last Post: 24-07-2018, 15:50
  3. Replies: 21
    Last Post: 03-10-2017, 19:42
  4. Replies: 8
    Last Post: 25-04-2017, 23:52
  5. Replies: 12
    Last Post: 23-10-2015, 21:10

Posting Permissions

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