Compare commits

...

5 commits

41 changed files with 1303 additions and 516 deletions

View file

@ -9,8 +9,8 @@
Determines the alpha test function used when rendering this surface. Determines the alpha test function used when rendering this surface.
Valid values are **GT0**, **LT128**, and **GE128**. These correspond to Valid values are **GT0**, **LT128**, and **GE128**. These correspond to
**"GREATER THAN 0"**, **"LESS THAN 128"**, and **"GREATER THAN OR EQUAL **GREATER THAN 0**, **LESS THAN 128**, and **GREATER THAN OR EQUAL
TO 128"**. TO 128**.
This function is used when determining if a pixel should be written to This function is used when determining if a pixel should be written to
the frame-buffer. For example, if **GT0** is specified, the only the the frame-buffer. For example, if **GT0** is specified, the only the
@ -18,13 +18,12 @@ portions of the texture map with corresponding alpha values greater than
zero will be written to the framebuffer. **By default alpha testing is zero will be written to the framebuffer. **By default alpha testing is
disabled.** disabled.**
Both alpha testing and normal [alpha Both alpha testing and normal [alpha blending](blendfunc.md) can be used to get
blending](blendFunc) can be used to get
textures that have see-through parts. The difference is that textures that have see-through parts. The difference is that
**alphaFunc** is an all-or-nothing test, while blending smoothly blends **alphaFunc** is an all-or-nothing test, while blending smoothly blends
between opaque and translucent at pixel edges. between opaque and translucent at pixel edges.
Alpha test can also be used with Alpha test can also be used with
[depthWrite](depthWrite), allowing other [depthWrite](depthwrite.md), allowing other
effects to be conditionally layered on top of just the opaque pixels by effects to be conditionally layered on top of just the opaque pixels by
setting [depthFunc](depthFunc) to equal. setting [depthFunc](depthfunc.md) to equal.

View file

@ -6,8 +6,7 @@
### Overview ### Overview
The alpha channel can be specified like the [rgb The alpha channel can be specified like the [rgb channels](rgbgen.md). If not specified, it
channels](rgbGen). If not specified, it
defaults to 1.0. defaults to 1.0.
### Functions {#functions} ### Functions {#functions}
@ -15,7 +14,7 @@ defaults to 1.0.
#### portal {#portal} #### portal {#portal}
This rendering stage keyword is used in conjunction with the This rendering stage keyword is used in conjunction with the
[surfaceparm](surfaceparm) keyword [surfaceparm](surfaceparm.md) keyword
portal. The function accomplishes the "fade" that causes the scene in portal. The function accomplishes the "fade" that causes the scene in
the portal to fade from view. Specifically, it means "Generate alpha the portal to fade from view. Specifically, it means "Generate alpha
values based on the distance from the viewer to the portal." values based on the distance from the viewer to the portal."

View file

@ -23,10 +23,10 @@ It accepts the standard **wave** functions of the type: **sin**,
**triangle**, **square**, **sawtooth** or **inversesawtooth**. **triangle**, **square**, **sawtooth** or **inversesawtooth**.
The "div" parameter is used to control the wave "spread" - a value equal The "div" parameter is used to control the wave "spread" - a value equal
to the [tessSize](vmap_tessSize) of the to the [tessSize](vmap_tesssize.md) of the
surface is a good default value. surface is a good default value.
#### normal <siv> <func> <base> &lt;amplitude ~0.1-~0.5&gt; &lt;frequency ~1.0-~4.0&gt; {#normal_amplitude_0.1_0.5_frequency_1.0_4.0} #### normal <siv> <func> <base> <amplitude> <frequency> {#normal_amplitude}
This deformation affects the normals of a vertex without actually moving This deformation affects the normals of a vertex without actually moving
it, which will effect later material options like lighting and it, which will effect later material options like lighting and
@ -43,14 +43,14 @@ rain, flags.
This forces a bulge to move along the given s and t directions. Designed This forces a bulge to move along the given s and t directions. Designed
for use on curved pipes. for use on curved pipes.
#### move<x> <y> <z> <func> <base> <amplitude> <phase> <freq> {#move} #### move <x> <y> <z> <func> <base> <amplitude> <phase> <freq> {#move}
This keyword is used to make a brush, curve patch or model appear to This keyword is used to make a brush, curve patch or model appear to
move together as a unit. The **<x>** **<y>** and **<z>** values are the move together as a unit. The **x** **y** and **z** values are the
distance and direction in game units the object appears to move relative distance and direction in game units the object appears to move relative
to it's point of origin in the map. to it's point of origin in the map.
The **<func> <base> <amplitude> <phase>** and **<freq>** values are the The **func base amplitude phase** and **freq** values are the
same as found in other wave form manipulations. same as found in other wave form manipulations.
The product of the function modifies the values x, y, and z.Therefore, The product of the function modifies the values x, y, and z.Therefore,
@ -101,9 +101,9 @@ smaller wave forms occurring in a given area. Larger values create a
lesser density of waves, or otherwise put, the appearance of larger lesser density of waves, or otherwise put, the appearance of larger
waves. To look correct this value should closely correspond to the value waves. To look correct this value should closely correspond to the value
(in pixels) set for (in pixels) set for
[tessSize](vmap_tessSize) of the texture. [tessSize](vmap_tesssize.md) of the texture.
A value of 100.0 is a good default value (which means your A value of 100.0 is a good default value (which means your
[tessSize](vmap_tessSize) should be close [tessSize](vmap_tesssize.md) should be close
to that for things tolook "wavelike"). to that for things tolook "wavelike").
#### <func> {#section_1} #### <func> {#section_1}

View file

@ -7,7 +7,7 @@
### Overview ### Overview
By default, writes to the depth buffer when By default, writes to the depth buffer when
[depthFunc](depthFunc) passes will happen [depthFunc](depthfunc.md) passes will happen
for opaque surfaces and not for translucent surfaces. for opaque surfaces and not for translucent surfaces.
Blended surfaces can have the depth writes forced with this function. Blended surfaces can have the depth writes forced with this function.

View file

@ -7,15 +7,15 @@
### Overview ### Overview
**Note**: you must also specify "surfaceparm fog" to cause **Note**: you must also specify "surfaceparm fog" to cause
[vmap](vmap) to identify the surfaces inside the volume. [vmap](vmap.md) to identify the surfaces inside the volume.
Fogparms only describes how to render the fog on the surfaces. Fogparms only describes how to render the fog on the surfaces.
**<red value> <green value> <blue value>** These are normalized values. **red value, green value, blue value**: These are normalized values.
To obtain the values that define fog color divide the desired color's To obtain the values that define fog color divide the desired color's
Red, Green and Blue values by 255 to obtain three normalized numbers Red, Green and Blue values by 255 to obtain three normalized numbers
within the 0.0 to 1.0 range. within the 0.0 to 1.0 range.
**<distance toopaque>** This is the distance, in game units, until the **distance to opaque**: This is the distance, in game units, until the
fog becomes totally opaque, as measured from the point of view of the fog becomes totally opaque, as measured from the point of view of the
observer. By making the height of the fog brush shorter than the observer. By making the height of the fog brush shorter than the
distance to opaque, the apparent density of the fog can be reduced distance to opaque, the apparent density of the fog can be reduced

View file

@ -7,7 +7,6 @@
### Overview ### Overview
Similar to [vmap_surfaceModel (Material Similar to [vmap_surfaceModel (vmap_surfacemodel.md), however
Command)](vmap_surfaceModel), however
it'll place models at runtime. The density can be controlled via the it'll place models at runtime. The density can be controlled via the
cvar `r_clutter_density`. cvar `r_clutter_density`.

View file

@ -9,17 +9,17 @@
The texture is essentially a fullbright overlay on top of the The texture is essentially a fullbright overlay on top of the
diffuse/albedomap. diffuse/albedomap.
Not all [Shaders](Shaders) support them. In some, like the Not all [Shaders](Shaders.md) support them. In some, like the
[unlit](unlit_(Shader)) shader, the [unlit](unlit.md) shader, the
[diffusemap](diffusemap) is always [diffusemap](diffusemap.md) is always
fullbright. fullbright.
### See also {#see_also} ### See also {#see_also}
- [diffusemap](diffusemap) - [diffusemap](diffusemap.md)
- [normalmap](normalmap) - [normalmap](normalmap.md)
- [specularmap](specularmap) - [specularmap](specularmap.md)
- [uppermap](uppermap) - [uppermap](uppermap.md)
- [lowermap](lowermap) - [lowermap](lowermap.md)
- [reflectmask](reflectmask) - [reflectmask](reflectmask.md)
- [reflectcube](reflectcube) - [reflectcube](reflectcube.md)

View file

@ -6,7 +6,7 @@
### Overview ### Overview
This implies [noPicMip](noPicMip), but This implies [noPicMip](nopicmip.md), but
also prevents the generation of any lower resolution mipmaps for use by also prevents the generation of any lower resolution mipmaps for use by
the 3d card. This will cause the texture to alias when it gets smaller, the 3d card. This will cause the texture to alias when it gets smaller,
but there are some cases where you would rather have this than a blurry but there are some cases where you would rather have this than a blurry

View file

@ -18,10 +18,10 @@ Check out our [Normal mapping guide](Normal_mapping_guide).
### See also {#see_also} ### See also {#see_also}
- [diffusemap](diffusemap) - [diffusemap](diffusemap.md)
- [specularmap](specularmap) - [specularmap](specularmap.md)
- [fullbrightmap](fullbrightmap) - [fullbrightmap](fullbrightmap.md)
- [uppermap](uppermap) - [uppermap](uppermap.md)
- [lowermap](lowermap) - [lowermap](lowermap.md)
- [reflectmask](reflectmask) - [reflectmask](reflectmask.md)
- [reflectcube](reflectcube) - [reflectcube](reflectcube.md)

View file

@ -4,11 +4,11 @@
material. It also accepts arguments that will recompile a shader with material. It also accepts arguments that will recompile a shader with
certain permutations. This is kinda ugly, certain permutations. This is kinda ugly,
Starting in [The Wastes](The_Wastes) 1.2, there are the Starting in **The Wastes 1.2**, there are the
following shader programs available to you: following shader programs available to you:
- program [unlit](unlit_(Shader)) - program [unlit](unlit.md)
- program [lightmapped](lightmapped_(Shader)) - program [lightmapped](lightmapped.md)
- program [vertexlit](vertexlit_(Shader)) - program [vertexlit](vertexlit.md)
- program [water](water_(Shader)) - program [water](water.md)
- program [refract](refract_(Shader)) - program [refract](refract.md)

View file

@ -11,10 +11,10 @@ will pass the nearest in-world cubemap instead.
### See also {#see_also} ### See also {#see_also}
- [diffusemap](diffusemap) - [diffusemap](diffusemap.md)
- [normalmap](normalmap) - [normalmap](normalmap.md)
- [specularmap](specularmap) - [specularmap](specularmap.md)
- [fullbrightmap](fullbrightmap) - [fullbrightmap](fullbrightmap.md)
- [uppermap](uppermap) - [uppermap](uppermap.md)
- [lowermap](lowermap) - [lowermap](lowermap.md)
- [reflectmask](reflectmask) - [reflectmask](reflectmask.md)

View file

@ -8,18 +8,18 @@
Defines a texture that specifies which parts of a material will reveal a Defines a texture that specifies which parts of a material will reveal a
reflective material, such as a reflective material, such as a
[cubemap](reflectcube). This applies to [cubemap](reflectcube.md). This applies to
standard FTEQW. In Nuclide the reflectmask is currently unused with the standard FTEQW. In Nuclide the reflectmask is currently unused with the
included shaders. If you want to apply reflectivity to your materials, included shaders. If you want to apply reflectivity to your materials,
use the alpha channel of your use the alpha channel of your
[normalmap](normalmap) instead. [normalmap](normalmap.md) instead.
### See also {#see_also} ### See also {#see_also}
- [diffusemap](diffusemap) - [diffusemap](diffusemap.md)
- [normalmap](normalmap) - [normalmap](normalmap.md)
- [specularmap](specularmap) - [specularmap](specularmap.md)
- [fullbrightmap](fullbrightmap) - [fullbrightmap](fullbrightmap.md)
- [uppermap](uppermap) - [uppermap](uppermap.md)
- [lowermap](lowermap) - [lowermap](lowermap.md)
- [reflectcube](reflectcube) - [reflectcube](reflectcube.md)

View file

@ -13,20 +13,20 @@ to use for color, intensity etc.
The renderer will take it into account only if you do not supply any The renderer will take it into account only if you do not supply any
Stages in the material. Stages in the material.
**<farbox>** Specifies a set of files to use as an environment box **farbox**: Specifies a set of files to use as an environment box
behind all cloudlayers. Specify "-" for no farbox, or a file base name. behind all cloudlayers. Specify "-" for no farbox, or a file base name.
A base name of "env/test" would look for files "env/test_rt.tga", A base name of "env/test" would look for files "env/test_rt.tga",
"env/test_lf.tga", "env/test_ft.tga", "env/test_bk.tga", "env/test_lf.tga", "env/test_ft.tga", "env/test_bk.tga",
"env/test_up.tga", "env/test_dn.tga" to use as the right / left / front "env/test_up.tga", "env/test_dn.tga" to use as the right / left / front
/ back / up / down sides. / back / up / down sides.
**<cloudheight>** controls apparent curvature of the cloud layers - **cloudheight**: controls apparent curvature of the cloud layers -
lower numbers mean more curvature (and thus more distortion at the lower numbers mean more curvature (and thus more distortion at the
horizons). Higher height values create "flatter" skies with less horizon horizons). Higher height values create "flatter" skies with less horizon
distortion. Think of height as the radius of a sphere on which the distortion. Think of height as the radius of a sphere on which the
clouds are mapped. Good ranges are 64 to 256. The default value is 128. clouds are mapped. Good ranges are 64 to 256. The default value is 128.
**<nearbox>** Specified as farbox, to be alpha blended ontop of the **nearbox**: Specified as farbox, to be alpha blended ontop of the
clouds. This has not be tested in a long time, so it probably doesn't clouds. This has not be tested in a long time, so it probably doesn't
actually work. Set to "-" to ignore. actually work. Set to "-" to ignore.

View file

@ -13,10 +13,10 @@ texture it for other purposes, too.
### See also {#see_also} ### See also {#see_also}
- [diffusemap](diffusemap) - [diffusemap](diffusemap.md)
- [normalmap](normalmap) - [normalmap](normalmap.md)
- [fullbrightmap](fullbrightmap) - [fullbrightmap](fullbrightmap.md)
- [uppermap](uppermap) - [uppermap](uppermap.md)
- [lowermap](lowermap) - [lowermap](lowermap.md)
- [reflectmask](reflectmask) - [reflectmask](reflectmask.md)
- [reflectcube](reflectcube) - [reflectcube](reflectcube.md)

View file

@ -8,9 +8,9 @@ by the renderer. A few keywords will only apply to any one of them.
All surfaceparm keywords are preceded by the word surfaceparm as All surfaceparm keywords are preceded by the word surfaceparm as
follows: follows:
: ```
surfaceparm **fog**
: surfaceparm **fog** ```
### Behaviour Keywords {#behaviour_keywords} ### Behaviour Keywords {#behaviour_keywords}

View file

@ -2,7 +2,7 @@
## tcmod ## tcmod
### Syntax ### Syntax
**tcMod <func> &lt;…&gt;** **tcMod <func> [...]**
### Overview ### Overview
@ -26,7 +26,7 @@ specified. In otherwords, if you see:
 tcMod scroll 1 1  tcMod scroll 1 1
``` ```
Then the texture coordinates will be **scaled then**scrolled'''. Then the texture coordinates will be **scaled** then **scrolled**.
### Functions {#functions} ### Functions {#functions}
@ -81,20 +81,20 @@ Stretches the texture coordinates with the given function. Stretching is
defined as stretching the texture coordinate away from the center of the defined as stretching the texture coordinate away from the center of the
polygon and then compressing it towards the center of the polygon. polygon and then compressing it towards the center of the polygon.
**<base>**: A base value of one is the original dimension of the texture **base**: A base value of one is the original dimension of the texture
when it reaches the stretch stage. Inserting other '''values positive or when it reaches the stretch stage. Inserting other '''values positive or
negative in this variable will produce unknown effects. negative in this variable will produce unknown effects.
**<amplitude>**: This is the measurement of distance the texture will **amplitude**: This is the measurement of distance the texture will
stretch from the base size. It is measured, like scroll, in textures. A stretch from the base size. It is measured, like scroll, in textures. A
value of 1 here will double the size of the texture at its peak. value of 1 here will double the size of the texture at its peak.
**<phase>**: See the explanation for phase under the deform vertexes **phase**: See the explanation for phase under the deform vertexes
keyword. keyword.
**<frequency>**: this is wave peaks per second. **frequency**: this is wave peaks per second.
**<func>**: **func**:
- **Sin**: the texture expands smoothly to its peak dimension and then - **Sin**: the texture expands smoothly to its peak dimension and then
shrinks smoothly to its valley dimension in a flowing manner. shrinks smoothly to its valley dimension in a flowing manner.
@ -122,13 +122,13 @@ forth churning and swirling effect on the texture.
The parameters for this are defined as follows: The parameters for this are defined as follows:
- **<base>**: Currently undefined. - **base**: Currently undefined.
- **<amplitude>**: This is essentially the intensity of the - **amplitude**: This is essentially the intensity of the
disturbance or twisting and squiggling of the texture. disturbance or twisting and squiggling of the texture.
- **<phase>**: See the explanation for phase under the - **phase**: See the explanation for phase under the
[deformvertexes](DeformVertexes) [deformvertexes](DeformVertexes)
keyword. keyword.
- **<freq>**: Frequency. This value is expressed as repetitions or - **freq**: Frequency. This value is expressed as repetitions or
cycles of the wave per second. A value of one would cycle once per cycles of the wave per second. A value of one would cycle once per
second. A value of 10 would cycle 10 times per second. A value of second. A value of 10 would cycle 10 times per second. A value of
0.1 would cycle once every 10 seconds. 0.1 would cycle once every 10 seconds.

15
Documentation/XR.md Normal file
View file

@ -0,0 +1,15 @@
# The XR Subsystem
To support XR (which is the support for Virtual, Augmented or Mixed Reality output) we have a few routines that help deal with this.
## Classes related to XR
We have 3 NSXRInput class instances per NSClient class, which itself belongs to a NSXRSpace.
### Head
The head is an NSXRInput of type XR_INPUT_HEAD. Yes, a head is just like any other XR compatible controller. It has a gyro and it potentially has buttons the user can press.
### Hands
We have two hands available, that will identify themselves as XR_INPUT_LEFT or XR_INPUT_RIGHT respectively. These are your primary tools for interacting with the 3D world.

View file

@ -958,7 +958,7 @@ msgid "IDS_CONFIGURE_GOREHELP"
msgstr "Disable visuals inappropriate for younger players and multiplayer." msgstr "Disable visuals inappropriate for younger players and multiplayer."
msgid "IDS_CONFIGURE_AUTOPATCHHELP" msgid "IDS_CONFIGURE_AUTOPATCHHELP"
msgstr "Download the latest version of %s." msgstr "Download updates for %s."
msgid "IDS_CHAT_NOSERVERS" msgid "IDS_CHAT_NOSERVERS"
msgstr "Could not locate any Frag-Net servers." msgstr "Could not locate any Frag-Net servers."
@ -1652,3 +1652,33 @@ msgstr "Do you want to uncompress the files for game '%s'?"
msgid "IDS_FAVSVRS_CORRUPT" msgid "IDS_FAVSVRS_CORRUPT"
msgstr "The server data file favsvrs.dat appears to be corrupt.\n\nYou can request a new list of servers by pressing the Update button.\n\nDo you want to remove the corrupt file (you will have to re-enter your 'favorites' if you remove the file)?" msgstr "The server data file favsvrs.dat appears to be corrupt.\n\nYou can request a new list of servers by pressing the Update button.\n\nDo you want to remove the corrupt file (you will have to re-enter your 'favorites' if you remove the file)?"
msgid "UPDATE_DISABLED"
msgstr "Disabled"
msgid "UPDATE_ENABLED"
msgstr "Enabled"
msgid "UPDATE_CORRUPT"
msgstr "Corrupt"
msgid "UPDATE_NOTINSTALLED"
msgstr "Not installed"
msgid "UPDATE_PENDING_INSTALL"
msgstr "Install (pending)"
msgid "UPDATE_PENDING_REINSTALL"
msgstr "Reinstall (pending)"
msgid "UPDATE_PENDING_UNINSTALL"
msgstr "Uninstall (pending)"
msgid "UPDATE_PENDING_AUTOINSTALL"
msgstr "Auto-install (pending)"
msgid "UPDATE_PENDING_DISABLE"
msgstr "Disable (pending)"
msgid "UPDATE_PENDING_RETAIN"
msgstr "Retain (pending)"

View file

@ -18,6 +18,7 @@
var int autocvar_menu_intro = TRUE; var int autocvar_menu_intro = TRUE;
var int autocvar__menu_singleplayer; var int autocvar__menu_singleplayer;
var bool autocvar_menu_steambg = false;
#define KEY_UNKNOWN -1 #define KEY_UNKNOWN -1
#define KEY_GAME 0 #define KEY_GAME 0
@ -59,50 +60,6 @@ typedef struct
void(float evtype, float scanx, float chary, float devid) m_input; void(float evtype, float scanx, float chary, float devid) m_input;
} menupage_t; } menupage_t;
typedef enum
{
GAMEINFO_NONE,
GAMEINFO_MANIFEST,
GAMEINFO_GITXT,
GAMEINFO_LIBLIST
} gi_type;
typedef struct
{
string game;
string gamedir;
string base_dir;
string url_info;
string url_dl;
string version;
string readme;
int size;
int svonly;
int cldll;
string type;
string hlversion;
string pkgname;
string pkgfile;
int pkgid;
int nomodels;
int nosprays;
int installed;
string mpentity;
string gamedll;
string startmap;
string trainingmap;
string fallback_dir;
string chatroom;
string menumap;
string introvideo;
int steambg;
gi_type info_type;
} gameinfo_t;
var int gameinfo_current = -1;
int gameinfo_count;
gameinfo_t *games;
int g_menupage; int g_menupage;
enum enum

View file

@ -84,6 +84,7 @@ void
m_init(void) m_init(void)
{ {
vector g_btnsize; vector g_btnsize;
string menuMap;
print("--------- Initializing Menu ----------\n"); print("--------- Initializing Menu ----------\n");
print("Built: " __DATE__ " " __TIME__"\n"); print("Built: " __DATE__ " " __TIME__"\n");
@ -116,6 +117,7 @@ m_init(void)
registercommand("map_background"); registercommand("map_background");
registercommand("menu_musicstart"); registercommand("menu_musicstart");
registercommand("richpresence_dump"); registercommand("richpresence_dump");
registercommand("platformRefreshUpdates");
Font_Load("fonts/fontcon.font", font_console); Font_Load("fonts/fontcon.font", font_console);
Font_Load("fonts/menu_label.font", font_label); Font_Load("fonts/menu_label.font", font_label);
@ -133,38 +135,31 @@ m_init(void)
g_btnofs = 26 / g_btnsize[1]; g_btnofs = 26 / g_btnsize[1];
GameLibrary_Init(); GameLibrary_Init();
MapLibrary_Init();
main_init(); main_init();
Colors_Init(); Colors_Init();
Strings_Init(); Strings_Init();
Updates_Init();
if (games[gameinfo_current].gamedir != "valve" || games[gameinfo_current].steambg == 1) { if (GameLibrary_GetInfo(GAMEINFO_GAMEDIR) != "valve") {
m_intro_skip(); m_intro_skip();
Music_MenuStart(); Music_MenuStart();
} }
if (games[gameinfo_current].pkgfile != "")
if not (whichpack(games[gameinfo_current].pkgfile) || autocvar_menu_updating) {
/* reload in case of video restarts? */
shaderforname("logo_avi", "{\n{\nvideomap av:media/logo.avi\n}\n}");
g_menupage = PAGE_UPDATES;
}
Menu_AutoScale(); Menu_AutoScale();
Menu_GammaHack(); Menu_GammaHack();
if (g_intro_stage == 0) { if (g_intro_stage == 0) {
string videofile = games[gameinfo_current].introvideo; string videofile = GameLibrary_GetInfo(GAMEINFO_INTROVIDEO);
if (videofile) { if (videofile) {
localcmd(strcat("playvideo ", videofile, "\n")); localcmd(strcat("playvideo ", videofile, "\n"));
} }
} }
if (games[gameinfo_current].menumap != "") { menuMap = GameLibrary_GetInfo(GAMEINFO_MENUMAP);
string mapname = games[gameinfo_current].menumap; if (menuMap != "") {
localcmd(strcat("map_background ", mapname, "\n")); localcmd(strcat("map_background ", menuMap, "\n"));
} }
/* prepare spray logo keys */ /* prepare spray logo keys */
@ -285,7 +280,7 @@ m_draw(vector screensize)
drawfill([0,0], screensize, [0,0,0], 1.0f); drawfill([0,0], screensize, [0,0,0], 1.0f);
/* draw either WON or Steam background */ /* draw either WON or Steam background */
if (games[gameinfo_current].steambg == 0) if (autocvar_menu_steambg == false)
Background_WON(); Background_WON();
else else
Background_Steam(); Background_Steam();
@ -414,6 +409,9 @@ m_consolecommand(string cmd)
localcmd(sprintf("maxplayers 2\nset coop 1\nset sv_background 1\nmap %s\n", localcmd(sprintf("maxplayers 2\nset coop 1\nset sv_background 1\nmap %s\n",
argv(1))); argv(1)));
break; break;
case "platformRefreshUpdates":
Updates_RefreshState();
break;
default: default:
return (0); return (0);
} }

View file

@ -339,7 +339,7 @@ cr_makeconnection(void)
string channel; string channel;
i = TCP_Connect(&tcp_irc, "tcp://irc.frag-net.com:6667"); i = TCP_Connect(&tcp_irc, "tcp://irc.frag-net.com:6667");
channel = games[gameinfo_current].chatroom; channel = GameLibrary_GetInfo(GAMEINFO_CHATROOM);
/* we're already setting a default channel, but a mod might set it /* we're already setting a default channel, but a mod might set it
set it to something empty to mess with us */ set it to something empty to mess with us */

View file

@ -30,11 +30,6 @@ CFrame create_frMaps;
CListBox create_lbMaps; CListBox create_lbMaps;
CScrollbar create_sbMaps; CScrollbar create_sbMaps;
int g_mapcount;
int map_blacklist_count;
string *map_blacklist;
void void
create_btnok_start(void) create_btnok_start(void)
{ {
@ -104,8 +99,7 @@ create_sbmaps_changed(int val)
void void
menu_creategame_init(void) menu_creategame_init(void)
{ {
int i = 0; int mapCount = 0i;
int realcount = 0;
fn_create = spawn(CWidget); fn_create = spawn(CWidget);
create_btnAdv = spawn(CMainButton); create_btnAdv = spawn(CMainButton);
@ -165,72 +159,15 @@ menu_creategame_init(void)
create_sbMaps.SetCallback(create_sbmaps_changed); create_sbMaps.SetCallback(create_sbmaps_changed);
Widget_Add(fn_createshared, create_sbMaps); Widget_Add(fn_createshared, create_sbMaps);
/* map blacklist code */ /* fill in our list box */
filestream fs_blacklist; mapCount = MapLibrary_GetMapCount();
fs_blacklist = fopen("scripts/map_blacklist", FILE_READ);
if (fs_blacklist < 0) { for (int i = 0i; i < mapCount; i++) {
print("^1WARNING: ^7Could NOT load scripts/map_blacklist\n"); string mapName = MapLibrary_GetInfo(i, MAPINFO_NAME);
create_lbMaps.AddEntry(mapName);
} }
if (fs_blacklist >= 0) { create_sbMaps.SetMax(mapCount);
string temp;
while ((temp = fgets(fs_blacklist))) {
map_blacklist_count++;
}
map_blacklist = memalloc(sizeof(string) * map_blacklist_count);
fseek(fs_blacklist, 0);
i = 0;
while ((temp = fgets(fs_blacklist))) {
map_blacklist[i] = temp;
i++;
}
fclose(fs_blacklist);
}
searchhandle mapsearch = search_begin("maps/*.bsp", SEARCH_NAMESORT | SEARCH_FULLPACKAGE, TRUE);
g_mapcount = search_getsize(mapsearch);
for (i = 0; i < g_mapcount; i++) {
string tmp, dir;
string gdir = games[gameinfo_current].gamedir;
int list = TRUE;
tmp = substring(search_getfilename(mapsearch, i), 5, -1);
/* work around FTEQW's path choice */
if (gdir == "ftehl")
gdir = "valve";
/* only look for maps in the current gamedir, requires SEARCH_FULLPACKAGE */
dir = substring(search_getpackagename(mapsearch, i), 0, strlen(gdir));
if (dir != gdir) {
continue;
}
/* ignore test_ prefix maps */
if (substring(tmp, 0, 5) == "test_") {
continue;
}
/* see if any of our blacklisted names match */
for (int b = 0; b < map_blacklist_count; b++) {
if (tmp == map_blacklist[b]) {
list = FALSE;
break;
}
}
if (list == TRUE) {
create_lbMaps.AddEntry(tmp);
realcount++;
}
}
create_sbMaps.SetMax(realcount);
search_end(mapsearch);
} }
void void

View file

@ -86,9 +86,9 @@ customgame_sbmods_changed(int val)
void void
customgame_lbmods_changed(void) customgame_lbmods_changed(void)
{ {
int gid = customgame_lbMods.GetSelected(); int gameID = customgame_lbMods.GetSelected();
if (games[gid].installed == TRUE) { if (GameLibrary_GetGameInfo(gameID, GAMEINFO_INSTALLED) == true) {
customgame_btnActivate.SetExecute(customgame_btnactivate_start); customgame_btnActivate.SetExecute(customgame_btnactivate_start);
customgame_btnInstall.SetExecute(__NULL__); customgame_btnInstall.SetExecute(__NULL__);
} else { } else {

View file

@ -224,8 +224,6 @@ menu_customize_init(void)
} }
} }
search_end(searchy); search_end(searchy);
} else {
games[gameinfo_current].nosprays = 1;
} }
/* scan and cache the models */ /* scan and cache the models */
@ -240,8 +238,6 @@ menu_customize_init(void)
precache_pic(g_models[i]); precache_pic(g_models[i]);
} }
search_end(searchy); search_end(searchy);
} else {
games[gameinfo_current].nomodels = 1;
} }
fn_customize = spawn(CWidget); fn_customize = spawn(CWidget);
@ -299,7 +295,7 @@ menu_customize_init(void)
cz_sldReceiveVol.SetCallback(cz_sldReceiveVolChanged); cz_sldReceiveVol.SetCallback(cz_sldReceiveVolChanged);
Widget_Add(fn_customize, cz_sldReceiveVol); Widget_Add(fn_customize, cz_sldReceiveVol);
if (games[gameinfo_current].nosprays == 0) { if (GameLibrary_GetInfo(GAMEINFO_NOSPRAYS) == false) {
cz_psSpray = spawn(CPictureSwitch); cz_psSpray = spawn(CPictureSwitch);
cz_psSpray.SetPos(212,226); cz_psSpray.SetPos(212,226);
cz_psSpray.SetSize(99,124); cz_psSpray.SetSize(99,124);
@ -312,7 +308,7 @@ menu_customize_init(void)
Widget_Add(fn_customize, cz_psSpray); Widget_Add(fn_customize, cz_psSpray);
} }
if (games[gameinfo_current].nomodels == 0) { if (GameLibrary_GetInfo(GAMEINFO_NOPLAYERMODELS) == false) {
#ifdef MODEL_PREVIEW #ifdef MODEL_PREVIEW
static vector vecDistance = [ 45, 0, 0 ]; static vector vecDistance = [ 45, 0, 0 ];
static void ModelPreview_SetModel( string strModel ) { static void ModelPreview_SetModel( string strModel ) {
@ -485,7 +481,7 @@ menu_customize_draw(void)
if (games[gameinfo_current].nomodels == 0) { if (GameLibrary_GetInfo(GAMEINFO_NOPLAYERMODELS) == false) {
drawfill([g_menuofs[0] + 410,g_menuofs[1] + 160], [170, 221], FRAME_COLOR, 1.0f); drawfill([g_menuofs[0] + 410,g_menuofs[1] + 160], [170, 221], FRAME_COLOR, 1.0f);
drawfill([g_menuofs[0] + 413,g_menuofs[1] + 163], [164,190], [0,0,0], 1.0f); drawfill([g_menuofs[0] + 413,g_menuofs[1] + 163], [164,190], [0,0,0], 1.0f);
WLabel_Static(410, 390, "Colors", 14, 14, [1,1,1], WLabel_Static(410, 390, "Colors", 14, 14, [1,1,1],
@ -516,7 +512,7 @@ menu_customize_draw(void)
} }
#endif #endif
if (games[gameinfo_current].nosprays == 0) if (GameLibrary_GetInfo(GAMEINFO_NOSPRAYS) == false)
WLabel_Static(212, 203, m_reslbl[IDS_PROFILE_LOGO], 14, 14, [1,1,1], WLabel_Static(212, 203, m_reslbl[IDS_PROFILE_LOGO], 14, 14, [1,1,1],
1.0f, 0, font_arial); 1.0f, 0, font_arial);

View file

@ -78,7 +78,7 @@ btn_training(void)
{ {
localcmd("stopmusic\n"); localcmd("stopmusic\n");
localcmd("maxplayers 1\n"); localcmd("maxplayers 1\n");
localcmd(games[gameinfo_current].trainingmap); localcmd(GameLibrary_GetInfo(GAMEINFO_TRAININGMAP));
/* TODO: Some mods may choose to put movies in this... */ /* TODO: Some mods may choose to put movies in this... */
RichPresence_Clear(); RichPresence_Clear();
@ -195,7 +195,7 @@ menu_main_init(void)
main_btnNewGame.SetImage(BTN_NEWGAME); main_btnNewGame.SetImage(BTN_NEWGAME);
main_btnNewGame.SetPos(70,208); main_btnNewGame.SetPos(70,208);
/* new game not available in multiplayer_only */ /* new game not available in multiplayer_only */
if (games[gameinfo_current].type != "Multiplayer") if (GameLibrary_GetInfo(GAMEINFO_TYPE) != "Multiplayer")
main_btnNewGame.SetExecute(btn_newgame_start); main_btnNewGame.SetExecute(btn_newgame_start);
Widget_Add(fn_main, main_btnNewGame); Widget_Add(fn_main, main_btnNewGame);
@ -204,7 +204,7 @@ menu_main_init(void)
main_btnTraining.SetImage(BTN_TRAINING); main_btnTraining.SetImage(BTN_TRAINING);
main_btnTraining.SetPos(70,236); main_btnTraining.SetPos(70,236);
/* some mods actively try to hide the option */ /* some mods actively try to hide the option */
if (games[gameinfo_current].trainingmap != "") if (GameLibrary_GetInfo(GAMEINFO_TRAININGMAP) != "")
main_btnTraining.SetExecute(btn_training); main_btnTraining.SetExecute(btn_training);
Widget_Add(fn_main, main_btnTraining); Widget_Add(fn_main, main_btnTraining);
@ -220,7 +220,7 @@ menu_main_init(void)
main_btnLoadGame.SetImage(BTN_LOADGAME); main_btnLoadGame.SetImage(BTN_LOADGAME);
main_btnLoadGame.SetPos(70,292); main_btnLoadGame.SetPos(70,292);
/* disable in multiplayer_only */ /* disable in multiplayer_only */
if (games[gameinfo_current].type != "Multiplayer") if (GameLibrary_GetInfo(GAMEINFO_TYPE) != "Multiplayer")
main_btnLoadGame.SetExecute(btn_loadgame_start); main_btnLoadGame.SetExecute(btn_loadgame_start);
Widget_Add(fn_main, main_btnLoadGame); Widget_Add(fn_main, main_btnLoadGame);
@ -229,7 +229,7 @@ menu_main_init(void)
main_btnMultiplayer.SetImage(BTN_MULTIPLAYER); main_btnMultiplayer.SetImage(BTN_MULTIPLAYER);
main_btnMultiplayer.SetPos(70,320); main_btnMultiplayer.SetPos(70,320);
/* disable when it's singleplayer_only */ /* disable when it's singleplayer_only */
if (games[gameinfo_current].type != "Singleplayer") if (GameLibrary_GetInfo(GAMEINFO_TYPE) != "Singleplayer")
main_btnMultiplayer.SetExecute(btn_multiplayer_start); main_btnMultiplayer.SetExecute(btn_multiplayer_start);
Widget_Add(fn_main, main_btnMultiplayer); Widget_Add(fn_main, main_btnMultiplayer);
@ -296,14 +296,14 @@ menu_main_init(void)
main_btn2NewGame.SetImage(BTN_NEWGAME); main_btn2NewGame.SetImage(BTN_NEWGAME);
main_btn2NewGame.SetPos(70,236); main_btn2NewGame.SetPos(70,236);
/* disable in multiplayer_only */ /* disable in multiplayer_only */
if (games[gameinfo_current].type != "Multiplayer") if (GameLibrary_GetInfo(GAMEINFO_TYPE) != "Multiplayer")
main_btn2NewGame.SetExecute(btn_newgame_start); main_btn2NewGame.SetExecute(btn_newgame_start);
Widget_Add(fn_main2, main_btn2NewGame); Widget_Add(fn_main2, main_btn2NewGame);
/* Training */ /* Training */
main_btn2Training = spawn(CMainButton); main_btn2Training = spawn(CMainButton);
main_btn2Training.SetImage(BTN_TRAINING); main_btn2Training.SetImage(BTN_TRAINING);
if (games[gameinfo_current].trainingmap) if (GameLibrary_GetInfo(GAMEINFO_TRAININGMAP) != "")
main_btn2Training.SetExecute(btn_training); main_btn2Training.SetExecute(btn_training);
main_btn2Training.SetPos(70,264); main_btn2Training.SetPos(70,264);
Widget_Add(fn_main2, main_btn2Training); Widget_Add(fn_main2, main_btn2Training);
@ -320,7 +320,7 @@ menu_main_init(void)
main_btn2LoadGame.SetImage(BTN_SAVELOAD); main_btn2LoadGame.SetImage(BTN_SAVELOAD);
main_btn2LoadGame.SetPos(70,320); main_btn2LoadGame.SetPos(70,320);
/* disable in multiplayer_only */ /* disable in multiplayer_only */
if (games[gameinfo_current].type != "Multiplayer") if (GameLibrary_GetInfo(GAMEINFO_TYPE) != "Multiplayer")
main_btn2LoadGame.SetExecute(btn_loadgame_start); main_btn2LoadGame.SetExecute(btn_loadgame_start);
Widget_Add(fn_main2, main_btn2LoadGame); Widget_Add(fn_main2, main_btn2LoadGame);
@ -328,7 +328,7 @@ menu_main_init(void)
main_btn2Multiplayer = spawn(CMainButton); main_btn2Multiplayer = spawn(CMainButton);
main_btn2Multiplayer.SetImage(BTN_MULTIPLAYER); main_btn2Multiplayer.SetImage(BTN_MULTIPLAYER);
main_btn2Multiplayer.SetPos(70,348); main_btn2Multiplayer.SetPos(70,348);
if (games[gameinfo_current].type != "Singleplayer") if (GameLibrary_GetInfo(GAMEINFO_TYPE) != "Singleplayer")
main_btn2Multiplayer.SetExecute(btn_multiplayer_start); main_btn2Multiplayer.SetExecute(btn_multiplayer_start);
Widget_Add(fn_main2, main_btn2Multiplayer); Widget_Add(fn_main2, main_btn2Multiplayer);
@ -379,7 +379,7 @@ menu_main_draw(void)
} else { } else {
#ifndef WEBMENU #ifndef WEBMENU
/* Don't even attempt to display the logo.avi otherwise */ /* Don't even attempt to display the logo.avi otherwise */
if (games[gameinfo_current].steambg == 0 && checkcommand("ffmpeg_videobitrate")) { if (autocvar_menu_steambg == false && checkcommand("ffmpeg_videobitrate")) {
drawpic([g_menuofs[0],g_menuofs[1] + 70], g_bmp[LOGO_AVI], drawpic([g_menuofs[0],g_menuofs[1] + 70], g_bmp[LOGO_AVI],
g_logosize, [1,1,1], 1.0f); g_logosize, [1,1,1], 1.0f);
g_logosize = gecko_get_texture_extent(g_bmp[LOGO_AVI]); g_logosize = gecko_get_texture_extent(g_bmp[LOGO_AVI]);

View file

@ -171,6 +171,12 @@ mp_btndisconnect(void)
RichPresence_Clear(); RichPresence_Clear();
} }
void
mp_btnquickstart(void)
{
}
void void
menu_multiplayer_init(void) menu_multiplayer_init(void)
{ {
@ -181,7 +187,7 @@ menu_multiplayer_init(void)
/* unconnected menu */ /* unconnected menu */
mp_btnQuickstart = spawn(CMainButton); mp_btnQuickstart = spawn(CMainButton);
mp_btnQuickstart.SetImage(BTN_QUICKSTART); mp_btnQuickstart.SetImage(BTN_QUICKSTART);
//mp_btnQuickstart.SetExecute(btn_console); mp_btnQuickstart.SetExecute(mp_btnquickstart);
mp_btnQuickstart.SetPos(50,140); mp_btnQuickstart.SetPos(50,140);
Widget_Add(fn_multiplayer, mp_btnQuickstart); Widget_Add(fn_multiplayer, mp_btnQuickstart);

View file

@ -28,7 +28,7 @@ ng_btneasy_start(void)
g_menupage = PAGE_MAIN; g_menupage = PAGE_MAIN;
localcmd("stopmusic\n"); localcmd("stopmusic\n");
localcmd("set skill 1; maxplayers 1\n"); localcmd("set skill 1; maxplayers 1\n");
localcmd(games[gameinfo_current].startmap); localcmd(GameLibrary_GetInfo(GAMEINFO_STARTMAP));
RichPresence_Clear(); RichPresence_Clear();
RichPresence_Set("status", "Singleplayer: Easy"); RichPresence_Set("status", "Singleplayer: Easy");
} }
@ -48,7 +48,7 @@ ng_btnnormal_start(void)
g_menupage = PAGE_MAIN; g_menupage = PAGE_MAIN;
localcmd("stopmusic\n"); localcmd("stopmusic\n");
localcmd("set skill 2; maxplayers 1\n"); localcmd("set skill 2; maxplayers 1\n");
localcmd(games[gameinfo_current].startmap); localcmd(GameLibrary_GetInfo(GAMEINFO_STARTMAP));
RichPresence_Clear(); RichPresence_Clear();
RichPresence_Set("status", "Singleplayer: Normal"); RichPresence_Set("status", "Singleplayer: Normal");
} }
@ -68,7 +68,7 @@ ng_btnhard_start(void)
g_menupage = PAGE_MAIN; g_menupage = PAGE_MAIN;
localcmd("stopmusic\n"); localcmd("stopmusic\n");
localcmd("set skill 3; maxplayers 1\n"); localcmd("set skill 3; maxplayers 1\n");
localcmd(games[gameinfo_current].startmap); localcmd(GameLibrary_GetInfo(GAMEINFO_STARTMAP));
RichPresence_Clear(); RichPresence_Clear();
RichPresence_Set("status", "Singleplayer: Hard"); RichPresence_Set("status", "Singleplayer: Hard");
} }

View file

@ -38,19 +38,19 @@ up_btndone_start(void)
cvar_set("menu_updating", "0"); cvar_set("menu_updating", "0");
localsound("../media/launch_dnmenu1.wav"); localsound("../media/launch_dnmenu1.wav");
g_menupage = PAGE_CONFIGURATION; g_menupage = PAGE_CONFIGURATION;
localcmd("seta menu_installedpackages 1;cfg_save\n"); localcmd("pkg revert;seta menu_installedpackages 1;cfg_save\n");
} }
void void
up_btninstall_start(void) up_btninstall_start(void)
{ {
Updates_Remove(up_lbUpdates.GetSelected()); Updates_Install(up_lbUpdates.GetSelected());
} }
void void
up_btnremove_start(void) up_btnremove_start(void)
{ {
Updates_Install(up_lbUpdates.GetSelected()); Updates_Destroy(up_lbUpdates.GetSelected());
} }
void void
@ -76,7 +76,7 @@ up_lbupdates_changed(void)
if (pkgid == -1) if (pkgid == -1)
return; return;
newpic = sprintf(FN_UPDATE_IMGURL, updates[pkgid].name); newpic = Updates_GetInfo(pkgid, UPDATE_PREVIEWIMAGE);
if not (newpic) if not (newpic)
return; return;
@ -94,7 +94,6 @@ up_sbupdates_changed(int val)
void void
menu_updates_refresh(void) menu_updates_refresh(void)
{ {
Updates_Refresh();
int updateCount = Updates_GetPackageCount(); int updateCount = Updates_GetPackageCount();
up_sbUpdates.SetMax(updateCount); up_sbUpdates.SetMax(updateCount);
@ -119,19 +118,19 @@ menu_updates_init(void)
up_btnApply = spawn(CMainButton); up_btnApply = spawn(CMainButton);
up_btnApply.SetImage(BTN_UPDATE); up_btnApply.SetImage(BTN_UPDATE);
up_btnApply.SetExecute(up_btnapply_start); up_btnApply.SetExecute(up_btnapply_start);
up_btnApply.SetPos(350+96,420+30); up_btnApply.SetPos(50 + 160,420+13);
Widget_Add(fn_updates, up_btnApply); Widget_Add(fn_updates, up_btnApply);
up_btnInstall = spawn(CMainButton); up_btnInstall = spawn(CMainButton);
up_btnInstall.SetImage(BTN_INSTALL); up_btnInstall.SetImage(BTN_INSTALL);
up_btnInstall.SetExecute(up_btninstall_start); up_btnInstall.SetExecute(up_btninstall_start);
up_btnInstall.SetPos(350,420); up_btnInstall.SetPos(380,400);
Widget_Add(fn_updates, up_btnInstall); Widget_Add(fn_updates, up_btnInstall);
up_btnDelete = spawn(CMainButton); up_btnDelete = spawn(CMainButton);
up_btnDelete.SetImage(BTN_DELETE); up_btnDelete.SetImage(BTN_DELETE);
up_btnDelete.SetExecute(up_btnremove_start); up_btnDelete.SetExecute(up_btnremove_start);
up_btnDelete.SetPos(350+200,420); up_btnDelete.SetPos(380,400+30);
Widget_Add(fn_updates, up_btnDelete); Widget_Add(fn_updates, up_btnDelete);
up_frUpdates = spawn(CFrame); up_frUpdates = spawn(CFrame);
@ -158,58 +157,43 @@ menu_updates_init(void)
up_frPreview.SetPos(350,160); up_frPreview.SetPos(350,160);
up_frPreview.SetSize(256+6,128+6); up_frPreview.SetSize(256+6,128+6);
Widget_Add(fn_updates, up_frPreview); Widget_Add(fn_updates, up_frPreview);
Updates_Refresh();
#endif #endif
} }
/* Drawing */
int g_pkgname_updating;
void void
menu_updates_draw(void) menu_updates_draw(void)
{ {
#ifndef WEBMENU #ifndef WEBMENU
static int old_enabled;
float fl = 0; float fl = 0;
if (!g_updates_initialized) { /* first draw run */
int pkg_ready = 0; if (g_updates_initialized == 0) {
Updates_Init();
/* we have no hard-coded list of supported packages, so query frag-net.com */ g_updates_initialized = 2;
if (!games[gameinfo_current].pkgname && !g_pkgname_updating) {
string gamedir = games[gameinfo_current].gamedir;
print(sprintf("Querying package names for %s\n", gamedir));
uri_get(sprintf("http://www.frag-net.com/dl/packages_%s", uri_escape(gamedir)), MODSERVER_REQ_PKGNAMES);
g_pkgname_updating = 1;
}
/* don't query packages YET until we get a response */
if (g_pkgname_updating == 1) {
return;
}
/* query until 1 package is ready */
for (int i = 0; (Updates_GetInfo(i, GPMI_NAME)); i++) {
string installed = Updates_GetInfo(i, GPMI_INSTALLED);
/* increment to keep track */
if (installed == "enabled")
old_enabled++;
pkg_ready = 1;
}
if (pkg_ready == 1) {
menu_updates_refresh();
g_updates_initialized = TRUE;
}
return; return;
} }
Widget_Draw(fn_updates); updaterStatus_t status = Updates_GetUpdaterStatus();
Header_Draw(HEAD_CONFIG); Header_Draw(HEAD_CONFIG);
/* we're still initializing... */
if (g_updates_initialized == 2) {
if (status == UPDATER_PENDING) {
customgame_dlgWait.Draw();
WField_Static(162, 180, "Contacting update server...", 320, 260,
col_prompt_text, 1.0f, 2, font_label_p);
return;
} else if (status == UPDATER_INITIALIZED) {
menu_updates_refresh();
g_updates_initialized = 1;
}
}
Widget_Draw(fn_updates);
drawpic([g_menuofs[0]+550,g_menuofs[1]+10], g_bmp[FN_LOGO],[80,80], [1,1,1], 1.0f, 0);
WLabel_Static(50, 143, "Data files:", 11, 11, [1,1,1], WLabel_Static(50, 143, "Data files:", 11, 11, [1,1,1],
1.0f, 0, font_arial); 1.0f, 0, font_arial);
@ -230,37 +214,22 @@ menu_updates_draw(void)
1.0f, 0, font_arial); 1.0f, 0, font_arial);
int i = up_lbUpdates.GetSelected(); int i = up_lbUpdates.GetSelected();
i = updates[i].uid;
fl = 310; fl = 310;
WLabel_Static(420,fl, getpackagemanagerinfo(i, GPMI_AUTHOR), 11, 11, [1,1,1], WLabel_Static(420,fl, Updates_GetInfo(i, UPDATE_AUTHOR), 11, 11, [1,1,1],
1.0f, 0, font_arial); fl += 18; 1.0f, 0, font_arial); fl += 18;
WLabel_Static(420,fl, getpackagemanagerinfo(i, GPMI_INSTALLED), 11, 11, [1,1,1], WLabel_Static(420,fl, Updates_GetInfo(i, UPDATE_STATUSSTRING), 11, 11, [1,1,1],
1.0f, 0, font_arial); fl += 18; 1.0f, 0, font_arial); fl += 18;
WLabel_Static(420,fl, getpackagemanagerinfo(i, GPMI_LICENSE), 11, 11, [1,1,1], WLabel_Static(420,fl, Updates_GetInfo(i, UPDATE_LICENSE), 11, 11, [1,1,1],
1.0f, 0, font_arial); fl += 18; 1.0f, 0, font_arial); fl += 18;
WLabel_Static(420,fl, getpackagemanagerinfo(i, GPMI_WEBSITE), 11, 11, [1,1,1], WLabel_Static(420,fl, Updates_GetInfo(i, UPDATE_WEBSITE), 11, 11, [1,1,1],
1.0f, 0, font_arial); fl += 18; 1.0f, 0, font_arial); fl += 18;
WLabel_Static(420,fl, getpackagemanagerinfo(i, GPMI_VERSION), 11, 11, [1,1,1], WLabel_Static(420,fl, Updates_GetInfo(i, UPDATE_VERSION), 11, 11, [1,1,1],
1.0f, 0, font_arial); 1.0f, 0, font_arial);
WLabel_Static(350, 143, "Preview:", 11, 11, [1,1,1], WLabel_Static(350, 143, "Preview:", 11, 11, [1,1,1],
1.0f, 0, font_arial); 1.0f, 0, font_arial);
/* check if we've got any more packages than upon init */
int new_packages = 0;
for (int b = 0; (getpackagemanagerinfo(b, GPMI_NAME)); b++) {
string installed = getpackagemanagerinfo(b, GPMI_INSTALLED);
/* increment to keep track */
if (installed == "enabled")
new_packages++;
}
if (old_enabled != new_packages) {
old_enabled = new_packages;
localcmd("menu_restart\nmenu_updates\n");
}
if (g_updates_previewpic) if (g_updates_previewpic)
drawpic([g_menuofs[0]+350+3,g_menuofs[1]+160+3], g_updates_previewpic, [256,128], [1,1,1], 1.0f); drawpic([g_menuofs[0]+350+3,g_menuofs[1]+160+3], g_updates_previewpic, [256,128], [1,1,1], 1.0f);
#endif #endif

View file

@ -71,7 +71,7 @@ menu_viewreadme_init(void)
filestream rdme; filestream rdme;
string lstline; string lstline;
rdme = fopen(games[gameinfo_current].readme, FILE_READ); rdme = fopen(GameLibrary_GetInfo(GAMEINFO_READMEFILE), FILE_READ);
if (rdme >= 0) { if (rdme >= 0) {
while((lstline = fgets(rdme))) { while((lstline = fgets(rdme))) {
vr_lbReadme.AddWrapped(lstline); vr_lbReadme.AddWrapped(lstline);

View file

@ -19,6 +19,7 @@ Strings_Init(void)
{ {
filestream stringslst; filestream stringslst;
string lstline; string lstline;
string gameTitle = GameLibrary_GetInfo(GAMEINFO_TITLE);
m_reslbl[IDS_LANGUAGE] = _("IDS_LANGUAGE"); m_reslbl[IDS_LANGUAGE] = _("IDS_LANGUAGE");
m_reslbl[IDS_BORDERLESS_REGFAIL] = _("IDS_BORDERLESS_REGFAIL"); m_reslbl[IDS_BORDERLESS_REGFAIL] = _("IDS_BORDERLESS_REGFAIL");
@ -209,19 +210,19 @@ Strings_Init(void)
m_reslbl[IDS_LOADSAVE_RETURN] = _("IDS_LOADSAVE_RETURN"); m_reslbl[IDS_LOADSAVE_RETURN] = _("IDS_LOADSAVE_RETURN");
m_reslbl[IDS_MAIN_RETURNHELP] = _("IDS_MAIN_RETURNHELP"); m_reslbl[IDS_MAIN_RETURNHELP] = _("IDS_MAIN_RETURNHELP");
m_reslbl[IDS_MAIN_NEWGAMEHELP] = _("IDS_MAIN_NEWGAMEHELP"); m_reslbl[IDS_MAIN_NEWGAMEHELP] = _("IDS_MAIN_NEWGAMEHELP");
m_reslbl[IDS_MAIN_TRAININGHELP] = sprintf(_("IDS_MAIN_TRAININGHELP"), games[gameinfo_current].game); m_reslbl[IDS_MAIN_TRAININGHELP] = sprintf(_("IDS_MAIN_TRAININGHELP"), gameTitle);
m_reslbl[IDS_MAIN_LOADHELP] = _("IDS_MAIN_LOADHELP"); m_reslbl[IDS_MAIN_LOADHELP] = _("IDS_MAIN_LOADHELP");
m_reslbl[IDS_MAIN_LOADSAVEHELP] = _("IDS_MAIN_LOADSAVEHELP"); m_reslbl[IDS_MAIN_LOADSAVEHELP] = _("IDS_MAIN_LOADSAVEHELP");
m_reslbl[IDS_MAIN_CONFIGUREHELP] = _("IDS_MAIN_CONFIGUREHELP"); m_reslbl[IDS_MAIN_CONFIGUREHELP] = _("IDS_MAIN_CONFIGUREHELP");
m_reslbl[IDS_MAIN_READMEHELP] = _("IDS_MAIN_READMEHELP"); m_reslbl[IDS_MAIN_READMEHELP] = _("IDS_MAIN_READMEHELP");
m_reslbl[IDS_MAIN_ORDERHELP] = sprintf(_("IDS_MAIN_ORDERHELP"), games[gameinfo_current].game); m_reslbl[IDS_MAIN_ORDERHELP] = sprintf(_("IDS_MAIN_ORDERHELP"), gameTitle);
m_reslbl[IDS_MAIN_QUITHELP] = sprintf(_("IDS_MAIN_QUITHELP"), games[gameinfo_current].game); m_reslbl[IDS_MAIN_QUITHELP] = sprintf(_("IDS_MAIN_QUITHELP"), gameTitle);
m_reslbl[IDS_MAIN_QUICKHELP] = _("IDS_MAIN_QUICKHELP"); m_reslbl[IDS_MAIN_QUICKHELP] = _("IDS_MAIN_QUICKHELP");
m_reslbl[IDS_MAIN_MULTIPLAYERHELP] = sprintf(_("IDS_MAIN_MULTIPLAYERHELP"), games[gameinfo_current].game); m_reslbl[IDS_MAIN_MULTIPLAYERHELP] = sprintf(_("IDS_MAIN_MULTIPLAYERHELP"), gameTitle);
m_reslbl[IDS_CHAT_PROMPT] = _("IDS_CHAT_PROMPT"); m_reslbl[IDS_CHAT_PROMPT] = _("IDS_CHAT_PROMPT");
m_reslbl[IDS_NEWGAME_EASYHELP] = sprintf(_("IDS_NEWGAME_EASYHELP"), games[gameinfo_current].game); m_reslbl[IDS_NEWGAME_EASYHELP] = sprintf(_("IDS_NEWGAME_EASYHELP"), gameTitle);
m_reslbl[IDS_NEWGAME_MEDIUMHELP] = sprintf(_("IDS_NEWGAME_MEDIUMHELP"), games[gameinfo_current].game); m_reslbl[IDS_NEWGAME_MEDIUMHELP] = sprintf(_("IDS_NEWGAME_MEDIUMHELP"), gameTitle);
m_reslbl[IDS_NEWGAME_DIFFICULTHELP] = sprintf(_("IDS_NEWGAME_DIFFICULTHELP"), games[gameinfo_current].game); m_reslbl[IDS_NEWGAME_DIFFICULTHELP] = sprintf(_("IDS_NEWGAME_DIFFICULTHELP"), gameTitle);
m_reslbl[IDS_NEWGAME_RETURNHELP] = _("IDS_NEWGAME_RETURNHELP"); m_reslbl[IDS_NEWGAME_RETURNHELP] = _("IDS_NEWGAME_RETURNHELP");
m_reslbl[IDS_NEWPROFILE_TITLE] = _("IDS_NEWPROFILE_TITLE"); m_reslbl[IDS_NEWPROFILE_TITLE] = _("IDS_NEWPROFILE_TITLE");
m_reslbl[IDS_NEWPROFILE_NAME] = _("IDS_NEWPROFILE_NAME"); m_reslbl[IDS_NEWPROFILE_NAME] = _("IDS_NEWPROFILE_NAME");
@ -235,7 +236,7 @@ Strings_Init(void)
m_reslbl[IDS_VIDEO_SCREENSIZE] = _("IDS_VIDEO_SCREENSIZE"); m_reslbl[IDS_VIDEO_SCREENSIZE] = _("IDS_VIDEO_SCREENSIZE");
m_reslbl[IDS_VIDEO_GAMMA] = _("IDS_VIDEO_GAMMA"); m_reslbl[IDS_VIDEO_GAMMA] = _("IDS_VIDEO_GAMMA");
m_reslbl[IDS_VIDEO_GLARE] = _("IDS_VIDEO_GLARE"); m_reslbl[IDS_VIDEO_GLARE] = _("IDS_VIDEO_GLARE");
m_reslbl[IDS_VIDEO_GAMMAHELP] = sprintf(_("IDS_VIDEO_GAMMAHELP"), games[gameinfo_current].game); m_reslbl[IDS_VIDEO_GAMMAHELP] = sprintf(_("IDS_VIDEO_GAMMAHELP"), gameTitle);
m_reslbl[IDS_VIDEO_GLAREHELP] = _("IDS_VIDEO_GLAREHELP"); m_reslbl[IDS_VIDEO_GLAREHELP] = _("IDS_VIDEO_GLAREHELP");
m_reslbl[IDS_VIDMODE_WINDOWED] = _("IDS_VIDMODE_WINDOWED"); m_reslbl[IDS_VIDMODE_WINDOWED] = _("IDS_VIDMODE_WINDOWED");
m_reslbl[IDS_VIDMODE_MOUSE] = _("IDS_VIDMODE_MOUSE"); m_reslbl[IDS_VIDMODE_MOUSE] = _("IDS_VIDMODE_MOUSE");
@ -254,7 +255,7 @@ Strings_Init(void)
m_reslbl[IDS_STATUS_SKIPSERVER] = _("IDS_STATUS_SKIPSERVER"); m_reslbl[IDS_STATUS_SKIPSERVER] = _("IDS_STATUS_SKIPSERVER");
m_reslbl[IDS_STATUS_ELPASEDTIME] = _("IDS_STATUS_ELPASEDTIME"); m_reslbl[IDS_STATUS_ELPASEDTIME] = _("IDS_STATUS_ELPASEDTIME");
m_reslbl[IDS_TRAINING_EXITCURRENT] = _("IDS_TRAINING_EXITCURRENT"); m_reslbl[IDS_TRAINING_EXITCURRENT] = _("IDS_TRAINING_EXITCURRENT");
m_reslbl[IDS_MAIN_QUITPROMPTINGAME] = sprintf(_("IDS_MAIN_QUITPROMPTINGAME"), games[gameinfo_current].game); m_reslbl[IDS_MAIN_QUITPROMPTINGAME] = sprintf(_("IDS_MAIN_QUITPROMPTINGAME"), gameTitle);
m_reslbl[IDS_MAIN_QUITPROMPT] = _("IDS_MAIN_QUITPROMPT"); m_reslbl[IDS_MAIN_QUITPROMPT] = _("IDS_MAIN_QUITPROMPT");
m_reslbl[IDS_CONTROLS_CANCELPROMPT] = _("IDS_CONTROLS_CANCELPROMPT"); m_reslbl[IDS_CONTROLS_CANCELPROMPT] = _("IDS_CONTROLS_CANCELPROMPT");
m_reslbl[IDS_LOAD_LOADPROMPT] = _("IDS_LOAD_LOADPROMPT"); m_reslbl[IDS_LOAD_LOADPROMPT] = _("IDS_LOAD_LOADPROMPT");
@ -305,11 +306,11 @@ Strings_Init(void)
m_reslbl[IDS_BTN_QUICK] = _("IDS_BTN_QUICK"); m_reslbl[IDS_BTN_QUICK] = _("IDS_BTN_QUICK");
m_reslbl[IDS_PROFILE_LOGO] = _("IDS_PROFILE_LOGO"); m_reslbl[IDS_PROFILE_LOGO] = _("IDS_PROFILE_LOGO");
m_reslbl[IDS_BTN_BROWSE] = _("IDS_BTN_BROWSE"); m_reslbl[IDS_BTN_BROWSE] = _("IDS_BTN_BROWSE");
m_reslbl[IDS_MULTI_BROWSEHELP] = sprintf(_("IDS_MULTI_BROWSEHELP"), games[gameinfo_current].game); m_reslbl[IDS_MULTI_BROWSEHELP] = sprintf(_("IDS_MULTI_BROWSEHELP"), gameTitle);
m_reslbl[IDS_BTN_CHAT] = _("IDS_BTN_CHAT"); m_reslbl[IDS_BTN_CHAT] = _("IDS_BTN_CHAT");
m_reslbl[IDS_MULTI_CHATHELP] = sprintf(_("IDS_MULTI_CHATHELP"), games[gameinfo_current].game); m_reslbl[IDS_MULTI_CHATHELP] = sprintf(_("IDS_MULTI_CHATHELP"), gameTitle);
m_reslbl[IDS_BTN_LAN] = _("IDS_BTN_LAN"); m_reslbl[IDS_BTN_LAN] = _("IDS_BTN_LAN");
m_reslbl[IDS_MULTI_LANHELP] = sprintf(_("IDS_MULTI_LANHELP"), games[gameinfo_current].game); m_reslbl[IDS_MULTI_LANHELP] = sprintf(_("IDS_MULTI_LANHELP"), gameTitle);
m_reslbl[IDS_BTN_CUSTOMIZE] = _("IDS_BTN_CUSTOMIZE"); m_reslbl[IDS_BTN_CUSTOMIZE] = _("IDS_BTN_CUSTOMIZE");
m_reslbl[IDS_MULTI_CUSTOMIZEHELP] = _("IDS_MULTI_CUSTOMIZEHELP"); m_reslbl[IDS_MULTI_CUSTOMIZEHELP] = _("IDS_MULTI_CUSTOMIZEHELP");
m_reslbl[IDS_CREATEROOM_ROOMPASSWORD] = _("IDS_CREATEROOM_ROOMPASSWORD"); m_reslbl[IDS_CREATEROOM_ROOMPASSWORD] = _("IDS_CREATEROOM_ROOMPASSWORD");
@ -340,12 +341,12 @@ Strings_Init(void)
m_reslbl[IDS_QUICKSTART_TITLE] = _("IDS_QUICKSTART_TITLE"); m_reslbl[IDS_QUICKSTART_TITLE] = _("IDS_QUICKSTART_TITLE");
m_reslbl[IDS_BTN_LISTROOMS] = _("IDS_BTN_LISTROOMS"); m_reslbl[IDS_BTN_LISTROOMS] = _("IDS_BTN_LISTROOMS");
m_reslbl[IDS_BTN_GORE] = _("IDS_BTN_GORE"); m_reslbl[IDS_BTN_GORE] = _("IDS_BTN_GORE");
m_reslbl[IDS_BTN_AUTOPATCH] = sprintf(_("IDS_BTN_AUTOPATCH"), games[gameinfo_current].game); m_reslbl[IDS_BTN_AUTOPATCH] = sprintf(_("IDS_BTN_AUTOPATCH"), gameTitle);
m_reslbl[IDS_CONFIGURE_GOREHELP] = _("IDS_CONFIGURE_GOREHELP"); m_reslbl[IDS_CONFIGURE_GOREHELP] = _("IDS_CONFIGURE_GOREHELP");
m_reslbl[IDS_CONFIGURE_AUTOPATCHHELP] = sprintf(_("IDS_CONFIGURE_AUTOPATCHHELP"), games[gameinfo_current].game); m_reslbl[IDS_CONFIGURE_AUTOPATCHHELP] = sprintf(_("IDS_CONFIGURE_AUTOPATCHHELP"), gameTitle);
m_reslbl[IDS_CHAT_NOSERVERS] = _("IDS_CHAT_NOSERVERS"); m_reslbl[IDS_CHAT_NOSERVERS] = _("IDS_CHAT_NOSERVERS");
m_reslbl[IDS_CHAT_NOPROFILE] = _("IDS_CHAT_NOPROFILE"); m_reslbl[IDS_CHAT_NOPROFILE] = _("IDS_CHAT_NOPROFILE");
m_reslbl[IDS_INIT_DX6REQUIRED] = sprintf(_("IDS_INIT_DX6REQUIRED"), games[gameinfo_current].game); m_reslbl[IDS_INIT_DX6REQUIRED] = sprintf(_("IDS_INIT_DX6REQUIRED"), gameTitle);
m_reslbl[IDS_FNET_VALIDATIONFAIL] = _("IDS_FNET_VALIDATIONFAIL"); m_reslbl[IDS_FNET_VALIDATIONFAIL] = _("IDS_FNET_VALIDATIONFAIL");
m_reslbl[IDS_FNET_VALIDATIONFAIL2] = _("IDS_FNET_VALIDATIONFAIL2"); m_reslbl[IDS_FNET_VALIDATIONFAIL2] = _("IDS_FNET_VALIDATIONFAIL2");
m_reslbl[IDS_FNET_AUTHOUTOFORDER] = _("IDS_FNET_AUTHOUTOFORDER"); m_reslbl[IDS_FNET_AUTHOUTOFORDER] = _("IDS_FNET_AUTHOUTOFORDER");
@ -353,7 +354,7 @@ Strings_Init(void)
m_reslbl[IDS_FNET_ENCRYPTBAD] = _("IDS_FNET_ENCRYPTBAD"); m_reslbl[IDS_FNET_ENCRYPTBAD] = _("IDS_FNET_ENCRYPTBAD");
m_reslbl[IDS_FNET_BADPUBLICKEY] = _("IDS_FNET_BADPUBLICKEY"); m_reslbl[IDS_FNET_BADPUBLICKEY] = _("IDS_FNET_BADPUBLICKEY");
m_reslbl[IDS_FNET_AUTHNOMEM] = _("IDS_FNET_AUTHNOMEM"); m_reslbl[IDS_FNET_AUTHNOMEM] = _("IDS_FNET_AUTHNOMEM");
m_reslbl[IDS_CD_NEEDCDKEY] = sprintf(_("IDS_CD_NEEDCDKEY"), games[gameinfo_current].game); m_reslbl[IDS_CD_NEEDCDKEY] = sprintf(_("IDS_CD_NEEDCDKEY"), gameTitle);
m_reslbl[IDS_CD_BADKEY] = _("IDS_CD_BADKEY"); m_reslbl[IDS_CD_BADKEY] = _("IDS_CD_BADKEY");
m_reslbl[IDS_CD_BADKEYTYPED] = _("IDS_CD_BADKEYTYPED"); m_reslbl[IDS_CD_BADKEYTYPED] = _("IDS_CD_BADKEYTYPED");
m_reslbl[IDS_MD5_HASHFAIL] = _("IDS_MD5_HASHFAIL"); m_reslbl[IDS_MD5_HASHFAIL] = _("IDS_MD5_HASHFAIL");
@ -372,7 +373,7 @@ Strings_Init(void)
m_reslbl[IDS_CHAT_NOROOMLIST] = _("IDS_CHAT_NOROOMLIST"); m_reslbl[IDS_CHAT_NOROOMLIST] = _("IDS_CHAT_NOROOMLIST");
m_reslbl[IDS_SAVELOAD_NUMBEROFGAMESCHANGED] = _("IDS_SAVELOAD_NUMBEROFGAMESCHANGED"); m_reslbl[IDS_SAVELOAD_NUMBEROFGAMESCHANGED] = _("IDS_SAVELOAD_NUMBEROFGAMESCHANGED");
m_reslbl[IDS_MAIN_EXITMULTIPLAYERPROMPT] = _("IDS_MAIN_EXITMULTIPLAYERPROMPT"); m_reslbl[IDS_MAIN_EXITMULTIPLAYERPROMPT] = _("IDS_MAIN_EXITMULTIPLAYERPROMPT");
m_reslbl[IDS_CD_ENTERPROMPT] = sprintf(_("IDS_CD_ENTERPROMPT"), games[gameinfo_current].game); m_reslbl[IDS_CD_ENTERPROMPT] = sprintf(_("IDS_CD_ENTERPROMPT"), gameTitle);
m_reslbl[IDS_MULTISELECT_EXITGAMEPROMPT] = _("IDS_MULTISELECT_EXITGAMEPROMPT"); m_reslbl[IDS_MULTISELECT_EXITGAMEPROMPT] = _("IDS_MULTISELECT_EXITGAMEPROMPT");
m_reslbl[IDS_MULTI_ADDSERVERPROMPT] = _("IDS_MULTI_ADDSERVERPROMPT"); m_reslbl[IDS_MULTI_ADDSERVERPROMPT] = _("IDS_MULTI_ADDSERVERPROMPT");
m_reslbl[IDS_ROOM_NEEDPASS] = _("IDS_ROOM_NEEDPASS"); m_reslbl[IDS_ROOM_NEEDPASS] = _("IDS_ROOM_NEEDPASS");
@ -416,7 +417,7 @@ Strings_Init(void)
m_reslbl[IDS_GORE_BADPW] = _("IDS_GORE_BADPW"); m_reslbl[IDS_GORE_BADPW] = _("IDS_GORE_BADPW");
m_reslbl[IDS_BTN_ADVANCEDSVR] = _("IDS_BTN_ADVANCEDSVR"); m_reslbl[IDS_BTN_ADVANCEDSVR] = _("IDS_BTN_ADVANCEDSVR");
m_reslbl[IDS_OPTS_AUTOAIM] = _("IDS_OPTS_AUTOAIM"); m_reslbl[IDS_OPTS_AUTOAIM] = _("IDS_OPTS_AUTOAIM");
m_reslbl[IDS_OPTS_AUTOAIMHELP] = sprintf(_("IDS_OPTS_AUTOAIMHELP"), games[gameinfo_current].game); m_reslbl[IDS_OPTS_AUTOAIMHELP] = sprintf(_("IDS_OPTS_AUTOAIMHELP"), gameTitle);
m_reslbl[IDS_BTN_PREVIEWS] = _("IDS_BTN_PREVIEWS"); m_reslbl[IDS_BTN_PREVIEWS] = _("IDS_BTN_PREVIEWS");
m_reslbl[IDS_MAIN_PREVIEWSHELP] = _("IDS_MAIN_PREVIEWSHELP"); m_reslbl[IDS_MAIN_PREVIEWSHELP] = _("IDS_MAIN_PREVIEWSHELP");
m_reslbl[IDS_SECONDS_LEFT] = _("IDS_SECONDS_LEFT"); m_reslbl[IDS_SECONDS_LEFT] = _("IDS_SECONDS_LEFT");
@ -452,8 +453,8 @@ Strings_Init(void)
m_reslbl[IDS_VID_RESELECT] = _("IDS_VID_RESELECT"); m_reslbl[IDS_VID_RESELECT] = _("IDS_VID_RESELECT");
m_reslbl[IDS_VID_INITFAIL] = _("IDS_VID_INITFAIL"); m_reslbl[IDS_VID_INITFAIL] = _("IDS_VID_INITFAIL");
m_reslbl[IDS_NET_FNETCONNFAIL] = _("IDS_NET_FNETCONNFAIL"); m_reslbl[IDS_NET_FNETCONNFAIL] = _("IDS_NET_FNETCONNFAIL");
m_reslbl[IDS_NET_CORRUPT] = sprintf(_("IDS_NET_CORRUPT"), games[gameinfo_current].game); m_reslbl[IDS_NET_CORRUPT] = sprintf(_("IDS_NET_CORRUPT"), gameTitle);
m_reslbl[IDS_CDKEY_BAD] = sprintf(_("IDS_CDKEY_BAD"), games[gameinfo_current].game); m_reslbl[IDS_CDKEY_BAD] = sprintf(_("IDS_CDKEY_BAD"), gameTitle);
m_reslbl[IDS_MULTI_REFRESH] = _("IDS_MULTI_REFRESH"); m_reslbl[IDS_MULTI_REFRESH] = _("IDS_MULTI_REFRESH");
m_reslbl[IDS_CHAT_NOROOM] = _("IDS_CHAT_NOROOM"); m_reslbl[IDS_CHAT_NOROOM] = _("IDS_CHAT_NOROOM");
m_reslbl[IDS_RUN_PATCH] = _("IDS_RUN_PATCH"); m_reslbl[IDS_RUN_PATCH] = _("IDS_RUN_PATCH");
@ -464,7 +465,7 @@ Strings_Init(void)
m_reslbl[IDS_LOGO_POWEROF2] = _("IDS_LOGO_POWEROF2"); m_reslbl[IDS_LOGO_POWEROF2] = _("IDS_LOGO_POWEROF2");
m_reslbl[IDS_MODEM_CUSTOM] = _("IDS_MODEM_CUSTOM"); m_reslbl[IDS_MODEM_CUSTOM] = _("IDS_MODEM_CUSTOM");
m_reslbl[IDS_MODEM_RATE] = _("IDS_MODEM_RATE"); m_reslbl[IDS_MODEM_RATE] = _("IDS_MODEM_RATE");
m_reslbl[IDS_REGISTRY_UPDATE] = sprintf(_("IDS_REGISTRY_UPDATE"), games[gameinfo_current].game); m_reslbl[IDS_REGISTRY_UPDATE] = sprintf(_("IDS_REGISTRY_UPDATE"), gameTitle);
m_reslbl[IDS_CHAT_JOIN] = _("IDS_CHAT_JOIN"); m_reslbl[IDS_CHAT_JOIN] = _("IDS_CHAT_JOIN");
m_reslbl[IDS_CHAT_FLOOD] = _("IDS_CHAT_FLOOD"); m_reslbl[IDS_CHAT_FLOOD] = _("IDS_CHAT_FLOOD");
m_reslbl[IDS_CHAT_SEARCH] = _("IDS_CHAT_SEARCH"); m_reslbl[IDS_CHAT_SEARCH] = _("IDS_CHAT_SEARCH");
@ -476,21 +477,21 @@ Strings_Init(void)
m_reslbl[IDS_CHAT_NOSUCHROOM] = _("IDS_CHAT_NOSUCHROOM"); m_reslbl[IDS_CHAT_NOSUCHROOM] = _("IDS_CHAT_NOSUCHROOM");
m_reslbl[IDS_CHAT_ROOMFULL] = _("IDS_CHAT_ROOMFULL"); m_reslbl[IDS_CHAT_ROOMFULL] = _("IDS_CHAT_ROOMFULL");
m_reslbl[IDS_CREATESV_NOADVANCED] = _("IDS_CREATESV_NOADVANCED"); m_reslbl[IDS_CREATESV_NOADVANCED] = _("IDS_CREATESV_NOADVANCED");
m_reslbl[IDS_FNET_CDINUSE] = sprintf(_("IDS_FNET_CDINUSE"), games[gameinfo_current].game); m_reslbl[IDS_FNET_CDINUSE] = sprintf(_("IDS_FNET_CDINUSE"), gameTitle);
m_reslbl[IDS_CHAT_JOINHINT] = _("IDS_CHAT_JOINHINT"); m_reslbl[IDS_CHAT_JOINHINT] = _("IDS_CHAT_JOINHINT");
m_reslbl[IDS_FNET_BANNED] = _("IDS_FNET_BANNED"); m_reslbl[IDS_FNET_BANNED] = _("IDS_FNET_BANNED");
m_reslbl[IDS_CONTENT_NOMULTIPLAYER] = _("IDS_CONTENT_NOMULTIPLAYER"); m_reslbl[IDS_CONTENT_NOMULTIPLAYER] = _("IDS_CONTENT_NOMULTIPLAYER");
m_reslbl[IDS_PATCH_ERROR] = sprintf(_("IDS_PATCH_ERROR"), games[gameinfo_current].game); m_reslbl[IDS_PATCH_ERROR] = sprintf(_("IDS_PATCH_ERROR"), gameTitle);
m_reslbl[IDS_PATCH_NOUTIL] = _("IDS_PATCH_NOUTIL"); m_reslbl[IDS_PATCH_NOUTIL] = _("IDS_PATCH_NOUTIL");
m_reslbl[IDS_PATCH_FAIL] = _("IDS_PATCH_FAIL"); m_reslbl[IDS_PATCH_FAIL] = _("IDS_PATCH_FAIL");
m_reslbl[IDS_PATCH_BADINSTALL] = sprintf(_("IDS_PATCH_BADINSTALL"), games[gameinfo_current].game); m_reslbl[IDS_PATCH_BADINSTALL] = sprintf(_("IDS_PATCH_BADINSTALL"), gameTitle);
m_reslbl[IDS_CONNECT_FAILURE] = _("IDS_CONNECT_FAILURE"); m_reslbl[IDS_CONNECT_FAILURE] = _("IDS_CONNECT_FAILURE");
m_reslbl[IDS_MULTI_NEEDPASSWORD] = _("IDS_MULTI_NEEDPASSWORD"); m_reslbl[IDS_MULTI_NEEDPASSWORD] = _("IDS_MULTI_NEEDPASSWORD");
m_reslbl[IDS_VID_HINT] = _("IDS_VID_HINT"); m_reslbl[IDS_VID_HINT] = _("IDS_VID_HINT");
m_reslbl[IDS_MULTI_LOGODISCONNECT] = _("IDS_MULTI_LOGODISCONNECT"); m_reslbl[IDS_MULTI_LOGODISCONNECT] = _("IDS_MULTI_LOGODISCONNECT");
m_reslbl[IDS_3D_WARNING] = sprintf(_("IDS_3D_WARNING"), games[gameinfo_current].game); m_reslbl[IDS_3D_WARNING] = sprintf(_("IDS_3D_WARNING"), gameTitle);
m_reslbl[IDS_3DSITE_URL] = _("IDS_3DSITE_URL"); m_reslbl[IDS_3DSITE_URL] = _("IDS_3DSITE_URL");
m_reslbl[IDS_FNET_MODIFIED] = sprintf(_("IDS_FNET_MODIFIED"), games[gameinfo_current].game); m_reslbl[IDS_FNET_MODIFIED] = sprintf(_("IDS_FNET_MODIFIED"), gameTitle);
m_reslbl[IDS_ADVANCEDMP_OFFSETS] = _("IDS_ADVANCEDMP_OFFSETS"); m_reslbl[IDS_ADVANCEDMP_OFFSETS] = _("IDS_ADVANCEDMP_OFFSETS");
m_reslbl[IDS_AUDIO_OFFSET] = _("IDS_AUDIO_OFFSET"); m_reslbl[IDS_AUDIO_OFFSET] = _("IDS_AUDIO_OFFSET");
m_reslbl[IDS_CONFIGURE_OFFSET] = _("IDS_CONFIGURE_OFFSET"); m_reslbl[IDS_CONFIGURE_OFFSET] = _("IDS_CONFIGURE_OFFSET");
@ -559,7 +560,7 @@ Strings_Init(void)
m_reslbl[IDS_NO] = _("IDS_NO"); m_reslbl[IDS_NO] = _("IDS_NO");
m_reslbl[IDS_UPDATE] = _("IDS_UPDATE"); m_reslbl[IDS_UPDATE] = _("IDS_UPDATE");
m_reslbl[IDS_MODREQ_TITLE] = _("IDS_MODREQ_TITLE"); m_reslbl[IDS_MODREQ_TITLE] = _("IDS_MODREQ_TITLE");
m_reslbl[IDS_DOWNLOAD_WARNING] = sprintf(_("IDS_DOWNLOAD_WARNING"), games[gameinfo_current].game); m_reslbl[IDS_DOWNLOAD_WARNING] = sprintf(_("IDS_DOWNLOAD_WARNING"), gameTitle);
m_reslbl[IDS_WARN_CHECKPROMPT] = _("IDS_WARN_CHECKPROMPT"); m_reslbl[IDS_WARN_CHECKPROMPT] = _("IDS_WARN_CHECKPROMPT");
m_reslbl[IDS_MOD_VERSION] = _("IDS_MOD_VERSION"); m_reslbl[IDS_MOD_VERSION] = _("IDS_MOD_VERSION");
m_reslbl[IDS_MOD_REINSTALL] = _("IDS_MOD_REINSTALL"); m_reslbl[IDS_MOD_REINSTALL] = _("IDS_MOD_REINSTALL");

View file

@ -55,15 +55,29 @@ CModList::Draw(void)
{ {
int visible; int visible;
int pos; int pos;
string gameType;
string gameTitle;
string gameURL;
int gameSize;
string gameVersion;
bool gameInstalled;
drawfill([g_menuofs[0] + m_x, g_menuofs[1] + m_y], [m_size[0], m_size[1]], drawfill([g_menuofs[0] + m_x, g_menuofs[1] + m_y], [m_size[0], m_size[1]],
[0,0,0], 1.0f); [0,0,0], 1.0f);
visible = floor(m_size[1] / 29); visible = floor(m_size[1] / 29);
visible = bound(0, visible, gameinfo_count - 1); visible = bound(0, visible, GameLibrary_GetGameCount() - 1);
pos = m_y; pos = m_y;
for (int i = m_scroll; i <= (visible + m_scroll); i++) { for (int i = m_scroll; i <= (visible + m_scroll); i++) {
vector colo; vector colo;
gameType = GameLibrary_GetGameInfo(i, GAMEINFO_TYPE);
gameTitle = GameLibrary_GetGameInfo(i, GAMEINFO_TITLE);
gameURL = GameLibrary_GetGameInfo(i, GAMEINFO_WEBSITE);
gameSize = GameLibrary_GetGameInfo(i, GAMEINFO_SIZE);
gameVersion = GameLibrary_GetGameInfo(i, GAMEINFO_VERSION);
gameInstalled = GameLibrary_GetGameInfo(i, GAMEINFO_INSTALLED);
if (m_selected == i) { if (m_selected == i) {
colo = ML_COL_2; colo = ML_COL_2;
drawfill([g_menuofs[0] + m_x, g_menuofs[1] + pos], [m_size[0], 29], drawfill([g_menuofs[0] + m_x, g_menuofs[1] + pos], [m_size[0], 29],
@ -72,33 +86,33 @@ CModList::Draw(void)
colo = ML_COL_1; colo = ML_COL_1;
} }
if (games[i].type != "") { if (gameType!= "") {
drawsetcliparea(g_menuofs[0] + m_x + 2, g_menuofs[1] + pos + 3, 50,30); drawsetcliparea(g_menuofs[0] + m_x + 2, g_menuofs[1] + pos + 3, 50,30);
WLabel_Static(m_x + 2, pos + 3, games[i].type, WLabel_Static(m_x + 2, pos + 3, gameType,
11, 11, colo, 1.0f, 0, font_arial); 11, 11, colo, 1.0f, 0, font_arial);
drawresetcliparea(); drawresetcliparea();
} }
/* Game */ /* Game */
drawsetcliparea(g_menuofs[0] + m_x + 57, g_menuofs[1] + pos + 3, 112,30); drawsetcliparea(g_menuofs[0] + m_x + 57, g_menuofs[1] + pos + 3, 112,30);
WLabel_Static(m_x + 57, pos + 3, games[i].game, 11, 11, colo, WLabel_Static(m_x + 57, pos + 3, gameTitle, 11, 11, colo,
1.0f, 0, font_arial); 1.0f, 0, font_arial);
drawresetcliparea(); drawresetcliparea();
/* URL */ /* URL */
WLabel_Static(m_x + 2, pos + 18, sprintf("Info: %s", games[i].url_info), 11, 11, ML_COL_4, WLabel_Static(m_x + 2, pos + 18, sprintf("Info: %s", gameURL), 11, 11, ML_COL_4,
1.0f, 0, font_arial); 1.0f, 0, font_arial);
/* Version */ /* Version */
WLabel_Static(m_x + 177, pos + 3, games[i].version, 11, 11, colo, WLabel_Static(m_x + 177, pos + 3, gameVersion, 11, 11, colo,
1.0f, 0, font_arial); 1.0f, 0, font_arial);
/* Size */ /* Size */
float size = games[i].size / 1024000; float size = gameSize / 1024000;
WLabel_Static(m_x + 227, pos + 3, sprintf("%.1fmb", size), 11, 11, colo, WLabel_Static(m_x + 227, pos + 3, sprintf("%.1fmb", size), 11, 11, colo,
1.0f, 0, font_arial); 1.0f, 0, font_arial);
/* Rating */ /* Rating */
WLabel_Static(m_x + 277, pos + 3, "0.0", 11, 11, colo, WLabel_Static(m_x + 277, pos + 3, "0.0", 11, 11, colo,
1.0f, 0, font_arial); 1.0f, 0, font_arial);
if (games[i].installed == 1) { if (gameInstalled == true) {
/* Installed */ /* Installed */
WLabel_Static(m_x + 327, pos + 3, "Yes", 11, 11, ML_COL_3, WLabel_Static(m_x + 327, pos + 3, "Yes", 11, 11, ML_COL_3,
1.0f, 0, font_arial); 1.0f, 0, font_arial);
@ -131,7 +145,7 @@ CModList::Input(float type, float x, float y, float devid)
int pos[2]; int pos[2];
visible = floor(m_size[1] / 29); visible = floor(m_size[1] / 29);
visible = bound(0, visible, gameinfo_count - 1); visible = bound(0, visible, GameLibrary_GetGameCount() - 1);
pos[0] = m_x; pos[0] = m_x;
pos[1] = m_y; pos[1] = m_y;

View file

@ -66,6 +66,9 @@ CUpdateList::Draw(void)
for (int i = m_scroll; i < (visible + m_scroll); i++) { for (int i = m_scroll; i < (visible + m_scroll); i++) {
vector colo; vector colo;
string updateTitle = Updates_GetInfo(i, UPDATE_TITLE);
updateState_t updateState = Updates_GetInfo(i, UPDATE_STATE);
updateAction_t updateAction = Updates_GetInfo(i, UPDATE_ACTION);
if (m_selected == i) { if (m_selected == i) {
colo = ML_COL_2; colo = ML_COL_2;
@ -75,60 +78,55 @@ CUpdateList::Draw(void)
colo = ML_COL_1; colo = ML_COL_1;
} }
int uid = updates[i].uid;
string status = Updates_GetInfo(uid, GPMI_INSTALLED);
switch (status) { switch (updateState) {
case "": case UPDATESTATE_ENABLED:
if (updates[i].installed == "") { colo = ML_COL_1;
colo = [1,0,0];
} else if (updates[i].installed == "pending") {
colo = [0,1,0];
} else if (updates[i].installed == "enabled") {
colo = colo;
}
break; break;
case "pending": case UPDATESTATE_CORRUPT:
colo[0] *= 0.5; colo = [1, 0, 0]; /* red */
colo[1] *= 0.5;
colo[2] *= 0.5;
break; break;
case "enabled": case UPDATESTATE_PENDING:
colo = colo; float p = Updates_GetInfo(i, UPDATE_DLPERCENTAGE) / 100;
break;
case "present":
colo[0] *= 0.5;
colo[1] *= 0.5;
colo[2] *= 0.5;
break;
case "corrupt":
colo = [1,0,0] * sin(time);
break;
default:
float p = stof(status) / 100;
colo = [0,1,0] * p; colo = [0,1,0] * p;
drawfill([g_menuofs[0] + m_x, g_menuofs[1] + pos], [m_size[0] * p, 18], drawfill([g_menuofs[0] + m_x, g_menuofs[1] + pos], [m_size[0] * p, 18],
colo, 0.5f); colo, 0.5f);
colo = [0.25,0.25,0.25] + ([0.75,0.75,0.75] * p); colo = [0.25,0.25,0.25] + ([0.75,0.75,0.75] * p);
break; break;
case UPDATESTATE_DISABLED:
case UPDATESTATE_NONE:
colo = [0.5, 0.5, 0.5]; /* grey */
default:
break;
} }
/* TODO: make this integrate with the above better */ if ((time*2) & 1) {
if (updates[i].installed == "rem") { switch (updateAction) {
if ((time*2) & 1) case UPDATEACTION_INSTALL: /* blinking orange */
colo = [1,0,0];
else
colo = [0,0,0];
} else if (updates[i].installed == "in") {
if ((time*2) & 1)
colo = [1,1,0]; colo = [1,1,0];
else break;
colo = [0,0,0]; case UPDATEACTION_REINSTALL:
colo = [0,1,0];
break;
case UPDATEACTION_UNINSTALL: /* blinking red */
colo = [1,0,0];
break;
case UPDATEACTION_AUTOINSTALL: /* blinking orange/grey */
colo = [0.5,0.5,0];
break;
case UPDATEACTION_DISABLE: /* blinking grey/color */
colo = [0.5,0.5,0.5];
break;
case UPDATEACTION_RETAIN:
case UPDATEACTION_NONE:
default:
break;
}
} }
/* Game */ /* Game */
WLabel_Static(m_x + 3, pos + 3, updates[i].title, 11, 11, colo, WLabel_Static(m_x + 3, pos + 3, updateTitle, 11, 11, colo,
1.0f, 0, font_arial); 1.0f, 0, font_arial);
pos += 18; pos += 18;

View file

@ -23,4 +23,22 @@
#include "servers.h" #include "servers.h"
#include "tcp.h" #include "tcp.h"
#include "updates.h" #include "updates.h"
#include "gamelibrary.h" #include "gamelibrary.h"
#include "maplibrary.h"
/** Definitions for FTE's internal package manager. We don't want you to talk to this one directly within Nuclide. */
typedef enum
{
GPMI_NAME, /**< name of the package, for use with the pkg command. */
GPMI_CATEGORY, /**< category text */
GPMI_TITLE, /**< name of the package, for showing the user. */
GPMI_VERSION, /**< version info (may have multiple with the same name but different versions) */
GPMI_DESCRIPTION, /**< some blurb */
GPMI_LICENSE, /**< what license its distributed under */
GPMI_AUTHOR, /**< name of the person(s) who created it */
GPMI_WEBSITE, /**< where to contribute/find out more info/etc */
GPMI_INSTALLED, /**< current state */
GPMI_ACTION, /**< desired state */
GPMI_AVAILABLE, /**< whether it may be downloaded or not. */
GPMI_FILESIZE, /**< size to download. */
} packageType_t;

View file

@ -14,6 +14,65 @@
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/ */
/*! @file gamelibrary.h
@brief Game library parsing and querying of metadata.
The GameLibrary concerns itself with everything around what a game is,
how to install, activate and deactivate it. Mods are included in this,
so we'll proceed calling them 'games' or 'custom games'.
A game can be installed through two primary means:
- Manual install, like from a .zip or some installer or archive
- Engine package manager install, through our own user interface
And between these, they can come with different metadata/manifests.
It assumed that every game has either a FTE Manifest description,
a gameinfo.txt (Source Engine format) or liblist.gam (GoldSrc format)
that describes various aspects of the game. Like which version it is, what
map will be loaded when you press 'New Game' and so on.
If that info is not available, some placeholder data will be used instead.
However, games installed via the package manager will at least for the
custom game menus not use the on-disk manifest file, but information
provided by the package manager. Once you switch into said game everything
within will be pulled from a file on disk, such as a liblist.gam or gameinfo.txt.
The menu needs to call GameLibrary_Init() once for parsing the currently running
game its own metadata. If you want to index custom games, aka mods, you need
to do so with GameLibrary_InitCustom() afterwards.
Because indexing mods can take a very long time depending on the amount
that is stored on disk, you may want to call GameLibrary_InitCustom() at
a later time.
*/
typedef enum
{
GAMEINFO_TITLE, /**< (string) The title of the game. E.g. "Action Game" */
GAMEINFO_GAMEDIR, /**< (string) The game directory name. E.g. "data" */
GAMEINFO_FALLBACKDIR, /**< (string) The directory to be loaded before the game directory. */
GAMEINFO_BASEDIR, /**< (string) The first game directory to be loaded. */
GAMEINFO_WEBSITE, /**< (string) The game its official website. */
GAMEINFO_VERSION, /**< (string) Version number string. */
GAMEINFO_SIZE, /**< (int) The size of the game, in bytes. */
GAMEINFO_TYPE, /**< (string) The game type. E.g. "Singleplayer" */
GAMEINFO_NOPLAYERMODELS,/**< (bool) If the game allows player model selection. */
GAMEINFO_NOSPRAYS, /**< (bool) If the game allows custom spray logos. */
GAMEINFO_STARTMAP, /**< (string) The command for starting a new game. */
GAMEINFO_TRAININGMAP, /**< (string) The command for starting the training. */
GAMEINFO_MINVERSION, /**< (string) The minimum base game version. */
GAMEINFO_CHATROOM, /**< (string) The chatroom for this game. E.g. #action */
GAMEINFO_READMEFILE, /**< (string) File name of the readme documentation. */
GAMEINFO_INTROVIDEO, /**< (string) File name of the intro video to play. */
GAMEINFO_MENUMAP, /**< (string) Name of the map to be used as a background. */
GAMEINFO_AUTHOR, /**< (string) Name of the author. */
GAMEINFO_AUTHORSITE, /**< (string) The author their website. */
GAMEINFO_PACKAGELIST, /**< (string) List of packages, separated by white-space. */
GAMEINFO_INSTALLED, /**< (bool) Whether the game is installed. */
} gameInfo_t;
/** Called when initializing the current game. Does not cache custom game/mod info. */ /** Called when initializing the current game. Does not cache custom game/mod info. */
void GameLibrary_Init(void); void GameLibrary_Init(void);
/** Called when you want to initialize custom games/mods. Might want to call this later /** Called when you want to initialize custom games/mods. Might want to call this later
@ -30,4 +89,57 @@ void GameLibrary_Deactivate(void);
/** Returns true/false depending on if a Game installation is in progress. */ /** Returns true/false depending on if a Game installation is in progress. */
bool GameLibrary_IsInstalling(void); bool GameLibrary_IsInstalling(void);
/** Returns a 0-100% value of game install progress, tracking across multiple packages. */ /** Returns a 0-100% value of game install progress, tracking across multiple packages. */
float GameLibrary_InstallProgress(void); float GameLibrary_InstallProgress(void);
/** Returns the total amount of games currently available. */
int GameLibrary_GetGameCount(void);
/** Return the ID for the currently active game. */
int GameLibrary_GetCurrentGame(void);
/** Retrieves fields for a given game. See gameInfo_t for a list of fields you can query. */
__variant GameLibrary_GetGameInfo(int, gameInfo_t);
/** Retrieves fields for the currently running game. See gameInfo_t for a list of fields you can query. */
__variant GameLibrary_GetInfo(gameInfo_t);
typedef enum
{
GAMEINFO_NONE, /**< No gameinfo available. This is probably the engine making assumptions. */
GAMEINFO_MANIFEST, /**< Game info was read from a manifest within the path. */
GAMEINFO_GITXT, /**< Game info stems from a Source Engine style gameinfo.txt file. */
GAMEINFO_LIBLIST, /**< Game info stems from a GoldSrc style liblist.gam file. */
GAMEINFO_PACKAGE,
} gi_type;
#ifndef DOXYGEN
typedef struct
{
string game;
string gamedir;
string base_dir;
string url_info;
string url_dl;
string version;
string readme;
int size;
int svonly;
int cldll;
string type;
string minversion;
string pkgname;
string pkgfile;
int pkgid;
int nomodels;
int nosprays;
int installed;
string mpentity;
string gamedll;
string startmap;
string trainingmap;
string fallback_dir;
string chatroom;
string menumap;
string introvideo;
gi_type info_type;
} gameinfo_t;
int gameinfo_count;
gameinfo_t *games;
#endif

View file

@ -17,6 +17,8 @@
int g_iModInstallCache; int g_iModInstallCache;
string g_strModInstallCache; string g_strModInstallCache;
var int gameinfo_current = -1;
/* local game/mod info parsing */ /* local game/mod info parsing */
static void static void
GameLibrary_Set(int id) GameLibrary_Set(int id)
@ -26,6 +28,27 @@ GameLibrary_Set(int id)
cvar_set("com_fullgamename", games[id].game); cvar_set("com_fullgamename", games[id].game);
} }
static int
GameLibrary_IDForPackageName(string packageName)
{
string f;
for (int i = 0; (getpackagemanagerinfo(i, GPMI_NAME)); i++) {
string name;
name = getpackagemanagerinfo(i, GPMI_NAME);
/* Spike started randomly putting version numbers into package names */
f = sprintf("%s=%s", packageName, getpackagemanagerinfo(i, GPMI_VERSION));
if (name == f) {
return i;
}
}
/* no package id whatsoever */
return (-1i);
}
/** Looks for a single file inside a gamedir, including its pk3s and returns a valid filehandle if it is found. */ /** Looks for a single file inside a gamedir, including its pk3s and returns a valid filehandle if it is found. */
static filestream static filestream
GameLibrary_FindInGameDir(string filename, string gamedirname) GameLibrary_FindInGameDir(string filename, string gamedirname)
@ -162,8 +185,8 @@ GameLibrary_LibListParse(int id, string strKey, string strValue)
} }
break; break;
case "minversion": case "minversion":
case "hlversion": case "minversion":
games[id].hlversion = strValue; games[id].minversion = strValue;
break; break;
case "nomodels": case "nomodels":
games[id].nomodels = (int)stof(strValue); games[id].nomodels = (int)stof(strValue);
@ -186,7 +209,7 @@ GameLibrary_LibListParse(int id, string strKey, string strValue)
/* newly added with Nuclide */ /* newly added with Nuclide */
case "pkgname": case "pkgname":
games[id].pkgname = strValue; games[id].pkgname = strValue;
games[id].pkgid = Updates_IDForName(games[id].pkgname); games[id].pkgid = GameLibrary_IDForPackageName(games[id].pkgname);
break; break;
case "pkgfile": case "pkgfile":
games[id].pkgfile = strValue; games[id].pkgfile = strValue;
@ -203,9 +226,6 @@ GameLibrary_LibListParse(int id, string strKey, string strValue)
case "introvideo": case "introvideo":
games[id].introvideo = strValue; games[id].introvideo = strValue;
break; break;
case "steambg":
games[id].steambg = (int)stof(strValue);
break;
case "base_dir": case "base_dir":
games[id].base_dir = strValue; games[id].base_dir = strValue;
break; break;
@ -377,13 +397,12 @@ GameLibrary_SetDefaults(int id, string gamedirname)
games[id].startmap = "map c0a0\n"; games[id].startmap = "map c0a0\n";
games[id].trainingmap = "map t0a0\n"; games[id].trainingmap = "map t0a0\n";
games[id].cldll = 1; games[id].cldll = 1;
games[id].hlversion = "1000"; games[id].minversion = "1000";
games[id].svonly = 0; games[id].svonly = 0;
games[id].installed = 1; games[id].installed = 1;
games[id].chatroom = gamedirname; games[id].chatroom = gamedirname;
games[id].readme = "readme.txt"; games[id].readme = "readme.txt";
games[id].pkgid = -1; games[id].pkgid = -1;
games[id].steambg = 0;
#if 0 #if 0
if (games[id].gamedir == "valve") { if (games[id].gamedir == "valve") {
@ -393,21 +412,61 @@ GameLibrary_SetDefaults(int id, string gamedirname)
#endif #endif
} }
/** Checks if a given game directory was installed manually. */
static bool
GameLibrary_CheckLocalPresence(string gameDir)
{
string testPkgDir = __NULL__;
bool returnSuccess = true;
for (int x = 0i; (testPkgDir = getgamedirinfo(x, 0)); x++) {
if (gameDir == testPkgDir) {
return true;
}
}
return false;
}
void void
GameLibrary_InitCustom(void) GameLibrary_InitCustom(void)
{ {
int id; int id;
int foundself = 0; int foundself = 0;
string gamedirname; string gamedirname = __NULL__;
gameinfo_count = 0; gameinfo_count = 0;
int packageinfo_count = 0i;
int c = 0i;
/* first count let's all manually installed mods */
for (id = 0; (gamedirname = getgamedirinfo(id, 0)); id++) { for (id = 0; (gamedirname = getgamedirinfo(id, 0)); id++) {
gameinfo_count++; gameinfo_count++;
} }
/* count the package installed mods after */
for (int i = 0; (getpackagemanagerinfo(i, GPMI_NAME)); i++) {
string packageName = getpackagemanagerinfo(i, GPMI_NAME);
string installStatus = getpackagemanagerinfo(i, GPMI_INSTALLED);
string prefix = substring(packageName, 0, 3);
/* only care about installed mods (custom games) */
if (prefix == "cg_" && installStatus == "enabled") {
string gameDir = substring(packageName, 3, -1);
tokenizebyseparator(gameDir, "=");
gameDir = argv(0);
/* check if this mod was installed manually already */
if (GameLibrary_CheckLocalPresence(gameDir) == true) {
continue;
}
packageinfo_count++;
}
}
/* re-allocate the game list */ /* re-allocate the game list */
memfree(games); memfree(games);
games = memalloc(sizeof(gameinfo_t) * gameinfo_count); games = memalloc(sizeof(gameinfo_t) * (gameinfo_count + packageinfo_count));
/* The things we do for frequent flyer mileage. */ /* The things we do for frequent flyer mileage. */
if (!games) if (!games)
@ -431,14 +490,56 @@ GameLibrary_InitCustom(void)
NSLog("[MENU] Found nothing for %s", gamedirname); NSLog("[MENU] Found nothing for %s", gamedirname);
games[id].info_type = GAMEINFO_NONE; games[id].info_type = GAMEINFO_NONE;
} }
c = id + 1;
} }
/* iterate through all packages again */
for (int i = 0i; (getpackagemanagerinfo(i, GPMI_NAME)); i++) {
string packageName = getpackagemanagerinfo(i, GPMI_NAME);
string installStatus = getpackagemanagerinfo(i, GPMI_INSTALLED);
string prefix = substring(packageName, 0, 3);
/* same check as above in the counter */
if (prefix == "cg_" && installStatus == "enabled") {
string gameDir = substring(packageName, 3, -1);
tokenizebyseparator(gameDir, "=");
gameDir = argv(0);
if (GameLibrary_CheckLocalPresence(gameDir) == true) {
continue;
}
string titleString = getpackagemanagerinfo(i, GPMI_TITLE);
string versionString = getpackagemanagerinfo(i, GPMI_VERSION);
string authorString = getpackagemanagerinfo(i, GPMI_AUTHOR);
string sizeString = getpackagemanagerinfo(i, GPMI_FILESIZE);
string websiteString = getpackagemanagerinfo(i, GPMI_WEBSITE);
//print(sprintf("Adding packaged game %S\n", gameDir));
GameLibrary_SetDefaults(c, gameDir);
games[c].game = substring(titleString, 5, -1); /* strip 'Mod: '*/
games[c].url_info = websiteString;
games[c].version = versionString;
games[c].size = (int)stof(sizeString);
games[c].type = "Both";
games[c].info_type = GAMEINFO_PACKAGE;
games[c].pkgname = strcat("cg_", gameDir, ";game_", gameDir, ";");
c++;
}
}
/* now we can pretend that these weren't their own thing */
gameinfo_count += packageinfo_count;
/* we may have some mods, but we're not running any of them. Fatal */ /* we may have some mods, but we're not running any of them. Fatal */
if (gameinfo_current == -1) { if (gameinfo_current == -1) {
print("^1FATAL ERROR: NO LIBLIST.GAM FOR CURRENT MOD FOUND!\n"); print("^1FATAL ERROR: NO LIBLIST.GAM FOR CURRENT MOD FOUND!\n");
crash(); crash();
return; return;
} }
print(sprintf("GameLibrary initialized (%i entries).\n", gameinfo_count));
} }
#endif #endif
@ -460,7 +561,6 @@ GameLibrary_Init(void)
/* only run this when not in web-client mode */ /* only run this when not in web-client mode */
#ifndef WEBMENU #ifndef WEBMENU
if (GameLibrary_CheckManifest(id, gamedirname) == 1) { if (GameLibrary_CheckManifest(id, gamedirname) == 1) {
NSLog("[MENU] Found manifest for %s", gamedirname); NSLog("[MENU] Found manifest for %s", gamedirname);
games[id].info_type = GAMEINFO_MANIFEST; games[id].info_type = GAMEINFO_MANIFEST;
@ -523,8 +623,8 @@ GameLibrary_InstallProgress(void)
int pkgid; int pkgid;
/* package query */ /* package query */
pkgid = Updates_IDForName(argv(i)); pkgid = GameLibrary_IDForPackageName(argv(i));
st = Updates_GetInfo(pkgid, GPMI_INSTALLED); st = getpackagemanagerinfo(pkgid, GPMI_INSTALLED);
/* filter out statuses so we can calculate percentage */ /* filter out statuses so we can calculate percentage */
switch (st) { switch (st) {
@ -557,10 +657,11 @@ static void
GameLibrary_InstallStart(int gameid) GameLibrary_InstallStart(int gameid)
{ {
int count; int count;
count = tokenize(games[gameid].pkgname); count = tokenize(games[gameid].pkgname);
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
int pkgid = Updates_IDForName(argv(i)); int pkgid = GameLibrary_IDForPackageName(argv(i));
localcmd(sprintf("pkg add %s\n", argv(i))); localcmd(sprintf("pkg add %s\n", argv(i)));
print(sprintf("Marking package %s for install.\n", print(sprintf("Marking package %s for install.\n",
argv(i))); argv(i)));
@ -577,7 +678,12 @@ GameLibrary_Install(int gameID)
{ {
string st; string st;
st = Updates_GetInfo(games[gameID].pkgid, GPMI_INSTALLED); if (gameID >= gameinfo_count || gameID < 0i) {
print(sprintf("GameLibrary_Install: Invalid game id %i!\n", gameID));
return;
}
st = getpackagemanagerinfo(games[gameID].pkgid, GPMI_INSTALLED);
print(st); print(st);
print("\n"); print("\n");
@ -595,6 +701,11 @@ GameLibrary_Install(int gameID)
void void
GameLibrary_Activate(int gameID) GameLibrary_Activate(int gameID)
{ {
if (gameID >= gameinfo_count || gameID < 0i) {
print(sprintf("GameLibrary_Activate: Invalid game id %i!\n", gameID));
return;
}
GameLibrary_Set(gameID); GameLibrary_Set(gameID);
if (games[gameID].info_type == GAMEINFO_MANIFEST) if (games[gameID].info_type == GAMEINFO_MANIFEST)
@ -623,4 +734,99 @@ GameLibrary_Deactivate(void)
localcmd("menu_restart\n"); localcmd("menu_restart\n");
localcmd("menu_customgame\n"); localcmd("menu_customgame\n");
localcmd("menu_musicstart\n"); localcmd("menu_musicstart\n");
} }
/** Return the ID for the currently activate game. */
int
GameLibrary_GetCurrentGame(void)
{
return gameinfo_current;
}
int
GameLibrary_GetGameCount(void)
{
return gameinfo_count;
}
__variant
GameLibrary_GetInfo(gameInfo_t infoType)
{
return GameLibrary_GetGameInfo(gameinfo_current, infoType);
}
__variant
GameLibrary_GetGameInfo(int gameID, gameInfo_t infoType)
{
if (gameID >= gameinfo_count || gameID < 0i) {
print(sprintf("GameLibrary_GetGameInfo: Invalid game id %i!\n", gameID));
return __NULL__;
}
switch (infoType) {
case GAMEINFO_TITLE:
return (string)games[gameID].game;
break;
case GAMEINFO_GAMEDIR:
return (string)games[gameID].gamedir;
break;
case GAMEINFO_FALLBACKDIR:
return (string)games[gameID].fallback_dir;
break;
case GAMEINFO_BASEDIR:
return (string)games[gameID].base_dir;
break;
case GAMEINFO_WEBSITE:
return (string)games[gameID].url_info;
break;
case GAMEINFO_VERSION:
return (string)games[gameID].version;
break;
case GAMEINFO_SIZE:
return (int)games[gameID].size;
break;
case GAMEINFO_TYPE:
return (string)games[gameID].type;
break;
case GAMEINFO_NOPLAYERMODELS:
return games[gameID].nomodels == 1 ? true : false;
break;
case GAMEINFO_NOSPRAYS:
return games[gameID].nosprays == 1 ? true : false;
break;
case GAMEINFO_STARTMAP:
return (string)games[gameID].startmap;
break;
case GAMEINFO_TRAININGMAP:
return (string)games[gameID].trainingmap;
break;
case GAMEINFO_MINVERSION:
return (string)games[gameID].minversion;
break;
case GAMEINFO_CHATROOM:
return (string)games[gameID].chatroom;
break;
case GAMEINFO_READMEFILE:
return (string)games[gameID].readme;
break;
case GAMEINFO_INTROVIDEO:
return (string)games[gameID].introvideo;
break;
case GAMEINFO_MENUMAP:
return (string)games[gameID].menumap;
break;
case GAMEINFO_AUTHOR:
return (string)"Unknown";
break;
case GAMEINFO_AUTHORSITE:
return (string)"Unknown";
break;
case GAMEINFO_PACKAGELIST:
return (string)games[gameID].pkgname;
break;
case GAMEINFO_INSTALLED:
return games[gameID].installed == 1 ? true : false;
default:
return __NULL__;
}
}

View file

@ -9,4 +9,5 @@ tcp.qc
util.qc util.qc
updates.qc updates.qc
gamelibrary.qc gamelibrary.qc
maplibrary.qc
#endlist #endlist

47
src/platform/maplibrary.h Normal file
View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2023 Vera Visions LLC.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*! @file maplibrary.h
@brief Map library parsing and querying of metadata.
Menus need to be aware of what levels aka maps are available for modes like
multiplayer. The MapLibrary is by configured to only parse levels from the
current game directory (much like GoldSrc) and optionally can blacklist
maps based on a variety of criteria which may be configurable at some point
in time.
The menu needs to call MapLibrary_Init() at least once, after which you can
query the total amount of maps that are available via MapLibrary_GetMapCount().
You can then iterate over the individual entries with MapLibrary_GetInfo().
*/
typedef enum
{
MAPINFO_NAME, /**< (string) Name of the map. E.g. e1m1 */
MAPINFO_TITLE, /**< (string) Title of the map. E.g. "Abyss of Cake" */
MAPINFO_AUTHOR, /**< (string) Author of the map. E.g. "John Doe" */
MAPINFO_TYPE /**< (string) Type of map.*/
} mapType_t;
/** Initialize the map library, MapLibrary_GetMapCount() will return the amount of maps available. */
void MapLibrary_Init(void);
/** Returns the total amount of maps available. */
int MapLibrary_GetMapCount(void);
/** Retrieve information about a given mapID. See mapType_t for which fields you can query. */
__variant MapLibrary_GetInfo(int, mapType_t);

178
src/platform/maplibrary.qc Normal file
View file

@ -0,0 +1,178 @@
/*
* Copyright (c) 2023 Vera Visions LLC.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
typedef struct
{
string name;
string title;
string author;
string type;
} mapLibrary_t;
mapLibrary_t *g_mapLibrary;
var int g_mapLibrary_count = 0i;
var int map_blacklist_count = 0i;
string *map_blacklist;
static string
MapLibrary_GetMapGamedir(void)
{
string gdir = cvar_string("fs_game");
/* HACK: work around FTEQW's path choice */
if (gdir == "ftehl")
gdir = "valve";
return gdir;
}
static bool
MapLibrary_MapInGameDir(string fileName, string gameDir)
{
bool list = true;
/* only look for maps in the current gamedir, requires SEARCH_FULLPACKAGE */
if (gameDir != MapLibrary_GetMapGamedir()) {
return false;
}
/* ignore test_ prefix maps */
if (substring(fileName, 0, 5) == "test_") {
return false;
}
/* see if any of our blacklisted names match */
for (int b = 0i; b < map_blacklist_count; b++) {
if (fileName == map_blacklist[b]) {
list = false;
break;
}
}
return list;
}
void
MapLibrary_Init(void)
{
int i = 0i;
int c = 0i;
int mapCount = 0i;
string mapFile, mapDir;
searchhandle mapsearch;
string gameDir;
/* already initialized */
if (g_mapLibrary_count != 0i)
return;
g_mapLibrary_count = 0i;
gameDir = MapLibrary_GetMapGamedir();
/* map blacklist code */
filestream fs_blacklist;
fs_blacklist = fopen("scripts/map_blacklist", FILE_READ);
if (fs_blacklist >= 0) {
string temp;
while ((temp = fgets(fs_blacklist))) {
map_blacklist_count++;
}
map_blacklist = memalloc(sizeof(string) * map_blacklist_count);
fseek(fs_blacklist, 0);
while ((temp = fgets(fs_blacklist))) {
map_blacklist[i] = temp;
i++;
}
fclose(fs_blacklist);
}
/* search for all maps in the current PATH */
mapsearch = search_begin("maps/*.bsp", SEARCH_NAMESORT | SEARCH_FULLPACKAGE, TRUE);
mapCount = search_getsize(mapsearch);
/* we now iterate over the search entries to filter results out */
for (i = 0i; i < mapCount; i++) {
mapFile = substring(search_getfilename(mapsearch, i), 5, -1);
/* the strlen(gameDir) is technically wrong, but it'll work anyway because
we only care about maps in the current game directory */
mapDir = substring(search_getpackagename(mapsearch, i), 0, strlen(gameDir));
if (MapLibrary_MapInGameDir(mapFile, mapDir) == true) {
g_mapLibrary_count++;
}
}
search_end(mapsearch);
g_mapLibrary = (mapLibrary_t *)memalloc(sizeof(mapLibrary_t) * g_mapLibrary_count);
/* let's do it again, but this time we'll sort the data into our array */
mapsearch = search_begin("maps/*.bsp", SEARCH_NAMESORT | SEARCH_FULLPACKAGE, TRUE);
mapCount = search_getsize(mapsearch);
for (i = 0i; i < mapCount; i++) {
mapFile = substring(search_getfilename(mapsearch, i), 5, -1);
mapDir = substring(search_getpackagename(mapsearch, i), 0, strlen(gameDir));
if (MapLibrary_MapInGameDir(mapFile, mapDir) == true) {
g_mapLibrary[c].name = mapFile;
g_mapLibrary[c].title = mapFile;
g_mapLibrary[c].author = "Unknown";
g_mapLibrary[c].type = "Unknown";
c++;
}
}
search_end(mapsearch);
print(sprintf("MapLibrary initialized (%i entries).\n", g_mapLibrary_count));
}
int
MapLibrary_GetMapCount(void)
{
return g_mapLibrary_count;
}
__variant
MapLibrary_GetInfo(int mapID, mapType_t infoType)
{
if (mapID >= g_mapLibrary_count || mapID < 0i) {
print(sprintf("MapLibrary_GetInfo: Invalid map id %i!\n", mapID));
return __NULL__;
}
switch (infoType) {
case MAPINFO_NAME:
return g_mapLibrary[mapID].name;
break;
case MAPINFO_TITLE:
return g_mapLibrary[mapID].title;
break;
case MAPINFO_AUTHOR:
return g_mapLibrary[mapID].author;
break;
case MAPINFO_TYPE:
return g_mapLibrary[mapID].type;
break;
default:
return __NULL__;
}
}

View file

@ -15,6 +15,28 @@
*/ */
#ifndef WEBMENU #ifndef WEBMENU
/* the same as GameLibrary_IDForPackageName */
static int
ModServer_IDForPackageName(string packageName)
{
string f;
for (int i = 0; (getpackagemanagerinfo(i, GPMI_NAME)); i++) {
string name;
name = getpackagemanagerinfo(i, GPMI_NAME);
/* Spike started randomly putting version numbers into package names */
f = sprintf("%s=%s", packageName, getpackagemanagerinfo(i, GPMI_VERSION));
if (name == f) {
return i;
}
}
/* no package id whatsoever */
return (-1i);
}
void* void*
memrealloc(__variant *oldptr, int elementsize, int old_num, int new_num) memrealloc(__variant *oldptr, int elementsize, int old_num, int new_num)
{ {
@ -51,7 +73,7 @@ ModServer_ParseList(string data)
for (int x = 0; x < gameinfo_count; x++) { for (int x = 0; x < gameinfo_count; x++) {
/* skip mods we already have. */ /* skip mods we already have. */
if (gamedir == games[x].gamedir) { if (gamedir == GameLibrary_GetGameInfo(x, GAMEINFO_GAMEDIR)) {
skip = 1; skip = 1;
break; break;
} }
@ -104,7 +126,7 @@ ModServer_ParseItem(string data)
games[id].startmap = "c0a0"; games[id].startmap = "c0a0";
games[id].trainingmap = "t0a0"; games[id].trainingmap = "t0a0";
games[id].cldll = 1; games[id].cldll = 1;
games[id].hlversion = "1110"; games[id].minversion = "1110";
games[id].svonly = 0; games[id].svonly = 0;
games[id].installed = 0; games[id].installed = 0;
games[id].pkgid = -1; games[id].pkgid = -1;
@ -141,8 +163,8 @@ ModServer_ParseItem(string data)
case "gameinfo_type": case "gameinfo_type":
games[id].type = argv(i+1); games[id].type = argv(i+1);
break; break;
case "gameinfo_hlversion": case "gameinfo_minversion":
games[id].hlversion = argv(i+1); games[id].minversion = argv(i+1);
break; break;
case "gameinfo_nomodels": case "gameinfo_nomodels":
games[id].nomodels = stof(argv(i+1)); games[id].nomodels = stof(argv(i+1));
@ -161,7 +183,7 @@ ModServer_ParseItem(string data)
break; break;
case "gameinfo_pkgname": case "gameinfo_pkgname":
games[id].pkgname = argv(i+1); games[id].pkgname = argv(i+1);
games[id].pkgid = Updates_IDForName(games[id].pkgname); games[id].pkgid = ModServer_IDForPackageName(games[id].pkgname);
break; break;
default: default:
break; break;
@ -169,6 +191,8 @@ ModServer_ParseItem(string data)
} }
} }
void Updater_URI_Callback(float id, float code, string data, int resourcebytes);
/* Called as an eventual result of the uri_get builtin. */ /* Called as an eventual result of the uri_get builtin. */
void void
ModServer_URI_Callback(float id, float code, string data, int resourcebytes) ModServer_URI_Callback(float id, float code, string data, int resourcebytes)
@ -202,8 +226,7 @@ ModServer_URI_Callback(float id, float code, string data, int resourcebytes)
ModServer_ParseItem(data); ModServer_ParseItem(data);
break; break;
case MODSERVER_REQ_PKGNAMES: case MODSERVER_REQ_PKGNAMES:
games[gameinfo_current].pkgname = data; Updater_URI_Callback(id, code, data, resourcebytes);
g_pkgname_updating = 0;
break; break;
default: default:
print(sprintf("^1ModServer_URI_Callback^7: Unknown request id %d with code %d\n", id, code)); print(sprintf("^1ModServer_URI_Callback^7: Unknown request id %d with code %d\n", id, code));

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2016-2022 Vera Visions LLC. * Copyright (c) 2016-2023 Vera Visions LLC.
* *
* Permission to use, copy, modify, and distribute this software for any * Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@ -14,54 +14,93 @@
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/ */
string(float id, float b) getgamedirinfo = #0; /*! @file updates.h
string(int packageidx, int desiredfield) getpackagemanagerinfo = #0; @brief Game and content update handler functions.
The updater is an abstraction of the engine its own package manager.
Not all packages the engine offers are related to the game you're currently
running. Therefore we offer our own API within Nuclide to deal with game updates.
You want to call Updates_Init() once, after which Updates_GetUpdaterStatus() should be queried to determine when the Updater is ready. That is not necessary, but it might be helpful to throw up a loading screen while that is returning UPDATER_PENDING.
Once Updates_GetUpdaterStatus() reports UPDATER_INITIALIZED, you can expect the loading to be fully done.
The function Updates_GetPackageCount() will report the total amount of update packages available for the game. These are not 'new' updates, they contain every package associated with the game that can and should be installed and kept updated.
Use Updates_GetInfo() to retrieve metadata about individual packages.
*/
/** Different types you can pass to `Updates_GetInfo(...)` to learn details about a given Update entry. */
typedef enum typedef enum
{ {
GPMI_NAME, /**< name of the package, for use with the pkg command. */ UPDATE_NAME, /**< (string) name of the package, for use with the pkg command. */
GPMI_CATEGORY, /**< category text */ UPDATE_CATEGORY, /**< (string) category text */
GPMI_TITLE, /**< name of the package, for showing the user. */ UPDATE_TITLE, /**< (string) name of the package, for showing the user. */
GPMI_VERSION, /**< version info (may have multiple with the same name but different versions) */ UPDATE_VERSION, /**< (string) version info (may have multiple with the same name but different versions) */
GPMI_DESCRIPTION, /**< some blurb */ UPDATE_DESCRIPTION, /**< (string) some blurb */
GPMI_LICENSE, /**< what license its distributed under */ UPDATE_LICENSE, /**< (string) what license its distributed under */
GPMI_AUTHOR, /**< name of the person(s) who created it */ UPDATE_AUTHOR, /**< (string) name of the person(s) who created it */
GPMI_WEBSITE, /**< where to contribute/find out more info/etc */ UPDATE_WEBSITE, /**< (string) where to contribute/find out more info/etc */
GPMI_INSTALLED, /**< current state */ UPDATE_STATE, /**< (updateState_t) The current state of the update. */
GPMI_ACTION, /**< desired state */ UPDATE_ACTION, /**< (updateAction_t) Pending action of the update. */
GPMI_AVAILABLE, /**< whether it may be downloaded or not. */ UPDATE_FILESIZE, /**< (int) size to download in bytes. */
GPMI_FILESIZE, /**< size to download. */ UPDATE_PREVIEWIMAGE, /**< (string) Path to a preview image in 4:3 aspect ratio. */
UPDATE_STATUSSTRING, /**< (string) Localizable string that gives you the update status. */
UPDATE_DLPERCENTAGE, /**< (float) Download progress in percent (0-100). */
} updateType_t; } updateType_t;
typedef struct /** Return values from passing UPDATE_STATE to Updates_GetInfo() */
typedef enum
{ {
string name; UPDATESTATE_NONE, /**< Update is not installed, or unavailable. */
string category; UPDATESTATE_DISABLED, /**< Update is installed, but disabled. */
string title; UPDATESTATE_ENABLED, /**< Update is installed and enabled. */
string version; UPDATESTATE_CORRUPT, /**< Update on disk is corrupted. */
string description; UPDATESTATE_PENDING /**< Update is pending a change. Usually when we're downloading it. */
string license; } updateState_t;
string author;
string website;
string installed;
int size;
int uid;
} update_t;
int g_platform_update_count; /** Return values from passing UPDATE_ACTION to Updates_GetInfo() */
update_t *updates; typedef enum
{
UPDATEACTION_NONE, /**< Update is not marked for any change. */
UPDATEACTION_INSTALL, /**< Update marked for installation. */
UPDATEACTION_REINSTALL, /**< Update marked as needing re-installation. */
UPDATEACTION_UNINSTALL, /**< Update marked for removal. */
UPDATEACTION_AUTOINSTALL, /**< Update marked as needing to be installed, due to a dependency. */
UPDATEACTION_DISABLE, /**< Update has been marked for disabling. */
UPDATEACTION_RETAIN /**< Update has been marked as being retained. */
} updateAction_t;
#define FN_UPDATE_IMGURL "http://www.frag-net.com/dl/img/%s.jpg" /** These are the possible return values from Updates_GetUpdaterStatus().
That way you can put up a loading screen for when the updater is still initiliazing,
or be notified of when an updater is not available at all. */
typedef enum
{
UPDATER_NONE, /**< Nuclide's updater has not been initialized. You need to call Update_Init(). */
UPDATER_UNAVAILABLE, /**< Nuclide's updater is unavailable. This may be due to the update server being offline. */
UPDATER_PENDING, /**< Nuclide's updater is pending. May change to UNAVAILABLE or INITIALIZED. */
UPDATER_INITIALIZED /**< Nuclide's updater is initialized and may have entries. Use Updates_GetUpdateCount() to query how many. */
} updaterStatus_t;
/** Call this in order to contact the update server and fill the list of updates. */
void Updates_Init(void); void Updates_Init(void);
void Updates_Refresh(void); /** Retrieve the status of the updater. See updaterStatus_t for valid return values. */
updaterStatus_t Updates_GetUpdaterStatus(void);
/** Returns the total amount of updates available for the currently running game. */
int Updates_GetPackageCount(void); int Updates_GetPackageCount(void);
int Updates_IDForName(string); /** Query a package (by ID) for its various info fields. See updateType_t for available fields. */
string Updates_NameForID(int); __variant Updates_GetInfo(int, updateType_t);
string Updates_GetInfo(int, updateType_t); /** Returns if our current game has updates available for any installed packages. */
bool Updates_Available(void); bool Updates_Available(void);
/** Toggle the installation/disabling of an update. May return true/false if it succeeded in marking the package. */
bool Updates_Toggle(int); bool Updates_Toggle(int);
/** Mark an update as pending installion. May return true/false if it succeeded in marking the package. */
bool Updates_Install(int); bool Updates_Install(int);
/** Mark an update as pending deletion. May return true/false if it succeeded in marking the package. */
bool Updates_Remove(int); bool Updates_Remove(int);
bool Updates_ApplyPendingChanges(void); /** Mark an update as pending uninstallation. May return true/false if it succeeded in marking the package. */
bool Updates_Destroy(int);
/** Apply all pending changes to packages. May return true/false if it succeeded in doing so. */
bool Updates_ApplyPendingChanges(void);
/** Called by the console command `platformRefreshUpdates`, which your menu needs to implement. Otherwise you will not see pending changes reflected in your menu interface. */
void Updates_RefreshState(void);

View file

@ -1,13 +1,72 @@
/** needs to be called upon menu-init, and call Updates_Refresh() if auto-updates /*
are enabled. if a chooser does not want updates, then we won't. */ * Copyright (c) 2016-2023 Vera Visions LLC.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
typedef struct
{
string name;
string category;
string title;
string version;
string description;
string license;
string author;
string website;
string installed;
updateState_t state;
updateAction_t pending_action;
int size;
int uid;
string preview_image;
float dlpercentage;
} update_t;
string(float id, float b) getgamedirinfo = #0;
string(int packageidx, int desiredfield) getpackagemanagerinfo = #0;
#define FN_UPDATE_IMGURL "http://www.frag-net.com/dl/img/%s.jpg"
int g_platform_update_count;
update_t *updates;
var updaterStatus_t updater_package_status = UPDATER_NONE;
void void
Updates_Init(void) Updates_Init(void)
{ {
/*localcmd("pkg addsource https://www.frag-net.com/pkgs/list\n");*/ string packages;
print("Update system initialized.\n");
/* first, see if our game info sets any packages. */
packages = GameLibrary_GetInfo(GAMEINFO_PACKAGELIST);
/* we have no hard-coded list of supported packages, so query frag-net.com */
if (!packages) {
string gamedir = cvar_string("fs_game");
print(sprintf("Querying package names for %s\n", gamedir));
uri_get(sprintf("http://www.frag-net.com/dl/packages_%s", uri_escape(gamedir)), MODSERVER_REQ_PKGNAMES);
updater_package_status = UPDATER_PENDING;
} else {
updater_package_status = UPDATER_INITIALIZED;
}
}
updaterStatus_t
Updates_GetUpdaterStatus(void)
{
return updater_package_status;
} }
/** will return a cached value */
int int
Updates_GetPackageCount(void) Updates_GetPackageCount(void)
{ {
@ -20,9 +79,10 @@ Updates_IsRecommended(string packageName)
{ {
string newName = ""; string newName = "";
int countPkg = 0i; int countPkg = 0i;
string packageList = GameLibrary_GetInfo(GAMEINFO_PACKAGELIST);
/* cancel out early if need be */ /* cancel out early if need be */
if not (games[gameinfo_current].pkgname) if not (packageList)
return true; return true;
/* get rid of the version string FTEQW appends */ /* get rid of the version string FTEQW appends */
@ -30,7 +90,7 @@ Updates_IsRecommended(string packageName)
newName = argv(0); newName = argv(0);
/* iterate over the recommended packages */ /* iterate over the recommended packages */
countPkg = (int)tokenizebyseparator(games[gameinfo_current].pkgname, ";"); countPkg = (int)tokenizebyseparator(packageList, ";");
for (int i = 0i; i < countPkg; i++) { for (int i = 0i; i < countPkg; i++) {
/* there's a match */ /* there's a match */
@ -42,8 +102,68 @@ Updates_IsRecommended(string packageName)
return false; return false;
} }
/** called whenever we need to re-initialize the updates struct */ static void
void Updates_RefreshStateValues(int packageID)
{
int pkgUID = updates[packageID].uid;
string installedState = getpackagemanagerinfo(pkgUID, GPMI_INSTALLED);
string actionState = getpackagemanagerinfo(pkgUID, GPMI_ACTION);
switch (actionState) {
case "user":
updates[packageID].pending_action = UPDATEACTION_INSTALL;
break;
case "reinstall":
updates[packageID].pending_action = UPDATEACTION_REINSTALL;
break;
case "purge":
updates[packageID].pending_action = UPDATEACTION_UNINSTALL;
break;
case "auto":
updates[packageID].pending_action = UPDATEACTION_AUTOINSTALL;
break;
case "disable":
updates[packageID].pending_action = UPDATEACTION_DISABLE;
break;
case "retain":
/*updates[packageID].pending_action = UPDATEACTION_RETAIN;
break;*/
default:
updates[packageID].pending_action = UPDATEACTION_NONE;
}
switch (installedState) {
case "present":
updates[packageID].state = UPDATESTATE_DISABLED;
break;
case "enabled":
updates[packageID].state = UPDATESTATE_ENABLED;
break;
case "corrupt":
updates[packageID].state = UPDATESTATE_CORRUPT;
break;
case "pending":
updates[packageID].state = UPDATESTATE_PENDING;
break;
default:
updates[packageID].state = UPDATESTATE_NONE;
}
updates[packageID].dlpercentage = stof(installedState);
/* HACK: the engine doesn't seem to set pending while installing, so let us do the job then */
if (updates[packageID].dlpercentage > 0)
updates[packageID].state = UPDATESTATE_PENDING;
/* HACK: enabled AND pending installation? smells like an engine bug! */
if (updates[packageID].state == UPDATESTATE_ENABLED) {
if (updates[packageID].pending_action == UPDATEACTION_INSTALL) {
updates[packageID].pending_action = UPDATEACTION_NONE;
}
}
}
static void
Updates_Refresh(void) Updates_Refresh(void)
{ {
int c = 0i; int c = 0i;
@ -55,7 +175,7 @@ Updates_Refresh(void)
} }
/* count all updates that we've got in our package sources */ /* count all updates that we've got in our package sources */
for (int i = 0i; (Updates_GetInfo(i, GPMI_NAME)); i++) { for (int i = 0i; (getpackagemanagerinfo(i, GPMI_NAME)); i++) {
g_platform_update_count++; g_platform_update_count++;
} }
@ -66,21 +186,23 @@ Updates_Refresh(void)
int id = i; int id = i;
/* skip not recommended packages */ /* skip not recommended packages */
if (Updates_IsRecommended(Updates_GetInfo(id, GPMI_NAME)) == false) if (Updates_IsRecommended(getpackagemanagerinfo(id, GPMI_NAME)) == false)
continue; continue;
updates[c].name = Updates_GetInfo(id, GPMI_NAME); updates[c].name = getpackagemanagerinfo(id, GPMI_NAME);
updates[c].category = Updates_GetInfo(id, GPMI_CATEGORY); updates[c].category = getpackagemanagerinfo(id, GPMI_CATEGORY);
updates[c].title = Updates_GetInfo(id, GPMI_TITLE); updates[c].title = getpackagemanagerinfo(id, GPMI_TITLE);
updates[c].version = Updates_GetInfo(id, GPMI_VERSION); updates[c].version = getpackagemanagerinfo(id, GPMI_VERSION);
updates[c].description = Updates_GetInfo(id, GPMI_DESCRIPTION); updates[c].description = getpackagemanagerinfo(id, GPMI_DESCRIPTION);
updates[c].license = Updates_GetInfo(id, GPMI_LICENSE); updates[c].license = getpackagemanagerinfo(id, GPMI_LICENSE);
updates[c].author = Updates_GetInfo(id, GPMI_AUTHOR); updates[c].author = getpackagemanagerinfo(id, GPMI_AUTHOR);
updates[c].website = Updates_GetInfo(id, GPMI_WEBSITE); updates[c].website = getpackagemanagerinfo(id, GPMI_WEBSITE);
updates[c].installed = Updates_GetInfo(id, GPMI_INSTALLED);
updates[c].size = (int)stof(Updates_GetInfo(id, GPMI_FILESIZE)); updates[c].size = (int)stof(getpackagemanagerinfo(id, GPMI_FILESIZE));
updates[c].uid = i; updates[c].uid = i;
precache_pic(sprintf(FN_UPDATE_IMGURL, updates[c].name)); updates[c].preview_image = sprintf(FN_UPDATE_IMGURL, updates[c].name);
Updates_RefreshStateValues(c);
c++; c++;
} }
@ -88,94 +210,179 @@ Updates_Refresh(void)
g_platform_update_count = c; g_platform_update_count = c;
} }
/** Returns the package ID for a given name. Will return -1 when not available. */ __variant
int
Updates_IDForName(string packageName)
{
string f;
for (int i = 0; (getpackagemanagerinfo(i, GPMI_NAME)); i++) {
string name;
name = getpackagemanagerinfo(i, GPMI_NAME);
/* Spike started randomly putting version numbers into package names */
f = sprintf("%s=%s", packageName, getpackagemanagerinfo(i, GPMI_VERSION));
if (name == f) {
return i;
}
}
/* no package id whatsoever */
return (-1i);
}
/** Returns the package name for a given ID. Returns __NULL__ when not available. */
string
Updates_NameForID(int packageID)
{
string packageName = getpackagemanagerinfo(packageID, GPMI_NAME);
if not (packageName)
return __NULL__;
return packageName;
}
/** Query a package (by ID) for its various info fields. See updateType_t for available fields. */
string
Updates_GetInfo(int packageID, updateType_t fieldType) Updates_GetInfo(int packageID, updateType_t fieldType)
{ {
return getpackagemanagerinfo(packageID, (int)fieldType); if (packageID >= g_platform_update_count || packageID < 0i) {
print(sprintf("Updates_GetInfo: Invalid package id %i!\n", packageID));
return __NULL__;
}
switch (fieldType) {
case UPDATE_NAME:
return (string)updates[packageID].name;
break;
case UPDATE_CATEGORY:
return (string)updates[packageID].category;
break;
case UPDATE_TITLE:
return (string)updates[packageID].title;
break;
case UPDATE_VERSION:
return (string)updates[packageID].version;
break;
case UPDATE_DESCRIPTION:
return (string)updates[packageID].description;
break;
case UPDATE_LICENSE:
return (string)updates[packageID].license;
break;
case UPDATE_AUTHOR:
return (string)updates[packageID].author;
break;
case UPDATE_WEBSITE:
return (string)updates[packageID].website;
break;
case UPDATE_STATE:
return (updateState_t)updates[packageID].state;
break;
case UPDATE_ACTION:
return (updateAction_t)updates[packageID].pending_action;
break;
case UPDATE_FILESIZE:
return (int)updates[packageID].size;
break;
case UPDATE_PREVIEWIMAGE:
return (string)updates[packageID].preview_image;
break;
case UPDATE_STATUSSTRING:
/* if we have a action, focus on that */
switch (updates[packageID].pending_action) {
case UPDATEACTION_INSTALL:
if (updates[packageID].dlpercentage > 0.0) {
return sprintf("%d %%", updates[packageID].dlpercentage);
} else {
return _("UPDATE_PENDING_INSTALL");
}
break;
case UPDATEACTION_REINSTALL:
return _("UPDATE_PENDING_REINSTALL");
break;
case UPDATEACTION_UNINSTALL:
return _("UPDATE_PENDING_UNINSTALL");
break;
case UPDATEACTION_AUTOINSTALL:
return _("UPDATE_PENDING_AUTOINSTALL");
break;
case UPDATEACTION_DISABLE:
return _("UPDATE_PENDING_DISABLE");
break;
case UPDATEACTION_RETAIN:
return _("UPDATE_PENDING_RETAIN");
break;
default:
switch (updates[packageID].state) {
case UPDATESTATE_DISABLED:
return _("UPDATE_DISABLED");
break;
case UPDATESTATE_ENABLED:
return _("UPDATE_ENABLED");
break;
case UPDATESTATE_CORRUPT:
return _("UPDATE_CORRUPT");
break;
default:
return _("UPDATE_NOTINSTALLED");
}
}
break;
break;
case UPDATE_DLPERCENTAGE:
return updates[packageID].dlpercentage;
break;
default:
return __NULL__;
}
} }
/** Returns if our current game has updates available for any installed packages. */
bool bool
Updates_Available(void) Updates_Available(void)
{ {
return true; return true;
} }
/** Toggle the installation of a package. Will return true if it was done. */
bool bool
Updates_Toggle(int packageID) Updates_Toggle(int packageID)
{ {
switch (updates[packageID].installed) { if (packageID >= g_platform_update_count || packageID < 0i) {
case "": print(sprintf("Updates_Toggle: Invalid package id %i!\n", packageID));
case "rem": return false;
}
switch (updates[packageID].pending_action) {
case UPDATEACTION_INSTALL:
case UPDATEACTION_REINSTALL:
localcmd(sprintf("pkg rem %s\n", updates[packageID].name));
break;
case UPDATEACTION_UNINSTALL:
case UPDATEACTION_DISABLE:
localcmd(sprintf("pkg add %s\n", updates[packageID].name)); localcmd(sprintf("pkg add %s\n", updates[packageID].name));
updates[packageID].installed = "pending";
break; break;
default: default:
localcmd(sprintf("pkg rem %s\n", updates[packageID].name)); if (updates[packageID].state == UPDATESTATE_ENABLED) {
updates[packageID].installed = "rem"; localcmd(sprintf("pkg rem %s\n", updates[packageID].name));
} else {
localcmd(sprintf("pkg add %s\n", updates[packageID].name));
}
break;
} }
localcmd(sprintf("platformRefreshUpdates %i\n", packageID));
return true; return true;
} }
/** Mark a package as pending installion. May return true/false if it succeeded in marking the package. */
bool bool
Updates_Install(int packageID) Updates_Install(int packageID)
{ {
if (packageID >= g_platform_update_count || packageID < 0i) {
print(sprintf("Updates_Install: Invalid package id %i!\n", packageID));
return false;
}
localcmd(sprintf("pkg add %s\n", updates[packageID].name)); localcmd(sprintf("pkg add %s\n", updates[packageID].name));
updates[packageID].installed = "pending"; localcmd(sprintf("platformRefreshUpdates %i\n", packageID));
print(sprintf("Marking package %s for install.\n", updates[packageID].title)); print(sprintf("Marking package %s for install.\n", updates[packageID].title));
return true; return true;
} }
/** Mark a package as pending deletion. May return true/false if it succeeded in marking the package. */
bool bool
Updates_Remove(int packageID) Updates_Remove(int packageID)
{ {
if (packageID >= g_platform_update_count || packageID < 0i) {
print(sprintf("Updates_Remove: Invalid package id %i!\n", packageID));
return false;
}
localcmd(sprintf("pkg rem %s\n", updates[packageID].name)); localcmd(sprintf("pkg rem %s\n", updates[packageID].name));
updates[packageID].installed = "rem"; localcmd(sprintf("platformRefreshUpdates %i\n", packageID));
print(sprintf("Marking package %s for 'removal'.\n", updates[packageID].title)); print(sprintf("Marking package %s for 'removal'.\n", updates[packageID].title));
return true; return true;
} }
/** Apply all pending changes to packages. May return true/false if it succeeded in doing so. */ bool
Updates_Destroy(int packageID)
{
if (packageID >= g_platform_update_count || packageID < 0i) {
print(sprintf("Updates_Destroy: Invalid package id %i!\n", packageID));
return false;
}
localcmd(sprintf("pkg del %s\n", updates[packageID].name));
localcmd(sprintf("platformRefreshUpdates %i\n", packageID));
print(sprintf("Marking package %s for 'deletion'.\n", updates[packageID].title));
return true;
}
bool bool
Updates_ApplyPendingChanges(void) Updates_ApplyPendingChanges(void)
{ {
@ -183,4 +390,42 @@ Updates_ApplyPendingChanges(void)
localcmd("pkg apply\n"); localcmd("pkg apply\n");
print("Applying package changes.\n"); print("Applying package changes.\n");
return true; return true;
}
/* I hate having to put this in, but because we can only interact with the
engine its own package manager via console commands (they are delayed as
as result) we cannot query the result of our `pkg` commands right after
calling them via localcmd(). Therefore menus need to implement the console
command `platformRefreshUpdates` which calls this very function below.
All in the hopes of improving performance just a tiny bit. */
void
Updates_RefreshState(void)
{
/* at least try to be a bit clever about it. */
if (argv(0) == "platformRefreshUpdates") {
Updates_RefreshStateValues((int)stof(argv(1)));
return;
}
/* just force refresh them all otherwise. */
for (int i = 0i; i < g_platform_update_count; i++) {
Updates_RefreshStateValues(i);
}
}
void
Updater_URI_Callback(float id, float code, string data, int resourcebytes)
{
/* game does not have a recommended package listing remotely? make something up. */
if (code == 404) {
string gameDir = cvar_string("fs_game");
games[GameLibrary_GetCurrentGame()].pkgname = strcat("cg_", gameDir, ";game_", gameDir, ";");
} else {
//print(sprintf("URI: %d %d %S %i\n", id, code, data, resourcebytes));
games[GameLibrary_GetCurrentGame()].pkgname = data;
}
updater_package_status = UPDATER_INITIALIZED;
Updates_Refresh();
} }