Home » MODDING HQ 1.13 » v1.13 Idea Incubation Lab » Hotkey Externalization
Hotkey Externalization[message #206363]
|
Thu, 15 January 2009 02:47
|
|
zilpin |
|
Messages:63
Registered:October 2007 |
|
|
It's been bothering me for nearly a decade that there is no configuration file for hotkeys.
This irks me in any game or application, and since can not progress farther in my Enemy Surrender mod without help, decided to start making externalized hotkeys tonight.
Already finished with working alpha, which has most of the tactical mode hotkeys in a settings structure. The settings structure defaults with hard-coded keys as they exist in the game now. Have not done the settings file parsing yet. Tested it, and there is no impact on performance.
Since this is a setting that is more likely to be modified by players, I figured that it should be an INI file in the game root folder. Something like Hotkeys.ini so it's very accessible and easy to edit.
Does anyone think this should be an XML file? Seams like overkill to me, but now's the time to ask.
Does anyone think this should be buried in the data folders?
My goals:
1) All game hotkeys can be configured by the player.
2) The hotkeys settings file is independent of other settings files, for flexibility.
3) The settings file is easy to find and easy to edit for non-technical users.
4) The game does not need the hotkeys settings file to run. Default values are hard-coded.
5) Each command can exist multiple times, permitting multiple keys to map to the same command.
6) Each key can be used multiple times, so that a single keystroke can perform multiple commands in the order defined in the settings file.
7) Fix any existing bugs directly related to input.
Anyone who want to take a peek at what's done so far can look at the code in SVN.
https://81.169.133.124/source/ja2/branches/zilpin/HotkeyExternalization
Report message to a moderator
|
Corporal
|
|
|
|
|
Re: Hotkey Externalization[message #206382]
|
Thu, 15 January 2009 10:47
|
|
cougar |
|
Messages:254
Registered:March 2000 |
|
|
zilpinDoes anyone think this should be an XML file? Seams like overkill to me, but now's the time to ask.
Does anyone think this should be buried in the data folders?
Is there any kind of advantage to be gained by this? If not go for the hotkeys.ini in the root folder but even if we (or you) gain a slight advantage by using an .xml go for it, I believe everyone should be able to edit an .xml.
Great job!
Report message to a moderator
|
Master Sergeant
|
|
|
|
Re: Hotkey Externalization[message #206418]
|
Thu, 15 January 2009 19:08
|
|
zilpin |
|
Messages:63
Registered:October 2007 |
|
|
Quote:"please list all new hotkeys in JA2's own in-game help section (the one that pops upwhen you hit "H")
I didn't see your post, but it's funny, because I've thought for a while that the help should show you the active hotkeys. I haven't seen a complete hotkey reference for 1.13 since it's first release, but once this is finished, all you have to do is open the INI.
I may end up including that feature, since it wouldn't be too difficult to print the settings structure in a window.
As it stands now (in trunk code), that would be difficult and prone to becoming incorrect, since the hotkeys are all hard-coded, and the help window would then be hard-coded as well.
Wherever you posted that, tell them not to bother and link this thread.
It's easier to merge code when changes are not made to the same places in code.
Report message to a moderator
|
Corporal
|
|
|
|
Re: Hotkey Externalization[message #206440]
|
Thu, 15 January 2009 23:46
|
|
zilpin |
|
Messages:63
Registered:October 2007 |
|
|
Here's what I have so far.
Over 160 individual hotkeys defined in the game.
I had no clue there were so many hotkey events available.
This list does include all testing and debugging key events, though it does not have all the mappings for all the meanwhile test keys, and body shape change hotkeys.
A few things, I don't quite know what the key does.
HOTKEY_STRUCT gHotkeySettings[HOTKEY_SETTINGS_MAX]=
{
{HOTKEY_PAUSE, 0, PAUSE, 0 },
{HOTKEY_SHOW_HELP, 0, 'h', 0 },
{HOTKEY_SHOW_HELP, 0, 'H', 0 },
{HOTKEY_SHOW_INFO, 0, 'v', 0 },
{HOTKEY_END_TURN, 0, 'd', 0 },
{HOTKEY_QUICK_SAVE, 0, 's', ALT_DOWN },
{HOTKEY_SELECT_MERC, 0, F1, 0 },
{HOTKEY_SELECT_MERC, 1, F2, 0 },
{HOTKEY_SELECT_MERC, 2, F3, 0 },
{HOTKEY_SELECT_MERC, 3, F4, 0 },
{HOTKEY_SELECT_MERC, 4, F5, 0 },
{HOTKEY_SELECT_MERC, 5, F6, 0 },
{HOTKEY_SELECT_MERC, 0, F1, 0 },
{HOTKEY_SELECT_MERC_PREV, 0, 0, 0 },
{HOTKEY_SELECT_MERC_NEXT, 0, SPACE, 0 },
{HOTKEY_LOCATE_MERC, 1, F2, SHIFT_DOWN },
{HOTKEY_LOCATE_MERC, 2, F3, SHIFT_DOWN },
{HOTKEY_LOCATE_MERC, 3, F4, SHIFT_DOWN },
{HOTKEY_LOCATE_MERC, 4, F5, SHIFT_DOWN },
{HOTKEY_LOCATE_MERC, 5, F6, SHIFT_DOWN },
{HOTKEY_LOCATE_MERC_PREV, 0, 0, 0 },
{HOTKEY_LOCATE_MERC_NEXT, 0, SPACE, SHIFT_DOWN },
{HOTKEY_SELECT_SQUAD, 0, '1', 0 },
{HOTKEY_SELECT_SQUAD, 1, '2', 0 },
{HOTKEY_SELECT_SQUAD, 2, '3', 0 },
{HOTKEY_SELECT_SQUAD, 3, '4', 0 },
{HOTKEY_SELECT_SQUAD, 4, '5', 0 },
{HOTKEY_SELECT_SQUAD, 5, '6', 0 },
{HOTKEY_SELECT_SQUAD, 6, '7', 0 },
{HOTKEY_SELECT_SQUAD, 7, '8', 0 },
{HOTKEY_SELECT_SQUAD, 8, '9', 0 },
{HOTKEY_SELECT_SQUAD, 9, '0', 0 },
{HOTKEY_SELECT_SQUAD_PREV, 0, 0, 0 },
{HOTKEY_SELECT_SQUAD_NEXT, 0, 0, 0 },
{HOTKEY_SELECT_ALL, 0, '=', 0 },
{HOTKEY_SELECT_RESORT_BY_ID,0, 'T', 0 },
{HOTKEY_SCREEN_CENTER_ON_MERC, 0, '/', 0 },
{HOTKEY_SCREEN_SHOW_MERC_KEYS, 0, 'k', 0 },
{HOTKEY_SCREEN_SHOW_MAP, 0, 'm', 0 },
{HOTKEY_SCREEN_SHOW_OPTIONS, 0, 'o', 0 },
{HOTKEY_SCREEN_SHOW_SECTOR_MAP, 0, INSERT, 0 },
{HOTKEY_SCREEN_FLAG_SCROLL_FAST, 0, 0, SHIFT_DOWN },
{HOTKEY_SCREEN_CENTER_NEXT_ENEMY_SEEN, 0, 'e', 0 },
{HOTKEY_SCREEN_CENTER_NEXT_ENEMY, 0, ENTER, 0 },
{HOTKEY_SCREEN_NEXT_THING_IN_STACK, 0, 'n', 0 },
{HOTKEY_SCREEN_CLEAR_TACTICAL_MSG, 0, F12, 0},
{HOTKEY_SCREEN_CLEAR_TACTICAL_MSG_QUEUE,0, F12, CTRL_DOWN },
{HOTKEY_SCREEN_TOGGLE_TACTICAL_PANEL, 0, '`', 0},
{HOTKEY_SCREEN_SHOW_LOAD_SAVE, 0, 's', CTRL_DOWN },
{HOTKEY_MERC_REMOVE_FROM_SQUAD, 0, 's', CTRL_DOWN | ALT_DOWN },
{HOTKEY_MERC_LOOK_AND_AIM, 0, 'l', 0 },
{HOTKEY_MERC_STANCE_CROUCH, 0, 'c', 0 },
{HOTKEY_MERC_STANCE_PRONE, 0, 'p', 0 },
{HOTKEY_MERC_STANCE_RUN, 0, 'r', 0 },
{HOTKEY_MERC_STANCE_STAND, 0, 's', 0 },
{HOTKEY_MERC_STANCE_UP, 0, PGUP, 0 },
{HOTKEY_MERC_STANCE_DOWN, 0, PGDN, 0 },
{HOTKEY_MERC_JUMP, 0, 'j', 0 },
{HOTKEY_MERC_SWAP_LOCATION, 0, 'x', 0 },
{HOTKEY_MERC_STEALTH, 0, 'z', 0 },
{HOTKEY_MERC_STEALTH_ALL, 0, 'z', ALT_DOWN },
{HOTKEY_MERC_FLAG_SIDESTEP, 0, 0, ALT_DOWN },
{HOTKEY_MERC_TOGGLE_BURST, 0, 'b', 0 },
{HOTKEY_MERC_CANCEL_ACTION, 0, ESC, 0 },
{HOTKEY_MERC_AUTOBANDAGE_ALL, 0, 'a', 0 },
{HOTKEY_MERC_DROP_PACK_ALL, 0, 'B', 0},
{HOTKEY_MERC_DROP_ALL_ITEMS, 0, 'E', 0},
{HOTKEY_MERC_SHOW_RANGE_TO_TARGET, 0, 'f', 0},
{HOTKEY_MERC_SWAP_GUNSLING, 0, 'K', 0},
{HOTKEY_MERC_SWAP_GOGGLES_ALL, 0, 'N', 0},
{HOTKEY_SECTOR_STACK_AND_SORT, 0, 'M', CTRL_DOWN },
{HOTKEY_SECTOR_STACK_AND_SORT, 0, 'S', 0 },
{HOTKEY_SECTOR_AMMO_MERGE, 0, 'A', 0},
{HOTKEY_SECTOR__SOMETHING_TO_DO_WITH_DROPPACKS_AND_SEPARATING_ITEMS, 0, 'F', CTRL_DOWN },
{HOTKEY_SECTOR_MOVE_ALL_ITEMS_TO_MERC, 0, 'M', 0 },
{HOTKEY_SECTOR_RELOAD_ALL_WEAPONS, 0, 'R', 0 },
{HOTKEY_RENDER_FLAG_SHOW_PATH, 0, 0, SHIFT_DOWN },
{HOTKEY_RENDER_FLAG_HAND_CURSOR, 0, 0, CTRL_DOWN },
{HOTKEY_RENDER_FLAG_COVER, 0, DELETE, 0 },
{HOTKEY_RENDER_FLAG_LOS, 0, END, 0 },
{HOTKEY_RENDER_FLAG_GUN_RANGE, 0, 'f', 0 },
{HOTKEY_RENDER_OVERLAY_RANGE_UP, 0, '+', 0 },
{HOTKEY_RENDER_OVERLAY_RANGE_DOWN, 0, '-', 0 },
{HOTKEY_RENDER_TOGGLE_GLOWING_ITEMS_COLOUR, 0, '*', 0 },
{HOTKEY_RENDER_TOGGLE_CURSOR_LEVEL, 0, TAB, 0 },
{HOTKEY_TOPTION_MERC_ALWAYS_LIGHT_UP, 0, 'g', 0 },
{HOTKEY_TOPTION_DROP_ALL, 0, 'D', 0 },
{HOTKEY_TOPTION_GL_HIGH_ANGLE, 0, 'q', 0 },
{HOTKEY_TOPTION_GL_BURST_CURSOR, 0, 'G', 0 },
{HOTKEY_TOPTION_3D_CURSOR, 0, HOME, 0 },
{HOTKEY_TOPTION_GLOW_ITEMS, 0, 'i', 0 },
{HOTKEY_TOPTION_TRACKING_MODE, 0, 'f', ALT_DOWN },
{HOTKEY_TOPTION_TOGGLE_WIREFRAME, 0, 'w', 0 },
{HOTKEY_TOPTION_TOGGLE_TREE_TOPS, 0, 't', 0 },
{HOTKEY_MAP_MERC_CONTRACT, 0, 'c', 0 },
{HOTKEY_MAP_SHOW_MILITIA, 0, 'z', 0 },
{HOTKEY_MAP_TOGGLE_PANEL, 0, ',', 0 },
{HOTKEY_MAP_TOGGLE_INVENTORY, 0, ENTER, 0 },
{HOTKEY_MAP_FLAG_GRAB_WHOLE_STACK, 0, 0, SHIFT_DOWN },
{HOTKEY_MAP_TOGGLE_PAUSE, 0, SPACE, 0 },
{HOTKEY_MAP_LEVEL_UP, 0, INSERT, 0 },
{HOTKEY_MAP_LEVEL_DOWN, 0, DELETE, 0 },
{HOTKEY_MAP_MSG_OLDEST, 0, HOME, 0 },
{HOTKEY_MAP_MSG_NEWEST, 0, END, 0 },
{HOTKEY_MAP_MSG_PAGE_UP, 0, PGUP, 0 },
{HOTKEY_MAP_MSG_PAGE_DOWN, 0, PGDN, 0 },
{HOTKEY_MAP_MSG_LINE_UP, 0, UPARROW, 0 },
{HOTKEY_MAP_MSG_LINE_DOWN, 0, DNARROW, 0 },
{HOTKEY_MAP_SORT_MERC_LIST, 0, F1, 0 },
{HOTKEY_MAP_SORT_MERC_LIST, 1, F2, 0 },
{HOTKEY_MAP_SORT_MERC_LIST, 2, F3, 0 },
{HOTKEY_MAP_SORT_MERC_LIST, 3, F4, 0 },
{HOTKEY_MAP_SORT_MERC_LIST, 4, F5, 0 },
{HOTKEY_MAP_SORT_MERC_LIST, 5, F6, 0 },
{HOTKEY_MAP_SELECT_MERC_NEXT, 0, 0, 0 },
{HOTKEY_MAP_SELECT_MERC_PREV, 0, 0, 0 },
{HOTKEY_DEBUG_TOGGLE_VIDEO_SCROLL, 0, 'v', ALT_DOWN },
{HOTKEY_DEBUG_VIDEO_DUMP, 0, 'v', CTRL_DOWN },
{HOTKEY_DEBUG_WARP_GAME_TIME, 0, '=', ALT_DOWN },
{HOTKEY_DEBUG_TOGGLE_REAL_TIME_CONFIRM, 0, 'p', ALT_DOWN },
{HOTKEY_DEBUG_DUMP_SECTOR_INFO, 0, 'p', CTRL_DOWN },
{HOTKEY_DEBUG_TOGGLE_STRATEGIC_AI_LOG, 0, 'L', 0 },
{HOTKEY_DEBUG_TOGGLE_MAP_EDGEPOINTS, 0, 'e', CTRL_DOWN },
{HOTKEY_DEBUG_ADVANCE_DAY, 0, 'd', CTRL_DOWN },
{HOTKEY_DEBUG_TEST_NEXT_AMBIENT_SOUND, 0, 'a', ALT_DOWN },
{HOTKEY_DEBUG_TEST_TONY, 0, F12, ALT_DOWN },
{HOTKEY_DEBUG_TOGGLE_NPC_RECORD_DISPLAY,0, 'j', CTRL_DOWN },
{HOTKEY_CHEAT_START_RAIN, 0, '`', ALT_DOWN },
{HOTKEY_CHEAT_END_RAIN, 0, '`', CTRL_DOWN },
{HOTKEY_CHEAT_TOGGLE_Z_BUFFER 0, 'z', CTRL_DOWN },
{HOTKEY_CHEAT_CREATE_IRA_WITH_GUN, 0, 'y', ALT_DOWN },
{HOTKEY_CHEAT_DEBUG_LOS, 0, 'y', 0 },
{HOTKEY_CHEAT_CREATE_FLAMER, 0, 'w', CTRL_DOWN },
{HOTKEY_CHEAT_CYCLE_ITEM, 0, 'w', ALT_DOWN },
{HOTKEY_CHEAT_REFRESH_SOLDIER, 0, 'u', ALT_DOWN },
{HOTKEY_CHEAT_PUMPUP_SOLDIER, 0, 'u', CTRL_DOWN },
{HOTKEY_CHEAT_TEST_CAPTURE, 0, 't', CTRL_DOWN },
{HOTKEY_CHEAT_TELEPORT, 0, 't', ALT_DOWN },
{HOTKEY_CHEAT_RELOAD_WEAPON, 0, 'r', ALT_DOWN },
{HOTKEY_CHEAT_CREATE_PLAYER_MONSTER, 0, 'o', CTRL_DOWN },
{HOTKEY_CHEAT_KILL_ALL_ENEMIES, 0, 'o', ALT_DOWN },
{HOTKEY_CHEAT_TEST_NPC_QUOTE, 0, 'n', ALT_DOWN },
{HOTKEY_CHEAT_FLOOR_LEVEL_UP, 0, PGUP, CTRL_DOWN },
{HOTKEY_CHEAT_FLOOR_LEVEL_DOWN, 0, PGDN, CTRL_DOWN },
{HOTKEY_CHEAT_COUNT_LEVEL_NODES, 0, 'm', ALT_DOWN },
{HOTKEY_CHEAT_SHOW_MEMORY_USED, 0, 'm', CTRL_DOWN },
{HOTKEY_CHEAT_NEXT_SHOT_KILLS, 0, 'k', CTRL_DOWN | ALT_DOWN },
{HOTKEY_CHEAT_CREATE_RANDOM_ITEM, 0, 'i', ALT_DOWN },
{HOTKEY_CHEAT_MODE_ON_I_THINK_____________,0,'i', CTRL_DOWN },
{HOTKEY_CHEAT_CREATE_LOTSA_ITEMS, 0, 'i', CTRL_DOWN | ALT_DOWN },
{HOTKEY_CHEAT_TOGGLE_REPORT_HIT_CHANCES,0, 'h', ALT_DOWN },
{HOTKEY_CHEAT_TEST_HIT, 0, 'h', CTRL_DOWN },
{HOTKEY_CHEAT_TEST_NEW_MERC_EVENT, 0, 'g', ALT_DOWN },
{HOTKEY_CHEAT_MODE_INCREASE_I_THINK_____,0,'g', CTRL_DOWN },
{HOTKEY_CHEAT_DISPLAY_FPS, 0, 'f', CTRL_DOWN },
{HOTKEY_CHEAT_TOGGLE_VIEW_ALL, 0, 'e', ALT_DOWN },
{HOTKEY_CHEAT_CREATE_NEXT_CIV_TYPE, 0, 'c', ALT_DOWN },
{HOTKEY_CHEAT_TOGGLE_CLIFF_DEBUG, 0, 'c', CTRL_DOWN },
{HOTKEY_CHEAT_TEST_BAD_MERC_EVENT, 0, 'g', ALT_DOWN },
{HOTKEY_CHEAT_NEXT_SHOT_JAMS, 0, 'j', ALT_DOWN },
//{HOTKEY_CHEAT_CHANGE_BODY_TYPE, YAM_MONSTER, '5', ALT_DOWN },
//{HOTKEY_CHEAT_TEST_MEANWHILE, 1, F1, CTRL_DOWN | ALT_DOWN | SHIFT_DOWN },
{HOTKEY_MUSIC_VOLUME_UP, 0, '+', ALT_DOWN },
{HOTKEY_MUSIC_VOLUME_DOWN, 0, '-', ALT_DOWN },
{HOTKEY_REALTIME_SPEED_UP, 0, '+', CTRL_DOWN },
{HOTKEY_REALTIME_SPEED_DOWN, 0, '-', CTRL_DOWN },
{HOTKEY_SCROLL_SPEED_UP, 0, '+', ALT_DOWN | CTRL_DOWN },
{HOTKEY_SCROLL_SPEED_DOWN, 0, '-', ALT_DOWN | CTRL_DOWN },
{HOTKEY__UNKNOWN_CHANGE_TO_ACTION_______________________________________,0,'u',0},
//Last index must allways be NONE.
{HOTKEY_NONE,},
};
Report message to a moderator
|
Corporal
|
|
|
|
Re: Hotkey Externalization[message #206721]
|
Tue, 20 January 2009 02:16
|
|
zilpin |
|
Messages:63
Registered:October 2007 |
|
|
They already are split up, by name.
I could put different keys in different load files, based on build type, for the sake of clarity, but there wouldn't be much gained.
The default hotkeys will remain in one big array, since that's essentially how the code handles right now. Editor hotkeys only get processed if the exe is built as editor, debug hotkeys only get processed if debug flags are set at build time, etc.
Perhaps having a JA2_Hotkey file and Editor_Hotkey file would be useful.
Using INI files may not have been the right choice. The windows API provides no way of sequentially reading key-value pairs in the ini file. You have to load an entire section into a buffer (max size limitation 32k bytes, for some reason), which then must be parsed by other proprietary Windows API calls. In addition, duplicates are not permitted, which breaks one of my design goals. I could write a hack to support duplicates, but why?
It will actually take more effort to implement INI than XML, and INI will have less flexibility.
I'm switching to XML file, but I'll make the parser tolerant so that people can still edit it easily. Ignore whitespace, ignore unknown elements/nodes, ignore case, etc.
Report message to a moderator
|
Corporal
|
|
|
|
|
|
|
Re: Hotkey Externalization[message #213683]
|
Mon, 20 April 2009 19:24
|
|
zilpin |
|
Messages:63
Registered:October 2007 |
|
|
Progress stalled as new hotkeys were being added to the game, no documentation on hotkeys (which I could find) was accurate or complete, and the keyboard handling implemented is "broken".
"Broken" because the key codes you get from the key handler do not reflect the truth, but are handled hackishly in JA2 to make it work. The input handler provides keycodes which are a mix of keyboard scancodes and translated scancodes (for i18n/l10n purposes). Shift keys further complicate matters, and is not working properly in trunk code (as stated in previous post). Makes a mess. Like everything else in JA2. But I can see why it was done during time of implementation.
Really, so many problems (beyond this) could be solved by migrating JA2 to use SDL, but that's a non-trivial undertaking, so I don't think 1.13 core team is interested in doing so. Justifiably so.
Now that the trunk addition of HAM (which I love) has stabilized a little, I'd like to revisit this. Hotkey externalization is on the top of my personal wishlist. But I think I'm going to have to address the input library first, and I've not yet spent time to figure out where it came from.
Report message to a moderator
|
Corporal
|
|
|
|
|
Re: Hotkey Externalization[message #213883]
|
Wed, 22 April 2009 20:39
|
|
zilpin |
|
Messages:63
Registered:October 2007 |
|
|
I included every hotkey I found in the source when I began externalizing.
Anything added since will not be there.
Yes, every hotkey that already existed will be supported on release.
I added a hard-coded CTRL-ALT-SHIFT-ESCAPE hotkey, which will toggle printing key information to the screen as you press them.
I also added a CTRL-ALT-SHIFT-K to print all hotkeys to an XML file. It is not hard-coded.
Also hard-coded defaults in settings.cpp so that it can run without the XML file. That will hopefully make deployment smooth for all those players out there who don't want to use the new externalized hotkey file.
Finished debugging. Now have a fully functional mod for tactical mode only. The hotkeys need to be reviewed for correctness and completeness, and the code still requires some clean up. There is a methodology to how the hotkey actions are named, but that should go through community review before any release.
Here is the current incarnation of the Ja2_Hotkeys.XML file:
The parser is very tolerant. Case insensitive, ignores whitespace, lets you have unrecognized nodes and tags, redundant attributes, etc. If you completely screw with the structure, it will throw an error, but if you just enter an invalid hotkey, it will skip it and keep going.
Actions can be defined multiple times, and keys can be defined multiple times. If you define a key multiple times, the associated actions will occur in the order they exist in the file. If you define an action more than once, it will be triggered by multiple keys. An action can be defined multiple times for a single hotkey, causing it to repeat repeat.
Action names are defined by hotkey_enum within GameSettings.h
Key names follow the Win32 API names for Virtual Keys. For example, VK_PAUSE is PAUSE, VK_A is A, VK_HOME is HOME, etc. There are also other aliases defined for convenience, which can be seen in KeynameToVK() within input.cpp
Right now the only shift keys are CTRL, ALT, SHIFT. Hotkeys don't care whether NumLock, CapsLock, or ScrollLock are currently toggled.
Does anyone think it would be beneficial to enable hotkeys to be defined based on CapsLock/NumLock/ScrollLock status?
Report message to a moderator
|
Corporal
|
|
|
|
Re: Hotkey Externalization[message #214079]
|
Fri, 24 April 2009 17:20
|
|
zilpin |
|
Messages:63
Registered:October 2007 |
|
|
Only tactical keys are implemented right now.
(edit)
Temporarily suspended to focus on updating other project slated to be included in trunk.
Work to continue in next couple weeks.
[Updated on: Tue, 09 June 2009 23:04] by Moderator Report message to a moderator
|
Corporal
|
|
|
|
Goto Forum:
Current Time: Fri Mar 29 07:39:03 GMT+2 2024
Total time taken to generate the page: 0.02772 seconds
|