| Home Page | Recent Changes | Preferences

Config Vars And .Ini Files

Often it is desireable to preserve the output of a program after it has terminated. Other times, it may be desireable to be able to read external files for user settings, etcetera. Games, of course, are no different. Unfortunately, UnrealScripts file system functions are somewhat limited. There are no file streams or other sorts of low-level file interaction, but you can usually still do what you want within the constraints of the file system functions that are availible.

Write-Only and Read-Write Operations

There are several reasons why one might want to write to the hard drive from within UnrealScript. The most common, of course, is for logging and debugging, but data is also often written to the hard drive for later recovery by some reading function. There are three known ways to write data to the hard drive: the log file, config variables/ini files, and the savepackage suite of functions.

Writing to the log file

The log file is generally the easiest and most straightforward of the writing functions. The log file is shared among all classes, and will be overwritten every time the game is started. In addition, no facility exists to read the log file from within Unrealscript, though it is easily readable with any text editor. Note that writing to the log file actually writes to a buffer which is periodically written to the log file, so if the engine crashes with a GPF or similar error shortly after the log statement, it may not have made it into the actual log file. The engine will automatically write out anything left in the log buffer during a normal shutdown. To write to the log file, you can use one of two functions:

Log( Text, Prefix );

The log function takes two arguments, though the second argument is optional. The first argument is a string and is the text that will be written into the log. You can put any variable type in here, and the function will automatically cast it into a string for you. The second argument is the prefix you would like to have for your log message. The prefix is a name variable, not a string, so it should be enclosed in single quotes and cannot contain spaces. If you omit a prefix, the prefix will default to 'ScriptLog'.

Warn( Text );

The Warn function is similar to log, but cannot take a prefix and also automatically provides a bit of information about itself. It will list the name of the thing that called it as well as the function and line it was called from. It will even tell you which class it was that the warning was set in, so if you make a call to a superclasses function and that function generates a warning, it will include the name of the superclass instead of the subclass.

Examples of Log and Warn

The usage of Log and Warn is fairly straightforward, but some examples are provided here to help illustrate the description.

class SomeClass extends Interaction;

function LogSample()
{
  local int i;
  local string Example;

  log( "First Message" );
  log( "Second Message", 'ExampleMsg' );
  warn( "Third Message" );

  Example = "Iterating Message";

  for ( i=0;i<5;i++ )
    log( Example$" Number "$I );
}

The example function above would produce the following output in the log file:

 ScriptLog: First Message
 ExampleMsg: Second Message
 Warning: Someclass Package.InteractionMaster.SomeClass (Function SomeClass.LogSample:0048) Third Message
 ScriptLog: Iterating Message Number 0
 ScriptLog: Iterating Message Number 1
 ScriptLog: Iterating Message Number 2
 ScriptLog: Iterating Message Number 3
 ScriptLog: Iterating Message Number 4

If you want to write to the log file, but do not want to write into the same log file as everything else, you can use a FileLog instead, which allows you to specify a filename but requires a bit more work to use.

Config Variables and .Ini Files

.Ini files are the closest to a two way (read/write) operation that Unrealscript can perform. .Ini files are read when the class they are associated with is first loaded. After that, they can only be written to, but the values from their reading remains in memory. .Ini files are very closely tied to config variables, and they work by changing the default properties of those variables. For instance, if you have a string that is a config variable, and you change it's value in the .Ini file, it's default value for that string will have updated when that class' package is next loaded. This is a very good method of having settings persist on something that has only one instance.

Writing to an .ini file is a two-step process, but it is simple to do. First, you must set the value of the variables you wish to write to disk. Usually if you are using .ini files to create persistant settings this will likely already be done. Once that is done, simply have your class call either SaveConfig() or StaticSaveConfig(). SaveConfig() will write the contents of all the config variables in the instance of the class you call it from to the .Ini file assigned to that class. This defaults to User.Ini, but can be changed in the class declaration. (See Class Syntax) StaticSaveConfig() is similar to SaveConfig(), but instead saves the default values of the config variables to disk.

Note that every subclass will have it's own version of the config variables stored in the .Ini file. If you want all subclasses to share the same persistent value, you should declare your variable as globalconfig instead of config. See Variable Syntax.

Also bear in mind that due to the nature of config variables, they will rarely be the same on both server and client. The server also will not automatically replicate it's config variables, unless they are replicated variables, in which case they will be replicated when they change or when a relevant actor replication takes place. See Replication.

There appears to be a limit on the amount of data that can be written to the .Ini file, though it appears to be fairly great. Anything over about a kilobyte of data (such as a large struct) stored in a single variable will not be written properly.

SavePackage

SavePackage is the most complex, most difficult to set up, but most powerful of the saving and loading methods. You can do some limited loading using this suite of functions, and you can even do so when it's needed, which may make it a better choice than .Ini Files if you are storing a very large amount of data that is scarcely needed every time the engine starts. (Like the player profiles in the single player game, for instance.) Once the data is loaded, however, it cannot be reloaded even if the package on disk changes.

Using the SavePackage suite is also the most secure of the methods. A package created with SavePackage is compressed, so it cannot be easily read by humans, though with enough effort and knowlege about the system it uses a person could likely still access the data.

I'll finish this up later, there's lots to write about savepackage.

Daid303: SavePackage is also usefull to store data without having the avage user access to the real data. I use it in my mod for example to keep track of earnd points.

I have a very simple class like this:

class UMS_PointHolder extends Object;

var() int Points;

defaultproperties
{
    Points=0
}

And this code in my playercontroller, the function SavePoints is called whenever the user changes from map OR opens the menu and presses disconnect, I haven't found a way yet to intersept the 'disconnect' console command.

var UMS_PointHolder PointHolder;

simulated function SavePoints()
{
    if (ExtraPoints == 0)
        return;
    if (PointHolder == None)
        GetPointHolder();

    PointHolder.Points += ExtraPoints;
    ExtraPoints = 0;
    GetEntryLevel().Game.SavePackage("UMS_Saved_Data");
}

function GetPointHolder()
{
    PointHolder = GetEntryLevel().Game.LoadDataObject(class'UMS_PointHolder', "pointholder", "UMS_Saved_Data");
    if (PointHolder == None)
    {
        PointHolder = GetEntryLevel().Game.CreateDataObject(class'UMS_PointHolder', "pointholder", "UMS_Saved_Data");
        PointHolder.Points = 0; //make sure nobody has messed with the default props.
    }
}

I don't do it every time the points get updated because it generates a entry in the log every time you save, and whrites to the HD, very speed inefficent.

Read-Only Operations

Read-only operations may not sound as useful, but can in some ways be even more useful for certain applications. These are often referred to as INT files, though there is more to them than they are usually given credit for. There are two main ways to use INT files:

Localized Strings

Behaving very much like an .Ini file, any string variable may be declared as localized in the same was as a variable could be declared as a config variable. A localized variable will be loaded from the INT file when the package is first loaded into memory. However, localized strings are tied into the lanugage preference setting in the Unreal Engine, so the INT file is not always used. INT is the international version of the localized information files. There are numerous other files with different extensions, and the extension indicates which language the file is intended for. If your Unreal Engine is set up for french, the FRT file will be used instead, etc. The INT file is always used as a fallback if there is no file for your language or if the file for your language of choice does not contain a value for a localized string. See Localization.

Meta Information

You can read lots of stuff from these and parse it out to get whatever information you need. See INT File.

Related Topics


Csimbi: I am coding a mutator that is configurable from the menu. Its settings are saved in an .ini file.

How can I make sure that when the mutator is used in a network game then the global variables are used from the server's ini file and not the local one - and vice versa: when it's a single game with bots then the variables are used from the client?

Or is it automatic?

The reason is because I intend to leave an option to display the target's health on the HUD, and I do not want to display the health when it was disabled on the server.

So basically the issue is that I need to read some specific settings from the server's .ini file, and other HUD settings from the local .ini file.

Foxpaw: I think this is discussed on Replication, or variable syntax. Or somewhere. Anyways, rather than search all over for it, I'll just tell you. :D

Config vars aren't replicated automatically. I don't know if you can just put them in the replication block and have it work, but they would probably have to change before they would get replicated. So, a way to do it is by setting the value of a second variable equal to the config variable, and then replicating THAT. IE:

// You'll need to use an actor to do this, BTW, because
// interactions like the HUD don't replicate to my knowledge.
class HUDReplicator extends Actor;

var bool bSomeConfigVariableSlave;

replication
{
  reliable if ( Role == ROLE_Authority )
    bSomeConfigVariableSlave;
}

simulated function PostBeginPlay()
{
  Super.PostBeginPlay();

  bSomeConfigVariableSlave = SomeHUDClass( Level.GetLocalPlayerController() ).default.bAVariable;
}

simulated event PostNetReceive()
{
  if ( Level.NetMode == NM_Client )
    SomeHUDClass( Level.GetLocalPlayerController().myHUD ).bAVariable = bSomeConfigVariableSlave;
}

defaultproperties
{
  bNetNotify=true
}

That may not work verbatim, since I just typed it out from memory, but it's probrably more or less correct.

Csimbi: Thanks for not making me search - been that a lot and did not find solution.

So, basically this is what happens:

  1. bSomeConfigVariableSlave (that is used for temp purpose) is false (all initialised values are zero).
  2. When PostBeginPlay is called the client setting is loaded into bSomeConfigVariableSlave.
  3. When PostNetReceive is called, and machine is a client, the client's setting is loaded into the local(?) class.

Correct me if I am wrong, but this does not do anything I need. You read the client setting, and it is loaded via a temporary variable.

Instead of the example would it be possible to have an explanation? If I get the logic, figuring out the code would not be hard.

Also, how bAVariable shall declared? (config, globalconfig, or it does not matter?)

Thank You.

Foxpaw: Right then, the chain of events is as follows:

  1. The server calls PostBeginPlay as soon as the actor is spawned, which should be done from a mutator or something at the beginning of the level. And now that I think about it, bAlwaysRelevant should be set to true in the default properties too just to make sure it gets replicated. Clients may also call PostBeginPlay, if the actor is built into the level, but even if they do nothing bad will happen.
  2. PostBeginPlay sets the temporary variable, bSomeConfigVariableSlave, to the value of the config variable you want to replicate. A dedicated server likely won't have a HUD on it, so that's why it accesses the default value. You could, in theory, read this from a different object if you want.
  3. Since the bSomeConfigVariableSlave has been changed, (if it was changed, if it's the default.. well, it'll be the default at the client side then so it doesn't matter.) that variable will be replicated to all clients.
  4. Because the actor has bNetNotify true, it calls PostNetRecieve whenever replication data is recieved. In this case, it's the bSomeConfigVariableSlave that's been recieved, so we update our HUD with the value sent to us by the server.

You can of course do this with any variable type, or with multiple variables without much added complication. The declaration of bAVariable doesn't really matter, whatever it is, it will be replicated and updated for all the clients.

Also, you could probrably optimize the above: since mutators ARE actors, there's not much sense in creating an actor like this from a mutator, you could likely just put the above into a mutator. The config variables that you want to replicate don't have to be from a separate object either. It's perfectly legal to do something like this:

// You'll need to use an actor to do this, BTW, because
// interactions like the HUD don't replicate to my knowledge.
class HUDReplicator extends Mutator;

var config bool bAVariable;
var bool bSomeConfigVariableSlave;

replication
{
  reliable if ( Role == ROLE_Authority )
    bSomeConfigVariableSlave;
}

simulated function PostBeginPlay()
{
  Super.PostBeginPlay();

  bSomeConfigVariableSlave = bAVariable;
}

The Unreal Engine Documentation Site

Wiki Community

Topic Categories

Image Uploads

Random Page

Recent Changes

Offline Wiki

Unreal Engine

Console Commands

Terminology

Mapping Topics

Mapping Lessons

UnrealEd Interface

Questions&Answers

Scripting Topics

Scripting Lessons

Making Mods

Class Tree

Questions&Answers

Modeling Topics

Questions&Answers

Log In