Jump to content


Photo

User Profiles And Their Implementation Proposition


  • Please log in to reply
9 replies to this topic

#1 Garo

Garo

    Programming Department

  • Xenocide Programming Department
  • 119 posts

Posted 11 January 2006 - 04:12 AM

After some chatting at #xenocide with guyver and RedKnight, here is my proposition how we could handle user profiles.

Definition: Users have their own profiles which stores their settings. When player starts the game first time, he is asked to create a profile. Subsequent starts then asks the player which profile he would like to use.

Implementation: All settings are stored internally in Settings/Param subsystem. All params are mapped to python under xenocide.param module, so param "audio_musicvolume" is found at python at "param.audio_musicvolume". As it's a python variable, it can be changed simply executing "param.audio_musicvolume = 0" (mutes the music) in the console.

The Settings/Param subsystem is modified so, that it allows adding human readable descriptions for every param. I'm thinking the following notation:
Settings::NewParam<float> audio_musicvolume("audio_musicvolume", 1.0, "Musicvolume. 0.0 is muted and 1.0 is highest level.");

User settings is stored into two files, which are placed under profiles/garo/ directory. These files are garo.cfg and garo.py. I'm using my profile name as an example here :)

garo.cfg
The garo.cfg is actually a python file which just has .cfg extension instead of normal .py. This file contains all parameters, and their values in python notation, which relates to the profile. Example:
# audio_musicvolume (float variable): Musicvolume. 0.0 is muted and 1.0 is highest level.
param.audio_musicvolume = 0.8

When user changes any setting via in-game menus, the garo.cfg is rewrited, so user can't edit this file directly, or his changes would be lost after he changes any setting via in-game menus. This is explained at the beginning of garo.cfg in comments.

It is easy to write the code which simply iterates over all params, writes the param comment among the param and its value itself into the garo.cfg file.

When a new user profile is first created, let's say a profile named "guyver", a directory called guyver is created under the profiles/ directory and guyver.cfg file is created into this directory with default values of the params.

garo.py
Member Quake and it's revolutionary console? It didn't support turing state scripting, but it had aliases and it could run commands which were implemented inside the Quake engine (change weaopn, or say something to the teammates, for example). Experienced users created often complex keyboard mappings. My own quake script contained over 300 lines, complex aliases to select best available weapon, or toggle different keyboard mapping modes, for example. As we use Python, experienced users will be able to create very complex scripts to modify the user interface, keyboard mappings etc to suite their needs and even implement new features.

The point is, that the experienced users can create the garo.py file, which is executed after garo.cfg. They can use all benefits of the Python scripting to do whatever they want, so that they can customize their profile as much as they ever need to. All these scripts and python code can be placed into this file (garo.py).

Params which can not be set via in-game menus
We will propably have dozens or even hundrets of params, which can't be set via in-game menus. I don't mean that this is a problem, because there will be many params which are simply not so important that we should add an option to change them via in-game menus. Other reason is, that if we would allow changing every possible parameter via in-game menus, there would simply be way to many options which user could change and the menus would be cluttered.

As every parameter is writed into garo.cfg with its default value and its help text, the experienced users can examine the garo.cfg file and re-set the parameter in garo.py file. So the garo.cfg servers also as a dictionary to all values which can be changed.

User keyboard mappings
Currently the keyboard mappings/shortcuts are stored inside a xml file. I'm thinkign of two choises:
1) This file should be moved into the user profile directory.

2) Rewrite the keyboard shortcuts so that a keyboard mapping is binded with a python function. Example:
keyboard.bind("F8", gamestate.quicksave)
keyboard.bind('F1',  console.toggleConsole)
These commands would be saved into garo.cfg among the param commands.

This gives us the ability to easily bind anything into a keyboard button (combination). When testing and developping, it is often easy to bind a python function into a button and then execute it without typing the command into the console. This also would allow experienced users to bind their custom python code into shortcuts.

And xml file would still be used to define what python functions can be binded via in-game menus and human readable explanations what they do. (Example: To bind quicksave into a button, the user would not see the raw function name "gamestate.quicksave' but an user friendly text "Quicksave")

About params which should not be changed
There will propably be need for params which can't be changed via python (they should be read-only) and also params which dont relate to user profile anyway. This proposition is not related directly to user profiles, but I explain it here anyway

We modify the Settings/Param subsystem further, so that it allows some attributes for every param. These would be: read only in python and no profile related. I was thinking of the following notation to express these attributes:
Settings::NewParam<float> current_gametime("current_gametime", 0, 'Current gametime in seconds since gamestart')
.Settings::readonly()
.Settings::noProfileRelated();
If the readonly attribute is supplied, trying to assign a value into the variable in python would throw an exception. If noProfileRelated is supplied, saving the settigns would not include the variable into the garo.cfg file.

Guyver has also some ideas which I'm hoping he will explain to us. Any thoughts about this? Member that this is only a proposition =)

edit: Fixed some fine details about keyboard mapping and bolding.

- Garo

Edited by Garo, 11 January 2006 - 04:24 AM.


#2 rincewind

rincewind

    Programming Department

  • Xenocide Programming Department
  • 541 posts

Posted 11 January 2006 - 11:55 AM

In general, I like it. Just some comments:

- guyver had some issues with using python scripts for the settings-storage as it might be hard to have hierarchical settings (even though I don't know if we can't get this to work in python as well... Maybe return a python-object per hierarchy-level, etc)

- we shouldn't write out the parameters that stay on their default value. That way we can update the system and changed default params for internal stuff (e.g. some tweaking for gameplay balance, etc) will still apply.

- about the readonly stuff. While I like the syntax you propose, I have no idea if this works as the construction takes place outside of a function-body. Effectivly we are creating global objects in an anonmous namespace. We need this to have the code executed before main(). I'm not sure if you can add function calls.
Another way would be to add a parameter to the constructor if NewParam.

Rincewind
Posted Image

I love boost!!! The next best thing since the invention of C++.

#3 guyver6

guyver6

    Captain

  • Xenocide Programming Department
  • 599 posts

Posted 13 January 2006 - 10:53 AM

Ok, so let's divide my post into 2 parts.

1. Params
I second what Garo said about extending Param class, and I agree with Rincewind about that the syntax proposed by Garo is nice. Thou it won't work (tested). My proposition is to use policy templates to define if Param is writtable or if it's saved in profile. Syntax will look like:
Settings::NewParam<float, Param::ReadOnly, Param::NonProfile> audio_musicvolume("audio_musicvolume", 1.0, "Musicvolume. 0.0 is muted and 1.0 is highest level.");
This is possible and I think it's how it should be done.

I also like the proposition about key mapping (so called, I suppose due to quake heavy influance, "binding";) ), this will surely be much cleaner to hand-edit, but... Why would one want to hand-edit key mapping file? This requires changing already working code and I'd rather like not to touch working code. Second: if some settings won't be available through in-game key mapping dialog, user can use that python syntax inside it's own user.py file to set arbitrary functions at key hits. This leads to...

2. Profiles
I agree on having optionally executed user.py file. That'll allow even us (developers) to customize some settings to ease our work. Generally good idea.

So what's wrong?

Well, .cfg files shouldn't be python files.
Rationale:
- .cfg files will hold settings values that can be needed a lot before any python script is executed (in example init.py will be executed on launch... but name "init.py" is a setting, that can be changed using .cfg)
- .cfg files will need to be executed when some other necessary python code isn't already executed
- user should be able to easily edit .cfg files and know that changes that she made to that file will be reflected in game (if user has an ability to put python code to .cfg then she'll surely do that instead of creating user.py... then she'll open up xenocide and how disappointed she'll be, when all changes made by her in .cfg file will be lost due to xenocide overwritting params settings); note, that if only settings will be changed in .cfg, that settings will be reflected in python, thus when quitting, those changed values will be rewritten to .cfg again - no one looses.

Another thing is that I don't like doubling things - two python files to execute... Why not one? My proposition is to use .cfg files format from Ogre::ConfigFile class (which btw is written and support more than one syntax, thus gives more freedom to user. It also supports multi-settings (so one setting name can have more than one value). Python is IMHO too much for simple configuration file (remember that you have access to "import" and system related modules... one malicious .cfg file could format user's HD)

Guyver
Posted Image
Sourceforge: guyver6
LinkedIn: Andrzej Haczewski
"A good business idea, they say, can be explained in one sentence. Similarly, each program entity should have one clear purpose."

Join #xenocide at irc.freenode.net.

#4 UnFleshed One

UnFleshed One

    Programming Department

  • Xenocide Inactive
  • 304 posts

Posted 13 January 2006 - 09:51 PM

About key mapping.

There are two levels of that. First (higher) is mapping a function to a combination. and second is mapping a keys or combinations for basic stuff, like Esc to close focused window.

Both of them will need some kind of a context. Now, what can serve as a context? Focused window?

I'm not sure how does CEGUI handles that, but at the worst case we can intercept key input in the frame listener and broadcast it to everything in hopes that it will somehow sort itself out.

Or we can bind a handler directly to the combination, so framelistener just fires the event of the combination. And then subscribers are responsible to sort it out among themselves. That is, a subscriber should know if it should process the event or not (window is out of focus, or something). To be on the safe side, event can be processed only once (handler returns true and all subsequent handlers are out of luck).

Now, there is a class Combination, and there should be a class Action (or something, I'm not too good at names :)).

So, everybody subscribes to Action, and it is mapped to Combination, which in turn gets recognized in framelistener and fires event on attached Action.

That's how I see it. I don't know much about python, so I can't evaluate Garo's approach :).

Now, I will probably start working on it (unless it will be vetoed or shifted out of my area of competence :)), but expect it to be extremely slow...

(What? Wrong topic? :))
Darkness is under the candle.

#5 Garo

Garo

    Programming Department

  • Xenocide Programming Department
  • 119 posts

Posted 14 January 2006 - 07:56 AM

Ok, so let's divide my post into 2 parts.

1. Params
I also like the proposition about key mapping (so called, I suppose due to quake heavy influance, "binding";) ), this will surely be much cleaner to hand-edit, but... Why would one want to hand-edit key mapping file?

When I still play natural-selection (a half-life mod), I have my settings stored in the garo.cfg. I never change anything via the ingame menusm but via console or the garo.cfg and then re-execute it via console.

The game menus might not support binding custom functions to keys, so the user must do it via console. And to make the bindings persistent, he needs to add the bindings into his user.py file (or the keymap file).

This requires changing already working code and I'd rather like not to touch working code. Second: if some settings won't be available through in-game key mapping dialog, user can use that python syntax inside it's own user.py file to set arbitrary functions at key hits.

I'm ok with that the key mappings are stored in a xml file, which is stored in the users profile directory, as long as the keys can be binded in the user.py

But I think that having two very different way to save/store bindings and configurations leads to complexity, which could be avoided.

2. Profiles
- .cfg files will hold settings values that can be needed a lot before any python script is executed (in example init.py will be executed on launch... but name "init.py" is a setting, that can be changed using .cfg)
- .cfg files will need to be executed when some other necessary python code isn't already executed

Are there any such situations? The python is currently initialized just after log4cxx.
Param bindings aren't yet there, but script system is initialized very soon after. There really aren't anything else than just few directory path parsing, before the full parameter python system is up and running. So I think that there would be very few or none parameters which would need to be loaded from a Ogre .cfg file due that the python isn't ready to do it.

- user should be able to easily edit .cfg files and know that changes that she made to that file will be reflected in game (if user has an ability to put python code to .cfg then she'll surely do that instead of creating user.py... then she'll open up xenocide and how disappointed she'll be, when all changes made by her in .cfg file will be lost due to xenocide overwritting params settings); note, that if only settings will be changed in .cfg, that settings will be reflected in python, thus when quitting, those changed values will be rewritten to .cfg again - no one looses.

Valid point. This could be avoided with warning comments in the python-executed user.cfg (my proposition).

Another thing is that I don't like doubling things - two python files to execute... Why not one? My proposition is to use .cfg files format from Ogre::ConfigFile class (which btw is written and support more than one syntax, thus gives more freedom to user. It also supports multi-settings (so one setting name can have more than one value). Python is IMHO too much for simple configuration file (remember that you have access to "import" and system related modules... one malicious .cfg file could format user's HD)

Using two different configuration syntaxes also doubles things. I'd like if we would have just one syntax to configure everything (python syntax, as it's already there)

The malicious python code problem is real. The user could still execute malicious python code even if it's not allowed in user.cfg file, so I don't see that is a sollution. We should look ways to sandbox python better, but preventing one .cfg file executing python code doesn't help a lot here.


EDIT: Here's an old quake cfg file: http://lysosomi.juho...t/tmp/noppa.cfg
Member that you could only create aliases, bind them to keys, change setting values and execute some internal quake functions, such as "stopsound" and echo.



- Garo

Edited by Garo, 14 January 2006 - 08:43 AM.


#6 guyver6

guyver6

    Captain

  • Xenocide Programming Department
  • 599 posts

Posted 14 January 2006 - 11:12 AM

- user should be able to easily edit .cfg files and know that changes that she made to that file will be reflected in game (if user has an ability to put python code to .cfg then she'll surely do that instead of creating user.py... then she'll open up xenocide and how disappointed she'll be, when all changes made by her in .cfg file will be lost due to xenocide overwritting params settings); note, that if only settings will be changed in .cfg, that settings will be reflected in python, thus when quitting, those changed values will be rewritten to .cfg again - no one looses.

Valid point. This could be avoided with warning comments in the python-executed user.cfg (my proposition).

Another thing is that I don't like doubling things - two python files to execute... Why not one? My proposition is to use .cfg files format from Ogre::ConfigFile class (which btw is written and support more than one syntax, thus gives more freedom to user. It also supports multi-settings (so one setting name can have more than one value). Python is IMHO too much for simple configuration file (remember that you have access to "import" and system related modules... one malicious .cfg file could format user's HD)

Using two different configuration syntaxes also doubles things. I'd like if we would have just one syntax to configure everything (python syntax, as it's already there)

The malicious python code problem is real. The user could still execute malicious python code even if it's not allowed in user.cfg file, so I don't see that is a sollution. We should look ways to sandbox python better, but preventing one .cfg file executing python code doesn't help a lot here.

Who ever reads comments in .cfg files? It's like reading manuals... no one reads manuals :P

Anyway... if config has to be in python then name it config.py, not .cfg
user.py and config.py, user.py being optional. Next: leave key mappings in XML and allow binding arbitrary python functions with python script (in example in user.py file)
Posted Image
Sourceforge: guyver6
LinkedIn: Andrzej Haczewski
"A good business idea, they say, can be explained in one sentence. Similarly, each program entity should have one clear purpose."

Join #xenocide at irc.freenode.net.

#7 guyver6

guyver6

    Captain

  • Xenocide Programming Department
  • 599 posts

Posted 14 January 2006 - 11:19 AM

About key mapping.

There are two levels of that. First (higher) is mapping a function to a combination. and second is mapping a keys or combinations for basic stuff, like Esc to close focused window.

Both of them will need some kind of a context. Now, what can serve as a context? Focused window?

I'm not sure how does CEGUI handles that, but at the worst case we can intercept key input in the frame listener and broadcast it to everything in hopes that it will somehow sort itself out.

Or we can bind a handler directly to the combination, so framelistener just fires the event of the combination. And then subscribers are responsible to sort it out among themselves. That is, a subscriber should know if it should process the event or not (window is out of focus, or something). To be on the safe side, event can be processed only once (handler returns true and all subsequent handlers are out of luck).

Now, there is a class Combination, and there should be a class Action (or something, I'm not too good at names :)).

So, everybody subscribes to Action, and it is mapped to Combination, which in turn gets recognized in framelistener and fires event on attached Action.

That's how I see it. I don't know much about python, so I can't evaluate Garo's approach :).

Now, I will probably start working on it (unless it will be vetoed or shifted out of my area of competence :)), but expect it to be extremely slow...

(What? Wrong topic? :))

<{POST_SNAPBACK}>

I understand the need of context-depend mapping of keys, but it'd be really hard to achive. So I don't like the idea too much. Closing focused window is really simple with cegui (which is binded to python, try in example type in console:
cegui.WindowManager.getSingleton().getWindow("Console").close()
or something like that). There are methods to get active window from window manager so cosing it isn't also that hard.

I think it's just necessary to bind keys to python code (so it's executed when key is pressed).
Posted Image
Sourceforge: guyver6
LinkedIn: Andrzej Haczewski
"A good business idea, they say, can be explained in one sentence. Similarly, each program entity should have one clear purpose."

Join #xenocide at irc.freenode.net.

#8 UnFleshed One

UnFleshed One

    Programming Department

  • Xenocide Inactive
  • 304 posts

Posted 14 January 2006 - 04:03 PM

I think it's just necessary to bind keys to python code (so it's executed when key is pressed).


Ok, so the action execute no matter what state the rest of the game is in?

Suppose I bind a code to bring up intercept window to Ctrl+I. Then I go to XNet and press Ctrl+I, what should happen?

I guess we need some kind of context system anyhow. At least we should distinguish between screens.

Every screen can have it's own set of mapped actions. (and one additional global set for all of 'em). So when a screen is up, mainframelistener uses it's set of actions (and global one) to find out if any combinations binded to this set was typed.

In global set can go such things as concole pop up, and so on (if concole should be mapped too, that is, it might do well being special case).
Darkness is under the candle.

#9 red knight

red knight

    Xenocide Project Leader

  • Xenocide Inactive
  • 3,310 posts

Posted 14 January 2006 - 06:20 PM

Guys I think that you are overcomplicating the design.

Store only as much information as you need, but never more. Do the simplier thing that can be accomplish without losing required functionality. Nothing more, nothing less.

AFAIS you need 3 very defined things.

One is a mapping between keys and actions (that is named in DirectX nomenclature "Action Mapping"). Then you need to bind actions with their respective executable code (that is out of the scope for this), cause whether you give posibility to bind the action from the console or the C++ is completly irrelevant for what you are working in.

Second, you need to specify parameters that are going to be accessed from whereever (just for simplicity they will get hardcoded but it is irrelevant where they are stored it is just a binding) and their specific attributes (lets say for now, only if they are readonly -after you set it for the first time of course- or writables) do not go for adding extra metadata that we do not need right now.

Third, you need to define how those things are going to be setted or binded (I would leave that out of scope until you finish the other 2), but my take is to make it simple, hardcode the default value on creation for a start and then move it to an script, if and only if it is required to have it accesible from the outside.

Lets try to solve 1 and 2, then move to 3 after those are ready, tested and consolidated. What do you think?

Greetings
Red Knight
Sourceforge Nick: flois - Federico Andres Lois
Visit my blog at: flois.blogspot.com

Posted Image

Pookie cover me, I am going in.

#10 UnFleshed One

UnFleshed One

    Programming Department

  • Xenocide Inactive
  • 304 posts

Posted 17 January 2006 - 08:40 PM

Ok, I'll start recognising combinations sometime soon and somebody will bind them to python or whatever :)
Darkness is under the candle.