Full Code of cormac-obrien/richter for AI

devel 506504d5f9f9 cached
122 files
1.0 MB
255.4k tokens
1986 symbols
1 requests
Download .txt
Showing preview only (1,096K chars total). Download the full file or copy to clipboard to get everything.
Repository: cormac-obrien/richter
Branch: devel
Commit: 506504d5f9f9
Files: 122
Total size: 1.0 MB

Directory structure:
gitextract_j_etf39x/

├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .rustfmt.toml
├── Cargo.toml
├── LICENSE.txt
├── README.md
├── shaders/
│   ├── alias.frag
│   ├── alias.vert
│   ├── blit.frag
│   ├── blit.vert
│   ├── brush.frag
│   ├── brush.vert
│   ├── deferred.frag
│   ├── deferred.vert
│   ├── glyph.frag
│   ├── glyph.vert
│   ├── particle.frag
│   ├── particle.vert
│   ├── postprocess.frag
│   ├── postprocess.vert
│   ├── quad.frag
│   ├── quad.vert
│   ├── sprite.frag
│   └── sprite.vert
├── site/
│   ├── config.toml
│   ├── content/
│   │   ├── _index.md
│   │   ├── blog/
│   │   │   ├── 2018-04-24.md
│   │   │   ├── 2018-04-26/
│   │   │   │   └── index.md
│   │   │   ├── 2018-05-12/
│   │   │   │   └── index.md
│   │   │   ├── 2018-07-20/
│   │   │   │   └── index.md
│   │   │   └── _index.md
│   │   └── index.html
│   ├── sass/
│   │   ├── _base.scss
│   │   ├── _reset.scss
│   │   ├── blog-post.scss
│   │   ├── blog.scss
│   │   └── style.scss
│   ├── templates/
│   │   └── home.html
│   └── themes/
│       └── richter/
│           ├── templates/
│           │   ├── base.html
│           │   ├── blog-post.html
│           │   ├── blog.html
│           │   └── index.html
│           └── theme.toml
├── specifications.md
└── src/
    ├── bin/
    │   ├── quake-client/
    │   │   ├── capture.rs
    │   │   ├── game.rs
    │   │   ├── main.rs
    │   │   ├── menu.rs
    │   │   └── trace.rs
    │   └── unpak.rs
    ├── client/
    │   ├── cvars.rs
    │   ├── demo.rs
    │   ├── entity/
    │   │   ├── mod.rs
    │   │   └── particle.rs
    │   ├── input/
    │   │   ├── console.rs
    │   │   ├── game.rs
    │   │   ├── menu.rs
    │   │   └── mod.rs
    │   ├── menu/
    │   │   ├── item.rs
    │   │   └── mod.rs
    │   ├── mod.rs
    │   ├── render/
    │   │   ├── atlas.rs
    │   │   ├── blit.rs
    │   │   ├── cvars.rs
    │   │   ├── error.rs
    │   │   ├── mod.rs
    │   │   ├── palette.rs
    │   │   ├── pipeline.rs
    │   │   ├── target.rs
    │   │   ├── ui/
    │   │   │   ├── console.rs
    │   │   │   ├── glyph.rs
    │   │   │   ├── hud.rs
    │   │   │   ├── layout.rs
    │   │   │   ├── menu.rs
    │   │   │   ├── mod.rs
    │   │   │   └── quad.rs
    │   │   ├── uniform.rs
    │   │   ├── warp.rs
    │   │   └── world/
    │   │       ├── alias.rs
    │   │       ├── brush.rs
    │   │       ├── deferred.rs
    │   │       ├── mod.rs
    │   │       ├── particle.rs
    │   │       ├── postprocess.rs
    │   │       └── sprite.rs
    │   ├── sound/
    │   │   ├── mod.rs
    │   │   └── music.rs
    │   ├── state.rs
    │   ├── trace.rs
    │   └── view.rs
    ├── common/
    │   ├── alloc.rs
    │   ├── bitset.rs
    │   ├── bsp/
    │   │   ├── load.rs
    │   │   └── mod.rs
    │   ├── console/
    │   │   └── mod.rs
    │   ├── engine.rs
    │   ├── host.rs
    │   ├── math.rs
    │   ├── mdl.rs
    │   ├── mod.rs
    │   ├── model.rs
    │   ├── net/
    │   │   ├── connect.rs
    │   │   └── mod.rs
    │   ├── pak.rs
    │   ├── parse/
    │   │   ├── console.rs
    │   │   ├── map.rs
    │   │   └── mod.rs
    │   ├── sprite.rs
    │   ├── util.rs
    │   ├── vfs.rs
    │   └── wad.rs
    ├── lib.rs
    └── server/
        ├── mod.rs
        ├── precache.rs
        ├── progs/
        │   ├── functions.rs
        │   ├── globals.rs
        │   ├── mod.rs
        │   ├── ops.rs
        │   └── string_table.rs
        └── world/
            ├── entity.rs
            ├── mod.rs
            └── phys.rs

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/ci.yml
================================================
name: Rust

on:
  push:
    branches: [ devel ]
  pull_request:
    branches: [ devel ]

env:
  CARGO_TERM_COLOR: always

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Install build deps
      run: sudo apt-get install libasound2-dev
    - name: Install latest nightly
      uses: actions-rs/toolchain@v1
      with:
        toolchain: nightly
        components: rustfmt, clippy
    - name: Build with nightly
      uses: actions-rs/cargo@v1.0.1
      with:
        command: build
        toolchain: nightly
        args: --all-targets
    - name: Test with nightly
      uses: actions-rs/cargo@v1.0.1
      with:
        command: test
        toolchain: nightly
        args: --workspace


================================================
FILE: .gitignore
================================================
Cargo.lock
site/public
target
*.bak
*.bk
*.pak
*.pak.d
.#*


================================================
FILE: .rustfmt.toml
================================================
unstable_features = true

imports_granularity = "Crate"


================================================
FILE: Cargo.toml
================================================
[package]
name = "richter"
version = "0.1.0"
authors = ["Cormac O'Brien <cormac@c-obrien.org>"]
edition = "2018"

[dependencies]
arrayvec = "0.7"
bitflags = "1.0.1"
bumpalo = "3.4"
byteorder = "1.3"
cgmath = "0.17.0"
chrono = "0.4.0"
env_logger = "0.5.3"
failure = "0.1.8"
futures = "0.3.5"
lazy_static = "1.0.0"
log = "0.4.1"
nom = "5.1"
num = "0.1.42"
num-derive = "0.1.42"
png = "0.16"
rand = { version = "0.7", features = ["small_rng"] }
regex = "0.2.6"
# rodio = "0.12"
rodio = { git = "https://github.com/RustAudio/rodio", rev = "82b4952" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
shaderc = "0.6.2"
slab = "0.4"
structopt = "0.3.12"
strum = "0.18.0"
strum_macros = "0.18.0"
thiserror = "1.0"
uluru = "2"
wgpu = "0.8"

# "winit" = "0.22.2"
# necessary until winit/#1524 is merged
winit = { git = "https://github.com/chemicstry/winit", branch = "optional_drag_and_drop" }


================================================
FILE: LICENSE.txt
================================================
Copyright © 2017 Cormac O'Brien

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.



================================================
FILE: README.md
================================================
# Richter

[![Build Status](https://travis-ci.org/cormac-obrien/richter.svg?branch=devel)](https://travis-ci.org/cormac-obrien/richter)

A modern implementation of the Quake engine in Rust.

![alt tag](https://i.imgur.com/25nOENn.png)

## Status

Richter is in pre-alpha development, so it's still under heavy construction.
However, the client is nearly alpha-ready -- check out the Client section below to see progress.

### Client

The client is capable of connecting to and playing on original Quake servers using `sv_protocol 15`.
To connect to a Quake server, run

```
$ cargo run --release --bin quake-client -- --connect <server_ip>:<server_port>
```

Quake servers run on port 26000 by default.
I can guarantee compatibility with FitzQuake and its derived engines, as I use the QuakeSpasm server for development (just remember `sv_protocol 15`).

The client also supports demo playback using the `--demo` option:

```
$ cargo run --release --bin quake-client -- --demo <demo_file>
```

This works for demos in the PAK archives (e.g. `demo1.dem`) or any demos you happen to have placed in the `id1` directory.

#### Feature checklist

- Networking
  - [x] NetQuake network protocol implementation (`sv_protocol 15`)
    - [x] Connection protocol implemented
    - [x] All in-game server commands handled
    - [x] Carryover between levels
  - [ ] FitzQuake extended protocol support (`sv_protocol 666`)
- Rendering
  - [x] Deferred dynamic lighting
  - [x] Particle effects
  - Brush model (`.bsp`) rendering
    - Textures
      - [x] Static textures
      - [x] Animated textures
      - [x] Alternate animated textures
      - [x] Liquid texture warping
      - [ ] Sky texture scrolling (currently partial support)
    - [x] Lightmaps
    - [x] Occlusion culling
  - Alias model (`.mdl`) rendering
    - [x] Keyframe animation
      - [x] Static keyframes
      - [x] Animated keyframes
    - [ ] Keyframe interpolation
    - [ ] Ambient lighting
    - [ ] Viewmodel rendering
  - UI
    - [x] Console
    - [x] HUD
    - [x] Level intermissions
    - [ ] On-screen messages
    - [ ] Menus
- Sound
  - [x] Loading and playback
  - [x] Entity sound
  - [ ] Ambient sound
  - [x] Spatial attenuation
  - [ ] Stereo spatialization
  - [x] Music
- Console
  - [x] Line editing
  - [x] History browsing
  - [x] Cvar modification
  - [x] Command execution
  - [x] Quake script file execution
- Demos
  - [x] Demo playback
  - [ ] Demo recording
- File formats
  - [x] BSP loader
  - [x] MDL loader
  - [x] SPR loader
  - [x] PAK archive extraction
  - [x] WAD archive extraction

### Server

The Richter server is still in its early stages, so there's no checklist here yet.
However, you can still check out the QuakeC bytecode VM in the [`progs` module](https://github.com/cormac-obrien/richter/blob/devel/src/server/progs/mod.rs).

## Building

Richter makes use of feature gates and compiler plugins, which means you'll need a nightly build of
`rustc`. The simplest way to do this is to download [rustup](https://www.rustup.rs/) and follow the
directions.

Because a Quake distribution contains multiple binaries, this software is packaged as a Cargo
library project. The source files for binaries are located in the `src/bin` directory and can be run
with

    $ cargo run --bin <name>

where `<name>` is the name of the source file without the `.rs` extension.

## Legal

This software is released under the terms of the MIT License (see LICENSE.txt).

This project is in no way affiliated with id Software LLC, Bethesda Softworks LLC, or ZeniMax Media
Inc. Information regarding the Quake trademark can be found at Bethesda's [legal information
page](https://bethesda.net/en/document/legal-information).

Due to licensing restrictions, the data files necessary to run Quake cannot be distributed with this
package. `pak0.pak`, which contains the files for the first episode ("shareware Quake"), can be
retrieved from id's FTP server at `ftp://ftp.idsoftware.com/idstuff/quake`. The full game can be
purchased from a number of retailers including Steam and GOG.


================================================
FILE: shaders/alias.frag
================================================
#version 450

layout(location = 0) in vec3 f_normal;
layout(location = 1) in vec2 f_diffuse;

// set 1: per-entity
layout(set = 1, binding = 1) uniform sampler u_diffuse_sampler;

// set 2: per-texture chain
layout(set = 2, binding = 0) uniform texture2D u_diffuse_texture;

layout(location = 0) out vec4 diffuse_attachment;
layout(location = 1) out vec4 normal_attachment;
layout(location = 2) out vec4 light_attachment;

void main() {
  diffuse_attachment = texture(
    sampler2D(u_diffuse_texture, u_diffuse_sampler),
    f_diffuse
  );

  // TODO: get ambient light from uniform
  light_attachment = vec4(0.25);

  // rescale normal to [0, 1]
  normal_attachment = vec4(f_normal / 2.0 + 0.5, 1.0);
}


================================================
FILE: shaders/alias.vert
================================================
#version 450

layout(location = 0) in vec3 a_position1;
// layout(location = 1) in vec3 a_position2;
layout(location = 2) in vec3 a_normal;
layout(location = 3) in vec2 a_diffuse;

layout(push_constant) uniform PushConstants {
  mat4 transform;
  mat4 model_view;
} push_constants;

layout(location = 0) out vec3 f_normal;
layout(location = 1) out vec2 f_diffuse;

// convert from Quake coordinates
vec3 convert(vec3 from) {
  return vec3(-from.y, from.z, -from.x);
}

void main() {
  f_normal = mat3(transpose(inverse(push_constants.model_view))) * convert(a_normal);
  f_diffuse = a_diffuse;
  gl_Position = push_constants.transform * vec4(convert(a_position1), 1.0);
}


================================================
FILE: shaders/blit.frag
================================================
#version 450

layout(location = 0) in vec2 f_texcoord;

layout(location = 0) out vec4 color_attachment;

layout(set = 0, binding = 0) uniform sampler u_sampler;
layout(set = 0, binding = 1) uniform texture2D u_color;

void main() {
  color_attachment = texture(sampler2D(u_color, u_sampler), f_texcoord);
}


================================================
FILE: shaders/blit.vert
================================================
#version 450

layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_texcoord;

layout(location = 0) out vec2 f_texcoord;

void main() {
  f_texcoord = a_texcoord;
  gl_Position = vec4(a_position * 2.0 - 1.0, 0.0, 1.0);
}


================================================
FILE: shaders/brush.frag
================================================
#version 450
#define LIGHTMAP_ANIM_END (255)

const uint TEXTURE_KIND_REGULAR = 0;
const uint TEXTURE_KIND_WARP = 1;
const uint TEXTURE_KIND_SKY = 2;

const float WARP_AMPLITUDE = 0.15;
const float WARP_FREQUENCY = 0.25;
const float WARP_SCALE = 1.0;

layout(location = 0) in vec3 f_normal;
layout(location = 1) in vec2 f_diffuse; // also used for fullbright
layout(location = 2) in vec2 f_lightmap;
flat layout(location = 3) in uvec4 f_lightmap_anim;

layout(push_constant) uniform PushConstants {
  layout(offset = 128) uint texture_kind;
} push_constants;

// set 0: per-frame
layout(set = 0, binding = 0) uniform FrameUniforms {
    float light_anim_frames[64];
    vec4 camera_pos;
    float time;
    bool r_lightmap;
} frame_uniforms;

// set 1: per-entity
layout(set = 1, binding = 1) uniform sampler u_diffuse_sampler; // also used for fullbright
layout(set = 1, binding = 2) uniform sampler u_lightmap_sampler;

// set 2: per-texture
layout(set = 2, binding = 0) uniform texture2D u_diffuse_texture;
layout(set = 2, binding = 1) uniform texture2D u_fullbright_texture;
layout(set = 2, binding = 2) uniform TextureUniforms {
    uint kind;
} texture_uniforms;

// set 3: per-face
layout(set = 3, binding = 0) uniform texture2D u_lightmap_texture[4];

layout(location = 0) out vec4 diffuse_attachment;
layout(location = 1) out vec4 normal_attachment;
layout(location = 2) out vec4 light_attachment;

vec4 calc_light() {
    vec4 light = vec4(0.0, 0.0, 0.0, 0.0);
    for (int i = 0; i < 4 && f_lightmap_anim[i] != LIGHTMAP_ANIM_END; i++) {
        float map = texture(
            sampler2D(u_lightmap_texture[i], u_lightmap_sampler),
            f_lightmap
        ).r;

        // range [0, 4]
        float style = frame_uniforms.light_anim_frames[f_lightmap_anim[i]];
        light[i] = map * style;
    }

    return light;
}

void main() {
    switch (push_constants.texture_kind) {
        case TEXTURE_KIND_REGULAR:
            diffuse_attachment = texture(
                sampler2D(u_diffuse_texture, u_diffuse_sampler),
                f_diffuse
            );

            float fullbright = texture(
                sampler2D(u_fullbright_texture, u_diffuse_sampler),
                f_diffuse
            ).r;

            if (fullbright != 0.0) {
                light_attachment = vec4(0.25);
            } else {
                light_attachment = calc_light();
            }
            break;

        case TEXTURE_KIND_WARP:
            // note the texcoord transpose here
            vec2 wave1 = 3.14159265359
                * (WARP_SCALE * f_diffuse.ts
                    + WARP_FREQUENCY * frame_uniforms.time);

            vec2 warp_texcoord = f_diffuse.st + WARP_AMPLITUDE
                * vec2(sin(wave1.s), sin(wave1.t));

            diffuse_attachment = texture(
                sampler2D(u_diffuse_texture, u_diffuse_sampler),
                warp_texcoord
            );
            light_attachment = vec4(0.25);
            break;

        case TEXTURE_KIND_SKY:
            vec2 base = mod(f_diffuse + frame_uniforms.time, 1.0);
            vec2 cloud_texcoord = vec2(base.s * 0.5, base.t);
            vec2 sky_texcoord = vec2(base.s * 0.5 + 0.5, base.t);

            vec4 sky_color = texture(
                sampler2D(u_diffuse_texture, u_diffuse_sampler),
                sky_texcoord
            );
            vec4 cloud_color = texture(
                sampler2D(u_diffuse_texture, u_diffuse_sampler),
                cloud_texcoord
            );

            // 0.0 if black, 1.0 otherwise
            float cloud_factor;
            if (cloud_color.r + cloud_color.g + cloud_color.b == 0.0) {
                cloud_factor = 0.0;
            } else {
                cloud_factor = 1.0;
            }
            diffuse_attachment = mix(sky_color, cloud_color, cloud_factor);
            light_attachment = vec4(0.25);
            break;

        // not possible
        default:
            break;
    }

    // rescale normal to [0, 1]
    normal_attachment = vec4(f_normal / 2.0 + 0.5, 1.0);
}


================================================
FILE: shaders/brush.vert
================================================
#version 450

const uint TEXTURE_KIND_NORMAL = 0;
const uint TEXTURE_KIND_WARP = 1;
const uint TEXTURE_KIND_SKY = 2;

layout(location = 0) in vec3 a_position;
layout(location = 1) in vec3 a_normal;
layout(location = 2) in vec2 a_diffuse;
layout(location = 3) in vec2 a_lightmap;
layout(location = 4) in uvec4 a_lightmap_anim;

layout(push_constant) uniform PushConstants {
  mat4 transform;
  mat4 model_view;
  uint texture_kind;
} push_constants;

layout(location = 0) out vec3 f_normal;
layout(location = 1) out vec2 f_diffuse;
layout(location = 2) out vec2 f_lightmap;
layout(location = 3) out uvec4 f_lightmap_anim;

layout(set = 0, binding = 0) uniform FrameUniforms {
    float light_anim_frames[64];
    vec4 camera_pos;
    float time;
} frame_uniforms;

// convert from Quake coordinates
vec3 convert(vec3 from) {
  return vec3(-from.y, from.z, -from.x);
}

void main() {
    if (push_constants.texture_kind == TEXTURE_KIND_SKY) {
        vec3 dir = a_position - frame_uniforms.camera_pos.xyz;
        dir.z *= 3.0;

        // the coefficients here are magic taken from the Quake source
        float len = 6.0 * 63.0 / length(dir);
        dir = vec3(dir.xy * len, dir.z);
        f_diffuse = (mod(8.0 * frame_uniforms.time, 128.0) + dir.xy) / 128.0;
    } else {
        f_diffuse = a_diffuse;
    }

    f_normal = mat3(transpose(inverse(push_constants.model_view))) * convert(a_normal);
    f_lightmap = a_lightmap;
    f_lightmap_anim = a_lightmap_anim;
    gl_Position = push_constants.transform * vec4(convert(a_position), 1.0);

}


================================================
FILE: shaders/deferred.frag
================================================
#version 450

// if this is changed, it must also be changed in client::entity
const uint MAX_LIGHTS = 32;

layout(location = 0) in vec2 a_texcoord;

layout(set = 0, binding = 0) uniform sampler u_sampler;
layout(set = 0, binding = 1) uniform texture2DMS u_diffuse;
layout(set = 0, binding = 2) uniform texture2DMS u_normal;
layout(set = 0, binding = 3) uniform texture2DMS u_light;
layout(set = 0, binding = 4) uniform texture2DMS u_depth;
layout(set = 0, binding = 5) uniform DeferredUniforms {
  mat4 inv_projection;
  uint light_count;
  uint _pad1;
  uvec2 _pad2;
  vec4 lights[MAX_LIGHTS];
} u_deferred;

layout(location = 0) out vec4 color_attachment;

vec3 dlight_origin(vec4 dlight) {
  return dlight.xyz;
}

float dlight_radius(vec4 dlight) {
  return dlight.w;
}

vec3 reconstruct_position(float depth) {
  float x = a_texcoord.s * 2.0 - 1.0;
  float y = (1.0 - a_texcoord.t) * 2.0 - 1.0;
  vec4 ndc = vec4(x, y, depth, 1.0);
  vec4 view = u_deferred.inv_projection * ndc;
  return view.xyz / view.w;
}

void main() {
  ivec2 dims = textureSize(sampler2DMS(u_diffuse, u_sampler));
  ivec2 texcoord = ivec2(vec2(dims) * a_texcoord);
  vec4 in_color = texelFetch(sampler2DMS(u_diffuse, u_sampler), texcoord, gl_SampleID);

  // scale from [0, 1] to [-1, 1]
  vec3 in_normal = 2.0
    * texelFetch(sampler2DMS(u_normal, u_sampler), texcoord, gl_SampleID).xyz
    - 1.0;

  // Double to restore overbright values.
  vec4 in_light = 2.0 * texelFetch(sampler2DMS(u_light, u_sampler), texcoord, gl_SampleID);

  float in_depth = texelFetch(sampler2DMS(u_depth, u_sampler), texcoord, gl_SampleID).x;
  vec3 position = reconstruct_position(in_depth);

  vec4 out_color = in_color;

  float light = in_light.x + in_light.y + in_light.z + in_light.w;
  for (uint i = 0; i < u_deferred.light_count && i < MAX_LIGHTS; i++) {
    vec4 dlight = u_deferred.lights[i];
    vec3 dir = normalize(position - dlight_origin(dlight));
    float dist = abs(distance(dlight_origin(dlight), position));
    float radius = dlight_radius(dlight);

    if (dist < radius && dot(dir, in_normal) < 0.0) {
      // linear attenuation
      light += (radius - dist) / radius;
    }
  }

  color_attachment = vec4(light * out_color.rgb, 1.0);
}


================================================
FILE: shaders/deferred.vert
================================================
#version 450

layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_texcoord;

layout(location = 0) out vec2 f_texcoord;

void main() {
  f_texcoord = a_texcoord;
  gl_Position = vec4(a_position * 2.0 - 1.0, 0.0, 1.0);
}


================================================
FILE: shaders/glyph.frag
================================================
#version 450
#extension GL_EXT_nonuniform_qualifier : require

layout(location = 0) in vec2 f_texcoord;
layout(location = 1) flat in uint f_layer;

layout(location = 0) out vec4 output_attachment;

layout(set = 0, binding = 0) uniform sampler u_sampler;
layout(set = 0, binding = 1) uniform texture2D u_texture[256];

void main() {
  vec4 color = texture(sampler2D(u_texture[f_layer], u_sampler), f_texcoord);
  if (color.a == 0) {
    discard;
  } else {
    output_attachment = color;
  }
}


================================================
FILE: shaders/glyph.vert
================================================
#version 450

// vertex rate
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_texcoord;

// instance rate
layout(location = 2) in vec2 a_instance_position;
layout(location = 3) in vec2 a_instance_scale;
layout(location = 4) in uint a_instance_layer;

layout(location = 0) out vec2 f_texcoord;
layout(location = 1) out uint f_layer;

void main() {
  f_texcoord = a_texcoord;
  f_layer = a_instance_layer;
  gl_Position = vec4(a_instance_scale * a_position + a_instance_position, 0.0, 1.0);
}


================================================
FILE: shaders/particle.frag
================================================
#version 450

layout(location = 0) in vec2 f_texcoord;

layout(push_constant) uniform PushConstants {
  layout(offset = 64) uint color;
} push_constants;

layout(set = 0, binding = 0) uniform sampler u_sampler;
layout(set = 0, binding = 1) uniform texture2D u_texture[256];

layout(location = 0) out vec4 diffuse_attachment;
// layout(location = 1) out vec4 normal_attachment;
layout(location = 2) out vec4 light_attachment;

void main() {
  vec4 tex_color = texture(
    sampler2D(u_texture[push_constants.color], u_sampler),
    f_texcoord
  );

  if (tex_color.a == 0.0) {
    discard;
  }

  diffuse_attachment = tex_color;
  light_attachment = vec4(0.25);
}


================================================
FILE: shaders/particle.vert
================================================
#version 450

layout(location = 0) in vec3 a_position;
layout(location = 1) in vec2 a_texcoord;

layout(push_constant) uniform PushConstants {
  mat4 transform;
} push_constants;

layout(location = 0) out vec2 f_texcoord;

void main() {
  f_texcoord = a_texcoord;
  gl_Position = push_constants.transform * vec4(a_position, 1.0);
}


================================================
FILE: shaders/postprocess.frag
================================================
#version 450

layout(location = 0) in vec2 a_texcoord;

layout(location = 0) out vec4 color_attachment;

layout(set = 0, binding = 0) uniform sampler u_sampler;
layout(set = 0, binding = 1) uniform texture2DMS u_color;
layout(set = 0, binding = 2) uniform PostProcessUniforms {
  vec4 color_shift;
} postprocess_uniforms;

void main() {
  ivec2 dims = textureSize(sampler2DMS(u_color, u_sampler));
  ivec2 texcoord = ivec2(vec2(dims) * a_texcoord);

  vec4 in_color = texelFetch(sampler2DMS(u_color, u_sampler), texcoord, gl_SampleID);

  float src_factor = postprocess_uniforms.color_shift.a;
  float dst_factor = 1.0 - src_factor;
  vec4 color_shifted = src_factor * postprocess_uniforms.color_shift
    + dst_factor * in_color;

  color_attachment = color_shifted;
}


================================================
FILE: shaders/postprocess.vert
================================================
#version 450

layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_texcoord;

layout(location = 0) out vec2 f_texcoord;

void main() {
  f_texcoord = a_texcoord;
  gl_Position = vec4(a_position * 2.0 - 1.0, 0.0, 1.0);
}


================================================
FILE: shaders/quad.frag
================================================
#version 450

layout(location = 0) in vec2 f_texcoord;

layout(location = 0) out vec4 color_attachment;

layout(set = 0, binding = 0) uniform sampler quad_sampler;
layout(set = 1, binding = 0) uniform texture2D quad_texture;

void main() {
  vec4 color = texture(sampler2D(quad_texture, quad_sampler), f_texcoord);
  if (color.a == 0) {
    discard;
  } else {
    color_attachment = color;
  }
}


================================================
FILE: shaders/quad.vert
================================================
#version 450

layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_texcoord;

layout(location = 0) out vec2 f_texcoord;

layout(set = 2, binding = 0) uniform QuadUniforms {
  mat4 transform;
} quad_uniforms;

void main() {
  f_texcoord = a_texcoord;
  gl_Position = quad_uniforms.transform * vec4(a_position, 0.0, 1.0);
}


================================================
FILE: shaders/sprite.frag
================================================
#version 450

layout(location = 0) in vec3 f_normal;
layout(location = 1) in vec2 f_diffuse;

// set 1: per-entity
layout(set = 1, binding = 1) uniform sampler u_diffuse_sampler;

// set 2: per-texture chain
layout(set = 2, binding = 0) uniform texture2D u_diffuse_texture;

layout(location = 0) out vec4 diffuse_attachment;
layout(location = 1) out vec4 normal_attachment;
layout(location = 2) out vec4 light_attachment;

void main() {
  diffuse_attachment = texture(sampler2D(u_diffuse_texture, u_diffuse_sampler), f_diffuse);

  // rescale normal to [0, 1]
  normal_attachment = vec4(f_normal / 2.0 + 0.5, 1.0);
  light_attachment = vec4(1.0, 1.0, 1.0, 1.0);
}


================================================
FILE: shaders/sprite.vert
================================================
#version 450

layout(location = 0) in vec3 a_position;
layout(location = 1) in vec3 a_normal;
layout(location = 2) in vec2 a_diffuse;

layout(location = 0) out vec3 f_normal;
layout(location = 1) out vec2 f_diffuse;

layout(set = 0, binding = 0) uniform FrameUniforms {
  float light_anim_frames[64];
  vec4 camera_pos;
  float time;
} frame_uniforms;

layout(set = 1, binding = 0) uniform EntityUniforms {
  mat4 u_transform;
  mat4 u_model;
} entity_uniforms;

// convert from Quake coordinates
vec3 convert(vec3 from) {
  return vec3(-from.y, from.z, -from.x);
}

void main() {
  f_normal = mat3(transpose(inverse(entity_uniforms.u_model))) * convert(a_normal);
  f_diffuse = a_diffuse;
  gl_Position = entity_uniforms.u_transform
    * vec4(convert(a_position), 1.0);
}


================================================
FILE: site/config.toml
================================================
# The URL the site will be built for
base_url = "http://c-obrien.org/richter"

# Whether to automatically compile all Sass files in the sass directory
compile_sass = true

# Whether to do syntax highlighting
# Theme can be customised by setting the `highlight_theme` variable to a theme supported by Gutenberg
highlight_code = true

# Whether to build a search index to be used later on by a JavaScript library
build_search_index = true

theme = "richter"

[extra]
# Put all your custom variables here


================================================
FILE: site/content/_index.md
================================================
+++
title = "Richter"
template = "index.html"
description = "An open-source Quake engine written in Rust"
date = 2018-04-22
+++

# RICHTER

## A modern Quake engine

<ul class="links">
<li><a href="https://github.com/cormac-obrien/richter">Github</a></li>
<li><a href="./blog">Blog</a></li>
</ul>

</div>

<div class="intro">

Richter is a brand-new Quake engine, built from the ground up in
[Rust](https://rust-lang.org). Currently under active development, Richter aims
to accurately reproduce the original Quake feel while removing some of the
cruft that might prevent new players from enjoying a landmark experience.

</div>



================================================
FILE: site/content/blog/2018-04-24.md
================================================
+++
title = "The New Site and the Way Forward"
template = "blog-post.html"
date = 2018-04-24
+++

I've started rebuilding the site with [Gutenberg](https://www.getgutenberg.io/)
now that I actually have something to show for the past couple years (!) of
on-and-off work. I figure a dev blog will be good to have when I look back on
this project, even if I don't update it that often (the blog, not the project).
It'll probably take me a while to get the site in order since my HTML/CSS skills
are rusty, but the old site was impossible to maintain so this ought to make
things easier.

As for the project itself, the client is coming along nicely -- I'm hoping to
reach a playable state by the end of the year, even if there are still some
graphical bugs. Now that the infrastructure is there for networking, input,
rendering, and sound, I can start work on the little things. The devil is in the
details, etc.

Defining the ultimate scope of the first alpha release is probably going to be
one of the biggest challenges. There are so many features I could add to the
engine, and I suspect many of them are far more complicated than they seem on
the surface. Failed past projects have taught me to be wary of feature creep,
so the alpha will most likely just be a working client and server -- no tools,
no installers, no plugin support or anything like that. With any luck I'll be
there soon.



================================================
FILE: site/content/blog/2018-04-26/index.md
================================================
+++
title = "HUD Updates and Timing Bugs"
template = "blog-post.html"
date = 2018-04-26
+++

![HUD Screenshot][1]

The HUD now renders armor, health and current ammo counts in addition to the
per-ammo type display at the top. The latter uses [conchars][2], which, as the
name suggests, are used for rendering text to the in-game console. Now that I
can load and display these I can start working on the console, which ought to
make debugging a great deal easier.

Unfortunately, the client is still plagued by a bug with position lerping that
causes the geometry to jitter back and forth. This is most likely caused by bad
time delta calculations in `Client::update_time()` ([Github][3]), but I haven't
been able to pinpoint the exact problem -- only that the lerp factor seems to go
out of the expected range of `[0, 1)` once per server frame. I'll keep an eye on
it.

[1]: http://c-obrien.org/richter/blog/2018-04-26/hud-screenshot.png
[2]: https://quakewiki.org/wiki/Quake_font
[3]: https://github.com/cormac-obrien/richter/blob/12b1d9448cf9c3cfed013108fe0866cb78755902/src/client/mod.rs#L1499-L1552


================================================
FILE: site/content/blog/2018-05-12/index.md
================================================
+++
title = "Shared Ownership of Rendering Resources"
template = "blog-post.html"
date = 2018-05-12
+++

Among the most challenging design decisions in writing the rendering code has been the issue of
ownership. In order to avoid linking the rendering logic too closely with the data, most of the
rendering is done by separate `Renderer` objects (i.e., to render an `AliasModel`, one must first
create an `AliasRenderer`).

The process of converting on-disk model data to renderable format is fairly complex. Brush models
are stored in a format designed for the Quake software renderer (which Michael Abrash explained
[quite nicely][1]), while alias models have texture oddities that make it difficult to render them
from a vertex buffer. In addition, all textures are composed of 8-bit indices into `gfx/palette.lmp`
and must be converted to RGB in order to upload them to the GPU. Richter interleaves the position
and texture coordinate data before upload.

The real challenge is in determining where to store the objects for resource creation (e.g.
`gfx::Factory`) and the resource handles (e.g. `gfx::handle::ShaderResourceView`). Some of these
objects are model-specific -- a particular texture might belong to one model, and thus can be stored
in that model's `Renderer` -- but others need to be more widely available.

The most obvious example of this is the vertex buffer used for rendering quads. This is conceptually
straightforward, but there are several layers of a renderer that might need this functionality.
The `ConsoleRenderer` needs it in order to render the console background, but also needs a
`GlyphRenderer` to render console output -- and the `GlyphRenderer` needs to be able to render
textured quads. The `ConsoleRenderer` could own the `GlyphRenderer`, but the `HudRenderer` also
needs access to render ammo counts.

This leads to a rather complex network of `Rc`s, where many different objects own the basic building
blocks that make up the rendering system. It isn't bad design *per se*, but it's a little difficult
to follow, and I'm hoping that once I have the renderer fully completed I can refine the architecture
to something more elegant.

[1]: https://www.bluesnews.com/abrash/


================================================
FILE: site/content/blog/2018-07-20/index.md
================================================
+++
title = "Complications with Cross-Platform Input Handling"
template = "blog-post.html"
date = 2018-07-20
+++

It was bound to happen eventually, but the input handling module is the first
part of the project to display different behavior across platforms. [winit][1]
provides a fairly solid basis for input handling, but Windows and Linux differ
in terms of what sort of event is delivered to the program.

Initially, I used `WindowEvent`s for everything. This works perfectly well for
keystrokes and mouse clicks, but mouse movement may still have acceleration
applied, which is undesirable for camera control. `winit` also offers
`DeviceEvent`s for this purpose. I tried just handling mouse movement with raw
input, keeping all other inputs in `WindowEvent`s, but it seems that handling
`DeviceEvent`s on Linux causes the `WindowEvent`s to be eaten.

The next obvious solution is to simply handle everything with `DeviceEvent`s,
but this presents additional problems. First, Windows doesn't seem to even
deliver keyboard input as a `DeviceEvent` -- keyboard input still needs to be
polled as a `WindowEvent`. It also means that window focus has to be handled
manually, since `DeviceEvent`s are delivered regardless of whether the window
is focused or not.

To add to the complexity of this problem, apparently not all window managers
are well-behaved when it comes to determining focus. I run [i3wm][2] on my
Linux install, and it doesn't deliver `WindowEvent::Focused` events when
toggling focus or switching workspaces. This will have to remain an unsolved
problem for the time being.

[1]: https://github.com/tomaka/winit/
[2]: https://i3wm.org/


================================================
FILE: site/content/blog/_index.md
================================================
+++
title = "Blog"
template = "blog.html"
description = "Richter project development log"
sort_by = "date"
+++

## Musings about the project and my experience with it.

---


================================================
FILE: site/content/index.html
================================================
<!DOCTYPE html>
<html>
 <head>
  <meta charset="utf-8">
  <title>richter</title>
  <link rel="stylesheet" type="text/css" href="hack.css" />
  <link rel="stylesheet" type="text/css" href="solarized-dark.css" />
 </head>
 <body class="hack solarized-dark">
  <div class="grid -center">

   <!-- spacer -->
   <div class="cell -3of12"></div>

   <!-- sidebar -->
   <div class="cell -2of12" style="padding-right:4.15%;">
    <h1>richter</h1>
    <div class="menu">
     <a class="menu-item active">home</a>
     <a class="menu-item">richter docs</a>
     <a class="menu-item" href="/richter/quake">quake docs</a>
    </div>
   </div>

   <!-- main content -->
   <div class="cell -4of12">
    <h1>richter - an open-source Quake engine</h1>
    <h2>about</h2>
    <p>Richter is an open-source reimplementation of the original Quake engine. The aims of the
     project are as follows:</p>
    <ul>
     <li>produce a high-performance server which accurately implements the original game behavior and
      the QuakeWorld network protocol</li>
     <li>produce a client which recreates the original Quake experience</li>
     <li>maintain a clean, legible code base to serve as an example for other Rust software,
      particularly games</li>
     <li>create comprehensive technical documentation for the original Quake engine, building upon
      the work of <a href="http://fabiensanglard.net/quakeSource/">Fabien Sanglard</a>, the
      <a href="http://www.gamers.org/dEngine/quake/spec/">Unofficial Quake Specs</a> team and
      others</li>
    </ul>
    <h2>status</h2>
    <p>Richter is currently in pre-alpha development. You can keep up with the project at its
     <a href="https://github.com/cormac-obrien/richter">github repository</a>.</p>
   </div>

   <!-- spacer -->
   <div class="cell -3of12"></div>

  </div>
 </body>
</html>


================================================
FILE: site/sass/_base.scss
================================================
@import "reset";

@font-face {
    font-family: "Renner";
    src: local('Renner*'), local('Renner-Book'),
    url("fonts/Renner-Book.woff2") format('woff2'),
    url("fonts/Renner-Book.woff") format('woff');
}

@font-face {
    font-family: "Roboto";
    font-style: normal;
    font-weight: 400;
    src: local("Roboto"), local("Roboto-Regular"),
    url("fonts/roboto-v18-latin-regular.woff2") format("woff2"),
    url("fonts/roboto-v18-latin-regular.woff") format("woff");
}

@font-face {
    font-family: "DejaVu Sans Mono";
    font-style: normal;
    src: local("DejaVu Sans Mono"),
    url("fonts/DejaVuSansMono.woff2") format("woff2"),
    url("fonts/DejaVuSansMono.woff") format("woff");
}

$display-font-stack: Renner, Futura, Arial, sans-serif;
$text-font-stack: Roboto, Arial, sans-serif;
$code-font-stack: "DejaVu Sans Mono", "Consolas", monospace;

$bg-color: #1F1F1F;
$bg-hl-color: #0F0F0F;
$text-color: #ABABAB;
$link-color: #EFEFEF;

$main-content-width: 40rem;

html {
    height: 100%;
}

body {
    font: 100% $text-font-stack;
    color: $text-color;
    background-color: $bg-color;

    display: grid;
    grid-template-areas:
        "header"
        "main"
        "footer";
    grid-template-rows: 0px 1fr auto;

    margin: 100px auto;
    max-width: $main-content-width;
    justify-items: center;

    min-height: 100%;
}

p {
    margin: 1rem auto;
    line-height: 1.5;
    text-align: justify;
}

code {
    font: 100% $code-font-stack;
    border: 1px solid $text-color;
    border-radius: 4px;
    padding: 0 0.25rem 0;
    margin: 0 0.25rem 0;
}

a {
    color: $link-color;
    text-decoration: none;
}

hr {
    margin: 1rem auto;
    border: 0;
    border-top: 1px solid $text-color;
    border-bottom: 1px solid $bg-hl-color;
}

img {
    max-width: 100%;
}

footer {
    font: 0.75rem $text-font-stack;
    text-align: center;

    p {
        text-align: center;
    }
}


================================================
FILE: site/sass/_reset.scss
================================================
/* http://meyerweb.com/eric/tools/css/reset/ 
   v2.0 | 20110126
   License: none (public domain)
*/

html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed, 
figure, figcaption, footer, header, hgroup, 
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
	margin: 0;
	padding: 0;
	border: 0;
	font-size: 100%;
	font: inherit;
	vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure, 
footer, header, hgroup, menu, nav, section {
	display: block;
}
body {
	line-height: 1;
}
ol, ul {
	list-style: none;
}
blockquote, q {
	quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
	content: '';
	content: none;
}
table {
	border-collapse: collapse;
	border-spacing: 0;
}


================================================
FILE: site/sass/blog-post.scss
================================================
@import "base";

body {
    margin: 0 auto;
    display: grid;

    max-width: $main-content-width;

    .date {
        font-family: $text-font-stack;
    }

    .title {
        font-size: 2rem;
    }
}


================================================
FILE: site/sass/blog.scss
================================================
@import "base";

body {
    // title of the page
    h1 {
        margin: 1rem auto;
        text-align: center;
        font: 3rem $display-font-stack;
    }

    // subtitle of the page
    h2 {
        margin: 1rem auto;
        text-align: center;
        font: 1.5rem $display-font-stack;
    }

    // post name
    h3 {
        font-weight: bold;
        font-family: $text-font-stack;
    }

    // post date
    h4 {
        font-style: italic;
        font-family: $text-font-stack;
    }

    .post {
        margin-bottom: 2rem;
    }
}



================================================
FILE: site/sass/style.scss
================================================
@import "base";

$body-margin-top: 150px;

body {
    margin: $body-margin-top auto;

    padding: $body-margin-top / 2 0;

    background-image: url(richter-insignia.svg);
    background-repeat: no-repeat;
    background-position: 50% $body-margin-top;
    background-size: 400px;

    max-width: $main-content-width;
    justify-items: center;

    h1 {
        $heading-font-size: 6rem;

        margin: ($heading-font-size / 3) auto ($heading-font-size / 3);

        font-family: $display-font-stack;
        font-size: $heading-font-size;
        text-align: center;
        letter-spacing: 1rem;
    }

    h2 {
        $subheading-font-size: 3rem;

        margin: ($subheading-font-size / 3) auto ($subheading-font-size / 3);

        font-family: $display-font-stack;
        font-size: $subheading-font-size;
        text-align: center;
    }

    .links {
        margin: 40px 0;

        font-family: $text-font-stack;
        font-size: 1.5rem;
        text-align: center;

        li {
            display: inline;
            list-style: none;

            // put dots between items
            &:not(:first-child):before {
                content: " · ";
            }
        }
    }

    .intro {
        margin-top: 40px;

        font-family: $text-font-stack;
        font-size: 1rem;
        text-align: justify;
    }
}



================================================
FILE: site/templates/home.html
================================================
<html>
  <head>
    <title>{% block title %}{% endblock title %}</title>
  </head>
</html>


================================================
FILE: site/themes/richter/templates/base.html
================================================
<!DOCTYPE html>
<html lang="en-us">
  <head>
    {% block head %}
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="{{ config.description }}">
    {% block style %}
    <link rel="stylesheet" href="{{ get_url(path='style.css', trailing_slash=false) }}" />
    {% endblock style %}
    <title>{% block title %}{% endblock title %} – Richter</title>
    {% endblock head %}
  </head>
  <body>
    <header>{% block header %}{% endblock header %}</header>
    <main>{% block main %}{% endblock main %}</main>

    <footer>
      {% block footer %}
      <p>
        Copyright © 2018 Cormac O'Brien.
        <br />
        This work is licensed under a
        <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">
          Creative Commons Attribution-ShareAlike 4.0 International License
        </a>.
      </p>

      {% endblock footer %}
    </footer>
  </body>
</html>


================================================
FILE: site/themes/richter/templates/blog-post.html
================================================
{% extends "base.html" %}
{% block style %}
<link rel="stylesheet" href="{{ get_url(path='blog-post.css', trailing_slash=false) }}" />
{% endblock style %}
{% block title %}{{ page.title }}{% endblock title%}

{% block main %}
<h4>{{ page.date }}</h4>
<h2>{{ page.title }}</h2>
{{ page.content | safe }}
{% endblock main %}


================================================
FILE: site/themes/richter/templates/blog.html
================================================
{% extends "base.html" %}

{% block style %}
<link rel="stylesheet" href="{{ get_url(path='blog.css', trailing_slash=false) }}" />
{% endblock style %}
{% block title %}{{ section.title }}{% endblock title %}
{% block main %}
<h1>{{ section.title }}</h1>
{{ section.content | safe }}
{% for page in section.pages %}
<div class="post">
  <h3><a href="{{ page.permalink }}">{{ page.title }}</a></h3>
  <h4>{{ page.date }}</h4>
  {{ page.content | safe }}
</div>
{% endfor %}
{% endblock main %}


================================================
FILE: site/themes/richter/templates/index.html
================================================
{% extends "base.html" %}

{% block title %}Home{% endblock title %}
{% block style %}
<link rel="stylesheet" href="{{ get_url(path='style.css', trailing_slash=false) }}" />
{% endblock style %}
{% block main %}{{ section.content | safe }}{% endblock main %}


================================================
FILE: site/themes/richter/theme.toml
================================================
name = "richter"
description = "theme for the Richter webpage"
license = "MIT"
min_version = "0.2.2"

[author]
name = "Mac O'Brien"
homepage = "http://c-obrien.org"

================================================
FILE: specifications.md
================================================
# Specifications for the Original Quake (idTech 2) Engine

### Coordinate Systems

Quake's coordinate system specifies its axes as follows:

- The x-axis specifies depth.
- The y-axis specifies width.
- The z-axis specifies height.

This contrasts with the OpenGL coordinate system, in which:

- The x-axis specifies width.
- The y-axis specifies height.
- The z-axis specifies depth (inverted).

Thus, to convert between the coordinate systems:

          x <-> -z
    Quake y <->  x OpenGL
          z <->  y

           x <->  y
    OpenGL y <->  z Quake
           z <-> -x


================================================
FILE: src/bin/quake-client/capture.rs
================================================
use std::{
    cell::RefCell,
    fs::File,
    io::BufWriter,
    num::NonZeroU32,
    path::{Path, PathBuf},
    rc::Rc,
};

use richter::client::render::Extent2d;

use chrono::Utc;

const BYTES_PER_PIXEL: u32 = 4;

/// Implements the "screenshot" command.
///
/// This function returns a boxed closure which sets the `screenshot_path`
/// argument to `Some` when called.
pub fn cmd_screenshot(
    screenshot_path: Rc<RefCell<Option<PathBuf>>>,
) -> Box<dyn Fn(&[&str]) -> String> {
    Box::new(move |args| {
        let path = match args.len() {
            // TODO: make default path configurable
            0 => PathBuf::from(format!("richter-{}.png", Utc::now().format("%FT%H-%M-%S"))),
            1 => PathBuf::from(args[0]),
            _ => {
                log::error!("Usage: screenshot [PATH]");
                return "Usage: screenshot [PATH]".to_owned();
            }
        };

        screenshot_path.replace(Some(path));
        String::new()
    })
}

pub struct Capture {
    // size of the capture image
    capture_size: Extent2d,

    // width of a row in the buffer, must be a multiple of 256 for mapped reads
    row_width: u32,

    // mappable buffer
    buffer: wgpu::Buffer,
}

impl Capture {
    pub fn new(device: &wgpu::Device, capture_size: Extent2d) -> Capture {
        // bytes_per_row must be a multiple of 256
        // 4 bytes per pixel, so width must be multiple of 64
        let row_width = (capture_size.width + 63) / 64 * 64;

        let buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("capture buffer"),
            size: (row_width * capture_size.height * BYTES_PER_PIXEL) as u64,
            usage: wgpu::BufferUsage::COPY_DST | wgpu::BufferUsage::MAP_READ,
            mapped_at_creation: false,
        });

        Capture {
            capture_size,
            row_width,
            buffer,
        }
    }

    pub fn copy_from_texture(
        &self,
        encoder: &mut wgpu::CommandEncoder,
        texture: wgpu::ImageCopyTexture,
    ) {
        encoder.copy_texture_to_buffer(
            texture,
            wgpu::ImageCopyBuffer {
                buffer: &self.buffer,
                layout: wgpu::ImageDataLayout {
                    offset: 0,
                    bytes_per_row: Some(NonZeroU32::new(self.row_width * BYTES_PER_PIXEL).unwrap()),
                    rows_per_image: None,
                },
            },
            self.capture_size.into(),
        );
    }

    pub fn write_to_file<P>(&self, device: &wgpu::Device, path: P)
    where
        P: AsRef<Path>,
    {
        let mut data = Vec::new();
        {
            // map the buffer
            // TODO: maybe make this async so we don't force the whole program to block
            let slice = self.buffer.slice(..);
            let map_future = slice.map_async(wgpu::MapMode::Read);
            device.poll(wgpu::Maintain::Wait);
            futures::executor::block_on(map_future).unwrap();

            // copy pixel data
            let mapped = slice.get_mapped_range();
            for row in mapped.chunks(self.row_width as usize * BYTES_PER_PIXEL as usize) {
                // don't copy padding
                for pixel in
                    (&row[..self.capture_size.width as usize * BYTES_PER_PIXEL as usize]).chunks(4)
                {
                    // swap BGRA->RGBA
                    data.extend_from_slice(&[pixel[2], pixel[1], pixel[0], pixel[3]]);
                }
            }
        }
        self.buffer.unmap();

        let f = File::create(path).unwrap();
        let mut png_encoder = png::Encoder::new(
            BufWriter::new(f),
            self.capture_size.width,
            self.capture_size.height,
        );
        png_encoder.set_color(png::ColorType::RGBA);
        png_encoder.set_depth(png::BitDepth::Eight);
        let mut writer = png_encoder.write_header().unwrap();
        writer.write_image_data(&data).unwrap();
    }
}


================================================
FILE: src/bin/quake-client/game.rs
================================================
// Copyright © 2018 Cormac O'Brien
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

use std::{cell::RefCell, path::PathBuf, rc::Rc};

use crate::{
    capture::{cmd_screenshot, Capture},
    trace::{cmd_trace_begin, cmd_trace_end},
};

use richter::{
    client::{
        input::Input,
        menu::Menu,
        render::{
            Extent2d, GraphicsState, RenderTarget as _, RenderTargetResolve as _, SwapChainTarget,
        },
        trace::TraceFrame,
        Client, ClientError,
    },
    common::console::{CmdRegistry, Console, CvarRegistry},
};

use chrono::Duration;
use failure::Error;
use log::info;

pub struct Game {
    cvars: Rc<RefCell<CvarRegistry>>,
    cmds: Rc<RefCell<CmdRegistry>>,
    input: Rc<RefCell<Input>>,
    pub client: Client,

    // if Some(v), trace is in progress
    trace: Rc<RefCell<Option<Vec<TraceFrame>>>>,

    // if Some(path), take a screenshot and save it to path
    screenshot_path: Rc<RefCell<Option<PathBuf>>>,
}

impl Game {
    pub fn new(
        cvars: Rc<RefCell<CvarRegistry>>,
        cmds: Rc<RefCell<CmdRegistry>>,
        input: Rc<RefCell<Input>>,
        client: Client,
    ) -> Result<Game, Error> {
        // set up input commands
        input.borrow().register_cmds(&mut cmds.borrow_mut());

        // set up screenshots
        let screenshot_path = Rc::new(RefCell::new(None));
        cmds.borrow_mut()
            .insert("screenshot", cmd_screenshot(screenshot_path.clone()))
            .unwrap();

        // set up frame tracing
        let trace = Rc::new(RefCell::new(None));
        cmds.borrow_mut()
            .insert("trace_begin", cmd_trace_begin(trace.clone()))
            .unwrap();
        cmds.borrow_mut()
            .insert("trace_end", cmd_trace_end(cvars.clone(), trace.clone()))
            .unwrap();

        Ok(Game {
            cvars,
            cmds,
            input,
            client,
            trace,
            screenshot_path,
        })
    }

    // advance the simulation
    pub fn frame(&mut self, gfx_state: &GraphicsState, frame_duration: Duration) {
        use ClientError::*;

        match self.client.frame(frame_duration, gfx_state) {
            Ok(()) => (),
            Err(e) => match e {
                Cvar(_)
                | UnrecognizedProtocol(_)
                | NoSuchClient(_)
                | NoSuchPlayer(_)
                | NoSuchEntity(_)
                | NullEntity
                | EntityExists(_)
                | InvalidViewEntity(_)
                | TooManyStaticEntities
                | NoSuchLightmapAnimation(_)
                | Model(_)
                | Network(_)
                | Sound(_)
                | Vfs(_) => {
                    log::error!("{}", e);
                    self.client.disconnect();
                }

                _ => panic!("{}", e),
            },
        };

        if let Some(ref mut game_input) = self.input.borrow_mut().game_input_mut() {
            self.client
                .handle_input(game_input, frame_duration)
                .unwrap();
        }

        // if there's an active trace, record this frame
        if let Some(ref mut trace_frames) = *self.trace.borrow_mut() {
            trace_frames.push(
                self.client
                    .trace(&[self.client.view_entity_id().unwrap()])
                    .unwrap(),
            );
        }
    }

    pub fn render(
        &mut self,
        gfx_state: &GraphicsState,
        color_attachment_view: &wgpu::TextureView,
        width: u32,
        height: u32,
        console: &Console,
        menu: &Menu,
    ) {
        info!("Beginning render pass");
        let mut encoder = gfx_state
            .device()
            .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });

        // render world, hud, console, menus
        self.client
            .render(
                gfx_state,
                &mut encoder,
                width,
                height,
                menu,
                self.input.borrow().focus(),
            )
            .unwrap();

        // screenshot setup
        let capture = self.screenshot_path.borrow().as_ref().map(|_| {
            let cap = Capture::new(gfx_state.device(), Extent2d { width, height });
            cap.copy_from_texture(
                &mut encoder,
                wgpu::ImageCopyTexture {
                    texture: gfx_state.final_pass_target().resolve_attachment(),
                    mip_level: 0,
                    origin: wgpu::Origin3d::ZERO,
                },
            );
            cap
        });

        // blit to swap chain
        {
            let swap_chain_target = SwapChainTarget::with_swap_chain_view(color_attachment_view);
            let blit_pass_builder = swap_chain_target.render_pass_builder();
            let mut blit_pass = encoder.begin_render_pass(&blit_pass_builder.descriptor());
            gfx_state.blit_pipeline().blit(gfx_state, &mut blit_pass);
        }

        let command_buffer = encoder.finish();
        {
            gfx_state.queue().submit(vec![command_buffer]);
            gfx_state.device().poll(wgpu::Maintain::Wait);
        }

        // write screenshot if requested and clear screenshot path
        self.screenshot_path.replace(None).map(|path| {
            capture
                .as_ref()
                .unwrap()
                .write_to_file(gfx_state.device(), path)
        });
    }
}

impl std::ops::Drop for Game {
    fn drop(&mut self) {
        let _ = self.cmds.borrow_mut().remove("trace_begin");
        let _ = self.cmds.borrow_mut().remove("trace_end");
    }
}


================================================
FILE: src/bin/quake-client/main.rs
================================================
// Copyright © 2018 Cormac O'Brien
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

mod capture;
mod game;
mod menu;
mod trace;

use std::{
    cell::{Ref, RefCell, RefMut},
    fs::File,
    io::{Cursor, Read, Write},
    net::SocketAddr,
    path::{Path, PathBuf},
    process::exit,
    rc::Rc,
};

use game::Game;

use chrono::Duration;
use common::net::ServerCmd;
use richter::{
    client::{
        self,
        demo::DemoServer,
        input::{Input, InputFocus},
        menu::Menu,
        render::{self, Extent2d, GraphicsState, UiRenderer, DIFFUSE_ATTACHMENT_FORMAT},
        Client,
    },
    common::{
        self,
        console::{CmdRegistry, Console, CvarRegistry},
        host::{Host, Program},
        vfs::Vfs,
    },
};
use structopt::StructOpt;
use winit::{
    event::{Event, WindowEvent},
    event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
    window::Window,
};

struct ClientProgram {
    vfs: Rc<Vfs>,
    cvars: Rc<RefCell<CvarRegistry>>,
    cmds: Rc<RefCell<CmdRegistry>>,
    console: Rc<RefCell<Console>>,
    menu: Rc<RefCell<Menu>>,

    window: Window,
    window_dimensions_changed: bool,

    surface: wgpu::Surface,
    swap_chain: RefCell<wgpu::SwapChain>,
    gfx_state: RefCell<GraphicsState>,
    ui_renderer: Rc<UiRenderer>,

    game: Game,
    input: Rc<RefCell<Input>>,
}

impl ClientProgram {
    pub async fn new(window: Window, base_dir: Option<PathBuf>, trace: bool) -> ClientProgram {
        let vfs = Vfs::with_base_dir(base_dir.unwrap_or(common::default_base_dir()));

        let con_names = Rc::new(RefCell::new(Vec::new()));

        let cvars = Rc::new(RefCell::new(CvarRegistry::new(con_names.clone())));
        client::register_cvars(&cvars.borrow()).unwrap();
        render::register_cvars(&cvars.borrow());

        let cmds = Rc::new(RefCell::new(CmdRegistry::new(con_names)));
        // TODO: register commands as other subsystems come online

        let console = Rc::new(RefCell::new(Console::new(cmds.clone(), cvars.clone())));
        let menu = Rc::new(RefCell::new(menu::build_main_menu().unwrap()));

        let input = Rc::new(RefCell::new(Input::new(
            InputFocus::Console,
            console.clone(),
            menu.clone(),
        )));
        input.borrow_mut().bind_defaults();

        let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY);
        let surface = unsafe { instance.create_surface(&window) };
        let adapter = instance
            .request_adapter(&wgpu::RequestAdapterOptions {
                power_preference: wgpu::PowerPreference::HighPerformance,
                compatible_surface: Some(&surface),
            })
            .await
            .unwrap();
        let (device, queue) = adapter
            .request_device(
                &wgpu::DeviceDescriptor {
                    label: None,
                    features: wgpu::Features::PUSH_CONSTANTS
                        | wgpu::Features::SAMPLED_TEXTURE_BINDING_ARRAY
                        | wgpu::Features::SAMPLED_TEXTURE_ARRAY_DYNAMIC_INDEXING
                        | wgpu::Features::SAMPLED_TEXTURE_ARRAY_NON_UNIFORM_INDEXING,
                    limits: wgpu::Limits {
                        max_sampled_textures_per_shader_stage: 256,
                        max_uniform_buffer_binding_size: 65536,
                        max_push_constant_size: 256,
                        ..Default::default()
                    },
                },
                if trace {
                    Some(Path::new("./trace/"))
                } else {
                    None
                },
            )
            .await
            .unwrap();
        let size: Extent2d = window.inner_size().into();
        let swap_chain = RefCell::new(device.create_swap_chain(
            &surface,
            &wgpu::SwapChainDescriptor {
                usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
                format: DIFFUSE_ATTACHMENT_FORMAT,
                width: size.width,
                height: size.height,
                present_mode: wgpu::PresentMode::Immediate,
            },
        ));

        let vfs = Rc::new(vfs);

        // TODO: warn user if r_msaa_samples is invalid
        let mut sample_count = cvars.borrow().get_value("r_msaa_samples").unwrap_or(2.0) as u32;
        if !&[2, 4].contains(&sample_count) {
            sample_count = 2;
        }

        let gfx_state = GraphicsState::new(device, queue, size, sample_count, vfs.clone()).unwrap();
        let ui_renderer = Rc::new(UiRenderer::new(&gfx_state, &menu.borrow()));

        // TODO: factor this out
        // implements "exec" command
        let exec_vfs = vfs.clone();
        let exec_console = console.clone();
        cmds.borrow_mut().insert_or_replace(
            "exec",
            Box::new(move |args| {
                match args.len() {
                    // exec (filename): execute a script file
                    1 => {
                        let mut script_file = match exec_vfs.open(args[0]) {
                            Ok(s) => s,
                            Err(e) => {
                                return format!("Couldn't exec {}: {:?}", args[0], e);
                            }
                        };

                        let mut script = String::new();
                        script_file.read_to_string(&mut script).unwrap();

                        exec_console.borrow().stuff_text(script);
                        String::new()
                    }

                    _ => format!("exec (filename): execute a script file"),
                }
            }),
        ).unwrap();

        // this will also execute config.cfg and autoexec.cfg (assuming an unmodified quake.rc)
        console.borrow().stuff_text("exec quake.rc\n");

        let client = Client::new(
            vfs.clone(),
            cvars.clone(),
            cmds.clone(),
            console.clone(),
            input.clone(),
            &gfx_state,
            &menu.borrow(),
        );

        let game = Game::new(cvars.clone(), cmds.clone(), input.clone(), client).unwrap();

        ClientProgram {
            vfs,
            cvars,
            cmds,
            console,
            menu,
            window,
            window_dimensions_changed: false,
            surface,
            swap_chain,
            gfx_state: RefCell::new(gfx_state),
            ui_renderer,
            game,
            input,
        }
    }

    /// Builds a new swap chain with the specified present mode and the window's current dimensions.
    fn recreate_swap_chain(&self, present_mode: wgpu::PresentMode) {
        let winit::dpi::PhysicalSize { width, height } = self.window.inner_size();
        let swap_chain = self.gfx_state.borrow().device().create_swap_chain(
            &self.surface,
            &wgpu::SwapChainDescriptor {
                usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
                format: DIFFUSE_ATTACHMENT_FORMAT,
                width,
                height,
                present_mode,
            },
        );
        let _ = self.swap_chain.replace(swap_chain);
    }

    fn render(&mut self) {
        let swap_chain_output = self.swap_chain.borrow_mut().get_current_frame().unwrap();
        let winit::dpi::PhysicalSize { width, height } = self.window.inner_size();
        self.game.render(
            &self.gfx_state.borrow(),
            &swap_chain_output.output.view,
            width,
            height,
            &self.console.borrow(),
            &self.menu.borrow(),
        );
    }
}

impl Program for ClientProgram {
    fn handle_event<T>(
        &mut self,
        event: Event<T>,
        _target: &EventLoopWindowTarget<T>,
        _control_flow: &mut ControlFlow,
    ) {
        match event {
            Event::WindowEvent {
                event: WindowEvent::Resized(_),
                ..
            } => {
                self.window_dimensions_changed = true;
            }

            e => self.input.borrow_mut().handle_event(e).unwrap(),
        }
    }

    fn frame(&mut self, frame_duration: Duration) {
        // recreate swapchain if needed
        if self.window_dimensions_changed {
            self.window_dimensions_changed = false;
            self.recreate_swap_chain(wgpu::PresentMode::Immediate);
        }

        let size: Extent2d = self.window.inner_size().into();

        // TODO: warn user if r_msaa_samples is invalid
        let mut sample_count = self
            .cvars
            .borrow()
            .get_value("r_msaa_samples")
            .unwrap_or(2.0) as u32;
        if !&[2, 4].contains(&sample_count) {
            sample_count = 2;
        }

        // recreate attachments and rebuild pipelines if necessary
        self.gfx_state.borrow_mut().update(size, sample_count);
        self.game.frame(&self.gfx_state.borrow(), frame_duration);

        match self.input.borrow().focus() {
            InputFocus::Game => {
                if let Err(e) = self.window.set_cursor_grab(true) {
                    // This can happen if the window is running in another
                    // workspace. It shouldn't be considered an error.
                    log::debug!("Couldn't grab cursor: {}", e);
                }

                self.window.set_cursor_visible(false);
            }

            _ => {
                if let Err(e) = self.window.set_cursor_grab(false) {
                    log::debug!("Couldn't release cursor: {}", e);
                };
                self.window.set_cursor_visible(true);
            }
        }

        // run console commands
        self.console.borrow().execute();

        self.render();
    }

    fn shutdown(&mut self) {
        // TODO: do cleanup things here
    }

    fn cvars(&self) -> Ref<CvarRegistry> {
        self.cvars.borrow()
    }

    fn cvars_mut(&self) -> RefMut<CvarRegistry> {
        self.cvars.borrow_mut()
    }
}

#[derive(StructOpt, Debug)]
struct Opt {
    #[structopt(long)]
    trace: bool,

    #[structopt(long)]
    connect: Option<SocketAddr>,

    #[structopt(long)]
    dump_demo: Option<String>,

    #[structopt(long)]
    demo: Option<String>,

    #[structopt(long)]
    base_dir: Option<PathBuf>,
}

fn main() {
    env_logger::init();
    let opt = Opt::from_args();

    let event_loop = EventLoop::new();
    let window = {
        #[cfg(target_os = "windows")]
        {
            use winit::platform::windows::WindowBuilderExtWindows as _;
            winit::window::WindowBuilder::new()
                // disable file drag-and-drop so cpal and winit play nice
                .with_drag_and_drop(false)
                .with_title("Richter client")
                .with_inner_size(winit::dpi::PhysicalSize::<u32>::from((1366u32, 768)))
                .build(&event_loop)
                .unwrap()
        }

        #[cfg(not(target_os = "windows"))]
        {
            winit::window::WindowBuilder::new()
                .with_title("Richter client")
                .with_inner_size(winit::dpi::PhysicalSize::<u32>::from((1366u32, 768)))
                .build(&event_loop)
                .unwrap()
        }
    };

    let client_program =
        futures::executor::block_on(ClientProgram::new(window, opt.base_dir, opt.trace));

    // TODO: make dump_demo part of top-level binary and allow choosing file name
    if let Some(ref demo) = opt.dump_demo {
        let mut demfile = match client_program.vfs.open(demo) {
            Ok(d) => d,
            Err(e) => {
                eprintln!("error opening demofile: {}", e);
                std::process::exit(1);
            }
        };

        let mut demserv = match DemoServer::new(&mut demfile) {
            Ok(d) => d,
            Err(e) => {
                eprintln!("error starting demo server: {}", e);
                std::process::exit(1);
            }
        };

        let mut outfile = File::create("demodump.txt").unwrap();
        loop {
            match demserv.next() {
                Some(msg) => {
                    let mut curs = Cursor::new(msg.message());
                    loop {
                        match ServerCmd::deserialize(&mut curs) {
                            Ok(Some(cmd)) => write!(&mut outfile, "{:#?}\n", cmd).unwrap(),
                            Ok(None) => break,
                            Err(e) => {
                                eprintln!("error processing demo: {}", e);
                                std::process::exit(1);
                            }
                        }
                    }
                }
                None => break,
            }
        }

        std::process::exit(0);
    }
    if let Some(ref server) = opt.connect {
        client_program
            .console
            .borrow_mut()
            .stuff_text(format!("connect {}", server));
    } else if let Some(ref demo) = opt.demo {
        client_program
            .console
            .borrow_mut()
            .stuff_text(format!("playdemo {}", demo));
    }

    let mut host = Host::new(client_program);

    event_loop.run(move |event, _target, control_flow| {
        host.handle_event(event, _target, control_flow);
    });
}


================================================
FILE: src/bin/quake-client/menu.rs
================================================
// Copyright © 2018 Cormac O'Brien
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

use richter::client::menu::{Menu, MenuBodyView, MenuBuilder, MenuView};

use failure::Error;

pub fn build_main_menu() -> Result<Menu, Error> {
    Ok(MenuBuilder::new()
        .add_submenu("Single Player", build_menu_sp()?)
        .add_submenu("Multiplayer", build_menu_mp()?)
        .add_submenu("Options", build_menu_options()?)
        .add_action("Help/Ordering", Box::new(|| ()))
        .add_action("Quit", Box::new(|| ()))
        .build(MenuView {
            draw_plaque: true,
            title_path: "gfx/ttl_main.lmp".to_string(),
            body: MenuBodyView::Predefined {
                path: "gfx/mainmenu.lmp".to_string(),
            },
        }))
}

fn build_menu_sp() -> Result<Menu, Error> {
    Ok(MenuBuilder::new()
        .add_action("New Game", Box::new(|| ()))
        // .add_submenu("Load", unimplemented!())
        // .add_submenu("Save", unimplemented!())
        .build(MenuView {
            draw_plaque: true,
            title_path: "gfx/ttl_sgl.lmp".to_string(),
            body: MenuBodyView::Predefined {
                path: "gfx/sp_menu.lmp".to_string(),
            },
        }))
}

fn build_menu_mp() -> Result<Menu, Error> {
    Ok(MenuBuilder::new()
        .add_submenu("Join a Game", build_menu_mp_join()?)
        // .add_submenu("New Game", unimplemented!())
        // .add_submenu("Setup", unimplemented!())
        .build(MenuView {
            draw_plaque: true,
            title_path: "gfx/p_multi.lmp".to_string(),
            body: MenuBodyView::Predefined {
                path: "gfx/mp_menu.lmp".to_string(),
            },
        }))
}

fn build_menu_mp_join() -> Result<Menu, Error> {
    Ok(MenuBuilder::new()
        .add_submenu("TCP", build_menu_mp_join_tcp()?)
        // .add_textbox // description
        .build(MenuView {
            draw_plaque: true,
            title_path: "gfx/p_multi.lmp".to_string(),
            body: MenuBodyView::Predefined {
                path: "gfx/mp_menu.lmp".to_string(),
            },
        }))
}

fn build_menu_mp_join_tcp() -> Result<Menu, Error> {
    // Join Game - TCP/IP          // title
    //
    //  Address: 127.0.0.1         // label
    //
    //  Port     [26000]           // text field
    //
    //  Search for local games...  // menu
    //
    //  Join game at:              // label
    //  [                        ] // text field
    Ok(MenuBuilder::new()
        // .add
        .add_toggle("placeholder", false, Box::new(|_| ()))
        .build(MenuView {
            draw_plaque: true,
            title_path: "gfx/p_multi.lmp".to_string(),
            body: MenuBodyView::Dynamic,
        }))
}

fn build_menu_options() -> Result<Menu, Error> {
    Ok(MenuBuilder::new()
        // .add_submenu("Customize controls", unimplemented!())
        .add_action("Go to console", Box::new(|| ()))
        .add_action("Reset to defaults", Box::new(|| ()))
        .add_slider("Render scale", 0.25, 1.0, 2, 0, Box::new(|_| ()))?
        .add_slider("Screen Size", 0.0, 1.0, 10, 9, Box::new(|_| ()))?
        .add_slider("Brightness", 0.0, 1.0, 10, 9, Box::new(|_| ()))?
        .add_slider("Mouse Speed", 0.0, 1.0, 10, 9, Box::new(|_| ()))?
        .add_slider("CD music volume", 0.0, 1.0, 10, 9, Box::new(|_| ()))?
        .add_slider("Sound volume", 0.0, 1.0, 10, 9, Box::new(|_| ()))?
        .add_toggle("Always run", true, Box::new(|_| ()))
        .add_toggle("Invert mouse", false, Box::new(|_| ()))
        .add_toggle("Lookspring", false, Box::new(|_| ()))
        .add_toggle("Lookstrafe", false, Box::new(|_| ()))
        // .add_submenu("Video options", unimplemented!())
        .build(MenuView {
            draw_plaque: true,
            title_path: "gfx/p_option.lmp".to_string(),
            body: MenuBodyView::Dynamic,
        }))
}


================================================
FILE: src/bin/quake-client/trace.rs
================================================
use std::{cell::RefCell, io::BufWriter, rc::Rc, fs::File};

use richter::{client::trace::TraceFrame, common::console::CvarRegistry};

const DEFAULT_TRACE_PATH: &'static str = "richter-trace.json";

/// Implements the `trace_begin` command.
pub fn cmd_trace_begin(trace: Rc<RefCell<Option<Vec<TraceFrame>>>>) -> Box<dyn Fn(&[&str]) -> String> {
    Box::new(move |_| {
        if trace.borrow().is_some() {
            log::error!("trace already in progress");
            "trace already in progress".to_owned()
        } else {
            // start a new trace
            trace.replace(Some(Vec::new()));
            String::new()
        }
    })
}

/// Implements the `trace_end` command.
pub fn cmd_trace_end(
    cvars: Rc<RefCell<CvarRegistry>>,
    trace: Rc<RefCell<Option<Vec<TraceFrame>>>>,
) -> Box<dyn Fn(&[&str]) -> String> {
    Box::new(move |_| {
        if let Some(trace_frames) = trace.replace(None) {
            let trace_path = cvars
                .borrow()
                .get("trace_path")
                .unwrap_or(DEFAULT_TRACE_PATH.to_string());
            let trace_file = match File::create(&trace_path) {
                Ok(f) => f,
                Err(e) => {
                    log::error!("Couldn't open trace file for write: {}", e);
                    return format!("Couldn't open trace file for write: {}", e);
                }
            };

            let mut writer = BufWriter::new(trace_file);

            match serde_json::to_writer(&mut writer, &trace_frames) {
                Ok(()) => (),
                Err(e) => {
                    log::error!("Couldn't serialize trace: {}", e);
                    return format!("Couldn't serialize trace: {}", e);
                }
            };

            log::debug!("wrote {} frames to {}", trace_frames.len(), &trace_path);
            format!("wrote {} frames to {}", trace_frames.len(), &trace_path)
        } else {
            log::error!("no trace in progress");
            "no trace in progress".to_owned()
        }
    })
}


================================================
FILE: src/bin/unpak.rs
================================================
// Copyright © 2018 Cormac O'Brien
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
// and associated documentation files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

extern crate richter;

use std::{
    fs,
    fs::File,
    io::{BufWriter, Write},
    path::PathBuf,
    process::exit,
};

use richter::common::pak::Pak;

use structopt::StructOpt;

#[derive(Debug, StructOpt)]
struct Opt {
    #[structopt(short, long)]
    verbose: bool,

    #[structopt(long)]
    version: bool,

    #[structopt(name = "INPUT_PAK", parse(from_os_str))]
    input_pak: PathBuf,

    #[structopt(name = "OUTPUT_DIR", parse(from_os_str))]
    output_dir: Option<PathBuf>,
}

const VERSION: &'static str = "
unpak 0.1
Copyright © 2020 Cormac O'Brien
Released under the terms of the MIT License
";

fn main() {
    let opt = Opt::from_args();

    if opt.version {
        println!("{}", VERSION);
        exit(0);
    }

    let pak = match Pak::new(&opt.input_pak) {
        Ok(p) => p,
        Err(why) => {
            println!("Couldn't open {:#?}: {}", &opt.input_pak, why);
            exit(1);
        }
    };

    for (k, v) in pak.iter() {
        let mut path = PathBuf::new();

        if let Some(ref d) = opt.output_dir {
            path.push(d);
        }

        path.push(k);

        if let Some(p) = path.parent() {
            if !p.exists() {
                if let Err(why) = fs::create_dir_all(p) {
                    println!("Couldn't create parent directories: {}", why);
                    exit(1);
                }
            }
        }

        let file = match File::create(&path) {
            Ok(f) => f,
            Err(why) => {
                println!("Couldn't open {}: {}", path.to_str().unwrap(), why);
                exit(1);
            }
        };

        let mut writer = BufWriter::new(file);
        match writer.write_all(v.as_ref()) {
            Ok(_) => (),
            Err(why) => {
                println!("Couldn't write to {}: {}", path.to_str().unwrap(), why);
                exit(1);
            }
        }
    }
}


================================================
FILE: src/client/cvars.rs
================================================
// Copyright © 2018 Cormac O'Brien
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

use crate::common::console::{CvarRegistry, ConsoleError};

pub fn register_cvars(cvars: &CvarRegistry) -> Result<(), ConsoleError> {
    cvars.register("cl_anglespeedkey", "1.5")?;
    cvars.register_archive("cl_backspeed", "200")?;
    cvars.register("cl_bob", "0.02")?;
    cvars.register("cl_bobcycle", "0.6")?;
    cvars.register("cl_bobup", "0.5")?;
    cvars.register_archive("_cl_color", "0")?;
    cvars.register("cl_crossx", "0")?;
    cvars.register("cl_crossy", "0")?;
    cvars.register_archive("cl_forwardspeed", "400")?;
    cvars.register("cl_movespeedkey", "2.0")?;
    cvars.register_archive("_cl_name", "player")?;
    cvars.register("cl_nolerp", "0")?;
    cvars.register("cl_pitchspeed", "150")?;
    cvars.register("cl_rollangle", "2.0")?;
    cvars.register("cl_rollspeed", "200")?;
    cvars.register("cl_shownet", "0")?;
    cvars.register("cl_sidespeed", "350")?;
    cvars.register("cl_upspeed", "200")?;
    cvars.register("cl_yawspeed", "140")?;
    cvars.register("fov", "90")?;
    cvars.register_archive("m_pitch", "0.022")?;
    cvars.register_archive("m_yaw", "0.022")?;
    cvars.register_archive("sensitivity", "3")?;
    cvars.register("v_idlescale", "0")?;
    cvars.register("v_ipitch_cycle", "1")?;
    cvars.register("v_ipitch_level", "0.3")?;
    cvars.register("v_iroll_cycle", "0.5")?;
    cvars.register("v_iroll_level", "0.1")?;
    cvars.register("v_iyaw_cycle", "2")?;
    cvars.register("v_iyaw_level", "0.3")?;
    cvars.register("v_kickpitch", "0.6")?;
    cvars.register("v_kickroll", "0.6")?;
    cvars.register("v_kicktime", "0.5")?;

    // some server cvars are needed by the client, but if the server is running
    // in the same process they will have been set already, so we can ignore
    // the duplicate cvar error
    let _ = cvars.register("sv_gravity", "800");

    Ok(())
}


================================================
FILE: src/client/demo.rs
================================================
use std::{io, ops::Range};

use crate::common::{
    net::{self, NetError},
    util::read_f32_3,
    vfs::VirtualFile,
};

use arrayvec::ArrayVec;
use byteorder::{LittleEndian, ReadBytesExt};
use cgmath::{Deg, Vector3};
use io::BufReader;
use thiserror::Error;

/// An error returned by a demo server.
#[derive(Error, Debug)]
pub enum DemoServerError {
    #[error("Invalid CD track number")]
    InvalidCdTrack,
    #[error("No such CD track: {0}")]
    NoSuchCdTrack(i32),
    #[error("Message size ({0}) exceeds maximum allowed size {}", net::MAX_MESSAGE)]
    MessageTooLong(u32),
    #[error("I/O error: {0}")]
    Io(#[from] io::Error),
    #[error("Network error: {0}")]
    Net(#[from] NetError),
}

struct DemoMessage {
    view_angles: Vector3<Deg<f32>>,
    msg_range: Range<usize>,
}

/// A view of a server message from a demo.
pub struct DemoMessageView<'a> {
    view_angles: Vector3<Deg<f32>>,
    message: &'a [u8],
}

impl<'a> DemoMessageView<'a> {
    /// Returns the view angles recorded for this demo message.
    pub fn view_angles(&self) -> Vector3<Deg<f32>> {
        self.view_angles
    }

    /// Returns the server message for this demo message as a slice of bytes.
    pub fn message(&self) -> &[u8] {
        self.message
    }
}

/// A server that yields commands from a demo file.
pub struct DemoServer {
    track_override: Option<u32>,

    // id of next message to "send"
    message_id: usize,

    messages: Vec<DemoMessage>,

    // all message data
    message_data: Vec<u8>,
}

impl DemoServer {
    /// Construct a new `DemoServer` from the specified demo file.
    pub fn new(file: &mut VirtualFile) -> Result<DemoServer, DemoServerError> {
        let mut dem_reader = BufReader::new(file);

        let mut buf = ArrayVec::<u8, 3>::new();
        // copy CD track number (terminated by newline) into buffer
        for i in 0..buf.capacity() {
            match dem_reader.read_u8()? {
                b'\n' => break,
                // cannot panic because we won't exceed capacity with a loop this small
                b => buf.push(b),
            }

            if i >= buf.capacity() - 1 {
                // CD track would be more than 2 digits long, which is impossible
                Err(DemoServerError::InvalidCdTrack)?;
            }
        }

        let track_override = {
            let track_str = match std::str::from_utf8(&buf) {
                Ok(s) => s,
                Err(_) => Err(DemoServerError::InvalidCdTrack)?,
            };

            match track_str {
                // if track is empty, default to track 0
                "" => Some(0),
                s => match s.parse::<i32>() {
                    Ok(track) => match track {
                        // if track is -1, allow demo to specify tracks in messages
                        -1 => None,
                        t if t < -1 => Err(DemoServerError::InvalidCdTrack)?,
                        _ => Some(track as u32),
                    },
                    Err(_) => Err(DemoServerError::InvalidCdTrack)?,
                },
            }
        };

        let mut message_data = Vec::new();
        let mut messages = Vec::new();

        // read all messages
        while let Ok(msg_len) = dem_reader.read_u32::<LittleEndian>() {
            // get view angles
            let view_angles_f32 = read_f32_3(&mut dem_reader)?;
            let view_angles = Vector3::new(
                Deg(view_angles_f32[0]),
                Deg(view_angles_f32[1]),
                Deg(view_angles_f32[2]),
            );

            // read next message
            let msg_start = message_data.len();
            for _ in 0..msg_len {
                message_data.push(dem_reader.read_u8()?);
            }
            let msg_end = message_data.len();

            messages.push(DemoMessage {
                view_angles,
                msg_range: msg_start..msg_end,
            });
        }

        Ok(DemoServer {
            track_override,
            message_id: 0,
            messages,
            message_data,
        })
    }

    /// Retrieve the next server message from the currently playing demo.
    ///
    /// If this returns `None`, the demo is complete.
    pub fn next(&mut self) -> Option<DemoMessageView> {
        if self.message_id >= self.messages.len() {
            return None;
        }

        let msg = &self.messages[self.message_id];
        self.message_id += 1;

        Some(DemoMessageView {
            view_angles: msg.view_angles,
            message: &self.message_data[msg.msg_range.clone()],
        })
    }

    /// Returns the currently playing demo's music track override, if any.
    ///
    /// If this is `Some`, any `CdTrack` commands from the demo server should
    /// cause the client to play this track instead of the one specified by the
    /// command.
    pub fn track_override(&self) -> Option<u32> {
        self.track_override
    }
}


================================================
FILE: src/client/entity/mod.rs
================================================
// Copyright © 2020 Cormac O'Brien
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

pub mod particle;

use crate::common::{
    alloc::LinkedSlab,
    engine,
    net::{EntityEffects, EntityState, EntityUpdate},
};

use cgmath::{Deg, Vector3};
use chrono::Duration;

// if this is changed, it must also be changed in deferred.frag
pub const MAX_LIGHTS: usize = 32;
pub const MAX_BEAMS: usize = 24;
pub const MAX_TEMP_ENTITIES: usize = 64;
pub const MAX_STATIC_ENTITIES: usize = 128;

#[derive(Debug)]
pub struct ClientEntity {
    pub force_link: bool,
    pub baseline: EntityState,
    pub msg_time: Duration,
    pub msg_origins: [Vector3<f32>; 2],
    pub origin: Vector3<f32>,
    pub msg_angles: [Vector3<Deg<f32>>; 2],
    pub angles: Vector3<Deg<f32>>,
    pub model_id: usize,
    model_changed: bool,
    pub frame_id: usize,
    pub skin_id: usize,
    colormap: Option<u8>,
    pub sync_base: Duration,
    pub effects: EntityEffects,
    pub light_id: Option<usize>,
    // vis_frame: usize,
}

impl ClientEntity {
    pub fn from_baseline(baseline: EntityState) -> ClientEntity {
        ClientEntity {
            force_link: false,
            baseline: baseline.clone(),
            msg_time: Duration::zero(),
            msg_origins: [Vector3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 0.0)],
            origin: baseline.origin,
            msg_angles: [
                Vector3::new(Deg(0.0), Deg(0.0), Deg(0.0)),
                Vector3::new(Deg(0.0), Deg(0.0), Deg(0.0)),
            ],
            angles: baseline.angles,
            model_id: baseline.model_id,
            model_changed: false,
            frame_id: baseline.frame_id,
            skin_id: baseline.skin_id,
            colormap: None,
            sync_base: Duration::zero(),
            effects: baseline.effects,
            light_id: None,
        }
    }

    pub fn uninitialized() -> ClientEntity {
        ClientEntity {
            force_link: false,
            baseline: EntityState::uninitialized(),
            msg_time: Duration::zero(),
            msg_origins: [Vector3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 0.0)],
            origin: Vector3::new(0.0, 0.0, 0.0),
            msg_angles: [
                Vector3::new(Deg(0.0), Deg(0.0), Deg(0.0)),
                Vector3::new(Deg(0.0), Deg(0.0), Deg(0.0)),
            ],
            angles: Vector3::new(Deg(0.0), Deg(0.0), Deg(0.0)),
            model_id: 0,
            model_changed: false,
            frame_id: 0,
            skin_id: 0,
            colormap: None,
            sync_base: Duration::zero(),
            effects: EntityEffects::empty(),
            light_id: None,
        }
    }

    /// Update the entity with values from the server.
    ///
    /// `msg_times` specifies the last two message times from the server, where
    /// `msg_times[0]` is more recent.
    pub fn update(&mut self, msg_times: [Duration; 2], update: EntityUpdate) {
        // enable lerping
        self.force_link = false;

        if update.no_lerp || self.msg_time != msg_times[1] {
            self.force_link = true;
        }

        self.msg_time = msg_times[0];

        // fill in missing values from baseline
        let new_state = update.to_entity_state(&self.baseline);

        self.msg_origins[1] = self.msg_origins[0];
        self.msg_origins[0] = new_state.origin;
        self.msg_angles[1] = self.msg_angles[0];
        self.msg_angles[0] = new_state.angles;

        if self.model_id != new_state.model_id {
            self.model_changed = true;
            self.force_link = true;
            self.model_id = new_state.model_id;
        }

        self.frame_id = new_state.frame_id;
        self.skin_id = new_state.skin_id;
        self.effects = new_state.effects;
        self.colormap = update.colormap;

        if self.force_link {
            self.msg_origins[1] = self.msg_origins[0];
            self.origin = self.msg_origins[0];
            self.msg_angles[1] = self.msg_angles[0];
            self.angles = self.msg_angles[0];
        }
    }

    /// Sets the entity's most recent message angles to the specified value.
    ///
    /// This is primarily useful for allowing interpolated view angles in demos.
    pub fn update_angles(&mut self, angles: Vector3<Deg<f32>>) {
        self.msg_angles[0] = angles;
    }

    /// Sets the entity's angles to the specified value, overwriting the message
    /// history.
    ///
    /// This causes the entity to "snap" to the correct angle rather than
    /// interpolating to it.
    pub fn set_angles(&mut self, angles: Vector3<Deg<f32>>) {
        self.msg_angles[0] = angles;
        self.msg_angles[1] = angles;
        self.angles = angles;
    }

    /// Returns the timestamp of the last message that updated this entity.
    pub fn msg_time(&self) -> Duration {
        self.msg_time
    }

    /// Returns true if the last update to this entity changed its model.
    pub fn model_changed(&self) -> bool {
        self.model_changed
    }

    pub fn colormap(&self) -> Option<u8> {
        self.colormap
    }

    pub fn get_origin(&self) -> Vector3<f32> {
        self.origin
    }

    pub fn get_angles(&self) -> Vector3<Deg<f32>> {
        self.angles
    }

    pub fn model_id(&self) -> usize {
        self.model_id
    }

    pub fn frame_id(&self) -> usize {
        self.frame_id
    }

    pub fn skin_id(&self) -> usize {
        self.skin_id
    }
}

/// A descriptor used to spawn dynamic lights.
#[derive(Clone, Debug)]
pub struct LightDesc {
    /// The origin of the light.
    pub origin: Vector3<f32>,

    /// The initial radius of the light.
    pub init_radius: f32,

    /// The rate of radius decay in units/second.
    pub decay_rate: f32,

    /// If the radius decays to this value, the light is ignored.
    pub min_radius: Option<f32>,

    /// Time-to-live of the light.
    pub ttl: Duration,
}

/// A dynamic point light.
#[derive(Clone, Debug)]
pub struct Light {
    origin: Vector3<f32>,
    init_radius: f32,
    decay_rate: f32,
    min_radius: Option<f32>,
    spawned: Duration,
    ttl: Duration,
}

impl Light {
    /// Create a light from a `LightDesc` at the specified time.
    pub fn from_desc(time: Duration, desc: LightDesc) -> Light {
        Light {
            origin: desc.origin,
            init_radius: desc.init_radius,
            decay_rate: desc.decay_rate,
            min_radius: desc.min_radius,
            spawned: time,
            ttl: desc.ttl,
        }
    }

    /// Return the origin of the light.
    pub fn origin(&self) -> Vector3<f32> {
        self.origin
    }

    /// Return the radius of the light for the given time.
    ///
    /// If the radius would decay to a negative value, returns 0.
    pub fn radius(&self, time: Duration) -> f32 {
        let lived = time - self.spawned;
        let decay = self.decay_rate * engine::duration_to_f32(lived);
        let radius = (self.init_radius - decay).max(0.0);

        if let Some(min) = self.min_radius {
            if radius < min {
                return 0.0;
            }
        }

        radius
    }

    /// Returns `true` if the light should be retained at the specified time.
    pub fn retain(&mut self, time: Duration) -> bool {
        self.spawned + self.ttl > time
    }
}

/// A set of active dynamic lights.
pub struct Lights {
    slab: LinkedSlab<Light>,
}

impl Lights {
    /// Create an empty set of lights with the given capacity.
    pub fn with_capacity(capacity: usize) -> Lights {
        Lights {
            slab: LinkedSlab::with_capacity(capacity),
        }
    }

    /// Return a reference to the light with the given key, or `None` if no
    /// such light exists.
    pub fn get(&self, key: usize) -> Option<&Light> {
        self.slab.get(key)
    }

    /// Return a mutable reference to the light with the given key, or `None`
    /// if no such light exists.
    pub fn get_mut(&mut self, key: usize) -> Option<&mut Light> {
        self.slab.get_mut(key)
    }

    /// Insert a new light into the set of lights.
    ///
    /// Returns a key corresponding to the newly inserted light.
    ///
    /// If `key` is `Some` and there is an existing light with that key, then
    /// the light will be overwritten with the new value.
    pub fn insert(&mut self, time: Duration, desc: LightDesc, key: Option<usize>) -> usize {
        if let Some(k) = key {
            if let Some(key_light) = self.slab.get_mut(k) {
                *key_light = Light::from_desc(time, desc);
                return k;
            }
        }

        self.slab.insert(Light::from_desc(time, desc))
    }

    /// Return an iterator over the active lights.
    pub fn iter(&self) -> impl Iterator<Item = &Light> {
        self.slab.iter()
    }

    /// Updates the set of dynamic lights for the specified time.
    ///
    /// This will deallocate any lights which have outlived their time-to-live.
    pub fn update(&mut self, time: Duration) {
        self.slab.retain(|_, light| light.retain(time));
    }
}

#[derive(Copy, Clone, Debug)]
pub struct Beam {
    pub entity_id: usize,
    pub model_id: usize,
    pub expire: Duration,
    pub start: Vector3<f32>,
    pub end: Vector3<f32>,
}


================================================
FILE: src/client/entity/particle.rs
================================================
// Copyright © 2020 Cormac O'Brien
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

use std::ops::RangeInclusive;

use crate::{
    client::ClientEntity,
    common::{
        alloc::LinkedSlab,
        engine,
        math::{self, VERTEX_NORMAL_COUNT},
    },
};

use cgmath::{InnerSpace as _, Vector3, Zero as _};
use chrono::Duration;
use rand::{
    distributions::{Distribution as _, Uniform},
    rngs::SmallRng,
    SeedableRng,
};

lazy_static! {
    static ref COLOR_RAMP_EXPLOSION_FAST: ColorRamp = ColorRamp {
        ramp: vec![0x6F, 0x6D, 0x6B, 0x69, 0x67, 0x65, 0x63, 0x61],
        fps: 10.0,
    };
    static ref COLOR_RAMP_EXPLOSION_SLOW: ColorRamp = ColorRamp {
        ramp: vec![0x6F, 0x6E, 0x6D, 0x6C, 0x6B, 0x6A, 0x68, 0x66],
        fps: 5.0,
    };
    static ref COLOR_RAMP_FIRE: ColorRamp = ColorRamp {
        ramp: vec![0x6D, 0x6B, 0x06, 0x05, 0x04, 0x03],
        fps: 15.0,
    };
    static ref EXPLOSION_SCATTER_DISTRIBUTION: Uniform<f32> = Uniform::new(-16.0, 16.0);
    static ref EXPLOSION_VELOCITY_DISTRIBUTION: Uniform<f32> = Uniform::new(-256.0, 256.0);
}

// TODO: make max configurable
pub const MIN_PARTICLES: usize = 512;

// should be possible to get the whole particle list in cache at once
pub const MAX_PARTICLES: usize = 16384;

/// An animated color ramp.
///
/// Colors are specified using 8-bit indexed values, which should be translated
/// using the palette.
#[derive(Debug)]
pub struct ColorRamp {
    // TODO: arrayvec, tinyvec, or array once const generics are stable
    ramp: Vec<u8>,

    // frames per second of the animation
    fps: f32,
}

impl ColorRamp {
    /// Returns the frame corresponding to the given time.
    ///
    /// If the animation has already completed by `elapsed`, returns `None`.
    pub fn color(&self, elapsed: Duration, frame_skip: usize) -> Option<u8> {
        let frame = (engine::duration_to_f32(elapsed) * self.fps) as usize + frame_skip;
        self.ramp.get(frame).map(|c| *c)
    }
}

/// Dictates the behavior of a particular particle.
///
/// Particles which are animated with a color ramp are despawned automatically
/// when the animation is complete.
#[derive(Copy, Clone, Debug)]
pub enum ParticleKind {
    /// Normal particle, unaffected by gravity.
    Static,

    /// Normal particle, affected by gravity.
    Grav,

    /// Fire and smoke particles. Animated using `COLOR_RAMP_FIRE`. Inversely
    /// affected by gravity, rising instead of falling.
    Fire {
        /// Specifies the number of frames to skip.
        frame_skip: usize,
    },

    /// Explosion particles. May have `COLOR_RAMP_EXPLOSION_FAST` or
    /// `COLOR_RAMP_EXPLOSION_SLOW`. Affected by gravity.
    Explosion {
        /// Specifies the color ramp to use.
        ramp: &'static ColorRamp,

        /// Specifies the number of frames to skip.
        frame_skip: usize,
    },

    /// Spawn (enemy) death explosion particle. Accelerates at
    /// `v(t2) = v(t1) + 4 * (t2 - t1)`. May or may not have an intrinsic
    /// z-velocity.
    Blob {
        /// If false, particle only moves in the XY plane and is unaffected by
        /// gravity.
        has_z_velocity: bool,
    },
}

/// Factor at which particles are affected by gravity.
pub const PARTICLE_GRAVITY_FACTOR: f32 = 0.05;

/// A live particle.
#[derive(Copy, Clone, Debug)]
pub struct Particle {
    kind: ParticleKind,
    origin: Vector3<f32>,
    velocity: Vector3<f32>,
    color: u8,
    spawned: Duration,
    expire: Duration,
}

impl Particle {
    /// Particle update function.
    ///
    /// The return value indicates whether the particle should be retained after this
    /// frame.
    ///
    /// For details on how individual particles behave, see the documentation for
    /// [`ParticleKind`](ParticleKind).
    pub fn update(&mut self, time: Duration, frame_time: Duration, sv_gravity: f32) -> bool {
        use ParticleKind::*;

        let velocity_factor = engine::duration_to_f32(frame_time);
        let gravity = velocity_factor * sv_gravity * PARTICLE_GRAVITY_FACTOR;

        // don't bother updating expired particles
        if time >= self.expire {
            return false;
        }

        match self.kind {
            Static => true,

            Grav => {
                self.origin += self.velocity * velocity_factor;
                self.velocity.z -= gravity;
                true
            }

            Fire { frame_skip } => match COLOR_RAMP_FIRE.color(time - self.spawned, frame_skip) {
                Some(c) => {
                    self.origin += self.velocity * velocity_factor;
                    // rises instead of falling
                    self.velocity.z += gravity;
                    self.color = c;
                    true
                }
                None => false,
            },

            Explosion { ramp, frame_skip } => match ramp.color(time - self.spawned, frame_skip) {
                Some(c) => {
                    self.origin += self.velocity * velocity_factor;
                    self.velocity.z -= gravity;
                    self.color = c;
                    true
                }
                None => false,
            },

            Blob { has_z_velocity } => {
                if !has_z_velocity {
                    let xy_velocity = Vector3::new(self.velocity.x, self.velocity.y, 0.0);
                    self.origin += xy_velocity * velocity_factor;
                } else {
                    self.origin += self.velocity * velocity_factor;
                    self.velocity.z -= gravity;
                }

                true
            }
        }
    }

    pub fn origin(&self) -> Vector3<f32> {
        self.origin
    }

    pub fn color(&self) -> u8 {
        self.color
    }
}

pub enum TrailKind {
    Rocket = 0,
    Smoke = 1,
    Blood = 2,
    TracerGreen = 3,
    BloodSlight = 4,
    TracerRed = 5,
    Vore = 6,
}

/// A list of particles.
///
/// Space for new particles is allocated from an internal [`Slab`](slab::Slab) of fixed
/// size.
pub struct Particles {
    // allocation pool
    slab: LinkedSlab<Particle>,

    // random number generator
    rng: SmallRng,

    angle_velocities: [Vector3<f32>; VERTEX_NORMAL_COUNT],
}

impl Particles {
    /// Create a new particle list with the given capacity.
    ///
    /// This determines the capacity of both the underlying `Slab` and the set of
    /// live particles.
    pub fn with_capacity(capacity: usize) -> Particles {
        lazy_static! {
            // avelocities initialized with (rand() & 255) * 0.01;
            static ref VELOCITY_DISTRIBUTION: Uniform<f32> = Uniform::new(0.0, 2.56);
        }

        let slab = LinkedSlab::with_capacity(capacity.min(MAX_PARTICLES));
        let rng = SmallRng::from_entropy();
        let angle_velocities = [Vector3::zero(); VERTEX_NORMAL_COUNT];

        let mut particles = Particles {
            slab,
            rng,
            angle_velocities,
        };

        for i in 0..angle_velocities.len() {
            particles.angle_velocities[i] = particles.random_vector3(&VELOCITY_DISTRIBUTION);
        }

        particles
    }

    /// Insert a particle into the live list.
    // TODO: come up with a better eviction policy
    // the original engine ignores new particles if at capacity, but it's not ideal
    pub fn insert(&mut self, particle: Particle) -> bool {
        // check capacity
        if self.slab.len() == self.slab.capacity() {
            return false;
        }

        // insert it
        self.slab.insert(particle);
        true
    }

    /// Clears all particles.
    pub fn clear(&mut self) {
        self.slab.clear();
    }

    pub fn iter(&self) -> impl Iterator<Item = &Particle> {
        self.slab.iter()
    }

    /// Update all live particles, deleting any that are expired.
    ///
    /// Particles are updated with [Particle::update]. That
    /// function's return value indicates whether the particle should be retained
    /// or not.
    pub fn update(&mut self, time: Duration, frame_time: Duration, sv_gravity: f32) {
        self.slab
            .retain(|_, particle| particle.update(time, frame_time, sv_gravity));
    }

    fn scatter(&mut self, origin: Vector3<f32>, scatter_distr: &Uniform<f32>) -> Vector3<f32> {
        origin
            + Vector3::new(
                scatter_distr.sample(&mut self.rng),
                scatter_distr.sample(&mut self.rng),
                scatter_distr.sample(&mut self.rng),
            )
    }

    fn random_vector3(&mut self, velocity_distr: &Uniform<f32>) -> Vector3<f32> {
        Vector3::new(
            velocity_distr.sample(&mut self.rng),
            velocity_distr.sample(&mut self.rng),
            velocity_distr.sample(&mut self.rng),
        )
    }

    /// Creates a spherical cloud of particles around an entity.
    pub fn create_entity_field(&mut self, time: Duration, entity: &ClientEntity) {
        let beam_length = 16.0;
        let dist = 64.0;

        for i in 0..VERTEX_NORMAL_COUNT {
            let float_time = engine::duration_to_f32(time);

            let angles = float_time * self.angle_velocities[i];

            let sin_yaw = angles[0].sin();
            let cos_yaw = angles[0].cos();
            let sin_pitch = angles[1].sin();
            let cos_pitch = angles[1].cos();

            let forward = Vector3::new(cos_pitch * cos_yaw, cos_pitch * sin_yaw, -sin_pitch);
            let ttl = Duration::milliseconds(10);

            let origin = entity.origin + dist * math::VERTEX_NORMALS[i] + beam_length * forward;

            self.insert(Particle {
                kind: ParticleKind::Explosion {
                    ramp: &COLOR_RAMP_EXPLOSION_FAST,
                    frame_skip: 0,
                },
                origin,
                velocity: Vector3::zero(),
                color: COLOR_RAMP_EXPLOSION_FAST.ramp[0],
                spawned: time,
                expire: time + ttl,
            });
        }
    }

    /// Spawns a cloud of particles at a point.
    ///
    /// Each particle's origin is offset by a vector with components sampled
    /// from `scatter_distr`, and each particle's velocity is assigned a
    /// vector with components sampled from `velocity_distr`.
    ///
    /// Each particle's color is taken from `colors`, which is an inclusive
    /// range of palette indices. The spawned particles have evenly distributed
    /// colors throughout the range.
    pub fn create_random_cloud(
        &mut self,
        count: usize,
        colors: RangeInclusive<u8>,
        kind: ParticleKind,
        time: Duration,
        ttl: Duration,
        origin: Vector3<f32>,
        scatter_distr: &Uniform<f32>,
        velocity_distr: &Uniform<f32>,
    ) {
        let color_start = *colors.start() as usize;
        let color_end = *colors.end() as usize;
        for i in 0..count {
            let origin = self.scatter(origin, scatter_distr);
            let velocity = self.random_vector3(velocity_distr);
            let color = (color_start + i % (color_end - color_start + 1)) as u8;
            if !self.insert(Particle {
                kind,
                origin,
                velocity,
                color,
                spawned: time,
                expire: time + ttl,
            }) {
                // can't fit any more particles
                return;
            };
        }
    }

    /// Creates a rocket explosion.
    pub fn create_explosion(&mut self, time: Duration, origin: Vector3<f32>) {
        lazy_static! {
            static ref FRAME_SKIP_DISTRIBUTION: Uniform<usize> = Uniform::new(0, 4);
        }

        // spawn 512 particles each for both color ramps
        for ramp in [&*COLOR_RAMP_EXPLOSION_FAST, &*COLOR_RAMP_EXPLOSION_SLOW].iter() {
            let frame_skip = FRAME_SKIP_DISTRIBUTION.sample(&mut self.rng);
            self.create_random_cloud(
                512,
                ramp.ramp[frame_skip]..=ramp.ramp[frame_skip],
                ParticleKind::Explosion { ramp, frame_skip },
                time,
                Duration::seconds(5),
                origin,
                &EXPLOSION_SCATTER_DISTRIBUTION,
                &EXPLOSION_VELOCITY_DISTRIBUTION,
            );
        }
    }

    /// Creates an explosion using the given range of colors.
    pub fn create_color_explosion(
        &mut self,
        time: Duration,
        origin: Vector3<f32>,
        colors: RangeInclusive<u8>,
    ) {
        self.create_random_cloud(
            512,
            colors,
            ParticleKind::Blob {
                has_z_velocity: true,
            },
            time,
            Duration::milliseconds(300),
            origin,
            &EXPLOSION_SCATTER_DISTRIBUTION,
            &EXPLOSION_VELOCITY_DISTRIBUTION,
        );
    }

    /// Creates a death explosion for the Spawn.
    pub fn create_spawn_explosion(&mut self, time: Duration, origin: Vector3<f32>) {
        // R_BlobExplosion picks a random ttl with 1 + (rand() & 8) * 0.05
        // which gives a value of either 1 or 1.4 seconds.
        // (it's possible it was supposed to be 1 + (rand() & 7) * 0.05, which
        // would yield between 1 and 1.35 seconds in increments of 50ms.)
        let ttls = [Duration::seconds(1), Duration::milliseconds(1400)];

        for ttl in ttls.iter().cloned() {
            self.create_random_cloud(
                256,
                66..=71,
                ParticleKind::Blob {
                    has_z_velocity: true,
                },
                time,
                ttl,
                origin,
                &EXPLOSION_SCATTER_DISTRIBUTION,
                &EXPLOSION_VELOCITY_DISTRIBUTION,
            );

            self.create_random_cloud(
                256,
                150..=155,
                ParticleKind::Blob {
                    has_z_velocity: false,
                },
                time,
                ttl,
                origin,
                &EXPLOSION_SCATTER_DISTRIBUTION,
                &EXPLOSION_VELOCITY_DISTRIBUTION,
            );
        }
    }

    /// Creates a projectile impact.
    pub fn create_projectile_impact(
        &mut self,
        time: Duration,
        origin: Vector3<f32>,
        direction: Vector3<f32>,
        color: u8,
        count: usize,
    ) {
        lazy_static! {
            static ref SCATTER_DISTRIBUTION: Uniform<f32> = Uniform::new(-8.0, 8.0);

            // any color in block of 8 (see below)
            static ref COLOR_DISTRIBUTION: Uniform<u8> = Uniform::new(0, 8);

            // ttl between 0.1 and 0.5 seconds
            static ref TTL_DISTRIBUTION: Uniform<i64> = Uniform::new(100, 500);
        }

        for _ in 0..count {
            let scatter = self.random_vector3(&SCATTER_DISTRIBUTION);

            // picks any color in the block of 8 the original color belongs to.
            // e.g., if the color argument is 17, picks randomly in [16, 23]
            let color = (color & !7) + COLOR_DISTRIBUTION.sample(&mut self.rng);

            let ttl = Duration::milliseconds(TTL_DISTRIBUTION.sample(&mut self.rng));

            self.insert(Particle {
                kind: ParticleKind::Grav,
                origin: origin + scatter,
                velocity: 15.0 * direction,
                color,
                spawned: time,
                expire: time + ttl,
            });
        }
    }

    /// Creates a lava splash effect.
    pub fn create_lava_splash(&mut self, time: Duration, origin: Vector3<f32>) {
        lazy_static! {
            // ttl between 2 and 2.64 seconds
            static ref TTL_DISTRIBUTION: Uniform<i64> = Uniform::new(2000, 2640);

            // any color on row 14
            static ref COLOR_DISTRIBUTION: Uniform<u8> = Uniform::new(224, 232);

            static ref DIR_OFFSET_DISTRIBUTION: Uniform<f32> = Uniform::new(0.0, 8.0);
            static ref SCATTER_Z_DISTRIBUTION: Uniform<f32> = Uniform::new(0.0, 64.0);
            static ref VELOCITY_DISTRIBUTION: Uniform<f32> = Uniform::new(50.0, 114.0);
        }

        for i in -16..16 {
            for j in -16..16 {
                let direction = Vector3::new(
                    8.0 * i as f32 + DIR_OFFSET_DISTRIBUTION.sample(&mut self.rng),
                    8.0 * j as f32 + DIR_OFFSET_DISTRIBUTION.sample(&mut self.rng),
                    256.0,
                );

                let scatter = Vector3::new(
                    direction.x,
                    direction.y,
                    SCATTER_Z_DISTRIBUTION.sample(&mut self.rng),
                );

                let velocity = VELOCITY_DISTRIBUTION.sample(&mut self.rng);

                let color = COLOR_DISTRIBUTION.sample(&mut self.rng);
                let ttl = Duration::milliseconds(TTL_DISTRIBUTION.sample(&mut self.rng));

                self.insert(Particle {
                    kind: ParticleKind::Grav,
                    origin: origin + scatter,
                    velocity: direction.normalize() * velocity,
                    color,
                    spawned: time,
                    expire: time + ttl,
                });
            }
        }
    }

    /// Creates a teleporter warp effect.
    pub fn create_teleporter_warp(&mut self, time: Duration, origin: Vector3<f32>) {
        lazy_static! {
            // ttl between 0.2 and 0.34 seconds
            static ref TTL_DISTRIBUTION: Uniform<i64> = Uniform::new(200, 340);

            // random grey particles
            static ref COLOR_DISTRIBUTION: Uniform<u8> = Uniform::new(7, 14);

            static ref SCATTER_DISTRIBUTION: Uniform<f32> = Uniform::new(0.0, 4.0);
            static ref VELOCITY_DISTRIBUTION: Uniform<f32> = Uniform::new(50.0, 114.0);
        }

        for i in (-16..16).step_by(4) {
            for j in (-16..16).step_by(4) {
                for k in (-24..32).step_by(4) {
                    let direction = Vector3::new(j as f32, i as f32, k as f32) * 8.0;
                    let scatter = Vector3::new(i as f32, j as f32, k as f32)
                        + self.random_vector3(&SCATTER_DISTRIBUTION);
                    let velocity = VELOCITY_DISTRIBUTION.sample(&mut self.rng);
                    let color = COLOR_DISTRIBUTION.sample(&mut self.rng);
                    let ttl = Duration::milliseconds(TTL_DISTRIBUTION.sample(&mut self.rng));

                    self.insert(Particle {
                        kind: ParticleKind::Grav,
                        origin: origin + scatter,
                        velocity: direction.normalize() * velocity,
                        color,
                        spawned: time,
                        expire: time + ttl,
                    });
                }
            }
        }
    }

    /// Create a particle trail between two points.
    ///
    /// Used for rocket fire/smoke trails, blood spatter, and projectile tracers.
    /// If `sparse` is true, the interval between particles is increased by 3 units.
    pub fn create_trail(
        &mut self,
        time: Duration,
        start: Vector3<f32>,
        end: Vector3<f32>,
        kind: TrailKind,
        sparse: bool,
    ) {
        use TrailKind::*;

        lazy_static! {
            static ref SCATTER_DISTRIBUTION: Uniform<f32> = Uniform::new(-3.0, 3.0);
            static ref FRAME_SKIP_DISTRIBUTION: Uniform<usize> = Uniform::new(0, 4);
            static ref BLOOD_COLOR_DISTRIBUTION: Uniform<u8> = Uniform::new(67, 71);
            static ref VORE_COLOR_DISTRIBUTION: Uniform<u8> = Uniform::new(152, 156);
        }

        let distance = (end - start).magnitude();
        let direction = (end - start).normalize();

        // particle interval in units
        let interval = if sparse { 3.0 } else { 1.0 }
            + match kind {
                BloodSlight => 3.0,
                _ => 0.0,
            };

        let ttl = Duration::seconds(2);

        for step in 0..(distance / interval) as i32 {
            let frame_skip = FRAME_SKIP_DISTRIBUTION.sample(&mut self.rng);
            let particle_kind = match kind {
                Rocket => ParticleKind::Fire { frame_skip },
                Smoke => ParticleKind::Fire {
                    frame_skip: frame_skip + 2,
                },
                Blood | BloodSlight => ParticleKind::Grav,
                TracerGreen | TracerRed | Vore => ParticleKind::Static,
            };

            let scatter = self.random_vector3(&SCATTER_DISTRIBUTION);

            let origin = start
                + direction * interval
                + match kind {
                    // vore scatter is [-16, 15] in original
                    // this gives range of ~[-16, 16]
                    Vore => scatter * 5.33,
                    _ => scatter,
                };

            let velocity = match kind {
                TracerGreen | TracerRed => {
                    30.0 * if step & 1 == 1 {
                        Vector3::new(direction.y, -direction.x, 0.0)
                    } else {
                        Vector3::new(-direction.y, direction.x, 0.0)
                    }
                }

                _ => Vector3::zero(),
            };

            let color = match kind {
                Rocket => COLOR_RAMP_FIRE.ramp[frame_skip],
                Smoke => COLOR_RAMP_FIRE.ramp[frame_skip + 2],
                Blood | BloodSlight => BLOOD_COLOR_DISTRIBUTION.sample(&mut self.rng),
                TracerGreen => 52 + 2 * (step & 4) as u8,
                TracerRed => 230 + 2 * (step & 4) as u8,
                Vore => VORE_COLOR_DISTRIBUTION.sample(&mut self.rng),
            };

            self.insert(Particle {
                kind: particle_kind,
                origin,
                velocity,
                color,
                spawned: time,
                expire: time + ttl,
            });
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use cgmath::Zero;

    fn particles_eq(p1: &Particle, p2: &Particle) -> bool {
        p1.color == p2.color && p1.velocity == p2.velocity && p1.origin == p2.origin
    }

    #[test]
    fn test_particle_list_update() {
        let mut list = Particles::with_capacity(10);
        let exp_times = vec![10, 5, 2, 7, 3];
        for exp in exp_times.iter() {
            list.insert(Particle {
                kind: ParticleKind::Static,
                origin: Vector3::zero(),
                velocity: Vector3::zero(),
                color: 0,
                spawned: Duration::zero(),
                expire: Duration::seconds(*exp),
            });
        }

        let expected: Vec<_> = exp_times
            .iter()
            .filter(|t| **t > 5)
            .map(|t| Particle {
                kind: ParticleKind::Static,
                origin: Vector3::zero(),
                velocity: Vector3::zero(),
                color: 0,
                spawned: Duration::zero(),
                expire: Duration::seconds(*t),
            })
            .collect();
        let mut after_update: Vec<Particle> = Vec::new();
        list.update(Duration::seconds(5), Duration::milliseconds(17), 10.0);
        after_update
            .iter()
            .zip(expected.iter())
            .for_each(|(p1, p2)| assert!(particles_eq(p1, p2)));
    }
}


================================================
FILE: src/client/input/console.rs
================================================
// Copyright © 2018 Cormac O'Brien
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
// and associated documentation files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

use std::{cell::RefCell, rc::Rc};

use crate::common::console::Console;

use failure::Error;
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode as Key, WindowEvent};

pub struct ConsoleInput {
    console: Rc<RefCell<Console>>,
}

impl ConsoleInput {
    pub fn new(console: Rc<RefCell<Console>>) -> ConsoleInput {
        ConsoleInput { console }
    }

    pub fn handle_event<T>(&self, event: Event<T>) -> Result<(), Error> {
        match event {
            Event::WindowEvent { event, .. } => match event {
                WindowEvent::ReceivedCharacter(c) => self.console.borrow_mut().send_char(c),

                WindowEvent::KeyboardInput {
                    input:
                        KeyboardInput {
                            virtual_keycode: Some(key),
                            state: ElementState::Pressed,
                            ..
                        },
                    ..
                } => match key {
                    Key::Up => self.console.borrow_mut().history_up(),
                    Key::Down => self.console.borrow_mut().history_down(),
                    Key::Left => self.console.borrow_mut().cursor_left(),
                    Key::Right => self.console.borrow_mut().cursor_right(),
                    Key::Grave => self.console.borrow_mut().stuff_text("toggleconsole\n"),
                    _ => (),
                },

                _ => (),
            },

            _ => (),
        }

        Ok(())
    }
}


================================================
FILE: src/client/input/game.rs
================================================
// Copyright © 2018 Cormac O'Brien
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
// and associated documentation files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

use std::{
    cell::{Cell, RefCell},
    collections::HashMap,
    rc::Rc,
    str::FromStr,
    string::ToString,
};

use crate::common::{
    console::{CmdRegistry, Console},
    parse,
};

use failure::Error;
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
use winit::{
    dpi::LogicalPosition,
    event::{
        DeviceEvent, ElementState, Event, KeyboardInput, MouseButton, MouseScrollDelta,
        VirtualKeyCode as Key, WindowEvent,
    },
};

const ACTION_COUNT: usize = 19;

static INPUT_NAMES: [&'static str; 79] = [
    ",",
    ".",
    "/",
    "0",
    "1",
    "2",
    "3",
    "4",
    "5",
    "6",
    "7",
    "8",
    "9",
    "A",
    "ALT",
    "B",
    "BACKSPACE",
    "C",
    "CTRL",
    "D",
    "DEL",
    "DOWNARROW",
    "E",
    "END",
    "ENTER",
    "ESCAPE",
    "F",
    "F1",
    "F10",
    "F11",
    "F12",
    "F2",
    "F3",
    "F4",
    "F5",
    "F6",
    "F7",
    "F8",
    "F9",
    "G",
    "H",
    "HOME",
    "I",
    "INS",
    "J",
    "K",
    "L",
    "LEFTARROW",
    "M",
    "MOUSE1",
    "MOUSE2",
    "MOUSE3",
    "MWHEELDOWN",
    "MWHEELUP",
    "N",
    "O",
    "P",
    "PGDN",
    "PGUP",
    "Q",
    "R",
    "RIGHTARROW",
    "S",
    "SEMICOLON",
    "SHIFT",
    "SPACE",
    "T",
    "TAB",
    "U",
    "UPARROW",
    "V",
    "W",
    "X",
    "Y",
    "Z",
    "[",
    "\\",
    "]",
    "`",
];

static INPUT_VALUES: [BindInput; 79] = [
    BindInput::Key(Key::Comma),
    BindInput::Key(Key::Period),
    BindInput::Key(Key::Slash),
    BindInput::Key(Key::Key0),
    BindInput::Key(Key::Key1),
    BindInput::Key(Key::Key2),
    BindInput::Key(Key::Key3),
    BindInput::Key(Key::Key4),
    BindInput::Key(Key::Key5),
    BindInput::Key(Key::Key6),
    BindInput::Key(Key::Key7),
    BindInput::Key(Key::Key8),
    BindInput::Key(Key::Key9),
    BindInput::Key(Key::A),
    BindInput::Key(Key::LAlt),
    BindInput::Key(Key::B),
    BindInput::Key(Key::Back),
    BindInput::Key(Key::C),
    BindInput::Key(Key::LControl),
    BindInput::Key(Key::D),
    BindInput::Key(Key::Delete),
    BindInput::Key(Key::Down),
    BindInput::Key(Key::E),
    BindInput::Key(Key::End),
    BindInput::Key(Key::Return),
    BindInput::Key(Key::Escape),
    BindInput::Key(Key::F),
    BindInput::Key(Key::F1),
    BindInput::Key(Key::F10),
    BindInput::Key(Key::F11),
    BindInput::Key(Key::F12),
    BindInput::Key(Key::F2),
    BindInput::Key(Key::F3),
    BindInput::Key(Key::F4),
    BindInput::Key(Key::F5),
    BindInput::Key(Key::F6),
    BindInput::Key(Key::F7),
    BindInput::Key(Key::F8),
    BindInput::Key(Key::F9),
    BindInput::Key(Key::G),
    BindInput::Key(Key::H),
    BindInput::Key(Key::Home),
    BindInput::Key(Key::I),
    BindInput::Key(Key::Insert),
    BindInput::Key(Key::J),
    BindInput::Key(Key::K),
    BindInput::Key(Key::L),
    BindInput::Key(Key::Left),
    BindInput::Key(Key::M),
    BindInput::MouseButton(MouseButton::Left),
    BindInput::MouseButton(MouseButton::Right),
    BindInput::MouseButton(MouseButton::Middle),
    BindInput::MouseWheel(MouseWheel::Down),
    BindInput::MouseWheel(MouseWheel::Up),
    BindInput::Key(Key::N),
    BindInput::Key(Key::O),
    BindInput::Key(Key::P),
    BindInput::Key(Key::PageDown),
    BindInput::Key(Key::PageUp),
    BindInput::Key(Key::Q),
    BindInput::Key(Key::R),
    BindInput::Key(Key::Right),
    BindInput::Key(Key::S),
    BindInput::Key(Key::Semicolon),
    BindInput::Key(Key::LShift),
    BindInput::Key(Key::Space),
    BindInput::Key(Key::T),
    BindInput::Key(Key::Tab),
    BindInput::Key(Key::U),
    BindInput::Key(Key::Up),
    BindInput::Key(Key::V),
    BindInput::Key(Key::W),
    BindInput::Key(Key::X),
    BindInput::Key(Key::Y),
    BindInput::Key(Key::Z),
    BindInput::Key(Key::LBracket),
    BindInput::Key(Key::Backslash),
    BindInput::Key(Key::RBracket),
    BindInput::Key(Key::Grave),
];

/// A unique identifier for an in-game action.
#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter)]
pub enum Action {
    /// Move forward.
    Forward = 0,

    /// Move backward.
    Back = 1,

    /// Strafe left.
    MoveLeft = 2,

    /// Strafe right.
    MoveRight = 3,

    /// Move up (when swimming).
    MoveUp = 4,

    /// Move down (when swimming).
    MoveDown = 5,

    /// Look up.
    LookUp = 6,

    /// Look down.
    LookDown = 7,

    /// Look left.
    Left = 8,

    /// Look right.
    Right = 9,

    /// Change move speed (walk/run).
    Speed = 10,

    /// Jump.
    Jump = 11,

    /// Interpret `Left`/`Right` like `MoveLeft`/`MoveRight`.
    Strafe = 12,

    /// Attack with the current weapon.
    Attack = 13,

    /// Interact with an object (not used).
    Use = 14,

    /// Interpret `Forward`/`Back` like `LookUp`/`LookDown`.
    KLook = 15,

    /// Interpret upward/downward vertical mouse movements like `LookUp`/`LookDown`.
    MLook = 16,

    /// If in single-player, show the current level stats. If in multiplayer, show the scoreboard.
    ShowScores = 17,

    /// Show the team scoreboard.
    ShowTeamScores = 18,
}

impl FromStr for Action {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let action = match s.to_lowercase().as_str() {
            "forward" => Action::Forward,
            "back" => Action::Back,
            "moveleft" => Action::MoveLeft,
            "moveright" => Action::MoveRight,
            "moveup" => Action::MoveUp,
            "movedown" => Action::MoveDown,
            "lookup" => Action::LookUp,
            "lookdown" => Action::LookDown,
            "left" => Action::Left,
            "right" => Action::Right,
            "speed" => Action::Speed,
            "jump" => Action::Jump,
            "strafe" => Action::Strafe,
            "attack" => Action::Attack,
            "use" => Action::Use,
            "klook" => Action::KLook,
            "mlook" => Action::MLook,
            "showscores" => Action::ShowScores,
            "showteamscores" => Action::ShowTeamScores,
            _ => bail!("Invalid action name: {}", s),
        };

        Ok(action)
    }
}

impl ToString for Action {
    fn to_string(&self) -> String {
        String::from(match *self {
            Action::Forward => "forward",
            Action::Back => "back",
            Action::MoveLeft => "moveleft",
            Action::MoveRight => "moveright",
            Action::MoveUp => "moveup",
            Action::MoveDown => "movedown",
            Action::LookUp => "lookup",
            Action::LookDown => "lookdown",
            Action::Left => "left",
            Action::Right => "right",
            Action::Speed => "speed",
            Action::Jump => "jump",
            Action::Strafe => "strafe",
            Action::Attack => "attack",
            Action::Use => "use",
            Action::KLook => "klook",
            Action::MLook => "mlook",
            Action::ShowScores => "showscores",
            Action::ShowTeamScores => "showteamscores",
        })
    }
}

// for game input, we only care about the direction the mouse wheel moved, not how far it went in
// one event
/// A movement of the mouse wheel up or down.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum MouseWheel {
    Up,
    Down,
}

// TODO: this currently doesn't handle NaN and treats 0.0 as negative which is probably not optimal
impl ::std::convert::From<MouseScrollDelta> for MouseWheel {
    fn from(src: MouseScrollDelta) -> MouseWheel {
        match src {
            MouseScrollDelta::LineDelta(_, y) => {
                if y > 0.0 {
                    MouseWheel::Up
                } else {
                    MouseWheel::Down
                }
            }

            MouseScrollDelta::PixelDelta(LogicalPosition { y, .. }) => {
                if y > 0.0 {
                    MouseWheel::Up
                } else {
                    MouseWheel::Down
                }
            }
        }
    }
}

/// A physical input that can be bound to a command.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum BindInput {
    /// A key pressed on the keyboard.
    Key(Key),

    /// A button pressed on the mouse.
    MouseButton(MouseButton),

    /// A direction scrolled on the mouse wheel.
    MouseWheel(MouseWheel),
}

impl ::std::convert::From<Key> for BindInput {
    fn from(src: Key) -> BindInput {
        BindInput::Key(src)
    }
}

impl ::std::convert::From<MouseButton> for BindInput {
    fn from(src: MouseButton) -> BindInput {
        BindInput::MouseButton(src)
    }
}

impl ::std::convert::From<MouseWheel> for BindInput {
    fn from(src: MouseWheel) -> BindInput {
        BindInput::MouseWheel(src)
    }
}

impl ::std::convert::From<MouseScrollDelta> for BindInput {
    fn from(src: MouseScrollDelta) -> BindInput {
        BindInput::MouseWheel(MouseWheel::from(src))
    }
}

impl FromStr for BindInput {
    type Err = Error;

    fn from_str(src: &str) -> Result<BindInput, Error> {
        let upper = src.to_uppercase();

        for (i, name) in INPUT_NAMES.iter().enumerate() {
            if upper == *name {
                return Ok(INPUT_VALUES[i].clone());
            }
        }

        bail!("\"{}\" isn't a valid key", src);
    }
}

impl ToString for BindInput {
    fn to_string(&self) -> String {
        // this could be a binary search but it's unlikely to affect performance much
        for (i, input) in INPUT_VALUES.iter().enumerate() {
            if self == input {
                return INPUT_NAMES[i].to_owned();
            }
        }

        String::new()
    }
}

/// An operation to perform when a `BindInput` is received.
#[derive(Clone, Debug)]
pub enum BindTarget {
    /// An action to set/unset.
    Action {
        // + is true, - is false
        // so "+forward" maps to trigger: true, action: Action::Forward
        trigger: ElementState,
        action: Action,
    },

    /// Text to push to the console execution buffer.
    ConsoleInput { text: String },
}

impl FromStr for BindTarget {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match parse::action(s) {
            // first, check if this is an action
            Ok((_, (trigger, action_str))) => {
                let action = match Action::from_str(&action_str) {
                    Ok(a) => a,
                    _ => return Ok(BindTarget::ConsoleInput { text: s.to_owned() }),
                };

                Ok(BindTarget::Action { trigger, action })
            }

            // if the parse fails, assume it's a cvar/cmd and return the text
            _ => Ok(BindTarget::ConsoleInput { text: s.to_owned() }),
        }
    }
}

impl ToString for BindTarget {
    fn to_string(&self) -> String {
        match *self {
            BindTarget::Action { trigger, action } => {
                String::new()
                    + match trigger {
                        ElementState::Pressed => "+",
                        ElementState::Released => "-",
                    }
                    + &action.to_string()
            }

            BindTarget::ConsoleInput { ref text } => format!("\"{}\"", text.to_owned()),
        }
    }
}

#[derive(Clone)]
pub struct GameInput {
    console: Rc<RefCell<Console>>,
    bindings: Rc<RefCell<HashMap<BindInput, BindTarget>>>,
    action_states: Rc<RefCell<[bool; ACTION_COUNT]>>,
    mouse_delta: (f64, f64),
    impulse: Rc<Cell<u8>>,
}

impl GameInput {
    pub fn new(console: Rc<RefCell<Console>>) -> GameInput {
        GameInput {
            console,
            bindings: Rc::new(RefCell::new(HashMap::new())),
            action_states: Rc::new(RefCell::new([false; ACTION_COUNT])),
            mouse_delta: (0.0, 0.0),
            impulse: Rc::new(Cell::new(0)),
        }
    }

    pub fn mouse_delta(&self) -> (f64, f64) {
        self.mouse_delta
    }

    pub fn impulse(&self) -> u8 {
        self.impulse.get()
    }

    /// Bind the default controls.
    pub fn bind_defaults(&mut self) {
        self.bind(Key::W, BindTarget::from_str("+forward").unwrap());
        self.bind(Key::A, BindTarget::from_str("+moveleft").unwrap());
        self.bind(Key::S, BindTarget::from_str("+back").unwrap());
        self.bind(Key::D, BindTarget::from_str("+moveright").unwrap());
        self.bind(Key::Space, BindTarget::from_str("+jump").unwrap());
        self.bind(Key::Up, BindTarget::from_str("+lookup").unwrap());
        self.bind(Key::Left, BindTarget::from_str("+left").unwrap());
        self.bind(Key::Down, BindTarget::from_str("+lookdown").unwrap());
        self.bind(Key::Right, BindTarget::from_str("+right").unwrap());
        self.bind(Key::LControl, BindTarget::from_str("+attack").unwrap());
        self.bind(Key::E, BindTarget::from_str("+use").unwrap());
        self.bind(Key::Grave, BindTarget::from_str("toggleconsole").unwrap());
        self.bind(Key::Key1, BindTarget::from_str("impulse 1").unwrap());
        self.bind(Key::Key2, BindTarget::from_str("impulse 2").unwrap());
        self.bind(Key::Key3, BindTarget::from_str("impulse 3").unwrap());
        self.bind(Key::Key4, BindTarget::from_str("impulse 4").unwrap());
        self.bind(Key::Key5, BindTarget::from_str("impulse 5").unwrap());
        self.bind(Key::Key6, BindTarget::from_str("impulse 6").unwrap());
        self.bind(Key::Key7, BindTarget::from_str("impulse 7").unwrap());
        self.bind(Key::Key8, BindTarget::from_str("impulse 8").unwrap());
        self.bind(Key::Key9, BindTarget::from_str("impulse 9").unwrap());
    }

    /// Bind a `BindInput` to a `BindTarget`.
    pub fn bind<I, T>(&mut self, input: I, target: T) -> Option<BindTarget>
    where
        I: Into<BindInput>,
        T: Into<BindTarget>,
    {
        self.bindings
            .borrow_mut()
            .insert(input.into(), target.into())
    }

    /// Return the `BindTarget` that `input` is bound to, or `None` if `input` is not present.
    pub fn binding<I>(&self, input: I) -> Option<BindTarget>
    where
        I: Into<BindInput>,
    {
        self.bindings.borrow().get(&input.into()).map(|t| t.clone())
    }

    pub fn handle_event<T>(&mut self, outer_event: Event<T>) {
        let (input, state): (BindInput, _) = match outer_event {
            Event::WindowEvent { event, .. } => match event {
                WindowEvent::KeyboardInput {
                    input:
                        KeyboardInput {
                            state,
                            virtual_keycode: Some(key),
                            ..
                        },
                    ..
                } => (key.into(), state),

                WindowEvent::MouseInput { state, button, .. } => (button.into(), state),
                WindowEvent::MouseWheel { delta, .. } => (delta.into(), ElementState::Pressed),
                _ => return,
            },

            Event::DeviceEvent { event, .. } => match event {
                DeviceEvent::MouseMotion { delta } => {
                    self.mouse_delta.0 += delta.0;
                    self.mouse_delta.1 += delta.1;
                    return;
                }

                _ => return,
            },

            _ => return,
        };

        self.handle_input(input, state);
    }

    pub fn handle_input<I>(&mut self, input: I, state: ElementState)
    where
        I: Into<BindInput>,
    {
        let bind_input = input.into();

        // debug!("handle input {:?}: {:?}", &bind_input, state);
        if let Some(target) = self.bindings.borrow().get(&bind_input) {
            match *target {
                BindTarget::Action { trigger, action } => {
                    self.action_states.borrow_mut()[action as usize] = state == trigger;
                    debug!(
                        "{}{}",
                        if state == trigger { '+' } else { '-' },
                        action.to_string()
                    );
                }

                BindTarget::ConsoleInput { ref text } => {
                    if state == ElementState::Pressed {
                        self.console.borrow_mut().stuff_text(text);
                    }
                }
            }
        }
    }

    pub fn action_state(&self, action: Action) -> bool {
        self.action_states.borrow()[action as usize]
    }

    // TODO: roll actions into a loop
    pub fn register_cmds(&self, cmds: &mut CmdRegistry) {
        let states = [("+", true), ("-", false)];
        for action in Action::iter() {
            for (state_str, state_bool) in states.iter().cloned() {
                let action_states = self.action_states.clone();
                let cmd_name = format!("{}{}", state_str, action.to_string());
                cmds.insert_or_replace(
                    &cmd_name,
                    Box::new(move |_| {
                        action_states.borrow_mut()[action as usize] = state_bool;
                        String::new()
                    }),
                )
                .unwrap();
            }
        }

        // "bind"
        let bindings = self.bindings.clone();
        cmds.insert_or_replace(
            "bind",
            Box::new(move |args| {
                match args.len() {
                    // bind (key)
                    // queries what (key) is bound to, if anything
                    1 => match BindInput::from_str(args[0]) {
                        Ok(i) => match bindings.borrow().get(&i) {
                            Some(t) => format!("\"{}\" = \"{}\"", i.to_string(), t.to_string()),
                            None => format!("\"{}\" is not bound", i.to_string()),
                        },

                        Err(_) => format!("\"{}\" isn't a valid key", args[0]),
                    },

                    // bind (key) [command]
                    2 => match BindInput::from_str(args[0]) {
                        Ok(input) => match BindTarget::from_str(args[1]) {
                            Ok(target) => {
                                bindings.borrow_mut().insert(input, target);
                                debug!("Bound {:?} to {:?}", input, args[1]);
                                String::new()
                            }
                            Err(_) => {
                                format!("\"{}\" isn't a valid bind target", args[1])
                            }
                        },

                        Err(_) => format!("\"{}\" isn't a valid key", args[0]),
                    },

                    _ => "bind [key] (command): attach a command to a key".to_owned(),
                }
            }),
        )
        .unwrap();

        // "unbindall"
        let bindings = self.bindings.clone();
        cmds.insert_or_replace(
            "unbindall",
            Box::new(move |args| match args.len() {
                0 => {
                    let _ = bindings.replace(HashMap::new());
                    String::new()
                }
                _ => "unbindall: delete all keybindings".to_owned(),
            }),
        )
        .unwrap();

        // "impulse"
        let impulse = self.impulse.clone();
        cmds.insert_or_replace(
            "impulse",
            Box::new(move |args| {
                println!("args: {}", args.len());
                match args.len() {
                    1 => match u8::from_str(args[0]) {
                        Ok(i) => {
                            impulse.set(i);
                            String::new()
                        }
                        Err(_) => "Impulse must be a number between 0 and 255".to_owned(),
                    },

                    _ => "usage: impulse [number]".to_owned(),
                }
            }),
        )
        .unwrap();
    }

    // must be called every frame!
    pub fn refresh(&mut self) {
        self.clear_mouse();
        self.clear_impulse();
    }

    fn clear_mouse(&mut self) {
        self.handle_input(MouseWheel::Up, ElementState::Released);
        self.handle_input(MouseWheel::Down, ElementState::Released);
        self.mouse_delta = (0.0, 0.0);
    }

    fn clear_impulse(&mut self) {
        self.impulse.set(0);
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_action_to_string() {
        let act = Action::Forward;
        assert_eq!(act.to_string(), "forward");
    }

    #[test]
    fn test_bind_target_action_to_string() {
        let target = BindTarget::Action {
            trigger: ElementState::Pressed,
            action: Action::Forward,
        };

        assert_eq!(target.to_string(), "+forward");
    }
}


================================================
FILE: src/client/input/menu.rs
================================================
// Copyright © 2019 Cormac O'Brien
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
// and associated documentation files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

use std::{cell::RefCell, rc::Rc};

use crate::{client::menu::Menu, common::console::Console};

use failure::Error;
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode as Key, WindowEvent};

pub struct MenuInput {
    menu: Rc<RefCell<Menu>>,
    console: Rc<RefCell<Console>>,
}

impl MenuInput {
    pub fn new(menu: Rc<RefCell<Menu>>, console: Rc<RefCell<Console>>) -> MenuInput {
        MenuInput { menu, console }
    }

    pub fn handle_event<T>(&self, event: Event<T>) -> Result<(), Error> {
        match event {
            Event::WindowEvent { event, .. } => match event {
                WindowEvent::ReceivedCharacter(_) => (),

                WindowEvent::KeyboardInput {
                    input:
                        KeyboardInput {
                            virtual_keycode: Some(key),
                            state: ElementState::Pressed,
                            ..
                        },
                    ..
                } => match key {
                    Key::Escape => {
                        if self.menu.borrow().at_root() {
                            self.console.borrow().stuff_text("togglemenu\n");
                        } else {
                            self.menu.borrow().back()?;
                        }
                    }

                    Key::Up => self.menu.borrow().prev()?,
                    Key::Down => self.menu.borrow().next()?,
                    Key::Return => self.menu.borrow().activate()?,
                    Key::Left => self.menu.borrow().left()?,
                    Key::Right => self.menu.borrow().right()?,

                    _ => (),
                },

                _ => (),
            },

            _ => (),
        }

        Ok(())
    }
}


================================================
FILE: src/client/input/mod.rs
================================================
// Copyright © 2018 Cormac O'Brien
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
// and associated documentation files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

pub mod console;
pub mod game;
pub mod menu;

use std::{cell::RefCell, rc::Rc};

use crate::{
    client::menu::Menu,
    common::console::{CmdRegistry, Console},
};

use failure::Error;
use winit::event::{Event, WindowEvent};

use self::{
    console::ConsoleInput,
    game::{BindInput, BindTarget, GameInput},
    menu::MenuInput,
};

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum InputFocus {
    Game,
    Console,
    Menu,
}

pub struct Input {
    window_focused: bool,
    focus: InputFocus,

    game_input: GameInput,
    console_input: ConsoleInput,
    menu_input: MenuInput,
}

impl Input {
    pub fn new(
        init_focus: InputFocus,
        console: Rc<RefCell<Console>>,
        menu: Rc<RefCell<Menu>>,
    ) -> Input {
        Input {
            window_focused: true,
            focus: init_focus,

            game_input: GameInput::new(console.clone()),
            console_input: ConsoleInput::new(console.clone()),
            menu_input: MenuInput::new(menu.clone(), console.clone()),
        }
    }

    pub fn handle_event<T>(&mut self, event: Event<T>) -> Result<(), Error> {
        match event {
            // we're polling for hardware events, so we have to check window focus ourselves
            Event::WindowEvent {
                event: WindowEvent::Focused(focused),
                ..
            } => self.window_focused = focused,

            _ => {
                if self.window_focused {
                    match self.focus {
                        InputFocus::Game => self.game_input.handle_event(event),
                        InputFocus::Console => self.console_input.handle_event(event)?,
                        InputFocus::Menu => self.menu_input.handle_event(event)?,
                    }
                }
            }
        }

        Ok(())
    }

    pub fn focus(&self) -> InputFocus {
        self.focus
    }

    pub fn set_focus(&mut self, new_focus: InputFocus) {
        self.focus = new_focus;
    }

    /// Bind a `BindInput` to a `BindTarget`.
    pub fn bind<I, T>(&mut self, input: I, target: T) -> Option<BindTarget>
    where
        I: Into<BindInput>,
        T: Into<BindTarget>,
    {
        self.game_input.bind(input, target)
    }

    pub fn bind_defaults(&mut self) {
        self.game_input.bind_defaults();
    }

    pub fn game_input(&self) -> Option<&GameInput> {
        if let InputFocus::Game = self.focus {
            Some(&self.game_input)
        } else {
            None
        }
    }

    pub fn game_input_mut(&mut self) -> Option<&mut GameInput> {
        if let InputFocus::Game = self.focus {
            Some(&mut self.game_input)
        } else {
            None
        }
    }

    pub fn register_cmds(&self, cmds: &mut CmdRegistry) {
        self.game_input.register_cmds(cmds);
    }
}


================================================
FILE: src/client/menu/item.rs
================================================
// Copyright © 2018 Cormac O'Brien
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

use std::cell::{Cell, RefCell};

use crate::client::menu::Menu;

use failure::Error;

pub enum Item {
    Submenu(Menu),
    Action(Box<dyn Fn()>),
    Toggle(Toggle),
    Enum(Enum),
    Slider(Slider),
    TextField(TextField),
}

pub struct Toggle {
    state: Cell<bool>,
    on_toggle: Box<dyn Fn(bool)>,
}

impl Toggle {
    pub fn new(init: bool, on_toggle: Box<dyn Fn(bool)>) -> Toggle {
        let t = Toggle {
            state: Cell::new(init),
            on_toggle,
        };

        // initialize with default
        (t.on_toggle)(init);

        t
    }

    pub fn set_false(&self) {
        self.state.set(false);
        (self.on_toggle)(self.state.get());
    }

    pub fn set_true(&self) {
        self.state.set(true);
        (self.on_toggle)(self.state.get());
    }

    pub fn toggle(&self) {
        self.state.set(!self.state.get());
        (self.on_toggle)(self.state.get());
    }

    pub fn get(&self) -> bool {
        self.state.get()
    }
}

// TODO: add wrapping configuration to enums
// e.g. resolution enum wraps, texture filtering does not
pub struct Enum {
    selected: Cell<usize>,
    items: Vec<EnumItem>,
}

impl Enum {
    pub fn new(init: usize, items: Vec<EnumItem>) -> Result<Enum, Error> {
        ensure!(items.len() > 0, "Enum element must have at least one item");
        ensure!(init < items.len(), "Invalid initial item ID");

        let e = Enum {
            selected: Cell::new(init),
            items,
        };

        // initialize with the default choice
        (e.items[e.selected.get()].on_select)();

        Ok(e)
    }

    pub fn selected_name(&self) -> &str {
        self.items[self.selected.get()].name.as_str()
    }

    pub fn select_next(&self) {
        let selected = match self.selected.get() + 1 {
            s if s >= self.items.len() => 0,
            s => s,
        };

        self.selected.set(selected);
        (self.items[selected].on_select)();
    }

    pub fn select_prev(&self) {
        let selected = match self.selected.get() {
            0 => self.items.len() - 1,
            s => s - 1,
        };

        self.selected.set(selected);
        (self.items[selected].on_select)();
    }
}

pub struct EnumItem {
    name: String,
    on_select: Box<dyn Fn()>,
}

impl EnumItem {
    pub fn new<S>(name: S, on_select: Box<dyn Fn()>) -> Result<EnumItem, Error>
    where
        S: AsRef<str>,
    {
        Ok(EnumItem {
            name: name.as_ref().to_string(),
            on_select,
        })
    }
}

pub struct Slider {
    min: f32,
    _max: f32,
    increment: f32,
    steps: usize,

    selected: Cell<usize>,
    on_select: Box<dyn Fn(f32)>,
}

impl Slider {
    pub fn new(
        min: f32,
        max: f32,
        steps: usize,
        init: usize,
        on_select: Box<dyn Fn(f32)>,
    ) -> Result<Slider, Error> {
        ensure!(steps > 1, "Slider must have at least 2 steps");
        ensure!(init < steps, "Invalid initial setting");
        ensure!(
            min < max,
            "Minimum setting must be less than maximum setting"
        );

        Ok(Slider {
            min,
            _max: max,
            increment: (max - min) / (steps - 1) as f32,
            steps,
            selected: Cell::new(init),
            on_select,
        })
    }

    pub fn increase(&self) {
        let old = self.selected.get();

        if old != self.steps - 1 {
            self.selected.set(old + 1);
        }

        (self.on_select)(self.min + self.selected.get() as f32 * self.increment);
    }

    pub fn decrease(&self) {
        let old = self.selected.get();

        if old != 0 {
            self.selected.set(old - 1);
        }

        (self.on_select)(self.min + self.selected.get() as f32 * self.increment);
    }

    pub fn position(&self) -> f32 {
        self.selected.get() as f32 / self.steps as f32
    }
}

pub struct TextField {
    chars: RefCell<Vec<char>>,
    max_len: Option<usize>,
    on_update: Box<dyn Fn(&str)>,
    cursor: Cell<usize>,
}

impl TextField {
    pub fn new<S>(
        default: Option<S>,
        max_len: Option<usize>,
        on_update: Box<dyn Fn(&str)>,
    ) -> Result<TextField, Error>
    where
        S: AsRef<str>,
    {
        let chars = RefCell::new(match default {
            Some(d) => d.as_ref().chars().collect(),
            None => Vec::new(),
        });

        let cursor = Cell::new(chars.borrow().len());

        Ok(TextField {
            chars,
            max_len,
            on_update,
            cursor,
        })
    }

    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }

    pub fn text(&self) -> String {
        self.chars.borrow().iter().collect()
    }

    pub fn len(&self) -> usize {
        self.chars.borrow().len()
    }

    pub fn set_cursor(&self, cursor: usize) -> Result<(), Error> {
        ensure!(cursor <= self.len(), "Index out of range");

        self.cursor.set(cursor);

        Ok(())
    }

    pub fn home(&self) {
        self.cursor.set(0);
    }

    pub fn end(&self) {
        self.cursor.set(self.len());
    }

    pub fn cursor_right(&self) {
        let curs = self.cursor.get();
        if curs < self.len() {
            self.cursor.set(curs + 1);
        }
    }

    pub fn cursor_left(&self) {
        let curs = self.cursor.get();
        if curs > 1 {
            self.cursor.set(curs - 1);
        }
    }

    pub fn insert(&self, c: char) {
        if let Some(l) = self.max_len {
            if self.len() == l {
                return;
            }
        }

        self.chars.borrow_mut().insert(self.cursor.get(), c);
        (self.on_update)(&self.text());
    }

    pub fn backspace(&self) {
        if self.cursor.get() > 1 {
            self.chars.borrow_mut().remove(self.cursor.get() - 1);
            (self.on_update)(&self.text());
        }
    }

    pub fn delete(&self) {
        if self.cursor.get() < self.len() {
            self.chars.borrow_mut().remove(self.cursor.get());
            (self.on_update)(&self.text());
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::{cell::RefCell, rc::Rc};

    #[test]
    fn test_toggle() {
        let s = Rc::new(RefCell::new("false".to_string()));

        let s2 = s.clone();
        let item = Toggle::new(
            false,
            Box::new(move |state| {
                s2.replace(format!("{}", state));
            }),
        );
        item.toggle();

        assert_eq!(*s.borrow(), "true");
    }

    #[test]
    fn test_enum() {
        let target = Rc::new(RefCell::new("null".to_string()));

        let enum_items = (0..3i32)
            .into_iter()
            .map(|i: i32| {
                let target_handle = target.clone();
                EnumItem::new(
                    format!("option_{}", i),
                    Box::new(move || {
                        target_handle.replace(format!("option_{}", i));
                    }),
                )
                .unwrap()
            })
            .collect();

        let e = Enum::new(0, enum_items).unwrap();
        assert_eq!(*target.borrow(), "option_0");

        // wrap under
        e.select_prev();
        assert_eq!(*target.borrow(), "option_2");

        e.select_next();
        e.select_next();
        e.select_next();
        assert_eq!(*target.borrow(), "option_2");

        // wrap over
        e.select_next();
        assert_eq!(*target.borrow(), "option_0");
    }

    #[test]
    fn test_slider() {
        let f = Rc::new(Cell::new(0.0f32));

        let f2 = f.clone();
        let item = Slider::new(
            0.0,
            10.0,
            11,
            0,
            Box::new(move |f| {
                f2.set(f);
            }),
        )
        .unwrap();

        // don't underflow
        item.decrease();
        assert_eq!(f.get(), 0.0);

        for i in 0..10 {
            item.increase();
            assert_eq!(f.get(), i as f32 + 1.0);
        }

        // don't overflow
        item.increase();
        assert_eq!(f.get(), 10.0);
    }

    #[test]
    fn test_textfield() {
        let MAX_LEN = 10;
        let s = Rc::new(RefCell::new("before".to_owned()));
        let s2 = s.clone();

        let mut tf = TextField::new(
            Some("default"),
            Some(MAX_LEN),
            Box::new(move |x| {
                s2.replace(x.to_string());
            }),
        )
        .unwrap();

        tf.cursor_left();
        tf.backspace();
        tf.backspace();
        tf.home();
        tf.delete();
        tf.delete();
        tf.delete();
        tf.cursor_right();
        tf.insert('f');
        tf.end();
        tf.insert('e');
        tf.insert('r');

        assert_eq!(tf.text(), *s.borrow());

        for _ in 0..2 * MAX_LEN {
            tf.insert('x');
        }

        assert_eq!(tf.len(), MAX_LEN);
    }
}


================================================
FILE: src/client/menu/mod.rs
================================================
// Copyright © 2018 Cormac O'Brien
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

mod item;

use std::cell::Cell;

use failure::Error;

pub use self::item::{Enum, EnumItem, Item, Slider, TextField, Toggle};

#[derive(Clone, Copy, Debug)]
pub enum MenuState {
    /// Menu is inactive.
    Inactive,

    /// Menu is active. `index` indicates the currently selected element.
    Active { index: usize },

    /// A submenu of this menu is active. `index` indicates the active submenu.
    InSubMenu { index: usize },
}

/// Specifies how the menu body should be rendered.
pub enum MenuBodyView {
    /// The menu body is rendered using a predefined bitmap.
    Predefined {
        /// The path to the bitmap.
        path: String,
    },
    /// The menu body is rendered dynamically based on its contents.
    Dynamic,
}

pub struct MenuView {
    pub draw_plaque: bool,
    pub title_path: String,
    pub body: MenuBodyView,
}

impl MenuView {
    /// Returns true if the Quake plaque should be drawn to the left of the menu.
    pub fn draw_plaque(&self) -> bool {
        self.draw_plaque
    }

    /// Returns the path to the menu title bitmap.
    pub fn title_path(&self) -> &str {
        &self.title_path
    }

    /// Returns a MenuBodyView which specifies how to render the menu body.
    pub fn body(&self) -> &MenuBodyView {
        &self.body
    }
}

pub struct Menu {
    items: Vec<NamedMenuItem>,
    state: Cell<MenuState>,
    view: MenuView,
}

impl Menu {
    /// Returns a reference to the active submenu of this menu and its parent.
    fn active_submenu_and_parent(&self) -> Result<(&Menu, Option<&Menu>), Error> {
        let mut m = self;
        let mut m_parent = None;

        while let MenuState::InSubMenu { index } = m.state.get() {
            match m.items[index].item {
                Item::Submenu(ref s) => {
                    m_parent = Some(m);
                    m = s;
                }
                _ => bail!("Menu state points to invalid submenu"),
            }
        }

        Ok((m, m_parent))
    }

    /// Return a reference to the active submenu of this menu
    pub fn active_submenu(&self) -> Result<&Menu, Error> {
        let (m, _) = self.active_submenu_and_parent()?;
        Ok(m)
    }

    /// Return a reference to the parent of the active submenu of this menu.
    ///
    /// If this is the root menu, returns None.
    fn active_submenu_parent(&self) -> Result<Option<&Menu>, Error> {
        let (_, m_parent) = self.active_submenu_and_parent()?;
        Ok(m_parent)
    }

    /// Select the next element of this Menu.
    pub fn next(&self) -> Result<(), Error> {
        let m = self.active_submenu()?;

        let s = m.state.get().clone();
        if let MenuState::Active { index } = s {
            m.state.replace(MenuState::Active {
                index: (index + 1) % m.items.len(),
            });
        } else {
            bail!("Selected menu is inactive (invariant violation)");
        }

        Ok(())
    }

    /// Select the previous element of this Menu.
    pub fn prev(&self) -> Result<(), Error> {
        let m = self.active_submenu()?;

        let s = m.state.get().clone();
        if let MenuState::Active { index } = s {
            m.state.replace(MenuState::Active {
                index: (index - 1) % m.items.len(),
            });
        } else {
            bail!("Selected menu is inactive (invariant violation)");
        }

        Ok(())
    }

    /// Return a reference to the currently selected menu item.
    pub fn selected(&self) -> Result<&Item, Error> {
        let m = self.active_submenu()?;

        if let MenuState::Active { index } = m.state.get() {
            return Ok(&m.items[index].item);
        } else {
            bail!("Active menu in invalid state (invariant violation)")
        }
    }

    /// Activate the currently selected menu item.
    ///
    /// If this item is a `Menu`, sets the active (sub)menu's state to
    /// `MenuState::InSubMenu` and the selected submenu's state to
    /// `MenuState::Active`.
    ///
    /// If this item is an `Action`, executes the function contained in the
    /// `Action`.
    ///
    /// Otherwise, this has no effect.
    pub fn activate(&self) -> Result<(), Error> {
        let m = self.active_submenu()?;

        if let MenuState::Active { index } = m.state.get() {
            match m.items[index].item {
                Item::Submenu(ref submenu) => {
                    m.state.replace(MenuState::InSubMenu { index });
                    submenu.state.replace(MenuState::Active { index: 0 });
                }

                Item::Action(ref action) => (action)(),

                _ => (),
            }
        }

        Ok(())
    }

    pub fn left(&self) -> Result<(), Error> {
        let m = self.active_submenu()?;

        if let MenuState::Active { index } = m.state.get() {
            match m.items[index].item {
                Item::Enum(ref e) => e.select_prev(),
                Item::Slider(ref slider) => slider.decrease(),
                Item::TextField(ref text) => text.cursor_left(),
                Item::Toggle(ref toggle) => toggle.set_false(),
                _ => (),
            }
        }

        Ok(())
    }

    pub fn right(&self) -> Result<(), Error> {
        let m = self.active_submenu()?;

        if let MenuState::Active { index } = m.state.get() {
            match m.items[index].item {
                Item::Enum(ref e) => e.select_next(),
                Item::Slider(ref slider) => slider.increase(),
                Item::TextField(ref text) => text.cursor_right(),
                Item::Toggle(ref toggle) => toggle.set_true(),
                _ => (),
            }
        }

        Ok(())
    }

    /// Return `true` if the root menu is active, `false` otherwise.
    pub fn at_root(&self) -> bool {
        match self.state.get() {
            MenuState::Active { .. } => true,
            _ => false,
        }
    }

    /// Deactivate the active menu and activate its parent
    pub fn back(&self) -> Result<(), Error> {
        if self.at_root() {
            bail!("Cannot back out of root menu!");
        }

        let (m, m_parent) = self.active_submenu_and_parent()?;
        m.state.replace(MenuState::Inactive);

        match m_parent {
            Some(mp) => {
                let s = mp.state.get().clone();
                match s {
                    MenuState::InSubMenu { index } => mp.state.replace(MenuState::Active { index }),
                    _ => unreachable!(),
                };
            }

            None => unreachable!(),
        }

        Ok(())
    }

    pub fn items(&self) -> &[NamedMenuItem] {
        &self.items
    }

    pub fn state(&self) -> MenuState {
        self.state.get()
    }

    pub fn view(&self) -> &MenuView {
        &self.view
    }
}

pub struct MenuBuilder {
    gfx_name: Option<String>,
    items: Vec<NamedMenuItem>,
}

impl MenuBuilder {
    pub fn new() -> MenuBuilder {
        MenuBuilder {
            gfx_name: None,
            items: Vec::new(),
        }
    }

    pub fn build(self, view: MenuView) -> Menu {
        // deactivate all child menus
        for item in self.items.iter() {
            if let Item::Submenu(ref m) = item.item {
                m.state.replace(MenuState::Inactive);
            }
        }

        Menu {
            items: self.items,
            state: Cell::new(MenuState::Active { index: 0 }),
            view,
        }
    }

    pub fn add_submenu<S>(mut self, name: S, submenu: Menu) -> MenuBuilder
    where
        S: AsRef<str>,
    {
        self.items
            .push(NamedMenuItem::new(name, Item::Submenu(submenu)));
        self
    }

    pub fn add_action<S>(mut self, name: S, action: Box<dyn Fn()>) -> MenuBuilder
    where
        S: AsRef<str>,
    {
        self.items
            .push(NamedMenuItem::new(name, Item::Action(action)));
        self
    }

    pub fn add_toggle<S>(mut self, name: S, init: bool, on_toggle: Box<dyn Fn(bool)>) -> MenuBuilder
    where
        S: AsRef<str>,
    {
        self.items.push(NamedMenuItem::new(
            name,
            Item::Toggle(Toggle::new(init, on_toggle)),
        ));
        self
    }

    pub fn add_enum<S, E>(mut self, name: S, items: E, init: usize) -> Result<MenuBuilder, Error>
    where
        S: AsRef<str>,
        E: Into<Vec<EnumItem>>,
    {
        self.items.push(NamedMenuItem::new(
            name,
            Item::Enum(Enum::new(init, items.into())?),
        ));
        Ok(self)
    }

    pub fn add_slider<S>(
        mut self,
        name: S,
        min: f32,
        max: f32,
        steps: usize,
        init: usize,
        on_select: Box<dyn Fn(f32)>,
    ) -> Result<MenuBuilder, Error>
    where
        S: AsRef<str>,
    {
        self.items.push(NamedMenuItem::new(
            name,
            Item::Slider(Slider::new(min, max, steps, init, on_select)?),
        ));
        Ok(self)
    }

    pub fn add_text_field<S>(
        mut self,
        name: S,
        default: Option<S>,
        max_len: Option<usize>,
        on_update: Box<dyn Fn(&str)>,
    ) -> Result<MenuBuilder, Error>
    where
        S: AsRef<str>,
    {
        self.items.push(NamedMenuItem::new(
            name,
            Item::TextField(TextField::new(default, max_len, on_update)?),
        ));
        Ok(self)
    }
}

pub struct NamedMenuItem {
    name: String,
    item: Item,
}

impl NamedMenuItem {
    fn new<S>(name: S, item: Item) -> NamedMenuItem
    where
        S: AsRef<str>,
    {
        NamedMenuItem {
            name: name.as_ref().to_string(),
            item,
        }
    }

    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn item(&self) -> &Item {
        &self.item
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::{cell::Cell, rc::Rc};

    fn view() -> MenuView {
        MenuView {
            draw_plaque: false,
            title_path: "path".to_string(),
            body: MenuBodyView::Dynamic,
        }
    }

    fn is_inactive(state: &MenuState) -> bool {
        match state {
            MenuState::Inactive => true,
            _ => false,
        }
    }

    fn is_active(state: &MenuState) -> bool {
        match state {
            MenuState::Active { .. } => true,
            _ => false,
        }
    }

    fn is_insubmenu(state: &MenuState) -> bool {
        match state {
            MenuState::InSubMenu { .. } => true,
            _ => false,
        }
    }

    #[test]
    fn test_menu_builder() {
        let action_target = Rc::new(Cell::new(false));
        let action_target_handle = action_target.clone();

        let _m = MenuBuilder::new()
            .add_action("action", Box::new(move || action_target_handle.set(true)))
            .build(view());

        // TODO
    }

    #[test]
    fn test_menu_active_submenu() {
        let menu = MenuBuilder::new()
            .add_submenu(
                "menu_1",
                MenuBuilder::new()
                    .add_action("action_1", Box::new(|| ()))
                    .build(view()),
            )
            .add_submenu(
                "menu_2",
                MenuBuilder::new()
                    .add_action("action_2", Box::new(|| ()))
                    .build(view()),
            )
            .build(view());

        let m = &menu;
        let m1 = match m.items[0].item {
            Item::Submenu(ref m1i) => m1i,
            _ => unreachable!(),
        };
        let m2 = match m.items[1].item {
            Item::Submenu(ref m2i) => m2i,
            _ => unreachable!(),
        };

        assert!(is_active(&m.state.get()));
        assert!(is_inactive(&m1.state.get()));
        assert!(is_inactive(&m2.state.get()));

        // enter m1
        m.activate().unwrap();
        assert!(is_insubmenu(&m.state.get()));
        assert!(is_active(&m1.state.get()));
        assert!(is_inactive(&m2.state.get()));

        // exit m1
        m.back().unwrap();
        assert!(is_active(&m.state.get()));
        assert!(is_inactive(&m1.state.get()));
        assert!(is_inactive(&m2.state.get()));

        // enter m2
        m.next().unwrap();
        m.activate().unwrap();
        assert!(is_insubmenu(&m.state.get()));
        assert!(is_inactive(&m1.state.get()));
        assert!(is_active(&m2.state.get()));
    }
}


================================================
FILE: src/client/mod.rs
================================================
// Copyright © 2020 Cormac O'Brien
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

mod cvars;
pub mod demo;
pub mod entity;
pub mod input;
pub mod menu;
pub mod render;
pub mod sound;
pub mod state;
pub mod trace;
pub mod view;

pub use self::cvars::register_cvars;

use std::{
    cell::RefCell,
    collections::{HashMap, VecDeque},
    io::BufReader,
    net::ToSocketAddrs,
    rc::Rc,
};

use crate::{
    client::{
        demo::{DemoServer, DemoServerError},
        entity::{ClientEntity, MAX_STATIC_ENTITIES},
        input::{game::GameInput, Input},
        sound::{MusicPlayer, StaticSound},
        state::{ClientState, PlayerInfo},
        trace::{TraceEntity, TraceFrame},
        view::{IdleVars, KickVars, MouseVars, RollVars},
    },
    common::{
        console::{CmdRegistry, Console, ConsoleError, CvarRegistry},
        engine,
        model::ModelError,
        net::{
            self,
            connect::{ConnectSocket, Request, Response, CONNECT_PROTOCOL_VERSION},
            BlockingMode, ClientCmd, ClientStat, ColorShift, EntityEffects, EntityState, GameType,
            NetError, PlayerColor, QSocket, ServerCmd, SignOnStage,
        },
        vfs::{Vfs, VfsError},
    },
};

use cgmath::Deg;
use chrono::Duration;
use input::InputFocus;
use menu::Menu;
use render::{ClientRenderer, GraphicsState, WorldRenderer};
use rodio::{OutputStream, OutputStreamHandle};
use sound::SoundError;
use thiserror::Error;
use view::BobVars;

// connections are tried 3 times, see
// https://github.com/id-Software/Quake/blob/master/WinQuake/net_dgrm.c#L1248
const MAX_CONNECT_ATTEMPTS: usize = 3;
const MAX_STATS: usize = 32;

const DEFAULT_SOUND_PACKET_VOLUME: u8 = 255;
const DEFAULT_SOUND_PACKET_ATTENUATION: f32 = 1.0;

const CONSOLE_DIVIDER: &'static str = "\
\n\n\
\x1D\x1E\x1E\x1E\x1E\x1E\x1E\x1E\
\x1E\x1E\x1E\x1E\x1E\x1E\x1E\x1E\
\x1E\x1E\x1E\x1E\x1E\x1E\x1E\x1E\
\x1E\x1E\x1E\x1E\x1E\x1E\x1E\x1F\
\n\n";

#[derive(Error, Debug)]
pub enum ClientError {
    #[error("Connection rejected: {0}")]
    ConnectionRejected(String),
    #[error("Couldn't read cvar value: {0}")]
    Cvar(ConsoleError),
    #[error("Server sent an invalid port number ({0})")]
    InvalidConnectPort(i32),
    #[error("Server sent an invalid connect response")]
    InvalidConnectResponse,
    #[error("Invalid server address")]
    InvalidServerAddress,
    #[error("No response from server")]
    NoResponse,
    #[error("Unrecognized protocol: {0}")]
    UnrecognizedProtocol(i32),
    #[error("Client is not connected")]
    NotConnected,
    #[error("Client has already signed on")]
    AlreadySignedOn,
    #[error("No client with ID {0}")]
    NoSuchClient(usize),
    #[error("No player with ID {0}")]
    NoSuchPlayer(usize),
    #[error("No entity with ID {0}")]
    NoSuchEntity(usize),
    #[error("Null entity access")]
    NullEntity,
    #[error("Entity already exists: {0}")]
    EntityExists(usize),
    #[error("Invalid view entity: {0}")]
    InvalidViewEntity(usize),
    #[error("Too many static entities")]
    TooManyStaticEntities,
    #[error("No such lightmap animation: {0}")]
    NoSuchLightmapAnimation(usize),
    // TODO: wrap PlayError
    #[error("Failed to open audio output stream")]
    OutputStream,
    #[error("Demo server error: {0}")]
    DemoServer(#[from] DemoServerError),
    #[error("Model error: {0}")]
    Model(#[from] ModelError),
    #[error("Network error: {0}")]
    Network(#[from] NetError),
    #[error("Failed to load sound: {0}")]
    Sound(#[from] SoundError),
    #[error("Virtual filesystem error: {0}")]
    Vfs(#[from] VfsError),
}

pub struct MoveVars {
    cl_anglespeedkey: f32,
    cl_pitchspeed: f32,
    cl_yawspeed: f32,
    cl_sidespeed: f32,
    cl_upspeed: f32,
    cl_forwardspeed: f32,
    cl_backspeed: f32,
    cl_movespeedkey: f32,
}

#[derive(Debug, FromPrimitive)]
enum ColorShiftCode {
    Contents = 0,
    Damage = 1,
    Bonus = 2,
    Powerup = 3,
}

struct ServerInfo {
    _max_clients: u8,
    _game_type: GameType,
}

#[derive(Clone, Debug)]
pub enum IntermissionKind {
    Intermission,
    Finale { text: String },
    Cutscene { text: String },
}

/// Indicates to the client what should be done with the current connection.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum ConnectionStatus {
    /// Maintain the connection.
    Maintain,

    /// Disconnect from the server or demo server.
    Disconnect,

    /// Play the next demo in the demo queue.
    NextDemo,
}

/// Indicates the state of an active connection.
enum ConnectionState {
    /// The client is in the sign-on process.
    SignOn(SignOnStage),

    /// The client is fully connected.
    Connected(WorldRenderer),
}

/// Possible targets that a client can be connected to.
enum ConnectionKind {
    /// A regular Quake server.
    Server {
        /// The [`QSocket`](crate::net::QSocket) used to communicate with the server.
        qsock: QSocket,

        /// The client's packet composition buffer.
        compose: Vec<u8>,
    },

    /// A demo server.
    Demo(DemoServer),
}

/// A connection to a game server of some kind.
///
/// The exact nature of the connected server is specified by [`ConnectionKind`].
pub struct Connection {
    state: ClientState,
    conn_state: ConnectionState,
    kind: ConnectionKind,
}

impl Connection {
    fn handle_signon(
        &mut self,
        new_stage: SignOnStage,
        gfx_state: &GraphicsState,
    ) -> Result<(), ClientError> {
        use SignOnStage::*;

        let new_conn_state = match self.conn_state {
            // TODO: validate stage transition
            ConnectionState::SignOn(ref mut _stage) => {
                if let ConnectionKind::Server {
                    ref mut compose, ..
                } = self.kind
                {
                    match new_stage {
                        Not => (), // TODO this is an error (invalid value)
                        Prespawn => {
                            ClientCmd::StringCmd {
                                cmd: String::from("prespawn"),
                            }
                            .serialize(compose)?;
                        }
                        ClientInfo => {
                            // TODO: fill in client info here
                            ClientCmd::StringCmd {
                                cmd: format!("name \"{}\"\n", "UNNAMED"),
                            }
                            .serialize(compose)?;
                            ClientCmd::StringCmd {
                                cmd: format!("color {} {}", 0, 0),
                            }
                            .serialize(compose)?;
                            // TODO: need default spawn parameters?
                            ClientCmd::StringCmd {
                                cmd: format!("spawn {}", ""),
                            }
                            .serialize(compose)?;
                        }
                        SignOnStage::Begin => {
                            ClientCmd::StringCmd {
                                cmd: String::from("begin"),
                            }
                            .serialize(compose)?;
                        }
                        SignOnStage::Done => {
                            debug!("SignOn complete");
                            // TODO: end load screen
                            self.state.start_time = self.state.time;
                        }
                    }
                }

                match new_stage {
                    // TODO proper error
                    Not => panic!("SignOnStage::Not in handle_signon"),
                    // still signing on, advance to the new stage
                    Prespawn | ClientInfo | Begin => ConnectionState::SignOn(new_stage),

                    // finished signing on, build world renderer
                    Done => ConnectionState::Connected(WorldRenderer::new(
                        gfx_state,
                        self.state.models(),
                        1,
                    )),
                }
            }

            // ignore spurious sign-on messages
            ConnectionState::Connected { .. } => return Ok(()),
        };

        self.conn_state = new_conn_state;

        Ok(())
    }

    fn parse_server_msg(
        &mut self,
        vfs: &Vfs,
        gfx_state: &GraphicsState,
        cmds: &mut CmdRegistry,
        console: &mut Console,
        music_player: &mut MusicPlayer,
        kick_vars: KickVars,
    ) -> Result<ConnectionStatus, ClientError> {
        use ConnectionStatus::*;

        let (msg, demo_view_angles, track_override) = match self.kind {
            ConnectionKind::Server { ref mut qsock, .. } => {
                let msg = qsock.recv_msg(match self.conn_state {
                    // if we're in the game, don't block waiting for messages
                    ConnectionState::Connected(_) => BlockingMode::NonBlocking,

                    // otherwise, give the server some time to respond
                    // TODO: might make sense to make this a future or something
                    ConnectionState::SignOn(_) => BlockingMode::Timeout(Duration::seconds(5)),
                })?;

                (msg, None, None)
            }

            ConnectionKind::Demo(ref mut demo_srv) => {
                // only get the next update once we've made it all the way to
                // the previous one
                if self.state.time >= self.state.msg_times[0] {
                    let msg_view = match demo_srv.next() {
                        Some(v) => v,
                        None => {
                            // if there are no commands left in the demo, play
                            // the next demo if there is one
                            return Ok(NextDemo);
                        }
                    };

                    let mut view_angles = msg_view.view_angles();
                    // invert entity angles to get the camera direction right.
                    // yaw is already inverted.
                    view_angles.z = -view_angles.z;

                    // TODO: we shouldn't have to copy the message here
                    (
                        msg_view.message().to_owned(),
                        Some(view_angles),
                        demo_srv.track_override(),
                    )
                } else {
                    (Vec::new(), None, demo_srv.track_override())
                }
            }
        };

        // no data available at this time
        if msg.is_empty() {
            return Ok(Maintain);
        }

        let mut reader = BufReader::new(msg.as_slice());

        while let Some(cmd) = ServerCmd::deserialize(&mut reader)? {
            match cmd {
                // TODO: have an error for this instead of panicking
                // once all other commands have placeholder handlers, just error
                // in the wildcard branch
                ServerCmd::Bad => panic!("Invalid command from server"),

                ServerCmd::NoOp => (),

                ServerCmd::CdTrack { track, .. } => {
                    music_player.play_track(match track_override {
                        Some(t) => t as usize,
                        None => track as usize,
                    })?;
                }

                ServerCmd::CenterPrint { text } => {
                    // TODO: print to center of screen
                    warn!("Center print not yet implemented!");
                    println!("{}", text);
                }

                ServerCmd::PlayerData(player_data) => self.state.update_player(player_data),

                ServerCmd::Cutscene { text } => {
                    self.state.intermission = Some(IntermissionKind::Cutscene { text });
                    self.state.completion_time = Some(self.state.time);
                }

                ServerCmd::Damage {
                    armor,
                    blood,
                    source,
                } => self.state.handle_damage(armor, blood, source, kick_vars),

                ServerCmd::Disconnect => {
                    return Ok(match self.kind {
                        ConnectionKind::Demo(_) => NextDemo,
                        ConnectionKind::Server { .. } => Disconnect,
                    })
                }

                ServerCmd::FastUpdate(ent_update) => {
                    // first update signals the last sign-on stage
                    self.handle_signon(SignOnStage::Done, gfx_state)?;

                    let ent_id = ent_update.ent_id as usize;
                    self.state.update_entity(ent_id, ent_update)?;

                    // patch view angles in demos
                    if let Some(angles) = demo_view_angles {
                        if ent_id == self.state.view_entity_id() {
                            self.state.update_view_angles(angles);
                        }
                    }
                }

                ServerCmd::Finale { text } => {
                    self.state.intermission = Some(IntermissionKind::Finale { text });
                    self.state.completion_time = Some(self.state.time);
                }

                ServerCmd::FoundSecret => self.state.stats[ClientStat::FoundSecrets as usize] += 1,
                ServerCmd::Intermission => {
                    self.state.intermission = Some(IntermissionKind::Intermission);
                    self.state.completion_time = Some(self.state.time);
                }
                ServerCmd::KilledMonster => {
                    self.state.stats[ClientStat::KilledMonsters as usize] += 1
                }

                ServerCmd::LightStyle { id, value } => {
                    trace!("Inserting light style {} with value {}", id, &value);
                    let _ = self.state.light_styles.insert(id, value);
                }

                ServerCmd::Particle {
                    origin,
                    direction,
                    count,
                    color,
                } => {
                    match count {
                        // if count is 255, this is an explosion
                        255 => self
                            .state
                            .particles
                            .create_explosion(self.state.time, origin),

                        // otherwise it's an impact
                        _ => self.state.particles.create_projectile_impact(
                            self.state.time,
                            origin,
                            direction,
                            color,
                            count as usize,
                        ),
                    }
                }

                ServerCmd::Print { text } => console.print_alert(&text),

                ServerCmd::ServerInfo {
                    protocol_version,
                    max_clients,
                    game_type,
                    message,
                    model_precache,
                    sound_precache,
                } => {
                    // check protocol version
                    if protocol_version != net::PROTOCOL_VERSION as i32 {
                        Err(ClientError::UnrecognizedProtocol(protocol_version))?;
                    }

                    console.println(CONSOLE_DIVIDER);
                    console.println(message);
                    console.println(CONSOLE_DIVIDER);

                    let _server_info = ServerInfo {
                        _max_clients: max_clients,
                        _game_type: game_type,
                    };

                    self.state = ClientState::from_server_info(
                        vfs,
                        self.state.mixer.stream(),
                        max_clients,
                        model_precache,
                        sound_precache,
                    )?;

                    let bonus_cshift =
                        self.state.color_shifts[ColorShiftCode::Bonus as usize].clone();
                    cmds.insert_or_replace(
                        "bf",
                        Box::new(move |_| {
                            bonus_cshift.replace(ColorShift {
                                dest_color: [215, 186, 69],
                                percent: 50,
                            });
                            String::new()
                        }),
                    )
                    .unwrap();
                }

                ServerCmd::SetAngle { angles } => self.state.set_view_angles(angles),

                ServerCmd::SetView { ent_id } => {
                    if ent_id <= 0 {
                        Err(ClientError::InvalidViewEntity(ent_id as usize))?;
                    }

                    self.state.set_view_entity(ent_id as usize)?;
                }

                ServerCmd::SignOnStage { stage } => self.handle_signon(stage, gfx_state)?,

                ServerCmd::Sound {
                    volume,
                    attenuation,
                    entity_id,
                    channel,
                    sound_id,
                    position,
                } => {
                    trace!(
                        "starting sound with id {} on entity {} channel {}",
                        sound_id,
                        entity_id,
                        channel
                    );

                    if entity_id as usize >= self.state.entities.len() {
                        warn!(
                            "server tried to start sound on nonexistent entity {}",
                            entity_id
                        );
                        break;
                    }

                    let volume = volume.unwrap_or(DEFAULT_SOUND_PACKET_VOLUME);
                    let attenuation = attenuation.unwrap_or(DEFAULT_SOUND_PACKET_ATTENUATION);
                    // TODO: apply volume, attenuation, spatialization
                    self.state.mixer.start_sound(
                        self.state.sounds[sound_id as usize].clone(),
                        self.state.msg_times[0],
                        Some(entity_id as usize),
                        channel,
                        volume as f32 / 255.0,
                        attenuation,
                        position,
                        &self.state.listener,
                    );
                }

                ServerCmd::SpawnBaseline {
                    ent_id,
                    model_id,
                    frame_id,
                    colormap,
                    skin_id,
                    origin,
                    angles,
                } => {
                    self.state.spawn_entities(
                        ent_id as usize,
                        EntityState {
                            model_id: model_id as usize,
                            frame_id: frame_id as usize,
                            colormap,
                            skin_id: skin_id as usize,
                            origin,
                            angles,
                            effects: EntityEffects::empty(),
                        },
                    )?;
                }

                ServerCmd::SpawnStatic {
                    model_id,
                    frame_id,
                    colormap,
                    skin_id,
                    origin,
                    angles,
                } => {
                    if self.state.static_entities.len() >= MAX_STATIC_ENTITIES {
                        Err(ClientError::TooManyStati
Download .txt
gitextract_j_etf39x/

├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .rustfmt.toml
├── Cargo.toml
├── LICENSE.txt
├── README.md
├── shaders/
│   ├── alias.frag
│   ├── alias.vert
│   ├── blit.frag
│   ├── blit.vert
│   ├── brush.frag
│   ├── brush.vert
│   ├── deferred.frag
│   ├── deferred.vert
│   ├── glyph.frag
│   ├── glyph.vert
│   ├── particle.frag
│   ├── particle.vert
│   ├── postprocess.frag
│   ├── postprocess.vert
│   ├── quad.frag
│   ├── quad.vert
│   ├── sprite.frag
│   └── sprite.vert
├── site/
│   ├── config.toml
│   ├── content/
│   │   ├── _index.md
│   │   ├── blog/
│   │   │   ├── 2018-04-24.md
│   │   │   ├── 2018-04-26/
│   │   │   │   └── index.md
│   │   │   ├── 2018-05-12/
│   │   │   │   └── index.md
│   │   │   ├── 2018-07-20/
│   │   │   │   └── index.md
│   │   │   └── _index.md
│   │   └── index.html
│   ├── sass/
│   │   ├── _base.scss
│   │   ├── _reset.scss
│   │   ├── blog-post.scss
│   │   ├── blog.scss
│   │   └── style.scss
│   ├── templates/
│   │   └── home.html
│   └── themes/
│       └── richter/
│           ├── templates/
│           │   ├── base.html
│           │   ├── blog-post.html
│           │   ├── blog.html
│           │   └── index.html
│           └── theme.toml
├── specifications.md
└── src/
    ├── bin/
    │   ├── quake-client/
    │   │   ├── capture.rs
    │   │   ├── game.rs
    │   │   ├── main.rs
    │   │   ├── menu.rs
    │   │   └── trace.rs
    │   └── unpak.rs
    ├── client/
    │   ├── cvars.rs
    │   ├── demo.rs
    │   ├── entity/
    │   │   ├── mod.rs
    │   │   └── particle.rs
    │   ├── input/
    │   │   ├── console.rs
    │   │   ├── game.rs
    │   │   ├── menu.rs
    │   │   └── mod.rs
    │   ├── menu/
    │   │   ├── item.rs
    │   │   └── mod.rs
    │   ├── mod.rs
    │   ├── render/
    │   │   ├── atlas.rs
    │   │   ├── blit.rs
    │   │   ├── cvars.rs
    │   │   ├── error.rs
    │   │   ├── mod.rs
    │   │   ├── palette.rs
    │   │   ├── pipeline.rs
    │   │   ├── target.rs
    │   │   ├── ui/
    │   │   │   ├── console.rs
    │   │   │   ├── glyph.rs
    │   │   │   ├── hud.rs
    │   │   │   ├── layout.rs
    │   │   │   ├── menu.rs
    │   │   │   ├── mod.rs
    │   │   │   └── quad.rs
    │   │   ├── uniform.rs
    │   │   ├── warp.rs
    │   │   └── world/
    │   │       ├── alias.rs
    │   │       ├── brush.rs
    │   │       ├── deferred.rs
    │   │       ├── mod.rs
    │   │       ├── particle.rs
    │   │       ├── postprocess.rs
    │   │       └── sprite.rs
    │   ├── sound/
    │   │   ├── mod.rs
    │   │   └── music.rs
    │   ├── state.rs
    │   ├── trace.rs
    │   └── view.rs
    ├── common/
    │   ├── alloc.rs
    │   ├── bitset.rs
    │   ├── bsp/
    │   │   ├── load.rs
    │   │   └── mod.rs
    │   ├── console/
    │   │   └── mod.rs
    │   ├── engine.rs
    │   ├── host.rs
    │   ├── math.rs
    │   ├── mdl.rs
    │   ├── mod.rs
    │   ├── model.rs
    │   ├── net/
    │   │   ├── connect.rs
    │   │   └── mod.rs
    │   ├── pak.rs
    │   ├── parse/
    │   │   ├── console.rs
    │   │   ├── map.rs
    │   │   └── mod.rs
    │   ├── sprite.rs
    │   ├── util.rs
    │   ├── vfs.rs
    │   └── wad.rs
    ├── lib.rs
    └── server/
        ├── mod.rs
        ├── precache.rs
        ├── progs/
        │   ├── functions.rs
        │   ├── globals.rs
        │   ├── mod.rs
        │   ├── ops.rs
        │   └── string_table.rs
        └── world/
            ├── entity.rs
            ├── mod.rs
            └── phys.rs
Download .txt
SYMBOL INDEX (1986 symbols across 77 files)

FILE: src/bin/quake-client/capture.rs
  constant BYTES_PER_PIXEL (line 14) | const BYTES_PER_PIXEL: u32 = 4;
  function cmd_screenshot (line 20) | pub fn cmd_screenshot(
  type Capture (line 39) | pub struct Capture {
    method new (line 51) | pub fn new(device: &wgpu::Device, capture_size: Extent2d) -> Capture {
    method copy_from_texture (line 70) | pub fn copy_from_texture(
    method write_to_file (line 89) | pub fn write_to_file<P>(&self, device: &wgpu::Device, path: P)

FILE: src/bin/quake-client/game.rs
  type Game (line 45) | pub struct Game {
    method new (line 59) | pub fn new(
    method frame (line 94) | pub fn frame(&mut self, gfx_state: &GraphicsState, frame_duration: Dur...
    method render (line 138) | pub fn render(
    method drop (line 203) | fn drop(&mut self) {

FILE: src/bin/quake-client/main.rs
  type ClientProgram (line 63) | struct ClientProgram {
    method new (line 83) | pub async fn new(window: Window, base_dir: Option<PathBuf>, trace: boo...
    method recreate_swap_chain (line 222) | fn recreate_swap_chain(&self, present_mode: wgpu::PresentMode) {
    method render (line 237) | fn render(&mut self) {
  method handle_event (line 252) | fn handle_event<T>(
  method frame (line 270) | fn frame(&mut self, frame_duration: Duration) {
  method shutdown (line 318) | fn shutdown(&mut self) {
  method cvars (line 322) | fn cvars(&self) -> Ref<CvarRegistry> {
  method cvars_mut (line 326) | fn cvars_mut(&self) -> RefMut<CvarRegistry> {
  type Opt (line 332) | struct Opt {
  function main (line 349) | fn main() {

FILE: src/bin/quake-client/menu.rs
  function build_main_menu (line 25) | pub fn build_main_menu() -> Result<Menu, Error> {
  function build_menu_sp (line 41) | fn build_menu_sp() -> Result<Menu, Error> {
  function build_menu_mp (line 55) | fn build_menu_mp() -> Result<Menu, Error> {
  function build_menu_mp_join (line 69) | fn build_menu_mp_join() -> Result<Menu, Error> {
  function build_menu_mp_join_tcp (line 82) | fn build_menu_mp_join_tcp() -> Result<Menu, Error> {
  function build_menu_options (line 103) | fn build_menu_options() -> Result<Menu, Error> {

FILE: src/bin/quake-client/trace.rs
  constant DEFAULT_TRACE_PATH (line 5) | const DEFAULT_TRACE_PATH: &'static str = "richter-trace.json";
  function cmd_trace_begin (line 8) | pub fn cmd_trace_begin(trace: Rc<RefCell<Option<Vec<TraceFrame>>>>) -> B...
  function cmd_trace_end (line 22) | pub fn cmd_trace_end(

FILE: src/bin/unpak.rs
  type Opt (line 33) | struct Opt {
  constant VERSION (line 47) | const VERSION: &'static str = "
  function main (line 53) | fn main() {

FILE: src/client/cvars.rs
  function register_cvars (line 23) | pub fn register_cvars(cvars: &CvarRegistry) -> Result<(), ConsoleError> {

FILE: src/client/demo.rs
  type DemoServerError (line 17) | pub enum DemoServerError {
  type DemoMessage (line 30) | struct DemoMessage {
  type DemoMessageView (line 36) | pub struct DemoMessageView<'a> {
  function view_angles (line 43) | pub fn view_angles(&self) -> Vector3<Deg<f32>> {
  function message (line 48) | pub fn message(&self) -> &[u8] {
  type DemoServer (line 54) | pub struct DemoServer {
    method new (line 68) | pub fn new(file: &mut VirtualFile) -> Result<DemoServer, DemoServerErr...
    method next (line 144) | pub fn next(&mut self) -> Option<DemoMessageView> {
    method track_override (line 163) | pub fn track_override(&self) -> Option<u32> {

FILE: src/client/entity/mod.rs
  constant MAX_LIGHTS (line 33) | pub const MAX_LIGHTS: usize = 32;
  constant MAX_BEAMS (line 34) | pub const MAX_BEAMS: usize = 24;
  constant MAX_TEMP_ENTITIES (line 35) | pub const MAX_TEMP_ENTITIES: usize = 64;
  constant MAX_STATIC_ENTITIES (line 36) | pub const MAX_STATIC_ENTITIES: usize = 128;
  type ClientEntity (line 39) | pub struct ClientEntity {
    method from_baseline (line 59) | pub fn from_baseline(baseline: EntityState) -> ClientEntity {
    method uninitialized (line 82) | pub fn uninitialized() -> ClientEntity {
    method update (line 109) | pub fn update(&mut self, msg_times: [Duration; 2], update: EntityUpdat...
    method update_angles (line 149) | pub fn update_angles(&mut self, angles: Vector3<Deg<f32>>) {
    method set_angles (line 158) | pub fn set_angles(&mut self, angles: Vector3<Deg<f32>>) {
    method msg_time (line 165) | pub fn msg_time(&self) -> Duration {
    method model_changed (line 170) | pub fn model_changed(&self) -> bool {
    method colormap (line 174) | pub fn colormap(&self) -> Option<u8> {
    method get_origin (line 178) | pub fn get_origin(&self) -> Vector3<f32> {
    method get_angles (line 182) | pub fn get_angles(&self) -> Vector3<Deg<f32>> {
    method model_id (line 186) | pub fn model_id(&self) -> usize {
    method frame_id (line 190) | pub fn frame_id(&self) -> usize {
    method skin_id (line 194) | pub fn skin_id(&self) -> usize {
  type LightDesc (line 201) | pub struct LightDesc {
  type Light (line 220) | pub struct Light {
    method from_desc (line 231) | pub fn from_desc(time: Duration, desc: LightDesc) -> Light {
    method origin (line 243) | pub fn origin(&self) -> Vector3<f32> {
    method radius (line 250) | pub fn radius(&self, time: Duration) -> f32 {
    method retain (line 265) | pub fn retain(&mut self, time: Duration) -> bool {
  type Lights (line 271) | pub struct Lights {
    method with_capacity (line 277) | pub fn with_capacity(capacity: usize) -> Lights {
    method get (line 285) | pub fn get(&self, key: usize) -> Option<&Light> {
    method get_mut (line 291) | pub fn get_mut(&mut self, key: usize) -> Option<&mut Light> {
    method insert (line 301) | pub fn insert(&mut self, time: Duration, desc: LightDesc, key: Option<...
    method iter (line 313) | pub fn iter(&self) -> impl Iterator<Item = &Light> {
    method update (line 320) | pub fn update(&mut self, time: Duration) {
  type Beam (line 326) | pub struct Beam {

FILE: src/client/entity/particle.rs
  constant MIN_PARTICLES (line 58) | pub const MIN_PARTICLES: usize = 512;
  constant MAX_PARTICLES (line 61) | pub const MAX_PARTICLES: usize = 16384;
  type ColorRamp (line 68) | pub struct ColorRamp {
    method color (line 80) | pub fn color(&self, elapsed: Duration, frame_skip: usize) -> Option<u8> {
  type ParticleKind (line 91) | pub enum ParticleKind {
  constant PARTICLE_GRAVITY_FACTOR (line 126) | pub const PARTICLE_GRAVITY_FACTOR: f32 = 0.05;
  type Particle (line 130) | pub struct Particle {
    method update (line 147) | pub fn update(&mut self, time: Duration, frame_time: Duration, sv_grav...
    method origin (line 202) | pub fn origin(&self) -> Vector3<f32> {
    method color (line 206) | pub fn color(&self) -> u8 {
  type TrailKind (line 211) | pub enum TrailKind {
  type Particles (line 225) | pub struct Particles {
    method with_capacity (line 240) | pub fn with_capacity(capacity: usize) -> Particles {
    method insert (line 266) | pub fn insert(&mut self, particle: Particle) -> bool {
    method clear (line 278) | pub fn clear(&mut self) {
    method iter (line 282) | pub fn iter(&self) -> impl Iterator<Item = &Particle> {
    method update (line 291) | pub fn update(&mut self, time: Duration, frame_time: Duration, sv_grav...
    method scatter (line 296) | fn scatter(&mut self, origin: Vector3<f32>, scatter_distr: &Uniform<f3...
    method random_vector3 (line 305) | fn random_vector3(&mut self, velocity_distr: &Uniform<f32>) -> Vector3...
    method create_entity_field (line 314) | pub fn create_entity_field(&mut self, time: Duration, entity: &ClientE...
    method create_random_cloud (line 356) | pub fn create_random_cloud(
    method create_explosion (line 388) | pub fn create_explosion(&mut self, time: Duration, origin: Vector3<f32...
    method create_color_explosion (line 410) | pub fn create_color_explosion(
    method create_spawn_explosion (line 431) | pub fn create_spawn_explosion(&mut self, time: Duration, origin: Vecto...
    method create_projectile_impact (line 468) | pub fn create_projectile_impact(
    method create_lava_splash (line 507) | pub fn create_lava_splash(&mut self, time: Duration, origin: Vector3<f...
    method create_teleporter_warp (line 552) | pub fn create_teleporter_warp(&mut self, time: Duration, origin: Vecto...
    method create_trail (line 591) | pub fn create_trail(
  function particles_eq (line 680) | fn particles_eq(p1: &Particle, p2: &Particle) -> bool {
  function test_particle_list_update (line 685) | fn test_particle_list_update() {

FILE: src/client/input/console.rs
  type ConsoleInput (line 25) | pub struct ConsoleInput {
    method new (line 30) | pub fn new(console: Rc<RefCell<Console>>) -> ConsoleInput {
    method handle_event (line 34) | pub fn handle_event<T>(&self, event: Event<T>) -> Result<(), Error> {

FILE: src/client/input/game.rs
  constant ACTION_COUNT (line 42) | const ACTION_COUNT: usize = 19;
  type Action (line 210) | pub enum Action {
  type Err (line 270) | type Err = Error;
  method from_str (line 272) | fn from_str(s: &str) -> Result<Self, Self::Err> {
  method to_string (line 301) | fn to_string(&self) -> String {
  type MouseWheel (line 330) | pub enum MouseWheel {
    method from (line 337) | fn from(src: MouseScrollDelta) -> MouseWheel {
  type BindInput (line 360) | pub enum BindInput {
    method from (line 372) | fn from(src: Key) -> BindInput {
    method from (line 378) | fn from(src: MouseButton) -> BindInput {
    method from (line 384) | fn from(src: MouseWheel) -> BindInput {
    method from (line 390) | fn from(src: MouseScrollDelta) -> BindInput {
  type Err (line 396) | type Err = Error;
  method from_str (line 398) | fn from_str(src: &str) -> Result<BindInput, Error> {
  method to_string (line 412) | fn to_string(&self) -> String {
  type BindTarget (line 426) | pub enum BindTarget {
  type Err (line 440) | type Err = Error;
  method from_str (line 442) | fn from_str(s: &str) -> Result<Self, Self::Err> {
  method to_string (line 461) | fn to_string(&self) -> String {
  type GameInput (line 478) | pub struct GameInput {
    method new (line 487) | pub fn new(console: Rc<RefCell<Console>>) -> GameInput {
    method mouse_delta (line 497) | pub fn mouse_delta(&self) -> (f64, f64) {
    method impulse (line 501) | pub fn impulse(&self) -> u8 {
    method bind_defaults (line 506) | pub fn bind_defaults(&mut self) {
    method bind (line 531) | pub fn bind<I, T>(&mut self, input: I, target: T) -> Option<BindTarget>
    method binding (line 542) | pub fn binding<I>(&self, input: I) -> Option<BindTarget>
    method handle_event (line 549) | pub fn handle_event<T>(&mut self, outer_event: Event<T>) {
    method handle_input (line 583) | pub fn handle_input<I>(&mut self, input: I, state: ElementState)
    method action_state (line 610) | pub fn action_state(&self, action: Action) -> bool {
    method register_cmds (line 615) | pub fn register_cmds(&self, cmds: &mut CmdRegistry) {
    method refresh (line 708) | pub fn refresh(&mut self) {
    method clear_mouse (line 713) | fn clear_mouse(&mut self) {
    method clear_impulse (line 719) | fn clear_impulse(&mut self) {
  function test_action_to_string (line 729) | fn test_action_to_string() {
  function test_bind_target_action_to_string (line 735) | fn test_bind_target_action_to_string() {

FILE: src/client/input/menu.rs
  type MenuInput (line 25) | pub struct MenuInput {
    method new (line 31) | pub fn new(menu: Rc<RefCell<Menu>>, console: Rc<RefCell<Console>>) -> ...
    method handle_event (line 35) | pub fn handle_event<T>(&self, event: Event<T>) -> Result<(), Error> {

FILE: src/client/input/mod.rs
  type InputFocus (line 39) | pub enum InputFocus {
  type Input (line 45) | pub struct Input {
    method new (line 55) | pub fn new(
    method handle_event (line 70) | pub fn handle_event<T>(&mut self, event: Event<T>) -> Result<(), Error> {
    method focus (line 92) | pub fn focus(&self) -> InputFocus {
    method set_focus (line 96) | pub fn set_focus(&mut self, new_focus: InputFocus) {
    method bind (line 101) | pub fn bind<I, T>(&mut self, input: I, target: T) -> Option<BindTarget>
    method bind_defaults (line 109) | pub fn bind_defaults(&mut self) {
    method game_input (line 113) | pub fn game_input(&self) -> Option<&GameInput> {
    method game_input_mut (line 121) | pub fn game_input_mut(&mut self) -> Option<&mut GameInput> {
    method register_cmds (line 129) | pub fn register_cmds(&self, cmds: &mut CmdRegistry) {

FILE: src/client/menu/item.rs
  type Item (line 27) | pub enum Item {
  type Toggle (line 36) | pub struct Toggle {
    method new (line 42) | pub fn new(init: bool, on_toggle: Box<dyn Fn(bool)>) -> Toggle {
    method set_false (line 54) | pub fn set_false(&self) {
    method set_true (line 59) | pub fn set_true(&self) {
    method toggle (line 64) | pub fn toggle(&self) {
    method get (line 69) | pub fn get(&self) -> bool {
  type Enum (line 76) | pub struct Enum {
    method new (line 82) | pub fn new(init: usize, items: Vec<EnumItem>) -> Result<Enum, Error> {
    method selected_name (line 97) | pub fn selected_name(&self) -> &str {
    method select_next (line 101) | pub fn select_next(&self) {
    method select_prev (line 111) | pub fn select_prev(&self) {
  type EnumItem (line 122) | pub struct EnumItem {
    method new (line 128) | pub fn new<S>(name: S, on_select: Box<dyn Fn()>) -> Result<EnumItem, E...
  type Slider (line 139) | pub struct Slider {
    method new (line 150) | pub fn new(
    method increase (line 174) | pub fn increase(&self) {
    method decrease (line 184) | pub fn decrease(&self) {
    method position (line 194) | pub fn position(&self) -> f32 {
  type TextField (line 199) | pub struct TextField {
    method new (line 207) | pub fn new<S>(
    method is_empty (line 230) | pub fn is_empty(&self) -> bool {
    method text (line 234) | pub fn text(&self) -> String {
    method len (line 238) | pub fn len(&self) -> usize {
    method set_cursor (line 242) | pub fn set_cursor(&self, cursor: usize) -> Result<(), Error> {
    method home (line 250) | pub fn home(&self) {
    method end (line 254) | pub fn end(&self) {
    method cursor_right (line 258) | pub fn cursor_right(&self) {
    method cursor_left (line 265) | pub fn cursor_left(&self) {
    method insert (line 272) | pub fn insert(&self, c: char) {
    method backspace (line 283) | pub fn backspace(&self) {
    method delete (line 290) | pub fn delete(&self) {
  function test_toggle (line 304) | fn test_toggle() {
  function test_enum (line 320) | fn test_enum() {
  function test_slider (line 355) | fn test_slider() {
  function test_textfield (line 385) | fn test_textfield() {

FILE: src/client/menu/mod.rs
  type MenuState (line 30) | pub enum MenuState {
  type MenuBodyView (line 42) | pub enum MenuBodyView {
  type MenuView (line 52) | pub struct MenuView {
    method draw_plaque (line 60) | pub fn draw_plaque(&self) -> bool {
    method title_path (line 65) | pub fn title_path(&self) -> &str {
    method body (line 70) | pub fn body(&self) -> &MenuBodyView {
  type Menu (line 75) | pub struct Menu {
    method active_submenu_and_parent (line 83) | fn active_submenu_and_parent(&self) -> Result<(&Menu, Option<&Menu>), ...
    method active_submenu (line 101) | pub fn active_submenu(&self) -> Result<&Menu, Error> {
    method active_submenu_parent (line 109) | fn active_submenu_parent(&self) -> Result<Option<&Menu>, Error> {
    method next (line 115) | pub fn next(&self) -> Result<(), Error> {
    method prev (line 131) | pub fn prev(&self) -> Result<(), Error> {
    method selected (line 147) | pub fn selected(&self) -> Result<&Item, Error> {
    method activate (line 167) | pub fn activate(&self) -> Result<(), Error> {
    method left (line 186) | pub fn left(&self) -> Result<(), Error> {
    method right (line 202) | pub fn right(&self) -> Result<(), Error> {
    method at_root (line 219) | pub fn at_root(&self) -> bool {
    method back (line 227) | pub fn back(&self) -> Result<(), Error> {
    method items (line 250) | pub fn items(&self) -> &[NamedMenuItem] {
    method state (line 254) | pub fn state(&self) -> MenuState {
    method view (line 258) | pub fn view(&self) -> &MenuView {
  type MenuBuilder (line 263) | pub struct MenuBuilder {
    method new (line 269) | pub fn new() -> MenuBuilder {
    method build (line 276) | pub fn build(self, view: MenuView) -> Menu {
    method add_submenu (line 291) | pub fn add_submenu<S>(mut self, name: S, submenu: Menu) -> MenuBuilder
    method add_action (line 300) | pub fn add_action<S>(mut self, name: S, action: Box<dyn Fn()>) -> Menu...
    method add_toggle (line 309) | pub fn add_toggle<S>(mut self, name: S, init: bool, on_toggle: Box<dyn...
    method add_enum (line 320) | pub fn add_enum<S, E>(mut self, name: S, items: E, init: usize) -> Res...
    method add_slider (line 332) | pub fn add_slider<S>(
    method add_text_field (line 351) | pub fn add_text_field<S>(
  type NamedMenuItem (line 369) | pub struct NamedMenuItem {
    method new (line 375) | fn new<S>(name: S, item: Item) -> NamedMenuItem
    method name (line 385) | pub fn name(&self) -> &str {
    method item (line 389) | pub fn item(&self) -> &Item {
  function view (line 399) | fn view() -> MenuView {
  function is_inactive (line 407) | fn is_inactive(state: &MenuState) -> bool {
  function is_active (line 414) | fn is_active(state: &MenuState) -> bool {
  function is_insubmenu (line 421) | fn is_insubmenu(state: &MenuState) -> bool {
  function test_menu_builder (line 429) | fn test_menu_builder() {
  function test_menu_active_submenu (line 441) | fn test_menu_active_submenu() {

FILE: src/client/mod.rs
  constant MAX_CONNECT_ATTEMPTS (line 78) | const MAX_CONNECT_ATTEMPTS: usize = 3;
  constant MAX_STATS (line 79) | const MAX_STATS: usize = 32;
  constant DEFAULT_SOUND_PACKET_VOLUME (line 81) | const DEFAULT_SOUND_PACKET_VOLUME: u8 = 255;
  constant DEFAULT_SOUND_PACKET_ATTENUATION (line 82) | const DEFAULT_SOUND_PACKET_ATTENUATION: f32 = 1.0;
  constant CONSOLE_DIVIDER (line 84) | const CONSOLE_DIVIDER: &'static str = "\
  type ClientError (line 93) | pub enum ClientError {
  type MoveVars (line 143) | pub struct MoveVars {
  type ColorShiftCode (line 155) | enum ColorShiftCode {
  type ServerInfo (line 162) | struct ServerInfo {
  type IntermissionKind (line 168) | pub enum IntermissionKind {
  type ConnectionStatus (line 176) | enum ConnectionStatus {
  type ConnectionState (line 188) | enum ConnectionState {
  type ConnectionKind (line 197) | enum ConnectionKind {
  type Connection (line 214) | pub struct Connection {
    method handle_signon (line 221) | fn handle_signon(
    method parse_server_msg (line 297) | fn parse_server_msg(
    method frame (line 735) | fn frame(
  type Client (line 809) | pub struct Client {
    method new (line 824) | pub fn new(
    method disconnect (line 917) | pub fn disconnect(&mut self) {
    method frame (line 922) | pub fn frame(
    method render (line 1009) | pub fn render(
    method cvar_value (line 1038) | pub fn cvar_value<S>(&self, name: S) -> Result<f32, ClientError>
    method handle_input (line 1048) | pub fn handle_input(
    method move_vars (line 1078) | fn move_vars(&self) -> Result<MoveVars, ClientError> {
    method idle_vars (line 1091) | fn idle_vars(&self) -> Result<IdleVars, ClientError> {
    method kick_vars (line 1103) | fn kick_vars(&self) -> Result<KickVars, ClientError> {
    method mouse_vars (line 1111) | fn mouse_vars(&self) -> Result<MouseVars, ClientError> {
    method roll_vars (line 1119) | fn roll_vars(&self) -> Result<RollVars, ClientError> {
    method bob_vars (line 1126) | fn bob_vars(&self) -> Result<BobVars, ClientError> {
    method view_entity_id (line 1134) | pub fn view_entity_id(&self) -> Option<usize> {
    method trace (line 1141) | pub fn trace<'a, I>(&self, entity_ids: I) -> Result<TraceFrame, Client...
    method drop (line 1193) | fn drop(&mut self) {
  function cmd_toggleconsole (line 1200) | fn cmd_toggleconsole(
  function cmd_togglemenu (line 1223) | fn cmd_togglemenu(
  function connect (line 1245) | fn connect<A>(server_addrs: A, stream: OutputStreamHandle) -> Result<Con...
  function cmd_connect (line 1334) | fn cmd_connect(
  function cmd_reconnect (line 1356) | fn cmd_reconnect(
  function cmd_disconnect (line 1374) | fn cmd_disconnect(
  function cmd_playdemo (line 1390) | fn cmd_playdemo(
  function cmd_startdemos (line 1422) | fn cmd_startdemos(
  function cmd_music (line 1463) | fn cmd_music(music_player: Rc<RefCell<MusicPlayer>>) -> Box<dyn Fn(&[&st...
  function cmd_music_stop (line 1480) | fn cmd_music_stop(music_player: Rc<RefCell<MusicPlayer>>) -> Box<dyn Fn(...
  function cmd_music_pause (line 1487) | fn cmd_music_pause(music_player: Rc<RefCell<MusicPlayer>>) -> Box<dyn Fn...
  function cmd_music_resume (line 1494) | fn cmd_music_resume(music_player: Rc<RefCell<MusicPlayer>>) -> Box<dyn F...

FILE: src/client/render/atlas.rs
  constant DEFAULT_ATLAS_DIM (line 7) | const DEFAULT_ATLAS_DIM: u32 = 1024;
  type Rect (line 9) | struct Rect {
  function area_order (line 16) | fn area_order(t1: &TextureData, t2: &TextureData) -> Ordering {
  type TextureData (line 21) | pub struct TextureData {
    method empty (line 28) | fn empty(width: u32, height: u32) -> TextureData {
    method subtexture (line 40) | fn subtexture(&mut self, other: &TextureData, xy: [u32; 2]) -> Result<...
  type TextureAtlasBuilder (line 56) | pub struct TextureAtlasBuilder {
    method new (line 61) | pub fn new() -> TextureAtlasBuilder {
    method add (line 67) | pub fn add(&mut self, texture: TextureData) -> Result<usize, Error> {
    method build (line 82) | pub fn build(
  type TextureAtlasSubtexture (line 306) | struct TextureAtlasSubtexture {
    method convert_texcoords (line 319) | fn convert_texcoords(&self, st: [f32; 2]) -> [f32; 2] {
  type TextureAtlas (line 327) | pub struct TextureAtlas {
    method convert_texcoords (line 338) | pub fn convert_texcoords(&self, id: usize, st: [f32; 2]) -> [f32; 2] {
  function test_texture_data_subtexture (line 348) | fn test_texture_data_subtexture() {

FILE: src/client/render/blit.rs
  type BlitPipeline (line 3) | pub struct BlitPipeline {
    method create_bind_group (line 11) | pub fn create_bind_group(
    method new (line 33) | pub fn new(
    method rebuild (line 65) | pub fn rebuild(
    method pipeline (line 78) | pub fn pipeline(&self) -> &wgpu::RenderPipeline {
    method bind_group_layouts (line 82) | pub fn bind_group_layouts(&self) -> &[wgpu::BindGroupLayout] {
    method blit (line 86) | pub fn blit<'a>(&'a self, state: &'a GraphicsState, pass: &mut wgpu::R...
  type VertexPushConstants (line 95) | type VertexPushConstants = ();
  type SharedPushConstants (line 96) | type SharedPushConstants = ();
  type FragmentPushConstants (line 97) | type FragmentPushConstants = ();
  method name (line 99) | fn name() -> &'static str {
  method bind_group_layout_descriptors (line 103) | fn bind_group_layout_descriptors() -> Vec<wgpu::BindGroupLayoutDescripto...
  method vertex_shader (line 132) | fn vertex_shader() -> &'static str {
  method fragment_shader (line 136) | fn fragment_shader() -> &'static str {
  method primitive_state (line 140) | fn primitive_state() -> wgpu::PrimitiveState {
  method color_target_states (line 144) | fn color_target_states() -> Vec<wgpu::ColorTargetState> {
  method depth_stencil_state (line 148) | fn depth_stencil_state() -> Option<wgpu::DepthStencilState> {
  method vertex_buffer_layouts (line 152) | fn vertex_buffer_layouts() -> Vec<wgpu::VertexBufferLayout<'static>> {

FILE: src/client/render/cvars.rs
  function register_cvars (line 23) | pub fn register_cvars(cvars: &CvarRegistry) {

FILE: src/client/render/error.rs
  type RenderError (line 12) | pub struct RenderError {
    method kind (line 17) | pub fn kind(&self) -> RenderErrorKind {
    method from (line 23) | fn from(kind: RenderErrorKind) -> Self {
    method from (line 31) | fn from(vfs_error: VfsError) -> Self {
    method from (line 42) | fn from(wad_error: WadError) -> Self {
    method from (line 48) | fn from(inner: Context<RenderErrorKind>) -> Self {
  method cause (line 54) | fn cause(&self) -> Option<&dyn Fail> {
  method backtrace (line 58) | fn backtrace(&self) -> Option<&Backtrace> {
  method fmt (line 64) | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  type RenderErrorKind (line 70) | pub enum RenderErrorKind {

FILE: src/client/render/mod.rs
  constant DEPTH_ATTACHMENT_FORMAT (line 115) | const DEPTH_ATTACHMENT_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat...
  constant DIFFUSE_ATTACHMENT_FORMAT (line 116) | pub const DIFFUSE_ATTACHMENT_FORMAT: wgpu::TextureFormat = wgpu::Texture...
  constant NORMAL_ATTACHMENT_FORMAT (line 117) | const NORMAL_ATTACHMENT_FORMAT: wgpu::TextureFormat = wgpu::TextureForma...
  constant LIGHT_ATTACHMENT_FORMAT (line 118) | const LIGHT_ATTACHMENT_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat...
  constant DIFFUSE_TEXTURE_FORMAT (line 120) | const DIFFUSE_TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat:...
  constant FULLBRIGHT_TEXTURE_FORMAT (line 121) | const FULLBRIGHT_TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureForm...
  constant LIGHTMAP_TEXTURE_FORMAT (line 122) | const LIGHTMAP_TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat...
  function texture_descriptor (line 125) | pub fn texture_descriptor<'a>(
  function create_texture (line 146) | pub fn create_texture<'a>(
  type DiffuseData (line 183) | pub struct DiffuseData<'a> {
  type FullbrightData (line 187) | pub struct FullbrightData<'a> {
  type LightmapData (line 191) | pub struct LightmapData<'a> {
  type TextureData (line 195) | pub enum TextureData<'a> {
  function format (line 202) | pub fn format(&self) -> wgpu::TextureFormat {
  function data (line 210) | pub fn data(&self) -> &[u8] {
  function stride (line 218) | pub fn stride(&self) -> u32 {
  function size (line 226) | pub fn size(&self) -> wgpu::BufferAddress {
  type Extent2d (line 232) | pub struct Extent2d {
    method into (line 238) | fn into(self) -> wgpu::Extent3d {
    method from (line 248) | fn from(other: winit::dpi::PhysicalSize<u32>) -> Extent2d {
  type GraphicsState (line 254) | pub struct GraphicsState {
    method new (line 293) | pub fn new(
    method create_texture (line 467) | pub fn create_texture<'a>(
    method update (line 483) | pub fn update(&mut self, size: Extent2d, sample_count: u32) {
    method recreate_pipelines (line 517) | fn recreate_pipelines(&mut self, sample_count: u32) {
    method device (line 554) | pub fn device(&self) -> &wgpu::Device {
    method queue (line 558) | pub fn queue(&self) -> &wgpu::Queue {
    method initial_pass_target (line 562) | pub fn initial_pass_target(&self) -> &InitialPassTarget {
    method deferred_pass_target (line 566) | pub fn deferred_pass_target(&self) -> &DeferredPassTarget {
    method final_pass_target (line 570) | pub fn final_pass_target(&self) -> &FinalPassTarget {
    method frame_uniform_buffer (line 574) | pub fn frame_uniform_buffer(&self) -> &wgpu::Buffer {
    method entity_uniform_buffer (line 578) | pub fn entity_uniform_buffer(&self) -> Ref<DynamicUniformBuffer<Entity...
    method entity_uniform_buffer_mut (line 582) | pub fn entity_uniform_buffer_mut(&self) -> RefMut<DynamicUniformBuffer...
    method diffuse_sampler (line 586) | pub fn diffuse_sampler(&self) -> &wgpu::Sampler {
    method default_lightmap (line 590) | pub fn default_lightmap(&self) -> &wgpu::Texture {
    method default_lightmap_view (line 594) | pub fn default_lightmap_view(&self) -> &wgpu::TextureView {
    method lightmap_sampler (line 598) | pub fn lightmap_sampler(&self) -> &wgpu::Sampler {
    method world_bind_group_layouts (line 602) | pub fn world_bind_group_layouts(&self) -> &[wgpu::BindGroupLayout] {
    method world_bind_groups (line 606) | pub fn world_bind_groups(&self) -> &[wgpu::BindGroup] {
    method alias_pipeline (line 612) | pub fn alias_pipeline(&self) -> &AliasPipeline {
    method brush_pipeline (line 616) | pub fn brush_pipeline(&self) -> &BrushPipeline {
    method sprite_pipeline (line 620) | pub fn sprite_pipeline(&self) -> &SpritePipeline {
    method deferred_pipeline (line 624) | pub fn deferred_pipeline(&self) -> &DeferredPipeline {
    method particle_pipeline (line 628) | pub fn particle_pipeline(&self) -> &ParticlePipeline {
    method postprocess_pipeline (line 632) | pub fn postprocess_pipeline(&self) -> &PostProcessPipeline {
    method glyph_pipeline (line 636) | pub fn glyph_pipeline(&self) -> &GlyphPipeline {
    method quad_pipeline (line 640) | pub fn quad_pipeline(&self) -> &QuadPipeline {
    method blit_pipeline (line 644) | pub fn blit_pipeline(&self) -> &BlitPipeline {
    method vfs (line 648) | pub fn vfs(&self) -> &Vfs {
    method palette (line 652) | pub fn palette(&self) -> &Palette {
    method gfx_wad (line 656) | pub fn gfx_wad(&self) -> &Wad {
  type ClientRenderer (line 661) | pub struct ClientRenderer {
    method new (line 670) | pub fn new(state: &GraphicsState, menu: &Menu) -> ClientRenderer {
    method render (line 689) | pub fn render(

FILE: src/client/render/palette.rs
  type Palette (line 10) | pub struct Palette {
    method new (line 15) | pub fn new(data: &[u8]) -> Palette {
    method load (line 30) | pub fn load<S>(vfs: &Vfs, path: S) -> Palette
    method translate (line 50) | pub fn translate(&self, indices: &[u8]) -> (DiffuseData, FullbrightDat...

FILE: src/client/render/pipeline.rs
  function create_shader (line 27) | fn create_shader<S>(
  type PushConstantUpdate (line 48) | pub enum PushConstantUpdate<T> {
  type Pipeline (line 61) | pub trait Pipeline {
    method name (line 72) | fn name() -> &'static str;
    method bind_group_layout_descriptors (line 75) | fn bind_group_layout_descriptors() -> Vec<wgpu::BindGroupLayoutDescrip...
    method vertex_shader (line 78) | fn vertex_shader() -> &'static str;
    method fragment_shader (line 81) | fn fragment_shader() -> &'static str;
    method primitive_state (line 84) | fn primitive_state() -> wgpu::PrimitiveState;
    method color_target_states (line 87) | fn color_target_states() -> Vec<wgpu::ColorTargetState>;
    method depth_stencil_state (line 90) | fn depth_stencil_state() -> Option<wgpu::DepthStencilState>;
    method vertex_buffer_layouts (line 93) | fn vertex_buffer_layouts() -> Vec<wgpu::VertexBufferLayout<'static>>;
    method vertex_push_constant_range (line 95) | fn vertex_push_constant_range() -> wgpu::PushConstantRange {
    method fragment_push_constant_range (line 105) | fn fragment_push_constant_range() -> wgpu::PushConstantRange {
    method push_constant_ranges (line 117) | fn push_constant_ranges() -> Vec<wgpu::PushConstantRange> {
    method validate_push_constant_types (line 135) | fn validate_push_constant_types(limits: wgpu::Limits) {
    method create (line 171) | fn create(
    method recreate (line 251) | fn recreate(
    method set_push_constants (line 311) | fn set_push_constants<'a>(

FILE: src/client/render/target.rs
  function create_color_attachment (line 31) | pub fn create_color_attachment(
  function create_normal_attachment (line 52) | pub fn create_normal_attachment(
  function create_light_attachment (line 73) | pub fn create_light_attachment(
  function create_depth_attachment (line 94) | pub fn create_depth_attachment(
  type RenderPassBuilder (line 112) | pub struct RenderPassBuilder<'a> {
  function descriptor (line 118) | pub fn descriptor(&self) -> wgpu::RenderPassDescriptor {
  type RenderTarget (line 131) | pub trait RenderTarget {
    method render_pass_builder (line 132) | fn render_pass_builder<'a>(&'a self) -> RenderPassBuilder<'a>;
    method render_pass_builder (line 230) | fn render_pass_builder<'a>(&'a self) -> RenderPassBuilder {
    method render_pass_builder (line 309) | fn render_pass_builder<'a>(&'a self) -> RenderPassBuilder {
    method render_pass_builder (line 368) | fn render_pass_builder<'a>(&'a self) -> RenderPassBuilder {
    method render_pass_builder (line 404) | fn render_pass_builder(&self) -> RenderPassBuilder {
  type RenderTargetResolve (line 136) | pub trait RenderTargetResolve: RenderTarget {
    method resolve_attachment (line 137) | fn resolve_attachment(&self) -> &wgpu::Texture;
    method resolve_view (line 138) | fn resolve_view(&self) -> &wgpu::TextureView;
    method resolve_attachment (line 384) | fn resolve_attachment(&self) -> &wgpu::Texture {
    method resolve_view (line 388) | fn resolve_view(&self) -> &wgpu::TextureView {
  type InitialPassTarget (line 145) | pub struct InitialPassTarget {
    method new (line 159) | pub fn new(device: &wgpu::Device, size: Extent2d, sample_count: u32) -...
    method size (line 188) | pub fn size(&self) -> Extent2d {
    method sample_count (line 192) | pub fn sample_count(&self) -> u32 {
    method diffuse_attachment (line 196) | pub fn diffuse_attachment(&self) -> &wgpu::Texture {
    method diffuse_view (line 200) | pub fn diffuse_view(&self) -> &wgpu::TextureView {
    method normal_attachment (line 204) | pub fn normal_attachment(&self) -> &wgpu::Texture {
    method normal_view (line 208) | pub fn normal_view(&self) -> &wgpu::TextureView {
    method light_attachment (line 212) | pub fn light_attachment(&self) -> &wgpu::Texture {
    method light_view (line 216) | pub fn light_view(&self) -> &wgpu::TextureView {
    method depth_attachment (line 220) | pub fn depth_attachment(&self) -> &wgpu::Texture {
    method depth_view (line 224) | pub fn depth_view(&self) -> &wgpu::TextureView {
  type DeferredPassTarget (line 270) | pub struct DeferredPassTarget {
    method new (line 278) | pub fn new(device: &wgpu::Device, size: Extent2d, sample_count: u32) -...
    method size (line 291) | pub fn size(&self) -> Extent2d {
    method sample_count (line 295) | pub fn sample_count(&self) -> u32 {
    method color_attachment (line 299) | pub fn color_attachment(&self) -> &wgpu::Texture {
    method color_view (line 303) | pub fn color_view(&self) -> &wgpu::TextureView {
  type FinalPassTarget (line 324) | pub struct FinalPassTarget {
    method new (line 334) | pub fn new(device: &wgpu::Device, size: Extent2d, sample_count: u32) -...
    method size (line 358) | pub fn size(&self) -> Extent2d {
    method sample_count (line 362) | pub fn sample_count(&self) -> u32 {
  type SwapChainTarget (line 393) | pub struct SwapChainTarget<'a> {
  function with_swap_chain_view (line 398) | pub fn with_swap_chain_view(swap_chain_view: &'a wgpu::TextureView) -> S...

FILE: src/client/render/ui/console.rs
  constant PAD_LEFT (line 15) | const PAD_LEFT: i32 = GLYPH_WIDTH as i32;
  type ConsoleRenderer (line 17) | pub struct ConsoleRenderer {
    method new (line 22) | pub fn new(state: &GraphicsState) -> ConsoleRenderer {
    method generate_commands (line 31) | pub fn generate_commands<'a>(

FILE: src/client/render/ui/glyph.rs
  constant GLYPH_WIDTH (line 17) | pub const GLYPH_WIDTH: usize = 8;
  constant GLYPH_HEIGHT (line 18) | pub const GLYPH_HEIGHT: usize = 8;
  constant GLYPH_COLS (line 19) | const GLYPH_COLS: usize = 16;
  constant GLYPH_ROWS (line 20) | const GLYPH_ROWS: usize = 16;
  constant GLYPH_COUNT (line 21) | const GLYPH_COUNT: usize = GLYPH_ROWS * GLYPH_COLS;
  constant GLYPH_TEXTURE_WIDTH (line 22) | const GLYPH_TEXTURE_WIDTH: usize = GLYPH_WIDTH * GLYPH_COLS;
  constant MAX_INSTANCES (line 25) | pub const MAX_INSTANCES: usize = 65536;
  type GlyphPipeline (line 41) | pub struct GlyphPipeline {
    method new (line 48) | pub fn new(
    method rebuild (line 70) | pub fn rebuild(
    method pipeline (line 80) | pub fn pipeline(&self) -> &wgpu::RenderPipeline {
    method bind_group_layouts (line 84) | pub fn bind_group_layouts(&self) -> &[wgpu::BindGroupLayout] {
    method instance_buffer (line 88) | pub fn instance_buffer(&self) -> &wgpu::Buffer {
  constant BIND_GROUP_LAYOUT_ENTRIES (line 93) | const BIND_GROUP_LAYOUT_ENTRIES: &[wgpu::BindGroupLayoutEntry] = &[
  type VertexPushConstants (line 118) | type VertexPushConstants = ();
  type SharedPushConstants (line 119) | type SharedPushConstants = ();
  type FragmentPushConstants (line 120) | type FragmentPushConstants = ();
  method name (line 122) | fn name() -> &'static str {
  method vertex_shader (line 126) | fn vertex_shader() -> &'static str {
  method fragment_shader (line 130) | fn fragment_shader() -> &'static str {
  method primitive_state (line 134) | fn primitive_state() -> wgpu::PrimitiveState {
  method bind_group_layout_descriptors (line 138) | fn bind_group_layout_descriptors() -> Vec<wgpu::BindGroupLayoutDescripto...
  method color_target_states (line 145) | fn color_target_states() -> Vec<wgpu::ColorTargetState> {
  method depth_stencil_state (line 149) | fn depth_stencil_state() -> Option<wgpu::DepthStencilState> {
  method vertex_buffer_layouts (line 153) | fn vertex_buffer_layouts() -> Vec<wgpu::VertexBufferLayout<'static>> {
  type GlyphInstance (line 171) | pub struct GlyphInstance {
  type GlyphRendererCommand (line 177) | pub enum GlyphRendererCommand {
  type GlyphRenderer (line 192) | pub struct GlyphRenderer {
    method new (line 201) | pub fn new(state: &GraphicsState) -> GlyphRenderer {
    method generate_instances (line 268) | pub fn generate_instances(
    method record_draw (line 357) | pub fn record_draw<'a>(

FILE: src/client/render/ui/hud.rs
  constant OVERLAY_WIDTH (line 29) | const OVERLAY_WIDTH: i32 = 320;
  constant OVERLAY_HEIGHT (line 30) | const OVERLAY_HEIGHT: i32 = 200;
  constant OVERLAY_X_OFS (line 32) | const OVERLAY_X_OFS: i32 = -OVERLAY_WIDTH / 2;
  constant OVERLAY_Y_OFS (line 33) | const OVERLAY_Y_OFS: i32 = -OVERLAY_HEIGHT / 2;
  constant OVERLAY_ANCHOR (line 35) | const OVERLAY_ANCHOR: Anchor = Anchor::CENTER;
  type HudState (line 37) | pub enum HudState<'a> {
  type HudTextureId (line 54) | enum HudTextureId {
    method fmt (line 75) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  constant WEAPON_ID_NAMES (line 99) | const WEAPON_ID_NAMES: [&'static str; 7] = [
  type WeaponId (line 103) | enum WeaponId {
    method fmt (line 114) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  type WeaponFrame (line 120) | enum WeaponFrame {
    method fmt (line 127) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  constant AMMO_ID_NAMES (line 136) | const AMMO_ID_NAMES: [&'static str; 4] = ["SHELLS", "NAILS", "ROCKET", "...
  type AmmoId (line 138) | enum AmmoId {
    method fmt (line 146) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  constant ITEM_ID_NAMES (line 151) | const ITEM_ID_NAMES: [&'static str; 6] = ["KEY1", "KEY2", "INVIS", "INVU...
  type ItemId (line 153) | enum ItemId {
    method fmt (line 163) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  type FaceId (line 169) | enum FaceId {
    method fmt (line 178) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  type HudRenderer (line 192) | pub struct HudRenderer {
    method new (line 198) | pub fn new(state: &GraphicsState) -> HudRenderer {
    method cmd_number (line 269) | fn cmd_number<'a>(
    method cmd_sbar_quad (line 323) | fn cmd_sbar_quad<'a>(
    method cmd_sbar_number (line 349) | fn cmd_sbar_number<'a>(
    method cmd_sbar (line 373) | fn cmd_sbar<'a>(
    method cmd_intermission_quad (line 551) | fn cmd_intermission_quad<'a>(
    method cmd_intermission_number (line 577) | fn cmd_intermission_number<'a>(
    method cmd_intermission_overlay (line 600) | fn cmd_intermission_overlay<'a>(
    method generate_commands (line 641) | pub fn generate_commands<'state, 'a>(

FILE: src/client/render/ui/layout.rs
  type Layout (line 2) | pub struct Layout {
  type AnchorCoord (line 15) | pub enum AnchorCoord {
    method to_value (line 33) | pub fn to_value(&self, max: u32) -> i32 {
  type Anchor (line 49) | pub struct Anchor {
    constant BOTTOM_LEFT (line 58) | pub const BOTTOM_LEFT: Anchor = Anchor {
    constant CENTER_LEFT (line 62) | pub const CENTER_LEFT: Anchor = Anchor {
    constant TOP_LEFT (line 66) | pub const TOP_LEFT: Anchor = Anchor {
    constant BOTTOM_CENTER (line 70) | pub const BOTTOM_CENTER: Anchor = Anchor {
    constant CENTER (line 74) | pub const CENTER: Anchor = Anchor {
    constant TOP_CENTER (line 78) | pub const TOP_CENTER: Anchor = Anchor {
    constant BOTTOM_RIGHT (line 82) | pub const BOTTOM_RIGHT: Anchor = Anchor {
    constant CENTER_RIGHT (line 86) | pub const CENTER_RIGHT: Anchor = Anchor {
    constant TOP_RIGHT (line 90) | pub const TOP_RIGHT: Anchor = Anchor {
    method absolute_xy (line 95) | pub fn absolute_xy(x: i32, y: i32) -> Anchor {
    method to_xy (line 102) | pub fn to_xy(&self, width: u32, height: u32) -> (i32, i32) {
  type ScreenPosition (line 109) | pub enum ScreenPosition {
    method to_xy (line 126) | pub fn to_xy(&self, display_width: u32, display_height: u32, scale: f3...
  type Size (line 153) | pub enum Size {
    method to_wh (line 177) | pub fn to_wh(
  function test_anchor_to_xy (line 203) | fn test_anchor_to_xy() {

FILE: src/client/render/ui/menu.rs
  constant MENU_WIDTH (line 21) | const MENU_WIDTH: i32 = 320;
  constant MENU_HEIGHT (line 22) | const MENU_HEIGHT: i32 = 200;
  constant SLIDER_LEFT (line 24) | const SLIDER_LEFT: u8 = 128;
  constant SLIDER_MIDDLE (line 25) | const SLIDER_MIDDLE: u8 = 129;
  constant SLIDER_RIGHT (line 26) | const SLIDER_RIGHT: u8 = 130;
  constant SLIDER_HANDLE (line 27) | const SLIDER_HANDLE: u8 = 131;
  constant SLIDER_WIDTH (line 28) | const SLIDER_WIDTH: i32 = 10;
  type Align (line 31) | enum Align {
    method x_ofs (line 37) | pub fn x_ofs(&self) -> i32 {
    method anchor (line 44) | pub fn anchor(&self) -> Anchor {
  type MenuRenderer (line 52) | pub struct MenuRenderer {
    method new (line 57) | pub fn new(state: &GraphicsState, menu: &Menu) -> MenuRenderer {
    method texture (line 94) | fn texture<S>(&self, name: S) -> &QuadTexture
    method cmd_draw_quad (line 102) | fn cmd_draw_quad<'state>(
    method cmd_draw_glyph (line 125) | fn cmd_draw_glyph(
    method cmd_draw_plaque (line 145) | fn cmd_draw_plaque<'a>(&'a self, scale: f32, quad_cmds: &mut Vec<QuadR...
    method cmd_draw_title (line 150) | fn cmd_draw_title<'a, S>(
    method cmd_draw_body_predef (line 162) | fn cmd_draw_body_predef<'a, S>(
    method cmd_draw_item_name (line 186) | fn cmd_draw_item_name<S>(
    method cmd_draw_item_text (line 208) | fn cmd_draw_item_text<S>(
    method cmd_draw_slider (line 230) | fn cmd_draw_slider(
    method cmd_draw_body_dynamic (line 247) | fn cmd_draw_body_dynamic(
    method generate_commands (line 290) | pub fn generate_commands<'a>(

FILE: src/client/render/ui/mod.rs
  function screen_space_vertex_translate (line 31) | pub fn screen_space_vertex_translate(
  function screen_space_vertex_scale (line 44) | pub fn screen_space_vertex_scale(
  function screen_space_vertex_transform (line 56) | pub fn screen_space_vertex_transform(
  type UiOverlay (line 76) | pub enum UiOverlay<'a> {
  type UiState (line 81) | pub enum UiState<'a> {
  type UiRenderer (line 91) | pub struct UiRenderer {
    method new (line 100) | pub fn new(state: &GraphicsState, menu: &Menu) -> UiRenderer {
    method render_pass (line 110) | pub fn render_pass<'pass>(

FILE: src/client/render/ui/quad.rs
  constant VERTICES (line 21) | pub const VERTICES: [QuadVertex; 6] = [
  type Position (line 49) | pub type Position = [f32; 2];
  type Texcoord (line 50) | pub type Texcoord = [f32; 2];
  type QuadVertex (line 54) | pub struct QuadVertex {
  type QuadPipeline (line 76) | pub struct QuadPipeline {
    method new (line 85) | pub fn new(
    method rebuild (line 112) | pub fn rebuild(
    method pipeline (line 122) | pub fn pipeline(&self) -> &wgpu::RenderPipeline {
    method bind_group_layouts (line 126) | pub fn bind_group_layouts(&self) -> &[wgpu::BindGroupLayout] {
    method vertex_buffer (line 130) | pub fn vertex_buffer(&self) -> &wgpu::Buffer {
    method uniform_buffer (line 134) | pub fn uniform_buffer(&self) -> Ref<DynamicUniformBuffer<QuadUniforms>> {
    method uniform_buffer_mut (line 138) | pub fn uniform_buffer_mut(&self) -> RefMut<DynamicUniformBuffer<QuadUn...
    method uniform_buffer_blocks (line 142) | pub fn uniform_buffer_blocks(&self) -> Ref<Vec<DynamicUniformBufferBlo...
    method uniform_buffer_blocks_mut (line 146) | pub fn uniform_buffer_blocks_mut(
  constant BIND_GROUP_LAYOUT_ENTRIES (line 153) | const BIND_GROUP_LAYOUT_ENTRIES: &[&[wgpu::BindGroupLayoutEntry]] = &[
  type VertexPushConstants (line 196) | type VertexPushConstants = ();
  type SharedPushConstants (line 197) | type SharedPushConstants = ();
  type FragmentPushConstants (line 198) | type FragmentPushConstants = ();
  method name (line 200) | fn name() -> &'static str {
  method bind_group_layout_descriptors (line 204) | fn bind_group_layout_descriptors() -> Vec<wgpu::BindGroupLayoutDescripto...
  method vertex_shader (line 224) | fn vertex_shader() -> &'static str {
  method fragment_shader (line 228) | fn fragment_shader() -> &'static str {
  method primitive_state (line 232) | fn primitive_state() -> wgpu::PrimitiveState {
  method color_target_states (line 244) | fn color_target_states() -> Vec<wgpu::ColorTargetState> {
  method depth_stencil_state (line 252) | fn depth_stencil_state() -> Option<wgpu::DepthStencilState> {
  method vertex_buffer_layouts (line 257) | fn vertex_buffer_layouts() -> Vec<wgpu::VertexBufferLayout<'static>> {
  type QuadUniforms (line 268) | pub struct QuadUniforms {
  type QuadTexture (line 272) | pub struct QuadTexture {
    method from_qpic (line 283) | pub fn from_qpic(state: &GraphicsState, qpic: &QPic) -> QuadTexture {
    method width (line 312) | pub fn width(&self) -> u32 {
    method height (line 316) | pub fn height(&self) -> u32 {
    method scale_width (line 320) | pub fn scale_width(&self, scale: f32) -> u32 {
    method scale_height (line 324) | pub fn scale_height(&self, scale: f32) -> u32 {
  type QuadRendererCommand (line 330) | pub struct QuadRendererCommand<'a> {
  type QuadRenderer (line 338) | pub struct QuadRenderer {
    method new (line 344) | pub fn new(state: &GraphicsState) -> QuadRenderer {
    method generate_uniforms (line 376) | fn generate_uniforms<'cmds>(
    method record_draw (line 426) | pub fn record_draw<'pass, 'cmds>(

FILE: src/client/render/uniform.rs
  constant DYNAMIC_UNIFORM_BUFFER_SIZE (line 16) | const DYNAMIC_UNIFORM_BUFFER_SIZE: wgpu::BufferAddress = 65536;
  constant DYNAMIC_UNIFORM_BUFFER_ALIGNMENT (line 19) | pub const DYNAMIC_UNIFORM_BUFFER_ALIGNMENT: usize = 256;
  type UniformBool (line 23) | pub struct UniformBool {
    method new (line 28) | pub fn new(value: bool) -> UniformBool {
  type UniformArrayFloat (line 38) | pub struct UniformArrayFloat {
    method new (line 43) | pub fn new(value: f32) -> UniformArrayFloat {
  type DynamicUniformBuffer (line 51) | pub struct DynamicUniformBuffer<T>
  function new (line 71) | pub fn new<'b>(device: &'b wgpu::Device) -> DynamicUniformBuffer<T> {
  function block_size (line 94) | pub fn block_size(&self) -> wgpu::BufferSize {
  function allocate (line 104) | pub fn allocate(&mut self, val: T) -> DynamicUniformBufferBlock<T> {
  function write_block (line 131) | pub fn write_block(&mut self, block: &DynamicUniformBufferBlock<T>, val:...
  function clear (line 142) | pub fn clear(&self) -> Result<(), Error> {
  function flush (line 157) | pub fn flush(&self, queue: &wgpu::Queue) {
  function buffer (line 161) | pub fn buffer(&self) -> &wgpu::Buffer {
  type DynamicUniformBufferBlock (line 168) | pub struct DynamicUniformBufferBlock<T> {
  function offset (line 176) | pub fn offset(&self) -> wgpu::DynamicOffset {
  function clear_and_rewrite (line 181) | pub fn clear_and_rewrite<T>(

FILE: src/client/render/warp.rs
  constant SUBDIVIDE_SIZE (line 8) | const SUBDIVIDE_SIZE: f32 = 32.0;
  function subdivide (line 22) | pub fn subdivide(verts: Vec<Vector3<f32>>) -> Vec<Vector3<f32>> {
  function subdivide_impl (line 28) | fn subdivide_impl(mut verts: Vec<Vector3<f32>>, output: &mut Vec<Vector3...

FILE: src/client/render/world/alias.rs
  type AliasPipeline (line 18) | pub struct AliasPipeline {
    method new (line 24) | pub fn new(
    method rebuild (line 39) | pub fn rebuild(
    method pipeline (line 53) | pub fn pipeline(&self) -> &wgpu::RenderPipeline {
    method bind_group_layouts (line 57) | pub fn bind_group_layouts(&self) -> &[wgpu::BindGroupLayout] {
  type VertexPushConstants (line 64) | pub struct VertexPushConstants {
  type VertexPushConstants (line 84) | type VertexPushConstants = VertexPushConstants;
  type SharedPushConstants (line 85) | type SharedPushConstants = ();
  type FragmentPushConstants (line 86) | type FragmentPushConstants = ();
  method name (line 88) | fn name() -> &'static str {
  method vertex_shader (line 92) | fn vertex_shader() -> &'static str {
  method fragment_shader (line 96) | fn fragment_shader() -> &'static str {
  method bind_group_layout_descriptors (line 100) | fn bind_group_layout_descriptors() -> Vec<wgpu::BindGroupLayoutDescripto...
  method primitive_state (line 122) | fn primitive_state() -> wgpu::PrimitiveState {
  method color_target_states (line 126) | fn color_target_states() -> Vec<wgpu::ColorTargetState> {
  method depth_stencil_state (line 130) | fn depth_stencil_state() -> Option<wgpu::DepthStencilState> {
  method vertex_buffer_layouts (line 135) | fn vertex_buffer_layouts() -> Vec<wgpu::VertexBufferLayout<'static>> {
  type Position (line 145) | type Position = [f32; 3];
  type Normal (line 146) | type Normal = [f32; 3];
  type DiffuseTexcoord (line 147) | type DiffuseTexcoord = [f32; 2];
  type AliasVertex (line 151) | struct AliasVertex {
  type Keyframe (line 157) | enum Keyframe {
    method animate (line 169) | fn animate(&self, time: Duration) -> Range<u32> {
  type Texture (line 192) | enum Texture {
    method animate (line 208) | fn animate(&self, time: Duration) -> &wgpu::BindGroup {
  type AliasRenderer (line 233) | pub struct AliasRenderer {
    method new (line 240) | pub fn new(state: &GraphicsState, alias_model: &AliasModel) -> Result<...
    method record_draw (line 418) | pub fn record_draw<'a>(

FILE: src/client/render/world/brush.rs
  type BrushPipeline (line 53) | pub struct BrushPipeline {
    method new (line 59) | pub fn new(
    method rebuild (line 76) | pub fn rebuild(
    method pipeline (line 90) | pub fn pipeline(&self) -> &wgpu::RenderPipeline {
    method bind_group_layouts (line 94) | pub fn bind_group_layouts(&self) -> &[wgpu::BindGroupLayout] {
    method bind_group_layout (line 98) | pub fn bind_group_layout(&self, id: BindGroupLayoutId) -> &wgpu::BindG...
  type VertexPushConstants (line 106) | pub struct VertexPushConstants {
  type SharedPushConstants (line 113) | pub struct SharedPushConstants {
  constant BIND_GROUP_LAYOUT_ENTRIES (line 117) | const BIND_GROUP_LAYOUT_ENTRIES: &[&[wgpu::BindGroupLayoutEntry]] = &[
  type VertexPushConstants (line 174) | type VertexPushConstants = VertexPushConstants;
  type SharedPushConstants (line 175) | type SharedPushConstants = SharedPushConstants;
  type FragmentPushConstants (line 176) | type FragmentPushConstants = ();
  method name (line 178) | fn name() -> &'static str {
  method vertex_shader (line 182) | fn vertex_shader() -> &'static str {
  method fragment_shader (line 186) | fn fragment_shader() -> &'static str {
  method bind_group_layout_descriptors (line 192) | fn bind_group_layout_descriptors() -> Vec<wgpu::BindGroupLayoutDescripto...
  method primitive_state (line 207) | fn primitive_state() -> wgpu::PrimitiveState {
  method color_target_states (line 211) | fn color_target_states() -> Vec<wgpu::ColorTargetState> {
  method depth_stencil_state (line 215) | fn depth_stencil_state() -> Option<wgpu::DepthStencilState> {
  method vertex_buffer_layouts (line 220) | fn vertex_buffer_layouts() -> Vec<wgpu::VertexBufferLayout<'static>> {
  function calculate_lightmap_texcoords (line 229) | fn calculate_lightmap_texcoords(
  type Position (line 246) | type Position = [f32; 3];
  type Normal (line 247) | type Normal = [f32; 3];
  type DiffuseTexcoord (line 248) | type DiffuseTexcoord = [f32; 2];
  type LightmapTexcoord (line 249) | type LightmapTexcoord = [f32; 2];
  type LightmapAnim (line 250) | type LightmapAnim = [u8; 4];
  type BrushVertex (line 254) | struct BrushVertex {
  type TextureKind (line 264) | pub enum TextureKind {
  type BrushTextureFrame (line 271) | pub struct BrushTextureFrame {
  type BrushTexture (line 281) | pub enum BrushTexture {
    method kind (line 296) | fn kind(&self) -> TextureKind {
  type BrushFace (line 305) | struct BrushFace {
  type BrushLeaf (line 323) | struct BrushLeaf {
    method from (line 331) | fn from(bsp_leaf: B) -> Self {
  type BrushRendererBuilder (line 339) | pub struct BrushRendererBuilder {
    method new (line 357) | pub fn new(bsp_model: &BspModel, worldmodel: bool) -> BrushRendererBui...
    method create_face (line 382) | fn create_face(&mut self, state: &GraphicsState, face_id: usize) -> Br...
    method create_per_texture_bind_group (line 491) | fn create_per_texture_bind_group(
    method create_per_face_bind_group (line 516) | fn create_per_face_bind_group(&self, state: &GraphicsState, face_id: u...
    method create_brush_texture_frame (line 542) | fn create_brush_texture_frame<S>(
    method create_brush_texture (line 596) | pub fn create_brush_texture(&self, state: &GraphicsState, tex: &BspTex...
    method build (line 648) | pub fn build(mut self, state: &GraphicsState) -> Result<BrushRenderer,...
  type BrushRenderer (line 698) | pub struct BrushRenderer {
    method record_draw (line 718) | pub fn record_draw<'a>(

FILE: src/client/render/world/deferred.rs
  type PointLight (line 15) | pub struct PointLight {
  type DeferredUniforms (line 22) | pub struct DeferredUniforms {
  type DeferredPipeline (line 29) | pub struct DeferredPipeline {
    method new (line 36) | pub fn new(
    method rebuild (line 68) | pub fn rebuild(
    method pipeline (line 79) | pub fn pipeline(&self) -> &wgpu::RenderPipeline {
    method bind_group_layouts (line 83) | pub fn bind_group_layouts(&self) -> &[wgpu::BindGroupLayout] {
    method uniform_buffer (line 87) | pub fn uniform_buffer(&self) -> &wgpu::Buffer {
  constant BIND_GROUP_LAYOUT_ENTRIES (line 92) | const BIND_GROUP_LAYOUT_ENTRIES: &[wgpu::BindGroupLayoutEntry] = &[
  type VertexPushConstants (line 161) | type VertexPushConstants = ();
  type SharedPushConstants (line 162) | type SharedPushConstants = ();
  type FragmentPushConstants (line 163) | type FragmentPushConstants = ();
  method name (line 165) | fn name() -> &'static str {
  method bind_group_layout_descriptors (line 169) | fn bind_group_layout_descriptors() -> Vec<wgpu::BindGroupLayoutDescripto...
  method vertex_shader (line 176) | fn vertex_shader() -> &'static str {
  method fragment_shader (line 183) | fn fragment_shader() -> &'static str {
  method primitive_state (line 190) | fn primitive_state() -> wgpu::PrimitiveState {
  method color_target_states (line 194) | fn color_target_states() -> Vec<wgpu::ColorTargetState> {
  method depth_stencil_state (line 198) | fn depth_stencil_state() -> Option<wgpu::DepthStencilState> {
  method vertex_buffer_layouts (line 202) | fn vertex_buffer_layouts() -> Vec<wgpu::VertexBufferLayout<'static>> {
  type DeferredRenderer (line 207) | pub struct DeferredRenderer {
    method create_bind_group (line 212) | fn create_bind_group(
    method new (line 263) | pub fn new(
    method rebuild (line 281) | pub fn rebuild(
    method update_uniform_buffers (line 298) | pub fn update_uniform_buffers(&self, state: &GraphicsState, uniforms: ...
    method record_draw (line 307) | pub fn record_draw<'pass>(

FILE: src/client/render/world/mod.rs
  type WorldPipelineBase (line 100) | struct WorldPipelineBase;
  type VertexPushConstants (line 103) | type VertexPushConstants = ();
  type SharedPushConstants (line 104) | type SharedPushConstants = ();
  type FragmentPushConstants (line 105) | type FragmentPushConstants = ();
  method name (line 107) | fn name() -> &'static str {
  method vertex_shader (line 111) | fn vertex_shader() -> &'static str {
  method fragment_shader (line 115) | fn fragment_shader() -> &'static str {
  method bind_group_layout_descriptors (line 119) | fn bind_group_layout_descriptors() -> Vec<wgpu::BindGroupLayoutDescripto...
  method primitive_state (line 124) | fn primitive_state() -> wgpu::PrimitiveState {
  method color_target_states (line 136) | fn color_target_states() -> Vec<wgpu::ColorTargetState> {
  method depth_stencil_state (line 159) | fn depth_stencil_state() -> Option<wgpu::DepthStencilState> {
  method vertex_buffer_layouts (line 178) | fn vertex_buffer_layouts() -> Vec<wgpu::VertexBufferLayout<'static>> {
  type BindGroupLayoutId (line 184) | pub enum BindGroupLayoutId {
  type Camera (line 191) | pub struct Camera {
    method new (line 202) | pub fn new(origin: Vector3<f32>, angles: Angles, projection: Matrix4<f...
    method origin (line 239) | pub fn origin(&self) -> Vector3<f32> {
    method angles (line 243) | pub fn angles(&self) -> Angles {
    method view (line 247) | pub fn view(&self) -> Matrix4<f32> {
    method view_projection (line 251) | pub fn view_projection(&self) -> Matrix4<f32> {
    method projection (line 255) | pub fn projection(&self) -> Matrix4<f32> {
    method inverse_projection (line 259) | pub fn inverse_projection(&self) -> Matrix4<f32> {
    method cull_point (line 265) | pub fn cull_point(&self, p: Vector3<f32>) -> bool {
  type FrameUniforms (line 279) | pub struct FrameUniforms {
  type EntityUniforms (line 291) | pub struct EntityUniforms {
  type EntityRenderer (line 299) | enum EntityRenderer {
  type WorldRenderer (line 307) | pub struct WorldRenderer {
    method new (line 316) | pub fn new(state: &GraphicsState, models: &[Model], worldmodel_id: usi...
    method update_uniform_buffers (line 372) | pub fn update_uniform_buffers<'a, I>(
    method render_pass (line 430) | pub fn render_pass<'a, E, P>(
    method renderer_for_entity (line 562) | fn renderer_for_entity(&self, ent: &ClientEntity) -> &EntityRenderer {
    method calculate_mvp_transform (line 567) | fn calculate_mvp_transform(&self, camera: &Camera, entity: &ClientEnti...
    method calculate_mv_transform (line 573) | fn calculate_mv_transform(&self, camera: &Camera, entity: &ClientEntit...
    method calculate_model_transform (line 579) | fn calculate_model_transform(&self, camera: &Camera, entity: &ClientEn...

FILE: src/client/render/world/particle.rs
  constant PARTICLE_TEXTURE_PIXELS (line 34) | const PARTICLE_TEXTURE_PIXELS: [u8; 64] = [
  type ParticlePipeline (line 45) | pub struct ParticlePipeline {
    method new (line 56) | pub fn new(
    method rebuild (line 145) | pub fn rebuild(
    method pipeline (line 155) | pub fn pipeline(&self) -> &wgpu::RenderPipeline {
    method bind_group_layouts (line 159) | pub fn bind_group_layouts(&self) -> &[wgpu::BindGroupLayout] {
    method vertex_buffer (line 163) | pub fn vertex_buffer(&self) -> &wgpu::Buffer {
    method record_draw (line 167) | pub fn record_draw<'a, 'b, P>(
  type VertexPushConstants (line 212) | pub struct VertexPushConstants {
  type FragmentPushConstants (line 217) | pub struct FragmentPushConstants {
  constant BIND_GROUP_LAYOUT_ENTRIES (line 221) | const BIND_GROUP_LAYOUT_ENTRIES: &[wgpu::BindGroupLayoutEntry] = &[
  type VertexPushConstants (line 262) | type VertexPushConstants = VertexPushConstants;
  type SharedPushConstants (line 263) | type SharedPushConstants = ();
  type FragmentPushConstants (line 264) | type FragmentPushConstants = FragmentPushConstants;
  method name (line 266) | fn name() -> &'static str {
  method vertex_shader (line 270) | fn vertex_shader() -> &'static str {
  method fragment_shader (line 277) | fn fragment_shader() -> &'static str {
  method bind_group_layout_descriptors (line 286) | fn bind_group_layout_descriptors() -> Vec<wgpu::BindGroupLayoutDescripto...
  method primitive_state (line 296) | fn primitive_state() -> wgpu::PrimitiveState {
  method color_target_states (line 300) | fn color_target_states() -> Vec<wgpu::ColorTargetState> {
  method depth_stencil_state (line 304) | fn depth_stencil_state() -> Option<wgpu::DepthStencilState> {
  method vertex_buffer_layouts (line 311) | fn vertex_buffer_layouts() -> Vec<wgpu::VertexBufferLayout<'static>> {
  type ParticleVertex (line 322) | pub struct ParticleVertex {
  constant VERTICES (line 327) | pub const VERTICES: [ParticleVertex; 6] = [
  type ParticleInstance (line 355) | pub struct ParticleInstance {

FILE: src/client/render/world/postprocess.rs
  type PostProcessUniforms (line 10) | pub struct PostProcessUniforms {
  type PostProcessPipeline (line 14) | pub struct PostProcessPipeline {
    method new (line 21) | pub fn new(
    method rebuild (line 46) | pub fn rebuild(
    method pipeline (line 57) | pub fn pipeline(&self) -> &wgpu::RenderPipeline {
    method bind_group_layouts (line 61) | pub fn bind_group_layouts(&self) -> &[wgpu::BindGroupLayout] {
    method uniform_buffer (line 65) | pub fn uniform_buffer(&self) -> &wgpu::Buffer {
  constant BIND_GROUP_LAYOUT_ENTRIES (line 70) | const BIND_GROUP_LAYOUT_ENTRIES: &[wgpu::BindGroupLayoutEntry] = &[
  type VertexPushConstants (line 106) | type VertexPushConstants = ();
  type SharedPushConstants (line 107) | type SharedPushConstants = ();
  type FragmentPushConstants (line 108) | type FragmentPushConstants = ();
  method name (line 110) | fn name() -> &'static str {
  method bind_group_layout_descriptors (line 114) | fn bind_group_layout_descriptors() -> Vec<wgpu::BindGroupLayoutDescripto...
  method vertex_shader (line 121) | fn vertex_shader() -> &'static str {
  method fragment_shader (line 128) | fn fragment_shader() -> &'static str {
  method primitive_state (line 135) | fn primitive_state() -> wgpu::PrimitiveState {
  method color_target_states (line 139) | fn color_target_states() -> Vec<wgpu::ColorTargetState> {
  method depth_stencil_state (line 143) | fn depth_stencil_state() -> Option<wgpu::DepthStencilState> {
  method vertex_buffer_layouts (line 147) | fn vertex_buffer_layouts() -> Vec<wgpu::VertexBufferLayout<'static>> {
  type PostProcessRenderer (line 152) | pub struct PostProcessRenderer {
    method create_bind_group (line 157) | pub fn create_bind_group(
    method new (line 191) | pub fn new(state: &GraphicsState, color_buffer: &wgpu::TextureView) ->...
    method rebuild (line 197) | pub fn rebuild(&mut self, state: &GraphicsState, color_buffer: &wgpu::...
    method update_uniform_buffers (line 201) | pub fn update_uniform_buffers(&self, state: &GraphicsState, color_shif...
    method record_draw (line 210) | pub fn record_draw<'pass>(

FILE: src/client/render/world/sprite.rs
  type SpritePipeline (line 16) | pub struct SpritePipeline {
    method new (line 23) | pub fn new(
    method rebuild (line 46) | pub fn rebuild(
    method pipeline (line 60) | pub fn pipeline(&self) -> &wgpu::RenderPipeline {
    method bind_group_layouts (line 64) | pub fn bind_group_layouts(&self) -> &[wgpu::BindGroupLayout] {
    method vertex_buffer (line 68) | pub fn vertex_buffer(&self) -> &wgpu::Buffer {
  type VertexPushConstants (line 86) | type VertexPushConstants = ();
  type SharedPushConstants (line 87) | type SharedPushConstants = ();
  type FragmentPushConstants (line 88) | type FragmentPushConstants = ();
  method name (line 90) | fn name() -> &'static str {
  method vertex_shader (line 94) | fn vertex_shader() -> &'static str {
  method fragment_shader (line 98) | fn fragment_shader() -> &'static str {
  method bind_group_layout_descriptors (line 104) | fn bind_group_layout_descriptors() -> Vec<wgpu::BindGroupLayoutDescripto...
  method primitive_state (line 126) | fn primitive_state() -> wgpu::PrimitiveState {
  method color_target_states (line 130) | fn color_target_states() -> Vec<wgpu::ColorTargetState> {
  method depth_stencil_state (line 134) | fn depth_stencil_state() -> Option<wgpu::DepthStencilState> {
  method vertex_buffer_layouts (line 139) | fn vertex_buffer_layouts() -> Vec<wgpu::VertexBufferLayout<'static>> {
  type Position (line 149) | type Position = [f32; 3];
  type Normal (line 150) | type Normal = [f32; 3];
  type DiffuseTexcoord (line 151) | type DiffuseTexcoord = [f32; 2];
  type SpriteVertex (line 155) | pub struct SpriteVertex {
  constant VERTICES (line 161) | pub const VERTICES: [SpriteVertex; 6] = [
  type Frame (line 194) | enum Frame {
    method new (line 210) | fn new(state: &GraphicsState, sframe: &SpriteFrame) -> Frame {
    method animate (line 279) | fn animate(&self, time: Duration) -> &wgpu::BindGroup {
  type SpriteRenderer (line 302) | pub struct SpriteRenderer {
    method new (line 308) | pub fn new(state: &GraphicsState, sprite: &SpriteModel) -> SpriteRende...
    method record_draw (line 321) | pub fn record_draw<'a>(
    method kind (line 338) | pub fn kind(&self) -> SpriteKind {

FILE: src/client/sound/mod.rs
  constant DISTANCE_ATTENUATION_FACTOR (line 39) | pub const DISTANCE_ATTENUATION_FACTOR: f32 = 0.001;
  constant MAX_ENTITY_CHANNELS (line 40) | const MAX_ENTITY_CHANNELS: usize = 128;
  type SoundError (line 43) | pub enum SoundError {
  type Listener (line 58) | pub struct Listener {
    method new (line 65) | pub fn new() -> Listener {
    method origin (line 73) | pub fn origin(&self) -> Vector3<f32> {
    method left_ear (line 77) | pub fn left_ear(&self) -> Vector3<f32> {
    method right_ear (line 81) | pub fn right_ear(&self) -> Vector3<f32> {
    method set_origin (line 85) | pub fn set_origin(&self, new_origin: Vector3<f32>) {
    method set_left_ear (line 89) | pub fn set_left_ear(&self, new_origin: Vector3<f32>) {
    method set_right_ear (line 93) | pub fn set_right_ear(&self, new_origin: Vector3<f32>) {
    method attenuate (line 97) | pub fn attenuate(
  type AudioSource (line 112) | pub struct AudioSource(Buffered<SamplesConverter<Decoder<Cursor<Vec<u8>>...
    method load (line 115) | pub fn load<S>(vfs: &Vfs, name: S) -> Result<AudioSource, SoundError>
  type StaticSound (line 133) | pub struct StaticSound {
    method new (line 141) | pub fn new(
    method update (line 163) | pub fn update(&self, listener: &Listener) {
  type Channel (line 171) | pub struct Channel {
    method new (line 180) | pub fn new(stream: OutputStreamHandle) -> Channel {
    method play (line 190) | pub fn play(
    method update (line 216) | pub fn update(&self, ent_pos: Vector3<f32>, listener: &Listener) {
    method stop (line 228) | pub fn stop(&self) {
    method in_use (line 233) | pub fn in_use(&self) -> bool {
  type EntityChannel (line 250) | pub struct EntityChannel {
    method channel (line 259) | pub fn channel(&self) -> &Channel {
    method entity_id (line 263) | pub fn entity_id(&self) -> Option<usize> {
  type EntityMixer (line 268) | pub struct EntityMixer {
    method new (line 275) | pub fn new(stream: OutputStreamHandle) -> EntityMixer {
    method find_free_channel (line 288) | fn find_free_channel(&self, ent_id: Option<usize>, ent_channel: i8) ->...
    method start_sound (line 328) | pub fn start_sound(
    method iter_entity_channels (line 357) | pub fn iter_entity_channels(&self) -> impl Iterator<Item = &EntityChan...
    method stream (line 361) | pub fn stream(&self) -> OutputStreamHandle {

FILE: src/client/sound/music.rs
  type MusicPlayer (line 11) | pub struct MusicPlayer {
    method new (line 19) | pub fn new(vfs: Rc<Vfs>, stream: OutputStreamHandle) -> MusicPlayer {
    method play_named (line 36) | pub fn play_named<S>(&mut self, name: S) -> Result<(), SoundError>
    method play_track (line 83) | pub fn play_track(&mut self, track_id: usize) -> Result<(), SoundError> {
    method stop (line 93) | pub fn stop(&mut self) {
    method pause (line 102) | pub fn pause(&mut self) {
    method resume (line 112) | pub fn resume(&mut self) {

FILE: src/client/state.rs
  constant CACHED_SOUND_NAMES (line 38) | const CACHED_SOUND_NAMES: &[&'static str] = &[
  type PlayerInfo (line 48) | pub struct PlayerInfo {
  type ClientState (line 56) | pub struct ClientState {
    method new (line 123) | pub fn new(stream: OutputStreamHandle) -> ClientState {
    method from_server_info (line 179) | pub fn from_server_info(
    method advance_time (line 237) | pub fn advance_time(&mut self, frame_time: Duration) {
    method update_interp_ratio (line 245) | pub fn update_interp_ratio(&mut self, cl_nolerp: f32) {
    method update_entities (line 307) | pub fn update_entities(&mut self) -> Result<(), ClientError> {
    method update_temp_entities (line 514) | pub fn update_temp_entities(&mut self) -> Result<(), ClientError> {
    method update_player (line 559) | pub fn update_player(&mut self, update: PlayerData) {
    method handle_input (line 607) | pub fn handle_input(
    method handle_damage (line 683) | pub fn handle_damage(
    method calc_final_view (line 724) | pub fn calc_final_view(
    method spawn_entities (line 752) | pub fn spawn_entities(&mut self, id: usize, baseline: EntityState) -> ...
    method update_entity (line 773) | pub fn update_entity(&mut self, id: usize, update: EntityUpdate) -> Re...
    method spawn_temp_entity (line 824) | pub fn spawn_temp_entity(&mut self, temp_entity: &TempEntity) {
    method spawn_beam (line 1002) | pub fn spawn_beam(
    method update_listener (line 1039) | pub fn update_listener(&self) {
    method update_sound_spatialization (line 1057) | pub fn update_sound_spatialization(&self) {
    method view_leaf_contents (line 1077) | fn view_leaf_contents(&self) -> Result<bsp::BspLeafContents, ClientErr...
    method update_color_shifts (line 1089) | pub fn update_color_shifts(&mut self, frame_time: Duration) -> Result<...
    method set_view_angles (line 1159) | pub fn set_view_angles(&mut self, angles: Vector3<Deg<f32>>) {
    method update_view_angles (line 1174) | pub fn update_view_angles(&mut self, angles: Vector3<Deg<f32>>) {
    method set_view_entity (line 1188) | pub fn set_view_entity(&mut self, entity_id: usize) -> Result<(), Clie...
    method models (line 1199) | pub fn models(&self) -> &[Model] {
    method viewmodel_id (line 1203) | pub fn viewmodel_id(&self) -> usize {
    method iter_visible_entities (line 1210) | pub fn iter_visible_entities(&self) -> impl Iterator<Item = &ClientEnt...
    method iter_particles (line 1218) | pub fn iter_particles(&self) -> impl Iterator<Item = &Particle> {
    method iter_lights (line 1222) | pub fn iter_lights(&self) -> impl Iterator<Item = &Light> {
    method time (line 1226) | pub fn time(&self) -> Duration {
    method view_entity_id (line 1230) | pub fn view_entity_id(&self) -> usize {
    method camera (line 1234) | pub fn camera(&self, aspect: f32, fov: Deg<f32>) -> Camera {
    method demo_camera (line 1243) | pub fn demo_camera(&self, aspect: f32, fov: Deg<f32>) -> Camera {
    method lightstyle_values (line 1257) | pub fn lightstyle_values(&self) -> Result<ArrayVec<f32, 64>, ClientErr...
    method intermission (line 1284) | pub fn intermission(&self) -> Option<&IntermissionKind> {
    method start_time (line 1288) | pub fn start_time(&self) -> Duration {
    method completion_time (line 1292) | pub fn completion_time(&self) -> Option<Duration> {
    method stats (line 1296) | pub fn stats(&self) -> &[i32] {
    method items (line 1300) | pub fn items(&self) -> ItemFlags {
    method item_pickup_times (line 1304) | pub fn item_pickup_times(&self) -> &[Duration] {
    method face_anim_time (line 1308) | pub fn face_anim_time(&self) -> Duration {
    method color_shift (line 1312) | pub fn color_shift(&self) -> [f32; 4] {
    method check_entity_id (line 1332) | pub fn check_entity_id(&self, id: usize) -> Result<(), ClientError> {
    method check_player_id (line 1340) | pub fn check_player_id(&self, id: usize) -> Result<(), ClientError> {

FILE: src/client/trace.rs
  type TraceEntity (line 28) | pub struct TraceEntity {
  type TraceFrame (line 35) | pub struct TraceFrame {

FILE: src/client/view.rs
  type View (line 15) | pub struct View {
    method new (line 48) | pub fn new() -> View {
    method entity_id (line 63) | pub fn entity_id(&self) -> usize {
    method set_entity_id (line 67) | pub fn set_entity_id(&mut self, id: usize) {
    method view_height (line 71) | pub fn view_height(&self) -> f32 {
    method set_view_height (line 75) | pub fn set_view_height(&mut self, view_height: f32) {
    method ideal_pitch (line 79) | pub fn ideal_pitch(&self) -> Deg<f32> {
    method set_ideal_pitch (line 83) | pub fn set_ideal_pitch(&mut self, ideal_pitch: Deg<f32>) {
    method punch_angles (line 87) | pub fn punch_angles(&self) -> Angles {
    method set_punch_angles (line 91) | pub fn set_punch_angles(&mut self, punch_angles: Angles) {
    method input_angles (line 95) | pub fn input_angles(&self) -> Angles {
    method update_input_angles (line 100) | pub fn update_input_angles(&mut self, input_angles: Angles) {
    method handle_input (line 104) | pub fn handle_input(
    method handle_damage (line 154) | pub fn handle_damage(
    method calc_final_angles (line 178) | pub fn calc_final_angles(
    method final_angles (line 206) | pub fn final_angles(&self) -> Angles {
    method calc_final_origin (line 210) | pub fn calc_final_origin(
    method final_origin (line 224) | pub fn final_origin(&self) -> Vector3<f32> {
    method viewmodel_angle (line 228) | pub fn viewmodel_angle(&self) -> Angles {
  type MouseVars (line 235) | pub struct MouseVars {
  type KickVars (line 242) | pub struct KickVars {
  type BobVars (line 249) | pub struct BobVars {
  function bob (line 255) | pub fn bob(time: Duration, velocity: Vector3<f32>, vars: BobVars) -> f32 {
  type RollVars (line 272) | pub struct RollVars {
  function roll (line 277) | pub fn roll(angles: Angles, velocity: Vector3<f32>, vars: RollVars) -> D...
  type IdleVars (line 293) | pub struct IdleVars {
  function idle (line 303) | pub fn idle(time: Duration, vars: IdleVars) -> Angles {

FILE: src/common/alloc.rs
  type LinkedSlab (line 12) | pub struct LinkedSlab<T> {
  function with_capacity (line 22) | pub fn with_capacity(capacity: usize) -> LinkedSlab<T> {
  function capacity (line 30) | pub fn capacity(&self) -> usize {
  function clear (line 35) | pub fn clear(&mut self) {
  function len (line 41) | pub fn len(&self) -> usize {
  function is_empty (line 46) | pub fn is_empty(&self) -> bool {
  function iter (line 51) | pub fn iter(&self) -> impl Iterator<Item = &T> {
  function get (line 60) | pub fn get(&self, key: usize) -> Option<&T> {
  function get_mut (line 67) | pub fn get_mut(&mut self, key: usize) -> Option<&mut T> {
  function insert (line 74) | pub fn insert(&mut self, val: T) -> usize {
  function remove (line 85) | pub fn remove(&mut self, key: usize) -> T {
  function contains (line 91) | pub fn contains(&self, key: usize) -> bool {
  function retain (line 100) | pub fn retain<F>(&mut self, mut f: F)
  function test_iter (line 136) | fn test_iter() {
  function test_retain (line 156) | fn test_retain() {

FILE: src/common/bitset.rs
  type BitSet (line 1) | pub struct BitSet<const N_64: usize> {
  function new (line 6) | pub fn new() -> Self {
  function all_set (line 10) | pub fn all_set() -> Self {
  function bit_location (line 17) | fn bit_location(bit: u64) -> (u64, u64) {
  function count (line 25) | pub fn count(&self) -> usize {
  function contains (line 36) | pub fn contains(&self, bit: u64) -> bool {
  function set (line 42) | pub fn set(&mut self, bit: u64) {
  function clear (line 48) | pub fn clear(&mut self, bit: u64) {
  function toggle (line 54) | pub fn toggle(&mut self, bit: u64) {
  function iter (line 60) | pub fn iter(&self) -> BitSetIter<'_, N_64> {
  type BitSetIter (line 65) | pub struct BitSetIter<'a, const N_64: usize> {
  function new (line 72) | fn new(blocks: &'a [u64; N_64]) -> BitSetIter<'_, N_64> {
  type Item (line 82) | type Item = u64;
  method next (line 84) | fn next(&mut self) -> Option<Self::Item> {
  function test_set_bit (line 116) | fn test_set_bit() {
  function test_clear_bit (line 128) | fn test_clear_bit() {
  function test_iter (line 140) | fn test_iter() {

FILE: src/common/bsp/load.rs
  constant VERSION (line 45) | const VERSION: i32 = 29;
  constant MAX_MODELS (line 47) | pub const MAX_MODELS: usize = 256;
  constant MAX_LEAVES (line 48) | const MAX_LEAVES: usize = 32767;
  constant MAX_ENTSTRING (line 50) | const MAX_ENTSTRING: usize = 65536;
  constant MAX_PLANES (line 51) | const MAX_PLANES: usize = 8192;
  constant MAX_RENDER_NODES (line 52) | const MAX_RENDER_NODES: usize = 32767;
  constant MAX_COLLISION_NODES (line 53) | const MAX_COLLISION_NODES: usize = 32767;
  constant MAX_VERTICES (line 54) | const MAX_VERTICES: usize = 65535;
  constant MAX_FACES (line 55) | const MAX_FACES: usize = 65535;
  constant _MAX_MARKTEXINFO (line 56) | const _MAX_MARKTEXINFO: usize = 65535;
  constant _MAX_TEXINFO (line 57) | const _MAX_TEXINFO: usize = 4096;
  constant MAX_EDGES (line 58) | const MAX_EDGES: usize = 256000;
  constant MAX_EDGELIST (line 59) | const MAX_EDGELIST: usize = 512000;
  constant MAX_TEXTURES (line 60) | const MAX_TEXTURES: usize = 0x200000;
  constant _MAX_LIGHTMAP (line 61) | const _MAX_LIGHTMAP: usize = 0x100000;
  constant MAX_VISLIST (line 62) | const MAX_VISLIST: usize = 0x100000;
  constant TEX_NAME_MAX (line 64) | const TEX_NAME_MAX: usize = 16;
  constant NUM_AMBIENTS (line 66) | const NUM_AMBIENTS: usize = 4;
  constant MAX_TEXTURE_FRAMES (line 67) | const MAX_TEXTURE_FRAMES: usize = 10;
  constant TEXTURE_FRAME_LEN_MS (line 68) | const TEXTURE_FRAME_LEN_MS: i64 = 200;
  constant ASCII_0 (line 70) | const ASCII_0: usize = '0' as usize;
  constant ASCII_9 (line 71) | const ASCII_9: usize = '9' as usize;
  constant ASCII_CAPITAL_A (line 72) | const ASCII_CAPITAL_A: usize = 'A' as usize;
  constant ASCII_CAPITAL_J (line 73) | const ASCII_CAPITAL_J: usize = 'J' as usize;
  constant ASCII_SMALL_A (line 74) | const ASCII_SMALL_A: usize = 'a' as usize;
  constant ASCII_SMALL_J (line 75) | const ASCII_SMALL_J: usize = 'j' as usize;
  type BspFileError (line 78) | pub enum BspFileError {
  type BspFileSection (line 102) | struct BspFileSection {
    method read_from (line 108) | fn read_from<R>(reader: &mut R) -> Result<BspFileSection, BspFileError>
  constant SECTION_COUNT (line 126) | const SECTION_COUNT: usize = 15;
  type BspFileSectionId (line 128) | pub enum BspFileSectionId {
    method element_size (line 160) | fn element_size(&self) -> usize {
  constant PLANE_SIZE (line 146) | const PLANE_SIZE: usize = 20;
  constant RENDER_NODE_SIZE (line 147) | const RENDER_NODE_SIZE: usize = 24;
  constant LEAF_SIZE (line 148) | const LEAF_SIZE: usize = 28;
  constant TEXTURE_INFO_SIZE (line 149) | const TEXTURE_INFO_SIZE: usize = 40;
  constant FACE_SIZE (line 150) | const FACE_SIZE: usize = 20;
  constant COLLISION_NODE_SIZE (line 151) | const COLLISION_NODE_SIZE: usize = 8;
  constant FACELIST_SIZE (line 152) | const FACELIST_SIZE: usize = 2;
  constant EDGE_SIZE (line 153) | const EDGE_SIZE: usize = 4;
  constant EDGELIST_SIZE (line 154) | const EDGELIST_SIZE: usize = 4;
  constant MODEL_SIZE (line 155) | const MODEL_SIZE: usize = 64;
  constant VERTEX_SIZE (line 156) | const VERTEX_SIZE: usize = 12;
  type BspFileTable (line 182) | struct BspFileTable {
    method read_from (line 187) | fn read_from<R>(reader: &mut R) -> Result<BspFileTable, BspFileError>
    method section (line 207) | fn section(&self, section_id: BspFileSectionId) -> BspFileSection {
    method check_end_position (line 211) | fn check_end_position<S>(
  function read_hyperplane (line 230) | fn read_hyperplane<R>(reader: &mut R) -> Result<Hyperplane, failure::Error>
  type BspFileTexture (line 249) | struct BspFileTexture {
  function load_texture (line 260) | fn load_texture<R>(
  function load_render_node (line 306) | fn load_render_node<R>(reader: &mut R) -> Result<BspRenderNode, failure:...
  function load_texinfo (line 351) | fn load_texinfo<R>(reader: &mut R, texture_count: usize) -> Result<BspTe...
  function load (line 384) | pub fn load<R>(data: R) -> Result<(Vec<Model>, String), failure::Error>
  function read_i16_3 (line 1112) | fn read_i16_3<R>(reader: &mut R) -> Result<[i16; 3], std::io::Error>

FILE: src/common/bsp/mod.rs
  constant MAX_HULLS (line 134) | const MAX_HULLS: usize = 3;
  constant MAX_LIGHTMAPS (line 136) | pub const MAX_LIGHTMAPS: usize = 64;
  constant MAX_LIGHTSTYLES (line 137) | pub const MAX_LIGHTSTYLES: usize = 4;
  constant MAX_SOUNDS (line 138) | pub const MAX_SOUNDS: usize = 4;
  constant MIPLEVELS (line 139) | pub const MIPLEVELS: usize = 4;
  constant DIST_EPSILON (line 140) | const DIST_EPSILON: f32 = 0.03125;
  function frame_duration (line 142) | pub fn frame_duration() -> Duration {
  type BspError (line 147) | pub enum BspError {
    method with_msg (line 153) | fn with_msg<S>(msg: S) -> Self
    method fmt (line 162) | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    method from (line 180) | fn from(error: ::std::io::Error) -> Self {
  method description (line 171) | fn description(&self) -> &str {
  type BspTextureMipmap (line 186) | pub enum BspTextureMipmap {
  type BspTextureFrame (line 194) | pub struct BspTextureFrame {
    method mipmap (line 199) | pub fn mipmap(&self, level: BspTextureMipmap) -> &[u8] {
  type BspTextureKind (line 205) | pub enum BspTextureKind {
  type BspTexture (line 214) | pub struct BspTexture {
    method name (line 223) | pub fn name(&self) -> &str {
    method width (line 227) | pub fn width(&self) -> u32 {
    method height (line 231) | pub fn height(&self) -> u32 {
    method dimensions (line 236) | pub fn dimensions(&self) -> (u32, u32) {
    method kind (line 241) | pub fn kind(&self) -> &BspTextureKind {
  type BspRenderNodeChild (line 247) | pub enum BspRenderNodeChild {
  type BspRenderNode (line 253) | pub struct BspRenderNode {
  type BspTexInfo (line 263) | pub struct BspTexInfo {
  type BspFaceSide (line 273) | pub enum BspFaceSide {
  type BspFace (line 279) | pub struct BspFace {
  type BspLeafContents (line 294) | pub enum BspLeafContents {
  type BspCollisionNodeChild (line 347) | pub enum BspCollisionNodeChild {
  type BspCollisionNode (line 353) | pub struct BspCollisionNode {
  type BspCollisionHull (line 359) | pub struct BspCollisionHull {
    method for_bounds (line 374) | pub fn for_bounds(
    method min (line 460) | pub fn min(&self) -> Vector3<f32> {
    method max (line 464) | pub fn max(&self) -> Vector3<f32> {
    method contents_at_point (line 469) | pub fn contents_at_point(&self, point: Vector3<f32>) -> Result<BspLeaf...
    method contents_at_point_node (line 473) | fn contents_at_point_node(
    method trace (line 490) | pub fn trace(&self, start: Vector3<f32>, end: Vector3<f32>) -> Result<...
    method recursive_trace (line 494) | fn recursive_trace(
    method gen_dot_graph (line 584) | pub fn gen_dot_graph(&self) -> String {
    method gen_dot_graph_recursive (line 618) | fn gen_dot_graph_recursive(
  type BspLeaf (line 653) | pub struct BspLeaf {
  type BspEdge (line 664) | pub struct BspEdge {
  type BspEdgeDirection (line 669) | pub enum BspEdgeDirection {
  type BspEdgeIndex (line 675) | pub struct BspEdgeIndex {
  type BspLightmap (line 681) | pub struct BspLightmap<'a> {
  function width (line 688) | pub fn width(&self) -> u32 {
  function height (line 692) | pub fn height(&self) -> u32 {
  function data (line 696) | pub fn data(&self) -> &[u8] {
  type BspData (line 702) | pub struct BspData {
    method planes (line 719) | pub fn planes(&self) -> &[Hyperplane] {
    method textures (line 723) | pub fn textures(&self) -> &[BspTexture] {
    method vertices (line 727) | pub fn vertices(&self) -> &[Vector3<f32>] {
    method render_nodes (line 731) | pub fn render_nodes(&self) -> &[BspRenderNode] {
    method texinfo (line 735) | pub fn texinfo(&self) -> &[BspTexInfo] {
    method face (line 739) | pub fn face(&self, face_id: usize) -> &BspFace {
    method face_iter_vertices (line 743) | pub fn face_iter_vertices(&self, face_id: usize) -> impl Iterator<Item...
    method face_texinfo (line 752) | pub fn face_texinfo(&self, face_id: usize) -> &BspTexInfo {
    method face_lightmaps (line 756) | pub fn face_lightmaps(&self, face_id: usize) -> Vec<BspLightmap> {
    method faces (line 783) | pub fn faces(&self) -> &[BspFace] {
    method lightmaps (line 787) | pub fn lightmaps(&self) -> &[u8] {
    method leaves (line 791) | pub fn leaves(&self) -> &[BspLeaf] {
    method facelist (line 795) | pub fn facelist(&self) -> &[usize] {
    method edges (line 799) | pub fn edges(&self) -> &[BspEdge] {
    method edgelist (line 803) | pub fn edgelist(&self) -> &[BspEdgeIndex] {
    method hulls (line 807) | pub fn hulls(&self) -> &[BspCollisionHull] {
    method find_leaf (line 812) | pub fn find_leaf<V>(&self, pos: V) -> usize
    method get_pvs (line 831) | pub fn get_pvs(&self, leaf_id: usize, leaf_count: usize) -> Vec<usize> {
    method gen_dot_graph (line 868) | pub fn gen_dot_graph(&self) -> String {
    method gen_dot_graph_recursive (line 901) | fn gen_dot_graph_recursive(
  type BspModel (line 939) | pub struct BspModel {
    method bsp_data (line 953) | pub fn bsp_data(&self) -> Rc<BspData> {
    method min (line 958) | pub fn min(&self) -> Vector3<f32> {
    method max (line 963) | pub fn max(&self) -> Vector3<f32> {
    method size (line 968) | pub fn size(&self) -> Vector3<f32> {
    method origin (line 973) | pub fn origin(&self) -> Vector3<f32> {
    method iter_leaves (line 977) | pub fn iter_leaves(&self) -> impl Iterator<Item = &BspLeaf> {
    method iter_faces (line 982) | pub fn iter_faces(&self) -> impl Iterator<Item = &BspFace> {
    method face_list (line 988) | pub fn face_list(&self) -> &[usize] {
    method hull (line 992) | pub fn hull(&self, index: usize) -> Result<BspCollisionHull, BspError> {
  function test_hull_for_bounds (line 1020) | fn test_hull_for_bounds() {

FILE: src/common/console/mod.rs
  type ConsoleError (line 35) | pub enum ConsoleError {
  type Cmd (line 50) | type Cmd = Box<dyn Fn(&[&str]) -> String>;
  function insert_name (line 52) | fn insert_name<S>(names: &mut Vec<String>, name: S) -> Result<usize, usize>
  type CmdRegistry (line 67) | pub struct CmdRegistry {
    method new (line 73) | pub fn new(names: Rc<RefCell<Vec<String>>>) -> CmdRegistry {
    method insert (line 83) | pub fn insert<S>(&mut self, name: S, cmd: Cmd) -> Result<(), ConsoleEr...
    method insert_or_replace (line 104) | pub fn insert_or_replace<S>(&mut self, name: S, cmd: Cmd) -> Result<()...
    method remove (line 125) | pub fn remove<S>(&mut self, name: S) -> Result<(), ConsoleError>
    method exec (line 145) | pub fn exec<S>(&mut self, name: S, args: &[&str]) -> Result<String, Co...
    method contains (line 157) | pub fn contains<S>(&self, name: S) -> bool
    method names (line 164) | pub fn names(&self) -> Rc<RefCell<Vec<String>>> {
  type Cvar (line 173) | struct Cvar {
  type CvarRegistry (line 190) | pub struct CvarRegistry {
    method new (line 197) | pub fn new(names: Rc<RefCell<Vec<String>>>) -> CvarRegistry {
    method register_impl (line 204) | fn register_impl<S>(
    method register (line 241) | pub fn register<S>(&self, name: S, default: S) -> Result<(), ConsoleEr...
    method register_archive (line 252) | pub fn register_archive<S>(&self, name: S, default: S) -> Result<(), C...
    method register_notify (line 264) | pub fn register_notify<S>(&self, name: S, default: S) -> Result<(), Co...
    method register_archive_notify (line 279) | pub fn register_archive_notify<S>(&mut self, name: S, default: S) -> R...
    method get (line 286) | pub fn get<S>(&self, name: S) -> Result<String, ConsoleError>
    method get_value (line 299) | pub fn get_value<S>(&self, name: S) -> Result<f32, ConsoleError>
    method set (line 327) | pub fn set<S>(&self, name: S, value: S) -> Result<(), ConsoleError>
    method contains (line 345) | pub fn contains<S>(&self, name: S) -> bool
  type ConsoleInput (line 354) | pub struct ConsoleInput {
    method new (line 363) | pub fn new() -> ConsoleInput {
    method get_text (line 371) | pub fn get_text(&self) -> Vec<char> {
    method set_text (line 378) | pub fn set_text(&mut self, text: &Vec<char>) {
    method insert (line 386) | pub fn insert(&mut self, c: char) {
    method cursor_right (line 394) | pub fn cursor_right(&mut self) {
    method cursor_left (line 403) | pub fn cursor_left(&mut self) {
    method delete (line 412) | pub fn delete(&mut self) {
    method backspace (line 421) | pub fn backspace(&mut self) {
    method clear (line 431) | pub fn clear(&mut self) {
  type History (line 437) | pub struct History {
    method new (line 443) | pub fn new() -> History {
    method add_line (line 450) | pub fn add_line(&mut self, line: Vec<char>) {
    method line_up (line 456) | pub fn line_up(&mut self) -> Option<Vec<char>> {
    method line_down (line 465) | pub fn line_down(&mut self) -> Option<Vec<char>> {
  type ConsoleOutput (line 478) | pub struct ConsoleOutput {
    method new (line 489) | pub fn new() -> ConsoleOutput {
    method push (line 495) | fn push<C>(&mut self, chars: C, timestamp: Option<i64>)
    method lines (line 504) | pub fn lines(&self) -> impl Iterator<Item = &[char]> {
    method recent_lines (line 516) | pub fn recent_lines(
  type Console (line 537) | pub struct Console {
    method new (line 551) | pub fn new(cmds: Rc<RefCell<CmdRegistry>>, cvars: Rc<RefCell<CvarRegis...
    method print_impl (line 641) | fn print_impl<S>(&self, s: S, timestamp: Option<i64>)
    method print (line 661) | pub fn print<S>(&self, s: S)
    method print_alert (line 668) | pub fn print_alert<S>(&self, s: S)
    method println (line 675) | pub fn println<S>(&self, s: S)
    method println_alert (line 683) | pub fn println_alert<S>(&self, s: S)
    method send_char (line 692) | pub fn send_char(&mut self, c: char) {
    method cursor (line 725) | pub fn cursor(&self) -> usize {
    method cursor_right (line 729) | pub fn cursor_right(&mut self) {
    method cursor_left (line 733) | pub fn cursor_left(&mut self) {
    method history_up (line 737) | pub fn history_up(&mut self) {
    method history_down (line 743) | pub fn history_down(&mut self) {
    method execute (line 750) | pub fn execute(&self) {
    method get_string (line 804) | pub fn get_string(&self) -> String {
    method stuff_text (line 808) | pub fn stuff_text<S>(&self, text: S)
    method output (line 819) | pub fn output(&self) -> Ref<ConsoleOutput> {

FILE: src/common/engine.rs
  function indexed_to_rgba (line 37) | pub fn indexed_to_rgba(indices: &[u8]) -> Vec<u8> {
  function duration_to_f32 (line 56) | pub fn duration_to_f32(d: Duration) -> f32 {
  function duration_from_f32 (line 61) | pub fn duration_from_f32(f: f32) -> Duration {
  function deg_vector_to_f32_vector (line 66) | pub fn deg_vector_to_f32_vector(av: Vector3<Deg<f32>>) -> Vector3<f32> {
  function deg_vector_from_f32_vector (line 71) | pub fn deg_vector_from_f32_vector(v: Vector3<f32>) -> Vector3<Deg<f32>> {

FILE: src/common/host.rs
  type Program (line 31) | pub trait Program: Sized {
    method handle_event (line 32) | fn handle_event<T>(
    method frame (line 39) | fn frame(&mut self, frame_duration: Duration);
    method shutdown (line 40) | fn shutdown(&mut self);
    method cvars (line 41) | fn cvars(&self) -> Ref<CvarRegistry>;
    method cvars_mut (line 42) | fn cvars_mut(&self) -> RefMut<CvarRegistry>;
  type Host (line 45) | pub struct Host<P>
  function new (line 60) | pub fn new(program: P) -> Host<P> {
  function handle_event (line 75) | pub fn handle_event<T>(
  function frame (line 102) | pub fn frame(&mut self) {
  function check_frame_duration (line 122) | fn check_frame_duration(&mut self, frame_duration: Duration) -> bool {
  function uptime (line 132) | pub fn uptime(&self) -> Duration {

FILE: src/common/math.rs
  type CoordSys (line 25) | trait CoordSys {}
  type Quake (line 27) | struct Quake;
  type Wgpu (line 30) | struct Wgpu;
  constant VERTEX_NORMAL_COUNT (line 33) | pub const VERTEX_NORMAL_COUNT: usize = 162;
  type Angles (line 203) | pub struct Angles {
    method zero (line 210) | pub fn zero() -> Angles {
    method mat3_quake (line 218) | pub fn mat3_quake(&self) -> Matrix3<f32> {
    method mat4_quake (line 224) | pub fn mat4_quake(&self) -> Matrix4<f32> {
    method mat3_wgpu (line 230) | pub fn mat3_wgpu(&self) -> Matrix3<f32> {
    method mat4_wgpu (line 236) | pub fn mat4_wgpu(&self) -> Matrix4<f32> {
    type Output (line 244) | type Output = Self;
    method add (line 246) | fn add(self, other: Self) -> Self {
    type Output (line 256) | type Output = Self;
    method mul (line 258) | fn mul(self, other: f32) -> Self {
  function clamp_deg (line 267) | pub fn clamp_deg(val: Deg<f32>, min: Deg<f32>, max: Deg<f32>) -> Deg<f32> {
  type HyperplaneSide (line 280) | pub enum HyperplaneSide {
    method from_dist (line 298) | pub fn from_dist(dist: f32) -> HyperplaneSide {
  type Output (line 286) | type Output = HyperplaneSide;
  method neg (line 288) | fn neg(self) -> Self::Output {
  type PointIntersection (line 309) | pub struct PointIntersection {
    method ratio (line 321) | pub fn ratio(&self) -> f32 {
    method point (line 325) | pub fn point(&self) -> Vector3<f32> {
    method plane (line 329) | pub fn plane(&self) -> &Hyperplane {
  type LinePlaneIntersect (line 340) | pub enum LinePlaneIntersect {
  type Axis (line 349) | pub enum Axis {
  type Alignment (line 356) | enum Alignment {
  type Hyperplane (line 362) | pub struct Hyperplane {
    method new (line 389) | pub fn new(normal: Vector3<f32>, dist: f32) -> Hyperplane {
    method axis_x (line 401) | pub fn axis_x(dist: f32) -> Hyperplane {
    method axis_y (line 411) | pub fn axis_y(dist: f32) -> Hyperplane {
    method axis_z (line 421) | pub fn axis_z(dist: f32) -> Hyperplane {
    method from_normal (line 432) | pub fn from_normal(normal: Vector3<f32>, dist: f32) -> Hyperplane {
    method normal (line 440) | pub fn normal(&self) -> Vector3<f32> {
    method point_dist (line 452) | pub fn point_dist(&self, point: Vector3<f32>) -> f32 {
    method point_side (line 462) | pub fn point_side(&self, point: Vector3<f32>) -> HyperplaneSide {
    method line_segment_intersection (line 475) | pub fn line_segment_intersection(
  type Output (line 368) | type Output = Self;
  method neg (line 370) | fn neg(self) -> Self::Output {
  function fov_x_to_fov_y (line 514) | pub fn fov_x_to_fov_y(fov_x: Deg<f32>, aspect: f32) -> Option<Deg<f32>> {
  constant COLLINEAR_EPSILON (line 528) | const COLLINEAR_EPSILON: f32 = 0.001;
  function collinear (line 541) | pub fn collinear(vs: &[Vector3<f32>]) -> bool {
  function remove_collinear (line 562) | pub fn remove_collinear(vs: Vec<Vector3<f32>>) -> Vec<Vector3<f32>> {
  function bounds (line 590) | pub fn bounds<'a, I>(points: I) -> (Vector3<f32>, Vector3<f32>)
  function vec2_extend_n (line 605) | pub fn vec2_extend_n(v: Vector2<f32>, n: usize, val: f32) -> Vector3<f32> {
  function vec3_truncate_n (line 618) | pub fn vec3_truncate_n(v: Vector3<f32>, n: usize) -> Vector2<f32> {
  function test_hyperplane_side_x (line 635) | fn test_hyperplane_side_x() {
  function test_hyperplane_side_y (line 648) | fn test_hyperplane_side_y() {
  function test_hyperplane_side_z (line 661) | fn test_hyperplane_side_z() {
  function test_hyperplane_side_arbitrary (line 674) | fn test_hyperplane_side_arbitrary() {
  function test_hyperplane_point_dist_x (line 705) | fn test_hyperplane_point_dist_x() {
  function test_hyperplane_point_dist_y (line 712) | fn test_hyperplane_point_dist_y() {
  function test_hyperplane_point_dist_z (line 719) | fn test_hyperplane_point_dist_z() {
  function test_hyperplane_point_dist_x_no_axis (line 726) | fn test_hyperplane_point_dist_x_no_axis() {
  function test_hyperplane_point_dist_y_no_axis (line 733) | fn test_hyperplane_point_dist_y_no_axis() {
  function test_hyperplane_point_dist_z_no_axis (line 740) | fn test_hyperplane_point_dist_z_no_axis() {
  function test_hyperplane_line_segment_intersection_x (line 747) | fn test_hyperplane_line_segment_intersection_x() {
  function test_hyperplane_line_segment_intersection_y (line 762) | fn test_hyperplane_line_segment_intersection_y() {
  function test_hyperplane_line_segment_intersection_z (line 777) | fn test_hyperplane_line_segment_intersection_z() {
  function test_collinear (line 792) | fn test_collinear() {
  function test_remove_collinear (line 826) | fn test_remove_collinear() {

FILE: src/common/mdl.rs
  constant MAGIC (line 32) | pub const MAGIC: i32 =
  constant VERSION (line 34) | pub const VERSION: i32 = 6;
  constant HEADER_SIZE (line 36) | const HEADER_SIZE: u64 = 84;
  type MdlFileError (line 39) | pub enum MdlFileError {
  type StaticTexture (line 73) | pub struct StaticTexture {
    method indices (line 79) | pub fn indices(&self) -> &[u8] {
  type AnimatedTextureFrame (line 85) | pub struct AnimatedTextureFrame {
    method duration (line 92) | pub fn duration(&self) -> Duration {
    method indices (line 97) | pub fn indices(&self) -> &[u8] {
  type AnimatedTexture (line 103) | pub struct AnimatedTexture {
    method frames (line 108) | pub fn frames(&self) -> &[AnimatedTextureFrame] {
  type Texture (line 114) | pub enum Texture {
  type Texcoord (line 120) | pub struct Texcoord {
    method is_on_seam (line 127) | pub fn is_on_seam(&self) -> bool {
    method s (line 131) | pub fn s(&self) -> u32 {
    method t (line 135) | pub fn t(&self) -> u32 {
  type IndexedPolygon (line 141) | pub struct IndexedPolygon {
    method faces_front (line 147) | pub fn faces_front(&self) -> bool {
    method indices (line 151) | pub fn indices(&self) -> &[u32; 3] {
  type StaticKeyframe (line 157) | pub struct StaticKeyframe {
    method name (line 166) | pub fn name(&self) -> &str {
    method min (line 171) | pub fn min(&self) -> Vector3<f32> {
    method max (line 176) | pub fn max(&self) -> Vector3<f32> {
    method vertices (line 181) | pub fn vertices(&self) -> &[Vector3<f32>] {
  type AnimatedKeyframeFrame (line 187) | pub struct AnimatedKeyframeFrame {
    method name (line 197) | pub fn name(&self) -> &str {
    method min (line 202) | pub fn min(&self) -> Vector3<f32> {
    method max (line 207) | pub fn max(&self) -> Vector3<f32> {
    method duration (line 212) | pub fn duration(&self) -> Duration {
    method vertices (line 217) | pub fn vertices(&self) -> &[Vector3<f32>] {
  type AnimatedKeyframe (line 223) | pub struct AnimatedKeyframe {
    method min (line 231) | pub fn min(&self) -> Vector3<f32> {
    method max (line 236) | pub fn max(&self) -> Vector3<f32> {
    method frames (line 241) | pub fn frames(&self) -> &[AnimatedKeyframeFrame] {
  type Keyframe (line 247) | pub enum Keyframe {
  type AliasModel (line 253) | pub struct AliasModel {
    method origin (line 266) | pub fn origin(&self) -> Vector3<f32> {
    method radius (line 270) | pub fn radius(&self) -> f32 {
    method texture_width (line 274) | pub fn texture_width(&self) -> u32 {
    method texture_height (line 278) | pub fn texture_height(&self) -> u32 {
    method textures (line 282) | pub fn textures(&self) -> &[Texture] {
    method texcoords (line 286) | pub fn texcoords(&self) -> &[Texcoord] {
    method polygons (line 290) | pub fn polygons(&self) -> &[IndexedPolygon] {
    method keyframes (line 294) | pub fn keyframes(&self) -> &[Keyframe] {
    method flags (line 298) | pub fn flags(&self) -> ModelFlags {
  function load (line 303) | pub fn load<R>(data: R) -> Result<AliasModel, MdlFileError>
  function read_vertex (line 588) | fn read_vertex<R>(

FILE: src/common/mod.rs
  function default_base_dir (line 40) | pub fn default_base_dir() -> std::path::PathBuf {
  constant MAX_LIGHTSTYLES (line 50) | pub const MAX_LIGHTSTYLES: usize = 64;
  constant MAX_PAKFILES (line 55) | pub const MAX_PAKFILES: usize = 32;

FILE: src/common/model.rs
  type ModelError (line 29) | pub enum ModelError {
  type SyncType (line 41) | pub enum SyncType {
  type Model (line 60) | pub struct Model {
    method none (line 76) | pub fn none() -> Model {
    method kind (line 84) | pub fn kind(&self) -> &ModelKind {
    method load (line 88) | pub fn load<S>(vfs: &Vfs, name: S) -> Result<Model, ModelError>
    method from_brush_model (line 112) | pub fn from_brush_model<S>(name: S, brush_model: BspModel) -> Model
    method from_alias_model (line 124) | pub fn from_alias_model<S>(name: S, alias_model: AliasModel) -> Model
    method from_sprite_model (line 138) | pub fn from_sprite_model<S>(name: S, sprite_model: SpriteModel) -> Model
    method name (line 150) | pub fn name(&self) -> &str {
    method min (line 155) | pub fn min(&self) -> Vector3<f32> {
    method max (line 169) | pub fn max(&self) -> Vector3<f32> {
    method sync_type (line 182) | pub fn sync_type(&self) -> SyncType {
    method flags (line 193) | pub fn flags(&self) -> ModelFlags {
    method has_flag (line 197) | pub fn has_flag(&self, flag: ModelFlags) -> bool {
  type ModelKind (line 67) | pub enum ModelKind {

FILE: src/common/net/connect.rs
  constant CONNECT_PROTOCOL_VERSION (line 36) | pub const CONNECT_PROTOCOL_VERSION: u8 = 3;
  constant CONNECT_CONTROL (line 37) | const CONNECT_CONTROL: i32 = 1 << 31;
  constant CONNECT_LENGTH_MASK (line 38) | const CONNECT_LENGTH_MASK: i32 = 0x0000FFFF;
  type ConnectPacket (line 40) | pub trait ConnectPacket {
    method code (line 42) | fn code(&self) -> u8;
    method content_len (line 45) | fn content_len(&self) -> usize;
    method write_content (line 48) | fn write_content<W>(&self, writer: &mut W) -> Result<(), NetError>
    method packet_len (line 53) | fn packet_len(&self) -> i32 {
    method control_header (line 68) | fn control_header(&self) -> i32 {
    method to_bytes (line 73) | fn to_bytes(&self) -> Result<Vec<u8>, NetError> {
    method code (line 98) | fn code(&self) -> u8 {
    method content_len (line 102) | fn content_len(&self) -> usize {
    method write_content (line 114) | fn write_content<W>(&self, writer: &mut W) -> Result<(), NetError>
    method code (line 131) | fn code(&self) -> u8 {
    method content_len (line 135) | fn content_len(&self) -> usize {
    method write_content (line 140) | fn write_content<W>(&self, writer: &mut W) -> Result<(), NetError>
    method code (line 156) | fn code(&self) -> u8 {
    method content_len (line 160) | fn content_len(&self) -> usize {
    method write_content (line 165) | fn write_content<W>(&self, writer: &mut W) -> Result<(), NetError>
    method code (line 180) | fn code(&self) -> u8 {
    method content_len (line 184) | fn content_len(&self) -> usize {
    method write_content (line 189) | fn write_content<W>(&self, writer: &mut W) -> Result<(), NetError>
    method code (line 243) | fn code(&self) -> u8 {
    method content_len (line 253) | fn content_len(&self) -> usize {
    method write_content (line 263) | fn write_content<W>(&self, writer: &mut W) -> Result<(), NetError>
    method code (line 292) | fn code(&self) -> u8 {
    method content_len (line 296) | fn content_len(&self) -> usize {
    method write_content (line 301) | fn write_content<W>(&self, writer: &mut W) -> Result<(), NetError>
    method code (line 316) | fn code(&self) -> u8 {
    method content_len (line 320) | fn content_len(&self) -> usize {
    method write_content (line 325) | fn write_content<W>(&self, writer: &mut W) -> Result<(), NetError>
    method code (line 346) | fn code(&self) -> u8 {
    method content_len (line 350) | fn content_len(&self) -> usize {
    method write_content (line 374) | fn write_content<W>(&self, writer: &mut W) -> Result<(), NetError>
    method code (line 402) | fn code(&self) -> u8 {
    method content_len (line 406) | fn content_len(&self) -> usize {
    method write_content (line 430) | fn write_content<W>(&self, writer: &mut W) -> Result<(), NetError>
    method code (line 453) | fn code(&self) -> u8 {
    method content_len (line 457) | fn content_len(&self) -> usize {
    method write_content (line 469) | fn write_content<W>(&self, writer: &mut W) -> Result<(), NetError>
    method code (line 491) | fn code(&self) -> u8 {
    method content_len (line 502) | fn content_len(&self) -> usize {
    method write_content (line 513) | fn write_content<W>(&self, writer: &mut W) -> Result<(), NetError>
  type RequestCode (line 84) | pub enum RequestCode {
  type RequestConnect (line 92) | pub struct RequestConnect {
  type RequestServerInfo (line 126) | pub struct RequestServerInfo {
  type RequestPlayerInfo (line 151) | pub struct RequestPlayerInfo {
  type RequestRuleInfo (line 175) | pub struct RequestRuleInfo {
  type Request (line 201) | pub enum Request {
    method connect (line 209) | pub fn connect<S>(game_name: S, proto_ver: u8) -> Request
    method server_info (line 219) | pub fn server_info<S>(game_name: S) -> Request
    method player_info (line 228) | pub fn player_info(player_id: u8) -> Request {
    method rule_info (line 232) | pub fn rule_info<S>(prev_cvar: S) -> Request
  type ResponseCode (line 278) | pub enum ResponseCode {
  type ResponseAccept (line 287) | pub struct ResponseAccept {
  type ResponseReject (line 311) | pub struct ResponseReject {
  type ResponseServerInfo (line 336) | pub struct ResponseServerInfo {
  type ResponsePlayerInfo (line 392) | pub struct ResponsePlayerInfo {
  type ResponseRuleInfo (line 447) | pub struct ResponseRuleInfo {
  type Response (line 482) | pub enum Response {
  type ConnectListener (line 529) | pub struct ConnectListener {
    method bind (line 535) | pub fn bind<A>(addr: A) -> Result<ConnectListener, NetError>
    method recv_request (line 545) | pub fn recv_request(&self) -> Result<(Request, SocketAddr), NetError> {
    method send_response (line 617) | pub fn send_response(&self, response: Response, remote: SocketAddr) ->...
  type ConnectSocket (line 623) | pub struct ConnectSocket {
    method bind (line 628) | pub fn bind<A>(local: A) -> Result<ConnectSocket, NetError>
    method into_qsocket (line 637) | pub fn into_qsocket(self, remote: SocketAddr) -> QSocket {
    method send_request (line 642) | pub fn send_request(&mut self, request: Request, remote: SocketAddr) -...
    method recv_response (line 651) | pub fn recv_response(
  function test_request_connect_packet_len (line 752) | fn test_request_connect_packet_len() {
  function test_request_server_info_packet_len (line 764) | fn test_request_server_info_packet_len() {
  function test_request_player_info_packet_len (line 774) | fn test_request_player_info_packet_len() {
  function test_request_rule_info_packet_len (line 782) | fn test_request_rule_info_packet_len() {
  function test_response_accept_packet_len (line 792) | fn test_response_accept_packet_len() {
  function test_response_reject_packet_len (line 800) | fn test_response_reject_packet_len() {
  function test_response_server_info_packet_len (line 810) | fn test_response_server_info_packet_len() {
  function test_response_player_info_packet_len (line 825) | fn test_response_player_info_packet_len() {
  function test_connect_listener_bind (line 840) | fn test_connect_listener_bind() {

FILE: src/common/net/mod.rs
  constant MAX_MESSAGE (line 40) | pub const MAX_MESSAGE: usize = 8192;
  constant MAX_DATAGRAM (line 41) | const MAX_DATAGRAM: usize = 1024;
  constant HEADER_SIZE (line 42) | const HEADER_SIZE: usize = 8;
  constant MAX_PACKET (line 43) | const MAX_PACKET: usize = HEADER_SIZE + MAX_DATAGRAM;
  constant PROTOCOL_VERSION (line 45) | pub const PROTOCOL_VERSION: u8 = 15;
  constant NAME_LEN (line 47) | const NAME_LEN: usize = 64;
  constant FAST_UPDATE_FLAG (line 49) | const FAST_UPDATE_FLAG: u8 = 0x80;
  constant VELOCITY_READ_FACTOR (line 51) | const VELOCITY_READ_FACTOR: f32 = 16.0;
  constant VELOCITY_WRITE_FACTOR (line 52) | const VELOCITY_WRITE_FACTOR: f32 = 1.0 / VELOCITY_READ_FACTOR;
  constant PARTICLE_DIRECTION_READ_FACTOR (line 54) | const PARTICLE_DIRECTION_READ_FACTOR: f32 = 1.0 / 16.0;
  constant PARTICLE_DIRECTION_WRITE_FACTOR (line 55) | const PARTICLE_DIRECTION_WRITE_FACTOR: f32 = 1.0 / PARTICLE_DIRECTION_RE...
  constant SOUND_ATTENUATION_WRITE_FACTOR (line 57) | const SOUND_ATTENUATION_WRITE_FACTOR: u8 = 64;
  constant SOUND_ATTENUATION_READ_FACTOR (line 58) | const SOUND_ATTENUATION_READ_FACTOR: f32 = 1.0 / SOUND_ATTENUATION_WRITE...
  constant MAX_CLIENTS (line 61) | pub const MAX_CLIENTS: usize = 16;
  constant MAX_ITEMS (line 62) | pub const MAX_ITEMS: usize = 32;
  constant DEFAULT_VIEWHEIGHT (line 64) | pub const DEFAULT_VIEWHEIGHT: f32 = 22.0;
  type NetError (line 67) | pub enum NetError {
    method with_msg (line 74) | pub fn with_msg<S>(msg: S) -> Self
    method fmt (line 83) | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    method from (line 106) | fn from(error: ::std::io::Error) -> Self {
  method description (line 96) | fn description(&self) -> &str {
  type MsgKind (line 114) | pub enum MsgKind {
  type PlayerColor (line 210) | pub struct PlayerColor {
    method new (line 216) | pub fn new(top: u8, bottom: u8) -> PlayerColor {
    method from_bits (line 228) | pub fn from_bits(bits: u8) -> PlayerColor {
    method bits (line 235) | pub fn bits(&self) -> u8 {
    method from (line 241) | fn from(src: u8) -> PlayerColor {
  type ColorShift (line 250) | pub struct ColorShift {
  type ClientStat (line 256) | pub enum ClientStat {
  type TempEntityCode (line 276) | pub enum TempEntityCode {
  type PointEntityKind (line 294) | pub enum PointEntityKind {
  type BeamEntityKind (line 308) | pub enum BeamEntityKind {
  type TempEntity (line 319) | pub enum TempEntity {
    method read_temp_entity (line 333) | pub fn read_temp_entity<R>(reader: &mut R) -> Result<TempEntity, NetEr...
    method write_temp_entity (line 410) | pub fn write_temp_entity<W>(&self, writer: &mut W) -> Result<(), NetEr...
  type SignOnStage (line 487) | pub enum SignOnStage {
  type EntityState (line 505) | pub struct EntityState {
    method uninitialized (line 518) | pub fn uninitialized() -> EntityState {
  type EntityUpdate (line 532) | pub struct EntityUpdate {
    method to_entity_state (line 576) | pub fn to_entity_state(&self, baseline: &EntityState) -> EntityState {
  type PlayerData (line 549) | pub struct PlayerData {
  type Cmd (line 598) | pub trait Cmd: Sized {
    method code (line 600) | fn code(&self) -> u8;
    method deserialize (line 603) | fn deserialize<R>(reader: &mut R) -> Result<Self, NetError>
    method serialize (line 608) | fn serialize<W>(&self, writer: &mut W) -> Result<(), NetError>
  type ServerCmdCode (line 615) | pub enum ServerCmdCode {
  type GameType (line 654) | pub enum GameType {
  type ServerCmd (line 660) | pub enum ServerCmd {
    method code (line 788) | pub fn code(&self) -> u8 {
    method deserialize (line 831) | pub fn deserialize<R>(reader: &mut R) -> Result<Option<ServerCmd>, Net...
    method serialize (line 1459) | pub fn serialize<W>(&self, writer: &mut W) -> Result<(), NetError>
  type ClientCmdCode (line 1844) | pub enum ClientCmdCode {
  type ClientCmd (line 1853) | pub enum ClientCmd {
    method code (line 1872) | pub fn code(&self) -> u8 {
    method deserialize (line 1882) | pub fn deserialize<R>(reader: &mut R) -> Result<ClientCmd, NetError>
    method serialize (line 1941) | pub fn serialize<W>(&self, writer: &mut W) -> Result<(), NetError>
  type BlockingMode (line 1979) | pub enum BlockingMode {
  type QSocket (line 1985) | pub struct QSocket {
    method new (line 2006) | pub fn new(socket: UdpSocket, remote: SocketAddr) -> QSocket {
    method can_send (line 2028) | pub fn can_send(&self) -> bool {
    method begin_send_msg (line 2033) | pub fn begin_send_msg(&mut self, msg: &[u8]) -> Result<(), NetError> {
    method resend_msg (line 2068) | pub fn resend_msg(&mut self) -> Result<(), NetError> {
    method send_msg_next (line 2080) | pub fn send_msg_next(&mut self) -> Result<(), NetError> {
    method send_msg_unreliable (line 2119) | pub fn send_msg_unreliable(&mut self, content: &[u8]) -> Result<(), Ne...
    method recv_msg (line 2153) | pub fn recv_msg(&mut self, block: BlockingMode) -> Result<Vec<u8>, Net...
  function read_coord (line 2314) | fn read_coord<R>(reader: &mut R) -> Result<f32, NetError>
  function read_coord_vector3 (line 2321) | fn read_coord_vector3<R>(reader: &mut R) -> Result<Vector3<f32>, NetError>
  function write_coord (line 2332) | fn write_coord<W>(writer: &mut W, coord: f32) -> Result<(), NetError>
  function write_coord_vector3 (line 2340) | fn write_coord_vector3<W>(writer: &mut W, coords: Vector3<f32>) -> Resul...
  function read_angle (line 2351) | fn read_angle<R>(reader: &mut R) -> Result<Deg<f32>, NetError>
  function read_angle_vector3 (line 2358) | fn read_angle_vector3<R>(reader: &mut R) -> Result<Vector3<Deg<f32>>, Ne...
  function write_angle (line 2369) | fn write_angle<W>(writer: &mut W, angle: Deg<f32>) -> Result<(), NetError>
  function write_angle_vector3 (line 2377) | fn write_angle_vector3<W>(writer: &mut W, angles: Vector3<Deg<f32>>) -> ...
  function test_server_cmd_update_stat_read_write_eq (line 2395) | fn test_server_cmd_update_stat_read_write_eq() {
  function test_server_cmd_version_read_write_eq (line 2410) | fn test_server_cmd_version_read_write_eq() {
  function test_server_cmd_set_view_read_write_eq (line 2422) | fn test_server_cmd_set_view_read_write_eq() {
  function test_server_cmd_time_read_write_eq (line 2434) | fn test_server_cmd_time_read_write_eq() {
  function test_server_cmd_print_read_write_eq (line 2446) | fn test_server_cmd_print_read_write_eq() {
  function test_server_cmd_stuff_text_read_write_eq (line 2460) | fn test_server_cmd_stuff_text_read_write_eq() {
  function test_server_cmd_server_info_read_write_eq (line 2474) | fn test_server_cmd_server_info_read_write_eq() {
  function test_server_cmd_light_style_read_write_eq (line 2493) | fn test_server_cmd_light_style_read_write_eq() {
  function test_server_cmd_update_name_read_write_eq (line 2508) | fn test_server_cmd_update_name_read_write_eq() {
  function test_server_cmd_update_frags_read_write_eq (line 2523) | fn test_server_cmd_update_frags_read_write_eq() {
  function test_server_cmd_stop_sound_read_write_eq (line 2538) | fn test_server_cmd_stop_sound_read_write_eq() {
  function test_server_cmd_update_colors_read_write_eq (line 2553) | fn test_server_cmd_update_colors_read_write_eq() {
  function test_server_cmd_set_pause_read_write_eq (line 2568) | fn test_server_cmd_set_pause_read_write_eq() {
  function test_server_cmd_sign_on_stage_read_write_eq (line 2579) | fn test_server_cmd_sign_on_stage_read_write_eq() {
  function test_server_cmd_center_print_read_write_eq (line 2592) | fn test_server_cmd_center_print_read_write_eq() {
  function test_server_cmd_finale_read_write_eq (line 2605) | fn test_server_cmd_finale_read_write_eq() {
  function test_server_cmd_cd_track_read_write_eq (line 2618) | fn test_server_cmd_cd_track_read_write_eq() {
  function test_server_cmd_cutscene_read_write_eq (line 2629) | fn test_server_cmd_cutscene_read_write_eq() {
  function test_client_cmd_string_cmd_read_write_eq (line 2642) | fn test_client_cmd_string_cmd_read_write_eq() {
  function test_client_cmd_move_read_write_eq (line 2655) | fn test_client_cmd_move_read_write_eq() {
  function gen_qsocket_pair (line 2675) | fn gen_qsocket_pair() -> (QSocket, QSocket) {
  function test_qsocket_send_msg_short (line 2689) | fn test_qsocket_send_msg_short() {
  function test_qsocket_send_msg_unreliable_recv_msg_eq (line 2701) | fn test_qsocket_send_msg_unreliable_recv_msg_eq() {
  function test_qsocket_send_msg_unreliable_zero_length_fails (line 2712) | fn test_qsocket_send_msg_unreliable_zero_length_fails() {
  function test_qsocket_send_msg_unreliable_exceeds_max_length_fails (line 2721) | fn test_qsocket_send_msg_unreliable_exceeds_max_length_fails() {

FILE: src/common/pak.rs
  constant PAK_MAGIC (line 30) | const PAK_MAGIC: [u8; 4] = [b'P', b'A', b'C', b'K'];
  constant PAK_ENTRY_SIZE (line 31) | const PAK_ENTRY_SIZE: usize = 64;
  type PakError (line 34) | pub enum PakError {
  type Pak (line 57) | pub struct Pak(HashMap<String, Box<[u8]>>);
    method new (line 61) | pub fn new<P>(path: P) -> Result<Pak, PakError>
    method open (line 137) | pub fn open<S>(&self, path: S) -> Result<&[u8], PakError>
    method iter (line 148) | pub fn iter<'a>(&self) -> Iter<String, impl AsRef<[u8]>> {

FILE: src/common/parse/console.rs
  function line_comment (line 34) | pub fn line_comment(input: &str) -> nom::IResult<&str, &str> {
  function empty_line (line 44) | pub fn empty_line(input: &str) -> nom::IResult<&str, &str> {
  function basic_arg_terminator (line 55) | pub fn basic_arg_terminator(input: &str) -> nom::IResult<&str, &str> {
  function basic_arg (line 61) | pub fn basic_arg(input: &str) -> nom::IResult<&str, &str> {
  function arg (line 98) | pub fn arg(input: &str) -> nom::IResult<&str, &str> {
  function command_terminator (line 107) | pub fn command_terminator(input: &str) -> nom::IResult<&str, &str> {
  function command (line 117) | pub fn command(input: &str) -> nom::IResult<&str, Vec<&str>> {
  function commands (line 121) | pub fn commands(input: &str) -> nom::IResult<&str, Vec<Vec<&str>>> {
  function test_line_comment (line 134) | fn test_line_comment() {
  function test_empty_line (line 140) | fn test_empty_line() {
  function test_basic_arg_space_terminated (line 146) | fn test_basic_arg_space_terminated() {
  function test_basic_arg_newline_terminated (line 152) | fn test_basic_arg_newline_terminated() {
  function test_basic_arg_semicolon_terminated (line 158) | fn test_basic_arg_semicolon_terminated() {
  function test_arg_basic (line 164) | fn test_arg_basic() {
  function test_quoted_arg (line 170) | fn test_quoted_arg() {
  function test_command_basic (line 176) | fn test_command_basic() {
  function test_command_quoted (line 182) | fn test_command_quoted() {
  function test_command_comment (line 188) | fn test_command_comment() {
  function test_commands_quake_rc (line 194) | fn test_commands_quake_rc() {

FILE: src/common/parse/map.rs
  function entity_attribute (line 31) | pub fn entity_attribute(input: &str) -> nom::IResult<&str, (&str, &str)> {
  function entity (line 40) | pub fn entity(input: &str) -> nom::IResult<&str, HashMap<&str, &str>> {
  function entities (line 48) | pub fn entities(input: &str) -> Result<Vec<HashMap<&str, &str>>, failure...

FILE: src/common/parse/mod.rs
  function non_newline_spaces (line 33) | pub fn non_newline_spaces(input: &str) -> nom::IResult<&str, &str> {
  function string_contents (line 37) | fn string_contents(input: &str) -> nom::IResult<&str, &str> {
  function quoted (line 41) | pub fn quoted(input: &str) -> nom::IResult<&str, &str> {
  function action (line 45) | pub fn action(input: &str) -> nom::IResult<&str, (ElementState, &str)> {
  function newline (line 56) | pub fn newline(input: &str) -> nom::IResult<&str, &str> {
  function line_ending (line 61) | pub fn line_ending(input: &str) -> nom::IResult<&str, &str> {
  function vector3_components (line 65) | pub fn vector3_components<S>(src: S) -> Option<[f32; 3]>
  function vector3 (line 94) | pub fn vector3<S>(src: S) -> Option<Vector3<f32>>
  function test_quoted (line 128) | fn test_quoted() {
  function test_action (line 134) | fn test_action() {

FILE: src/common/sprite.rs
  constant MAGIC (line 27) | const MAGIC: u32 = ('I' as u32) << 0 | ('D' as u32) << 8 | ('S' as u32) ...
  constant VERSION (line 28) | const VERSION: u32 = 1;
  type SpriteKind (line 31) | pub enum SpriteKind {
  type SpriteModel (line 40) | pub struct SpriteModel {
    method min (line 49) | pub fn min(&self) -> Vector3<f32> {
    method max (line 57) | pub fn max(&self) -> Vector3<f32> {
    method radius (line 65) | pub fn radius(&self) -> f32 {
    method kind (line 69) | pub fn kind(&self) -> SpriteKind {
    method frames (line 73) | pub fn frames(&self) -> &[SpriteFrame] {
  type SpriteFrame (line 79) | pub enum SpriteFrame {
  type SpriteSubframe (line 90) | pub struct SpriteSubframe {
    method width (line 101) | pub fn width(&self) -> u32 {
    method height (line 105) | pub fn height(&self) -> u32 {
    method indexed (line 109) | pub fn indexed(&self) -> &[u8] {
  function load (line 114) | pub fn load<R>(data: R) -> SpriteModel

FILE: src/common/util.rs
  type Pod (line 23) | pub trait Pod: 'static + Copy + Sized + Send + Sync {}
  function read_f32_3 (line 27) | pub fn read_f32_3<R>(reader: &mut R) -> Result<[f32; 3], std::io::Error>
  function read_cstring (line 42) | pub fn read_cstring<R>(src: &mut R) -> Result<String, std::string::FromU...
  function any_as_bytes (line 52) | pub unsafe fn any_as_bytes<T>(t: &T) -> &[u8]
  function any_slice_as_bytes (line 59) | pub unsafe fn any_slice_as_bytes<T>(t: &[T]) -> &[u8]
  function bytes_as_any (line 66) | pub unsafe fn bytes_as_any<T>(bytes: &[u8]) -> T
  function any_as_u32_slice (line 74) | pub unsafe fn any_as_u32_slice<T>(t: &T) -> &[u32]

FILE: src/common/vfs.rs
  type VfsError (line 29) | pub enum VfsError {
  type VfsComponent (line 37) | enum VfsComponent {
  type Vfs (line 43) | pub struct Vfs {
    method new (line 48) | pub fn new() -> Vfs {
    method with_base_dir (line 55) | pub fn with_base_dir(base_dir: PathBuf) -> Vfs {
    method add_pakfile (line 103) | pub fn add_pakfile<P>(&mut self, path: P) -> Result<(), VfsError>
    method add_directory (line 112) | pub fn add_directory<P>(&mut self, path: P) -> Result<(), VfsError>
    method open (line 121) | pub fn open<S>(&self, virtual_path: S) -> Result<VirtualFile, VfsError>
  type VirtualFile (line 151) | pub enum VirtualFile<'a> {
  method read (line 157) | fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
  method seek (line 166) | fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {

FILE: src/common/wad.rs
  constant LUMPINFO_SIZE (line 35) | const LUMPINFO_SIZE: usize = 32;
  constant MAGIC (line 36) | const MAGIC: u32 = 'W' as u32 | ('A' as u32) << 8 | ('D' as u32) << 16 |...
  type WadError (line 39) | pub struct WadError {
    method kind (line 44) | pub fn kind(&self) -> WadErrorKind {
    method from (line 50) | fn from(kind: WadErrorKind) -> Self {
    method from (line 58) | fn from(inner: Context<WadErrorKind>) -> Self {
    method from (line 64) | fn from(io_error: io::Error) -> Self {
  method cause (line 74) | fn cause(&self) -> Option<&dyn Fail> {
  method backtrace (line 78) | fn backtrace(&self) -> Option<&Backtrace> {
  method fmt (line 84) | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  type WadErrorKind (line 90) | pub enum WadErrorKind {
  type QPic (line 105) | pub struct QPic {
    method load (line 112) | pub fn load<R>(data: R) -> Result<QPic, WadError>
    method width (line 133) | pub fn width(&self) -> u32 {
    method height (line 137) | pub fn height(&self) -> u32 {
    method indices (line 141) | pub fn indices(&self) -> &[u8] {
  type LumpInfo (line 146) | struct LumpInfo {
  type Wad (line 152) | pub struct Wad {
    method load (line 157) | pub fn load<R>(data: R) -> Result<Wad, Error>
    method open_conchars (line 206) | pub fn open_conchars(&self) -> Result<QPic, Error> {
    method open_qpic (line 224) | pub fn open_qpic<S>(&self, name: S) -> Result<QPic, WadError>

FILE: src/server/mod.rs
  constant MAX_DATAGRAM (line 63) | const MAX_DATAGRAM: usize = 1024;
  constant MAX_LIGHTSTYLES (line 64) | const MAX_LIGHTSTYLES: usize = 64;
  type ClientState (line 67) | pub enum ClientState {
  type ClientActive (line 75) | pub struct ClientActive {
  type ClientSlots (line 96) | pub struct ClientSlots {
    method new (line 103) | pub fn new(limit: usize) -> ClientSlots {
    method get (line 114) | pub fn get(&self, id: usize) -> Option<&ClientState> {
    method limit (line 119) | pub fn limit(&self) -> usize {
    method find_available (line 124) | pub fn find_available(&mut self) -> Option<&mut ClientState> {
  type SessionPersistent (line 131) | pub struct SessionPersistent {
    method new (line 137) | pub fn new(max_clients: usize) -> SessionPersistent {
    method client (line 144) | pub fn client(&self, slot: usize) -> Option<&ClientState> {
  type SessionState (line 150) | pub enum SessionState {
  type SessionLoading (line 162) | pub struct SessionLoading {
    method new (line 167) | pub fn new(
    method precache_sound (line 183) | pub fn precache_sound(&mut self, name_id: StringId) {
    method precache_model (line 191) | pub fn precache_model(&mut self, name_id: StringId) {
    method finish (line 198) | pub fn finish(self) -> SessionActive {
  type SessionActive (line 204) | pub struct SessionActive {
  type Session (line 209) | pub struct Session {
    method new (line 215) | pub fn new(
    method max_clients (line 232) | pub fn max_clients(&self) -> usize {
    method client (line 237) | pub fn client(&self, slot: usize) -> Option<&ClientState> {
    method precache_sound (line 241) | pub fn precache_sound(&mut self, name_id: StringId) {
    method precache_model (line 249) | pub fn precache_model(&mut self, name_id: StringId) {
    method level (line 258) | fn level(&self) -> &LevelState {
    method level_mut (line 266) | fn level_mut(&mut self) -> &mut LevelState {
    method sound_id (line 274) | pub fn sound_id(&self, name_id: StringId) -> Option<usize> {
    method model_id (line 279) | pub fn model_id(&self, name_id: StringId) -> Option<usize> {
    method set_lightstyle (line 284) | pub fn set_lightstyle(&mut self, index: usize, val: StringId) {
    method time (line 290) | pub fn time(&self) -> Option<Duration> {
  type LevelState (line 300) | pub struct LevelState {
    method new (line 329) | pub fn new(
    method precache_sound (line 381) | pub fn precache_sound(&mut self, name_id: StringId) {
    method precache_model (line 389) | pub fn precache_model(&mut self, name_id: StringId) {
    method sound_id (line 397) | pub fn sound_id(&self, name_id: StringId) -> Option<usize> {
    method model_id (line 405) | pub fn model_id(&self, name_id: StringId) -> Option<usize> {
    method set_lightstyle (line 413) | pub fn set_lightstyle(&mut self, index: usize, val: StringId) {
    method execute_program (line 418) | pub fn execute_program(&mut self, f: FunctionId) -> Result<(), ProgsEr...
    method execute_program_by_name (line 630) | pub fn execute_program_by_name<S>(&mut self, name: S) -> Result<(), Pr...
    method link_entity (line 643) | pub fn link_entity(
    method spawn_entity (line 657) | pub fn spawn_entity(&mut self) -> Result<EntityId, ProgsError> {
    method spawn_entity_from_map (line 665) | pub fn spawn_entity_from_map(
    method set_entity_origin (line 689) | pub fn set_entity_origin(
    method set_entity_model (line 702) | pub fn set_entity_model(
    method think (line 730) | pub fn think(&mut self, ent_id: EntityId, frame_time: Duration) -> Res...
    method physics (line 753) | pub fn physics(
    method physics_player (line 820) | pub fn physics_player(
    method physics_push (line 839) | pub fn physics_push(
    method physics_noclip (line 884) | pub fn physics_noclip(
    method physics_step (line 909) | pub fn physics_step(
    method move_push (line 959) | pub fn move_push(
    constant MAX_BALLISTIC_COLLISIONS (line 982) | const MAX_BALLISTIC_COLLISIONS: usize = 4;
    method move_ballistic (line 985) | pub fn move_ballistic(
    constant DROP_TO_FLOOR_DIST (line 1118) | const DROP_TO_FLOOR_DIST: f32 = 256.0;
    method drop_entity_to_floor (line 1127) | pub fn drop_entity_to_floor(&mut self, ent_id: EntityId) -> Result<boo...
    method touch_triggers (line 1163) | pub fn touch_triggers(&mut self, ent_id: EntityId) -> Result<(), Progs...
    method impact_entities (line 1192) | pub fn impact_entities(&mut self, ent_a: EntityId, ent_b: EntityId) ->...
    method op_return (line 1225) | pub fn op_return(&mut self, a: i16, b: i16, c: i16) -> Result<(), Prog...
    method op_load_f (line 1242) | pub fn op_load_f(&mut self, e_ofs: i16, e_f: i16, dest_ofs: i16) -> Re...
    method op_load_v (line 1254) | pub fn op_load_v(
    method op_load_s (line 1268) | pub fn op_load_s(
    method op_load_ent (line 1285) | pub fn op_load_ent(
    method op_load_fnc (line 1302) | pub fn op_load_fnc(
    method op_address (line 1319) | pub fn op_address(
    method op_storep_f (line 1338) | pub fn op_storep_f(
    method op_storep_v (line 1359) | pub fn op_storep_v(
    method op_storep_s (line 1380) | pub fn op_storep_s(
    method op_storep_ent (line 1401) | pub fn op_storep_ent(
    method op_storep_fnc (line 1422) | pub fn op_storep_fnc(
    method op_state (line 1443) | pub fn op_state(
    method builtin_set_origin (line 1469) | pub fn builtin_set_origin(&mut self) -> Result<(), ProgsError> {
    method builtin_set_model (line 1477) | pub fn builtin_set_model(&mut self) -> Result<(), ProgsError> {
    method builtin_set_size (line 1485) | pub fn builtin_set_size(&mut self) -> Result<(), ProgsError> {
    method builtin_random (line 1495) | pub fn builtin_random(&mut self) -> Result<(), ProgsError> {
    method builtin_spawn (line 1502) | pub fn builtin_spawn(&mut self) -> Result<(), ProgsError> {
    method builtin_remove (line 1510) | pub fn builtin_remove(&mut self) -> Result<(), ProgsError> {
    method builtin_precache_sound (line 1517) | pub fn builtin_precache_sound(&mut self) -> Result<(), ProgsError> {
    method builtin_precache_model (line 1528) | pub fn builtin_precache_model(&mut self) -> Result<(), ProgsError> {
    method builtin_dprint (line 1543) | pub fn builtin_dprint(&mut self) -> Result<(), ProgsError> {
    method builtin_drop_to_floor (line 1552) | pub fn builtin_drop_to_floor(&mut self) -> Result<(), ProgsError> {
    method builtin_light_style (line 1561) | pub fn builtin_light_style(&mut self) -> Result<(), ProgsError> {
    method builtin_cvar (line 1572) | pub fn builtin_cvar(&mut self) -> Result<(), ProgsError> {
    method builtin_cvar_set (line 1582) | pub fn builtin_cvar_set(&mut self) -> Result<(), ProgsError> {
    method builtin_ambient_sound (line 1595) | pub fn builtin_ambient_sound(&mut self) -> Result<(), ProgsError> {

FILE: src/server/precache.rs
  constant MAX_PRECACHE_PATH (line 6) | const MAX_PRECACHE_PATH: usize = 64;
  constant MAX_PRECACHE_ENTRIES (line 8) | const MAX_PRECACHE_ENTRIES: usize = 256;
  type Precache (line 20) | pub struct Precache {
    method new (line 27) | pub fn new() -> Precache {
    method get (line 35) | pub fn get(&self, index: usize) -> Option<&str> {
    method find (line 45) | pub fn find<S>(&self, target: S) -> Option<usize>
    method precache (line 59) | pub fn precache<S>(&mut self, item: S)
    method iter (line 85) | pub fn iter(&self) -> impl Iterator<Item = &str> {
  function test_precache_one (line 98) | fn test_precache_one() {
  function test_precache_several (line 106) | fn test_precache_several() {

FILE: src/server/progs/functions.rs
  constant MAX_ARGS (line 24) | pub const MAX_ARGS: usize = 8;
  type Statement (line 28) | pub struct Statement {
    method new (line 36) | pub fn new(op: i16, arg1: i16, arg2: i16, arg3: i16) -> Result<Stateme...
  type FunctionId (line 53) | pub struct FunctionId(pub usize);
    type Error (line 56) | type Error = ProgsError;
    method try_into (line 58) | fn try_into(self) -> Result<i32, Self::Error> {
  type FunctionKind (line 68) | pub enum FunctionKind {
  type BuiltinFunctionId (line 74) | pub enum BuiltinFunctionId {
  type FunctionDef (line 151) | pub struct FunctionDef {
  type Functions (line 162) | pub struct Functions {
    method id_from_i32 (line 169) | pub fn id_from_i32(&self, value: i32) -> Result<FunctionId, ProgsError> {
    method get_def (line 184) | pub fn get_def(&self, id: FunctionId) -> Result<&FunctionDef, ProgsErr...
    method find_function_by_name (line 195) | pub fn find_function_by_name<S>(&self, name: S) -> Result<FunctionId, ...

FILE: src/server/progs/globals.rs
  constant GLOBAL_STATIC_START (line 27) | pub const GLOBAL_STATIC_START: usize = 28;
  constant GLOBAL_DYNAMIC_START (line 28) | pub const GLOBAL_DYNAMIC_START: usize = 64;
  constant GLOBAL_STATIC_COUNT (line 30) | pub const GLOBAL_STATIC_COUNT: usize = GLOBAL_DYNAMIC_START - GLOBAL_STA...
  constant GLOBAL_ADDR_NULL (line 33) | pub const GLOBAL_ADDR_NULL: usize = 0;
  constant GLOBAL_ADDR_RETURN (line 34) | pub const GLOBAL_ADDR_RETURN: usize = 1;
  constant GLOBAL_ADDR_ARG_0 (line 35) | pub const GLOBAL_ADDR_ARG_0: usize = 4;
  constant GLOBAL_ADDR_ARG_1 (line 36) | pub const GLOBAL_ADDR_ARG_1: usize = 7;
  constant GLOBAL_ADDR_ARG_2 (line 37) | pub const GLOBAL_ADDR_ARG_2: usize = 10;
  constant GLOBAL_ADDR_ARG_3 (line 38) | pub const GLOBAL_ADDR_ARG_3: usize = 13;
  constant GLOBAL_ADDR_ARG_4 (line 40) | pub const GLOBAL_ADDR_ARG_4: usize = 16;
  constant GLOBAL_ADDR_ARG_5 (line 42) | pub const GLOBAL_ADDR_ARG_5: usize = 19;
  constant GLOBAL_ADDR_ARG_6 (line 44) | pub const GLOBAL_ADDR_ARG_6: usize = 22;
  constant GLOBAL_ADDR_ARG_7 (line 46) | pub const GLOBAL_ADDR_ARG_7: usize = 25;
  type GlobalsError (line 49) | pub enum GlobalsError {
    method with_msg (line 56) | pub fn with_msg<S>(msg: S) -> Self
    method fmt (line 65) | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    method from (line 80) | fn from(error: ::std::io::Error) -> Self {
  type GlobalAddr (line 85) | pub trait GlobalAddr {
    method load (line 90) | fn load(&self, globals: &Globals) -> Result<Self::Value, GlobalsError>;
    method store (line 93) | fn store(&self, globals: &mut Globals, value: Self::Value) -> Result<(...
    type Value (line 149) | type Value = f32;
    method load (line 152) | fn load(&self, globals: &Globals) -> Result<Self::Value, GlobalsError> {
    method store (line 157) | fn store(&self, globals: &mut Globals, value: Self::Value) -> Result<(...
    type Value (line 172) | type Value = [f32; 3];
    method load (line 175) | fn load(&self, globals: &Globals) -> Result<Self::Value, GlobalsError> {
    method store (line 180) | fn store(&self, globals: &mut Globals, value: Self::Value) -> Result<(...
    type Value (line 200) | type Value = EntityId;
    method load (line 203) | fn load(&self, globals: &Globals) -> Result<Self::Value, GlobalsError> {
    method store (line 208) | fn store(&self, globals: &mut Globals, value: Self::Value) -> Result<(...
  type GlobalAddrFloat (line 97) | pub enum GlobalAddrFloat {
  type GlobalAddrVector (line 163) | pub enum GlobalAddrVector {
  type GlobalAddrString (line 186) | pub enum GlobalAddrString {
  type GlobalAddrEntity (line 191) | pub enum GlobalAddrEntity {
  type GlobalAddrField (line 214) | pub enum GlobalAddrField {}
  type GlobalAddrFunction (line 217) | pub enum GlobalAddrFunction {
  type Globals (line 231) | pub struct Globals {
    method new (line 239) | pub fn new(
    method type_check (line 256) | pub fn type_check(&self, addr: usize, type_: Type) -> Result<(), Globa...
    method get_addr (line 274) | pub fn get_addr(&self, addr: i16) -> Result<&[u8], GlobalsError> {
    method get_addr_mut (line 289) | pub fn get_addr_mut(&mut self, addr: i16) -> Result<&mut [u8], Globals...
    method get_bytes (line 304) | pub fn get_bytes(&self, addr: i16) -> Result<[u8; 4], GlobalsError> {
    method put_bytes (line 322) | pub fn put_bytes(&mut self, val: [u8; 4], addr: i16) -> Result<(), Glo...
    method get_int (line 338) | pub fn get_int(&self, addr: i16) -> Result<i32, GlobalsError> {
    method put_int (line 343) | pub fn put_int(&mut self, val: i32, addr: i16) -> Result<(), GlobalsEr...
    method get_float (line 349) | pub fn get_float(&self, addr: i16) -> Result<f32, GlobalsError> {
    method put_float (line 355) | pub fn put_float(&mut self, val: f32, addr: i16) -> Result<(), Globals...
    method get_vector (line 362) | pub fn get_vector(&self, addr: i16) -> Result<[f32; 3], GlobalsError> {
    method put_vector (line 375) | pub fn put_vector(&mut self, val: [f32; 3], addr: i16) -> Result<(), G...
    method string_id (line 386) | pub fn string_id(&self, addr: i16) -> Result<StringId, GlobalsError> {
    method put_string_id (line 395) | pub fn put_string_id(&mut self, val: StringId, addr: i16) -> Result<()...
    method entity_id (line 404) | pub fn entity_id(&self, addr: i16) -> Result<EntityId, GlobalsError> {
    method put_entity_id (line 417) | pub fn put_entity_id(&mut self, val: EntityId, addr: i16) -> Result<()...
    method get_field_addr (line 426) | pub fn get_field_addr(&self, addr: i16) -> Result<FieldAddr, GlobalsEr...
    method put_field_addr (line 439) | pub fn put_field_addr(&mut self, val: FieldAddr, addr: i16) -> Result<...
    method function_id (line 447) | pub fn function_id(&self, addr: i16) -> Result<FunctionId, GlobalsErro...
    method put_function_id (line 455) | pub fn put_function_id(&mut self, val: FunctionId, addr: i16) -> Resul...
    method get_entity_field (line 464) | pub fn get_entity_field(&self, addr: i16) -> Result<i32, GlobalsError> {
    method put_entity_field (line 468) | pub fn put_entity_field(&mut self, val: i32, addr: i16) -> Result<(), ...
    method load (line 473) | pub fn load<A: GlobalAddr>(&self, addr: A) -> Result<A::Value, Globals...
    method store (line 477) | pub fn store<A: GlobalAddr>(&mut self, addr: A, value: A::Value) -> Re...
    method untyped_copy (line 482) | pub fn untyped_copy(&mut self, src_addr: i16, dst_addr: i16) -> Result...
    method op_mul_f (line 495) | pub fn op_mul_f(&mut self, f1_id: i16, f2_id: i16, prod_id: i16) -> Re...
    method op_mul_v (line 504) | pub fn op_mul_v(&mut self, v1_id: i16, v2_id: i16, dot_id: i16) -> Res...
    method op_mul_fv (line 519) | pub fn op_mul_fv(&mut self, f_id: i16, v_id: i16, prod_id: i16) -> Res...
    method op_mul_vf (line 534) | pub fn op_mul_vf(&mut self, v_id: i16, f_id: i16, prod_id: i16) -> Res...
    method op_div (line 549) | pub fn op_div(&mut self, f1_id: i16, f2_id: i16, quot_id: i16) -> Resu...
    method op_add_f (line 558) | pub fn op_add_f(&mut self, f1_ofs: i16, f2_ofs: i16, sum_ofs: i16) -> ...
    method op_add_v (line 567) | pub fn op_add_v(&mut self, v1_id: i16, v2_id: i16, sum_id: i16) -> Res...
    method op_sub_f (line 582) | pub fn op_sub_f(&mut self, f1_id: i16, f2_id: i16, diff_id: i16) -> Re...
    method op_sub_v (line 591) | pub fn op_sub_v(&mut self, v1_id: i16, v2_id: i16, diff_id: i16) -> Re...
    method op_eq_f (line 606) | pub fn op_eq_f(&mut self, f1_id: i16, f2_id: i16, eq_id: i16) -> Resul...
    method op_eq_v (line 621) | pub fn op_eq_v(&mut self, v1_id: i16, v2_id: i16, eq_id: i16) -> Resul...
    method op_eq_s (line 636) | pub fn op_eq_s(&mut self, s1_ofs: i16, s2_ofs: i16, eq_ofs: i16) -> Re...
    method op_eq_ent (line 651) | pub fn op_eq_ent(&mut self, e1_ofs: i16, e2_ofs: i16, eq_ofs: i16) -> ...
    method op_eq_fnc (line 667) | pub fn op_eq_fnc(&mut self, f1_ofs: i16, f2_ofs: i16, eq_ofs: i16) -> ...
    method op_ne_f (line 683) | pub fn op_ne_f(&mut self, f1_ofs: i16, f2_ofs: i16, ne_ofs: i16) -> Re...
    method op_ne_v (line 698) | pub fn op_ne_v(&mut self, v1_ofs: i16, v2_ofs: i16, ne_ofs: i16) -> Re...
    method op_ne_s (line 713) | pub fn op_ne_s(&mut self, s1_ofs: i16, s2_ofs: i16, ne_ofs: i16) -> Re...
    method op_ne_ent (line 727) | pub fn op_ne_ent(&mut self, e1_ofs: i16, e2_ofs: i16, ne_ofs: i16) -> ...
    method op_ne_fnc (line 742) | pub fn op_ne_fnc(&mut self, f1_ofs: i16, f2_ofs: i16, ne_ofs: i16) -> ...
    method op_le (line 758) | pub fn op_le(&mut self, f1_ofs: i16, f2_ofs: i16, le_ofs: i16) -> Resu...
    method op_ge (line 773) | pub fn op_ge(&mut self, f1_ofs: i16, f2_ofs: i16, ge_ofs: i16) -> Resu...
    method op_lt (line 788) | pub fn op_lt(&mut self, f1_ofs: i16, f2_ofs: i16, lt_ofs: i16) -> Resu...
    method op_gt (line 803) | pub fn op_gt(&mut self, f1_ofs: i16, f2_ofs: i16, gt_ofs: i16) -> Resu...
    method op_store_f (line 818) | pub fn op_store_f(
    method op_store_v (line 835) | pub fn op_store_v(
    method op_store_s (line 863) | pub fn op_store_s(
    method op_store_ent (line 879) | pub fn op_store_ent(
    method op_store_fld (line 895) | pub fn op_store_fld(
    method op_store_fnc (line 911) | pub fn op_store_fnc(
    method op_not_f (line 928) | pub fn op_not_f(&mut self, f_id: i16, unused: i16, not_id: i16) -> Res...
    method op_not_v (line 946) | pub fn op_not_v(&mut self, v_id: i16, unused: i16, not_id: i16) -> Res...
    method op_not_s (line 965) | pub fn op_not_s(&mut self, s_ofs: i16, unused: i16, not_ofs: i16) -> R...
    method op_not_fnc (line 986) | pub fn op_not_fnc(
    method op_not_ent (line 1009) | pub fn op_not_ent(
    method op_and (line 1032) | pub fn op_and(&mut self, f1_id: i16, f2_id: i16, and_id: i16) -> Resul...
    method op_or (line 1047) | pub fn op_or(&mut self, f1_id: i16, f2_id: i16, or_id: i16) -> Result<...
    method op_bit_and (line 1062) | pub fn op_bit_and(
    method op_bit_or (line 1077) | pub fn op_bit_or(
    method builtin_random (line 1094) | pub fn builtin_random(&mut self) -> Result<(), GlobalsError> {
    method make_vectors (line 1106) | pub fn make_vectors(&mut self) -> Result<(), GlobalsError> {
    method builtin_v_len (line 1122) | pub fn builtin_v_len(&mut self) -> Result<(), GlobalsError> {
    method builtin_vec_to_yaw (line 1132) | pub fn builtin_vec_to_yaw(&mut self) -> Result<(), GlobalsError> {
    method builtin_r_int (line 1153) | pub fn builtin_r_int(&mut self) -> Result<(), GlobalsError> {
    method builtin_floor (line 1163) | pub fn builtin_floor(&mut self) -> Result<(), GlobalsError> {
    method builtin_ceil (line 1173) | pub fn builtin_ceil(&mut self) -> Result<(), GlobalsError> {
    method builtin_f_abs (line 1183) | pub fn builtin_f_abs(&mut self) -> Result<(), GlobalsError> {
  function make_vectors (line 1190) | pub fn make_vectors(angles: [f32; 3]) -> Matrix3<f32> {
  function test_make_vectors_no_rotation (line 1205) | fn test_make_vectors_no_rotation() {
  function test_make_vectors_pitch (line 1212) | fn test_make_vectors_pitch() {
  function test_make_vectors_yaw (line 1219) | fn test_make_vectors_yaw() {
  function test_make_vectors_roll (line 1226) | fn test_make_vectors_roll() {

FILE: src/server/progs/mod.rs
  constant VERSION (line 129) | const VERSION: i32 = 6;
  constant CRC (line 130) | const CRC: i32 = 5927;
  constant MAX_CALL_STACK_DEPTH (line 131) | const MAX_CALL_STACK_DEPTH: usize = 32;
  constant MAX_LOCAL_STACK_DEPTH (line 132) | const MAX_LOCAL_STACK_DEPTH: usize = 2048;
  constant LUMP_COUNT (line 133) | const LUMP_COUNT: usize = 6;
  constant SAVE_GLOBAL (line 134) | const SAVE_GLOBAL: u16 = 1 << 15;
  constant STATEMENT_SIZE (line 137) | const STATEMENT_SIZE: usize = 8;
  constant FUNCTION_SIZE (line 140) | const FUNCTION_SIZE: usize = 36;
  constant DEF_SIZE (line 143) | const DEF_SIZE: usize = 8;
  type ProgsError (line 146) | pub enum ProgsError {
    method with_msg (line 156) | pub fn with_msg<S>(msg: S) -> Self
    method fmt (line 165) | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    method from (line 190) | fn from(error: ::std::io::Error) -> Self {
    method from (line 196) | fn from(error: GlobalsError) -> Self {
    method from (line 202) | fn from(error: EntityError) -> Self {
  type StringId (line 209) | pub struct StringId(pub usize);
    type Error (line 212) | type Error = ProgsError;
    method try_into (line 214) | fn try_into(self) -> Result<i32, Self::Error> {
    method new (line 224) | pub fn new() -> StringId {
  type EntityId (line 231) | pub struct EntityId(pub usize);
  type FieldAddr (line 235) | pub struct FieldAddr(pub usize);
  type EntityFieldAddr (line 239) | pub struct EntityFieldAddr {
  type LumpId (line 244) | enum LumpId {
  type Type (line 255) | pub enum Type {
  type Lump (line 267) | struct Lump {
  type GlobalDef (line 273) | pub struct GlobalDef {
  type FieldDef (line 286) | pub struct FieldDef {
  type LoadProgs (line 293) | pub struct LoadProgs {
  function load (line 303) | pub fn load<R>(mut src: R) -> Result<LoadProgs, ProgsError>
  type StackFrame (line 533) | struct StackFrame {
  type ExecutionContext (line 540) | pub struct ExecutionContext {
    method create (line 550) | pub fn create(
    method call_stack_depth (line 564) | pub fn call_stack_depth(&self) -> usize {
    method find_function_by_name (line 568) | pub fn find_function_by_name<S: AsRef<str>>(
    method function_def (line 575) | pub fn function_def(&self, id: FunctionId) -> Result<&FunctionDef, Pro...
    method enter_function (line 579) | pub fn enter_function(
    method leave_function (line 631) | pub fn leave_function(&mut self, globals: &mut Globals) -> Result<(), ...
    method load_statement (line 653) | pub fn load_statement(&self) -> Statement {
    method jump_relative (line 658) | pub fn jump_relative(&mut self, rel: i16) {

FILE: src/server/progs/ops.rs
  type Opcode (line 20) | pub enum Opcode {

FILE: src/server/progs/string_table.rs
  type StringTable (line 6) | pub struct StringTable {
    method new (line 15) | pub fn new(data: Vec<u8>) -> StringTable {
    method id_from_i32 (line 22) | pub fn id_from_i32(&self, value: i32) -> Result<StringId, ProgsError> {
    method find (line 36) | pub fn find<S>(&self, target: S) -> Option<StringId>
    method get (line 61) | pub fn get(&self, id: StringId) -> Option<&str> {
    method insert (line 88) | pub fn insert<S>(&mut self, s: S) -> StringId
    method find_or_insert (line 102) | pub fn find_or_insert<S>(&mut self, target: S) -> StringId
    method iter (line 112) | pub fn iter(&self) -> impl Iterator<Item = &str> {

FILE: src/server/world/entity.rs
  constant MAX_ENT_LEAVES (line 35) | pub const MAX_ENT_LEAVES: usize = 16;
  constant STATIC_ADDRESS_COUNT (line 37) | pub const STATIC_ADDRESS_COUNT: usize = 105;
  type EntityError (line 40) | pub enum EntityError {
    method with_msg (line 47) | pub fn with_msg<S>(msg: S) -> Self
    method fmt (line 56) | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    method from (line 71) | fn from(error: ::std::io::Error) -> Self {
  type FieldAddr (line 77) | pub trait FieldAddr {
    method load (line 82) | fn load(&self, ent: &Entity) -> Result<Self::Value, EntityError>;
    method store (line 85) | fn store(&self, ent: &mut Entity, value: Self::Value) -> Result<(), En...
    type Value (line 194) | type Value = f32;
    method load (line 197) | fn load(&self, ent: &Entity) -> Result<Self::Value, EntityError> {
    method store (line 202) | fn store(&self, ent: &mut Entity, value: Self::Value) -> Result<(), En...
    type Value (line 226) | type Value = [f32; 3];
    method load (line 229) | fn load(&self, ent: &Entity) -> Result<Self::Value, EntityError> {
    method store (line 234) | fn store(&self, ent: &mut Entity, value: Self::Value) -> Result<(), En...
    type Value (line 255) | type Value = StringId;
    method load (line 257) | fn load(&self, ent: &Entity) -> Result<Self::Value, EntityError> {
    method store (line 262) | fn store(&self, ent: &mut Entity, value: Self::Value) -> Result<(), En...
    type Value (line 280) | type Value = EntityId;
    method load (line 282) | fn load(&self, ent: &Entity) -> Result<Self::Value, EntityError> {
    method store (line 286) | fn store(&self, ent: &mut Entity, value: Self::Value) -> Result<(), En...
    type Value (line 300) | type Value = FunctionId;
    method load (line 303) | fn load(&self, ent: &Entity) -> Result<Self::Value, EntityError> {
    method store (line 308) | fn store(&self, ent: &mut Entity, value: Self::Value) -> Result<(), En...
  type FieldAddrFloat (line 89) | pub enum FieldAddrFloat {
  type FieldAddrVector (line 208) | pub enum FieldAddrVector {
  type FieldAddrStringId (line 240) | pub enum FieldAddrStringId {
  type FieldAddrEntityId (line 268) | pub enum FieldAddrEntityId {
  type FieldAddrFunctionId (line 292) | pub enum FieldAddrFunctionId {
  function float_addr (line 333) | fn float_addr(addr: usize) -> Result<FieldAddrFloat, ProgsError> {
  function vector_addr (line 345) | fn vector_addr(addr: usize) -> Result<FieldAddrVector, ProgsError> {
  type FieldDefCacheEntry (line 356) | struct FieldDefCacheEntry {
  type EntityTypeDef (line 362) | pub struct EntityTypeDef {
    method new (line 371) | pub fn new(
    method addr_count (line 391) | pub fn addr_count(&self) -> usize {
    method field_defs (line 395) | pub fn field_defs(&self) -> &[FieldDef] {
    method find (line 400) | pub fn find<S>(&self, name: S) -> Option<&FieldDef>
  type EntitySolid (line 432) | pub enum EntitySolid {
  type Entity (line 441) | pub struct Entity {
    method new (line 452) | pub fn new(string_table: Rc<RefCell<StringTable>>, type_def: Rc<Entity...
    method type_check (line 468) | pub fn type_check(&self, addr: usize, type_: Type) -> Result<(), Entit...
    method field_def (line 493) | pub fn field_def<S>(&self, name: S) -> Option<&FieldDef>
    method get_addr (line 501) | pub fn get_addr(&self, addr: i16) -> Result<&[u8], EntityError> {
    method get_addr_mut (line 516) | pub fn get_addr_mut(&mut self, addr: i16) -> Result<&mut [u8], EntityE...
    method get_bytes (line 531) | pub fn get_bytes(&self, addr: i16) -> Result<[u8; 4], EntityError> {
    method put_bytes (line 549) | pub fn put_bytes(&mut self, val: [u8; 4], addr: i16) -> Result<(), Ent...
    method load (line 565) | pub fn load<F>(&self, field: F) -> Result<F::Value, EntityError>
    method store (line 573) | pub fn store<F>(&mut self, field: F, value: F::Value) -> Result<(), En...
    method get_int (line 581) | pub fn get_int(&self, addr: i16) -> Result<i32, EntityError> {
    method put_int (line 586) | pub fn put_int(&mut self, val: i32, addr: i16) -> Result<(), EntityErr...
    method get_float (line 592) | pub fn get_float(&self, addr: i16) -> Result<f32, EntityError> {
    method put_float (line 598) | pub fn put_float(&mut self, val: f32, addr: i16) -> Result<(), EntityE...
    method get_vector (line 605) | pub fn get_vector(&self, addr: i16) -> Result<[f32; 3], EntityError> {
    method put_vector (line 618) | pub fn put_vector(&mut self, val: [f32; 3], addr: i16) -> Result<(), E...
    method string_id (line 629) | pub fn string_id(&self, addr: i16) -> Result<StringId, EntityError> {
    method put_string_id (line 638) | pub fn put_string_id(&mut self, val: StringId, addr: i16) -> Result<()...
    method entity_id (line 647) | pub fn entity_id(&self, addr: i16) -> Result<EntityId, EntityError> {
    method put_entity_id (line 657) | pub fn put_entity_id(&mut self, val: EntityId, addr: i16) -> Result<()...
    method function_id (line 666) | pub fn function_id(&self, addr: i16) -> Result<FunctionId, EntityError> {
    method put_function_id (line 674) | pub fn put_function_id(&mut self, val: FunctionId, addr: i16) -> Resul...
    method set_min_max_size (line 682) | pub fn set_min_max_size<V>(&mut self, min: V, max: V) -> Result<(), En...
    method model_index (line 701) | pub fn model_index(&self) -> Result<usize, EntityError> {
    method abs_min (line 713) | pub fn abs_min(&self) -> Result<Vector3<f32>, EntityError> {
    method abs_max (line 717) | pub fn abs_max(&self) -> Result<Vector3<f32>, EntityError> {
    method solid (line 721) | pub fn solid(&self) -> Result<EntitySolid, EntityError> {
    method origin (line 732) | pub fn origin(&self) -> Result<Vector3<f32>, EntityError> {
    method min (line 736) | pub fn min(&self) -> Result<Vector3<f32>, EntityError> {
    method max (line 740) | pub fn max(&self) -> Result<Vector3<f32>, EntityError> {
    method size (line 744) | pub fn size(&self) -> Result<Vector3<f32>, EntityError> {
    method velocity (line 748) | pub fn velocity(&self) -> Result<Vector3<f32>, EntityError> {
    method apply_gravity (line 757) | pub fn apply_gravity(
    method limit_velocity (line 776) | pub fn limit_velocity(&mut self, sv_maxvelocity: f32) -> Result<(), En...
    method move_kind (line 786) | pub fn move_kind(&self) -> Result<MoveKind, EntityError> {
    method flags (line 798) | pub fn flags(&self) -> Result<EntityFlags, EntityError> {
    method add_flags (line 809) | pub fn add_flags(&mut self, flags: EntityFlags) -> Result<(), EntityEr...
    method owner (line 815) | pub fn owner(&self) -> Result<EntityId, EntityError> {

FILE: src/server/world/mod.rs
  constant AREA_DEPTH (line 57) | const AREA_DEPTH: usize = 4;
  constant NUM_AREA_NODES (line 58) | const NUM_AREA_NODES: usize = 2usize.pow(AREA_DEPTH as u32 + 1) - 1;
  constant MAX_ENTITIES (line 59) | const MAX_ENTITIES: usize = 600;
  type AreaNodeKind (line 62) | enum AreaNodeKind {
  type AreaNode (line 68) | struct AreaNode {
    method generate (line 106) | pub fn generate(mins: Vector3<f32>, maxs: Vector3<f32>) -> ArrayVec<Ar...
    method setup (line 144) | fn setup(
  type AreaBranchAxis (line 185) | enum AreaBranchAxis {
  type AreaBranch (line 191) | struct AreaBranch {
  type AreaEntity (line 199) | struct AreaEntity {
  type AreaEntitySlot (line 205) | enum AreaEntitySlot {
  type World (line 212) | pub struct World {
    method create (line 222) | pub fn create(
    method add_model (line 269) | pub fn add_model(&mut self, vfs: &Vfs, name_id: StringId) -> Result<()...
    method find_def (line 302) | fn find_def<S>(&self, name: S) -> Result<&FieldDef, ProgsError>
    method ent_fld_addr_to_i32 (line 322) | pub fn ent_fld_addr_to_i32(&self, ent_fld_addr: EntityFieldAddr) -> i32 {
    method ent_fld_addr_from_i32 (line 334) | pub fn ent_fld_addr_from_i32(&self, val: i32) -> EntityFieldAddr {
    method find_vacant_slot (line 350) | fn find_vacant_slot(&self) -> Result<usize, ()> {
    method alloc_uninitialized (line 360) | pub fn alloc_uninitialized(&mut self) -> Result<EntityId, ProgsError> {
    method alloc_from_map (line 384) | pub fn alloc_from_map(&mut self, map: HashMap<&str, &str>) -> Result<E...
    method free (line 460) | pub fn free(&mut self, entity_id: EntityId) -> Result<(), ProgsError> {
    method entity (line 485) | pub fn entity(&self, entity_id: EntityId) -> &Entity {
    method try_entity (line 492) | pub fn try_entity(&self, entity_id: EntityId) -> Result<&Entity, Progs...
    method entity_mut (line 509) | pub fn entity_mut(&mut self, entity_id: EntityId) -> Result<&mut Entit...
    method entity_exists (line 526) | pub fn entity_exists(&mut self, entity_id: EntityId) -> bool {
    method list_entities (line 533) | pub fn list_entities(&self, list: &mut Vec<EntityId>) {
    method area_entity (line 541) | fn area_entity(&self, entity_id: EntityId) -> Result<&AreaEntity, Prog...
    method area_entity_mut (line 558) | fn area_entity_mut(&mut self, entity_id: EntityId) -> Result<&mut Area...
    method list_touched_triggers (line 578) | pub fn list_touched_triggers(
    method unlink_entity (line 620) | pub fn unlink_entity(&mut self, e_id: EntityId) -> Result<(), ProgsErr...
    method link_entity (line 644) | pub fn link_entity(&mut self, e_id: EntityId) -> Result<(), ProgsError> {
    method set_entity_model (line 740) | pub fn set_entity_model(&mut self, e_id: EntityId, model_id: usize) ->...
    method set_entity_size (line 752) | pub fn set_entity_size(
    method remove_entity (line 764) | pub fn remove_entity(&mut self, e_id: EntityId) -> Result<(), ProgsErr...
    method hull_for_entity (line 771) | pub fn hull_for_entity(
    method move_entity (line 832) | pub fn move_entity(
    method collide (line 891) | pub fn collide(&self, collide: &Collide) -> Result<(Trace, Option<Enti...
    method collide_area (line 895) | fn collide_area(
    method collide_move_with_entity (line 1011) | pub fn collide_move_with_entity(

FILE: src/server/world/phys.rs
  constant STOP_THRESHOLD (line 32) | const STOP_THRESHOLD: f32 = 0.1;
  type MoveKind (line 35) | pub enum MoveKind {
  type CollideKind (line 53) | pub enum CollideKind {
  type Collide (line 60) | pub struct Collide {
  function velocity_after_collision (line 98) | pub fn velocity_after_collision(
  function velocity_after_multi_collision (line 124) | pub fn velocity_after_multi_collision(
  type TraceStart (line 166) | pub struct TraceStart {
    method new (line 174) | pub fn new(point: Vector3<f32>, ratio: f32) -> TraceStart {
  type TraceEndBoundary (line 181) | pub struct TraceEndBoundary {
  type TraceEndKind (line 188) | pub enum TraceEndKind {
  type TraceEnd (line 198) | pub struct TraceEnd {
    method terminal (line 204) | pub fn terminal(point: Vector3<f32>) -> TraceEnd {
    method boundary (line 211) | pub fn boundary(point: Vector3<f32>, ratio: f32, plane: Hyperplane) ->...
    method kind (line 218) | pub fn kind(&self) -> &TraceEndKind {
  type Trace (line 224) | pub struct Trace {
    method new (line 232) | pub fn new(start: TraceStart, end: TraceEnd, contents: BspLeafContents...
    method join (line 254) | pub fn join(self, other: Trace) -> Trace {
    method adjust (line 293) | pub fn adjust(self, offset: Vector3<f32>) -> Trace {
    method start_point (line 309) | pub fn start_point(&self) -> Vector3<f32> {
    method end (line 314) | pub fn end(&self) -> &TraceEnd {
    method end_point (line 319) | pub fn end_point(&self) -> Vector3<f32> {
    method all_solid (line 324) | pub fn all_solid(&self) -> bool {
    method start_solid (line 329) | pub fn start_solid(&self) -> bool {
    method in_open (line 333) | pub fn in_open(&self) -> bool {
    method in_water (line 337) | pub fn in_water(&self) -> bool {
    method is_terminal (line 342) | pub fn is_terminal(&self) -> bool {
    method ratio (line 354) | pub fn ratio(&self) -> f32 {
  function bounds_for_move (line 370) | pub fn bounds_for_move(
Condensed preview — 122 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,119K chars).
[
  {
    "path": ".github/workflows/ci.yml",
    "chars": 741,
    "preview": "name: Rust\n\non:\n  push:\n    branches: [ devel ]\n  pull_request:\n    branches: [ devel ]\n\nenv:\n  CARGO_TERM_COLOR: always"
  },
  {
    "path": ".gitignore",
    "chars": 59,
    "preview": "Cargo.lock\nsite/public\ntarget\n*.bak\n*.bk\n*.pak\n*.pak.d\n.#*\n"
  },
  {
    "path": ".rustfmt.toml",
    "chars": 56,
    "preview": "unstable_features = true\n\nimports_granularity = \"Crate\"\n"
  },
  {
    "path": "Cargo.toml",
    "chars": 906,
    "preview": "[package]\nname = \"richter\"\nversion = \"0.1.0\"\nauthors = [\"Cormac O'Brien <cormac@c-obrien.org>\"]\nedition = \"2018\"\n\n[depen"
  },
  {
    "path": "LICENSE.txt",
    "chars": 1057,
    "preview": "Copyright © 2017 Cormac O'Brien\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis so"
  },
  {
    "path": "README.md",
    "chars": 4073,
    "preview": "# Richter\n\n[![Build Status](https://travis-ci.org/cormac-obrien/richter.svg?branch=devel)](https://travis-ci.org/cormac-"
  },
  {
    "path": "shaders/alias.frag",
    "chars": 705,
    "preview": "#version 450\n\nlayout(location = 0) in vec3 f_normal;\nlayout(location = 1) in vec2 f_diffuse;\n\n// set 1: per-entity\nlayou"
  },
  {
    "path": "shaders/alias.vert",
    "chars": 672,
    "preview": "#version 450\n\nlayout(location = 0) in vec3 a_position1;\n// layout(location = 1) in vec3 a_position2;\nlayout(location = 2"
  },
  {
    "path": "shaders/blit.frag",
    "chars": 307,
    "preview": "#version 450\n\nlayout(location = 0) in vec2 f_texcoord;\n\nlayout(location = 0) out vec4 color_attachment;\n\nlayout(set = 0,"
  },
  {
    "path": "shaders/blit.vert",
    "chars": 239,
    "preview": "#version 450\n\nlayout(location = 0) in vec2 a_position;\nlayout(location = 1) in vec2 a_texcoord;\n\nlayout(location = 0) ou"
  },
  {
    "path": "shaders/brush.frag",
    "chars": 4055,
    "preview": "#version 450\n#define LIGHTMAP_ANIM_END (255)\n\nconst uint TEXTURE_KIND_REGULAR = 0;\nconst uint TEXTURE_KIND_WARP = 1;\ncon"
  },
  {
    "path": "shaders/brush.vert",
    "chars": 1550,
    "preview": "#version 450\n\nconst uint TEXTURE_KIND_NORMAL = 0;\nconst uint TEXTURE_KIND_WARP = 1;\nconst uint TEXTURE_KIND_SKY = 2;\n\nla"
  },
  {
    "path": "shaders/deferred.frag",
    "chars": 2222,
    "preview": "#version 450\n\n// if this is changed, it must also be changed in client::entity\nconst uint MAX_LIGHTS = 32;\n\nlayout(locat"
  },
  {
    "path": "shaders/deferred.vert",
    "chars": 239,
    "preview": "#version 450\n\nlayout(location = 0) in vec2 a_position;\nlayout(location = 1) in vec2 a_texcoord;\n\nlayout(location = 0) ou"
  },
  {
    "path": "shaders/glyph.frag",
    "chars": 493,
    "preview": "#version 450\n#extension GL_EXT_nonuniform_qualifier : require\n\nlayout(location = 0) in vec2 f_texcoord;\nlayout(location "
  },
  {
    "path": "shaders/glyph.vert",
    "chars": 514,
    "preview": "#version 450\n\n// vertex rate\nlayout(location = 0) in vec2 a_position;\nlayout(location = 1) in vec2 a_texcoord;\n\n// insta"
  },
  {
    "path": "shaders/particle.frag",
    "chars": 663,
    "preview": "#version 450\n\nlayout(location = 0) in vec2 f_texcoord;\n\nlayout(push_constant) uniform PushConstants {\n  layout(offset = "
  },
  {
    "path": "shaders/particle.vert",
    "chars": 332,
    "preview": "#version 450\n\nlayout(location = 0) in vec3 a_position;\nlayout(location = 1) in vec2 a_texcoord;\n\nlayout(push_constant) u"
  },
  {
    "path": "shaders/postprocess.frag",
    "chars": 770,
    "preview": "#version 450\n\nlayout(location = 0) in vec2 a_texcoord;\n\nlayout(location = 0) out vec4 color_attachment;\n\nlayout(set = 0,"
  },
  {
    "path": "shaders/postprocess.vert",
    "chars": 239,
    "preview": "#version 450\n\nlayout(location = 0) in vec2 a_position;\nlayout(location = 1) in vec2 a_texcoord;\n\nlayout(location = 0) ou"
  },
  {
    "path": "shaders/quad.frag",
    "chars": 397,
    "preview": "#version 450\n\nlayout(location = 0) in vec2 f_texcoord;\n\nlayout(location = 0) out vec4 color_attachment;\n\nlayout(set = 0,"
  },
  {
    "path": "shaders/quad.vert",
    "chars": 341,
    "preview": "#version 450\n\nlayout(location = 0) in vec2 a_position;\nlayout(location = 1) in vec2 a_texcoord;\n\nlayout(location = 0) ou"
  },
  {
    "path": "shaders/sprite.frag",
    "chars": 664,
    "preview": "#version 450\n\nlayout(location = 0) in vec3 f_normal;\nlayout(location = 1) in vec2 f_diffuse;\n\n// set 1: per-entity\nlayou"
  },
  {
    "path": "shaders/sprite.vert",
    "chars": 774,
    "preview": "#version 450\n\nlayout(location = 0) in vec3 a_position;\nlayout(location = 1) in vec3 a_normal;\nlayout(location = 2) in ve"
  },
  {
    "path": "site/config.toml",
    "chars": 502,
    "preview": "# The URL the site will be built for\nbase_url = \"http://c-obrien.org/richter\"\n\n# Whether to automatically compile all Sa"
  },
  {
    "path": "site/content/_index.md",
    "chars": 630,
    "preview": "+++\ntitle = \"Richter\"\ntemplate = \"index.html\"\ndescription = \"An open-source Quake engine written in Rust\"\ndate = 2018-04"
  },
  {
    "path": "site/content/blog/2018-04-24.md",
    "chars": 1394,
    "preview": "+++\ntitle = \"The New Site and the Way Forward\"\ntemplate = \"blog-post.html\"\ndate = 2018-04-24\n+++\n\nI've started rebuildin"
  },
  {
    "path": "site/content/blog/2018-04-26/index.md",
    "chars": 1103,
    "preview": "+++\ntitle = \"HUD Updates and Timing Bugs\"\ntemplate = \"blog-post.html\"\ndate = 2018-04-26\n+++\n\n![HUD Screenshot][1]\n\nThe H"
  },
  {
    "path": "site/content/blog/2018-05-12/index.md",
    "chars": 2212,
    "preview": "+++\ntitle = \"Shared Ownership of Rendering Resources\"\ntemplate = \"blog-post.html\"\ndate = 2018-05-12\n+++\n\nAmong the most "
  },
  {
    "path": "site/content/blog/2018-07-20/index.md",
    "chars": 1655,
    "preview": "+++\ntitle = \"Complications with Cross-Platform Input Handling\"\ntemplate = \"blog-post.html\"\ndate = 2018-07-20\n+++\n\nIt was"
  },
  {
    "path": "site/content/blog/_index.md",
    "chars": 173,
    "preview": "+++\ntitle = \"Blog\"\ntemplate = \"blog.html\"\ndescription = \"Richter project development log\"\nsort_by = \"date\"\n+++\n\n## Musin"
  },
  {
    "path": "site/content/index.html",
    "chars": 1842,
    "preview": "<!DOCTYPE html>\n<html>\n <head>\n  <meta charset=\"utf-8\">\n  <title>richter</title>\n  <link rel=\"stylesheet\" type=\"text/css"
  },
  {
    "path": "site/sass/_base.scss",
    "chars": 1913,
    "preview": "@import \"reset\";\n\n@font-face {\n    font-family: \"Renner\";\n    src: local('Renner*'), local('Renner-Book'),\n    url(\"font"
  },
  {
    "path": "site/sass/_reset.scss",
    "chars": 1093,
    "preview": "/* http://meyerweb.com/eric/tools/css/reset/ \n   v2.0 | 20110126\n   License: none (public domain)\n*/\n\nhtml, body, div, s"
  },
  {
    "path": "site/sass/blog-post.scss",
    "chars": 205,
    "preview": "@import \"base\";\n\nbody {\n    margin: 0 auto;\n    display: grid;\n\n    max-width: $main-content-width;\n\n    .date {\n       "
  },
  {
    "path": "site/sass/blog.scss",
    "chars": 550,
    "preview": "@import \"base\";\n\nbody {\n    // title of the page\n    h1 {\n        margin: 1rem auto;\n        text-align: center;\n       "
  },
  {
    "path": "site/sass/style.scss",
    "chars": 1345,
    "preview": "@import \"base\";\n\n$body-margin-top: 150px;\n\nbody {\n    margin: $body-margin-top auto;\n\n    padding: $body-margin-top / 2 "
  },
  {
    "path": "site/templates/home.html",
    "chars": 91,
    "preview": "<html>\n  <head>\n    <title>{% block title %}{% endblock title %}</title>\n  </head>\n</html>\n"
  },
  {
    "path": "site/themes/richter/templates/base.html",
    "chars": 978,
    "preview": "<!DOCTYPE html>\n<html lang=\"en-us\">\n  <head>\n    {% block head %}\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" c"
  },
  {
    "path": "site/themes/richter/templates/blog-post.html",
    "chars": 324,
    "preview": "{% extends \"base.html\" %}\n{% block style %}\n<link rel=\"stylesheet\" href=\"{{ get_url(path='blog-post.css', trailing_slash"
  },
  {
    "path": "site/themes/richter/templates/blog.html",
    "chars": 493,
    "preview": "{% extends \"base.html\" %}\n\n{% block style %}\n<link rel=\"stylesheet\" href=\"{{ get_url(path='blog.css', trailing_slash=fal"
  },
  {
    "path": "site/themes/richter/templates/index.html",
    "chars": 259,
    "preview": "{% extends \"base.html\" %}\n\n{% block title %}Home{% endblock title %}\n{% block style %}\n<link rel=\"stylesheet\" href=\"{{ g"
  },
  {
    "path": "site/themes/richter/theme.toml",
    "chars": 164,
    "preview": "name = \"richter\"\ndescription = \"theme for the Richter webpage\"\nlicense = \"MIT\"\nmin_version = \"0.2.2\"\n\n[author]\nname = \"M"
  },
  {
    "path": "specifications.md",
    "chars": 578,
    "preview": "# Specifications for the Original Quake (idTech 2) Engine\n\n### Coordinate Systems\n\nQuake's coordinate system specifies i"
  },
  {
    "path": "src/bin/quake-client/capture.rs",
    "chars": 3973,
    "preview": "use std::{\n    cell::RefCell,\n    fs::File,\n    io::BufWriter,\n    num::NonZeroU32,\n    path::{Path, PathBuf},\n    rc::R"
  },
  {
    "path": "src/bin/quake-client/game.rs",
    "chars": 6671,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n//"
  },
  {
    "path": "src/bin/quake-client/main.rs",
    "chars": 14246,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n//"
  },
  {
    "path": "src/bin/quake-client/menu.rs",
    "chars": 4899,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n//"
  },
  {
    "path": "src/bin/quake-client/trace.rs",
    "chars": 2040,
    "preview": "use std::{cell::RefCell, io::BufWriter, rc::Rc, fs::File};\n\nuse richter::{client::trace::TraceFrame, common::console::Cv"
  },
  {
    "path": "src/bin/unpak.rs",
    "chars": 3004,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of"
  },
  {
    "path": "src/client/cvars.rs",
    "chars": 2951,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n//"
  },
  {
    "path": "src/client/demo.rs",
    "chars": 4945,
    "preview": "use std::{io, ops::Range};\n\nuse crate::common::{\n    net::{self, NetError},\n    util::read_f32_3,\n    vfs::VirtualFile,\n"
  },
  {
    "path": "src/client/entity/mod.rs",
    "chars": 10242,
    "preview": "// Copyright © 2020 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n//"
  },
  {
    "path": "src/client/entity/particle.rs",
    "chars": 24320,
    "preview": "// Copyright © 2020 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n//"
  },
  {
    "path": "src/client/input/console.rs",
    "chars": 2600,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of"
  },
  {
    "path": "src/client/input/game.rs",
    "chars": 21706,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of"
  },
  {
    "path": "src/client/input/menu.rs",
    "chars": 2870,
    "preview": "// Copyright © 2019 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of"
  },
  {
    "path": "src/client/input/mod.rs",
    "chars": 3920,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of"
  },
  {
    "path": "src/client/menu/item.rs",
    "chars": 9988,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n//"
  },
  {
    "path": "src/client/menu/mod.rs",
    "chars": 13431,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n//"
  },
  {
    "path": "src/client/mod.rs",
    "chars": 51572,
    "preview": "// Copyright © 2020 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n//"
  },
  {
    "path": "src/client/render/atlas.rs",
    "chars": 12873,
    "preview": "use std::{cmp::Ordering, mem::size_of};\n\nuse crate::client::render::Palette;\n\nuse failure::Error;\n\nconst DEFAULT_ATLAS_D"
  },
  {
    "path": "src/client/render/blit.rs",
    "chars": 5076,
    "preview": "use crate::client::render::{pipeline::Pipeline, ui::quad::QuadPipeline, GraphicsState};\n\npub struct BlitPipeline {\n    p"
  },
  {
    "path": "src/client/render/cvars.rs",
    "chars": 1303,
    "preview": "// Copyright © 2020 Cormac O'Brien.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n/"
  },
  {
    "path": "src/client/render/error.rs",
    "chars": 1695,
    "preview": "use crate::common::{\n    vfs::VfsError,\n    wad::WadError,\n};\nuse failure::{Backtrace, Context, Fail};\nuse std::{\n    co"
  },
  {
    "path": "src/client/render/mod.rs",
    "chars": 29348,
    "preview": "// Copyright © 2020 Cormac O'Brien.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n/"
  },
  {
    "path": "src/client/render/palette.rs",
    "chars": 2146,
    "preview": "use std::{borrow::Cow, io::BufReader};\n\nuse crate::{\n    client::render::{DiffuseData, FullbrightData},\n    common::vfs:"
  },
  {
    "path": "src/client/render/pipeline.rs",
    "chars": 14585,
    "preview": "// Copyright © 2020 Cormac O'Brien.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n/"
  },
  {
    "path": "src/client/render/target.rs",
    "chars": 13406,
    "preview": "// Copyright © 2020 Cormac O'Brien.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n/"
  },
  {
    "path": "src/client/render/ui/console.rs",
    "chars": 4268,
    "preview": "use crate::{\n    client::render::{\n        ui::{\n            glyph::{GlyphRendererCommand, GLYPH_HEIGHT, GLYPH_WIDTH},\n "
  },
  {
    "path": "src/client/render/ui/glyph.rs",
    "chars": 12381,
    "preview": "use std::{mem::size_of, num::NonZeroU32};\n\nuse crate::{\n    client::render::{\n        ui::{\n            layout::{Anchor,"
  },
  {
    "path": "src/client/render/ui/hud.rs",
    "chars": 23726,
    "preview": "use std::{collections::HashMap, iter::FromIterator};\n\nuse crate::{\n    client::{\n        render::{\n            ui::{\n   "
  },
  {
    "path": "src/client/render/ui/layout.rs",
    "chars": 6233,
    "preview": "#[derive(Clone, Copy, Debug)]\npub struct Layout {\n    /// The position of the quad on the screen.\n    pub position: Scre"
  },
  {
    "path": "src/client/render/ui/menu.rs",
    "chars": 9468,
    "preview": "use std::collections::HashMap;\n\nuse crate::{\n    client::{\n        menu::{Item, Menu, MenuBodyView, MenuState, NamedMenu"
  },
  {
    "path": "src/client/render/ui/mod.rs",
    "chars": 4420,
    "preview": "pub mod console;\npub mod glyph;\npub mod hud;\npub mod layout;\npub mod menu;\npub mod quad;\n\nuse std::cell::RefCell;\n\nuse c"
  },
  {
    "path": "src/client/render/ui/quad.rs",
    "chars": 13785,
    "preview": "use std::{\n    cell::{Ref, RefCell, RefMut},\n    mem::size_of,\n    num::NonZeroU64,\n};\n\nuse crate::{\n    client::render:"
  },
  {
    "path": "src/client/render/uniform.rs",
    "chars": 5758,
    "preview": "use std::{\n    cell::{Cell, RefCell},\n    marker::PhantomData,\n    mem::{align_of, size_of},\n    rc::Rc,\n};\n\nuse crate::"
  },
  {
    "path": "src/client/render/warp.rs",
    "chars": 3160,
    "preview": "use std::cmp::Ordering;\n\nuse crate::common::math;\n\nuse cgmath::{InnerSpace, Vector2, Vector3};\n\n// TODO: make this a cva"
  },
  {
    "path": "src/client/render/world/alias.rs",
    "chars": 15616,
    "preview": "use std::{mem::size_of, ops::Range};\n\nuse crate::{\n    client::render::{\n        world::{BindGroupLayoutId, WorldPipelin"
  },
  {
    "path": "src/client/render/world/brush.rs",
    "chars": 26880,
    "preview": "// Copyright © 2020 Cormac O'Brien.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n/"
  },
  {
    "path": "src/client/render/world/deferred.rs",
    "chars": 9769,
    "preview": "use std::{mem::size_of, num::NonZeroU64};\n\nuse cgmath::{Matrix4, SquareMatrix as _, Vector3, Zero as _};\n\nuse crate::{\n "
  },
  {
    "path": "src/client/render/world/mod.rs",
    "chars": 20466,
    "preview": "pub mod alias;\npub mod brush;\npub mod deferred;\npub mod particle;\npub mod postprocess;\npub mod sprite;\n\nuse std::{cell::"
  },
  {
    "path": "src/client/render/world/particle.rs",
    "chars": 10223,
    "preview": "use std::{\n    mem::size_of,\n    num::{NonZeroU32, NonZeroU8},\n};\n\nuse crate::{\n    client::{\n        entity::particle::"
  },
  {
    "path": "src/client/render/world/postprocess.rs",
    "chars": 6902,
    "preview": "use std::{mem::size_of, num::NonZeroU64};\n\nuse crate::{\n    client::render::{pipeline::Pipeline, ui::quad::QuadPipeline,"
  },
  {
    "path": "src/client/render/world/sprite.rs",
    "chars": 10386,
    "preview": "use std::mem::size_of;\n\nuse crate::{\n    client::render::{\n        world::{BindGroupLayoutId, WorldPipelineBase},\n      "
  },
  {
    "path": "src/client/sound/mod.rs",
    "chars": 10272,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n//"
  },
  {
    "path": "src/client/sound/music.rs",
    "chars": 3618,
    "preview": "use std::{\n    io::{Cursor, Read},\n    rc::Rc,\n};\n\nuse crate::{client::sound::SoundError, common::vfs::Vfs};\n\nuse rodio:"
  },
  {
    "path": "src/client/state.rs",
    "chars": 47600,
    "preview": "use std::{cell::RefCell, collections::HashMap, rc::Rc};\n\nuse super::view::BobVars;\nuse crate::{\n    client::{\n        en"
  },
  {
    "path": "src/client/trace.rs",
    "chars": 1520,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n//"
  },
  {
    "path": "src/client/view.rs",
    "chars": 9053,
    "preview": "use std::f32::consts::PI;\n\nuse crate::{\n    client::input::game::{Action, GameInput},\n    common::{\n        engine::{dur"
  },
  {
    "path": "src/common/alloc.rs",
    "chars": 5276,
    "preview": "use std::{collections::LinkedList, mem};\n\nuse slab::Slab;\n\n/// A slab allocator with a linked list of allocations.\n///\n/"
  },
  {
    "path": "src/common/bitset.rs",
    "chars": 3560,
    "preview": "pub struct BitSet<const N_64: usize> {\n    blocks: [u64; N_64],\n}\n\nimpl<const N_64: usize> BitSet<N_64> {\n    pub fn new"
  },
  {
    "path": "src/common/bsp/load.rs",
    "chars": 37910,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of"
  },
  {
    "path": "src/common/bsp/mod.rs",
    "chars": 33648,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of"
  },
  {
    "path": "src/common/console/mod.rs",
    "chars": 24697,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n//"
  },
  {
    "path": "src/common/engine.rs",
    "chars": 2668,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of"
  },
  {
    "path": "src/common/host.rs",
    "chars": 4366,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n//"
  },
  {
    "path": "src/common/math.rs",
    "chars": 29658,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n//"
  },
  {
    "path": "src/common/mdl.rs",
    "chars": 18008,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of"
  },
  {
    "path": "src/common/mod.rs",
    "chars": 1871,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n//"
  },
  {
    "path": "src/common/model.rs",
    "chars": 6336,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of"
  },
  {
    "path": "src/common/net/connect.rs",
    "chars": 24154,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n//"
  },
  {
    "path": "src/common/net/mod.rs",
    "chars": 85692,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n//"
  },
  {
    "path": "src/common/pak.rs",
    "chars": 5099,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of"
  },
  {
    "path": "src/common/parse/console.rs",
    "chars": 7141,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of"
  },
  {
    "path": "src/common/parse/map.rs",
    "chars": 2175,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of"
  },
  {
    "path": "src/common/parse/mod.rs",
    "chars": 3861,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of"
  },
  {
    "path": "src/common/sprite.rs",
    "chars": 8249,
    "preview": "// Copyright © 2018 Cormac O'Brien.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy o"
  },
  {
    "path": "src/common/util.rs",
    "chars": 2737,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of"
  },
  {
    "path": "src/common/vfs.rs",
    "chars": 5181,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of"
  },
  {
    "path": "src/common/wad.rs",
    "chars": 6843,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n//"
  },
  {
    "path": "src/lib.rs",
    "chars": 1571,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of"
  },
  {
    "path": "src/server/mod.rs",
    "chars": 55748,
    "preview": "// Copyright © 2018 Cormac O'Brien.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy o"
  },
  {
    "path": "src/server/precache.rs",
    "chars": 3305,
    "preview": "use std::ops::Range;\n\nuse arrayvec::{ArrayString, ArrayVec};\n\n/// Maximum permitted length of a precache path.\nconst MAX"
  },
  {
    "path": "src/server/progs/functions.rs",
    "chars": 5796,
    "preview": "// Copyright © 2018 Cormac O'Brien.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy o"
  },
  {
    "path": "src/server/progs/globals.rs",
    "chars": 35572,
    "preview": "// Copyright © 2018 Cormac O'Brien.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy o"
  },
  {
    "path": "src/server/progs/mod.rs",
    "chars": 19296,
    "preview": "// Copyright © 2018 Cormac O'Brien.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy o"
  },
  {
    "path": "src/server/progs/ops.rs",
    "chars": 2235,
    "preview": "// Copyright © 2018 Cormac O'Brien.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy o"
  },
  {
    "path": "src/server/progs/string_table.rs",
    "chars": 2980,
    "preview": "use std::{cell::RefCell, collections::HashMap};\n\nuse crate::server::progs::{ProgsError, StringId};\n\n#[derive(Debug)]\npub"
  },
  {
    "path": "src/server/world/entity.rs",
    "chars": 24146,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of"
  },
  {
    "path": "src/server/world/mod.rs",
    "chars": 34487,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of"
  },
  {
    "path": "src/server/world/phys.rs",
    "chars": 11721,
    "preview": "// Copyright © 2018 Cormac O'Brien\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of"
  }
]

About this extraction

This page contains the full source code of the cormac-obrien/richter GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 122 files (1.0 MB), approximately 255.4k tokens, and a symbol index with 1986 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!