Skip to content

Commit e080869

Browse files
authored
Add some markdown docs i wrote (#350)
1 parent 949b77f commit e080869

6 files changed

Lines changed: 390 additions & 0 deletions

File tree

specs/changelevels.md

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
2+
The FTEQW engine offers many improvements in changelevel functionality over the
3+
original Quake engine from 1996. In the original engine, your only way to send
4+
player data across a level transition was through the `parm` globals in the
5+
QuakeC server code, which meant you had exactly 16 floating-point numbers to
6+
use to store any data the players needed to remember across a level change.
7+
Everything else about the player's state was dropped. This worked well enough
8+
for Quake, but is severely limiting if you need to send anything that couldn't
9+
fit into a `float`.
10+
11+
Not only does FTEQW offer 64 `parm` globals instead of 16 (for DarkPlaces
12+
compatibility), it also has a `parm_string` global which can store a
13+
theoretically unlimited amount of string or buffer data. In this document I'll
14+
show you how I decided to use it for my projects.
15+
16+
When the player spawns in a level for the first time, the function
17+
`SetNewParms()` is called in the SSQC progs. This is where you set the default
18+
`parm` values for every player that spawns in a level. For example, let's say
19+
that `parm1` is going to store the player's health value across a level change.
20+
You would set the default health value like so:
21+
22+
```c
23+
void() SetNewParms =
24+
{
25+
parm1 = 100;
26+
};
27+
```
28+
29+
When the engine is sending the players through a level transition, it calls the
30+
function `SetChangeParms()` for each player entity in sequence. For storing the
31+
player's health, you would do it like so:
32+
33+
```c
34+
void() SetChangeParms =
35+
{
36+
parm1 = self.health;
37+
};
38+
```
39+
40+
But, as stated before, this can get rather limiting if the player has data that
41+
doesn't fit into a floating point number, such as a dynamic inventory of items
42+
and weapons or an stats tree or something. This is where `parm_string` comes
43+
in. If you're using a modern version of FTEQW and the FTEQCC compiler, you also
44+
have access to several JSON parsing builtins. We will be using these to make
45+
the usage of `parm_string` easier.
46+
47+
If we wanted to store more complex data in `parm_string`, the default value for
48+
it should be a valid empty JSON node, like so:
49+
50+
```c
51+
void() SetNewParms =
52+
{
53+
parm1 = 100;
54+
parm_string = "{}";
55+
};
56+
```
57+
58+
And when `SetChangeParms()` is called, you will write your extended data as
59+
valid JSON entries in `parm_string`, like so:
60+
61+
```c
62+
void() SetChangeParms =
63+
{
64+
parm1 = self.health;
65+
parm_string = strcat(
66+
"{\"some_extended_field_string\":\"",
67+
self.some_extended_field_string,
68+
"\"}"
69+
);
70+
};
71+
```
72+
73+
Note we are using the `strcat()` builtin to construct our JSON string rather
74+
than `sprintf()`, because `sprintf()` seems to have some difficulties with very
75+
long strings.
76+
77+
The above code will store a per-player string value called
78+
`some_extended_field_string` into `parm_string` for storage across the level
79+
transition. Ideally you'd wanna make some helper functions to easily write and
80+
format your JSON, but this is just a basic example that constructs it directly.
81+
82+
Now the interesting part comes when it's time to read the data back. You'd want
83+
to do this in the function `PutClientInServer()` after setting up your
84+
fundamental player class, like so:
85+
86+
```c
87+
void() PutClientInServer =
88+
{
89+
// setup fundamental player state
90+
self.takedamage = DAMAGE_YES;
91+
self.solid = SOLID_SLIDEBOX;
92+
self.movetype = MOVETYPE_WALK;
93+
self.fixangle = TRUE;
94+
setsize(self, [-16, -16, -36], [16, 16, 36]);
95+
self.flags |= FL_CLIENT;
96+
self.view_ofs = [0, 0, 28];
97+
self.classname = "player";
98+
99+
// load health value from parm1
100+
self.health = parm1;
101+
102+
// parse parm_string as json
103+
jsonnode root = json_parse(parm_string);
104+
if (!root)
105+
error("couldn't decode parm_string as JSON!");
106+
107+
// check if we have the field
108+
// if so, copy it out as a string
109+
if (root["some_extended_field_string"])
110+
self.some_extended_field_string = root["some_extended_field_string"].s;
111+
112+
// clean up
113+
json_free(root);
114+
};
115+
```
116+
117+
You don't have to use `parm_string` as JSON, though. You could just print the
118+
values as space-separated tokens and use the `tokenize()` or
119+
`tokenize_console()` builtins to read them back. Either way, it's a good method
120+
for storing lots of custom data across level transitions. Enjoy!

specs/glsl.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
2+
FTEQW uses [GLSL](https://www.khronos.org/opengl/wiki/Core_Language_%28GLSL%29)
3+
for all of its shaders, with the legacy
4+
[Quake 3 shader format](https://graphics.stanford.edu/courses/cs448-00-spring/q3ashader_manual.pdf)
5+
acting as the glue between the raw textures and the GLSL.
6+
7+
A standard "wall" Q3 shader in FTEQW might be located at
8+
`mygame/scripts/brick.shader` and might look like this:
9+
10+
```glsl
11+
// material name used in level file
12+
brick/wall001a
13+
{
14+
// glsl program to use
15+
program defaultwall
16+
17+
// diffuse texture file path, relative to gamedir
18+
diffusemap textures/brick/wall001a.tga
19+
}
20+
```
21+
22+
`defaultwall` is the embedded GLSL shader in FTEQW for anything that is not
23+
water, sky, sprites, or models. These shader files are present in
24+
[FTEQW's source code](https://github.com/fte-team/fteqw/tree/master/engine/shaders/glsl),
25+
but you can also dump them with the console command `r_dumpshaders`. `diffusemap`
26+
is an FTEQW-specific shortcut that passes the texture name into the GLSL shader
27+
as the diffuse sampler.
28+
29+
Here is an incomplete list of the FTEQW shortcuts for passing samplers to GLSL
30+
shaders:
31+
32+
- `diffusemap`: Used for the base texture.
33+
- `fullbrightmap`: Used for glowing pixels.
34+
- `specularmap`: Used for specular reflections.
35+
- `normalmap`: Used for bump mapping.
36+
- `lightmap`: Used for adding baked lighting to a surface.
37+
38+
Here is an incomplete list of the default shaders, and their uses:
39+
40+
- `defaultskin.glsl`: Used for models and their skins.
41+
- `defaultsky.glsl`: Used for Quake's scrolling skies.
42+
- `defaultskybox.glsl`: Used for Quake II and Half-Life's skyboxes.
43+
- `defaultsprite.glsl`: Used for particles and sprites.
44+
- `defaultwall.glsl`: Used for most level geometry.
45+
- `defaultwarp.glsl`: Used for Quake's turbulent water surfaces.
46+
47+
These can be used in any Q3 shader with `program <name>`. Any Q3 shader file
48+
with the extension `.shader` in the `scripts` folder of your game or mod will be
49+
automatically loaded by the engine.
50+
51+
## Example Code
52+
53+
Here is an example of a very simple `defaultwall.glsl` replacement. It only takes
54+
a diffuse sampler, an optional fullbrightmap, and a lightmap:
55+
56+
```glsl
57+
!!ver 130 460
58+
!!permu FULLBRIGHT
59+
!!permu FOG
60+
!!permu LIGHTSTYLED
61+
!!samps diffuse lightmap fullbright
62+
63+
varying vec2 txc;
64+
varying vec2 lmc;
65+
66+
#include "sys/defs.h"
67+
#include "sys/fog.h"
68+
69+
#ifdef VERTEX_SHADER
70+
void main()
71+
{
72+
txc = v_texcoord;
73+
lmc = v_lmcoord;
74+
75+
gl_Position = ftetransform();
76+
}
77+
#endif
78+
79+
#ifdef FRAGMENT_SHADER
80+
void main()
81+
{
82+
// diffuse sampler
83+
vec4 diffuse = texture2D(s_diffuse, txc);
84+
85+
// apply lightmap
86+
vec3 lightmaps = (texture2D(s_lightmap, lmc) * e_lmscale).rgb;
87+
diffuse.rgb *= lightmaps.rgb;
88+
89+
// apply brightmap
90+
#if defined(FULLBRIGHT)
91+
vec4 brightmap = texture2D(s_fullbright, txc);
92+
diffuse = brightmap * brightmap.a + diffuse * (1.0 - brightmap.a);
93+
#endif
94+
95+
// final color
96+
gl_FragColor = fog4(diffuse * e_colourident);
97+
}
98+
#endif
99+
```

specs/images/func_rotate.png

58.9 KB
Loading

specs/rotating-brushes.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
2+
With the `DP_SV_ROTATINGBMODEL` QuakeC extension, brush entities can be made to
3+
rotate using the `.avelocity` field. The brush entity must have an "origin brush"
4+
or else it will rotate around the map origin.
5+
6+
![func_rotate example in TrenchBroom](./images/func_rotate.png)
7+
8+
To create an origin brush, create another brush in your level editor of choice
9+
and make it a part of the entity. The center of the new brush should be where
10+
you want the entity to rotate from. The new brush should also have an "ORIGIN"
11+
texture on it, so the map compiler can properly mark it as the center of that
12+
entity. In Q1-based games, this is usually just a texture named "ORIGIN" with no
13+
other special properties. In Q2-based games, it will be a texture with the "ORIGIN"
14+
contents flag set.
15+
16+
Finally, it needs some extra additions in QuakeC. In your entities `.think`
17+
function, assign the `.avelocity` field to a vector representing the rotation,
18+
in degrees per second.
19+
20+
**NOTE**: If you're having issues getting this to work, try using the
21+
`checkextension` builtin to query the `DP_SV_ROTATINGBMODEL` extension before
22+
using it.
23+
24+
## Example Code
25+
26+
Here is an extremely basic `func_rotate` implementation in QuakeC for FTE:
27+
28+
```c
29+
enumflags {
30+
FUNC_ROTATE_X_AXIS,
31+
FUNC_ROTATE_Y_AXIS,
32+
FUNC_ROTATE_Z_AXIS
33+
};
34+
35+
void func_rotate_think()
36+
{
37+
// do rotations
38+
if (self.spawnflags & FUNC_ROTATE_X_AXIS)
39+
self.avelocity.x = self.speed;
40+
if (self.spawnflags & FUNC_ROTATE_Y_AXIS)
41+
self.avelocity.y = self.speed;
42+
if (self.spawnflags & FUNC_ROTATE_Z_AXIS)
43+
self.avelocity.z = self.speed;
44+
45+
self.nextthink = self.ltime + 0.1;
46+
}
47+
48+
void func_rotate()
49+
{
50+
// setup brush model
51+
setorigin(self, self.origin);
52+
setmodel(self, self.model);
53+
self.movetype = MOVETYPE_PUSH;
54+
self.solid = SOLID_BSP;
55+
56+
// start thinking
57+
self.think = func_rotate_think;
58+
self.nextthink = self.ltime + 0.1;
59+
}
60+
```
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
2+
One way to have prefab elements in the FTEQW engine is to use separate BSP or
3+
MAP files that contain their own lighting, geometry and entities. These are
4+
called bmodels, or brush models. By default, FTEQW does not handle the spawning
5+
of entities from these files, and will ignore them. But with the
6+
`FTE_TERRAIN_MAP` extension, you can spawn them in QuakeC.
7+
8+
## Example Code
9+
10+
```c
11+
// spawn all child entities inside brush model
12+
// NOTE: this must be called as self! it uses self.modelindex to get the bmodel
13+
void spawn_child_entities()
14+
{
15+
// get number of child entities
16+
int num_entities = (int)terrain_edit(TEREDIT_ENT_COUNT);
17+
18+
// start at 1 so we don't create another worldspawn
19+
for (int entity_id = 1i; entity_id < num_entities; entity_id++)
20+
{
21+
// spawn child entity
22+
entity ent = spawn();
23+
24+
// get entity data
25+
string entdata = (string)terrain_edit(TEREDIT_ENT_GET, entity_id);
26+
entdata = strcat("{", entdata, "}");
27+
28+
// parse it into the entity we spawned
29+
parseentitydata(ent, entdata);
30+
31+
// fix up origin and angles
32+
setorigin(ent, ent.origin + self.origin);
33+
ent.angles += self.angles;
34+
35+
// get spawn function
36+
var void() spawnfunc = externvalue(0, ent.classname);
37+
38+
// run spawnfunc as self
39+
entity oself = self;
40+
self = ent;
41+
spawnfunc();
42+
self = oself;
43+
}
44+
}
45+
```

specs/viewmodels.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
2+
In FTEQW, you can use CSQC to give you more control over weapon viewmodels.
3+
4+
Here is a chunk of code showing how to spawn a viewmodel entity in CSQC, with
5+
the appropriate flags set.
6+
7+
```c
8+
static entity viewmodel;
9+
10+
void CSQC_Init(float apilevel, string enginename, float engineversion)
11+
{
12+
viewmodel = spawn();
13+
viewmodel.drawmask = MASK_VIEWMODEL;
14+
viewmodel.renderflags |= RF_VIEWMODEL;
15+
viewmodel.effects |= EF_NOSHADOW;
16+
// you may have to rotate the viewmodel angles depending on how you
17+
// exported it from your modeling software
18+
viewmodel.angles = [0, 0, 0];
19+
setorigin(viewmodel, [0, 0, 0]);
20+
setsize(viewmodel, [0, 0, 0], [0, 0, 0]);
21+
}
22+
```
23+
24+
...and if your model is skeletally animated, you would do this in
25+
`CSQC_UpdateView`:
26+
27+
```c
28+
void CSQC_UpdateView(float vwidth, float vheight, float notmenu)
29+
{
30+
// push viewmodel animation at constant framerate
31+
// note the use of frametime rather than clframetime, you probably don't
32+
// want the viewmodel animating while the game is paused
33+
viewmodel.frame1time += frametime;
34+
35+
// other stuff down here...
36+
}
37+
```
38+
39+
Note that we have not set a model on it yet, so it will not yet be visible. To
40+
do that, we should first figure out what weapon the player is holding. There's
41+
a few different ways you could do this, but I'm gonna show you the Quake-y way.
42+
To get the modelindex of the player's viewmodel, you could do this:
43+
44+
```c
45+
setmodelindex(viewmodel, getstatf(STAT_WEAPONMODELI));
46+
```
47+
48+
`STAT_WEAPONMODELI` is a networked player stat that corresponds to the
49+
modelindex of the model specified by `self.weaponmodel` in the SSQC module. You
50+
should probably call the above code only if you detect that the player has
51+
switched weapons, or if the viewmodel's current modelindex is different from
52+
the value read from `STAT_WEAPONMODELI`. You can also read the player's current
53+
weapon index by reading the stat `STAT_ACTIVEWEAPON`, though you must set this
54+
yourself in the SSQC module with the `self.weapon` field, otherwise it will
55+
mean nothing.
56+
57+
To set the weapon's animation, you could set `self.weaponframe` in SSQC and
58+
then read it from CSQC with the `STAT_WEAPONFRAME` stat. In the CSQC module
59+
you'd then do:
60+
61+
```c
62+
viewmodel.frame = getstatf(STAT_WEAPONFRAME);
63+
```
64+
65+
Note that whenever a new animation starts on the viewmodel, you should set
66+
`viewmodel.frame1time` back to 0. This will reset the animation time.

0 commit comments

Comments
 (0)